diff --git a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService+DataProvider.swift b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService+DataProvider.swift index e53352f09d..75fc4b0a72 100644 --- a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService+DataProvider.swift +++ b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService+DataProvider.swift @@ -45,7 +45,7 @@ extension ConvertService { ), InMemoryDataProvider( files: files, - fallbackFileManager: FileManager.default + fallback: FileManager.default ) ) } diff --git a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift index 6b93e1f9a9..4904fa6ac4 100644 --- a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift +++ b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift @@ -138,7 +138,7 @@ public struct ConvertService: DocumentationService { } let bundle: DocumentationBundle - let dataProvider: DocumentationBundleDataProvider + let dataProvider: DataProvider let inputProvider = DocumentationContext.InputsProvider() if let bundleLocation = request.bundleLocation, @@ -267,7 +267,7 @@ public struct ConvertService: DocumentationService { .compactMap { (value, isDocumentationExtensionContent) -> (ResolvedTopicReference, RenderReferenceStore.TopicContent)? in let (topicReference, article) = value - guard let bundle = context.bundle(identifier: topicReference.bundleIdentifier) else { return nil } + guard let bundle = context.bundle, bundle.identifier == topicReference.bundleIdentifier else { return nil } let renderer = DocumentationContentRenderer(documentationContext: context, bundle: bundle) let documentationNodeKind: DocumentationNode.Kind = isDocumentationExtensionContent ? .unknownSymbol : .article diff --git a/Sources/SwiftDocC/Infrastructure/Context/DocumentationContext+Configuration.swift b/Sources/SwiftDocC/Infrastructure/Context/DocumentationContext+Configuration.swift index b4dd0cf40d..19fb6a78a0 100644 --- a/Sources/SwiftDocC/Infrastructure/Context/DocumentationContext+Configuration.swift +++ b/Sources/SwiftDocC/Infrastructure/Context/DocumentationContext+Configuration.swift @@ -94,5 +94,15 @@ extension DocumentationContext { /// Controls whether the context stores the set of references that are manually curated. package var shouldStoreManuallyCuratedReferences: Bool = false } + + // MARK: Topic analysis + + /// Configuration related to topic analysis. + var topicAnalysisConfiguration = TopicAnalysisConfiguration() + + /// A collection of configuration related to topic analysis. + struct TopicAnalysisConfiguration { + var additionalChecks: [DocumentationContext.ReferenceCheck] = [] + } } } diff --git a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift index 166b0e684c..7820427b56 100644 --- a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift +++ b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift @@ -69,8 +69,12 @@ package enum ConvertActionConverter { // An inner function to gather problems for errors encountered during the conversion. // - // These problems only represent unexpected thrown errors and aren't particularly user-facing but because - // `DocumentationConverter.convert(outputConsumer:)` emits them as diagnostics we do the same here. + // These problems only represent unexpected thrown errors and aren't particularly user-facing. + // For now we emit them as diagnostics because `DocumentationConverter.convert(outputConsumer:)` (which this replaced) used to do that. + // + // FIXME: In the future we could simplify this control flow by not catching these errors and turning them into diagnostics. + // Since both error-level diagnostics and thrown errors fail the documentation build, + // the only practical different this would have is that we stop on the first unexpected error instead of processing all pages and gathering all unexpected errors. func recordProblem(from error: Swift.Error, in problems: inout [Problem], withIdentifier identifier: String) { let problem = Problem(diagnostic: Diagnostic( severity: .error, diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift index 5ca6e5e95e..940f04155c 100644 --- a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift @@ -33,7 +33,7 @@ public final class DiagnosticConsoleWriter: DiagnosticFormattingConsumer { baseURL: URL? = nil, highlight: Bool? = nil ) { - self.init(stream, formattingOptions: options, baseURL: baseURL, highlight: highlight, fileManager: FileManager.default) + self.init(stream, formattingOptions: options, baseURL: baseURL, highlight: highlight, dataProvider: FileManager.default) } package init( @@ -41,7 +41,7 @@ public final class DiagnosticConsoleWriter: DiagnosticFormattingConsumer { formattingOptions options: DiagnosticFormattingOptions = [], baseURL: URL? = nil, highlight: Bool? = nil, - fileManager: FileManagerProtocol = FileManager.default + dataProvider: DataProvider = FileManager.default ) { outputStream = stream formattingOptions = options @@ -49,7 +49,7 @@ public final class DiagnosticConsoleWriter: DiagnosticFormattingConsumer { options, baseURL: baseURL, highlight: highlight ?? TerminalHelper.isConnectedToTerminal, - fileManager: fileManager + dataProvider: dataProvider ) } @@ -79,12 +79,12 @@ public final class DiagnosticConsoleWriter: DiagnosticFormattingConsumer { _ options: DiagnosticFormattingOptions, baseURL: URL?, highlight: Bool, - fileManager: FileManagerProtocol + dataProvider: DataProvider ) -> DiagnosticConsoleFormatter { if options.contains(.formatConsoleOutputForTools) { return IDEDiagnosticConsoleFormatter(options: options) } else { - return DefaultDiagnosticConsoleFormatter(baseUrl: baseURL, highlight: highlight, options: options, fileManager: fileManager) + return DefaultDiagnosticConsoleFormatter(baseUrl: baseURL, highlight: highlight, options: options, dataProvider: dataProvider) } } } @@ -103,7 +103,7 @@ extension DiagnosticConsoleWriter { formattedDescription(for: problem, options: options, fileManager: FileManager.default) } package static func formattedDescription(for problem: Problem, options: DiagnosticFormattingOptions = [], fileManager: FileManagerProtocol = FileManager.default) -> String { - let diagnosticFormatter = makeDiagnosticFormatter(options, baseURL: nil, highlight: TerminalHelper.isConnectedToTerminal, fileManager: fileManager) + let diagnosticFormatter = makeDiagnosticFormatter(options, baseURL: nil, highlight: TerminalHelper.isConnectedToTerminal, dataProvider: fileManager) return diagnosticFormatter.formattedDescription(for: problem) } @@ -111,7 +111,7 @@ extension DiagnosticConsoleWriter { formattedDescription(for: diagnostic, options: options, fileManager: FileManager.default) } package static func formattedDescription(for diagnostic: Diagnostic, options: DiagnosticFormattingOptions = [], fileManager: FileManagerProtocol) -> String { - let diagnosticFormatter = makeDiagnosticFormatter(options, baseURL: nil, highlight: TerminalHelper.isConnectedToTerminal, fileManager: fileManager) + let diagnosticFormatter = makeDiagnosticFormatter(options, baseURL: nil, highlight: TerminalHelper.isConnectedToTerminal, dataProvider: fileManager) return diagnosticFormatter.formattedDescription(for: diagnostic) } } @@ -220,7 +220,7 @@ final class DefaultDiagnosticConsoleFormatter: DiagnosticConsoleFormatter { private let baseUrl: URL? private let highlight: Bool private var sourceLines: [URL: [String]] = [:] - private var fileManager: FileManagerProtocol + private var dataProvider: DataProvider /// The number of additional lines from the source file that should be displayed both before and after the diagnostic source line. private static let contextSize = 2 @@ -229,12 +229,12 @@ final class DefaultDiagnosticConsoleFormatter: DiagnosticConsoleFormatter { baseUrl: URL?, highlight: Bool, options: DiagnosticFormattingOptions, - fileManager: FileManagerProtocol + dataProvider: DataProvider ) { self.baseUrl = baseUrl self.highlight = highlight self.options = options - self.fileManager = fileManager + self.dataProvider = dataProvider } func formattedDescription(for problems: some Sequence) -> String { @@ -507,7 +507,7 @@ extension DefaultDiagnosticConsoleFormatter { } // TODO: Add support for also getting the source lines from the symbol graph files. - guard let data = fileManager.contents(atPath: url.path), + guard let data = try? dataProvider.contents(of: url), let content = String(data: data, encoding: .utf8) else { return [] diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 42f77f1714..dc1eced4b0 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -13,6 +13,7 @@ import Markdown import SymbolKit /// A type that provides information about documentation bundles and their content. +@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") public protocol DocumentationContextDataProvider { /// An object to notify when bundles are added or removed. var delegate: DocumentationContextDataProviderDelegate? { get set } @@ -31,6 +32,7 @@ public protocol DocumentationContextDataProvider { } /// An object that responds to changes in available documentation bundles for a specific provider. +@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") public protocol DocumentationContextDataProviderDelegate: AnyObject { /// Called when the `dataProvider` has added a new documentation bundle to its list of `bundles`. @@ -79,7 +81,7 @@ public typealias BundleIdentifier = String /// - ``children(of:kind:)`` /// - ``parents(of:)`` /// -public class DocumentationContext: DocumentationContextDataProviderDelegate { +public class DocumentationContext { /// An error that's encountered while interacting with a ``SwiftDocC/DocumentationContext``. public enum ContextError: DescribedError { @@ -111,16 +113,18 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { } /// A class that resolves documentation links by orchestrating calls to other link resolver implementations. - public var linkResolver = LinkResolver() + public var linkResolver: LinkResolver private enum _Provider { + @available(*, deprecated, message: "Use 'DataProvider' instead. This deprecated API will be removed after 6.2 is released") case legacy(DocumentationContextDataProvider) - case new(DocumentationBundleDataProvider) + case new(DataProvider) } private var dataProvider: _Provider /// The provider of documentation bundles for this context. - var _legacyDataProvider: DocumentationContextDataProvider! { + @available(*, deprecated, message: "Use 'DataProvider' instead. This deprecated API will be removed after 6.2 is released") + private var _legacyDataProvider: DocumentationContextDataProvider! { get { switch dataProvider { case .legacy(let legacyDataProvider): @@ -144,6 +148,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { } } + /// The documentation bundle that is registered with the context. var bundle: DocumentationBundle? /// A collection of configuration for this context. @@ -285,6 +290,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { /// - diagnosticEngine: The pre-configured engine that will collect problems encountered during compilation. /// - configuration: A collection of configuration for the created context. /// - Throws: If an error is encountered while registering a documentation bundle. + @available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") public init( dataProvider: DocumentationContextDataProvider, diagnosticEngine: DiagnosticEngine = .init(), @@ -293,6 +299,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { self.dataProvider = .legacy(dataProvider) self.diagnosticEngine = diagnosticEngine self._configuration = configuration + self.linkResolver = LinkResolver(dataProvider: FileManager.default) _legacyDataProvider.delegate = self @@ -311,7 +318,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { /// - Throws: If an error is encountered while registering a documentation bundle. package init( bundle: DocumentationBundle, - dataProvider: DocumentationBundleDataProvider, + dataProvider: DataProvider, diagnosticEngine: DiagnosticEngine = .init(), configuration: Configuration = .init() ) throws { @@ -319,6 +326,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { self.dataProvider = .new(dataProvider) self.diagnosticEngine = diagnosticEngine self._configuration = configuration + self.linkResolver = LinkResolver(dataProvider: dataProvider) ResolvedTopicReference.enableReferenceCaching(for: bundle.identifier) try register(bundle) @@ -329,6 +337,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { /// - Parameters: /// - dataProvider: The provider that added this bundle. /// - bundle: The bundle that was added. + @available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") public func dataProvider(_ dataProvider: DocumentationContextDataProvider, didAddBundle bundle: DocumentationBundle) throws { try benchmark(wrap: Benchmark.Duration(id: "bundle-registration")) { // Enable reference caching for this documentation bundle. @@ -343,6 +352,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { /// - Parameters: /// - dataProvider: The provider that removed this bundle. /// - bundle: The bundle that was removed. + @available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") public func dataProvider(_ dataProvider: DocumentationContextDataProvider, didRemoveBundle bundle: DocumentationBundle) throws { linkResolver.localResolver?.unregisterBundle(identifier: bundle.identifier) @@ -354,7 +364,20 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { } /// The documentation bundles that are currently registered with the context. + @available(*, deprecated, message: "Use 'bundle' instead. This deprecated API will be removed after 6.2 is released") public var registeredBundles: some Collection { + _registeredBundles + } + + /// Returns the `DocumentationBundle` with the given `identifier` if it's registered with the context, otherwise `nil`. + @available(*, deprecated, message: "Use 'bundle' instead. This deprecated API will be removed after 6.2 is released") + public func bundle(identifier: String) -> DocumentationBundle? { + _bundle(identifier: identifier) + } + + // Remove these when removing `registeredBundles` and `bundle(identifier:)`. + // These exist so that internal code that need to be compatible with legacy data providers can access the bundles without deprecation warnings. + var _registeredBundles: [DocumentationBundle] { switch dataProvider { case .legacy(let legacyDataProvider): Array(legacyDataProvider.bundles.values) @@ -363,8 +386,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { } } - /// Returns the `DocumentationBundle` with the given `identifier` if it's registered with the context, otherwise `nil`. - public func bundle(identifier: String) -> DocumentationBundle? { + func _bundle(identifier: String) -> DocumentationBundle? { switch dataProvider { case .legacy(let legacyDataProvider): return legacyDataProvider.bundles[identifier] @@ -2551,14 +2573,13 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { /// A closure type getting the information about a reference in a context and returns any possible problems with it. public typealias ReferenceCheck = (DocumentationContext, ResolvedTopicReference) -> [Problem] - - private var checks: [ReferenceCheck] = [] /// Adds new checks to be run during the global topic analysis; after a bundle has been fully registered and its topic graph has been fully built. /// /// - Parameter newChecks: The new checks to add. + @available(*, deprecated, message: "Use 'TopicAnalysisConfiguration.additionalChecks' instead. This deprecated API will be removed after 6.2 is released") public func addGlobalChecks(_ newChecks: [ReferenceCheck]) { - checks.append(contentsOf: newChecks) + configuration.topicAnalysisConfiguration.additionalChecks.append(contentsOf: newChecks) } /// Crawls the hierarchy of the given list of nodes, adding relationships in the topic graph for all resolvable task group references. @@ -2624,7 +2645,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { func topicGraphGlobalAnalysis() { // Run any checks added to the context. let problems = knownIdentifiers.flatMap { reference in - return checks.flatMap { check in + return configuration.topicAnalysisConfiguration.additionalChecks.flatMap { check in return check(self, reference) } } @@ -2672,7 +2693,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { - Throws: ``ContextError/notFound(_:)` if a resource with the given was not found. */ public func resource(with identifier: ResourceReference, trait: DataTraitCollection = .init()) throws -> Data { - guard let bundle = bundle(identifier: identifier.bundleIdentifier), + guard let bundle, let assetManager = assetManagers[identifier.bundleIdentifier], let asset = assetManager.allData(named: identifier.path) else { throw ContextError.notFound(identifier.url) @@ -3055,3 +3076,6 @@ extension DataAsset { } } } + +@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") +extension DocumentationContext: DocumentationContextDataProviderDelegate {} diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift b/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift index 761ef4614b..8da62b2ede 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift @@ -17,6 +17,7 @@ import Foundation /// ## See Also /// /// - ``DocumentationConverter`` +@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") public protocol DocumentationConverterProtocol { /// Converts documentation, outputting products using the given output consumer. /// - Parameter outputConsumer: The output consumer for content produced during conversion. @@ -37,6 +38,7 @@ public protocol DocumentationConverterProtocol { /// /// You can also configure the documentation converter to emit extra metadata such as linkable entities and indexing records /// information. +@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") public struct DocumentationConverter: DocumentationConverterProtocol { let rootURL: URL? let emitDigest: Bool diff --git a/Sources/SwiftDocC/Infrastructure/Input Discovery/DocumentationBundle+DataProvider.swift b/Sources/SwiftDocC/Infrastructure/Input Discovery/DataProvider.swift similarity index 65% rename from Sources/SwiftDocC/Infrastructure/Input Discovery/DocumentationBundle+DataProvider.swift rename to Sources/SwiftDocC/Infrastructure/Input Discovery/DataProvider.swift index 946f2cf835..ce7def20d5 100644 --- a/Sources/SwiftDocC/Infrastructure/Input Discovery/DocumentationBundle+DataProvider.swift +++ b/Sources/SwiftDocC/Infrastructure/Input Discovery/DataProvider.swift @@ -10,8 +10,8 @@ import Foundation -/// A type that provides data for files in a documentation bundle. -package protocol DocumentationBundleDataProvider { +/// A type that provides data for files. +package protocol DataProvider { /// Returns the contents of the file at the specified location. /// /// - Parameter url: The url of the file to read. @@ -19,29 +19,29 @@ package protocol DocumentationBundleDataProvider { func contents(of url: URL) throws -> Data } -/// A type that provides in-memory data for files in a bundle. -struct InMemoryDataProvider: DocumentationBundleDataProvider { +/// A type that provides in-memory data for a known collection of files. +struct InMemoryDataProvider: DataProvider { private let files: [URL: Data] - private let fileManager: FileManagerProtocol? + private let fallback: DataProvider? /// Creates a data provider with a collection of in-memory files. /// - /// If the provider doesn't have in-memory data for a given file it will use the file manager as a fallback. + /// If the provider doesn't have in-memory data for a given file it will use the fallback. /// This allows the in-memory provider to be used for a mix of in-memory and on-disk content. /// /// - Parameters: /// - files: The in-memory data for the files that provider can provide - /// - fallbackFileManager: The file manager that the provider uses as a fallback for any file it doesn't have in-memory data for. - init(files: [URL: Data], fallbackFileManager: FileManagerProtocol?) { + /// - fallback: The file manager that the provider uses as a fallback for any file it doesn't have in-memory data for. + init(files: [URL: Data], fallback: DataProvider?) { self.files = files - self.fileManager = fallbackFileManager + self.fallback = fallback } func contents(of url: URL) throws -> Data { if let inMemoryResult = files[url] { return inMemoryResult } - if let onDiskResult = try fileManager?.contents(of: url) { + if let onDiskResult = try fallback?.contents(of: url) { return onDiskResult } diff --git a/Sources/SwiftDocC/Infrastructure/Input Discovery/DocumentationInputsProvider.swift b/Sources/SwiftDocC/Infrastructure/Input Discovery/DocumentationInputsProvider.swift index cddbde7bf3..a08bd5f6ee 100644 --- a/Sources/SwiftDocC/Infrastructure/Input Discovery/DocumentationInputsProvider.swift +++ b/Sources/SwiftDocC/Infrastructure/Input Discovery/DocumentationInputsProvider.swift @@ -185,7 +185,7 @@ extension DocumentationContext.InputsProvider { let info = try DocumentationContext.Inputs.Info(bundleDiscoveryOptions: options, derivedDisplayName: derivedDisplayName) let topLevelPages: [URL] - let provider: DocumentationBundleDataProvider + let provider: DataProvider if moduleNames.count == 1, let moduleName = moduleNames.first, moduleName != info.displayName, let url = URL(string: "in-memory-data://\(moduleName).md") { let synthesizedExtensionFileData = Data(""" # ``\(moduleName)`` @@ -198,7 +198,7 @@ extension DocumentationContext.InputsProvider { topLevelPages = [url] provider = InMemoryDataProvider( files: [url: synthesizedExtensionFileData], - fallbackFileManager: fileManager + fallback: fileManager ) } else { topLevelPages = [] @@ -235,7 +235,7 @@ private struct SymbolGraphModuleContainer: Decodable { extension DocumentationContext.InputsProvider { /// A pair of documentation inputs and a corresponding data provider for those input files. - package typealias InputsAndDataProvider = (inputs: DocumentationContext.Inputs, dataProvider: DocumentationBundleDataProvider) + package typealias InputsAndDataProvider = (inputs: DocumentationContext.Inputs, dataProvider: DataProvider) /// Traverses the file system from the given starting point to find a documentation catalog and creates a collection of documentation inputs from that catalog. /// diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift index 708e2b5eb3..431ea32981 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift @@ -161,14 +161,14 @@ final class ExternalPathHierarchyResolver { } } - convenience init(dependencyArchive: URL, fileManager: FileManagerProtocol) throws { + convenience init(dependencyArchive: URL, dataProvider: DataProvider) throws { // ???: Should it be the callers responsibility to pass both these URLs? let linkHierarchyFile = dependencyArchive.appendingPathComponent("link-hierarchy.json") let entityURL = dependencyArchive.appendingPathComponent("linkable-entities.json") self.init( - linkInformation: try JSONDecoder().decode(SerializableLinkResolutionInformation.self, from: fileManager.contents(of: linkHierarchyFile)), - entityInformation: try JSONDecoder().decode([LinkDestinationSummary].self, from: fileManager.contents(of: entityURL)) + linkInformation: try JSONDecoder().decode(SerializableLinkResolutionInformation.self, from: dataProvider.contents(of: linkHierarchyFile)), + entityInformation: try JSONDecoder().decode([LinkDestinationSummary].self, from: dataProvider.contents(of: entityURL)) ) } } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift index 4546df4792..4a7c25303c 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift @@ -13,7 +13,11 @@ import SymbolKit /// A class that resolves documentation links by orchestrating calls to other link resolver implementations. public class LinkResolver { - var fileManager: FileManagerProtocol = FileManager.default + var dataProvider: DataProvider + init(dataProvider: DataProvider) { + self.dataProvider = dataProvider + } + /// The link resolver to use to resolve links in the local bundle var localResolver: PathHierarchyBasedLinkResolver! /// A fallback resolver to use when the local resolver fails to resolve a link. @@ -27,7 +31,7 @@ public class LinkResolver { /// - Parameter dependencyArchives: A list of URLs to documentation archives that the local documentation depends on. func loadExternalResolvers(dependencyArchives: [URL]) throws { let resolvers = try dependencyArchives.compactMap { - try ExternalPathHierarchyResolver(dependencyArchive: $0, fileManager: fileManager) + try ExternalPathHierarchyResolver(dependencyArchive: $0, dataProvider: dataProvider) } for resolver in resolvers { for moduleNode in resolver.pathHierarchy.modules { @@ -89,7 +93,7 @@ public class LinkResolver { // Check if this is a link to an external documentation source that should have previously been resolved in `DocumentationContext.preResolveExternalLinks(...)` if let bundleID = unresolvedReference.bundleIdentifier, - !context.registeredBundles.contains(where: { $0.identifier == bundleID || urlReadablePath($0.displayName) == bundleID }) + !context._registeredBundles.contains(where: { $0.identifier == bundleID || urlReadablePath($0.displayName) == bundleID }) { return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo("No external resolver registered for \(bundleID.singleQuoted).")) } @@ -167,7 +171,8 @@ private final class FallbackResolverBasedLinkResolver { // Check if a fallback reference resolver should resolve this let referenceBundleIdentifier = unresolvedReference.bundleIdentifier ?? parent.bundleIdentifier guard let fallbackResolver = context.configuration.convertServiceConfiguration.fallbackResolver, - let knownBundleIdentifier = context.registeredBundles.first(where: { $0.identifier == referenceBundleIdentifier || urlReadablePath($0.displayName) == referenceBundleIdentifier })?.identifier, + // This uses an underscored internal variant of `registeredBundles` to avoid deprecation warnings and remain compatible with legacy data providers. + let knownBundleIdentifier = context._registeredBundles.first(where: { $0.identifier == referenceBundleIdentifier || urlReadablePath($0.displayName) == referenceBundleIdentifier })?.identifier, fallbackResolver.bundleIdentifier == knownBundleIdentifier else { return nil @@ -186,7 +191,8 @@ private final class FallbackResolverBasedLinkResolver { ) allCandidateURLs.append(alreadyResolved.url) - let currentBundle = context.bundle(identifier: knownBundleIdentifier)! + // This uses an underscored internal variant of `bundle(identifier:)` to avoid deprecation warnings and remain compatible with legacy data providers. + let currentBundle = context._bundle(identifier: knownBundleIdentifier)! if !isCurrentlyResolvingSymbolLink { // First look up articles path allCandidateURLs.append(contentsOf: [ diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift index f4908b9e86..a2f23813df 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift @@ -39,23 +39,6 @@ struct SymbolGraphLoader { self.symbolGraphTransformer = symbolGraphTransformer } - /// Creates a new loader, initialized with the given bundle. - /// - Parameters: - /// - bundle: The documentation bundle from which to load symbol graphs. - /// - dataProvider: A data provider in the bundle's context. - /// - symbolGraphTransformer: An optional closure that transforms the symbol graph after the loader decodes it. - init( - bundle: DocumentationBundle, - dataProvider: DocumentationContextDataProvider, - symbolGraphTransformer: ((inout SymbolGraph) -> ())? = nil - ) { - self.bundle = bundle - self.dataLoader = { url, bundle in - try dataProvider.contentsOfURL(url, in: bundle) - } - self.symbolGraphTransformer = symbolGraphTransformer - } - /// A strategy to decode symbol graphs. enum DecodingConcurrencyStrategy { /// Decode all symbol graph files on separate threads concurrently. diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspace.swift b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspace.swift index f53ce292b6..45bf9ae843 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspace.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspace.swift @@ -57,6 +57,7 @@ import Foundation /// - ``DocumentationContextDataProvider`` /// - ``DocumentationContextDataProviderDelegate`` /// +@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") public class DocumentationWorkspace: DocumentationContextDataProvider { /// An error when requesting information from a workspace. public enum WorkspaceError: DescribedError { diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspaceDataProvider.swift b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspaceDataProvider.swift index e273f5b161..5b1fe92905 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspaceDataProvider.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspaceDataProvider.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -11,6 +11,7 @@ import Foundation /// A type that vends bundles and responds to requests for data. +@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") public protocol DocumentationWorkspaceDataProvider { /// A string that uniquely identifies this data provider. /// @@ -35,6 +36,7 @@ public protocol DocumentationWorkspaceDataProvider { func bundles(options: BundleDiscoveryOptions) throws -> [DocumentationBundle] } +@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") public extension DocumentationWorkspaceDataProvider { /// Returns the documentation bundles that your data provider provides; discovered with the default options. /// diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/GeneratedDataProvider.swift b/Sources/SwiftDocC/Infrastructure/Workspace/GeneratedDataProvider.swift index 940406a8b2..f8ccbc5da7 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/GeneratedDataProvider.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/GeneratedDataProvider.swift @@ -12,6 +12,7 @@ import Foundation import SymbolKit /// A type that provides documentation bundles that it discovers by traversing the local file system. +@available(*, deprecated, message: "Use 'DocumentationContext.InputProvider' instead. This deprecated API will be removed after 6.2 is released") public class GeneratedDataProvider: DocumentationWorkspaceDataProvider { public var identifier: String = UUID().uuidString diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider+BundleDiscovery.swift b/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider+BundleDiscovery.swift index 8694506fc8..7eff159b16 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider+BundleDiscovery.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider+BundleDiscovery.swift @@ -10,7 +10,8 @@ import Foundation -extension LocalFileSystemDataProvider { +@available(*, deprecated, message: "Use 'DocumentationContext.InputProvider' instead. This deprecated API will be removed after 6.2 is released") +extension LocalFileSystemDataProvider: DocumentationWorkspaceDataProvider { public func bundles(options: BundleDiscoveryOptions) throws -> [DocumentationBundle] { var bundles = try bundlesInTree(fileSystem, options: options) diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider.swift b/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider.swift index c26145a62f..568c3f9e70 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider.swift @@ -11,7 +11,7 @@ import Foundation /// A type that provides documentation bundles that it discovers by traversing the local file system. -public struct LocalFileSystemDataProvider: DocumentationWorkspaceDataProvider, FileSystemProvider { +public struct LocalFileSystemDataProvider: FileSystemProvider { public var identifier: String = UUID().uuidString /// The location that this provider searches for documentation bundles in. diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/PrebuiltLocalFileSystemDataProvider.swift b/Sources/SwiftDocC/Infrastructure/Workspace/PrebuiltLocalFileSystemDataProvider.swift index f3eca3b0ee..557695a70f 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/PrebuiltLocalFileSystemDataProvider.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/PrebuiltLocalFileSystemDataProvider.swift @@ -11,6 +11,7 @@ import Foundation /// A data provider that provides existing in-memory documentation bundles with files on the local filesystem. +@available(*, deprecated, message: "Use 'DocumentationContext.InputProvider' instead. This deprecated API will be removed after 6.2 is released") public struct PrebuiltLocalFileSystemDataProvider: DocumentationWorkspaceDataProvider { public var identifier: String = UUID().uuidString diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index b8ab3892df..9a9fa77f74 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -311,7 +311,7 @@ public extension DocumentationNode { renderNode: RenderNode, includeTaskGroups: Bool = true ) -> [LinkDestinationSummary] { - guard let bundle = context.bundle(identifier: reference.bundleIdentifier) else { + guard let bundle = context.bundle, bundle.identifier == reference.bundleIdentifier else { // Don't return anything for external references that don't have a bundle in the context. return [] } diff --git a/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift b/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift index 7db36ede7e..690d985589 100644 --- a/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift +++ b/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift @@ -27,7 +27,7 @@ struct LinkTitleResolver { /// - Parameter page: The page for which to resolve the title. /// - Returns: The variants of the link title for this page, or `nil` if the page doesn't exist in the context. func title(for page: DocumentationNode) -> DocumentationDataVariants? { - if let bundle = context.bundle(identifier: page.reference.bundleIdentifier), + if let bundle = context.bundle, let directive = page.markup.child(at: 0) as? BlockDirective { var problems = [Problem]() diff --git a/Sources/SwiftDocC/Utility/FileManagerProtocol.swift b/Sources/SwiftDocC/Utility/FileManagerProtocol.swift index 7f42f5b554..4331c99c5a 100644 --- a/Sources/SwiftDocC/Utility/FileManagerProtocol.swift +++ b/Sources/SwiftDocC/Utility/FileManagerProtocol.swift @@ -22,7 +22,7 @@ import Foundation /// Should you need a file system with a different storage, create your own /// protocol implementations to manage files in memory, /// on a network, in a database, or elsewhere. -package protocol FileManagerProtocol: DocumentationBundleDataProvider { +package protocol FileManagerProtocol: DataProvider { /// Returns the data content of a file at the given path, if it exists. func contents(atPath: String) -> Data? diff --git a/Sources/SwiftDocCTestUtilities/TestFileSystem.swift b/Sources/SwiftDocCTestUtilities/TestFileSystem.swift index c111141ca1..5c5a8b3bb6 100644 --- a/Sources/SwiftDocCTestUtilities/TestFileSystem.swift +++ b/Sources/SwiftDocCTestUtilities/TestFileSystem.swift @@ -40,16 +40,9 @@ import XCTest /// /// - Note: This class is thread-safe by using a naive locking for each access to the files dictionary. /// - Warning: Use this type for unit testing. -package class TestFileSystem: FileManagerProtocol, DocumentationWorkspaceDataProvider { +package class TestFileSystem: FileManagerProtocol { package let currentDirectoryPath = "/" - - package var identifier: String = UUID().uuidString - - package func bundles(options: BundleDiscoveryOptions) throws -> [DocumentationBundle] { - [try DocumentationContext.InputsProvider(fileManager: self) - .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: currentDirectoryPath), options: options).inputs] - } - + /// Thread safe access to the file system. private var filesLock = NSRecursiveLock() @@ -113,7 +106,9 @@ package class TestFileSystem: FileManagerProtocol, DocumentationWorkspaceDataPro let contentBase = at.appendingPathComponent(folder.name) result[contentBase.path] = Self.folderFixtureData - let basePathString = folder.original.standardizedFileURL.deletingLastPathComponent().path + let at = at.appendingPathComponent(folder.name) + + let basePathString = folder.original.standardizedFileURL.path for case let url as URL in enumerator where folder.shouldCopyFile(url) { let data = try url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory == true ? Self.folderFixtureData diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift index 35bb2280e3..7338e528b8 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift @@ -37,7 +37,7 @@ public struct ConvertAction: AsyncAction { private let diagnosticWriterOptions: (formatting: DiagnosticFormattingOptions, baseURL: URL) - /// Initializes the action with the given validated options, creates or uses the given action workspace & context. + /// Initializes the action with the given validated options. /// /// - Parameters: /// - documentationBundleURL: The root of the documentation catalog to convert. @@ -50,9 +50,6 @@ public struct ConvertAction: AsyncAction { /// - buildIndex: Whether or not the convert action should emit an LMDB representation of the navigator index. /// /// A JSON representation is built and emitted regardless of this value. - /// - workspace: A provided documentation workspace. Creates a new empty workspace if value is `nil` - /// - context: A provided documentation context. Creates a new empty context in the workspace if value is `nil` - /// - dataProvider: A data provider to use when registering bundles /// - fileManager: The file manager that the convert action uses to create directories and write data to files. /// - documentationCoverageOptions: Indicates whether or not to generate coverage output and at what level. /// - bundleDiscoveryOptions: Options to configure how the converter discovers documentation bundles. @@ -79,9 +76,6 @@ public struct ConvertAction: AsyncAction { emitDigest: Bool, currentPlatforms: [String : PlatformVersion]?, buildIndex: Bool = false, - workspace _: DocumentationWorkspace = DocumentationWorkspace(), - context _: DocumentationContext? = nil, - dataProvider _: DocumentationWorkspaceDataProvider? = nil, fileManager: FileManagerProtocol = FileManager.default, temporaryDirectory: URL, documentationCoverageOptions: DocumentationCoverageOptions = .noCoverage, @@ -185,7 +179,7 @@ public struct ConvertAction: AsyncAction { let configuration: DocumentationContext.Configuration private let bundle: DocumentationBundle - private let dataProvider: DocumentationBundleDataProvider + private let dataProvider: DataProvider /// A block of extra work that tests perform to affect the time it takes to convert documentation var _extraTestWork: (() async -> Void)? @@ -219,7 +213,7 @@ public struct ConvertAction: AsyncAction { logHandle, formattingOptions: diagnosticWriterOptions.formatting, baseURL: diagnosticWriterOptions.baseURL, - fileManager: fileManager + dataProvider: dataProvider ) ) } diff --git a/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift index f943f6f6e5..d9f09af2f2 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift @@ -62,19 +62,16 @@ public final class PreviewAction: AsyncAction { /// - Parameters: /// - port: The port number used by the preview server. /// - createConvertAction: A closure that returns the action used to convert the documentation before preview. - /// On macOS, this action will be reused to convert documentation each time the source is modified. - /// - workspace: The documentation workspace used by the action's documentation context. - /// - context: The documentation context for the action. + /// + /// On macOS, this action will be recreated each time the source is modified to rebuild the documentation. /// - printTemplatePath: Whether or not the HTML template used by the convert action should be printed when the action /// is performed. /// - Throws: If an error is encountered while initializing the documentation context. public init( port: Int, createConvertAction: @escaping () throws -> ConvertAction, - workspace _: DocumentationWorkspace = DocumentationWorkspace(), - context _: DocumentationContext? = nil, - printTemplatePath: Bool = true) throws - { + printTemplatePath: Bool = true + ) throws { if !Self.allowConcurrentPreviews && !servers.isEmpty { assertionFailure("Running multiple preview actions is not allowed.") } diff --git a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift index a345717095..0edc5eab9c 100644 --- a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift +++ b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift @@ -178,12 +178,10 @@ func aBlackListedFunc() { func testDisabledByDefault() throws { // Create a test bundle with some non-inclusive content. - let (bundleURL, _, _) = try testBundleAndContext(copying: "TestBundle", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, _, context) = try testBundleAndContext(copying: "TestBundle", diagnosticEngine: .init(filterLevel: .error)) { url in try self.nonInclusiveContent.write(to: url.appendingPathComponent("documentation").appendingPathComponent("sidekit.md"), atomically: true, encoding: .utf8) - }) + } - // Load the bundle - let (_, _, context) = try loadBundle(from: bundleURL, externalResolvers: [:], externalSymbolResolver: nil, diagnosticFilterLevel: .error, configureContext: nil) XCTAssertEqual(context.problems.count, 0) } @@ -196,17 +194,12 @@ func aBlackListedFunc() { (.warning, false), (.error, false), ] - - // Create a test bundle with some non-inclusive content. - let (bundleURL, _, _) = try testBundleAndContext(copying: "TestBundle", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in - try self.nonInclusiveContent.write(to: url.appendingPathComponent("documentation").appendingPathComponent("sidekit.md"), atomically: true, encoding: .utf8) - }) - for expectation in expectations { - let (severity, enabled) = expectation + for (severity, enabled) in expectations { + let (_, _, context) = try testBundleAndContext(copying: "TestBundle", diagnosticEngine: .init(filterLevel: severity)) { url in + try self.nonInclusiveContent.write(to: url.appendingPathComponent("documentation").appendingPathComponent("sidekit.md"), atomically: true, encoding: .utf8) + } - // Load the bundle - let (_, _, context) = try loadBundle(from: bundleURL, externalResolvers: [:], externalSymbolResolver: nil, diagnosticFilterLevel: severity, configureContext: nil) // Verify that checker diagnostics were emitted or not, depending on the diagnostic level set. XCTAssertEqual(context.problems.contains(where: { $0.diagnostic.identifier == "org.swift.docc.NonInclusiveLanguage" }), enabled) } diff --git a/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift b/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift index d0302a8761..14578bc7f4 100644 --- a/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift +++ b/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -12,6 +12,9 @@ import XCTest @testable import SwiftDocC +// This test verifies the behavior of `DocumentationConverter` which is a deprecated type. +// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. +@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") class DocumentationConverterTests: XCTestCase { /// An empty implementation of `ConvertOutputConsumer` that purposefully does nothing. struct EmptyConvertOutputConsumer: ConvertOutputConsumer { diff --git a/Tests/SwiftDocCTests/Converter/RenderNodeTransformerTests.swift b/Tests/SwiftDocCTests/Converter/RenderNodeTransformerTests.swift index 5c14af0cbc..bbcc20d559 100644 --- a/Tests/SwiftDocCTests/Converter/RenderNodeTransformerTests.swift +++ b/Tests/SwiftDocCTests/Converter/RenderNodeTransformerTests.swift @@ -14,7 +14,7 @@ import XCTest class RenderNodeTransformerTests: XCTestCase { // This test uses ``RemoveAutomaticallyCuratedSeeAlsoSectionsTransformation`` which is deprecated. // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) + @available(*, deprecated, message: "This deprecated API will be removed after 6.1 is released") func testRemovesAutomaticallyCuratedSeeAlsoSections() throws { let symbolJSON = try String(contentsOf: Bundle.module.url( forResource: "symbol-with-automatic-see-also-section", withExtension: "json", @@ -33,7 +33,7 @@ class RenderNodeTransformerTests: XCTestCase { // This test uses ``RemoveAutomaticallyCuratedSeeAlsoSectionsTransformation`` which is deprecated. // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) + @available(*, deprecated, message: "This deprecated API will be removed after 6.1 is released") func testRemovesAutomaticallyCuratedSeeAlsoSectionsPreservingReferences() throws { let symbolJSON = try String(contentsOf: Bundle.module.url( forResource: "symbol-auto-see-also-fragments-and-relationships", withExtension: "json", diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift index 84ea2610ce..04f811b2ee 100644 --- a/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift @@ -275,13 +275,15 @@ class DiagnosticConsoleWriterDefaultFormattingTest: XCTestCase { let summary = "Test diagnostic summary" let explanation = "Test diagnostic explanation." - let bundle = try XCTUnwrap(fs.bundles().first) + let (bundle, _) = try DocumentationContext.InputsProvider(fileManager: fs) + .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/"), options: .init()) + let baseURL = bundle.baseURL let source = try XCTUnwrap(bundle.markupURLs.first) let range = SourceLocation(line: 3, column: 18, source: source).. [URL] { + let testBundleLocation = try testCatalogURL(named: "TestBundle") + let enumerator = try XCTUnwrap(FileManager.default.enumerator(at: testBundleLocation, includingPropertiesForKeys: [.isDirectoryKey], options: .skipsHiddenFiles)) + + var files: [URL] = [] + for case let fileURL as URL in enumerator where try fileURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory == false { + files.append(fileURL) + } + return files + } + // This tests registration of multiple catalogs which is deprecated + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") func testFirstBundle() throws { let url = try createTemporaryDirectory() // Create 3 minimal doc bundles @@ -54,10 +63,13 @@ class BundleDiscoveryTests: XCTestCase { XCTAssertEqual(converter.firstAvailableBundle()?.identifier, "com.example.bundle1") } + // This test registration more than once data provider which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") func testLoadComplexWorkspace() throws { - + let allFiles = try flatListOfFiles() let workspace = Folder(name: "TestWorkspace", content: [ - CopyOfFile(original: testBundleLocation), + CopyOfFolder(original: testBundleLocation), Folder(name: "nested", content: [ Folder(name: "irrelevant", content: [ TextFile(name: "irrelevant.txt", utf8Content: "distraction"), @@ -106,27 +118,26 @@ class BundleDiscoveryTests: XCTestCase { } func testBundleFormat() throws { - func parsedBundle(from folder: File) throws -> DocumentationBundle? { - let tempURL = try createTemporaryDirectory() - - let workspaceURL = try folder.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: workspaceURL) - let bundles = try dataProvider.bundles() + let allFiles = try flatListOfFiles() + + func parsedBundle(from folder: File) throws -> DocumentationBundle { + let fileSystem = try TestFileSystem(folders: [ + Folder(name: "path", content: [ + Folder(name: "to", content: [ + folder + ]) + ]) + ]) - XCTAssertEqual(bundles.count, 1) - return bundles.first + let inputProvider = DocumentationContext.InputsProvider(fileManager: fileSystem) + let (bundle, _) = try inputProvider.inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/"), options: .init()) + return bundle } - guard let expectedBundle = try parsedBundle(from: CopyOfFolder(original: testBundleLocation)) else { - XCTFail("Failed to parse the Test Bundle") - return - } + let expectedBundle = try parsedBundle(from: CopyOfFolder(original: testBundleLocation)) func checkExpectedFilesFoundIn(_ folder: File, file: StaticString = #file, line: UInt = #line) throws { - guard let bundle = try parsedBundle(from: folder) else { - XCTFail("Failed to parse bundle for folder structure") - return - } + let bundle = try parsedBundle(from: folder) XCTAssertEqual(bundle.identifier, expectedBundle.identifier) XCTAssertEqual(bundle.displayName, expectedBundle.displayName) @@ -195,33 +206,31 @@ class BundleDiscoveryTests: XCTestCase { } func testBundleDiscoveryOptions() throws { - let workspace = Folder(name: "TestWorkspace", content: [ - // The test bundle without all the symbol graph files - CopyOfFolder(original: testBundleLocation, filter: { !DocumentationBundleFileTypes.isSymbolGraphFile($0) }), - - // Just the symbol graph files in a non-bundle folder - CopyOfFolder(original: testBundleLocation, newName: "Not a doc bundle", filter: { DocumentationBundleFileTypes.isSymbolGraphFile($0) }), + let fileSystem = try TestFileSystem(folders: [ + Folder(name: "path", content: [ + Folder(name: "to", content: [ + // The test bundle without all the symbol graph files + CopyOfFolder(original: testBundleLocation, filter: { !DocumentationBundleFileTypes.isSymbolGraphFile($0) }), + + // Just the symbol graph files in a non-bundle folder + CopyOfFolder(original: testBundleLocation, newName: "Not a catalog", filter: { DocumentationBundleFileTypes.isSymbolGraphFile($0) }), + ]) + ]) ]) - let tempURL = try createTemporaryDirectory() - - let workspaceURL = try workspace.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: workspaceURL) - let bundleDiscoveryOptions = BundleDiscoveryOptions( infoPlistFallbacks: [ "CFBundleDisplayName": "Fallback Display Name", ], additionalSymbolGraphFiles: [ - tempURL.appendingPathComponent("TestWorkspace/Not a doc bundle/mykit-iOS.symbols.json"), - tempURL.appendingPathComponent("TestWorkspace/Not a doc bundle/sidekit.symbols.json"), - tempURL.appendingPathComponent("TestWorkspace/Not a doc bundle/MyKit@SideKit.symbols.json"), + URL(fileURLWithPath: "path/to/Not a catalog/mykit-iOS.symbols.json"), + URL(fileURLWithPath: "path/to/Not a catalog/sidekit.symbols.json"), + URL(fileURLWithPath: "path/to/Not a catalog/MyKit@SideKit.symbols.json"), ] ) - let bundles = try dataProvider.bundles(options: bundleDiscoveryOptions) - - XCTAssertEqual(bundles.count, 1) - guard let bundle = bundles.first else { return } + + let inputProvider = DocumentationContext.InputsProvider(fileManager: fileSystem) + let (bundle, _) = try inputProvider.inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/"), options: bundleDiscoveryOptions) // The bundle information was overridden from the options XCTAssertEqual(bundle.identifier, "org.swift.docc.example") @@ -240,19 +249,8 @@ class BundleDiscoveryTests: XCTestCase { } func testNoInfoPlist() throws { - let workspace = Folder(name: "TestWorkspace", content: [ - // The test bundle without the Info.plist file - CopyOfFolder(original: testBundleLocation, filter: { !DocumentationBundleFileTypes.isInfoPlistFile($0) }), - ]) - - XCTAssertFalse(workspace.recursiveContent.contains(where: { $0.name == "Info.plist" }), "This bundle shouldn't contain an Info.plist file") - - let tempURL = try createTemporaryDirectory() - - let workspaceURL = try workspace.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: workspaceURL) + let catalog = Folder(name: "Something.docc", content: []) - // All the required information is passed via overrides let bundleDiscoveryOptions = BundleDiscoveryOptions( infoPlistFallbacks: [ "CFBundleDisplayName": "Fallback Display Name", @@ -260,10 +258,11 @@ class BundleDiscoveryTests: XCTestCase { ], additionalSymbolGraphFiles: [] ) - let bundles = try dataProvider.bundles(options: bundleDiscoveryOptions) - XCTAssertEqual(bundles.count, 1) - guard let bundle = bundles.first else { return } + let fileSystem = try TestFileSystem(folders: [catalog]) + + let inputProvider = DocumentationContext.InputsProvider(fileManager: fileSystem) + let (bundle, _) = try inputProvider.inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/\(catalog.name)"), options: bundleDiscoveryOptions) // The bundle information was specified via the options XCTAssertEqual(bundle.identifier, "com.fallback.bundle.identifier") @@ -271,19 +270,12 @@ class BundleDiscoveryTests: XCTestCase { } func testNoCustomTemplates() throws { - let workspace = Folder(name: "TestWorkspace", content: [ - CopyOfFolder(original: testBundleLocation), - ]) + let catalog = Folder(name: "Something.docc", content: []) - let tempURL = try createTemporaryDirectory() - - let workspaceURL = try workspace.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: workspaceURL) - - let bundles = try dataProvider.bundles(options: BundleDiscoveryOptions()) - - XCTAssertEqual(bundles.count, 1) - guard let bundle = bundles.first else { return } + let fileSystem = try TestFileSystem(folders: [catalog]) + + let inputProvider = DocumentationContext.InputsProvider(fileManager: fileSystem) + let (bundle, _) = try inputProvider.inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/\(catalog.name)"), options: .init()) // Ensure that `customHeader` is `nil` if no top level `header.html` // file was found in the bundle @@ -297,26 +289,19 @@ class BundleDiscoveryTests: XCTestCase { } func testCustomTemplatesFound() throws { - let workspace = Folder(name: "TestBundle.docc", content: - allFiles.map { CopyOfFile(original: $0) } + [ - TextFile(name: "header.html", utf8Content: """ -
hello world
- """), - TextFile(name: "footer.html", utf8Content: """ -
goodbye world
- """), - ] - ) - - let tempURL = try createTemporaryDirectory() - - let workspaceURL = try workspace.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: workspaceURL) - - let bundles = try dataProvider.bundles(options: BundleDiscoveryOptions()) + let catalog = Folder(name: "Something.docc", content: [ + TextFile(name: "header.html", utf8Content: """ +
hello world
+ """), + TextFile(name: "footer.html", utf8Content: """ +
goodbye world
+ """), + ]) - XCTAssertEqual(bundles.count, 1) - guard let bundle = bundles.first else { return } + let fileSystem = try TestFileSystem(folders: [catalog]) + + let inputProvider = DocumentationContext.InputsProvider(fileManager: fileSystem) + let (bundle, _) = try inputProvider.inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/\(catalog.name)"), options: .init()) // Ensure that `customHeader` points to the location of a top level // `header.html` file if one is found in the bundle @@ -327,31 +312,24 @@ class BundleDiscoveryTests: XCTestCase { } func testThemeSettingsFound() throws { - let workspace = Folder(name: "TestBundle.docc", content: - allFiles.map { CopyOfFile(original: $0) } + [ - TextFile(name: "theme-settings.json", utf8Content: """ - { - "meta": {}, - "theme": { - "colors": { - "text": "#ff0000" - } - }, - "features": {} + let catalog = Folder(name: "Something.docc", content: [ + TextFile(name: "theme-settings.json", utf8Content: """ + { + "meta": {}, + "theme": { + "colors": { + "text": "#ff0000" } - """), - ] - ) - - let tempURL = try createTemporaryDirectory() - - let workspaceURL = try workspace.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: workspaceURL) - - let bundles = try dataProvider.bundles(options: BundleDiscoveryOptions()) + }, + "features": {} + } + """), + ]) - XCTAssertEqual(bundles.count, 1) - guard let bundle = bundles.first else { return } + let fileSystem = try TestFileSystem(folders: [catalog]) + + let inputProvider = DocumentationContext.InputsProvider(fileManager: fileSystem) + let (bundle, _) = try inputProvider.inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/\(catalog.name)"), options: .init()) // Ensure that `themeSettings` points to the location of a // `theme-settings.json` file if one is found in the bundle diff --git a/Tests/SwiftDocCTests/Infrastructure/DataAssetManagerTests.swift b/Tests/SwiftDocCTests/Infrastructure/DataAssetManagerTests.swift index 52b139c722..c18c24ab7f 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DataAssetManagerTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DataAssetManagerTests.swift @@ -119,15 +119,10 @@ class DataAssetManagerTests: XCTestCase { forResource: "image", withExtension: "png", subdirectory: "Test Resources")! XCTAssertTrue(FileManager.default.fileExists(atPath: imageFile.path)) - // Create the manager - let workspace = DocumentationWorkspace() - let catalogURL = try testCatalogURL(named: "TestBundle") - let dataProvider = try LocalFileSystemDataProvider(rootURL: catalogURL) - try workspace.registerProvider(dataProvider) - var manager = DataAssetManager() // Register an image asset + let catalogURL = try testCatalogURL(named: "TestBundle") let imageFileURL = catalogURL.appendingPathComponent("figure1.png") try manager.register(data: [imageFileURL]) diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift index 7671718f07..79bd6e3147 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift @@ -14,8 +14,8 @@ import SymbolKit import SwiftDocCTestUtilities class DocumentationContext_RootPageTests: XCTestCase { - func testNoSGFBundle() throws { - let tempFolderURL = try createTempFolder(content: [ + func testArticleOnlyCatalogWithExplicitTechnologyRoot() throws { + let (_, context) = try loadBundle(catalog: Folder(name: "no-sgf-test.docc", content: [ // Root page for the collection TextFile(name: "ReleaseNotes.md", utf8Content: """ @@ -36,14 +36,8 @@ class DocumentationContext_RootPageTests: XCTestCase { - """), InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), - ]), - ]) - - // Parse this test content - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: tempFolderURL.appendingPathComponent("no-sgf-test.docc")) - try workspace.registerProvider(dataProvider) + ]) + ) // Verify all articles were loaded in the context XCTAssertEqual(context.knownIdentifiers.count, 2) @@ -56,8 +50,8 @@ class DocumentationContext_RootPageTests: XCTestCase { ["/documentation/TestBundle/ReleaseNotes-1.2"]) } - func testWarnForSidecarRootPage() throws { - let tempFolderURL = try createTempFolder(content: [ + func testWarnsAboutExtensionFileTechnologyRoot() throws { + let (_, context) = try loadBundle(catalog: Folder(name: "no-sgf-test.docc", content: [ // Root page for the collection TextFile(name: "ReleaseNotes.md", utf8Content: """ @@ -70,7 +64,7 @@ class DocumentationContext_RootPageTests: XCTestCase { ### Release Notes - """), - // A sidecar documentation file + // A documentation extension file TextFile(name: "MyClass.md", utf8Content: """ # ``ReleaseNotes/MyClass`` @Metadata { @@ -78,18 +72,12 @@ class DocumentationContext_RootPageTests: XCTestCase { } """), InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), - ]), - ]) - - // Parse this test content - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: tempFolderURL.appendingPathComponent("no-sgf-test.docc")) - try workspace.registerProvider(dataProvider) + ]) + ) // Verify that we emit a warning when trying to make a symbol a root page let technologyRootProblem = try XCTUnwrap(context.problems.first(where: { $0.diagnostic.identifier == "org.swift.docc.UnexpectedTechnologyRoot" })) - XCTAssertEqual(technologyRootProblem.diagnostic.source, tempFolderURL.appendingPathComponent("no-sgf-test.docc").appendingPathComponent("MyClass.md")) + XCTAssertEqual(technologyRootProblem.diagnostic.source, URL(fileURLWithPath: "/no-sgf-test.docc/MyClass.md")) XCTAssertEqual(technologyRootProblem.diagnostic.range?.lowerBound.line, 3) let solution = try XCTUnwrap(technologyRootProblem.possibleSolutions.first) XCTAssertEqual(solution.replacements.first?.range.lowerBound.line, 3) @@ -97,20 +85,15 @@ class DocumentationContext_RootPageTests: XCTestCase { } func testSingleArticleWithoutTechnologyRootDirective() throws { - let tempFolderURL = try createTempFolder(content: [ + let (_, context) = try loadBundle(catalog: Folder(name: "Something.docc", content: [ TextFile(name: "Article.md", utf8Content: """ # My article A regular article without an explicit `@TechnologyRoot` directive. """) - ]), - ]) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: tempFolderURL) - try workspace.registerProvider(dataProvider) + ]) + ) XCTAssertEqual(context.knownPages.map(\.absoluteString), ["doc://Something/documentation/Article"]) XCTAssertEqual(context.rootModules.map(\.absoluteString), ["doc://Something/documentation/Article"]) @@ -119,7 +102,7 @@ class DocumentationContext_RootPageTests: XCTestCase { } func testMultipleArticlesWithoutTechnologyRootDirective() throws { - let tempFolderURL = try createTempFolder(content: [ + let (_, context) = try loadBundle(catalog: Folder(name: "Something.docc", content: [ TextFile(name: "First.md", utf8Content: """ # My first article @@ -138,13 +121,8 @@ class DocumentationContext_RootPageTests: XCTestCase { Yet another regular article without an explicit `@TechnologyRoot` directive. """), - ]), - ]) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: tempFolderURL) - try workspace.registerProvider(dataProvider) + ]) + ) XCTAssertEqual(context.knownPages.map(\.absoluteString).sorted(), [ "doc://Something/documentation/Something", // A synthesized root @@ -158,7 +136,7 @@ class DocumentationContext_RootPageTests: XCTestCase { } func testMultipleArticlesWithoutTechnologyRootDirectiveWithOneMatchingTheCatalogName() throws { - let tempFolderURL = try createTempFolder(content: [ + let (_, context) = try loadBundle(catalog: Folder(name: "Something.docc", content: [ TextFile(name: "Something.md", utf8Content: """ # Some article @@ -179,13 +157,8 @@ class DocumentationContext_RootPageTests: XCTestCase { Yet another regular article without an explicit `@TechnologyRoot` directive. """), - ]), - ]) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: tempFolderURL) - try workspace.registerProvider(dataProvider) + ]) + ) XCTAssertEqual(context.knownPages.map(\.absoluteString).sorted(), [ "doc://Something/documentation/Something", // This article became the root diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index 1a6e2b27dc..b287205e0b 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -10,7 +10,7 @@ import XCTest import SymbolKit -@testable import SwiftDocC +@testable @_spi(ExternalLinks) import SwiftDocC import Markdown import SwiftDocCTestUtilities @@ -36,6 +36,9 @@ extension CollectionDifference { } class DocumentationContextTests: XCTestCase { + // This test checks unregistration of workspace data providers which is deprecated + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") func testResolve() throws { let workspace = DocumentationWorkspace() let context = try DocumentationContext(dataProvider: workspace) @@ -414,8 +417,7 @@ class DocumentationContextTests: XCTestCase { } func testThrowsErrorForMissingResource() throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) + let (_, context) = try testBundleAndContext() XCTAssertThrowsError(try context.resource(with: ResourceReference(bundleIdentifier: "com.example.missing", path: "/missing.swift")), "Expected requesting an unknown file to result in an error.") } @@ -495,15 +497,9 @@ class DocumentationContextTests: XCTestCase { ]), InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), ]) - let tempURL = try createTemporaryDirectory() - - let bundleURL = try exampleDocumentation.write(inside: tempURL) // Parse this test content - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: exampleDocumentation) // Verify all the reference identifiers for this content XCTAssertEqual(context.knownIdentifiers.count, 3) @@ -518,11 +514,7 @@ class DocumentationContextTests: XCTestCase { } func testRegisteredImages() throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let bundle = try testBundle(named: "TestBundle") - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) + let (bundle, context) = try testBundleAndContext(named: "TestBundle") let imagesRegistered = context .registeredImageAssets(forBundleID: bundle.identifier) @@ -549,9 +541,7 @@ class DocumentationContextTests: XCTestCase { } func testExternalAssets() throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let bundle = try testBundle(named: "TestBundle") + let (bundle, context) = try testBundleAndContext(named: "TestBundle") let image = context.resolveAsset(named: "https://example.com/figure.png", in: bundle.rootReference) XCTAssertNotNil(image) @@ -569,11 +559,7 @@ class DocumentationContextTests: XCTestCase { } func testDownloadAssets() throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let bundle = try testBundle(named: "TestBundle") - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) + let (bundle, context) = try testBundleAndContext(named: "TestBundle") let downloadsBefore = context.registeredDownloadsAssets(forBundleID: bundle.identifier) XCTAssertEqual(downloadsBefore.count, 1) @@ -616,6 +602,9 @@ class DocumentationContextTests: XCTestCase { XCTAssertEqual(downloadsAfter.first?.variants.values.first?.lastPathComponent, "intro.png") } + // This test registration more than once data provider which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") func testCreatesCorrectIdentifiers() throws { let testBundleLocation = Bundle.module.url( forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! @@ -654,7 +643,7 @@ class DocumentationContextTests: XCTestCase { } - func testDetectsMultipleMDfilesWithSameName() throws { + func testDetectsMultipleMarkdownFilesWithSameName() throws { let (_, context) = try testBundleAndContext(named: "TestBundleWithDupMD") let problemWithDuplicateReference = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.DuplicateReference" } @@ -731,14 +720,13 @@ class DocumentationContextTests: XCTestCase { } func testGraphChecks() throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - context.addGlobalChecks([{ (context, reference) -> [Problem] in - return [Problem(diagnostic: Diagnostic(source: reference.url, severity: DiagnosticSeverity.error, range: nil, identifier: "com.tests.testGraphChecks", summary: "test error"), possibleSolutions: [])] - }]) - let bundle = try testBundle(named: "TestBundle") - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) + var configuration = DocumentationContext.Configuration() + configuration.topicAnalysisConfiguration.additionalChecks.append( + { (context, reference) -> [Problem] in + [Problem(diagnostic: Diagnostic(source: reference.url, severity: DiagnosticSeverity.error, range: nil, identifier: "com.tests.testGraphChecks", summary: "test error"), possibleSolutions: [])] + } + ) + let (_, _, context) = try testBundleAndContext(named: "TestBundle", configuration: configuration) /// Checks if the custom check added problems to the context. let testProblems = context.problems.filter({ (problem) -> Bool in @@ -763,7 +751,7 @@ class DocumentationContextTests: XCTestCase { } func testIgnoresUnknownMarkupFiles() throws { - let testBundle = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ + let testCatalog = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ InfoPlist(displayName: "TestIgnoresUnknownMarkupFiles", identifier: "com.example.documentation"), Folder(name: "Resources", content: [ TextFile(name: "Article1.tutorial", utf8Content: "@Article"), @@ -771,20 +759,14 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let tempURL = try createTemporaryDirectory() - let bundleURL = try testBundle.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: testCatalog) XCTAssertEqual(context.knownPages.map { $0.path }, ["/tutorials/TestIgnoresUnknownMarkupFiles/Article1"]) XCTAssertTrue(context.problems.map { $0.diagnostic.identifier }.contains("org.swift.docc.Article.Title.NotFound")) } func testLoadsSymbolData() throws { - let testBundle = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ + let testCatalog = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ InfoPlist(displayName: "TestIgnoresUnknownMarkupFiles", identifier: "com.example.documentation"), Folder(name: "Resources", content: [ CopyOfFile(original: Bundle.module.url( @@ -799,13 +781,7 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let tempURL = try createTemporaryDirectory() - let bundleURL = try testBundle.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: testCatalog) // Symbols are loaded XCTAssertFalse(context.documentationCache.isEmpty) @@ -1039,7 +1015,7 @@ class DocumentationContextTests: XCTestCase { .replacingOccurrences(of: "\"name\" : \"ios\"", with: "\"name\" : \"tvos\"") .replacingOccurrences(of: "\"spelling\" : \"MyClass\"", with: "\"spelling\" : \"MyClassTV\"") - let testBundle = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ + let testCatalog = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ InfoPlist(displayName: "TestIgnoresUnknownMarkupFiles", identifier: "com.example.documentation"), Folder(name: "Symbols", content: [ TextFile(name: "mykit-iOS.symbols.json", utf8Content: graphContentiOS), @@ -1048,13 +1024,7 @@ class DocumentationContextTests: XCTestCase { ]), ]) - let tempURL = try createTemporaryDirectory() - let bundleURL = try testBundle.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: testCatalog) // MyClass is loaded guard let myClass = context.documentationCache["s:5MyKit0A5ClassC"], @@ -1086,9 +1056,9 @@ class DocumentationContextTests: XCTestCase { // Add a modified PlatformSpecificFunctionSymbolPreciseIdentifier symbol var myPlatformSpecificFunctionSymbol = myFunctionSymbol - let myplatformSpecificFunctionName = "myPlatformSpecificFunction" - myPlatformSpecificFunctionSymbol.names.title = myplatformSpecificFunctionName - myPlatformSpecificFunctionSymbol.identifier.precise = "s:5MyKit0A\(myplatformSpecificFunctionName.count)\(myplatformSpecificFunctionName)C" + let myPlatformSpecificFunctionName = "myPlatformSpecificFunction" + myPlatformSpecificFunctionSymbol.names.title = myPlatformSpecificFunctionName + myPlatformSpecificFunctionSymbol.identifier.precise = "s:5MyKit0A\(myPlatformSpecificFunctionName.count)\(myPlatformSpecificFunctionName)C" graph.symbols[myPlatformSpecificFunctionSymbol.identifier.precise] = myPlatformSpecificFunctionSymbol @@ -1103,7 +1073,7 @@ class DocumentationContextTests: XCTestCase { encoder.outputFormatting = .prettyPrinted let newGraphContent = try String(data: encoder.encode(graph), encoding: .utf8)! - let testBundle = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ + let testCatalog = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ InfoPlist(displayName: "TestIgnoresUnknownMarkupFiles", identifier: "com.example.documentation"), Folder(name: "Symbols", content: [ TextFile(name: "mykit-iOS.symbols.json", utf8Content: graphContentiOS), @@ -1111,13 +1081,7 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let tempURL = try createTemporaryDirectory() - let bundleURL = try testBundle.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: testCatalog) // MyFunction is loaded XCTAssertNotNil(context.documentationCache[myFunctionSymbolPreciseIdentifier], "myFunction which only exist on iOS should be found in the graph") @@ -1131,7 +1095,7 @@ class DocumentationContextTests: XCTestCase { } func testResolvesSymbolsBetweenSymbolGraphs() throws { - let testBundle = Folder(name: "CrossGraphResolving.docc", content: [ + let testCatalog = Folder(name: "CrossGraphResolving.docc", content: [ InfoPlist(displayName: "CrossGraphResolving", identifier: "com.example.documentation"), Folder(name: "Resources", content: [ ]), @@ -1145,13 +1109,7 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let tempURL = try createTemporaryDirectory() - let bundleURL = try testBundle.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: testCatalog) // SideClass is loaded guard let sideClass = context.documentationCache["s:7SideKit0A5ClassC"], @@ -1174,7 +1132,7 @@ class DocumentationContextTests: XCTestCase { // "remove" the operating system information graphContentiOS = graphContentiOS.replacingOccurrences(of: "\"operatingSystem\"", with: "\"ignored\"") - let testBundle = Folder(name: "NoOSDeclaration.docc", content: [ + let testCatalog = Folder(name: "NoOSDeclaration.docc", content: [ InfoPlist(displayName: "NoOSDeclaration", identifier: "com.example.documentation"), Folder(name: "Resources", content: []), Folder(name: "Symbols", content: [ @@ -1182,13 +1140,7 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let tempURL = try createTemporaryDirectory() - let bundleURL = try testBundle.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: testCatalog) // MyClass is loaded guard let myClass = context.documentationCache["s:5MyKit0A5ClassC"], @@ -1208,7 +1160,7 @@ class DocumentationContextTests: XCTestCase { let myKitURL = documentationURL.appendingPathComponent("mykit.md") - let testBundle = Folder(name: "TestDetectsDuplicateSymbolArticles.docc", content: [ + let testCatalog = Folder(name: "TestDetectsDuplicateSymbolArticles.docc", content: [ InfoPlist(displayName: "TestDetectsDuplicateSymbolArticles", identifier: "com.example.documentation"), Folder(name: "Resources", content: [ CopyOfFile(original: myKitURL, newName: "mykit.md"), @@ -1223,13 +1175,7 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let tempURL = try createTemporaryDirectory() - let bundleURL = try testBundle.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: testCatalog) XCTAssertNotNil(context.problems .map { $0.diagnostic } @@ -1268,7 +1214,7 @@ class DocumentationContextTests: XCTestCase { ] for testData in combinationsToTest { - let testBundle = Folder(name: "TestCanResolveArticleFromTutorial.docc", content: [ + let testCatalog = Folder(name: "TestCanResolveArticleFromTutorial.docc", content: [ InfoPlist(displayName: "TestCanResolveArticleFromTutorial", identifier: "com.example.documentation"), TextFile(name: "extra-article.md", utf8Content: """ @@ -1286,15 +1232,7 @@ class DocumentationContextTests: XCTestCase { """), ] + testData.symbolGraphFiles) - let tempURL = try createTemporaryDirectory() - let bundleURL = try testBundle.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) - - let bundle = try XCTUnwrap(workspace.bundles.values.first) + let (bundle, context) = try loadBundle(catalog: testCatalog) let renderContext = RenderContext(documentationContext: context, bundle: bundle) let identifier = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/tutorials/TestOverview", sourceLanguage: .swift) @@ -2659,24 +2597,19 @@ let expected = """ } } - func renderNodeForPath(path: String) throws -> (DocumentationNode, RenderNode) { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let bundle = try testBundle(named: "TestBundle") - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) - - let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: path, sourceLanguage: .swift) - let node = try context.entity(with: reference) + func testNavigatorTitle() throws { + let (bundle, context) = try testBundleAndContext(named: "TestBundle") + func renderNodeForPath(path: String) throws -> (DocumentationNode, RenderNode) { + let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: path, sourceLanguage: .swift) + let node = try context.entity(with: reference) - let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) - let renderNode = translator.visitSymbol(symbol) as! RenderNode + let symbol = node.semantic as! Symbol + var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + let renderNode = translator.visitSymbol(symbol) as! RenderNode - return (node, renderNode) - } - - func testNavigatorTitle() throws { + return (node, renderNode) + } + do { let (node, renderNode) = try renderNodeForPath(path: "/documentation/MyKit/MyClass") @@ -2708,7 +2641,7 @@ let expected = """ let tempURL = try createTemporaryDirectory() // Create test bundle - let bundleURL = try Folder(name: "collisions.docc", content: [ + let catalogURL = try Folder(name: "collisions.docc", content: [ InfoPlist(displayName: "Collisions", identifier: "com.test.collisions"), CopyOfFile(original: Bundle.module.url( forResource: "Collisions-iOS.symbols", withExtension: "json", @@ -2719,7 +2652,7 @@ let expected = """ ]).write(inside: tempURL) // Load test bundle - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try loadBundle(from: catalogURL) let referenceForPath: (String) -> ResolvedTopicReference = { path in return ResolvedTopicReference(bundleIdentifier: "com.test.collisions", path: "/documentation" + path, sourceLanguage: .swift) @@ -3197,7 +3130,7 @@ let expected = """ } func testResolvingLinksToTopicSections() throws { - let fileSystem = try TestFileSystem(folders: [ + let (_, context) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName")), @@ -3264,11 +3197,7 @@ let expected = """ An article that only exists to be linked to """), ]) - ]) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - try workspace.registerProvider(fileSystem) + ) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary).sorted())") @@ -3306,7 +3235,7 @@ let expected = """ ]) // Verify that the links are resolved in the render model. - let bundle = try XCTUnwrap(context.registeredBundles.first) + let bundle = try XCTUnwrap(context.bundle) let converter = DocumentationNodeConverter(bundle: bundle, context: context) let renderNode = try converter.convert(entity) @@ -3345,7 +3274,7 @@ let expected = """ func testExtensionCanUseLanguageSpecificRelativeLinks() throws { // This test uses a symbol with different names in Swift and Objective-C, each with a member that's only available in that language. let symbolID = "some-symbol-id" - let fileSystem = try TestFileSystem(folders: [ + let (_, context) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ Folder(name: "swift", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( @@ -3426,11 +3355,7 @@ let expected = """ - ``SwiftName/objectiveCOnlyMemberName`` """), ]) - ]) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - try workspace.registerProvider(fileSystem) + ) XCTAssertEqual(context.problems.map(\.diagnostic.summary).sorted(), [ "'objectiveCOnlyMemberName' doesn't exist at '/ModuleName/SwiftName'", @@ -4503,13 +4428,7 @@ let expected = """ InfoPlist(displayName: "TestBundle", identifier: "com.test.example") ]) - let tempURL = try createTemporaryDirectory() - let bundleURL = try exampleDocumentation.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: exampleDocumentation) XCTAssertEqual( context.documentationCache.count, @@ -5346,7 +5265,7 @@ let expected = """ let externalModuleName = "ExternalModuleName" - func makeExternalResolver() throws -> ExternalPathHierarchyResolver { + func makeExternalDependencyFiles() throws -> (SerializableLinkResolutionInformation, [LinkDestinationSummary]) { let (bundle, context) = try loadBundle( catalog: Folder(name: "Dependency.docc", content: [ JSONFile(name: "\(externalModuleName).symbols.json", content: makeSymbolGraph(moduleName: externalModuleName)), @@ -5368,7 +5287,7 @@ let expected = """ } let linkResolutionInformation = try context.linkResolver.localResolver.prepareForSerialization(bundleID: bundle.identifier) - return ExternalPathHierarchyResolver(linkInformation: linkResolutionInformation, entityInformation: linkSummaries) + return (linkResolutionInformation, linkSummaries) } let catalog = Folder(name: "unit-test.docc", content: [ @@ -5381,9 +5300,27 @@ let expected = """ """), ]) - let (_, bundle, context) = try loadBundle(from: createTempFolder(content: [catalog])) { context in - context.linkResolver.externalResolvers = [externalModuleName: try makeExternalResolver()] - } + let (linkResolutionInformation, linkSummaries) = try makeExternalDependencyFiles() + + var configuration = DocumentationContext.Configuration() + configuration.externalDocumentationConfiguration.dependencyArchives = [ + URL(fileURLWithPath: "/path/to/SomeDependency.doccarchive") + ] + + let (bundle, context) = try loadBundle( + catalog: catalog, + otherFileSystemDirectories: [ + Folder(name: "path", content: [ + Folder(name: "to", content: [ + Folder(name: "SomeDependency.doccarchive", content: [ + JSONFile(name: "link-hierarchy.json", content: linkResolutionInformation), + JSONFile(name: "linkable-entities.json", content: linkSummaries), + ]) + ]) + ]) + ], + configuration: configuration + ) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let reference = try XCTUnwrap(context.soleRootModuleReference) diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift index 9423aadaf4..f17f0ad7d5 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift @@ -28,11 +28,7 @@ class DocumentationCuratorTests: XCTestCase { } func testCrawl() throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let bundle = try testBundle(named: "TestBundle") - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) + let (bundle, context) = try testBundleAndContext(named: "TestBundle") var crawler = DocumentationCurator.init(in: context, bundle: bundle) let mykit = try context.entity(with: ResolvedTopicReference(bundleIdentifier: "org.swift.docc.example", path: "/documentation/MyKit", sourceLanguage: .swift)) @@ -79,46 +75,34 @@ class DocumentationCuratorTests: XCTestCase { } func testCrawlDiagnostics() throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - - let tempURL = try createTemporaryDirectory().appendingPathComponent("unit-test.docc") - let testBundleURL = Bundle.module.url( - forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! + let (tempCatalogURL, bundle, context) = try testBundleAndContext(copying: "TestBundle") { url in + let extensionFile = url.appendingPathComponent("documentation/myfunction.md") - XCTAssert(FileManager.default.fileExists(atPath: testBundleURL.path)) - try FileManager.default.copyItem(at: testBundleURL, to: tempURL) - - let sidecarFile = tempURL.appendingPathComponent("documentation/myfunction.md") - - let source = """ - # ``MyKit/MyClass/myFunction()`` - - myFunction abstract. - - ## Topics - - ### Invalid curation - - A few different curations that should each result in warnings. - The first is a reference to the parent, the second is a reference to self, and the last is an unresolved reference. - - - ``MyKit`` - - ``myFunction()`` - - ``UnknownSymbol`` - """ + try """ + # ``MyKit/MyClass/myFunction()`` - try source.write(to: sidecarFile, atomically: true, encoding: .utf8) - - let dataProvider = try LocalFileSystemDataProvider(rootURL: tempURL) - try workspace.registerProvider(dataProvider) + myFunction abstract. + + ## Topics + + ### Invalid curation + + A few different curations that should each result in warnings. + The first is a reference to the parent, the second is a reference to self, and the last is an unresolved reference. + + - ``MyKit`` + - ``myFunction()`` + - ``UnknownSymbol`` + """.write(to: extensionFile, atomically: true, encoding: .utf8) + } + let extensionFile = tempCatalogURL.appendingPathComponent("documentation/myfunction.md") - var crawler = DocumentationCurator(in: context, bundle: workspace.bundles.values.first!) + var crawler = DocumentationCurator(in: context, bundle: bundle) let mykit = try context.entity(with: ResolvedTopicReference(bundleIdentifier: "org.swift.docc.example", path: "/documentation/MyKit", sourceLanguage: .swift)) XCTAssertNoThrow(try crawler.crawlChildren(of: mykit.reference, prepareForCuration: { _ in }, relateNodes: { _, _ in })) - let myClassProblems = crawler.problems.filter({ $0.diagnostic.source?.standardizedFileURL == sidecarFile.standardizedFileURL }) + let myClassProblems = crawler.problems.filter({ $0.diagnostic.source?.standardizedFileURL == extensionFile.standardizedFileURL }) XCTAssertEqual(myClassProblems.count, 2) let moduleCurationProblem = myClassProblems.first(where: { $0.diagnostic.identifier == "org.swift.docc.ModuleCuration" }) @@ -153,7 +137,7 @@ class DocumentationCuratorTests: XCTestCase { } func testCyclicCurationDiagnostic() throws { - let tempURL = try createTempFolder(content: [ + let (_, context) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ // A number of articles with this cyclic curation: // @@ -199,9 +183,8 @@ class DocumentationCuratorTests: XCTestCase { - """), ]) - ]) + ) - let (_, _, context) = try loadBundle(from: tempURL) XCTAssertEqual(context.problems.map(\.diagnostic.identifier), ["org.swift.docc.CyclicReference"]) let curationProblem = try XCTUnwrap(context.problems.first) @@ -294,13 +277,8 @@ class DocumentationCuratorTests: XCTestCase { XCTAssertEqual(root.path, "/documentation/Root") } - func testSymbolLinkResolving() throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let bundle = try testBundle(named: "TestBundle") - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) + let (bundle, context) = try testBundleAndContext(named: "TestBundle") let crawler = DocumentationCurator.init(in: context, bundle: bundle) @@ -353,13 +331,7 @@ class DocumentationCuratorTests: XCTestCase { } func testLinkResolving() throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let bundle = try testBundle(named: "TestBundle") - let sourceRoot = Bundle.module.url( - forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) + let (sourceRoot, bundle, context) = try testBundleAndContext(named: "TestBundle") var crawler = DocumentationCurator.init(in: context, bundle: bundle) diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationWorkspaceTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationWorkspaceTests.swift index 1a02ac29d7..51b0d2bc62 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationWorkspaceTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationWorkspaceTests.swift @@ -11,6 +11,9 @@ import XCTest @testable import SwiftDocC +// This test verifies the behavior of `DocumentationWorkspace` which is a deprecated type. +// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. +@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") class DocumentationWorkspaceTests: XCTestCase { func testEmptyWorkspace() { let workspace = DocumentationWorkspace() diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift index 1adc33e3d1..44ab509649 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift @@ -16,8 +16,6 @@ import SwiftDocCTestUtilities class ExternalPathHierarchyResolverTests: XCTestCase { - private var originalFeatureFlagsState: FeatureFlags! - override func setUp() { super.setUp() enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled) @@ -910,7 +908,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { let unresolvedReference = try XCTUnwrap(ValidatedURL(parsingAuthoredLink: authoredLink).map(UnresolvedTopicReference.init(topicURL:))) let rootModule = try XCTUnwrap(context.soleRootModuleReference) - let linkResolver = LinkResolver() + let linkResolver = LinkResolver(dataProvider: FileManager.default) linkResolver.localResolver = localResolver let localResult = linkResolver.resolve(unresolvedReference, in: rootModule, fromSymbolLink: true, context: context) let externalResult = externalResolver.resolve(unresolvedReference, fromSymbolLink: true) diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 6be3de2bcc..95eb507a93 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -58,31 +58,16 @@ class ExternalReferenceResolverTests: XCTestCase { } func testResolveExternalReference() throws { - let sourceURL = Bundle.module.url( - forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! - - // Create a copy of the test bundle - let bundleURL = try createTemporaryDirectory().appendingPathComponent("test.docc") - try FileManager.default.copyItem(at: sourceURL, to: bundleURL) - - // Add external link - let myClassMDURL = bundleURL.appendingPathComponent("documentation").appendingPathComponent("myclass.md") - try String(contentsOf: myClassMDURL) - .replacingOccurrences(of: "MyClass abstract.", with: "MyClass uses a .") - .write(to: myClassMDURL, atomically: true, encoding: .utf8) - - // Load bundle and context - let automaticDataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - let bundle = try XCTUnwrap(automaticDataProvider.bundles().first) + let (_, bundle, context) = try testBundleAndContext( + copying: "TestBundle", + externalResolvers: ["com.external.testbundle" : TestExternalReferenceResolver()] + ) { url in + let myClassExtensionFile = url.appendingPathComponent("documentation").appendingPathComponent("myclass.md") + try String(contentsOf: myClassExtensionFile) + .replacingOccurrences(of: "MyClass abstract.", with: "MyClass uses a .") + .write(to: myClassExtensionFile, atomically: true, encoding: .utf8) + } - let workspace = DocumentationWorkspace() - var configuration = DocumentationContext.Configuration() - configuration.externalDocumentationConfiguration.sources = ["com.external.testbundle" : TestExternalReferenceResolver()] - let context = try DocumentationContext(dataProvider: workspace, configuration: configuration) - - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) - let unresolved = UnresolvedTopicReference(topicURL: ValidatedURL(parsingExact: "doc://com.external.testbundle/article")!) let parent = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/MyClass", sourceLanguage: .swift) @@ -170,7 +155,7 @@ class ExternalReferenceResolverTests: XCTestCase { // This test verifies the behavior of a deprecated functionality (changing external documentation sources after registering the documentation) // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) + @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") func testResolvesReferencesExternallyOnlyWhenFallbackResolversAreSet() throws { let workspace = DocumentationWorkspace() let bundle = try testBundle(named: "TestBundle") diff --git a/Tests/SwiftDocCTests/Infrastructure/GeneratedDataProvider.swift b/Tests/SwiftDocCTests/Infrastructure/GeneratedDataProvider.swift index b8c725188a..56360a8793 100644 --- a/Tests/SwiftDocCTests/Infrastructure/GeneratedDataProvider.swift +++ b/Tests/SwiftDocCTests/Infrastructure/GeneratedDataProvider.swift @@ -12,6 +12,9 @@ import XCTest @testable import SwiftDocC import SymbolKit +// This test verifies the behavior of `GeneratedDataProvider` which is a deprecated type. +// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. +@available(*, deprecated, message: "Use 'DocumentationContext.InputProvider' instead. This deprecated API will be removed after 6.2 is released") class GeneratedDataProviderTests: XCTestCase { func testGeneratingBundles() throws { diff --git a/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift b/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift index d394ace874..4864c1dc81 100644 --- a/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift @@ -14,6 +14,9 @@ import SwiftDocCTestUtilities class DocumentationInputsProviderTests: XCTestCase { + // After 6.2 we can update this test to verify that the input provider discovers the same inputs regardless of FileManagerProtocol + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated, message: "This test uses `LocalFileSystemDataProvider` as a `DocumentationWorkspaceDataProvider` which is deprecated and will be removed after 6.2 is released") func testDiscoversSameFilesAsPreviousImplementation() throws { let folderHierarchy = Folder(name: "one", content: [ Folder(name: "two", content: [ diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index 5b1aef5726..7fb04508e8 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -287,11 +287,7 @@ class PathHierarchyTests: XCTestCase { } func testAmbiguousPaths() throws { - let originalFeatureFlagsState = FeatureFlags.current - FeatureFlags.current.isExperimentalLinkHierarchySerializationEnabled = true - defer { - FeatureFlags.current = originalFeatureFlagsState - } + enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled) let (_, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let tree = context.linkResolver.localResolver.pathHierarchy diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift index d73c776ae2..87dd835cea 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift @@ -191,7 +191,7 @@ class SymbolDisambiguationTests: XCTestCase { func testMixedLanguageFramework() throws { let (bundle, context) = try testBundleAndContext(named: "MixedLanguageFramework") - var loader = SymbolGraphLoader(bundle: bundle, dataProvider: context._legacyDataProvider!) + var loader = SymbolGraphLoader(bundle: bundle, dataLoader: { try context.contentsOfURL($0, in: $1) }) try loader.loadAll() let references = context.linkResolver.localResolver.referencesForSymbols(in: loader.unifiedGraphs, bundle: bundle, context: context).mapValues(\.path) @@ -330,27 +330,12 @@ class SymbolDisambiguationTests: XCTestCase { miscResourceURLs: [] ) - class TestProvider: DocumentationContextDataProvider { - var delegate: DocumentationContextDataProviderDelegate? = nil - var bundles: [SwiftDocC.BundleIdentifier : SwiftDocC.DocumentationBundle] = [:] - var symbolGraphData: [URL: Data] = [:] - - func contentsOfURL(_ url: URL, in bundle: SwiftDocC.DocumentationBundle) throws -> Data { - guard let data = symbolGraphData[url] else { - fatalError("Only symbol graph content will be loaded from the bundle in this test") - } - return data - } - } - - let provider = TestProvider() - provider.symbolGraphData = [ + let provider = InMemoryDataProvider(files: [ swiftSymbolGraphURL: try JSONEncoder().encode(graph), objcSymbolGraphURL: try JSONEncoder().encode(graph2), - ] - provider.bundles[bundle.identifier] = bundle + ], fallback: nil) - let context = try DocumentationContext(dataProvider: provider) + let context = try DocumentationContext(bundle: bundle, dataProvider: provider) return context.linkResolver.localResolver.referencesForSymbols(in: ["SymbolDisambiguationTests": unified], bundle: bundle, context: context) } diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift index 8f503f128d..b0c5a1b2db 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift @@ -114,7 +114,7 @@ class SymbolGraphLoaderTests: XCTestCase { // This test calls ``SymbolGraph.relationships`` which is deprecated. // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) + @available(*, deprecated) // `SymbolGraph.relationships` doesn't specify when it will be removed func testLoadingHighNumberOfModulesConcurrently() throws { let tempURL = try createTemporaryDirectory() @@ -1788,7 +1788,6 @@ class SymbolGraphLoaderTests: XCTestCase { symbolGraphURLs: [URL], configureSymbolGraph: ((inout SymbolGraph) -> ())? = nil ) throws -> SymbolGraphLoader { - let workspace = DocumentationWorkspace() let bundle = DocumentationBundle( info: DocumentationBundle.Info( displayName: "Test", @@ -1799,11 +1798,12 @@ class SymbolGraphLoaderTests: XCTestCase { markupURLs: [], miscResourceURLs: [] ) - try workspace.registerProvider(PrebuiltLocalFileSystemDataProvider(bundles: [bundle])) return SymbolGraphLoader( bundle: bundle, - dataProvider: workspace, + dataLoader: { url, _ in + try FileManager.default.contents(of: url) + }, symbolGraphTransformer: configureSymbolGraph ) } diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift index de617e817e..f91081fb56 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift @@ -106,7 +106,7 @@ class SymbolReferenceTests: XCTestCase { } func testCreatesUniquePathsForOverloadSymbols() throws { - let testBundle = Folder(name: "TestCreatesUniquePathsForOverloadSymbols.docc", content: [ + let testCatalog = Folder(name: "TestCreatesUniquePathsForOverloadSymbols.docc", content: [ InfoPlist(displayName: "TestCreatesUniquePathsForOverloadSymbols", identifier: "com.example.documentation"), Folder(name: "Resources", content: [ ]), @@ -198,13 +198,7 @@ class SymbolReferenceTests: XCTestCase { ]), ]) - let tempURL = try createTemporaryDirectory() - let bundleURL = try testBundle.write(inside: tempURL) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: testCatalog) // The overloads are sorted and all dupes get a hash suffix. XCTAssertEqual( diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 5b70e705aa..64527bd049 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -16,7 +16,7 @@ import SwiftDocCTestUtilities class ExternalLinkableTests: XCTestCase { // Write example documentation bundle with a minimal Tutorials page - let bundleFolderHierarchy = Folder(name: "unit-test.docc", content: [ + let catalogHierarchy = Folder(name: "unit-test.docc", content: [ Folder(name: "Symbols", content: []), Folder(name: "Resources", content: [ TextFile(name: "TechnologyX.tutorial", utf8Content: """ @@ -94,15 +94,8 @@ class ExternalLinkableTests: XCTestCase { ]) func testSummaryOfTutorialPage() throws { - let workspace = DocumentationWorkspace() - let context = try! DocumentationContext(dataProvider: workspace) + let (bundle, context) = try loadBundle(catalog: catalogHierarchy) - let bundleURL = try bundleFolderHierarchy.write(inside: createTemporaryDirectory()) - - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) - - let bundle = context.bundle(identifier: "com.test.example")! let converter = DocumentationNodeConverter(bundle: bundle, context: context) let node = try context.entity(with: ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/tutorials/TestBundle/Tutorial", sourceLanguage: .swift)) @@ -726,19 +719,12 @@ class ExternalLinkableTests: XCTestCase { ] ) - let bundleFolderHierarchy = Folder(name: "unit-test.docc", content: [ + let catalogHierarchy = Folder(name: "unit-test.docc", content: [ JSONFile(name: "MyModule.symbols.json", content: symbolGraph), InfoPlist(displayName: "MyModule", identifier: "com.example.mymodule") ]) - let workspace = DocumentationWorkspace() - let context = try! DocumentationContext(dataProvider: workspace) - - let bundleURL = try bundleFolderHierarchy.write(inside: createTemporaryDirectory()) - - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) - - let bundle = context.bundle(identifier: "com.example.mymodule")! + let (bundle, context) = try loadBundle(catalog: catalogHierarchy) + let converter = DocumentationNodeConverter(bundle: bundle, context: context) let node = try context.entity(with: ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/MyModule/MyClass/myFunc()", sourceLanguage: .swift)) diff --git a/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift b/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift index f5e0bdfc35..f8444527c1 100644 --- a/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift +++ b/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -36,34 +36,20 @@ class LineHighlighterTests: XCTestCase { } """) - static func bundleFolder(overview: TextFile = defaultOverview, - tutorial: TextFile, - codeFiles: [TextFile]) -> Folder { - return Folder(name: "TestNoSteps.docc", content: [ + private static func makeCatalog( + overview: TextFile = defaultOverview, + tutorial: TextFile, + codeFiles: [TextFile] + ) -> Folder { + Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "Line Highlighter Tests", identifier: bundleIdentifier), - Folder(name: "Symbols", content: []), - Folder(name: "Resources", content: [ - overview, - tutorial, - ] + codeFiles), - ]) - } - - func testBundleAndContext(bundleRoot: Folder, bundleIdentifier: BundleIdentifier) throws -> (DocumentationBundle, DocumentationContext) { - let workspace = DocumentationWorkspace() - let context = try! DocumentationContext(dataProvider: workspace) - - let bundleURL = try bundleRoot.write(inside: createTemporaryDirectory()) - - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) - let bundle = context.bundle(identifier: bundleIdentifier)! - return (bundle, context) + Folder(name: "Resources", content: [overview, tutorial] + codeFiles), + ]) } func highlights(tutorialFile: TextFile, codeFiles: [TextFile]) throws -> [LineHighlighter.Result] { - let bundleFolder = LineHighlighterTests.bundleFolder(tutorial: tutorialFile, codeFiles: codeFiles) - let (bundle, context) = try testBundleAndContext(bundleRoot: bundleFolder, bundleIdentifier: LineHighlighterTests.bundleIdentifier) + let catalog = Self.makeCatalog(tutorial: tutorialFile, codeFiles: codeFiles) + let (bundle, context) = try loadBundle(catalog: catalog) let tutorialReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/tutorials/Line-Highlighter-Tests/Tutorial", fragment: nil, sourceLanguage: .swift) let tutorial = try context.entity(with: tutorialReference).semantic as! Tutorial diff --git a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift index c248776b34..26d8cf017a 100644 --- a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift +++ b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift @@ -683,7 +683,14 @@ class ParametersAndReturnValidatorTests: XCTestCase { file: StaticString = #file, line: UInt = #line ) throws -> String { - let url = try createTempFolder(content: [ + let fileSystem = try TestFileSystem(folders: [ + Folder(name: "path", content: [ + Folder(name: "to", content: [ + // The generated symbol graph uses a fake source file where the documentation comment starts at line 7, column 6 + TextFile(name: "SomeFile.swift", utf8Content: String(repeating: "\n", count: 7) + docComment.splitByNewlines.map { " /// \($0)" }.joined(separator: "\n")) + ]) + ]), + Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( docComment: docComment, @@ -693,23 +700,18 @@ class ParametersAndReturnValidatorTests: XCTestCase { )) ]) ]) + let logStorage = LogHandle.LogStorage() - let (_, _, context) = try loadBundle(from: url, configureContext: { context in - for consumerID in context.diagnosticEngine.consumers.sync({ $0.values }) { - context.diagnosticEngine.remove(consumerID) - } - let fileSystem = try TestFileSystem(folders: [ - Folder(name: "path", content: [ - Folder(name: "to", content: [ - // The generated symbol graph uses a fake source file where the documentation comment starts at line 7, column 6 - TextFile(name: "SomeFile.swift", utf8Content: String(repeating: "\n", count: 7) + docComment.splitByNewlines.map { " /// \($0)" }.joined(separator: "\n")) - ]) - ]) - ]) - context.diagnosticEngine.add(DiagnosticConsoleWriter(LogHandle.memory(logStorage), highlight: false, fileManager: fileSystem)) - }) - context.diagnosticEngine.flush() + let diagnosticEngine = DiagnosticEngine() + diagnosticEngine.add(DiagnosticConsoleWriter(LogHandle.memory(logStorage), highlight: false, dataProvider: fileSystem)) + + let (bundle, dataProvider) = try DocumentationContext.InputsProvider(fileManager: fileSystem) + .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/unit-test.docc"), options: .init()) + + _ = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine) + + diagnosticEngine.flush() return logStorage.text.trimmingCharacters(in: .newlines) } diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift index fcd377cf99..452ee5b2d2 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift @@ -972,7 +972,7 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { } func testAutomaticSeeAlsoSectionElementLimit() throws { - let fileSystem = try TestFileSystem(folders: [ + let (bundle, context) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: (1...50).map { makeSymbol(id: "symbol-id-\($0)", kind: .class, pathComponents: ["SymbolName\($0)"]) @@ -990,14 +990,9 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { \((1...50).map { "- ``SymbolName\($0)``" }.joined(separator: "\n")) """), ]) - ]) - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - try workspace.registerProvider(fileSystem) + ) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") - let bundle = try XCTUnwrap(context.registeredBundles.first) let converter = DocumentationNodeConverter(bundle: bundle, context: context) diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeSourceRepositoryTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeSourceRepositoryTests.swift index 4dd6867fe0..0725e946b8 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeSourceRepositoryTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeSourceRepositoryTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -17,9 +17,7 @@ class SemaToRenderNodeSourceRepositoryTests: XCTestCase { func testDoesNotEmitsSourceRepositoryInformationWhenNoSourceIsGiven() throws { let outputConsumer = try renderNodeConsumer( for: "SourceLocations", - configureConverter: { converter in - converter.sourceRepository = nil - } + sourceRepository: nil ) XCTAssertNil(try outputConsumer.renderNode(withTitle: "MyStruct").metadata.remoteSource) @@ -28,12 +26,10 @@ class SemaToRenderNodeSourceRepositoryTests: XCTestCase { func testEmitsSourceRepositoryInformationForSymbolsWhenPresent() throws { let outputConsumer = try renderNodeConsumer( for: "SourceLocations", - configureConverter: { converter in - converter.sourceRepository = SourceRepository.github( - checkoutPath: "/path/to/checkout", - sourceServiceBaseURL: URL(string: "https://example.com/my-repo")! - ) - } + sourceRepository: SourceRepository.github( + checkoutPath: "/path/to/checkout", + sourceServiceBaseURL: URL(string: "https://example.com/my-repo")! + ) ) XCTAssertEqual( try outputConsumer.renderNode(withTitle: "MyStruct").metadata.remoteSource, diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 5b4346e01c..032a0ef74e 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -1905,7 +1905,7 @@ Document var configuration = DocumentationContext.Configuration() configuration.externalMetadata.currentPlatforms = currentPlatforms - let (_, bundle, context) = try testBundleAndContext(copying: "TestBundle", configuration: configuration) + let (_, bundle, context) = try testBundleAndContext(named: "TestBundle", configuration: configuration) let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift) return (bundle, context, reference) @@ -3380,9 +3380,7 @@ Document let tempURL = try createTemporaryDirectory() let bundleURL = try exampleDocumentation.write(inside: tempURL) - let (_, bundle, context) = try loadBundle(from: bundleURL) { context in - context.diagnosticEngine.consumers.sync({ $0.removeAll() }) - } + let (_, bundle, context) = try loadBundle(from: bundleURL, diagnosticEngine: .init() /* no diagnostic consumers */) let reference = try XCTUnwrap(context.soleRootModuleReference) diff --git a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift index 1531398355..d19a6acb18 100644 --- a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift @@ -202,7 +202,7 @@ class DefaultAvailabilityTests: XCTestCase { var configuration = DocumentationContext.Configuration() // Set a beta status for the docs (which would normally be set via command line argument) configuration.externalMetadata.currentPlatforms = ["iOS": PlatformVersion(VersionTriplet(14, 0, 0), beta: true)] - let (_, bundle, context) = try testBundleAndContext(copying: "TestBundle", configuration: configuration) + let (_, bundle, context) = try testBundleAndContext(named: "TestBundle", configuration: configuration) do { let identifier = ResolvedTopicReference(bundleIdentifier: "org.swift.docc.example", path: "/documentation/MyKit/MyClass/myFunction()", fragment: nil, sourceLanguage: .swift) diff --git a/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift b/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift index 8819e366d0..47c542d533 100644 --- a/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift @@ -15,7 +15,7 @@ import Markdown class DocumentationContentRendererTests: XCTestCase { func testReplacesTypeIdentifierSubHeadingFragmentWithIdentifierForSwift() throws { - let subHeadingFragments = documentationContentRenderer + let subHeadingFragments = try makeDocumentationContentRenderer() .subHeadingFragments(for: nodeWithSubheadingAndNavigatorVariants) XCTAssertEqual( @@ -46,7 +46,7 @@ class DocumentationContentRendererTests: XCTestCase { } func testDoesNotReplaceSubHeadingFragmentsForOtherLanguagesThanSwift() throws { - let subHeadingFragments = documentationContentRenderer + let subHeadingFragments = try makeDocumentationContentRenderer() .subHeadingFragments(for: nodeWithSubheadingAndNavigatorVariants) guard case .replace(let fragments) = subHeadingFragments.variants.first?.patch.first else { @@ -74,7 +74,7 @@ class DocumentationContentRendererTests: XCTestCase { } func testReplacesTypeIdentifierNavigatorFragmentWithIdentifierForSwift() throws { - let navigatorFragments = documentationContentRenderer + let navigatorFragments = try makeDocumentationContentRenderer() .navigatorFragments(for: nodeWithSubheadingAndNavigatorVariants) XCTAssertEqual( @@ -105,7 +105,7 @@ class DocumentationContentRendererTests: XCTestCase { } func testDoesNotReplacesNavigatorFragmentsForOtherLanguagesThanSwift() throws { - let navigatorFragments = documentationContentRenderer + let navigatorFragments = try makeDocumentationContentRenderer() .navigatorFragments(for: nodeWithSubheadingAndNavigatorVariants) guard case .replace(let fragments) = navigatorFragments.variants.first?.patch.first else { @@ -138,20 +138,9 @@ private extension DocumentationDataVariantsTrait { } private extension DocumentationContentRendererTests { - var documentationContentRenderer: DocumentationContentRenderer { - DocumentationContentRenderer( - documentationContext: try! DocumentationContext(dataProvider: DocumentationWorkspace()), - bundle: DocumentationBundle( - info: DocumentationBundle.Info( - displayName: "Test", - identifier: "org.swift.test" - ), - baseURL: URL(string: "https://example.com/example")!, - symbolGraphURLs: [], - markupURLs: [], - miscResourceURLs: [] - ) - ) + func makeDocumentationContentRenderer() throws -> DocumentationContentRenderer { + let (bundle, context) = try testBundleAndContext() + return DocumentationContentRenderer(documentationContext: context, bundle: bundle) } var nodeWithSubheadingAndNavigatorVariants: DocumentationNode { diff --git a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift index 22f96fdcb4..bf242a92ec 100644 --- a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift +++ b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -86,30 +86,25 @@ extension TestRenderNodeOutputConsumer { extension XCTestCase { func renderNodeConsumer( for bundleName: String, - configureBundle: ((URL) throws -> Void)? = nil, - configureConverter: ((inout DocumentationConverter) throws -> Void)? = nil + sourceRepository: SourceRepository? = nil, + configureBundle: ((URL) throws -> Void)? = nil ) throws -> TestRenderNodeOutputConsumer { - let (bundleURL, _, context) = try testBundleAndContext( + let (_, bundle, context) = try testBundleAndContext( copying: bundleName, configureBundle: configureBundle ) - var converter = DocumentationConverter( - documentationBundleURL: bundleURL, - emitDigest: false, - documentationCoverageOptions: .noCoverage, - currentPlatforms: nil, - workspace: context._legacyDataProvider as! DocumentationWorkspace, + let outputConsumer = TestRenderNodeOutputConsumer() + + _ = try ConvertActionConverter.convert( + bundle: bundle, context: context, - dataProvider: try LocalFileSystemDataProvider(rootURL: bundleURL), - bundleDiscoveryOptions: BundleDiscoveryOptions() + outputConsumer: outputConsumer, + sourceRepository: sourceRepository, + emitDigest: false, + documentationCoverageOptions: .noCoverage ) - try configureConverter?(&converter) - - let outputConsumer = TestRenderNodeOutputConsumer() - let (_, _) = try converter.convert(outputConsumer: outputConsumer) - return outputConsumer } } diff --git a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift index c01e3802ce..b617a69e50 100644 --- a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift +++ b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift @@ -412,7 +412,7 @@ class ListItemExtractorTests: XCTestCase { XCTAssert(expectedLinkProblems.allSatisfy { $0.diagnostic.range != nil }, "Diagnostics are missing range information.", file: file, line: line) let logStorage = LogHandle.LogStorage() - let diagnosticWriter = DiagnosticConsoleWriter(LogHandle.memory(logStorage), formattingOptions: [], highlight: true, fileManager: context.linkResolver.fileManager) + let diagnosticWriter = DiagnosticConsoleWriter(LogHandle.memory(logStorage), formattingOptions: [], highlight: true, dataProvider: context.linkResolver.dataProvider) diagnosticWriter.receive(expectedLinkProblems) try diagnosticWriter.flush() XCTAssertEqual(logStorage.text, expectedLogText, file: file, line: line) diff --git a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift index 907b935dbc..ef75b5e81c 100644 --- a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift +++ b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift @@ -22,26 +22,19 @@ extension XCTestCase { externalResolvers: [String: ExternalDocumentationSource] = [:], externalSymbolResolver: GlobalExternalSymbolResolver? = nil, fallbackResolver: ConvertServiceFallbackResolver? = nil, - diagnosticFilterLevel: DiagnosticSeverity = .hint, - configuration: DocumentationContext.Configuration = .init(), - configureContext: ((DocumentationContext) throws -> Void)? = nil + diagnosticEngine: DiagnosticEngine = .init(filterLevel: .hint), + configuration: DocumentationContext.Configuration = .init() ) throws -> (URL, DocumentationBundle, DocumentationContext) { - let workspace = DocumentationWorkspace() - var configuration = configuration configuration.externalDocumentationConfiguration.sources = externalResolvers configuration.externalDocumentationConfiguration.globalSymbolResolver = externalSymbolResolver configuration.convertServiceConfiguration.fallbackResolver = fallbackResolver - configuration.externalMetadata.diagnosticLevel = diagnosticFilterLevel + configuration.externalMetadata.diagnosticLevel = diagnosticEngine.filterLevel - let context = try DocumentationContext(dataProvider: workspace, diagnosticEngine: DiagnosticEngine(filterLevel: diagnosticFilterLevel), configuration: configuration) - try configureContext?(context) - // Load the bundle using automatic discovery - let automaticDataProvider = try LocalFileSystemDataProvider(rootURL: catalogURL) - // Mutate the bundle to include the code listings, then apply to the workspace using a manual provider. - let bundle = try XCTUnwrap(automaticDataProvider.bundles().first) - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) + let (bundle, dataProvider) = try DocumentationContext.InputsProvider() + .inputsAndDataProvider(startingPoint: catalogURL, options: .init()) + + let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) return (catalogURL, bundle, context) } @@ -57,14 +50,12 @@ extension XCTestCase { otherFileSystemDirectories: [Folder] = [], configuration: DocumentationContext.Configuration = .init() ) throws -> (DocumentationBundle, DocumentationContext) { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace, configuration: configuration) - let fileSystem = try TestFileSystem(folders: [catalog] + otherFileSystemDirectories) - context.linkResolver.fileManager = fileSystem - try workspace.registerProvider(fileSystem) - let bundle = try XCTUnwrap(context.registeredBundles.first) + let (bundle, dataProvider) = try DocumentationContext.InputsProvider(fileManager: fileSystem) + .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/\(catalog.name)"), options: .init()) + + let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: configuration) return (bundle, context) } @@ -81,6 +72,7 @@ extension XCTestCase { externalResolvers: [BundleIdentifier : ExternalDocumentationSource] = [:], externalSymbolResolver: GlobalExternalSymbolResolver? = nil, fallbackResolver: ConvertServiceFallbackResolver? = nil, + diagnosticEngine: DiagnosticEngine = .init(filterLevel: .hint), configuration: DocumentationContext.Configuration = .init(), configureBundle: ((URL) throws -> Void)? = nil ) throws -> (URL, DocumentationBundle, DocumentationContext) { @@ -107,6 +99,7 @@ extension XCTestCase { externalResolvers: externalResolvers, externalSymbolResolver: externalSymbolResolver, fallbackResolver: fallbackResolver, + diagnosticEngine: diagnosticEngine, configuration: configuration ) } @@ -114,10 +107,11 @@ extension XCTestCase { func testBundleAndContext( named name: String, externalResolvers: [String: ExternalDocumentationSource] = [:], - fallbackResolver: ConvertServiceFallbackResolver? = nil + fallbackResolver: ConvertServiceFallbackResolver? = nil, + configuration: DocumentationContext.Configuration = .init() ) throws -> (URL, DocumentationBundle, DocumentationContext) { let catalogURL = try testCatalogURL(named: name) - return try loadBundle(from: catalogURL, externalResolvers: externalResolvers, fallbackResolver: fallbackResolver) + return try loadBundle(from: catalogURL, externalResolvers: externalResolvers, fallbackResolver: fallbackResolver, configuration: configuration) } func testBundleAndContext(named name: String, externalResolvers: [String: ExternalDocumentationSource] = [:]) throws -> (DocumentationBundle, DocumentationContext) { @@ -156,7 +150,7 @@ extension XCTestCase { miscResourceURLs: [] ) - let context = try DocumentationContext(bundle: bundle, dataProvider: FileManager.default) + let context = try DocumentationContext(bundle: bundle, dataProvider: TestFileSystem(folders: [])) return (bundle, context) } diff --git a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift index 015a27e1b7..4cc94120f2 100644 --- a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift @@ -235,9 +235,6 @@ class ConvertSubcommandTests: XCTestCase { } } - // This test calls ``ConvertOptions.additionalSymbolGraphFiles`` which is deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testAdditionalSymbolGraphFiles() throws { // Default to [] when not passed do { @@ -584,7 +581,7 @@ class ConvertSubcommandTests: XCTestCase { // This test calls ``ConvertOptions.infoPlistFallbacks._unusedVersionForBackwardsCompatibility`` which is deprecated. // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) + @available(*, deprecated) // We'll probably keep this deprecated property for a long time for backwards compatibility. func testVersionFlag() throws { let noFlagConvert = try Docc.Convert.parse([]) XCTAssertEqual(noFlagConvert.infoPlistFallbacks._unusedVersionForBackwardsCompatibility, nil) diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift index f72bcf1691..848be40d5d 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2022 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -17,36 +17,19 @@ class ConvertActionIndexerTests: XCTestCase { // Tests the standalone indexer func testConvertActionIndexer() throws { - let originalURL = Bundle.module.url( - forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! + let (bundle, dataProvider) = try DocumentationContext.InputsProvider() + .inputsAndDataProvider(startingPoint: testCatalogURL(named: "TestBundle"), options: .init()) - // Create temo folder. - let url = try createTemporaryDirectory() - - // Copy TestBundle into a temp folder - let testBundleURL = url.appendingPathComponent("TestBundle.docc") - try FileManager.default.copyItem(at: originalURL, to: testBundleURL) - - // Load the test bundle - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: testBundleURL) - try workspace.registerProvider(dataProvider) - - guard !context.registeredBundles.isEmpty else { - XCTFail("Didn't load test bundle in test.") - return - } - - let converter = DocumentationNodeConverter(bundle: context.registeredBundles.first!, context: context) + let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider) + let converter = DocumentationNodeConverter(bundle: bundle, context: context) // Add /documentation/MyKit to the index, verify the tree dump do { let reference = ResolvedTopicReference(bundleIdentifier: "org.swift.docc.example", path: "/documentation/MyKit", sourceLanguage: .swift) let renderNode = try converter.convert(context.entity(with: reference)) - try FileManager.default.createDirectory(at: testBundleURL.appendingPathComponent("index1"), withIntermediateDirectories: false, attributes: nil) - let indexer = try ConvertAction.Indexer(outputURL: testBundleURL.appendingPathComponent("index1"), bundleIdentifier: context.registeredBundles.first!.identifier) + let tempIndexURL = try createTemporaryDirectory(named: "index") + let indexer = try ConvertAction.Indexer(outputURL: tempIndexURL, bundleIdentifier: bundle.identifier) indexer.index(renderNode) XCTAssertTrue(indexer.finalize(emitJSON: false, emitLMDB: false).isEmpty) let treeDump = try XCTUnwrap(indexer.dumpTree()) @@ -70,8 +53,8 @@ class ConvertActionIndexerTests: XCTestCase { let reference2 = ResolvedTopicReference(bundleIdentifier: "org.swift.docc.example", path: "/documentation/Test-Bundle/Default-Code-Listing-Syntax", sourceLanguage: .swift) let renderNode2 = try converter.convert(context.entity(with: reference2)) - try FileManager.default.createDirectory(at: testBundleURL.appendingPathComponent("index2"), withIntermediateDirectories: false, attributes: nil) - let indexer = try ConvertAction.Indexer(outputURL: testBundleURL.appendingPathComponent("index2"), bundleIdentifier: context.registeredBundles.first!.identifier) + let tempIndexURL = try createTemporaryDirectory(named: "index") + let indexer = try ConvertAction.Indexer(outputURL: tempIndexURL, bundleIdentifier: bundle.identifier) indexer.index(renderNode1) indexer.index(renderNode2) XCTAssertTrue(indexer.finalize(emitJSON: false, emitLMDB: false).isEmpty) diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift index abac91cfb8..9830a83217 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift @@ -58,7 +58,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) @@ -107,7 +106,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let result = try await action.perform(logHandle: .none) @@ -168,7 +166,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let result = try await action.perform(logHandle: .none) @@ -204,7 +201,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let result = try await action.perform(logHandle: .none) @@ -237,7 +233,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let result = try await action.perform(logHandle: .none) @@ -332,7 +327,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) @@ -367,7 +361,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) @@ -396,7 +389,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let result = try await action.perform(logHandle: .none) @@ -439,7 +431,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) @@ -509,7 +500,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let result = try await action.perform(logHandle: .none) @@ -597,7 +587,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: true, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let result = try await action.perform(logHandle: .none) @@ -656,7 +645,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: nil, emitDigest: true, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticLevel: "hint") // report all errors during the test @@ -737,7 +725,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: nil, emitDigest: true, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticLevel: "hint") // report all errors during the test @@ -817,7 +804,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let result = try await action.perform(logHandle: .none) @@ -919,7 +905,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: true, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let (result, context) = try await action.perform(logHandle: .none) @@ -1127,7 +1112,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: true, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) let result = try await action.perform(logHandle: .none) @@ -1248,7 +1232,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: true, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -1339,7 +1322,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: true, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -1502,7 +1484,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: true, // emit digest files currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -1528,7 +1509,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, // don't emit digest files currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -1567,7 +1547,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: fileSystem, fileManager: fileSystem, temporaryDirectory: fileSystem.uniqueTemporaryDirectory(), documentationCoverageOptions: coverageOptions @@ -1619,7 +1598,6 @@ class ConvertActionTests: XCTestCase { "platform1": PlatformVersion(.init(10, 11, 12), beta: false), "platform2": PlatformVersion(.init(11, 12, 13), beta: false), ], - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -1651,7 +1629,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: currentPlatforms, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -1716,7 +1693,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: [:], - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -1746,7 +1722,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticEngine: engine) @@ -1787,15 +1762,11 @@ class ConvertActionTests: XCTestCase { } } - func convertTestBundle(batchSize: Int, emitDigest: Bool, targetURL: URL, testDataProvider: DocumentationWorkspaceDataProvider & FileManagerProtocol) async throws -> ActionResult { + func convertTestBundle(batchSize: Int, emitDigest: Bool, targetURL: URL, testDataProvider: FileManagerProtocol) async throws -> ActionResult { // Run the create ConvertAction - var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources["com.example.test"] = TestReferenceResolver() - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace, configuration: configuration) - var action = try ConvertAction( documentationBundleURL: bundle.absoluteURL, outOfProcessResolver: nil, @@ -1804,9 +1775,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: emitDigest, currentPlatforms: nil, - workspace: workspace, - context: context, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -1887,7 +1855,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testFileSystem, fileManager: testFileSystem, temporaryDirectory: testFileSystem.uniqueTemporaryDirectory() ) @@ -2223,7 +2190,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticLevel: "error", @@ -2260,7 +2226,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticLevel: "error", @@ -2298,7 +2263,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticLevel: "error" @@ -2333,7 +2297,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticLevel: "error", @@ -2363,7 +2326,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), inheritDocs: flag @@ -2381,7 +2343,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -2408,7 +2369,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: true, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -2431,7 +2391,6 @@ class ConvertActionTests: XCTestCase { let catalogURL = try catalog.write(inside: temporaryDirectory) let targetDirectory = temporaryDirectory.appendingPathComponent("target", isDirectory: true) - let dataProvider = try LocalFileSystemDataProvider(rootURL: catalogURL) var action = try ConvertAction( documentationBundleURL: catalogURL, @@ -2441,7 +2400,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: nil, emitDigest: false, currentPlatforms: nil, - dataProvider: dataProvider, fileManager: FileManager.default, temporaryDirectory: createTemporaryDirectory() ) @@ -2475,7 +2433,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) @@ -2570,7 +2527,6 @@ class ConvertActionTests: XCTestCase { ) let targetDirectory = temporaryDirectory.appendingPathComponent("target.doccarchive", isDirectory: true) - let dataProvider = try LocalFileSystemDataProvider(rootURL: catalogURL) var action = try ConvertAction( documentationBundleURL: catalogURL, @@ -2580,7 +2536,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: htmlTemplateDirectory, emitDigest: false, currentPlatforms: nil, - dataProvider: dataProvider, fileManager: FileManager.default, temporaryDirectory: createTemporaryDirectory(), transformForStaticHosting: true @@ -2624,8 +2579,6 @@ class ConvertActionTests: XCTestCase { let bundleURL = try bundle.write(inside: tempURL) let templateURL = try template.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - var action = try ConvertAction( documentationBundleURL: bundleURL, outOfProcessResolver: nil, @@ -2634,7 +2587,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: templateURL, emitDigest: false, currentPlatforms: nil, - dataProvider: dataProvider, fileManager: FileManager.default, temporaryDirectory: createTemporaryDirectory(), experimentalEnableCustomTemplates: true @@ -2705,8 +2657,6 @@ class ConvertActionTests: XCTestCase { let bundleURL = try bundle.write(inside: tempURL) let templateURL = try template.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - var action = try ConvertAction( documentationBundleURL: bundleURL, outOfProcessResolver: nil, @@ -2715,7 +2665,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: templateURL, emitDigest: false, currentPlatforms: nil, - dataProvider: dataProvider, fileManager: FileManager.default, temporaryDirectory: createTemporaryDirectory(), experimentalEnableCustomTemplates: true, @@ -2775,8 +2724,6 @@ class ConvertActionTests: XCTestCase { let bundleURL = try bundle.write(inside: tempURL) let templateURL = try template.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - var action = try ConvertAction( documentationBundleURL: bundleURL, outOfProcessResolver: nil, @@ -2785,7 +2732,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: templateURL, emitDigest: false, currentPlatforms: nil, - dataProvider: dataProvider, fileManager: FileManager.default, temporaryDirectory: createTemporaryDirectory(), experimentalEnableCustomTemplates: true @@ -2826,7 +2772,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticEngine: engine @@ -2848,7 +2793,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticEngine: engine @@ -2867,7 +2811,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticEngine: nil, @@ -2908,8 +2851,7 @@ class ConvertActionTests: XCTestCase { ) let (_, context) = try await action.perform(logHandle: .none) - XCTAssertEqual(context.registeredBundles.count, 1) - let bundle = try XCTUnwrap(context.registeredBundles.first, "Should have registered the generated test bundle.") + let bundle = try XCTUnwrap(context.bundle, "Should have registered the generated test bundle.") XCTAssertEqual(bundle.displayName, "MyKit") XCTAssertEqual(bundle.identifier, "MyKit") } @@ -2987,8 +2929,7 @@ class ConvertActionTests: XCTestCase { ) let (_, context) = try await action.perform(logHandle: .none) - XCTAssertEqual(context.registeredBundles.count, 1) - let bundle = try XCTUnwrap(context.registeredBundles.first, "Should have registered the generated test bundle.") + let bundle = try XCTUnwrap(context.bundle, "Should have registered the generated test bundle.") XCTAssertEqual(bundle.displayName, "Something") XCTAssertEqual(bundle.identifier, "com.example.test") } @@ -3016,7 +2957,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: URL(fileURLWithPath: "/tmp"), diagnosticEngine: engine @@ -3108,7 +3048,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticEngine: engine @@ -3158,7 +3097,6 @@ class ConvertActionTests: XCTestCase { htmlTemplateDirectory: Folder.emptyHTMLTemplateDirectory.absoluteURL, emitDigest: false, currentPlatforms: nil, - dataProvider: fileSystem, fileManager: fileSystem, temporaryDirectory: fileSystem.uniqueTemporaryDirectory() ) diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift index 3ea5d57956..98017e3c74 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift @@ -83,9 +83,6 @@ class PreviewActionIntegrationTests: XCTestCase { let fileHandle = try FileHandle(forUpdating: pipeURL) defer { fileHandle.closeFile() } - let workspace = DocumentationWorkspace() - _ = try! DocumentationContext(dataProvider: workspace) - let convertActionTempDirectory = try createTemporaryDirectory() let createConvertAction = { try ConvertAction( diff --git a/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift b/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift index 510fe98977..6ff7276a4c 100644 --- a/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -15,7 +15,7 @@ import Markdown import SwiftDocCTestUtilities class SemanticAnalyzerTests: XCTestCase { - let bundleFolderHierarchy = Folder(name: "SemanticAnalyzerTests.docc", content: [ + private let catalogHierarchy = Folder(name: "SemanticAnalyzerTests.docc", content: [ Folder(name: "Symbols", content: []), Folder(name: "Resources", content: [ TextFile(name: "Oops.md", utf8Content: """ @@ -54,23 +54,15 @@ class SemanticAnalyzerTests: XCTestCase { InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), ]) - func testDontCrashOnInvalidContent() throws { - let workspace = DocumentationWorkspace() - let context = try! DocumentationContext(dataProvider: workspace) - let bundleURL = try bundleFolderHierarchy.write(inside: createTemporaryDirectory()) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) - let bundle = context.bundle(identifier: "com.test.example")! + func testDoNotCrashOnInvalidContent() throws { + let (bundle, context) = try loadBundle(catalog: catalogHierarchy) XCTAssertThrowsError(try context.entity(with: ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/Oops", sourceLanguage: .swift))) } func testWarningsAboutDirectiveSupport() throws { func problemsConvertingTestContent(withFileExtension fileExtension: String) throws -> (unsupportedTopLevelChildProblems: [Problem], missingTopLevelChildProblems: [Problem]) { - let workspace = DocumentationWorkspace() - let context = try! DocumentationContext(dataProvider: workspace) - - let folderHierarchy = Folder(name: "SemanticAnalyzerTests.docc", content: [ + let catalogHierarchy = Folder(name: "SemanticAnalyzerTests.docc", content: [ TextFile(name: "FileWithDirective.\(fileExtension)", utf8Content: """ @Article """), @@ -81,9 +73,7 @@ class SemanticAnalyzerTests: XCTestCase { """), InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), ]) - let bundleURL = try folderHierarchy.write(inside: createTemporaryDirectory()) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) + let (_, context) = try loadBundle(catalog: catalogHierarchy) return ( context.problems.filter({ $0.diagnostic.identifier == "org.swift.docc.unsupportedTopLevelChild" }), @@ -120,12 +110,7 @@ class SemanticAnalyzerTests: XCTestCase { } func testDoesNotWarnOnEmptyTutorials() throws { - let workspace = DocumentationWorkspace() - let context = try! DocumentationContext(dataProvider: workspace) - let bundleURL = try bundleFolderHierarchy.write(inside: createTemporaryDirectory()) - let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL) - try workspace.registerProvider(dataProvider) - let bundle = context.bundle(identifier: "com.test.example")! + let (bundle, context) = try loadBundle(catalog: catalogHierarchy) let document = Document(parsing: "", options: .parseBlockDirectives) var analyzer = SemanticAnalyzer(source: URL(string: "/empty.tutorial"), context: context, bundle: bundle) diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift b/Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift index 57966ac569..c40c496742 100644 --- a/Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift @@ -17,7 +17,6 @@ class TestFileSystemTests: XCTestCase { func testEmpty() throws { let fs = try TestFileSystem(folders: []) XCTAssertEqual(fs.currentDirectoryPath, "/") - XCTAssertFalse(fs.identifier.isEmpty) var isDirectory = ObjCBool(false) XCTAssertTrue(fs.fileExists(atPath: "/", isDirectory: &isDirectory)) XCTAssertEqual(fs.files.keys.sorted(), ["/", "/tmp"], "The root (/) should be the only existing path.") @@ -307,10 +306,9 @@ class TestFileSystemTests: XCTestCase { ]) let fs = try TestFileSystem(folders: [folders]) - let bundles = try fs.bundles() - XCTAssertEqual(bundles.count, 1) + let (bundle, _) = try DocumentationContext.InputsProvider(fileManager: fs) + .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/"), options: .init()) - let bundle = try XCTUnwrap(bundles.first) XCTAssertFalse(bundle.markupURLs.isEmpty) XCTAssertFalse(bundle.miscResourceURLs.isEmpty) XCTAssertFalse(bundle.symbolGraphURLs.isEmpty) @@ -330,7 +328,8 @@ class TestFileSystemTests: XCTestCase { DataFile(name: "Something.symbols.json", data: somethingSymbolGraphData), ]) ]) - let bundle = try XCTUnwrap(fs.bundles().first) + let (bundle, _) = try DocumentationContext.InputsProvider(fileManager: fs) + .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/"), options: .init()) XCTAssertEqual(bundle.displayName, "DisplayName", "Display name is read from Info.plist") XCTAssertEqual(bundle.identifier, "com.example", "Identifier is read from Info.plist") } @@ -342,7 +341,8 @@ class TestFileSystemTests: XCTestCase { DataFile(name: "Something.symbols.json", data: somethingSymbolGraphData), ]) ]) - let bundle = try XCTUnwrap(fs.bundles().first) + let (bundle, _) = try DocumentationContext.InputsProvider(fileManager: fs) + .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/"), options: .init()) XCTAssertEqual(bundle.displayName, "CatalogName", "Display name is derived from catalog name") XCTAssertEqual(bundle.displayName, "CatalogName", "Identifier is derived the display name") } diff --git a/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift b/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift new file mode 100644 index 0000000000..17309f9e6e --- /dev/null +++ b/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift @@ -0,0 +1,44 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2024 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import Foundation +import XCTest +import SwiftDocC +import SwiftDocCTestUtilities + +extension XCTestCase { + /// Loads a documentation catalog from an in-memory test file system. + /// + /// - Parameters: + /// - catalog: The directory structure of the documentation catalog + /// - otherFileSystemDirectories: Any other directories in the test file system. + /// - configuration: Configuration for the created context. + /// - Returns: The loaded documentation bundle and context for the given catalog input. + func loadBundle( + catalog: Folder, + otherFileSystemDirectories: [Folder] = [], + configuration: DocumentationContext.Configuration = .init() + ) throws -> (DocumentationBundle, DocumentationContext) { + let fileSystem = try TestFileSystem(folders: [catalog] + otherFileSystemDirectories) + + let (bundle, dataProvider) = try DocumentationContext.InputsProvider(fileManager: fileSystem) + .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/\(catalog.name)"), options: .init()) + + let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: configuration) + return (bundle, context) + } + + func testCatalogURL(named name: String, file: StaticString = #file, line: UInt = #line) throws -> URL { + try XCTUnwrap( + Bundle.module.url(forResource: name, withExtension: "docc", subdirectory: "Test Bundles"), + file: file, line: line + ) + } +}