From 15ac29e4de35e03535544eaf2e2d6fa05be6c210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Thu, 27 Feb 2025 17:04:11 +0100 Subject: [PATCH] Formally deprecate the old diagnostics.json digest file rdar://145729962 --- .../ConvertActionConverter.swift | 6 +- .../ConvertOutputConsumer.swift | 40 ++++++++- .../DocumentationConverter.swift | 8 +- .../SwiftDocC.docc/Resources/Diagnostics.json | 2 +- .../Actions/Convert/ConvertAction.swift | 4 +- .../Convert/ConvertFileWritingConsumer.swift | 5 +- ...recatedDiagnosticsDigestWarningTests.swift | 90 +++++++++++++++++++ .../TestRenderNodeOutputConsumer.swift | 3 +- .../ConvertActionTests.swift | 47 +++++++++- 9 files changed, 190 insertions(+), 15 deletions(-) create mode 100644 Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift diff --git a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift index 939d4ef123..51642f8c32 100644 --- a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift +++ b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2024 Apple Inc. and the Swift project authors + Copyright (c) 2024-2025 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 @@ -54,7 +54,7 @@ package enum ConvertActionConverter { guard !context.problems.containsErrors else { if emitDigest { - try outputConsumer.consume(problems: context.problems) + try (_Deprecated(outputConsumer) as _DeprecatedConsumeProblemsAccess)._consume(problems: context.problems) } return [] } @@ -198,7 +198,7 @@ package enum ConvertActionConverter { if emitDigest { signposter.withIntervalSignpost("Emit digest", id: signposter.makeSignpostID()) { do { - try outputConsumer.consume(problems: context.problems + conversionProblems) + try (_Deprecated(outputConsumer) as _DeprecatedConsumeProblemsAccess)._consume(problems: context.problems + conversionProblems) } catch { recordProblem(from: error, in: &conversionProblems, withIdentifier: "problems") } diff --git a/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift b/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift index 11389feda4..21e48b6386 100644 --- a/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift +++ b/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.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-2025 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 @@ -16,6 +16,7 @@ import Foundation /// or store them in memory. public protocol ConvertOutputConsumer { /// Consumes an array of problems that were generated during a conversion. + @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") func consume(problems: [Problem]) throws /// Consumes a render node that was generated during a conversion. @@ -58,3 +59,40 @@ public extension ConvertOutputConsumer { func consume(buildMetadata: BuildMetadata) throws {} func consume(linkResolutionInformation: SerializableLinkResolutionInformation) throws {} } + +// Default implementation so that conforming types don't need to implement deprecated API. +public extension ConvertOutputConsumer { + func consume(problems: [Problem]) throws {} +} + +package protocol _DeprecatedConsumeProblemsAccess { + func _consume(problems: [Problem]) throws +} + +package struct _Deprecated: _DeprecatedConsumeProblemsAccess { + private let consumer: Consumer + package init(_ consumer: Consumer) { + self.consumer = consumer + } + + @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") + package func _consume(problems: [Problem]) throws { + var problems = problems + + if !problems.isEmpty { + problems.insert( + Problem(diagnostic: Diagnostic( + severity: .warning, + identifier: "org.swift.docc.DeprecatedDiagnosticsDigets", + summary: """ + The 'diagnostics.json' digest file is deprecated and will be removed after 6.2 is released. \ + Pass a `--diagnostics-file ` to specify a custom location where DocC will write a diagnostics JSON file with more information. + """) + ), + at: 0 + ) + } + + try consumer.consume(problems: problems) + } +} diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift b/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift index 63107572e3..b3424d4dfb 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationConverter.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-2025 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 @@ -212,7 +212,7 @@ public struct DocumentationConverter: DocumentationConverterProtocol { if let rootURL { throw Error.doesNotContainBundle(url: rootURL) } else { - try outputConsumer.consume(problems: context.problems) + try (_Deprecated(outputConsumer) as _DeprecatedConsumeProblemsAccess)._consume(problems: context.problems) throw GeneratedDataProvider.Error.notEnoughDataToGenerateBundle(options: bundleDiscoveryOptions, underlyingError: nil) } } @@ -232,7 +232,7 @@ public struct DocumentationConverter: DocumentationConverterProtocol { guard !context.problems.containsErrors else { if emitDigest { - try outputConsumer.consume(problems: context.problems) + try (_Deprecated(outputConsumer) as _DeprecatedConsumeProblemsAccess)._consume(problems: context.problems) } return (analysisProblems: context.problems, conversionProblems: []) } @@ -367,7 +367,7 @@ public struct DocumentationConverter: DocumentationConverterProtocol { if emitDigest { do { - try outputConsumer.consume(problems: context.problems + conversionProblems) + try (_Deprecated(outputConsumer) as _DeprecatedConsumeProblemsAccess)._consume(problems: context.problems + conversionProblems) } catch { recordProblem(from: error, in: &conversionProblems, withIdentifier: "problems") } diff --git a/Sources/SwiftDocC/SwiftDocC.docc/Resources/Diagnostics.json b/Sources/SwiftDocC/SwiftDocC.docc/Resources/Diagnostics.json index fb97876351..0c268b7b7b 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/Resources/Diagnostics.json +++ b/Sources/SwiftDocC/SwiftDocC.docc/Resources/Diagnostics.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "description": "Specification of the DocC diagnostics.json digest file.", + "description": "Specification of the deprecated DocC diagnostics.json digest file. This deprecated file will be removed after 6.2 is released.", "version": "0.1.0", "title": "Diagnostics" }, diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift index 206c98f75c..c5cc3f4163 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.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-2025 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 @@ -330,7 +330,7 @@ public struct ConvertAction: AsyncAction { } catch { if emitDigest { let problem = Problem(description: (error as? DescribedError)?.errorDescription ?? error.localizedDescription, source: nil) - try outputConsumer.consume(problems: context.problems + [problem]) + try (_Deprecated(outputConsumer) as _DeprecatedConsumeProblemsAccess)._consume(problems: context.problems + [problem]) try moveOutput(from: temporaryFolder, to: targetDirectory) } throw error diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift index 982170b324..89f3d853dc 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.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-2025 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 @@ -50,6 +50,7 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer { self.assetPrefixComponent = bundleID?.rawValue.split(separator: "/").joined(separator: "-") } + @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") func consume(problems: [Problem]) throws { let diagnostics = problems.map { problem in Digest.Diagnostic(diagnostic: problem.diagnostic, rootURL: bundleRootFolder) @@ -245,6 +246,7 @@ enum Digest { let downloads: [DownloadReference] } + @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") struct Diagnostic: Codable { struct Location: Codable { let line: Int @@ -263,6 +265,7 @@ enum Digest { } } +@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") private extension Digest.Diagnostic { init(diagnostic: Diagnostic, rootURL: URL?) { self.start = (diagnostic.range?.lowerBound).map { Location(line: $0.line, column: $0.column) } diff --git a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift new file mode 100644 index 0000000000..a3d40c64f2 --- /dev/null +++ b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift @@ -0,0 +1,90 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2025 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 SwiftDocC +import SwiftDocCTestUtilities +import XCTest + +class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { + func testNoDeprecationWarningWhenThereAreNoOtherWarnings() throws { + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Root.md", utf8Content: """ + # Root + + An empty root page + """) + ]) + let (bundle, context) = try loadBundle(catalog: catalog) + + let outputConsumer = TestOutputConsumer() + + _ = try ConvertActionConverter.convert( + bundle: bundle, + context: context, + outputConsumer: outputConsumer, + sourceRepository: nil, + emitDigest: true, + documentationCoverageOptions: .noCoverage + ) + + XCTAssert(outputConsumer.problems.isEmpty, "Unexpected problems: \(outputConsumer.problems.map(\.diagnostic.summary).joined(separator: "\n"))") + } + + func testDeprecationWarningWhenThereAreOtherWarnings() throws { + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Root.md", utf8Content: """ + # Root + + An empty root page + + This link will result in a warning: ``NotFound``. + """) + ]) + let (bundle, context) = try loadBundle(catalog: catalog) + + let outputConsumer = TestOutputConsumer() + + _ = try ConvertActionConverter.convert( + bundle: bundle, + context: context, + outputConsumer: outputConsumer, + sourceRepository: nil, + emitDigest: true, + documentationCoverageOptions: .noCoverage + ) + + XCTAssertEqual(outputConsumer.problems.count, 2, "Unexpected problems: \(outputConsumer.problems.map(\.diagnostic.summary).joined(separator: "\n"))") + + let deprecationWarning = try XCTUnwrap(outputConsumer.problems.first?.diagnostic) + + XCTAssertEqual(deprecationWarning.identifier, "org.swift.docc.DeprecatedDiagnosticsDigets") + XCTAssertEqual(deprecationWarning.summary, "The 'diagnostics.json' digest file is deprecated and will be removed after 6.2 is released. Pass a `--diagnostics-file ` to specify a custom location where DocC will write a diagnostics JSON file with more information.") + } +} + +private class TestOutputConsumer: ConvertOutputConsumer { + var problems: [Problem] = [] + + func consume(problems: [Problem]) throws { + self.problems.append(contentsOf: problems) + } + + func consume(renderNode: RenderNode) throws { } + func consume(assetsInBundle bundle: DocumentationBundle) throws { } + func consume(linkableElementSummaries: [LinkDestinationSummary]) throws { } + func consume(indexingRecords: [IndexingRecord]) throws { } + func consume(assets: [RenderReferenceType: [RenderReference]]) throws { } + func consume(benchmarks: Benchmark) throws { } + func consume(documentationCoverageInfo: [CoverageDataEntry]) throws { } + func consume(renderReferenceStore: RenderReferenceStore) throws { } + func consume(buildMetadata: BuildMetadata) throws { } + func consume(linkResolutionInformation: SerializableLinkResolutionInformation) throws { } +} diff --git a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift index bf242a92ec..17b585f96d 100644 --- a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift +++ b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022-2024 Apple Inc. and the Swift project authors + Copyright (c) 2022-2025 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,7 +21,6 @@ class TestRenderNodeOutputConsumer: ConvertOutputConsumer { } } - func consume(problems: [Problem]) throws { } func consume(assetsInBundle bundle: DocumentationBundle) throws { } func consume(linkableElementSummaries: [LinkDestinationSummary]) throws { } func consume(indexingRecords: [IndexingRecord]) throws { } diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift index d932b8d037..c022804a3c 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.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-2025 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 @@ -547,6 +547,9 @@ class ConvertActionTests: XCTestCase { } } + // This test uses ``Digest.Diagnostic`` which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testOutputFolderContainsDiagnosticJSONWhenThereAreWarnings() async throws { // Documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ @@ -602,6 +605,15 @@ class ConvertActionTests: XCTestCase { Folder(name: "com.test.example", content: []), ]), JSONFile(name: "diagnostics.json", content: [ + Digest.Diagnostic( + start: nil, + source: nil, + severity: .warning, + summary: "The 'diagnostics.json' digest file is deprecated and will be removed after 6.2 is released. Pass a `--diagnostics-file ` to specify a custom location where DocC will write a diagnostics JSON file with more information.", + explanation: nil, + notes: [] + ), + Digest.Diagnostic( start: .init(line: 11, column: 7), source: URL(string: "TechnologyX.tutorial"), @@ -615,6 +627,9 @@ class ConvertActionTests: XCTestCase { expectedOutput.assertExist(at: result.outputs[0], fileManager: testDataProvider) } + // This test uses ``Digest.Diagnostic`` which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testOutputFolderContainsDiagnosticJSONWhenThereAreErrorsAndNoTemplate() async throws { // Documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ @@ -653,6 +668,15 @@ class ConvertActionTests: XCTestCase { // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ JSONFile(name: "diagnostics.json", content: [ + Digest.Diagnostic( + start: nil, + source: nil, + severity: .warning, + summary: "The 'diagnostics.json' digest file is deprecated and will be removed after 6.2 is released. Pass a `--diagnostics-file ` to specify a custom location where DocC will write a diagnostics JSON file with more information.", + explanation: nil, + notes: [] + ), + Digest.Diagnostic( start: .init(line: 6, column: 4), source: URL(string: "TechnologyX.tutorial"), @@ -682,6 +706,9 @@ class ConvertActionTests: XCTestCase { expectedOutput.assertExist(at: result.outputs[0], fileManager: testDataProvider) } + // This test uses ``Digest.Diagnostic`` which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testWarningForUncuratedTutorial() async throws { // Documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ @@ -733,6 +760,15 @@ class ConvertActionTests: XCTestCase { // Verify that the following files and folder exist at the output location let expectedOutput = Folder(name: ".docc-build", content: [ JSONFile(name: "diagnostics.json", content: [ + Digest.Diagnostic( + start: nil, + source: nil, + severity: .warning, + summary: "The 'diagnostics.json' digest file is deprecated and will be removed after 6.2 is released. Pass a `--diagnostics-file ` to specify a custom location where DocC will write a diagnostics JSON file with more information.", + explanation: nil, + notes: [] + ), + Digest.Diagnostic( start: nil, source: URL(string: "TechnologyX.tutorial"), @@ -825,6 +861,9 @@ class ConvertActionTests: XCTestCase { /// Verifies that digest is correctly emitted for API documentation topics /// like module pages, symbols, and articles. + // This test uses ``Digest.Diagnostic`` which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testMetadataIsWrittenToOutputFolderAPIDocumentation() async throws { // Example documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ @@ -1259,6 +1298,9 @@ class ConvertActionTests: XCTestCase { })) } + // This test uses ``Digest.Diagnostic`` which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testMetadataIsWrittenToOutputFolder() async throws { // Example documentation bundle that contains an image let bundle = Folder(name: "unit-test.docc", content: [ @@ -2351,6 +2393,9 @@ class ConvertActionTests: XCTestCase { XCTAssertEqual(action.configuration.externalMetadata.inheritDocs, false) } + // This test uses ``Digest.Diagnostic`` which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testEmitsDigest() async throws { let bundle = Folder(name: "unit-test.docc", content: [ InfoPlist(displayName: "TestBundle", identifier: "com.test.example"),