From 316913d8df1696701da4b867f0dccddbb3c6ff28 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Mon, 14 Oct 2024 18:20:53 -0700 Subject: [PATCH] =?UTF-8?q?Revert=20"Define=20commands=20as=20asynchronous?= =?UTF-8?q?=20and=20use=20Task=20for=20preview=20cancellation=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1c05ea62aa0fae8266c1a40e3863c51c9c10fa16. --- .../Diagnostics/DiagnosticEngine.swift | 4 +- .../Infrastructure/DocumentationContext.swift | 2 - .../DocumentationConverter.swift | 39 ++- .../SwiftDocCUtilities/Action/Action.swift | 22 +- .../Action/Actions/Action+MoveOutput.swift | 2 +- .../Actions/Convert/ConvertAction.swift | 59 +++- .../Action/Actions/CoverageAction.swift | 12 +- .../Actions/EmitGeneratedCurationAction.swift | 4 +- .../Action/Actions/IndexAction.swift | 7 +- .../Action/Actions/Init/InitAction.swift | 7 +- .../Action/Actions/Merge/MergeAction.swift | 4 +- .../Action/Actions/PreviewAction.swift | 87 +++-- .../TransformForStaticHostingAction.swift | 13 +- .../Action+performAndHandleResult.swift | 9 +- .../ArgumentParsing/Subcommands/Convert.swift | 11 +- .../Subcommands/EmitGeneratedCuration.swift | 10 +- .../ArgumentParsing/Subcommands/Index.swift | 26 +- .../ArgumentParsing/Subcommands/Init.swift | 23 +- .../ArgumentParsing/Subcommands/Merge.swift | 6 +- .../ArgumentParsing/Subcommands/Preview.swift | 20 +- .../Subcommands/ProcessArchive.swift | 4 +- .../Subcommands/ProcessCatalog.swift | 4 +- .../TransformForStaticHosting.swift | 14 +- Sources/SwiftDocCUtilities/Docc.swift | 6 +- .../PreviewServer/PreviewServer.swift | 10 +- .../SwiftDocCUtilities.md | 2 +- Sources/docc/main.swift | 6 +- .../ConvertActionStaticHostableTests.swift | 4 +- .../ConvertActionTests.swift | 230 +++++++------ .../EmitGeneratedCurationsActionTests.swift | 12 +- .../IndexActionTests.swift | 12 +- .../Init/InitActionTests.swift | 16 +- .../MergeActionTests.swift | 45 +-- .../PreviewActionIntegrationTests.swift | 314 ++++++++++-------- .../StaticHostableTransformerTests.swift | 8 +- ...TransformForStaticHostingActionTests.swift | 13 +- 36 files changed, 607 insertions(+), 460 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticEngine.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticEngine.swift index 647a457b5f..35bfeceb14 100644 --- a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticEngine.swift +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticEngine.swift @@ -29,9 +29,7 @@ public final class DiagnosticEngine { /// diagnostics with a severity up to and including `.information` will be printed. public var filterLevel: DiagnosticSeverity { didSet { - self.filter = { [filterLevel] in - $0.diagnostic.severity.rawValue <= filterLevel.rawValue - } + self.filter = { $0.diagnostic.severity.rawValue <= self.filterLevel.rawValue } } } diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 4aafd1024b..6ff807ecef 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -1331,7 +1331,6 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { } private func shouldContinueRegistration() throws { - try Task.checkCancellation() guard isRegistrationEnabled.sync({ $0 }) else { throw ContextError.registrationDisabled } @@ -1774,7 +1773,6 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { /// /// When given `false` the context will try to cancel as quick as possible /// any ongoing bundle registrations. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") public func setRegistrationEnabled(_ value: Bool) { isRegistrationEnabled.sync({ $0 = value }) } diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift b/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift index 4d5724314a..0f9a240e36 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift @@ -50,7 +50,6 @@ public struct DocumentationConverter: DocumentationConverterProtocol { private var dataProvider: DocumentationWorkspaceDataProvider /// An optional closure that sets up a context before the conversion begins. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") public var setupContext: ((inout DocumentationContext) -> Void)? /// Conversion batches should be big enough to keep all cores busy but small enough not to keep @@ -190,17 +189,49 @@ public struct DocumentationConverter: DocumentationConverterProtocol { if let dataProvider = self.currentDataProvider { try workspace.unregisterProvider(dataProvider) } + + // Do additional context setup. + setupContext?(&context) + /* + Asynchronously cancel registration if necessary. + We spawn a timer that periodically checks `isCancelled` and if necessary + disables registration in `DocumentationContext` as registration being + the largest part of a documentation conversion. + */ let context = self.context + let isCancelled = self.isCancelled + + // `true` if the `isCancelled` flag is set. + func isConversionCancelled() -> Bool { + return isCancelled?.sync({ $0 }) == true + } + + // Run a timer that synchronizes the cancelled state between the converter and the context directly. + // We need a timer on a separate dispatch queue because `workspace.registerProvider()` blocks + // the current thread until it loads all symbol graphs, markdown files, and builds the topic graph + // so in order to be able to update the context cancellation flag we need to run on a different thread. + var cancelTimerQueue: DispatchQueue? = DispatchQueue(label: "org.swift.docc.ConvertActionCancelTimer", qos: .unspecified, attributes: .concurrent) + let cancelTimer = DispatchSource.makeTimerSource(queue: cancelTimerQueue) + cancelTimer.schedule(deadline: .now(), repeating: .milliseconds(500), leeway: .milliseconds(50)) + cancelTimer.setEventHandler { + if isConversionCancelled() { + cancelTimer.cancel() + context.setRegistrationEnabled(false) + } + } + cancelTimer.resume() // Start bundle registration try workspace.registerProvider(dataProvider, options: bundleDiscoveryOptions) self.currentDataProvider = dataProvider + + // Bundle registration is finished - stop the timer and reset the context cancellation state. + cancelTimer.cancel() + cancelTimerQueue = nil + context.setRegistrationEnabled(true) // If cancelled, return early before we emit diagnostics. - func isConversionCancelled() -> Bool { - Task.isCancelled || isCancelled?.sync({ $0 }) == true - } guard !isConversionCancelled() else { return ([], []) } processingDurationMetric = benchmark(begin: Benchmark.Duration(id: "documentation-processing")) diff --git a/Sources/SwiftDocCUtilities/Action/Action.swift b/Sources/SwiftDocCUtilities/Action/Action.swift index 3544e61ced..69d46f4d15 100644 --- a/Sources/SwiftDocCUtilities/Action/Action.swift +++ b/Sources/SwiftDocCUtilities/Action/Action.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors + Copyright (c) 2021 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 @@ -13,16 +13,18 @@ import SwiftDocC /// An independent unit of work in the command-line workflow. /// -/// An action represents a discrete documentation task; it takes options and inputs, performs its work, reports any problems it encounters, and outputs it generates. -package protocol AsyncAction { +/// An `Action` represents a discrete documentation task; it takes options and inputs, +/// performs its work, reports any problems it encounters, and outputs it generates. +public protocol Action { /// Performs the action and returns an ``ActionResult``. - mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult + mutating func perform(logHandle: LogHandle) throws -> ActionResult } -package extension AsyncAction { - mutating func perform(logHandle: LogHandle) async throws -> ActionResult { - var logHandle = logHandle - return try await perform(logHandle: &logHandle) - } +/// An action for which you can optionally customize the documentation context. +public protocol RecreatingContext: Action { + /// A closure that an action calls with the action's context for built documentation, + /// before the action performs work. + /// + /// Use this closure to set the action's context to a certain state before the action runs. + var setupContext: ((inout DocumentationContext) -> Void)? { get set } } - diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift b/Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift index 3cd7a50e4b..658c9e38bd 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift @@ -11,7 +11,7 @@ import Foundation import SwiftDocC -extension AsyncAction { +extension Action { /// Creates a new unique directory, with an optional template, inside of specified container. /// - Parameters: diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift index 6d5a3171c6..31c4ff3852 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift @@ -14,7 +14,7 @@ import Foundation import SwiftDocC /// An action that converts a source bundle into compiled documentation. -public struct ConvertAction: AsyncAction { +public struct ConvertAction: Action, RecreatingContext { enum Error: DescribedError { case doesNotContainBundle(url: URL) case cancelPending @@ -59,6 +59,12 @@ public struct ConvertAction: AsyncAction { private var fileManager: FileManagerProtocol private let temporaryDirectory: URL + public var setupContext: ((inout DocumentationContext) -> Void)? { + didSet { + converter.setupContext = setupContext + } + } + var converter: DocumentationConverter private var durationMetric: Benchmark.Duration? @@ -233,17 +239,52 @@ public struct ConvertAction: AsyncAction { dataProvider: dataProvider, bundleDiscoveryOptions: bundleDiscoveryOptions, sourceRepository: sourceRepository, + isCancelled: isCancelled, diagnosticEngine: self.diagnosticEngine, experimentalModifyCatalogWithGeneratedCuration: experimentalModifyCatalogWithGeneratedCuration ) } + + /// `true` if the convert action is cancelled. + private let isCancelled = Synchronized(false) - /// A block of extra work that tests perform to affect the time it takes to convert documentation - var _extraTestWork: (() async -> Void)? + /// `true` if the convert action is currently running. + let isPerforming = Synchronized(false) + + /// A block to execute when conversion has finished. + /// It's used as a "future" for when the action is cancelled. + var didPerformFuture: (()->Void)? + + /// A block to execute when conversion has started. + var willPerformFuture: (()->Void)? + + /// Cancels the action. + /// + /// The method blocks until the action has completed cancelling. + mutating func cancel() throws { + /// If the action is not running, there is nothing to cancel + guard isPerforming.sync({ $0 }) == true else { return } + + /// If the action is already cancelled throw `cancelPending`. + if isCancelled.sync({ $0 }) == true { + throw Error.cancelPending + } + + /// Set the cancelled flag. + isCancelled.sync({ $0 = true }) + + /// Wait for the `perform(logHandle:)` method to call `didPerformFuture()` + let waitGroup = DispatchGroup() + waitGroup.enter() + didPerformFuture = { + waitGroup.leave() + } + waitGroup.wait() + } /// Converts each eligible file from the source documentation bundle, /// saves the results in the given output alongside the template files. - public mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult { + mutating public func perform(logHandle: LogHandle) throws -> ActionResult { // Add the default diagnostic console writer now that we know what log handle it should write to. if !diagnosticEngine.hasConsumer(matching: { $0 is DiagnosticConsoleWriter }) { diagnosticEngine.add( @@ -261,13 +302,15 @@ public struct ConvertAction: AsyncAction { var postConversionProblems: [Problem] = [] let totalTimeMetric = benchmark(begin: Benchmark.Duration(id: "convert-total-time")) + // While running this method keep the `isPerforming` flag up. + isPerforming.sync({ $0 = true }) + willPerformFuture?() defer { + didPerformFuture?() + isPerforming.sync({ $0 = false }) diagnosticEngine.flush() } - // Run any extra work that the test may have injected - await _extraTestWork?() - let temporaryFolder = try createTempFolder(with: htmlTemplateDirectory) defer { @@ -408,7 +451,7 @@ public struct ConvertAction: AsyncAction { benchmark(end: totalTimeMetric) if !didEncounterError { - let coverageResults = try await coverageAction.perform(logHandle: &logHandle) + let coverageResults = try coverageAction.perform(logHandle: logHandle) postConversionProblems.append(contentsOf: coverageResults.problems) } diff --git a/Sources/SwiftDocCUtilities/Action/Actions/CoverageAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/CoverageAction.swift index 481a43fe01..2f93db15ef 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/CoverageAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/CoverageAction.swift @@ -12,24 +12,24 @@ import Foundation import SwiftDocC /// An action that creates documentation coverage info for a documentation bundle. -public struct CoverageAction: AsyncAction { - init( +public struct CoverageAction: Action { + internal init( documentationCoverageOptions: DocumentationCoverageOptions, workingDirectory: URL, - fileManager: FileManagerProtocol - ) { + fileManager: FileManagerProtocol) { self.documentationCoverageOptions = documentationCoverageOptions self.workingDirectory = workingDirectory self.fileManager = fileManager } public let documentationCoverageOptions: DocumentationCoverageOptions - let workingDirectory: URL + internal let workingDirectory: URL private let fileManager: FileManagerProtocol - public mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult { + public mutating func perform(logHandle: LogHandle) throws -> ActionResult { switch documentationCoverageOptions.level { case .brief, .detailed: + var logHandle = logHandle print(" --- Experimental coverage output enabled. ---", to: &logHandle) let summaryString = try CoverageDataEntry.generateSummary( diff --git a/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift index 12b07721d7..4f090c5e69 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift @@ -12,7 +12,7 @@ import Foundation import SwiftDocC /// An action that emits documentation extension files that reflect the auto-generated curation. -struct EmitGeneratedCurationAction: AsyncAction { +struct EmitGeneratedCurationAction: Action { let catalogURL: URL? let additionalSymbolGraphDirectory: URL? let outputURL: URL @@ -41,7 +41,7 @@ struct EmitGeneratedCurationAction: AsyncAction { self.fileManager = fileManager } - mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult { + mutating func perform(logHandle: LogHandle) throws -> ActionResult { let workspace = DocumentationWorkspace() let context = try DocumentationContext(dataProvider: workspace) diff --git a/Sources/SwiftDocCUtilities/Action/Actions/IndexAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/IndexAction.swift index 06bfd3aa03..4b79ca1ce9 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/IndexAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/IndexAction.swift @@ -12,7 +12,7 @@ import Foundation import SwiftDocC /// An action that creates an index of a documentation bundle. -public struct IndexAction: AsyncAction { +public struct IndexAction: Action { let rootURL: URL let outputURL: URL let bundleIdentifier: String @@ -22,7 +22,8 @@ public struct IndexAction: AsyncAction { private var dataProvider: LocalFileSystemDataProvider! /// Initializes the action with the given validated options, creates or uses the given action workspace & context. - public init(documentationBundleURL: URL, outputURL: URL, bundleIdentifier: String, diagnosticEngine: DiagnosticEngine = .init()) throws { + public init(documentationBundleURL: URL, outputURL: URL, bundleIdentifier: String, diagnosticEngine: DiagnosticEngine = .init()) throws + { // Initialize the action context. self.rootURL = documentationBundleURL self.outputURL = outputURL @@ -34,7 +35,7 @@ public struct IndexAction: AsyncAction { /// Converts each eligible file from the source documentation bundle, /// saves the results in the given output alongside the template files. - mutating public func perform(logHandle: inout LogHandle) async throws -> ActionResult { + mutating public func perform(logHandle: LogHandle) throws -> ActionResult { let problems = try buildIndex() diagnosticEngine.emit(problems) diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Init/InitAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Init/InitAction.swift index 2e6f1f4167..76da69595d 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Init/InitAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Init/InitAction.swift @@ -12,8 +12,8 @@ import Foundation import SwiftDocC /// An action that generates a documentation catalog from a template seed. -public struct InitAction: AsyncAction { - +public struct InitAction: Action { + enum Error: DescribedError { case catalogAlreadyExists var errorDescription: String { @@ -72,7 +72,8 @@ public struct InitAction: AsyncAction { /// Generates a documentation catalog from a catalog template. /// /// - Parameter logHandle: The file handle that the convert and preview actions will print debug messages to. - public mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult { + public mutating func perform(logHandle: SwiftDocC.LogHandle) throws -> ActionResult { + let diagnosticEngine: DiagnosticEngine = DiagnosticEngine(treatWarningsAsErrors: false) diagnosticEngine.filterLevel = .warning diagnosticEngine.add(DiagnosticConsoleWriter(formattingOptions: [])) diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift index 48559ff79c..a9ace04e2c 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift @@ -13,7 +13,7 @@ import SwiftDocC import Markdown /// An action that merges a list of documentation archives into a combined archive. -struct MergeAction: AsyncAction { +struct MergeAction: Action { var archives: [URL] var landingPageInfo: LandingPageInfo var outputURL: URL @@ -33,7 +33,7 @@ struct MergeAction: AsyncAction { } } - mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult { + mutating func perform(logHandle: LogHandle) throws -> ActionResult { guard let firstArchive = archives.first else { // A validation warning should have already been raised in `Docc/Merge/InputAndOutputOptions/validate()`. return ActionResult(didEncounterError: true, outputs: []) diff --git a/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift index 8d9d6d5c53..0211cfc9d6 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift @@ -33,7 +33,7 @@ fileprivate func trapSignals() { } /// An action that monitors a documentation bundle for changes and runs a live web-preview. -public final class PreviewAction: AsyncAction { +public final class PreviewAction: Action, RecreatingContext { /// A test configuration allowing running multiple previews for concurrent testing. static var allowConcurrentPreviews = false @@ -46,7 +46,8 @@ public final class PreviewAction: AsyncAction { let port: Int var convertAction: ConvertAction - + + public var setupContext: ((inout DocumentationContext) -> Void)? private var previewPaths: [String] = [] // Use for testing to override binding to a system port @@ -95,7 +96,7 @@ public final class PreviewAction: AsyncAction { /// > Important: On macOS, the bundle will be converted each time the source is modified. /// /// - Parameter logHandle: The file handle that the convert and preview actions will print debug messages to. - public func perform(logHandle: inout LogHandle) async throws -> ActionResult { + public func perform(logHandle: LogHandle) throws -> ActionResult { self.logHandle = logHandle if let rootURL = convertAction.rootURL { @@ -109,21 +110,19 @@ public final class PreviewAction: AsyncAction { print("Template: \(htmlTemplateDirectory.path)", to: &self.logHandle) } - let previewResult = try await preview() + let previewResult = try preview() return ActionResult(didEncounterError: previewResult.didEncounterError, outputs: [convertAction.targetDirectory]) } /// Stops a currently running preview session. func stop() throws { - monitoredConvertTask?.cancel() - try servers[serverIdentifier]?.stop() servers.removeValue(forKey: serverIdentifier) } - func preview() async throws -> ActionResult { + func preview() throws -> ActionResult { // Convert the documentation source for previewing. - let result = try await convert() + let result = try convert() guard !result.didEncounterError else { return result } @@ -164,10 +163,14 @@ public final class PreviewAction: AsyncAction { return previewResult } - func convert() async throws -> ActionResult { - convertAction = try createConvertAction() - let result = try await convertAction.perform(logHandle: &logHandle) + func convert() throws -> ActionResult { + // `cancel()` will throw `cancelPending` if there is already queued conversion. + try convertAction.cancel() + convertAction = try createConvertAction() + convertAction.setupContext = setupContext + + let result = try convertAction.perform(logHandle: logHandle) previewPaths = try convertAction.context.previewPaths() return result } @@ -182,8 +185,6 @@ public final class PreviewAction: AsyncAction { print("\t \(spacing) \(base.appendingPathComponent(previewPath).absoluteString)", to: &logHandle) } } - - var monitoredConvertTask: Task? } // Monitoring a source folder: Asynchronous output reading and file system events are supported only on macOS. @@ -197,48 +198,42 @@ extension PreviewAction { guard let rootURL = convertAction.rootURL else { return } - - monitor = try DirectoryMonitor(root: rootURL) { _, _ in - print("Source bundle was modified, converting... ", terminator: "", to: &self.logHandle) - self.monitoredConvertTask?.cancel() - self.monitoredConvertTask = Task { - defer { - // Reload the directory contents and start to monitor for changes. - do { - try monitor.restart() - } catch { - // The file watching system API has thrown, stop watching. - print("Watching for changes has failed. To continue preview with watching restart docc.", to: &self.logHandle) - print(error.localizedDescription, to: &self.logHandle) - } - } - + monitor = try DirectoryMonitor(root: rootURL) { _, folderURL in + defer { + // Reload the directory contents and start to monitor for changes. do { - let result = try await self.convert() - if result.didEncounterError { - throw ErrorsEncountered() - } - print("Done.", to: &self.logHandle) - } catch ConvertAction.Error.cancelPending { - // `monitor.restart()` is already queueing a new convert action which will start when the previous one completes. - // We can safely ignore the current action and just log to the console. - print("\nConversion already in progress...", to: &self.logHandle) - } catch DocumentationContext.ContextError.registrationDisabled { - // The context cancelled loading the bundles and threw to yield execution early. - print("\nConversion cancelled...", to: &self.logHandle) - } catch is CancellationError { - print("\nConversion cancelled...", to: &self.logHandle) + try monitor.restart() } catch { - print("\n\(error.localizedDescription)\nCompilation failed", to: &self.logHandle) + // The file watching system API has thrown, stop watching. + print("Watching for changes has failed. To continue preview with watching restart docc.", to: &self.logHandle) + print(error.localizedDescription, to: &self.logHandle) + } + } + + do { + print("Source bundle was modified, converting... ", terminator: "", to: &self.logHandle) + let result = try self.convert() + if result.didEncounterError { + throw ErrorsEncountered() } + print("Done.", to: &self.logHandle) + } catch ConvertAction.Error.cancelPending { + // `monitor.restart()` is already queueing a new convert action which will start when the previous one completes. + // We can safely ignore the current action and just log to the console. + print("\nConversion already in progress...", to: &self.logHandle) + } catch DocumentationContext.ContextError.registrationDisabled { + // The context cancelled loading the bundles and threw to yield execution early. + print("\nConversion cancelled...", to: &self.logHandle) + } catch { + print("\n\(error.localizedDescription)\nCompilation failed", to: &self.logHandle) } } try monitor.start() print("Monitoring \(rootURL.path) for changes...", to: &self.logHandle) } } -#endif // !os(Linux) && !os(Android) -#endif // canImport(NIOHTTP1) +#endif +#endif extension DocumentationContext { diff --git a/Sources/SwiftDocCUtilities/Action/Actions/TransformForStaticHostingAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/TransformForStaticHostingAction.swift index fe20cb2e76..3c0b9e7fd6 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/TransformForStaticHostingAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/TransformForStaticHostingAction.swift @@ -12,7 +12,8 @@ import Foundation import SwiftDocC /// An action that emits a static hostable website from a DocC Archive. -struct TransformForStaticHostingAction: AsyncAction { +struct TransformForStaticHostingAction: Action { + let rootURL: URL let outputURL: URL let hostingBasePath: String? @@ -29,8 +30,8 @@ struct TransformForStaticHostingAction: AsyncAction { hostingBasePath: String?, htmlTemplateDirectory: URL, fileManager: FileManagerProtocol = FileManager.default, - diagnosticEngine: DiagnosticEngine = .init() - ) throws { + diagnosticEngine: DiagnosticEngine = .init()) throws + { // Initialize the action context. self.rootURL = documentationBundleURL self.outputURL = outputURL ?? documentationBundleURL @@ -44,16 +45,19 @@ struct TransformForStaticHostingAction: AsyncAction { /// Converts each eligible file from the source archive and /// saves the results in the given output folder. - mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult { + mutating func perform(logHandle: LogHandle) throws -> ActionResult { try emit() return ActionResult(didEncounterError: false, outputs: [outputURL]) } mutating private func emit() throws { + + // If the emit is to create the static hostable content outside of the source archive // then the output folder needs to be set up and the archive data copied // to the new folder. if outputIsExternal { + try setupOutputDirectory(outputURL: outputURL) // Copy the appropriate folders from the archive. @@ -100,6 +104,7 @@ struct TransformForStaticHostingAction: AsyncAction { /// Create output directory or empty its contents if it already exists. private func setupOutputDirectory(outputURL: URL) throws { + var isDirectory: ObjCBool = false if fileManager.fileExists(atPath: outputURL.path, isDirectory: &isDirectory), isDirectory.boolValue { let contents = try fileManager.contentsOfDirectory(at: outputURL, includingPropertiesForKeys: [], options: [.skipsHiddenFiles]) diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift index 4edf038f8b..ebe2a410b1 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift @@ -11,16 +11,15 @@ import SwiftDocC import Foundation -extension AsyncAction { +extension Action { /// Performs the action and checks for any problems in the result. - /// + /// /// - Parameter logHandle: The log handle to write encountered warnings and errors to. /// /// - Throws: `ErrorsEncountered` if any errors are produced while performing the action. - public mutating func performAndHandleResult(logHandle: LogHandle = .standardOutput) async throws { - var logHandle = logHandle + public mutating func performAndHandleResult(logHandle: LogHandle = .standardOutput) throws { // Perform the Action and collect the result - let result = try await perform(logHandle: &logHandle) + let result = try perform(logHandle: logHandle) // Throw if any errors were encountered. Errors will have already been // reported to the user by the diagnostic engine. diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift index ab98c07118..35a7d603ca 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift @@ -14,7 +14,7 @@ import Foundation extension Docc { /// Converts documentation markup, assets, and symbol information into a documentation archive. - public struct Convert: AsyncParsableCommand { + public struct Convert: ParsableCommand { public init() {} /// The name of the directory docc will write its build artifacts to. @@ -625,6 +625,8 @@ extension Docc { set { featureFlags.emitLMDBIndex = newValue } } + + // MARK: - ParsableCommand conformance public mutating func validate() throws { if transformForStaticHosting { @@ -673,9 +675,12 @@ extension Docc { } } - public func run() async throws { + public mutating func run() throws { + // Initialize a `ConvertAction` from the current options in the `Convert` command. var convertAction = try ConvertAction(fromConvertCommand: self) - try await convertAction.performAndHandleResult() + + // Perform the conversion and print any warnings or errors found + try convertAction.performAndHandleResult() } // MARK: Warnings diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift index c0d953efbb..a97203c530 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023 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 @@ -13,7 +13,7 @@ import ArgumentParser extension Docc.ProcessCatalog { /// Emits documentation extension files that reflect the auto-generated curation. - struct EmitGeneratedCuration: AsyncParsableCommand { + struct EmitGeneratedCuration: ParsableCommand { static var configuration = CommandConfiguration( commandName: "emit-generated-curation", @@ -155,10 +155,12 @@ extension Docc.ProcessCatalog { get { generationOptions.startingPointSymbolLink } set { generationOptions.startingPointSymbolLink = newValue } } + + // MARK: - Execution - func run() async throws { + mutating func run() throws { var action = try EmitGeneratedCurationAction(fromCommand: self) - try await action.performAndHandleResult() + try action.performAndHandleResult() } } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Index.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Index.swift index 224c9b267c..41ba02a7c1 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Index.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Index.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors + Copyright (c) 2021 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 @@ -13,12 +13,17 @@ import Foundation extension Docc { /// Indexes a documentation bundle. - public struct Index: AsyncParsableCommand { + public struct Index: ParsableCommand { + public init() {} + // MARK: - Configuration + public static var configuration = CommandConfiguration( abstract: "Create an index for the documentation from compiled data.") + // MARK: - Command Line Options & Arguments + /// The user-provided path to a `.doccarchive` documentation archive. @OptionGroup() public var documentationBundle: DocCArchiveOption @@ -31,20 +36,27 @@ extension Docc { @Flag(help: "Print out the index information while the process runs.") public var verbose = false + // MARK: - Computed Properties + /// The path to the directory that all build output should be placed in. public var outputURL: URL { documentationBundle.urlOrFallback.appendingPathComponent("index", isDirectory: true) } - public func run() async throws { + // MARK: - Execution + + public mutating func run() throws { + // Initialize an `IndexAction` from the current options in the `Index` command. var indexAction = try IndexAction(fromIndexCommand: self) - try await indexAction.performAndHandleResult() + + // Perform the index and print any warnings or errors found + try indexAction.performAndHandleResult() } } // This command wraps the Index command so that we can still support it as a top-level command without listing it in the help // text (but still list the Index command as a subcommand of the ProcessArchive command). - struct _Index: AsyncParsableCommand { + struct _Index: ParsableCommand { init() {} static var configuration = CommandConfiguration( @@ -56,8 +68,8 @@ extension Docc { @OptionGroup var command: Index - public func run() async throws { - try await command.run() + public mutating func run() throws { + try command.run() } } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Init.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Init.swift index 51d6ba34d1..52e2cbaa4b 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Init.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Init.swift @@ -13,20 +13,33 @@ import Foundation import SwiftDocC extension Docc { - public struct Init: AsyncParsableCommand { - public init() {} + public struct Init: ParsableCommand { + + public init() { + } + + // MARK: - Configuration public static var configuration: CommandConfiguration = CommandConfiguration( - abstract: "Generate a documentation catalog from the selected template." + abstract: "Generate a documentation catalog from the selected template.", + shouldDisplay: true ) + // MARK: - Command Line Options & Arguments + /// The options used for configuring the init action. @OptionGroup public var initOptions: InitOptions - public func run() async throws { + // MARK: - Execution + + public mutating func run() throws { + + // Initialize a `InitAction` from the current options in the `Init` command. var initAction = try InitAction(fromInitOptions: initOptions) - try await initAction.performAndHandleResult() + + // Perform the action and print any warnings or errors found. + try initAction.performAndHandleResult() } } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Merge.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Merge.swift index cde3cae489..8b39ee0ea4 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Merge.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Merge.swift @@ -14,7 +14,7 @@ import Foundation extension Docc { /// Merge a list of documentation archives into a combined archive. - public struct Merge: AsyncParsableCommand { + public struct Merge: ParsableCommand { public init() {} public static var configuration = CommandConfiguration( @@ -167,7 +167,7 @@ extension Docc { synthesizedLandingPageOptions.topicStyle } - public func run() async throws { + public mutating func run() throws { // Initialize a `ConvertAction` from the current options in the `Convert` command. var convertAction = MergeAction( archives: archives, @@ -177,7 +177,7 @@ extension Docc { ) // Perform the conversion and print any warnings or errors found - try await convertAction.performAndHandleResult() + try convertAction.performAndHandleResult() } } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift index b7898f87cd..26d1a5941f 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors + Copyright (c) 2021 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 @@ -14,9 +14,12 @@ import Foundation extension Docc { /// Runs the ``Convert`` command and then sets up a web server that can be used to preview that documentation content. - public struct Preview: AsyncParsableCommand { + public struct Preview: ParsableCommand { + public init() {} + // MARK: - Configuration + public static var configuration = CommandConfiguration( abstract: "Convert documentation inputs and preview the documentation output.", usage: """ @@ -28,10 +31,14 @@ extension Docc { The 'preview' command extends the 'convert' command by running a preview server and monitoring the documentation input for modifications to rebuild the documentation. """ ) + + // MARK: - Command Line Options & Arguments /// The options used for configuring the preview server. @OptionGroup(title: "Preview options") public var previewOptions: PreviewOptions + + // MARK: - Property Validation public mutating func validate() throws { // The default template wasn't validated by the Convert command. @@ -43,9 +50,14 @@ extension Docc { } } - public func run() async throws { + // MARK: - Execution + + public mutating func run() throws { + // Initialize a `PreviewAction` from the current options in the `Preview` command. var previewAction = try PreviewAction(fromPreviewOptions: previewOptions) - try await previewAction.performAndHandleResult() + + // Perform the preview and print any warnings or errors found + try previewAction.performAndHandleResult() } } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift index 59a4fad713..59da03fff5 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors + Copyright (c) 2021 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 @@ -13,7 +13,7 @@ import Foundation extension Docc { /// Processes an action on an archive - struct ProcessArchive: AsyncParsableCommand { + struct ProcessArchive: ParsableCommand { static var configuration = CommandConfiguration( commandName: "process-archive", diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessCatalog.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessCatalog.swift index 8255fea90f..ed0bb55f3f 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessCatalog.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessCatalog.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023 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 @@ -13,7 +13,7 @@ import Foundation extension Docc { /// Processes an action on a catalog - struct ProcessCatalog: AsyncParsableCommand { + struct ProcessCatalog: ParsableCommand { static var configuration = CommandConfiguration( commandName: "process-catalog", diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/TransformForStaticHosting.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/TransformForStaticHosting.swift index 96d3d9d894..419bfc0b72 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/TransformForStaticHosting.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/TransformForStaticHosting.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors + Copyright (c) 2021 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 @@ -13,7 +13,7 @@ import ArgumentParser extension Docc.ProcessArchive { /// Emits a statically hostable website from a DocC Archive. - struct TransformForStaticHosting: AsyncParsableCommand { + struct TransformForStaticHosting: ParsableCommand { static var configuration = CommandConfiguration( commandName: "transform-for-static-hosting", @@ -47,6 +47,7 @@ extension Docc.ProcessArchive { var templateOption: TemplateOption mutating func validate() throws { + if let templateURL = templateOption.templateURL { let indexTemplate = templateURL.appendingPathComponent(HTMLTemplate.templateFileName.rawValue) if !FileManager.default.fileExists(atPath: indexTemplate.path) { @@ -61,10 +62,15 @@ extension Docc.ProcessArchive { ) } } + + // MARK: - Execution - func run() async throws { + mutating func run() throws { + // Initialize an `TransformForStaticHostingAction` from the current options in the `TransformForStaticHostingAction` command. var action = try TransformForStaticHostingAction(fromCommand: self) - try await action.performAndHandleResult() + + // Perform the emit and print any warnings or errors found + try action.performAndHandleResult() } } } diff --git a/Sources/SwiftDocCUtilities/Docc.swift b/Sources/SwiftDocCUtilities/Docc.swift index acffea6400..49ac4bb9cf 100644 --- a/Sources/SwiftDocCUtilities/Docc.swift +++ b/Sources/SwiftDocCUtilities/Docc.swift @@ -10,8 +10,8 @@ import ArgumentParser -private var subcommands: [AsyncParsableCommand.Type] { - var subcommands: [AsyncParsableCommand.Type] = [ +private var subcommands: [ParsableCommand.Type] { + var subcommands: [ParsableCommand.Type] = [ Docc.Convert.self, Docc.ProcessArchive.self, Docc.ProcessCatalog.self, @@ -34,7 +34,7 @@ private var usage: String { } /// The default, command-line interface you use to compile and preview documentation. -public struct Docc: AsyncParsableCommand { +public struct Docc: ParsableCommand { public static var configuration = CommandConfiguration( abstract: "Documentation Compiler: compile, analyze, and preview documentation.", usage: usage, diff --git a/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift b/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift index 95e8b2edef..03ad44572f 100644 --- a/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift +++ b/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift @@ -126,10 +126,10 @@ final class PreviewServer { .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) // Configure the channel handler - it handles plain HTTP requests - .childChannelInitializer { [contentURL] channel in + .childChannelInitializer { channel in // HTTP pipeline return channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true).flatMap { - channel.pipeline.addHandler(PreviewHTTPHandler(rootURL: contentURL)) + channel.pipeline.addHandler(PreviewHTTPHandler(rootURL: self.contentURL)) } } @@ -184,11 +184,5 @@ final class PreviewServer { try threadPool.syncShutdownGracefully() print("Stopped preview server at \(bindTo)", to: &logHandle) } - - deinit { - if channel?.isWritable == true { - try? stop() - } - } } #endif diff --git a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md index ba608e6ea0..1a6f094160 100644 --- a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md +++ b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md @@ -11,7 +11,7 @@ Use SwiftDocCUtilities to build a custom, command-line interface and extend it w ```swift import ArgumentParser -public struct MyDocumentationTool: AsyncParsableCommand { +public struct MyDocumentationTool: ParsableCommand { public static var configuration = CommandConfiguration( abstract: "My custom documentation tool", subcommands: [ConvertAction.self, ExampleAction.self] diff --git a/Sources/docc/main.swift b/Sources/docc/main.swift index 30852ca2ad..2a157a7dd0 100644 --- a/Sources/docc/main.swift +++ b/Sources/docc/main.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors + Copyright (c) 2021 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,9 +11,7 @@ #if os(macOS) || os(Linux) || os(Android) || os(Windows) import SwiftDocCUtilities -await Task { - await Docc.main() -}.value +Docc.main() #else fatalError("Command line interface supported only on macOS, Linux and Windows platforms.") #endif diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift index 047373eac6..2573eb9395 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift @@ -16,7 +16,7 @@ import SwiftDocCTestUtilities class ConvertActionStaticHostableTests: StaticHostingBaseTests { /// Creates a DocC archive and then archives it with options to produce static content which is then validated. - func testConvertActionStaticHostableTestOutput() async throws { + func testConvertActionStaticHostableTestOutput() throws { let bundleURL = Bundle.module.url(forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! let targetURL = try createTemporaryDirectory() @@ -44,7 +44,7 @@ class ConvertActionStaticHostableTests: StaticHostingBaseTests { transformForStaticHosting: true, hostingBasePath: basePath ) - _ = try await action.perform(logHandle: .none) + _ = try action.perform(logHandle: .none) // Test the content of the output folder. var expectedContent = ["data", "documentation", "tutorials", "downloads", "images", "metadata.json" ,"videos", "index.html", "index"] diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift index 63ba225566..a183249220 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift @@ -36,7 +36,7 @@ class ConvertActionTests: XCTestCase { forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! .appendingPathComponent("project.zip") - func testCopyingImageAssets() async throws { + func testCopyingImageAssets() throws { XCTAssert(FileManager.default.fileExists(atPath: imageFile.path)) let testImageName = "TestImage.png" @@ -61,8 +61,8 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) + let result = try action.perform(logHandle: .none) - let result = try await action.perform(logHandle: .none) // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ Folder(name: "images", content: [ @@ -81,7 +81,7 @@ class ConvertActionTests: XCTestCase { XCTAssertEqual(copiedImageOutput, [testImageName]) } - func testCopyingVideoAssets() async throws { + func testCopyingVideoAssets() throws { let videoFile = Bundle.module.url( forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! .appendingPathComponent("introvideo.mp4") @@ -110,7 +110,7 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ @@ -131,7 +131,7 @@ class ConvertActionTests: XCTestCase { } // Ensures we don't regress on copying download assets to the build folder (72599615) - func testCopyingDownloadAssets() async throws { + func testCopyingDownloadAssets() throws { let downloadFile = Bundle.module.url( forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! .appendingPathComponent("project.zip") @@ -171,7 +171,7 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ @@ -186,7 +186,7 @@ class ConvertActionTests: XCTestCase { // Ensures we always create the required asset folders even if no assets are explicitly // provided - func testCreationOfAssetFolders() async throws { + func testCreationOfAssetFolders() throws { // Empty documentation bundle let bundle = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), @@ -207,7 +207,7 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ @@ -218,7 +218,7 @@ class ConvertActionTests: XCTestCase { expectedOutput.assertExist(at: result.outputs[0], fileManager: testDataProvider) } - func testConvertsWithoutErrorsWhenBundleIsNotAtRoot() async throws { + func testConvertsWithoutErrorsWhenBundleIsNotAtRoot() throws { let bundle = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), ]) @@ -240,11 +240,11 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertEqual(result.problems.count, 0) } - func testConvertWithoutBundle() async throws { + func testConvertWithoutBundle() throws { let myKitSymbolGraph = Bundle.module.url(forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! .appendingPathComponent("mykit-iOS.symbols.json") @@ -278,7 +278,7 @@ class ConvertActionTests: XCTestCase { ) ) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertEqual(result.problems.count, 0) XCTAssertEqual(result.outputs, [outputLocation.absoluteURL]) @@ -380,7 +380,7 @@ class ConvertActionTests: XCTestCase { XCTAssertThrowsError(try action.moveOutput(from: source.absoluteURL, to: targetURL)) } - func testConvertDoesNotLowercasesResourceFileNames() async throws { + func testConvertDoesNotLowercasesResourceFileNames() throws { // Documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ CopyOfFile(original: imageFile, newName: "TEST.png"), @@ -403,7 +403,7 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ @@ -423,7 +423,7 @@ class ConvertActionTests: XCTestCase { // Ensures that render JSON produced by the convert action // does not include file location information for symbols. - func testConvertDoesNotIncludeFilePathsInRenderNodes() async throws { + func testConvertDoesNotIncludeFilePathsInRenderNodes() throws { // Documentation bundle that contains a symbol graph. // The symbol graph contains symbols that include location information. let bundle = Folder(name: "unit-test.docc", content: [ @@ -446,8 +446,8 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) + let result = try action.perform(logHandle: .none) - let result = try await action.perform(logHandle: .none) // Construct the URLs for the produced render json: let documentationDataDirectoryURL = result.outputs[0] @@ -493,7 +493,7 @@ class ConvertActionTests: XCTestCase { } // Ensures that render JSON produced by the convert action does not include symbol access level information. - func testConvertDoesNotIncludeSymbolAccessLevelsInRenderNodes() async throws { + func testConvertDoesNotIncludeSymbolAccessLevelsInRenderNodes() throws { // Documentation bundle that contains a symbol graph. // The symbol graph contains symbols that include access level information. let bundle = Folder(name: "unit-test.docc", content: [ @@ -516,7 +516,7 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Construct the URLs for the produced render json: @@ -561,7 +561,7 @@ class ConvertActionTests: XCTestCase { } } - func testOutputFolderContainsDiagnosticJSONWhenThereAreWarnings() async throws { + func testOutputFolderContainsDiagnosticJSONWhenThereAreWarnings() throws { // Documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ CopyOfFile(original: imageFile, newName: "referenced-tutorials-image.png"), @@ -604,7 +604,7 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ @@ -630,7 +630,7 @@ class ConvertActionTests: XCTestCase { expectedOutput.assertExist(at: result.outputs[0], fileManager: testDataProvider) } - func testOutputFolderContainsDiagnosticJSONWhenThereAreErrorsAndNoTemplate() async throws { + func testOutputFolderContainsDiagnosticJSONWhenThereAreErrorsAndNoTemplate() throws { // Documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ TextFile(name: "TechnologyX.tutorial", utf8Content: """ @@ -664,7 +664,7 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticLevel: "hint") // report all errors during the test - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ @@ -698,7 +698,7 @@ class ConvertActionTests: XCTestCase { expectedOutput.assertExist(at: result.outputs[0], fileManager: testDataProvider) } - func testWarningForUncuratedTutorial() async throws { + func testWarningForUncuratedTutorial() throws { // Documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ TextFile(name: "TechnologyX.tutorial", utf8Content: """ @@ -745,7 +745,7 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticLevel: "hint") // report all errors during the test - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ @@ -771,7 +771,7 @@ class ConvertActionTests: XCTestCase { expectedOutput.assertExist(at: result.outputs[0], fileManager: testDataProvider) } - func testOutputFolderIsNotRemovedWhenThereAreErrors() async throws { + func testOutputFolderIsNotRemovedWhenThereAreErrors() throws { let tutorialsFile = TextFile(name: "TechnologyX.tutorial", utf8Content: """ @Tutorials(name: "Technology Z") { @Intro(title: "Technology Z") { @@ -824,7 +824,7 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertFalse( result.didEncounterError, @@ -843,7 +843,7 @@ class ConvertActionTests: XCTestCase { /// Verifies that digest is correctly emitted for API documentation topics /// like module pages, symbols, and articles. - func testMetadataIsWrittenToOutputFolderAPIDocumentation() async throws { + func testMetadataIsWrittenToOutputFolderAPIDocumentation() throws { // Example documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ // An asset @@ -926,7 +926,7 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Because the page order isn't deterministic, we create the indexing records and linkable entities in the same order as the pages. let indexingRecords: [IndexingRecord] = action.context.knownPages.compactMap { reference in @@ -1102,7 +1102,7 @@ class ConvertActionTests: XCTestCase { XCTAssertEqual(resultAssets.images.map({ $0.identifier.identifier }).sorted(), images.map({ $0.identifier.identifier }).sorted()) } - func testLinkableEntitiesMetadataIncludesOverloads() async throws { + func testLinkableEntitiesMetadataIncludesOverloads() throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) let bundle = try Folder.createFromDisk( @@ -1134,7 +1134,7 @@ class ConvertActionTests: XCTestCase { dataProvider: testDataProvider, fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory()) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) guard let resultLikableEntities: [LinkDestinationSummary] = contentsOfJSONFile(url: result.outputs[0].appendingPathComponent("linkable-entities.json")) else { XCTFail("Can't find linkable-entities.json in output") @@ -1160,7 +1160,7 @@ class ConvertActionTests: XCTestCase { ]) } - func testDownloadMetadataIsWrittenToOutputFolder() async throws { + func testDownloadMetadataIsWrittenToOutputFolder() throws { let bundle = Folder(name: "unit-test.docc", content: [ CopyOfFile(original: projectZipFile), CopyOfFile(original: imageFile, newName: "referenced-tutorials-image.png"), @@ -1256,7 +1256,7 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) func contentsOfJSONFile(url: URL) -> Result? { guard let data = testDataProvider.contents(atPath: url.path) else { @@ -1280,7 +1280,7 @@ class ConvertActionTests: XCTestCase { })) } - func testMetadataIsWrittenToOutputFolder() async throws { + func testMetadataIsWrittenToOutputFolder() throws { // Example documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ CopyOfFile(original: imageFile, newName: "referenced-article-image.png"), @@ -1347,7 +1347,7 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // Because the page order isn't deterministic, we create the indexing records and linkable entities in the same order as the pages. let indexingRecords: [IndexingRecord] = action.context.knownPages.compactMap { reference in @@ -1485,8 +1485,8 @@ class ConvertActionTests: XCTestCase { XCTAssertEqual(resultAssets.images.map({ $0.identifier.identifier }).sorted(), images.map({ $0.identifier.identifier }).sorted()) } + func testMetadataIsOnlyWrittenToOutputFolderWhenEmitDigestFlagIsSet() throws { - func testMetadataIsOnlyWrittenToOutputFolderWhenEmitDigestFlagIsSet() async throws { // An empty documentation bundle let bundle = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), @@ -1510,7 +1510,7 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertTrue(testDataProvider.fileExists(atPath: result.outputs[0].appendingPathComponent("assets.json").path)) XCTAssertTrue(testDataProvider.fileExists(atPath: result.outputs[0].appendingPathComponent("diagnostics.json").path)) @@ -1536,7 +1536,7 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertFalse(testDataProvider.fileExists(atPath: result.outputs[0].appendingPathComponent("assets.json").path)) XCTAssertFalse(testDataProvider.fileExists(atPath: result.outputs[0].appendingPathComponent("diagnostics.json").path)) @@ -1569,7 +1569,7 @@ class ConvertActionTests: XCTestCase { } } - func testMetadataIsOnlyWrittenToOutputFolderWhenDocumentationCoverage() async throws { + func testMetadataIsOnlyWrittenToOutputFolderWhenDocumentationCoverage() throws { // An empty documentation bundle, except for a single symbol graph file // containing 8 symbols. @@ -1601,12 +1601,12 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), documentationCoverageOptions: .noCoverage) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertFalse(testDataProvider.fileExists(atPath: result.outputs[0].appendingPathComponent("documentation-coverage.json").path)) // Rerun the convert and test no coverage info structs were consumed - _ = try action.converter.convert(outputConsumer: TestDocumentationCoverageConsumer(coverageConsumeHandler: coverageInfoHandler)) + let _ = try action.converter.convert(outputConsumer: TestDocumentationCoverageConsumer(coverageConsumeHandler: coverageInfoHandler)) XCTAssertEqual(coverageInfoCount, 0) } @@ -1628,13 +1628,13 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), documentationCoverageOptions: DocumentationCoverageOptions(level: .brief)) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertTrue(testDataProvider.fileExists(atPath: result.outputs[0].appendingPathComponent("documentation-coverage.json").path)) // Rerun the convert and test one coverage info structs was consumed for each symbol page (8) coverageInfoCount = 0 - _ = try action.converter.convert(outputConsumer: TestDocumentationCoverageConsumer(coverageConsumeHandler: coverageInfoHandler)) + let _ = try action.converter.convert(outputConsumer: TestDocumentationCoverageConsumer(coverageConsumeHandler: coverageInfoHandler)) XCTAssertEqual(coverageInfoCount, 8) } @@ -1656,13 +1656,13 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), documentationCoverageOptions: DocumentationCoverageOptions(level: .detailed)) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertTrue(testDataProvider.fileExists(atPath: result.outputs[0].appendingPathComponent("documentation-coverage.json").path)) // Rerun the convert and test one coverage info structs was consumed for each symbol page (8) coverageInfoCount = 0 - _ = try action.converter.convert(outputConsumer: TestDocumentationCoverageConsumer(coverageConsumeHandler: coverageInfoHandler)) + let _ = try action.converter.convert(outputConsumer: TestDocumentationCoverageConsumer(coverageConsumeHandler: coverageInfoHandler)) XCTAssertEqual(coverageInfoCount, 8) } } @@ -1769,7 +1769,7 @@ class ConvertActionTests: XCTestCase { ]) } - func testResolvedTopicReferencesAreCachedByDefaultWhenConverting() async throws { + func testResolvedTopicReferencesAreCachedByDefaultWhenConverting() throws { let bundle = Folder( name: "unit-test.docc", content: [ @@ -1795,13 +1795,13 @@ class ConvertActionTests: XCTestCase { temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) - _ = try await action.perform(logHandle: .none) + _ = try action.perform(logHandle: .none) XCTAssertEqual(ResolvedTopicReference._numberOfCachedReferences(bundleID: #function), 13) } - func testIgnoresAnalyzerHintsByDefault() async throws { - func runCompiler(analyze: Bool) async throws -> [Problem] { + func testIgnoresAnalyzerHintsByDefault() throws { + func runCompiler(analyze: Bool) throws -> [Problem] { // This bundle has both non-analyze and analyze style warnings. let testBundleURL = Bundle.module.url( forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! @@ -1824,13 +1824,13 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticEngine: engine) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertFalse(result.didEncounterError) return engine.problems } - let analyzeDiagnostics = try await runCompiler(analyze: true) - let noAnalyzeDiagnostics = try await runCompiler(analyze: false) + let analyzeDiagnostics = try runCompiler(analyze: true) + let noAnalyzeDiagnostics = try runCompiler(analyze: false) XCTAssertTrue(analyzeDiagnostics.contains { $0.diagnostic.severity == .information }) XCTAssertFalse(noAnalyzeDiagnostics.contains { $0.diagnostic.severity == .information }) @@ -1846,7 +1846,7 @@ class ConvertActionTests: XCTestCase { /// Verify that the conversion of the same input given high concurrency and no concurrency, /// and also with and without generating digest produces the same results - func testConvertTestBundleWithHighConcurrency() async throws { + func testConvertTestBundleWithHighConcurrency() throws { let testBundleURL = Bundle.module.url( forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! let bundle = try Folder.createFromDisk(url: testBundleURL) @@ -1861,7 +1861,7 @@ 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: DocumentationWorkspaceDataProvider & FileManagerProtocol) throws -> ActionResult { // Run the create ConvertAction var configuration = DocumentationContext.Configuration() @@ -1887,7 +1887,7 @@ class ConvertActionTests: XCTestCase { action.converter.batchNodeCount = batchSize - return try await action.perform(logHandle: .none) + return try action.perform(logHandle: .none) } for withDigest in [false, true] { @@ -1895,11 +1895,11 @@ class ConvertActionTests: XCTestCase { // Set a batch size to a high number to have no concurrency let serialOutputURL = URL(string: "/serialOutput")! - let serialResult = try await convertTestBundle(batchSize: 10_000, emitDigest: withDigest, targetURL: serialOutputURL, testDataProvider: testDataProvider) + let serialResult = try convertTestBundle(batchSize: 10_000, emitDigest: withDigest, targetURL: serialOutputURL, testDataProvider: testDataProvider) // Set a batch size to 1 to have maximum concurrency (this is bad for performance maximizes our chances of encountering an issue). let parallelOutputURL = URL(string: "/parallelOutput")! - let parallelResult = try await convertTestBundle(batchSize: 1, emitDigest: withDigest, targetURL: parallelOutputURL, testDataProvider: testDataProvider) + let parallelResult = try convertTestBundle(batchSize: 1, emitDigest: withDigest, targetURL: parallelOutputURL, testDataProvider: testDataProvider) // Compare the results XCTAssertEqual( @@ -1928,7 +1928,7 @@ class ConvertActionTests: XCTestCase { } } - func testConvertActionProducesDeterministicOutput() async throws { + func testConvertActionProducesDeterministicOutput() throws { // Pretty printing the output JSON also enables sorting keys during encoding // which is required for testing if the conversion output is deterministic. let priorPrettyPrintValue = shouldPrettyPrintOutputJSON @@ -1950,7 +1950,7 @@ class ConvertActionTests: XCTestCase { ) let bundle = try Folder.createFromDisk(url: testBundleURL) - func performConvertAction(outputURL: URL, testFileSystem: TestFileSystem) async throws { + func performConvertAction(outputURL: URL, testFileSystem: TestFileSystem) throws { var action = try ConvertAction( documentationBundleURL: bundle.absoluteURL, outOfProcessResolver: nil, @@ -1965,7 +1965,7 @@ class ConvertActionTests: XCTestCase { ) action.diagnosticEngine.consumers.sync { $0.removeAll() } - _ = try await action.perform(logHandle: .none) + _ = try action.perform(logHandle: .none) } // We'll perform 3 sets of conversions to confirm the output is deterministic @@ -1977,11 +1977,11 @@ class ConvertActionTests: XCTestCase { // Convert the same bundle three times and place the output in // separate directories. - try await performConvertAction( + try performConvertAction( outputURL: URL(fileURLWithPath: "/1", isDirectory: true), testFileSystem: testFileSystem ) - try await performConvertAction( + try performConvertAction( outputURL: URL(fileURLWithPath: "/2", isDirectory: true), testFileSystem: testFileSystem ) @@ -2018,7 +2018,7 @@ class ConvertActionTests: XCTestCase { } } - func testConvertActionNavigatorIndexGeneration() async throws { + func testConvertActionNavigatorIndexGeneration() throws { // The navigator index needs to test with the real file manager let bundleURL = Bundle.module.url(forResource: "TestBundle", withExtension: "docc", subdirectory: "Test Bundles")! @@ -2039,7 +2039,7 @@ class ConvertActionTests: XCTestCase { buildIndex: true, temporaryDirectory: createTemporaryDirectory() // Create an index ) - _ = try await action.perform(logHandle: .none) + _ = try action.perform(logHandle: .none) let indexURL = targetURL.appendingPathComponent("index") @@ -2056,7 +2056,7 @@ class ConvertActionTests: XCTestCase { outputURL: indexURL, bundleIdentifier: indexFromConvertAction.bundleIdentifier ) - _ = try await indexAction.perform(logHandle: .none) + _ = try indexAction.perform(logHandle: .none) let indexFromIndexAction = try NavigatorIndex.readNavigatorIndex(url: indexURL) XCTAssertEqual(indexFromIndexAction.count, 37) @@ -2067,7 +2067,7 @@ class ConvertActionTests: XCTestCase { ) } - func testObjectiveCNavigatorIndexGeneration() async throws { + func testObjectiveCNavigatorIndexGeneration() throws { let bundle = Folder(name: "unit-test-objc.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), CopyOfFile(original: objectiveCSymbolGraphFile), @@ -2099,7 +2099,7 @@ class ConvertActionTests: XCTestCase { temporaryDirectory: createTemporaryDirectory() ) - _ = try await action.perform(logHandle: .none) + _ = try action.perform(logHandle: .none) let index = try NavigatorIndex.readNavigatorIndex(url: targetDirectory.appendingPathComponent("index")) func assertAllChildrenAreObjectiveC(_ node: NavigatorTree.Node) { @@ -2126,7 +2126,7 @@ class ConvertActionTests: XCTestCase { assertAllChildrenAreObjectiveC(firstChild) } - func testMixedLanguageNavigatorIndexGeneration() async throws { + func testMixedLanguageNavigatorIndexGeneration() throws { // The navigator index needs to test with the real File Manager let temporaryTestOutputDirectory = try createTemporaryDirectory() @@ -2151,7 +2151,7 @@ class ConvertActionTests: XCTestCase { temporaryDirectory: createTemporaryDirectory() ) - _ = try await action.perform(logHandle: .none) + _ = try action.perform(logHandle: .none) let index = try NavigatorIndex.readNavigatorIndex( url: temporaryTestOutputDirectory.appendingPathComponent("index") @@ -2270,7 +2270,7 @@ class ConvertActionTests: XCTestCase { ) } - func testDiagnosticLevel() async throws { + func testDiagnosticLevel() throws { let bundle = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), CopyOfFile(original: symbolGraphFile, newName: "MyKit.symbols.json"), @@ -2301,13 +2301,13 @@ class ConvertActionTests: XCTestCase { diagnosticLevel: "error", diagnosticEngine: engine ) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertEqual(engine.problems.count, 0, "\(ConvertAction.self) didn't filter out diagnostics at-or-above the 'error' level.") XCTAssertFalse(result.didEncounterError, "The issues with this test bundle are not severe enough to fail the build.") } - func testDiagnosticLevelIgnoredWhenAnalyzeIsPresent() async throws { + func testDiagnosticLevelIgnoredWhenAnalyzeIsPresent() throws { let bundle = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), CopyOfFile(original: symbolGraphFile, newName: "MyKit.symbols.json"), @@ -2338,7 +2338,7 @@ class ConvertActionTests: XCTestCase { diagnosticLevel: "error", diagnosticEngine: engine ) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) XCTAssertEqual(engine.problems.count, 1, "\(ConvertAction.self) shouldn't filter out diagnostics when the '--analyze' flag is passed") XCTAssertEqual(engine.problems.map { $0.diagnostic.identifier }, ["org.swift.docc.Article.Title.NotFound"]) @@ -2346,7 +2346,7 @@ class ConvertActionTests: XCTestCase { XCTAssert(engine.problems.contains(where: { $0.diagnostic.severity == .warning })) } - func testDoesNotIncludeDiagnosticsInThrownError() async throws { + func testDoesNotIncludeDiagnosticsInThrownError() throws { let bundle = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), CopyOfFile(original: symbolGraphFile, newName: "MyKit.symbols.json"), @@ -2375,10 +2375,10 @@ class ConvertActionTests: XCTestCase { temporaryDirectory: testDataProvider.uniqueTemporaryDirectory(), diagnosticLevel: "error" ) - try await action.performAndHandleResult(logHandle: .none) + XCTAssertNoThrow(try action.performAndHandleResult(logHandle: .none)) } - func testWritesDiagnosticFileWhenThrowingError() async throws { + func testWritesDiagnosticFileWhenThrowingError() throws { let bundle = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), CopyOfFile(original: symbolGraphFile, newName: "MyKit.symbols.json"), @@ -2414,7 +2414,7 @@ class ConvertActionTests: XCTestCase { // TODO: Support TestFileSystem in DiagnosticFileWriter XCTAssertFalse(FileManager.default.fileExists(atPath: diagnosticFile.path), "Diagnostic file doesn't exist before") - try await action.performAndHandleResult(logHandle: .none) + XCTAssertNoThrow(try action.performAndHandleResult(logHandle: .none)) XCTAssertTrue(FileManager.default.fileExists(atPath: diagnosticFile.path), "Diagnostic file exist after") } @@ -2461,7 +2461,7 @@ class ConvertActionTests: XCTestCase { XCTAssertEqual(action.context.configuration.externalMetadata.inheritDocs, false) } - func testEmitsDigest() async throws { + func testEmitsDigest() throws { let bundle = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), CopyOfFile(original: symbolGraphFile, newName: "MyKit.symbols.json"), @@ -2486,7 +2486,7 @@ class ConvertActionTests: XCTestCase { temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) - try await action.performAndHandleResult(logHandle: .none) + XCTAssertNoThrow(try action.performAndHandleResult(logHandle: .none)) XCTAssert(testDataProvider.fileExists(atPath: digestFileURL.path)) let data = try testDataProvider.contentsOfURL(digestFileURL) @@ -2494,7 +2494,7 @@ class ConvertActionTests: XCTestCase { XCTAssertEqual(diagnostics.count, 0) } - func testRenderIndexJSONGeneration() async throws { + func testRenderIndexJSONGeneration() throws { let catalog = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), CopyOfFile(original: symbolGraphFile, newName: "MyKit.symbols.json"), @@ -2519,19 +2519,19 @@ class ConvertActionTests: XCTestCase { temporaryDirectory: createTemporaryDirectory() ) + try action.performAndHandleResult(logHandle: .none) - try await action.performAndHandleResult(logHandle: .none) let indexDirectory = targetDirectory.appendingPathComponent("index", isDirectory: true) let renderIndexJSON = indexDirectory.appendingPathComponent("index.json", isDirectory: false) - try await action.performAndHandleResult(logHandle: .none) + try action.performAndHandleResult(logHandle: .none) XCTAssertTrue(FileManager.default.directoryExists(atPath: indexDirectory.path)) XCTAssertTrue(FileManager.default.fileExists(atPath: renderIndexJSON.path)) try XCTAssertEqual(FileManager.default.contentsOfDirectory(at: indexDirectory, includingPropertiesForKeys: nil).count, 1) } /// Verifies that a metadata.json file is created in the output folder with additional metadata. - func testCreatesBuildMetadataFileForBundleWithInfoPlistValues() async throws { + func testCreatesBuildMetadataFileForBundleWithInfoPlistValues() throws { let bundle = Folder( name: "unit-test.docc", content: [InfoPlist(displayName: "TestBundle", identifier: "com.test.example")] @@ -2553,7 +2553,7 @@ class ConvertActionTests: XCTestCase { fileManager: testDataProvider, temporaryDirectory: testDataProvider.uniqueTemporaryDirectory() ) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) let expectedOutput = Folder(name: ".docc-build", content: [ JSONFile( @@ -2567,7 +2567,7 @@ class ConvertActionTests: XCTestCase { // Tests that the default behavior of `docc convert` on the command-line does not throw an error // when processing a DocC catalog that does not actually produce documentation. (r91790147) - func testConvertDocCCatalogThatProducesNoDocumentationDoesNotThrowError() async throws { + func testConvertDocCCatalogThatProducesNoDocumentationDoesNotThrowError() throws { let emptyCatalog = Folder( name: "unit-test.docc", content: [InfoPlist(displayName: "TestBundle", identifier: "com.test.example")] @@ -2591,7 +2591,7 @@ class ConvertActionTests: XCTestCase { ) var action = try ConvertAction(fromConvertCommand: convertCommand) - _ = try await action.perform(logHandle: .none) + _ = try action.perform(logHandle: .none) } func emitEmptySymbolGraph(moduleName: String, destination: URL) throws { @@ -2627,7 +2627,7 @@ class ConvertActionTests: XCTestCase { // Tests that when `docc convert` is given input that produces multiple pages at the same path // on disk it does not throw an error when attempting to transform it for static hosting. (94311195) - func testConvertDocCCatalogThatProducesMultipleDocumentationPagesAtTheSamePathDoesNotThrowError() async throws { + func testConvertDocCCatalogThatProducesMultipleDocumentationPagesAtTheSamePathDoesNotThrowError() throws { let temporaryDirectory = try createTemporaryDirectory() let catalogURL = try Folder( @@ -2660,9 +2660,9 @@ class ConvertActionTests: XCTestCase { transformForStaticHosting: true ) - try await action.performAndHandleResult(logHandle: .none) + XCTAssertNoThrow(try action.performAndHandleResult(logHandle: .none)) } - func testConvertWithCustomTemplates() async throws { + func testConvertWithCustomTemplates() throws { let info = InfoPlist(displayName: "TestConvertWithCustomTemplates", identifier: "com.test.example") let index = TextFile(name: "index.html", utf8Content: """ @@ -2713,7 +2713,7 @@ class ConvertActionTests: XCTestCase { temporaryDirectory: createTemporaryDirectory(), experimentalEnableCustomTemplates: true ) - let result = try await action.perform(logHandle: .none) + let result = try action.perform(logHandle: .none) // The custom template contents should be wrapped in