diff --git a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift index e13d78f2fe..6af2b63403 100644 --- a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift +++ b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.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-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 @@ -192,7 +192,7 @@ public struct ConvertService: DocumentationService { guard conversionProblems.isEmpty else { throw ConvertServiceError.conversionError( - underlyingError: conversionProblems.localizedDescription) + underlyingError: DiagnosticConsoleWriter.formattedDescription(for: conversionProblems)) } let references: RenderReferenceStore? diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/Diagnostic.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/Diagnostic.swift index 6381c8f497..a695e87555 100644 --- a/Sources/SwiftDocC/Infrastructure/Diagnostics/Diagnostic.swift +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/Diagnostic.swift @@ -17,8 +17,7 @@ import SymbolKit public typealias BasicDiagnostic = Diagnostic /// A diagnostic explains a problem or issue that needs the end-user's attention. -public struct Diagnostic: DescribedError { - +public struct Diagnostic { /// The origin of the diagnostic, such as a file or process. public var source: URL? @@ -35,30 +34,21 @@ public struct Diagnostic: DescribedError { /// `org.swift.docc.SummaryContainsLink` public var identifier: String - /// Provides the short, localized abstract provided by ``localizedExplanation`` in plain text if an - /// explanation is available. - /// - /// At a bare minimum, all diagnostics must have at least one paragraph or sentence describing what the diagnostic is. - public var localizedSummary: String + /// A brief summary that describe the problem or issue. + public var summary: String + + @available(*, deprecated, renamed: "summary") + public var localizedSummary: String { + return summary + } - /// Provides a markup document for this diagnostic in the end-user's most preferred language, the base language - /// if one isn't available, or `nil` if no explanations are provided for this diagnostic's identifier. - /// - /// - Note: All diagnostics *must have* an explanation. If a diagnostic can't be explained in plain language - /// and easily understood by the reader, it should not be shown. - /// - /// An explanation should have at least the following items: - /// - /// - Document - /// - Abstract: A summary paragraph; one or two sentences. - /// - Discussion: A discussion of the situation and why it's interesting or a problem for the end-user. - /// This discussion should implicitly justify the diagnostic's existence. - /// - Heading, level 2, text: "Example" - /// - Problem Example: Show an example of the problematic situation and highlight important areas. - /// - Heading, level 2, text: "Solution" - /// - Solution: Explain what the end-user needs to do to correct the problem in plain language. - /// - Solution Example: Show the *Problem Example* as corrected and highlight the changes made. - public var localizedExplanation: String? + /// Additional details that explain the the problem or issue to the end-user in plain language. + public var explanation: String? + + @available(*, deprecated, renamed: "explanation") + public var localizedExplanation: String? { + return explanation + } /// Extra notes to tack onto the editor for additional information. /// @@ -79,8 +69,8 @@ public struct Diagnostic: DescribedError { self.severity = severity self.range = range self.identifier = identifier - self.localizedSummary = summary - self.localizedExplanation = explanation + self.summary = summary + self.explanation = explanation self.notes = notes } } @@ -95,13 +85,19 @@ public extension Diagnostic { range?.offsetWithRange(docRange) } +} + +// MARK: Deprecated +@available(*, deprecated, message: "Use 'DiagnosticConsoleWriter.formattedDescription(for:options:)' instead.") +extension Diagnostic: DescribedError { + @available(*, deprecated, message: "Use 'DiagnosticConsoleWriter.formattedDescription(for:options:)' instead.") var localizedDescription: String { - return DiagnosticConsoleWriter.formattedDescriptionFor(self) + return DiagnosticConsoleWriter.formattedDescription(for: self) } - var errorDescription: String { - return DiagnosticConsoleWriter.formattedDescriptionFor(self) + @available(*, deprecated, message: "Use 'DiagnosticConsoleWriter.formattedDescription(for:options:)' instead.") + public var errorDescription: String { + return DiagnosticConsoleWriter.formattedDescription(for: self) } } - diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift index 6113880015..48d2cab77d 100644 --- a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift @@ -17,14 +17,14 @@ public final class DiagnosticConsoleWriter: DiagnosticFormattingConsumer { var outputStream: TextOutputStream public var formattingOptions: DiagnosticFormattingOptions + private var diagnosticFormatter: DiagnosticConsoleFormatter /// Creates a new instance of this class with the provided output stream and filter level. /// - Parameter stream: The output stream to which this instance will write. /// - Parameter filterLevel: Determines what diagnostics should be printed. This filter level is inclusive, i.e. if a level of ``DiagnosticSeverity/information`` is specified, diagnostics with a severity up to and including `.information` will be printed. @available(*, deprecated, message: "Use init(_:formattingOptions:) instead") - public init(_ stream: TextOutputStream = LogHandle.standardError, filterLevel: DiagnosticSeverity = .warning) { - outputStream = stream - formattingOptions = [] + public convenience init(_ stream: TextOutputStream = LogHandle.standardError, filterLevel: DiagnosticSeverity = .warning) { + self.init(stream, formattingOptions: []) } /// Creates a new instance of this class with the provided output stream. @@ -32,25 +32,69 @@ public final class DiagnosticConsoleWriter: DiagnosticFormattingConsumer { public init(_ stream: TextOutputStream = LogHandle.standardError, formattingOptions options: DiagnosticFormattingOptions = []) { outputStream = stream formattingOptions = options + diagnosticFormatter = Self.makeDiagnosticFormatter(options) } public func receive(_ problems: [Problem]) { - let text = Self.formattedDescriptionFor(problems, options: formattingOptions).appending("\n") + // Add a newline after each formatter description, including the last one. + let text = problems.map { diagnosticFormatter.formattedDescription(for: $0).appending("\n") }.joined() outputStream.write(text) } + + public func finalize() throws { + // The console writer writes each diagnostic as they are received. + } + + private static func makeDiagnosticFormatter(_ options: DiagnosticFormattingOptions) -> DiagnosticConsoleFormatter { + if options.contains(.formatConsoleOutputForTools) { + return IDEDiagnosticConsoleFormatter(options: options) + } else { + return DefaultDiagnosticConsoleFormatter(options: options) + } + } } // MARK: Formatted descriptions extension DiagnosticConsoleWriter { - public static func formattedDescriptionFor(_ problems: Problems, options: DiagnosticFormattingOptions = []) -> String where Problems: Sequence, Problems.Element == Problem { - return problems.map { formattedDescriptionFor($0, options: options) }.joined(separator: "\n") + public static func formattedDescription(for problems: Problems, options: DiagnosticFormattingOptions = []) -> String where Problems: Sequence, Problems.Element == Problem { + return problems.map { formattedDescription(for: $0, options: options) }.joined(separator: "\n") + } + + public static func formattedDescription(for problem: Problem, options: DiagnosticFormattingOptions = []) -> String { + let diagnosticFormatter = makeDiagnosticFormatter(options) + return diagnosticFormatter.formattedDescription(for: problem) } - public static func formattedDescriptionFor(_ problem: Problem, options: DiagnosticFormattingOptions = []) -> String { - guard let source = problem.diagnostic.source, options.contains(.showFixits) else { - return formattedDescriptionFor(problem.diagnostic) + public static func formattedDescription(for diagnostic: Diagnostic, options: DiagnosticFormattingOptions = []) -> String { + let diagnosticFormatter = makeDiagnosticFormatter(options) + return diagnosticFormatter.formattedDescription(for: diagnostic) + } +} + +protocol DiagnosticConsoleFormatter { + var options: DiagnosticFormattingOptions { get set } + + func formattedDescription(for problems: Problems) -> String where Problems: Sequence, Problems.Element == Problem + func formattedDescription(for problem: Problem) -> String + func formattedDescription(for diagnostic: Diagnostic) -> String +} + +extension DiagnosticConsoleFormatter { + func formattedDescription(for problems: Problems) -> String where Problems: Sequence, Problems.Element == Problem { + return problems.map { formattedDescription(for: $0) }.joined(separator: "\n") + } +} + +// MARK: IDE formatting + +struct IDEDiagnosticConsoleFormatter: DiagnosticConsoleFormatter { + var options: DiagnosticFormattingOptions + + func formattedDescription(for problem: Problem) -> String { + guard let source = problem.diagnostic.source else { + return formattedDescription(for: problem.diagnostic) } var description = formattedDiagnosticSummary(problem.diagnostic) @@ -82,11 +126,11 @@ extension DiagnosticConsoleWriter { return description } - public static func formattedDescriptionFor(_ diagnostic: Diagnostic, options: DiagnosticFormattingOptions = []) -> String { + public func formattedDescription(for diagnostic: Diagnostic) -> String { return formattedDiagnosticSummary(diagnostic) + formattedDiagnosticDetails(diagnostic) } - private static func formattedDiagnosticSummary(_ diagnostic: Diagnostic) -> String { + private func formattedDiagnosticSummary(_ diagnostic: Diagnostic) -> String { var result = "" if let range = diagnostic.range, let url = diagnostic.source { @@ -95,23 +139,41 @@ extension DiagnosticConsoleWriter { result += "\(url.path): " } - result += "\(diagnostic.severity): \(diagnostic.localizedSummary)" + result += "\(diagnostic.severity): \(diagnostic.summary)" return result } - private static func formattedDiagnosticDetails(_ diagnostic: Diagnostic) -> String { + private func formattedDiagnosticDetails(_ diagnostic: Diagnostic) -> String { var result = "" - if let explanation = diagnostic.localizedExplanation { + if let explanation = diagnostic.explanation { result += "\n\(explanation)" } if !diagnostic.notes.isEmpty { result += "\n" - result += diagnostic.notes.map { $0.description }.joined(separator: "\n") + result += diagnostic.notes.map { formattedDescription(for: $0) }.joined(separator: "\n") } return result } + + private func formattedDescription(for note: DiagnosticNote) -> String { + let location = "\(note.source.path):\(note.range.lowerBound.line):\(note.range.lowerBound.column)" + return "\(location): note: \(note.message)" + } +} + +// FIXME: Improve the readability for diagnostics on the command line https://github.com/apple/swift-docc/issues/496 +struct DefaultDiagnosticConsoleFormatter: DiagnosticConsoleFormatter { + var options: DiagnosticFormattingOptions + + func formattedDescription(for problem: Problem) -> String { + formattedDescription(for: problem.diagnostic) + } + + func formattedDescription(for diagnostic: Diagnostic) -> String { + return IDEDiagnosticConsoleFormatter(options: options).formattedDescription(for: diagnostic) + } } diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsumer.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsumer.swift index 7dbfe620f0..65faa0ad06 100644 --- a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsumer.swift +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsumer.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-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 @@ -15,6 +15,9 @@ public protocol DiagnosticConsumer: AnyObject { /// Receive diagnostics encountered by a ``DiagnosticEngine``. /// - Parameter problems: The encountered diagnostics. func receive(_ problems: [Problem]) + + /// Inform the consumer that the engine has sent all diagnostics for this build. + func finalize() throws } /// A type that can format received diagnostics in way that's suitable for writing to a destination such as a file or `TextOutputStream`. diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticEngine.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticEngine.swift index 6966a477f5..75d6e59309 100644 --- a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticEngine.swift +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticEngine.swift @@ -95,6 +95,16 @@ public final class DiagnosticEngine { } } } + + public func finalize() { + workQueue.async { [weak self] in + // If the engine isn't around then return early + guard let self = self else { return } + for consumer in self.consumers.sync({ $0.values }) { + try? consumer.finalize() + } + } + } /// Subscribes a given consumer to the diagnostics emitted by this engine. /// - Parameter consumer: The consumer to subscribe to this engine. diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFile.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFile.swift new file mode 100644 index 0000000000..407861f62a --- /dev/null +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFile.swift @@ -0,0 +1,147 @@ +/* + This source file is part of the Swift.org open source project + + 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 + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import Foundation +import struct Markdown.SourceLocation + +struct DiagnosticFile: Codable { + var version: SemanticVersion + var diagnostics: [Diagnostic] + + init(version: SemanticVersion = Self.currentVersion, problems: [Problem]) { + self.version = version + self.diagnostics = problems.map { .init($0) } + } + + // This file format follows semantic versioning. + // Breaking changes should increment the major version component. + // Non breaking additions should increment the minor version. + // Bug fixes should increment the patch version. + static var currentVersion = SemanticVersion(major: 1, minor: 0, patch: 0, prerelease: nil, buildMetadata: nil) + + enum Error: Swift.Error { + case unknownMajorVersion(found: SemanticVersion, latestKnown: SemanticVersion) + } + + static func verifyIsSupported(_ version: SemanticVersion, current: SemanticVersion = Self.currentVersion) throws { + guard version.major == current.major else { + throw Error.unknownMajorVersion(found: version, latestKnown: current) + } + } + + struct Diagnostic: Codable { + struct Range: Codable { + var start: Location + var end: Location + struct Location: Codable { + var line: Int + var column: Int + } + } + var source: URL? + var range: Range? + var severity: Severity + var summary: String + var explanation: String? + var solutions: [Solution] + struct Solution: Codable { + var summary: String + var replacements: [Replacement] + struct Replacement: Codable { + var range: Range + var text: String + } + } + var notes: [Note] + struct Note: Codable { + var source: URL? + var range: Range? + var message: String + } + enum Severity: String, Codable { + case error, warning, note, remark + } + } + + enum CodingKeys: String, CodingKey { + case version, diagnostics + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + version = try container.decode(SemanticVersion.self, forKey: .version) + try Self.verifyIsSupported(version) + + diagnostics = try container.decode([Diagnostic].self, forKey: .diagnostics) + } +} + +// MARK: Initialization + +extension DiagnosticFile.Diagnostic { + init(_ problem: Problem) { + self.source = problem.diagnostic.source + self.range = problem.diagnostic.range.map { .init($0) } + self.severity = .init(problem.diagnostic.severity) + self.summary = problem.diagnostic.summary + self.explanation = problem.diagnostic.explanation + self.solutions = problem.possibleSolutions.map { .init($0) } + self.notes = problem.diagnostic.notes.map { .init($0) } + } +} + +extension DiagnosticFile.Diagnostic.Range { + init(_ sourceRange: Range) { + start = .init(sourceRange.lowerBound) + end = .init(sourceRange.upperBound) + } +} + +extension DiagnosticFile.Diagnostic.Range.Location { + init(_ sourceLocation: SourceLocation) { + self.line = sourceLocation.line + self.column = sourceLocation.column + } +} + +extension DiagnosticFile.Diagnostic.Solution { + init(_ solution: Solution) { + self.summary = solution.summary + self.replacements = solution.replacements.map { .init($0) } + } +} + +extension DiagnosticFile.Diagnostic.Solution.Replacement { + init(_ replacement: Replacement) { + self.range = .init(replacement.range) + self.text = replacement.replacement + } +} + +extension DiagnosticFile.Diagnostic.Note { + init(_ note: DiagnosticNote) { + self.source = note.source + self.range = .init(note.range) + self.message = note.message + } +} + +extension DiagnosticFile.Diagnostic.Severity { + init(_ severity: DiagnosticSeverity) { + switch severity { + case .error: + self = .error + case .warning: + self = .warning + case .information, .hint: + self = .note + } + } +} diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFileWriter.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFileWriter.swift new file mode 100644 index 0000000000..521ff761d8 --- /dev/null +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFileWriter.swift @@ -0,0 +1,38 @@ +/* + This source file is part of the Swift.org open source project + + 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 + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import Foundation + +/// A diagnostic consumer that writes detailed diagnostic information to a file. +/// +/// For tools interacting with DocC, the diagnostic file format includes more information about the diagnostics than what +/// is output to the consoles. +public final class DiagnosticFileWriter: DiagnosticConsumer { + /// The path where the diagnostic file writer should write the diagnostics file. + var outputPath: URL + + /// Creates a new diagnostic file writer with a specific output path. + /// - Parameter outputPath: The path where the diagnostic file writer should write the diagnostics file. + public init(outputPath: URL) { + self.outputPath = outputPath + } + private var receivedProblems: [Problem] = [] + + public func receive(_ problems: [Problem]) { + receivedProblems.append(contentsOf: problems) + } + + public func finalize() throws { + let fileContent = DiagnosticFile(problems: receivedProblems) + receivedProblems = [] + let encoder = RenderJSONEncoder.makeEncoder(emitVariantOverrides: false) + try encoder.encode(fileContent).write(to: outputPath, options: .atomic) + } +} diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFormattingOptions.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFormattingOptions.swift index 6a759e67b9..1b653f0220 100644 --- a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFormattingOptions.swift +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticFormattingOptions.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-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 @@ -17,8 +17,12 @@ public struct DiagnosticFormattingOptions: OptionSet { } /// Problem fix-its should be included when printing diagnostics to a file or output stream. - public static let showFixits = DiagnosticFormattingOptions(rawValue: 1 << 0) + @available(*, deprecated, renamed: "formatConsoleOutputForTools") + public static let showFixits = formatConsoleOutputForTools + + /// Output to the console should be formatted for an IDE or other tool to parse. + public static let formatConsoleOutputForTools = DiagnosticFormattingOptions(rawValue: 1 << 0) /// All of the available formatting options. - public static let all: DiagnosticFormattingOptions = [.showFixits] + public static let all: DiagnosticFormattingOptions = [.formatConsoleOutputForTools] } diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticNote.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticNote.swift index a87b2396ae..c98b62d757 100644 --- a/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticNote.swift +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticNote.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-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 @@ -14,7 +14,7 @@ import Markdown /** A diagnostic note is a simple string message that should appear somewhere in a document. */ -public struct DiagnosticNote: CustomStringConvertible { +public struct DiagnosticNote { /// The source file to which to attach the `message`. public var source: URL @@ -23,7 +23,11 @@ public struct DiagnosticNote: CustomStringConvertible { /// The message to attach to the document. public var message: String +} +@available(*, deprecated, message: "Use 'DiagnosticConsoleWriter.formattedDescription(for:options:)' instead.") +extension DiagnosticNote: CustomStringConvertible { + @available(*, deprecated, message: "Use 'DiagnosticConsoleWriter.formattedDescription(for:options:)' instead.") public var description: String { let location = "\(source.path):\(range.lowerBound.line):\(range.lowerBound.column)" return "\(location): note: \(message)" diff --git a/Sources/SwiftDocC/Infrastructure/Diagnostics/Problem.swift b/Sources/SwiftDocC/Infrastructure/Diagnostics/Problem.swift index ff3b76aa13..6b011ad27f 100644 --- a/Sources/SwiftDocC/Infrastructure/Diagnostics/Problem.swift +++ b/Sources/SwiftDocC/Infrastructure/Diagnostics/Problem.swift @@ -29,16 +29,6 @@ public struct Problem { } } -extension Problem { - var localizedDescription: String { - return DiagnosticConsoleWriter.formattedDescriptionFor(self) - } - - func formattedLocalizedDescription(withOptions options: DiagnosticFormattingOptions = []) -> String { - return DiagnosticConsoleWriter.formattedDescriptionFor(self, options: options) - } -} - extension Problem { /// Offsets the problem using a certain SymbolKit `SourceRange`. /// @@ -61,12 +51,29 @@ extension Sequence where Element == Problem { $0.diagnostic.severity == .error } } - - /// The human readable summary description for the problems. +} + +// MARK: Deprecated + +extension Problem { + @available(*, deprecated, message: "Use 'DiagnosticConsoleWriter.formattedDescription(for:options:)' instead.") + var localizedDescription: String { + return DiagnosticConsoleWriter.formattedDescription(for: self) + } + + @available(*, deprecated, message: "Use 'DiagnosticConsoleWriter.formattedDescription(for:options:)' instead.") + func formattedLocalizedDescription(withOptions options: DiagnosticFormattingOptions = []) -> String { + return DiagnosticConsoleWriter.formattedDescription(for: self, options: options) + } +} + +extension Sequence where Element == Problem { + @available(*, deprecated, message: "Use 'DiagnosticConsoleWriter.formattedDescription(for:options:)' instead.") public var localizedDescription: String { return map { $0.localizedDescription }.joined(separator: "\n") } + @available(*, deprecated, message: "Use 'DiagnosticConsoleWriter.formattedDescription(for:options:)' instead.") public func formattedLocalizedDescription(withOptions options: DiagnosticFormattingOptions) -> String { return map { $0.formattedLocalizedDescription(withOptions: options) }.joined(separator: "\n") } diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift b/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift index 8256d0ae32..1c2102eed0 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift @@ -381,6 +381,8 @@ public struct DocumentationConverter: DocumentationConverterProtocol { context.linkResolutionMismatches.reportGatheredMismatchesIfEnabled() + diagnosticEngine.finalize() + return (analysisProblems: context.problems, conversionProblems: conversionProblems) } diff --git a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC.md b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC.md index efe24ad101..627f5b1583 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC.md +++ b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC.md @@ -51,4 +51,8 @@ Converting in-memory documentation into rendering nodes and persisting them on d - - - +### Development + +- + + diff --git a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/Features.md b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/Features.md new file mode 100644 index 0000000000..dbd0c6a827 --- /dev/null +++ b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/Features.md @@ -0,0 +1,34 @@ +# Indicating Feature Availability to Tools + +Add features to DocC and indicate the feature's availability to other tools. + +Over time as we develop new features in DocC we also add, update, or remove the flags and options that the `docc` executable accepts. So that other tools can know what flags and options a certain version of the `docc` executable accepts, we add new entries in the "features.json" file. + +## Adding a New Feature + +When adding a new collection of command line flags or options to the `docc` executable that relate to some new feature, add a new entry to the "feature.json" file that name the new feature. For example: + +```json +{ + "features": [ + { + "name": "name-of-first-feature" + }, + { + "name": "name-of-second-feature" + } + ] +} +``` + +> Note: Use a single entry for multiple related command line flags and options if they are all added in the same build. + +## Checking what Features DocC Supports + +In a Swift toolchain, the `docc` executable is installed at `usr/bin/docc`. In the same toolchain, the "features.json" file is installed at `usr/share/docc/features.json`. + +Tools that call the `docc` executable from a Swift toolchain can read the "features.json" file to check if a specific feature is available in that build. + +> Note: The feature entry describes the feature name and doesn't list what command line flags or options that feature corresponds to. + + diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift index e5ca056521..cf9e5ed040 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift @@ -98,7 +98,8 @@ public struct ConvertAction: Action, RecreatingContext { bundleDiscoveryOptions: BundleDiscoveryOptions = .init(), diagnosticLevel: String? = nil, diagnosticEngine: DiagnosticEngine? = nil, - emitFixits: Bool = false, + diagnosticFilePath: URL? = nil, + formatConsoleOutputForTools: Bool = false, inheritDocs: Bool = false, treatWarningsAsErrors: Bool = false, experimentalEnableCustomTemplates: Bool = false, @@ -131,8 +132,8 @@ public struct ConvertAction: Action, RecreatingContext { } let formattingOptions: DiagnosticFormattingOptions - if emitFixits { - formattingOptions = [.showFixits] + if formatConsoleOutputForTools || diagnosticFilePath != nil { + formattingOptions = [.formatConsoleOutputForTools] } else { formattingOptions = [] } @@ -144,6 +145,9 @@ public struct ConvertAction: Action, RecreatingContext { let engine = diagnosticEngine ?? DiagnosticEngine(treatWarningsAsErrors: treatWarningsAsErrors) engine.filterLevel = filterLevel engine.add(DiagnosticConsoleWriter(formattingOptions: formattingOptions)) + if let diagnosticFilePath = diagnosticFilePath { + engine.add(DiagnosticFileWriter(outputPath: diagnosticFilePath)) + } self.diagnosticEngine = engine self.context = try context ?? DocumentationContext(dataProvider: workspace, diagnosticEngine: engine) @@ -192,6 +196,52 @@ public struct ConvertAction: Action, RecreatingContext { ) } + @available(*, deprecated, renamed: "init(documentationBundleURL:outOfProcessResolver:analyze:targetDirectory:htmlTemplateDirectory:emitDigest:currentPlatforms:buildIndex:workspace:context:dataProvider:documentationCoverageOptions:bundleDiscoveryOptions:diagnosticLevel:diagnosticEngine:formatConsoleOutputForTools:inheritDocs:experimentalEnableCustomTemplates:transformForStaticHosting:hostingBasePath:sourceRepository:temporaryDirectory:)") + public init( + documentationBundleURL: URL, outOfProcessResolver: OutOfProcessReferenceResolver?, + analyze: Bool, targetDirectory: URL, htmlTemplateDirectory: URL?, emitDigest: Bool, + currentPlatforms: [String : PlatformVersion]?, buildIndex: Bool = false, + workspace: DocumentationWorkspace = DocumentationWorkspace(), + context: DocumentationContext? = nil, + dataProvider: DocumentationWorkspaceDataProvider? = nil, + documentationCoverageOptions: DocumentationCoverageOptions = .noCoverage, + bundleDiscoveryOptions: BundleDiscoveryOptions = .init(), + diagnosticLevel: String? = nil, + diagnosticEngine: DiagnosticEngine? = nil, + emitFixits: Bool, // No default value, this argument has been renamed + inheritDocs: Bool = false, + experimentalEnableCustomTemplates: Bool = false, + transformForStaticHosting: Bool, + hostingBasePath: String?, + sourceRepository: SourceRepository? = nil, + temporaryDirectory: URL + ) throws { + try self.init( + documentationBundleURL: documentationBundleURL, + outOfProcessResolver: outOfProcessResolver, + analyze: analyze, + targetDirectory: targetDirectory, + htmlTemplateDirectory: htmlTemplateDirectory, + emitDigest: emitDigest, + currentPlatforms: currentPlatforms, + buildIndex: buildIndex, + workspace: workspace, + context: context, + dataProvider: dataProvider, + documentationCoverageOptions: documentationCoverageOptions, + bundleDiscoveryOptions: bundleDiscoveryOptions, + diagnosticLevel: diagnosticLevel, + diagnosticEngine: diagnosticEngine, + formatConsoleOutputForTools: emitFixits, + inheritDocs: inheritDocs, + experimentalEnableCustomTemplates: experimentalEnableCustomTemplates, + transformForStaticHosting: transformForStaticHosting, + hostingBasePath: hostingBasePath, + sourceRepository: sourceRepository, + temporaryDirectory: temporaryDirectory + ) + } + /// Initializes the action with the given validated options, creates or uses the given action workspace & context. /// - Parameter workspace: A provided documentation workspace. Creates a new empty workspace if value is `nil` /// - Parameter context: A provided documentation context. Creates a new empty context in the workspace if value is `nil` @@ -210,7 +260,7 @@ public struct ConvertAction: Action, RecreatingContext { bundleDiscoveryOptions: BundleDiscoveryOptions = .init(), diagnosticLevel: String? = nil, diagnosticEngine: DiagnosticEngine? = nil, - emitFixits: Bool = false, + formatConsoleOutputForTools: Bool = false, inheritDocs: Bool = false, experimentalEnableCustomTemplates: Bool = false, transformForStaticHosting: Bool, @@ -243,7 +293,7 @@ public struct ConvertAction: Action, RecreatingContext { bundleDiscoveryOptions: bundleDiscoveryOptions, diagnosticLevel: diagnosticLevel, diagnosticEngine: diagnosticEngine, - emitFixits: emitFixits, + formatConsoleOutputForTools: formatConsoleOutputForTools, inheritDocs: inheritDocs, experimentalEnableCustomTemplates: experimentalEnableCustomTemplates, transformForStaticHosting: transformForStaticHosting, diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift index bb4eeaf731..b4bc2d5e3a 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift @@ -253,8 +253,8 @@ private extension Digest.Diagnostic { self.start = (diagnostic.range?.lowerBound).map { Location(line: $0.line, column: $0.column) } self.source = rootURL.flatMap { diagnostic.source?.relative(to: $0) } self.severity = diagnostic.severity - self.summary = diagnostic.localizedSummary - self.explanation = diagnostic.localizedExplanation + self.summary = diagnostic.summary + self.explanation = diagnostic.explanation self.notes = diagnostic.notes.map { Note(location: Location(line: $0.range.lowerBound.line, column: $0.range.lowerBound.column), message: $0.message) } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift index d8aaef70cd..b870a203b4 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift @@ -78,7 +78,8 @@ extension ConvertAction { ), bundleDiscoveryOptions: bundleDiscoveryOptions, diagnosticLevel: convert.diagnosticLevel, - emitFixits: convert.emitFixits, + diagnosticFilePath: convert.diagnosticsOutputPath, + formatConsoleOutputForTools: convert.formatConsoleOutputForTools, inheritDocs: convert.enableInheritedDocs, treatWarningsAsErrors: convert.warningsAsErrors, experimentalEnableCustomTemplates: convert.experimentalEnableCustomTemplates, diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift index 132982bc93..98ea118b26 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.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-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 @@ -99,12 +99,28 @@ extension Docc { @Flag(help: .hidden) public var index = false - /// A user-provided value that is true if fix-its should be written to output. - /// - /// Defaults to false. - @Flag(inversion: .prefixedNo, help: "Outputs fixits for common issues") - public var emitFixits = false - + @available(*, deprecated, renamed: "formatConsoleOutputForTools") + public var emitFixits: Bool { + return formatConsoleOutputForTools + } + + /// A user-provided value that is true if output to the console should be formatted for an IDE or other tool to parse. + @Flag( + name: [.customLong("ide-console-output"), .customLong("emit-fixits")], + help: "Format output to the console intended for an IDE or other tool to parse.") + public var formatConsoleOutputForTools = false + + /// A user-provided location where the convert action writes the diagnostics file. + @Option( + name: [.customLong("diagnostics-file"), .customLong("diagnostics-output-path")], + help: ArgumentHelp( + "The location where the documentation compiler writes the diagnostics file.", + discussion: "Specifying a diagnostic file path implies '--ide-console-output'." + ), + transform: URL.init(fileURLWithPath:) + ) + var diagnosticsOutputPath: URL? + /// A user-provided value that is true if the user wants to opt in to Experimental documentation coverage generation. /// /// Defaults to none. @@ -331,7 +347,7 @@ extension Docc { ) print( - invalidOrMissingTemplateDiagnostic.localizedDescription, + DiagnosticConsoleWriter.formattedDescription(for: invalidOrMissingTemplateDiagnostic), to: &Self._errorLogHandle ) diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterTests.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterTests.swift index e07e86c63b..d488431359 100644 --- a/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterTests.swift +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterTests.swift @@ -75,7 +75,7 @@ class DiagnosticConsoleWriterTests: XCTestCase { let problem = Problem(diagnostic: diagnostic, possibleSolutions: [solution]) let logger = Logger() - let consumer = DiagnosticConsoleWriter(logger, formattingOptions: [.showFixits]) + let consumer = DiagnosticConsoleWriter(logger, formattingOptions: [.formatConsoleOutputForTools]) consumer.receive([problem]) XCTAssertEqual(logger.output, """ \(expectedLocation): error: \(summary). \(solutionSummary). @@ -95,7 +95,7 @@ class DiagnosticConsoleWriterTests: XCTestCase { let problem = Problem(diagnostic: diagnostic, possibleSolutions: [firstSolution, secondSolution]) let logger = Logger() - let consumer = DiagnosticConsoleWriter(logger, formattingOptions: [.showFixits]) + let consumer = DiagnosticConsoleWriter(logger, formattingOptions: [.formatConsoleOutputForTools]) consumer.receive([problem]) XCTAssertEqual(logger.output, """ \(expectedLocation): error: \(summary). \(firstSolutionSummary) \(secondSolutionSummary). @@ -116,7 +116,7 @@ class DiagnosticConsoleWriterTests: XCTestCase { let problem = Problem(diagnostic: diagnostic, possibleSolutions: [solution]) let logger = Logger() - let consumer = DiagnosticConsoleWriter(logger, formattingOptions: [.showFixits]) + let consumer = DiagnosticConsoleWriter(logger, formattingOptions: [.formatConsoleOutputForTools]) consumer.receive([problem]) XCTAssertEqual(logger.output, """ \(expectedLocation): error: \(summary). \(solutionSummary). diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticEngineTests.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticEngineTests.swift index f443237739..df23ce0054 100644 --- a/Tests/SwiftDocCTests/Diagnostics/DiagnosticEngineTests.swift +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticEngineTests.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-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 @@ -21,6 +21,7 @@ class DiagnosticEngineTests: XCTestCase { func receive(_ problems: [Problem]) { expectation.fulfill() } + func finalize() { } } func testEmitDiagnostic() { @@ -113,7 +114,7 @@ class DiagnosticEngineTests: XCTestCase { defaultEngine.emit(warning) defaultEngine.emit(information) defaultEngine.emit(hint) - XCTAssertEqual(defaultEngine.problems.localizedDescription, """ + XCTAssertEqual(DiagnosticConsoleWriter.formattedDescription(for: defaultEngine.problems), """ error: Test error warning: Test warning """) @@ -123,7 +124,7 @@ class DiagnosticEngineTests: XCTestCase { engine.emit(warning) engine.emit(information) engine.emit(hint) - XCTAssertEqual(engine.problems.localizedDescription, """ + XCTAssertEqual(DiagnosticConsoleWriter.formattedDescription(for: engine.problems), """ error: Test error warning: Test warning note: Test information @@ -139,7 +140,7 @@ class DiagnosticEngineTests: XCTestCase { defaultEngine.emit(error) defaultEngine.emit(warning) defaultEngine.emit(information) - XCTAssertEqual(defaultEngine.problems.localizedDescription, """ + XCTAssertEqual(DiagnosticConsoleWriter.formattedDescription(for: defaultEngine.problems), """ error: Test error warning: Test warning """) @@ -148,7 +149,7 @@ class DiagnosticEngineTests: XCTestCase { engine.emit(error) engine.emit(warning) engine.emit(information) - XCTAssertEqual(engine.problems.localizedDescription, """ + XCTAssertEqual(DiagnosticConsoleWriter.formattedDescription(for: engine.problems), """ error: Test error error: Test warning note: Test information @@ -158,7 +159,7 @@ class DiagnosticEngineTests: XCTestCase { errorFilterLevelEngine.emit(error) errorFilterLevelEngine.emit(warning) errorFilterLevelEngine.emit(information) - XCTAssertEqual(errorFilterLevelEngine.problems.localizedDescription, """ + XCTAssertEqual(DiagnosticConsoleWriter.formattedDescription(for: errorFilterLevelEngine.problems), """ error: Test error error: Test warning """) diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticFileWriterTests.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticFileWriterTests.swift new file mode 100644 index 0000000000..5216ae2283 --- /dev/null +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticFileWriterTests.swift @@ -0,0 +1,189 @@ +/* + This source file is part of the Swift.org open source project + + 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 + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import XCTest +import Markdown +@testable import SwiftDocC + +class DiagnosticFileWriterTests: XCTestCase { + + func testWritesDiagnosticsWhenFinalized() throws { + let diagnosticFileURL = try createTemporaryDirectory().appendingPathComponent("test-diagnostics.json") + let writer = DiagnosticFileWriter(outputPath: diagnosticFileURL) + + let source = URL(string: "/path/to/file.md")! + let range = SourceLocation(line: 1, column: 8, source: source).. Bool in return problem.diagnostic.identifier == "org.swift.docc.InvalidDocumentationLink" - && problem.diagnostic.localizedSummary.contains("https://external.com/link") + && problem.diagnostic.summary.contains("https://external.com/link") })) } @@ -2700,7 +2700,7 @@ Document return p.diagnostic.identifier == "org.swift.docc.unresolvedResource" } XCTAssertFalse(missingResources.contains(where: { p -> Bool in - return p.diagnostic.localizedSummary == "Resource 'my-inherited-image.png' couldn't be found" + return p.diagnostic.summary == "Resource 'my-inherited-image.png' couldn't be found" })) let myFuncReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) @@ -2903,7 +2903,7 @@ Document // Verify that we don't reference resolve inherited docs. XCTAssertFalse(context.diagnosticEngine.problems.contains(where: { problem in - problem.diagnostic.localizedSummary.contains("my-inherited-image.png") + problem.diagnostic.summary.contains("my-inherited-image.png") })) let myFuncReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) diff --git a/Tests/SwiftDocCTests/Semantics/ArticleTests.swift b/Tests/SwiftDocCTests/Semantics/ArticleTests.swift index a03e716cf1..c061b83a2a 100644 --- a/Tests/SwiftDocCTests/Semantics/ArticleTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ArticleTests.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-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 @@ -26,7 +26,7 @@ class ArticleTests: XCTestCase { var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article) - XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(problems.localizedDescription)") + XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(DiagnosticConsoleWriter.formattedDescription(for: problems))") XCTAssertEqual(article?.title?.plainText, "This is my article") XCTAssertEqual(article?.abstract?.plainText, "This is an abstract.") @@ -48,7 +48,7 @@ class ArticleTests: XCTestCase { var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article) - XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(problems.localizedDescription)") + XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(DiagnosticConsoleWriter.formattedDescription(for: problems))") XCTAssertEqual(article?.title?.plainText, "This is my article") XCTAssertEqual(article?.abstract?.plainText, "This is an abstract.") @@ -77,7 +77,7 @@ class ArticleTests: XCTestCase { var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article) - XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(problems.localizedDescription)") + XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(DiagnosticConsoleWriter.formattedDescription(for: problems))") XCTAssertEqual(article?.title?.detachedFromParent.format(), "# This is my article") XCTAssertEqual(article?.abstract?.detachedFromParent.format(), "This is an abstract.") @@ -101,7 +101,7 @@ class ArticleTests: XCTestCase { var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article) - XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(problems.localizedDescription)") + XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(DiagnosticConsoleWriter.formattedDescription(for: problems))") XCTAssertEqual(article?.title?.plainText, "This is my article") XCTAssertNil(article?.abstract) @@ -119,7 +119,7 @@ class ArticleTests: XCTestCase { var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article) - XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(problems.localizedDescription)") + XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(DiagnosticConsoleWriter.formattedDescription(for: problems))") XCTAssertEqual(article?.title?.plainText, "This is my article") XCTAssertNil(article?.abstract) diff --git a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtMostOneTests.swift b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtMostOneTests.swift index ce52b4c8d4..28af8640a4 100644 --- a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtMostOneTests.swift +++ b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtMostOneTests.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-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 @@ -76,7 +76,7 @@ class HasAtMostOneTests: XCTestCase { XCTAssertEqual(""" warning: Duplicate 'Child' child directive The 'Parent' directive must have at most one 'Child' child directive - """, problems[0].diagnostic.localizedDescription) + """, DiagnosticConsoleWriter.formattedDescription(for: problems[0].diagnostic)) } } diff --git a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasExactlyOneTests.swift b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasExactlyOneTests.swift index 930c4992f5..794f0dcbfe 100644 --- a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasExactlyOneTests.swift +++ b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasExactlyOneTests.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-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 @@ -30,7 +30,7 @@ class HasExactlyOneTests: XCTestCase { problems.first.map { problem in XCTAssertEqual("org.swift.docc.HasExactlyOne.Missing", problem.diagnostic.identifier) XCTAssertEqual( - problem.diagnostic.localizedDescription, + DiagnosticConsoleWriter.formattedDescription(for: problem.diagnostic), """ error: Missing 'Child' child directive The 'Parent' directive must have exactly one 'Child' child directive @@ -87,7 +87,7 @@ class HasExactlyOneTests: XCTestCase { XCTAssertEqual(""" error: Duplicate 'Child' child directive The 'Parent' directive must have exactly one 'Child' child directive - """, problems[0].diagnostic.localizedDescription) + """, DiagnosticConsoleWriter.formattedDescription(for: problems[0].diagnostic)) } } diff --git a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlyKnownArgumentsTests.swift b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlyKnownArgumentsTests.swift index e8d2c227c2..922b1fc644 100644 --- a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlyKnownArgumentsTests.swift +++ b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlyKnownArgumentsTests.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-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 @@ -68,6 +68,6 @@ class HasOnlyKnownArgumentsTests: XCTestCase { XCTAssertEqual(problems.count, 1) guard let first = problems.first else { return } - XCTAssertEqual("error: Unknown argument 'baz' in Intro. These arguments are currently unused but allowed: 'bark', 'woof'.", first.diagnostic.localizedDescription) + XCTAssertEqual("error: Unknown argument 'baz' in Intro. These arguments are currently unused but allowed: 'bark', 'woof'.", DiagnosticConsoleWriter.formattedDescription(for: first.diagnostic)) } } diff --git a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlyKnownDirectivesTests.swift b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlyKnownDirectivesTests.swift index 3412fa0c15..a3350051b6 100644 --- a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlyKnownDirectivesTests.swift +++ b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlyKnownDirectivesTests.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-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 @@ -121,7 +121,7 @@ class HasOnlyKnownDirectivesTests: XCTestCase { XCTAssertEqual(problems.count, 1) guard let first = problems.first else { return } - XCTAssertEqual("error: 'baz' directive is unsupported as a child of the 'dir' directive\nThese directives are allowed: 'Comment', 'bar', 'bark', 'foo', 'woof'", first.diagnostic.localizedDescription) + XCTAssertEqual("error: 'baz' directive is unsupported as a child of the 'dir' directive\nThese directives are allowed: 'Comment', 'bar', 'bark', 'foo', 'woof'", DiagnosticConsoleWriter.formattedDescription(for: first.diagnostic)) } } diff --git a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlySequentialHeadingsTests.swift b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlySequentialHeadingsTests.swift index 932a8ffdcd..2b504ccc44 100644 --- a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlySequentialHeadingsTests.swift +++ b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlySequentialHeadingsTests.swift @@ -70,7 +70,7 @@ some more *stuff* var problems: [Problem] = [] Semantic.Analyses.HasOnlySequentialHeadings(severityIfFound: .warning, startingFromLevel: 2).analyze(containerDirective, children: document.children, source: nil, for: bundle, in: context, problems: &problems) - XCTAssertEqual(problems.map { $0.diagnostic.localizedSummary }, + XCTAssertEqual(problems.map { $0.diagnostic.summary }, [ "This heading doesn't meet or exceed the minimum allowed heading level (2)", "This heading doesn't meet or exceed the minimum allowed heading level (2)", @@ -91,7 +91,7 @@ some more *stuff* var problems: [Problem] = [] Semantic.Analyses.HasOnlySequentialHeadings(severityIfFound: .warning, startingFromLevel: 2).analyze(containerDirective, children: document.children, source: nil, for: bundle, in: context, problems: &problems) - XCTAssertEqual(problems.map { $0.diagnostic.localizedSummary }, + XCTAssertEqual(problems.map { $0.diagnostic.summary }, [ "This heading doesn't sequentially follow the previous heading", "This heading doesn't sequentially follow the previous heading", diff --git a/Tests/SwiftDocCTests/Semantics/MetadataTests.swift b/Tests/SwiftDocCTests/Semantics/MetadataTests.swift index 9766fd9efd..af038b49d2 100644 --- a/Tests/SwiftDocCTests/Semantics/MetadataTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MetadataTests.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-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 @@ -126,7 +126,7 @@ class MetadataTests: XCTestCase { var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(metadata) - XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.localizedSummary })") + XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.summary })") XCTAssertEqual(metadata?.displayName?.name, "Custom Name") } @@ -165,11 +165,11 @@ class MetadataTests: XCTestCase { var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Metadata child.") - XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.localizedSummary })") + XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.summary })") var analyzer = SemanticAnalyzer(source: nil, context: context, bundle: bundle) _ = analyzer.visit(document) - XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got:\n \(analyzer.problems.localizedDescription)") + XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got:\n \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } func testSymbolArticleSupportsMetadataDisplayName() throws { @@ -189,11 +189,11 @@ class MetadataTests: XCTestCase { XCTAssertNotNil(article, "An Article value can be created with a Metadata child with a DisplayName child.") XCTAssertNotNil(article?.metadata?.displayName, "The Article has the parsed DisplayName metadata.") - XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.localizedSummary })") + XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.summary })") var analyzer = SemanticAnalyzer(source: nil, context: context, bundle: bundle) _ = analyzer.visit(document) - XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got:\n \(analyzer.problems.localizedDescription)") + XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got:\n \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } func testArticleDoesNotSupportsMetadataDisplayName() throws { @@ -218,7 +218,7 @@ class MetadataTests: XCTestCase { let problem = try XCTUnwrap(problems.first) XCTAssertEqual(problem.diagnostic.identifier, "org.swift.docc.Article.DisplayName.NotSupported") - XCTAssertEqual(problem.diagnostic.localizedSummary, "A 'DisplayName' directive is only supported in documentation extension files. To customize the display name of an article, change the content of the level-1 heading.") + XCTAssertEqual(problem.diagnostic.summary, "A 'DisplayName' directive is only supported in documentation extension files. To customize the display name of an article, change the content of the level-1 heading.") XCTAssertEqual(problem.possibleSolutions.count, 1) let solution = try XCTUnwrap(problem.possibleSolutions.first) diff --git a/Tests/SwiftDocCTests/Semantics/RedirectedTests.swift b/Tests/SwiftDocCTests/Semantics/RedirectedTests.swift index 92ea9408f3..df646e7f4d 100644 --- a/Tests/SwiftDocCTests/Semantics/RedirectedTests.swift +++ b/Tests/SwiftDocCTests/Semantics/RedirectedTests.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-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 @@ -58,7 +58,7 @@ class RedirectedTests: XCTestCase { XCTAssertEqual(1, problems.count) XCTAssertEqual(problems.first?.diagnostic.identifier, "org.swift.docc.HasArgument.from.ConversionFailed") XCTAssertEqual( - problems.first?.diagnostic.localizedSummary, + problems.first?.diagnostic.summary, "Cannot convert '\(pathWithInvalidCharacter)' to type 'URL'" ) } @@ -134,11 +134,11 @@ class RedirectedTests: XCTestCase { var problems = [Problem]() let technology = Technology(from: directive, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(technology, "A Technology value can be created with a Redirected child.") - XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.localizedSummary })") + XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.summary })") var analyzer = SemanticAnalyzer(source: nil, context: context, bundle: bundle) _ = analyzer.visit(document) - XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(analyzer.problems.localizedDescription)") + XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } func testVolumeAndChapterSupportsRedirect() throws { @@ -166,7 +166,7 @@ class RedirectedTests: XCTestCase { var problems = [Problem]() let volume = Volume(from: directive, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(volume, "A Volume value can be created with a Redirected child.") - XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.localizedSummary })") + XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.summary })") } func testTutorialAndSectionsSupportsRedirect() throws { @@ -231,11 +231,11 @@ class RedirectedTests: XCTestCase { var problems = [Problem]() let tutorial = Tutorial(from: directive, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(tutorial, "A Tutorial value can be created with a Redirected child.") - XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.localizedSummary })") + XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.summary })") var analyzer = SemanticAnalyzer(source: nil, context: context, bundle: bundle) _ = analyzer.visit(document) - XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(analyzer.problems.localizedDescription)") + XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } func testTutorialArticleSupportsRedirect() throws { @@ -259,11 +259,11 @@ class RedirectedTests: XCTestCase { var problems = [Problem]() let article = TutorialArticle(from: directive, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article, "A TutorialArticle value can be created with a Redirected child.") - XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.localizedSummary })") + XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.summary })") var analyzer = SemanticAnalyzer(source: nil, context: context, bundle: bundle) _ = analyzer.visit(document) - XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(analyzer.problems.localizedDescription)") + XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } func testResourcesSupportsRedirect() throws { @@ -308,7 +308,7 @@ class RedirectedTests: XCTestCase { var problems = [Problem]() let article = Resources(from: directive, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article, "A Resources value can be created with a Redirected child.") - XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.localizedSummary })") + XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.summary })") } func testArticleSupportsRedirect() throws { @@ -329,11 +329,11 @@ class RedirectedTests: XCTestCase { var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Redirected child.") - XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.localizedSummary })") + XCTAssert(problems.isEmpty, "There shouldn't be any problems. Got:\n\(problems.map { $0.diagnostic.summary })") var analyzer = SemanticAnalyzer(source: nil, context: context, bundle: bundle) _ = analyzer.visit(document) - XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(analyzer.problems.localizedDescription)") + XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } func testIncorrectArgumentLabel() throws { diff --git a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift index 961dd3fa0e..e3a68a2f23 100644 --- a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SymbolTests.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-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 @@ -560,26 +560,26 @@ class SymbolTests: XCTestCase { let unresolvedTopicProblems = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.unresolvedTopicReference" } - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'doc://com.test.external/ExternalPage' couldn't be resolved. No external resolver registered for 'com.test.external'." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'doc://com.test.external/ExternalPage' couldn't be resolved. No external resolver registered for 'com.test.external'." })) if LinkResolutionMigrationConfiguration.shouldUseHierarchyBasedLinkResolver { var problem: Problem - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview<>(_:))' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'UnresolvableSymbolLinkInMyClassOverview<>(_:))'. No similar pages." })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview<>(_:))' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'UnresolvableSymbolLinkInMyClassOverview<>(_:))'. No similar pages." })) XCTAssertEqual(problem.diagnostic.notes.map(\.message), ["Available children: init(), myFunction()."]) XCTAssertEqual(problem.possibleSolutions.count, 2) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'UnresolvableClassInMyClassTopicCuration'. No similar pages." })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'UnresolvableClassInMyClassTopicCuration'. No similar pages." })) XCTAssertEqual(problem.diagnostic.notes.map(\.message), ["Available children: init(), myFunction()."]) XCTAssertEqual(problem.possibleSolutions.count, 2) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'unresolvablePropertyInMyClassTopicCuration'. No similar pages." })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'unresolvablePropertyInMyClassTopicCuration'. No similar pages." })) XCTAssertEqual(problem.diagnostic.notes.map(\.message), ["Available children: init(), myFunction()."]) XCTAssertEqual(problem.possibleSolutions.count, 2) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'init()' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass'." })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'init()' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass'." })) XCTAssert(problem.diagnostic.notes.isEmpty) XCTAssertEqual(problem.possibleSolutions.count, 2) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -625,7 +625,7 @@ class SymbolTests: XCTestCase { """) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/init()-swift.init' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass'." })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'MyClass/init()-swift.init' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass'." })) XCTAssert(problem.diagnostic.notes.isEmpty) XCTAssertEqual(problem.possibleSolutions.count, 2) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -671,7 +671,7 @@ class SymbolTests: XCTestCase { """) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'doc:MyClass/init()-swift.init' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass'." })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'doc:MyClass/init()-swift.init' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass'." })) XCTAssert(problem.diagnostic.notes.isEmpty) XCTAssertEqual(problem.possibleSolutions.count, 2) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -717,7 +717,7 @@ class SymbolTests: XCTestCase { """) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'otherFunction()' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'otherFunction()'. Did you mean myFunction()?" })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'otherFunction()' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'otherFunction()'. Did you mean myFunction()?" })) XCTAssertEqual(problem.diagnostic.notes.map(\.message), ["Available children: init(), myFunction()."]) XCTAssertEqual(problem.possibleSolutions.count, 1) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -762,7 +762,7 @@ class SymbolTests: XCTestCase { """) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference '/MyKit/MyClas' couldn't be resolved. Reference at '/MyKit' can't resolve 'MyClas'. Did you mean MyClass?" })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference '/MyKit/MyClas' couldn't be resolved. Reference at '/MyKit' can't resolve 'MyClas'. Did you mean MyClass?" })) XCTAssertEqual(problem.diagnostic.notes.map(\.message), ["Available children: Discussion, globalFunction(_:considering:), MyClass, MyProtocol."]) XCTAssertEqual(problem.possibleSolutions.count, 1) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -807,7 +807,7 @@ class SymbolTests: XCTestCase { """) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyKit/MyClas/myFunction()' couldn't be resolved. Reference at '/MyKit' can't resolve 'MyClas/myFunction()'. Did you mean MyClass?" })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'MyKit/MyClas/myFunction()' couldn't be resolved. Reference at '/MyKit' can't resolve 'MyClas/myFunction()'. Did you mean MyClass?" })) XCTAssertEqual(problem.diagnostic.notes.map(\.message), ["Available children: Discussion, globalFunction(_:considering:), MyClass, MyProtocol."]) XCTAssertEqual(problem.possibleSolutions.count, 1) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -852,7 +852,7 @@ class SymbolTests: XCTestCase { """) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyKit/MyClas/myFunction()' couldn't be resolved. Reference at '/MyKit' can't resolve 'MyClas/myFunction()'. Did you mean MyClass?" })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'MyKit/MyClas/myFunction()' couldn't be resolved. Reference at '/MyKit' can't resolve 'MyClas/myFunction()'. Did you mean MyClass?" })) XCTAssertEqual(problem.diagnostic.notes.map(\.message), ["Available children: Discussion, globalFunction(_:considering:), MyClass, MyProtocol."]) XCTAssertEqual(problem.possibleSolutions.count, 1) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -897,7 +897,7 @@ class SymbolTests: XCTestCase { """) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'doc:MyKit/MyClas/myFunction()' couldn't be resolved. Reference at '/MyKit' can't resolve 'MyClas/myFunction()'. Did you mean MyClass?" })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'doc:MyKit/MyClas/myFunction()' couldn't be resolved. Reference at '/MyKit' can't resolve 'MyClas/myFunction()'. Did you mean MyClass?" })) XCTAssertEqual(problem.diagnostic.notes.map(\.message), ["Available children: Discussion, globalFunction(_:considering:), MyClass, MyProtocol."]) XCTAssertEqual(problem.possibleSolutions.count, 1) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -941,11 +941,11 @@ class SymbolTests: XCTestCase { - """) } else { - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview<>(_:))' couldn't be resolved. No local documentation matches this reference." })) - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." })) - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." })) - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'init()' couldn't be resolved. No local documentation matches this reference." })) - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/init()-swift.init' couldn't be resolved. No local documentation matches this reference." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview<>(_:))' couldn't be resolved. No local documentation matches this reference." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'init()' couldn't be resolved. No local documentation matches this reference." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'MyClass/init()-swift.init' couldn't be resolved. No local documentation matches this reference." })) } } @@ -988,7 +988,7 @@ class SymbolTests: XCTestCase { if LinkResolutionMigrationConfiguration.shouldUseHierarchyBasedLinkResolver { var problem: Problem - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview' couldn't be resolved. Reference at '/MyKit/MyClass/myFunction()' can't resolve 'UnresolvableSymbolLinkInMyClassOverview'. Did you mean Unresolvable-curation?" })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview' couldn't be resolved. Reference at '/MyKit/MyClass/myFunction()' can't resolve 'UnresolvableSymbolLinkInMyClassOverview'. Did you mean Unresolvable-curation?" })) XCTAssert(problem.diagnostic.notes.isEmpty) XCTAssertEqual(problem.possibleSolutions.count, 1) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -1013,7 +1013,7 @@ class SymbolTests: XCTestCase { - """) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass/myFunction()' can't resolve 'UnresolvableClassInMyClassTopicCuration'. Did you mean Unresolvable-curation?" })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass/myFunction()' can't resolve 'UnresolvableClassInMyClassTopicCuration'. Did you mean Unresolvable-curation?" })) XCTAssert(problem.diagnostic.notes.isEmpty) XCTAssertEqual(problem.possibleSolutions.count, 1) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -1039,7 +1039,7 @@ class SymbolTests: XCTestCase { """) - problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'unresolvablePropertyInMyClassTopicCuration'. No similar pages." })) + problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.summary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'unresolvablePropertyInMyClassTopicCuration'. No similar pages." })) XCTAssert(problem.diagnostic.notes.isEmpty) XCTAssertEqual(problem.possibleSolutions.count, 2) XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 }) @@ -1065,11 +1065,11 @@ class SymbolTests: XCTestCase { - """) } else { - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview' couldn't be resolved. No local documentation matches this reference." })) - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." })) - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview' couldn't be resolved. No local documentation matches this reference." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." })) } - XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'doc://com.test.external/ExternalPage' couldn't be resolved. No external resolver registered for 'com.test.external'." })) + XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "Topic reference 'doc://com.test.external/ExternalPage' couldn't be resolved. No external resolver registered for 'com.test.external'." })) } func testTopicSectionInDocComment() throws { @@ -1182,7 +1182,7 @@ class SymbolTests: XCTestCase { var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, in: context, problems: &problems) XCTAssertNotNil(article, "The sidecar Article couldn't be created.", file: (file), line: line) - XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(problems.localizedDescription)", file: (file), line: line) + XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(DiagnosticConsoleWriter.formattedDescription(for: problems))", file: (file), line: line) return article } diff --git a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift index caa0f00290..8d144d68b2 100644 --- a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift @@ -275,8 +275,8 @@ TutorialArticle @1:1-23:2 title: 'Basic Augmented Reality App' time: '20' XCTAssertEqual(3, problems.count) let arbitraryMarkupProblem = problems.first(where: { $0.diagnostic.identifier == "org.swift.docc.Stack.UnexpectedContent" }) XCTAssertNotNil(arbitraryMarkupProblem) - XCTAssertEqual(arbitraryMarkupProblem?.diagnostic.localizedSummary, "'Stack' contains unexpected content") - XCTAssertEqual(arbitraryMarkupProblem?.diagnostic.localizedExplanation, "Arbitrary markup content is not allowed as a child of the 'Stack' directive.") + XCTAssertEqual(arbitraryMarkupProblem?.diagnostic.summary, "'Stack' contains unexpected content") + XCTAssertEqual(arbitraryMarkupProblem?.diagnostic.explanation, "Arbitrary markup content is not allowed as a child of the 'Stack' directive.") article.map { article in let expectedDump = """ TutorialArticle @1:1-81:2 diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift index 976adc003b..f796d8e63b 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift @@ -2945,10 +2945,10 @@ class ConvertActionTests: XCTestCase { private func uniformlyPrintDiagnosticMessages(_ problems: [Problem]) -> String { return problems.sorted(by: { (lhs, rhs) -> Bool in guard lhs.diagnostic.identifier != rhs.diagnostic.identifier else { - return lhs.diagnostic.localizedSummary < rhs.diagnostic.localizedSummary + return lhs.diagnostic.summary < rhs.diagnostic.summary } return lhs.diagnostic.identifier < rhs.diagnostic.identifier - }) .map { $0.diagnostic.localizedDescription }.sorted().joined(separator: "\n") + }) .map { DiagnosticConsoleWriter.formattedDescription(for: $0.diagnostic) }.sorted().joined(separator: "\n") } #endif diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift index a34775b0e9..ed916ff2b0 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.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-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 @@ -285,8 +285,6 @@ class PreviewActionIntegrationTests: XCTestCase { let workspace = DocumentationWorkspace() _ = try! DocumentationContext(dataProvider: workspace) - let engine = DiagnosticEngine() - let convertActionTempDirectory = try createTemporaryDirectory() let createConvertAction = { try ConvertAction( @@ -298,8 +296,7 @@ class PreviewActionIntegrationTests: XCTestCase { emitDigest: false, currentPlatforms: nil, fileManager: FileManager.default, - temporaryDirectory: convertActionTempDirectory, - diagnosticEngine: engine) + temporaryDirectory: convertActionTempDirectory) } guard let preview = try? PreviewAction( @@ -308,9 +305,17 @@ class PreviewActionIntegrationTests: XCTestCase { XCTFail("Could not create preview action from parameters", file: file, line: line) return } - + defer { + do { + try preview.stop() + } catch { + XCTFail("Failed to stop preview server", file: file, line: line) + } + } // Start watching the source and get the initial (successful) state. do { + let engine = preview.convertAction.diagnosticEngine + // Wait for watch to produce output. let logOutputExpectation = expectation(description: "Did produce log output") let logChecker = OutputChecker(fileURL: pipeURL, expectation: logOutputExpectation) { output in @@ -329,7 +334,7 @@ class PreviewActionIntegrationTests: XCTestCase { XCTAssertTrue(result.didEncounterError, "Did not find an error when running preview", file: file, line: line) XCTAssertNotNil(engine.problems.first(where: { problem -> Bool in - problem.diagnostic.localizedDescription.contains(expectedErrorMessage) + DiagnosticConsoleWriter.formattedDescription(for: problem.diagnostic).contains(expectedErrorMessage) }), "Didn't find expected error message '\(expectedErrorMessage)'", file: file, line: line) // Verify that the failed server is not added to the server list @@ -342,7 +347,6 @@ class PreviewActionIntegrationTests: XCTestCase { wait(for: [logOutputExpectation, erroredExpectation], timeout: 20.0) logTimer.invalidate() } - try preview.stop() #endif } @@ -468,7 +472,7 @@ class PreviewActionIntegrationTests: XCTestCase { } if !result.problems.isEmpty { - print(result.problems.localizedDescription, to: &logHandle) + print(DiagnosticConsoleWriter.formattedDescription(for: result.problems), to: &logHandle) } } catch { XCTFail(error.localizedDescription) diff --git a/Tests/SwiftDocCUtilitiesTests/ProblemTests.swift b/Tests/SwiftDocCUtilitiesTests/ProblemTests.swift index 9b89bffb84..f5f18a41be 100644 --- a/Tests/SwiftDocCUtilitiesTests/ProblemTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ProblemTests.swift @@ -19,6 +19,6 @@ class ProblemTests: XCTestCase { let problem = Problem(description: "Lorem ipsum", source: nil) XCTAssertNil(problem.diagnostic.source, "Convenience initializer for Problem should not capture source file location") XCTAssertEqual(problem.diagnostic.identifier, "org.swift.docc.ProblemTests") - XCTAssertEqual(problem.diagnostic.localizedSummary, "Lorem ipsum") + XCTAssertEqual(problem.diagnostic.summary, "Lorem ipsum") } } diff --git a/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift b/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift index 59af3ff9e7..510fe98977 100644 --- a/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift @@ -98,7 +98,7 @@ class SemanticAnalyzerTests: XCTestCase { XCTAssertEqual(problems.unsupportedTopLevelChildProblems.count, 1) if let diagnostic = problems.unsupportedTopLevelChildProblems.first?.diagnostic { - XCTAssertEqual(diagnostic.localizedSummary, "Found unsupported 'Article' directive in '.md' file") + XCTAssertEqual(diagnostic.summary, "Found unsupported 'Article' directive in '.md' file") XCTAssertEqual(diagnostic.severity, .warning) XCTAssertEqual(diagnostic.source?.lastPathComponent, "FileWithDirective.md") } @@ -111,8 +111,8 @@ class SemanticAnalyzerTests: XCTestCase { XCTAssertEqual(problems.unsupportedTopLevelChildProblems.count, 0) if let diagnostic = problems.missingTopLevelChildProblems.first?.diagnostic { - XCTAssertEqual(diagnostic.localizedSummary, "No valid content was found in this file") - XCTAssertEqual(diagnostic.localizedExplanation, "A '.tutorial' file should contain a top-level directive ('Tutorials', 'Tutorial', or 'Article') and valid child content. Only '.md' files support content without a top-level directive") + XCTAssertEqual(diagnostic.summary, "No valid content was found in this file") + XCTAssertEqual(diagnostic.explanation, "A '.tutorial' file should contain a top-level directive ('Tutorials', 'Tutorial', or 'Article') and valid child content. Only '.md' files support content without a top-level directive") XCTAssertEqual(diagnostic.severity, .warning) XCTAssertEqual(diagnostic.source?.lastPathComponent, "FileWithoutDirective.tutorial") } diff --git a/build-script-helper.py b/build-script-helper.py index f28acfc7e8..13b4134d72 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -253,6 +253,21 @@ def install(args, env): verbose=verbose ) + features_path = os.path.join(args.package_path, 'features.json') + # Install features.json relative to the docc executable at "../../share/docc/features.json" + features_install_path = os.path.join( + os.path.dirname(docc_install_dir), + 'share', + 'docc', + 'features.json' + ) + create_intermediate_directories(os.path.dirname(features_install_path), verbose=verbose) + check_and_sync( + file_path=features_path, + install_path=features_install_path, + verbose=verbose + ) + # Copy the content of the build_dir into the install dir with a call like # rsync -a src/ dest copy_render_from=args.copy_doccrender_from diff --git a/features.json b/features.json new file mode 100644 index 0000000000..be2e6d24b7 --- /dev/null +++ b/features.json @@ -0,0 +1,7 @@ +{ + "features": [ + { + "name": "diagnostics-file" + } + ] +}