From b9c4b50a2a0af3f558c413aabed7254fb72ca316 Mon Sep 17 00:00:00 2001 From: Joseph Heck Date: Tue, 17 Jun 2025 01:45:55 -0700 Subject: [PATCH 01/90] corrects schema version property name in RenderIndex.spec.json (#1224) --- .../SwiftDocC/SwiftDocC.docc/Resources/RenderIndex.spec.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderIndex.spec.json b/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderIndex.spec.json index c1c8beb5b3..d100305d97 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderIndex.spec.json +++ b/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderIndex.spec.json @@ -15,7 +15,7 @@ "interfaceLanguages" ], "properties": { - "identifier": { + "schemaVersion": { "$ref": "#/components/schemas/SchemaVersion" }, "interfaceLanguages": { From b76e07fa46b2c018712b526f69233008de616ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 17 Jun 2025 11:16:38 +0200 Subject: [PATCH 02/90] Don't omit "Type" suffix from disambiguation for Swift metatypes (#1230) * Don't omit "Type" suffix from disambiguation for Swift metatype parameters rdar://152496046 * Remove trailing commas for compatibility before Swift 6.1 --- .../PathHierarchy+TypeSignature.swift | 5 +++ .../Infrastructure/PathHierarchyTests.swift | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift index 52af84c174..4eb428bca4 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift @@ -136,6 +136,11 @@ extension PathHierarchy { // For example: "[", "?", "<", "...", ",", "(", "->" etc. contribute to the type spellings like // `[Name]`, `Name?`, "Name", "Name...", "()", "(Name, Name)", "(Name)->Name" and more. let utf8Spelling = fragment.spelling.utf8 + guard !utf8Spelling.elementsEqual(".Type".utf8) else { + // Once exception to that is "Name.Type" which is different from just "Name" (and we don't want a trailing ".") + accumulated.append(contentsOf: utf8Spelling) + continue + } for index in utf8Spelling.indices { let char = utf8Spelling[index] switch char { diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index 47c61b9702..88eed6aa90 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -3772,6 +3772,42 @@ class PathHierarchyTests: XCTestCase { ]) } + // The second overload refers to the metatype of the parameter + do { + func makeSignature(first: DeclToken...) -> SymbolGraph.Symbol.FunctionSignature { + .init( + parameters: [.init(name: "first", externalName: "with", declarationFragments: makeFragments(first), children: []),], + returns: makeFragments([voidType]) + ) + } + + let someGenericTypeID = "some-generic-type-id" + let catalog = Folder(name: "unit-test.docc", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( + moduleName: "ModuleName", + symbols: [ + makeSymbol(id: "function-overload-1", kind: .func, pathComponents: ["doSomething(with:)"], signature: makeSignature( + // GenericName + first: .typeIdentifier("GenericName", precise: someGenericTypeID) + )), + + makeSymbol(id: "function-overload-2", kind: .func, pathComponents: ["doSomething(with:)"], signature: makeSignature( + // GenericName.Type + first: .typeIdentifier("GenericName", precise: someGenericTypeID), ".Type" + )), + ] + )) + ]) + + let (_, context) = try loadBundle(catalog: catalog) + let tree = context.linkResolver.localResolver.pathHierarchy + + try assertPathCollision("ModuleName/doSomething(with:)", in: tree, collisions: [ + (symbolID: "function-overload-1", disambiguation: "-(GenericName)"), // GenericName + (symbolID: "function-overload-2", disambiguation: "-(GenericName.Type)"), // GenericName.Type + ]) + } + // Second overload requires combination of two non-unique types to disambiguate do { // String Set (Double)->Void From 9c91d10260014bbe65feb5f45543215a19800d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 17 Jun 2025 11:24:17 +0200 Subject: [PATCH 03/90] Omit whitespace from suggested link completion disambiguations (#1232) rdar://152496072 --- .../LinkCompletionTools.swift | 10 +++++++-- .../LinkCompletionToolsTests.swift | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift b/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift index 7ed5ac3d52..a747125410 100644 --- a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift +++ b/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift @@ -131,8 +131,8 @@ public enum LinkCompletionTools { node, kind: symbol.kind, hash: symbol.symbolIDHash, - parameterTypes: symbol.parameterTypes, - returnTypes: symbol.returnTypes + parameterTypes: symbol.parameterTypes?.map { $0.withoutWhitespace() }, + returnTypes: symbol.returnTypes?.map { $0.withoutWhitespace() } ) } @@ -236,3 +236,9 @@ private extension PathHierarchy.PathComponent.Disambiguation { } } } + +private extension String { + func withoutWhitespace() -> String { + filter { !$0.isWhitespace } + } +} diff --git a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/LinkCompletionToolsTests.swift b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/LinkCompletionToolsTests.swift index 6cc34779d6..76d963527b 100644 --- a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/LinkCompletionToolsTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/LinkCompletionToolsTests.swift @@ -237,4 +237,25 @@ class LinkCompletionToolsTests: XCTestCase { "->_", // The only overload that returns something ]) } + + func testRemovesWhitespaceFromTypeSignatureDisambiguation() { + let overloads = [ + // The caller included whitespace in these closure type spellings but the DocC disambiguation won't include this whitespace. + (parameters: ["(Int) -> Int"], returns: []), // ((Int) -> Int) -> Void + (parameters: ["(Bool) -> ()"], returns: []), // ((Bool) -> () ) -> Void + ].map { + LinkCompletionTools.SymbolInformation( + kind: "func", + symbolIDHash: "\($0)".stableHashString, + parameterTypes: $0.parameters, + returnTypes: $0.returns + ) + } + + XCTAssertEqual(LinkCompletionTools.suggestedDisambiguation(forCollidingSymbols: overloads), [ + // Both parameters require the only parameter type as disambiguation. The suggested disambiguation shouldn't contain extra whitespace. + "-((Int)->Int)", + "-((Bool)->())", + ]) + } } From cd4c34c0cb499e23e8fb6da0ece66405de79f7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 17 Jun 2025 11:39:25 +0200 Subject: [PATCH 04/90] Don't remove leading and trailing parentheses for closure types in type signature disambiguation (#1231) * Fix issue where suggested link disambiguation removed leading and trailing parentheses for some closure types rdar://151311221 * Minor unrelated correction of whitespace in symbol declaration test data * Include more information in test failure messages about unknown disambiguation --- .../PathHierarchy+TypeSignature.swift | 42 ++++-- .../Infrastructure/PathHierarchyTests.swift | 123 +++++++++++++++++- 2 files changed, 152 insertions(+), 13 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift index 4eb428bca4..481615a90a 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift @@ -45,7 +45,7 @@ extension PathHierarchy { } let spelling = utf8TypeSpelling(for: fragments, isSwift: isSwift) - guard isSwift, spelling[...].isTuple() else { + guard isSwift, spelling[...].shapeOfSwiftTypeSpelling() == .tuple else { return [String(decoding: spelling, as: UTF8.self)] } @@ -194,14 +194,14 @@ extension PathHierarchy { } // Check if the type names are wrapped in redundant parenthesis and remove them - if accumulated.first == openParen, accumulated.last == closeParen, !accumulated[...].isTuple() { + if accumulated.first == openParen, accumulated.last == closeParen, accumulated[...].shapeOfSwiftTypeSpelling() == .scalar { // In case there are multiple // Use a temporary slice until all the layers of redundant parenthesis have been removed. var temp = accumulated[...] repeat { temp = temp.dropFirst().dropLast() - } while temp.first == openParen && temp.last == closeParen && !temp.isTuple() + } while temp.first == openParen && temp.last == closeParen && temp.shapeOfSwiftTypeSpelling() == .scalar // Adjust the markers so that they align with the expected characters let difference = (accumulated.count - temp.count) / 2 @@ -282,26 +282,48 @@ private let question = UTF8.CodeUnit(ascii: "?") private let colon = UTF8.CodeUnit(ascii: ":") private let hyphen = UTF8.CodeUnit(ascii: "-") +/// A guesstimate of the "shape" of a Swift type based on its spelling. +private enum ShapeOfSwiftTypeSpelling { + /// This type spelling looks like a scalar. + /// + /// For example `Name` or `(Name)`. + /// - Note: We treat `(Name)` as a non-tuple so that we can remove the redundant leading and trailing parenthesis. + case scalar + /// This type spelling looks like a tuple. + /// + /// For example `(First, Second)`. + case tuple + /// This type spelling looks like a closure. + /// + /// For example `(First)->Second` or `(First, Second)->()` or `()->()`. + case closure +} + private extension ContiguousArray.SubSequence { - /// Checks if the UTF-8 string looks like a tuple with comma separated values. + /// Checks if the UTF-8 string looks like a tuple, scalar, or closure. /// /// This is used to remove redundant parenthesis around expressions. - func isTuple() -> Bool { - guard first == openParen, last == closeParen else { return false } + func shapeOfSwiftTypeSpelling() -> ShapeOfSwiftTypeSpelling { + guard first == openParen, last == closeParen else { return .scalar } var depth = 0 - for char in self { - switch char { + for index in indices { + switch self[index] { case openParen: depth += 1 case closeParen: depth -= 1 case comma where depth == 1: - return true + // If we find "," in one level of parenthesis, we've found a tuple. + return .tuple + case closeAngle where depth == 0 && index > startIndex && self[index - 1] == hyphen: + // If we find "->" outside any parentheses, we've found a closure. + return .closure default: continue } } - return false + // If we traversed the entire type name without finding a tuple or a closure we treat the type name as a scalar. + return .scalar } } diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index 88eed6aa90..4c9c1b00dd 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -3328,6 +3328,73 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("/ModuleName/ContainerName", in: tree, asSymbolID: containerID) } + func testMinimalTypeDisambiguationForClosureParameterWithVoidReturnType() throws { + // Create a `doSomething(with:and:)` function with a `String` parameter (same in every overload) and a `(TYPE)->()` closure parameter. + func makeSymbolOverload(closureParameterType: SymbolGraph.Symbol.DeclarationFragments.Fragment) -> SymbolGraph.Symbol { + makeSymbol( + id: "some-function-overload-\(closureParameterType.spelling.lowercased())", + kind: .method, + pathComponents: ["doSomething(with:and:)"], + signature: .init( + parameters: [ + .init(name: "first", externalName: "with", declarationFragments: [ + .init(kind: .externalParameter, spelling: "with", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .internalParameter, spelling: "first", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "String", preciseIdentifier: "s:SS") + ], children: []), + + .init(name: "second", externalName: "and", declarationFragments: [ + .init(kind: .externalParameter, spelling: "and", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .internalParameter, spelling: "second", preciseIdentifier: nil), + .init(kind: .text, spelling: " (", preciseIdentifier: nil), + closureParameterType, + .init(kind: .text, spelling: ") -> ()", preciseIdentifier: nil), + ], children: []) + ], + returns: [.init(kind: .typeIdentifier, spelling: "Void", preciseIdentifier: "s:s4Voida")] + ) + ) + } + + let catalog = Folder(name: "unit-test.docc", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( + moduleName: "ModuleName", + symbols: [ + makeSymbolOverload(closureParameterType: .init(kind: .typeIdentifier, spelling: "Int", preciseIdentifier: "s:Si")), // (String, (Int)->()) -> Void + makeSymbolOverload(closureParameterType: .init(kind: .typeIdentifier, spelling: "Double", preciseIdentifier: "s:Sd")), // (String, (Double)->()) -> Void + makeSymbolOverload(closureParameterType: .init(kind: .typeIdentifier, spelling: "Float", preciseIdentifier: "s:Sf")), // (String, (Float)->()) -> Void + ], + relationships: [] + )) + ]) + + let (_, context) = try loadBundle(catalog: catalog) + let tree = context.linkResolver.localResolver.pathHierarchy + + let link = "/ModuleName/doSomething(with:and:)" + try assertPathRaisesErrorMessage(link, in: tree, context: context, expectedErrorMessage: "'doSomething(with:and:)' is ambiguous at '/ModuleName'") { errorInfo in + XCTAssertEqual(errorInfo.solutions.count, 3, "There should be one suggestion per overload") + for solution in errorInfo.solutions { + // Apply the suggested replacements for each solution and verify that _that_ link resolves to a single symbol. + var linkWithSuggestion = link + XCTAssertFalse(solution.replacements.isEmpty, "Diagnostics about ambiguous links should have some replacements for each solution.") + for (replacementText, start, end) in solution.replacements { + let range = linkWithSuggestion.index(linkWithSuggestion.startIndex, offsetBy: start) ..< linkWithSuggestion.index(linkWithSuggestion.startIndex, offsetBy: end) + linkWithSuggestion.replaceSubrange(range, with: replacementText) + } + + XCTAssertNotNil(try? tree.findSymbol(path: linkWithSuggestion), """ + Failed to resolve \(linkWithSuggestion) after applying replacements \(solution.replacements.map { "'\($0.0)'@\($0.start)-\($0.end)" }.joined(separator: ",")) to '\(link)'. + + The replacement that DocC suggests in its warnings should unambiguously refer to a single symbol match. + """) + } + } + } + func testMissingMemberOfAnonymousStructInsideUnion() throws { let outerContainerID = "some-outer-container-symbol-id" let innerContainerID = "some-inner-container-symbol-id" @@ -3665,7 +3732,7 @@ class PathHierarchyTests: XCTestCase { let voidType = DeclToken.typeIdentifier("Void", precise: "s:s4Voida") func makeParameter(_ name: String, decl: [DeclToken]) -> SymbolGraph.Symbol.FunctionSignature.FunctionParameter { - .init(name: name, externalName: nil, declarationFragments: makeFragments([.internalParameter(name), .text("")] + decl), children: []) + .init(name: name, externalName: nil, declarationFragments: makeFragments([.internalParameter(name), .text(" ")] + decl), children: []) } func makeSignature(first: DeclToken..., second: DeclToken..., third: DeclToken...) -> SymbolGraph.Symbol.FunctionSignature { @@ -3772,6 +3839,56 @@ class PathHierarchyTests: XCTestCase { ]) } + // Each overload has a unique closure parameter with a "()" literal closure return type + do { + func makeSignature(first: DeclToken..., second: DeclToken...) -> SymbolGraph.Symbol.FunctionSignature { + .init( + parameters: [ + .init(name: "first", externalName: nil, declarationFragments: makeFragments(first), children: []), + .init(name: "second", externalName: nil, declarationFragments: makeFragments(second), children: []) + ], + returns: makeFragments([voidType]) + ) + } + + // String (Int)->() + // String (Double)->() + // String (Float)->() + let catalog = Folder(name: "unit-test.docc", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( + moduleName: "ModuleName", + symbols: [ + // String (Int)->Void + makeSymbol(id: "function-overload-1", kind: .func, pathComponents: ["doSomething(first:second:)"], signature: makeSignature( + first: stringType, // String + second: "(", intType, ") -> ()" // (Int)->() + )), + + // String (Double)->Void + makeSymbol(id: "function-overload-2", kind: .func, pathComponents: ["doSomething(first:second:)"], signature: makeSignature( + first: stringType, // String + second: "(", doubleType, ") -> ()" // (Double)->() + )), + + // String (Float)->Void + makeSymbol(id: "function-overload-3", kind: .func, pathComponents: ["doSomething(first:second:)"], signature: makeSignature( + first: stringType, // String + second: "(", floatType, ") -> ()" // (Double)->() + )), + ] + )) + ]) + + let (_, context) = try loadBundle(catalog: catalog) + let tree = context.linkResolver.localResolver.pathHierarchy + + try assertPathCollision("ModuleName/doSomething(first:second:)", in: tree, collisions: [ + (symbolID: "function-overload-1", disambiguation: "-(_,(Int)->())"), // _ (Int)->() + (symbolID: "function-overload-2", disambiguation: "-(_,(Double)->())"), // _ (Double)->() + (symbolID: "function-overload-3", disambiguation: "-(_,(Float)->())"), // _ (Float)->() + ]) + } + // The second overload refers to the metatype of the parameter do { func makeSignature(first: DeclToken...) -> SymbolGraph.Symbol.FunctionSignature { @@ -4350,8 +4467,8 @@ class PathHierarchyTests: XCTestCase { XCTFail("Symbol for \(path.singleQuoted) not found in tree", file: file, line: line) } catch PathHierarchy.Error.unknownName { XCTFail("Symbol for \(path.singleQuoted) not found in tree. Only part of path is found.", file: file, line: line) - } catch PathHierarchy.Error.unknownDisambiguation { - XCTFail("Symbol for \(path.singleQuoted) not found in tree. Unknown disambiguation.", file: file, line: line) + } catch PathHierarchy.Error.unknownDisambiguation(_, _, let candidates) { + XCTFail("Symbol for \(path.singleQuoted) not found in tree. Unknown disambiguation. Suggested disambiguations: \(candidates.map(\.disambiguation.singleQuoted).sorted().joined(separator: ", "))", file: file, line: line) } catch PathHierarchy.Error.lookupCollision(_, _, let collisions) { let symbols = collisions.map { $0.node.symbol! } XCTFail("Unexpected collision for \(path.singleQuoted); \(symbols.map { return "\($0.names.title) - \($0.kind.identifier.identifier) - \($0.identifier.precise.stableHashString)"})", file: file, line: line) From 6fa695d3eba03f957495b8229feda4c689d0696d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 17 Jun 2025 11:47:16 +0200 Subject: [PATCH 05/90] Fix warnings in Swift 6.2 about non-sendable types in isolated closures (#1233) --- ...ternalReferenceResolverServiceClient.swift | 2 +- .../Collection+ConcurrentPerform.swift | 4 ++-- .../SendableMetatypeShim.swift | 21 +++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 Sources/SwiftDocC/Utility/FoundationExtensions/SendableMetatypeShim.swift diff --git a/Sources/SwiftDocC/DocumentationService/ExternalReferenceResolverServiceClient.swift b/Sources/SwiftDocC/DocumentationService/ExternalReferenceResolverServiceClient.swift index 6fd3fd51ad..5fcc59cdaf 100644 --- a/Sources/SwiftDocC/DocumentationService/ExternalReferenceResolverServiceClient.swift +++ b/Sources/SwiftDocC/DocumentationService/ExternalReferenceResolverServiceClient.swift @@ -42,7 +42,7 @@ class ExternalReferenceResolverServiceClient { self.convertRequestIdentifier = convertRequestIdentifier } - func sendAndWait(_ request: some Codable) throws -> Data { + func sendAndWait(_ request: some Codable & SendableMetatype) throws -> Data { let resultGroup = DispatchGroup() var result: Result? diff --git a/Sources/SwiftDocC/Utility/Collection+ConcurrentPerform.swift b/Sources/SwiftDocC/Utility/Collection+ConcurrentPerform.swift index 5fbcdea3be..37a0a5fe33 100644 --- a/Sources/SwiftDocC/Utility/Collection+ConcurrentPerform.swift +++ b/Sources/SwiftDocC/Utility/Collection+ConcurrentPerform.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 @@ -20,7 +20,7 @@ private let useConcurrentCollectionExtensions = true private let useConcurrentCollectionExtensions = false #endif -extension Collection where Index == Int { +extension Collection where Index == Int, Self: SendableMetatype { /// Concurrently transforms the elements of a collection. /// - Parameters: diff --git a/Sources/SwiftDocC/Utility/FoundationExtensions/SendableMetatypeShim.swift b/Sources/SwiftDocC/Utility/FoundationExtensions/SendableMetatypeShim.swift new file mode 100644 index 0000000000..f9471a06d2 --- /dev/null +++ b/Sources/SwiftDocC/Utility/FoundationExtensions/SendableMetatypeShim.swift @@ -0,0 +1,21 @@ +/* + 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 +*/ + +// In Swift 6.2, metatypes are no longer sendable by default (SE-0470). +// Instead a type needs to conform to `SendableMetatype` to indicate that its metatype is sendable. +// +// However, `SendableMetatype` doesn't exist before Swift 6.1 so we define an internal alias to `Any` here. +// This means that conformances to `SendableMetatype` has no effect before 6.2 indicates metatype sendability in 6.2 onwards. +// +// Note: Adding a protocol requirement to a _public_ API is a breaking change. + +#if compiler(<6.2) +typealias SendableMetatype = Any +#endif From c7660bb4f428db5f7651b26a02beb58fe701d662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Thu, 26 Jun 2025 09:50:49 +0200 Subject: [PATCH 06/90] Fix false positive curation for article links with extra path components (#1235) * Fix false positive curation for article links with extra path components rdar://150874238 * Remove explicit `.init` in test file * Remove unused variable in test * Add missing path separator in curator fallback code path --- .../Infrastructure/DocumentationCurator.swift | 20 ++- .../DocumentationCuratorTests.swift | 115 ++++++++++++++++-- 2 files changed, 123 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift b/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift index b310940a53..1519965672 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift @@ -90,9 +90,22 @@ struct DocumentationCurator { } // Try extracting an article from the cache - let articleFilename = unresolved.topicURL.components.path.components(separatedBy: "/").last! - let sourceArticlePath = NodeURLGenerator.Path.article(bundleName: bundle.displayName, articleName: articleFilename).stringValue - + let sourceArticlePath: String = { + let path = unresolved.topicURL.components.path.removingLeadingSlash + + // The article path can either be written as + // - "ArticleName" + // - "CatalogName/ArticleName" + // - "documentation/CatalogName/ArticleName" + switch path.components(separatedBy: "/").count { + case 0,1: + return NodeURLGenerator.Path.article(bundleName: bundle.displayName, articleName: path).stringValue + case 2: + return "\(NodeURLGenerator.Path.documentationFolder)/\(path)" + default: + return path.prependingLeadingSlash + } + }() let reference = ResolvedTopicReference( bundleID: resolved.bundleID, path: sourceArticlePath, @@ -115,6 +128,7 @@ struct DocumentationCurator { context.topicGraph.addNode(curatedNode) // Move the article from the article cache to the documentation + let articleFilename = reference.url.pathComponents.last! context.linkResolver.localResolver.addArticle(filename: articleFilename, reference: reference, anchorSections: documentationNode.anchorSections) context.documentationCache[reference] = documentationNode diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift index f40e9ef03d..0149024be8 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.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 @@ -30,7 +30,7 @@ class DocumentationCuratorTests: XCTestCase { func testCrawl() throws { let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var crawler = DocumentationCurator.init(in: context, bundle: bundle) + var crawler = DocumentationCurator(in: context, bundle: bundle) let mykit = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", sourceLanguage: .swift)) var symbolsWithCustomCuration = [ResolvedTopicReference]() @@ -303,7 +303,7 @@ class DocumentationCuratorTests: XCTestCase { """.write(to: url.appendingPathComponent("Root.md"), atomically: true, encoding: .utf8) } - let crawler = DocumentationCurator.init(in: context, bundle: bundle) + let crawler = DocumentationCurator(in: context, bundle: bundle) XCTAssert(context.problems.isEmpty, "Expected no problems. Found: \(context.problems.map(\.diagnostic.summary))") guard let moduleNode = context.documentationCache["SourceLocations"], @@ -316,11 +316,109 @@ class DocumentationCuratorTests: XCTestCase { XCTAssertEqual(root.path, "/documentation/Root") XCTAssertEqual(crawler.problems.count, 0) - + } + + func testCuratorDoesNotRelateNodesWhenArticleLinksContainExtraPathComponents() throws { + let (bundle, context) = try loadBundle(catalog: + Folder(name: "CatalogName.docc", content: [ + TextFile(name: "Root.md", utf8Content: """ + # Root + + @Metadata { + @TechnologyRoot + } + + Add an API Collection of indirection to more easily detect the failed curation. + + ## Topics + - + """), + + TextFile(name: "API-Collection.md", utf8Content: """ + # Some API Collection + + Fail to curate all 4 articles because of extra incorrect path components. + + ## Topics + + ### No links will resolve in this section + + - + - + - + - + """), + + TextFile(name: "First.md", utf8Content: "# First"), + TextFile(name: "Second.md", utf8Content: "# Second"), + TextFile(name: "Third.md", utf8Content: "# Third"), + TextFile(name: "Forth.md", utf8Content: "# Forth"), + ]) + ) + let (linkResolutionProblems, otherProblems) = context.problems.categorize(where: { $0.diagnostic.identifier == "org.swift.docc.unresolvedTopicReference" }) + XCTAssert(otherProblems.isEmpty, "Unexpected problems: \(otherProblems.map(\.diagnostic.summary).sorted())") + + XCTAssertEqual( + linkResolutionProblems.map(\.diagnostic.source?.lastPathComponent), + ["API-Collection.md", "API-Collection.md", "API-Collection.md", "API-Collection.md"], + "Every unresolved link is in the API collection" + ) + XCTAssertEqual( + linkResolutionProblems.map({ $0.diagnostic.range?.lowerBound.line }), [9, 10, 11, 12], + "There should be one warning about an unresolved reference for each link in the API collection's top" + ) + + let rootReference = try XCTUnwrap(context.soleRootModuleReference) + + for articleName in ["First", "Second", "Third", "Forth"] { + let reference = try XCTUnwrap(context.documentationCache.allReferences.first(where: { $0.lastPathComponent == articleName })) + XCTAssertEqual( + context.topicGraph.nodeWithReference(reference)?.shouldAutoCurateInCanonicalLocation, true, + "Article '\(articleName)' isn't (successfully) manually curated and should therefore automatically curate." + ) + XCTAssertEqual( + context.topicGraph.reverseEdges[reference]?.map(\.path), [rootReference.path], + "Article '\(articleName)' should only have a reverse edge to the root page where it will be automatically curated." + ) + } + + let apiCollectionReference = try XCTUnwrap(context.documentationCache.allReferences.first(where: { $0.lastPathComponent == "API-Collection" })) + let apiCollectionSemantic = try XCTUnwrap(try context.entity(with: apiCollectionReference).semantic as? Article) + XCTAssertEqual(apiCollectionSemantic.topics?.taskGroups.count, 1, "The API Collection has one topic section") + let topicSection = try XCTUnwrap(apiCollectionSemantic.topics?.taskGroups.first) + XCTAssertEqual(topicSection.links.map(\.destination), [ + // All these links are the same as they were authored which means that they didn't resolve. + "doc:WrongModuleName/First", + "doc:documentation/WrongModuleName/Second", + "doc:documentation/CatalogName/ExtraPathComponent/Third", + "doc:CatalogName/ExtraPathComponent/Forth", + ]) + + let rootPage = try context.entity(with: rootReference) + let renderer = DocumentationNodeConverter(bundle: bundle, context: context) + let renderNode = renderer.convert(rootPage) + + XCTAssertEqual(renderNode.topicSections.map(\.title), [ + nil, // An unnamed topic section + "Articles", // The automatic topic section + ]) + XCTAssertEqual(renderNode.topicSections.map { $0.identifiers.sorted() }, [ + // The unnamed topic section curates the API collection + [ + "doc://CatalogName/documentation/CatalogName/API-Collection" + ], + // The automatic "Articles" section curates all 4 articles + [ + "doc://CatalogName/documentation/CatalogName/First", + "doc://CatalogName/documentation/CatalogName/Forth", + "doc://CatalogName/documentation/CatalogName/Second", + "doc://CatalogName/documentation/CatalogName/Third", + ], + ]) } func testModuleUnderAncestorOfTechnologyRoot() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "SourceLocations") { url in + let (_, _, context) = try testBundleAndContext(copying: "SourceLocations") { url in try """ # Root with ancestor curating a module @@ -347,7 +445,6 @@ class DocumentationCuratorTests: XCTestCase { """.write(to: url.appendingPathComponent("Ancestor.md"), atomically: true, encoding: .utf8) } - let _ = DocumentationCurator.init(in: context, bundle: bundle) XCTAssert(context.problems.isEmpty, "Expected no problems. Found: \(context.problems.map(\.diagnostic.summary))") guard let moduleNode = context.documentationCache["SourceLocations"], @@ -364,7 +461,7 @@ class DocumentationCuratorTests: XCTestCase { func testSymbolLinkResolving() throws { let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let crawler = DocumentationCurator.init(in: context, bundle: bundle) + let crawler = DocumentationCurator(in: context, bundle: bundle) // Resolve top-level symbol in module parent do { @@ -417,7 +514,7 @@ class DocumentationCuratorTests: XCTestCase { func testLinkResolving() throws { let (sourceRoot, bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var crawler = DocumentationCurator.init(in: context, bundle: bundle) + var crawler = DocumentationCurator(in: context, bundle: bundle) // Resolve and curate an article in module root (absolute link) do { @@ -510,7 +607,7 @@ class DocumentationCuratorTests: XCTestCase { """.write(to: root.appendingPathComponent("documentation").appendingPathComponent("api-collection.md"), atomically: true, encoding: .utf8) } - var crawler = DocumentationCurator.init(in: context, bundle: bundle) + var crawler = DocumentationCurator(in: context, bundle: bundle) let reference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit", sourceLanguage: .swift) try crawler.crawlChildren(of: reference, prepareForCuration: {_ in }) { (_, _) in } From 65aaf926ec079ddbd40f29540d4180a70af99e5e Mon Sep 17 00:00:00 2001 From: Andrea Fernandez Buitrago <15234535+anferbui@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:26:50 +0100 Subject: [PATCH 07/90] Support external links in the navigator (#1247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * External Render Node Support Created an `ExternalRenderNode` structure that represents a pseudo render node out of an externally resolved value. This structure contains the minimal information needed to hold a value that can be passed to the Navigator Index. Fixes rdar://146123573. Co-authored-by: Sofía Rodríguez * External Render Node representation for the navigator The structures `NavigatorExternalRenderNode` and `ExternalRenderNodeMetadataRepresentation` are meant to hold the information needed to add an external render node in the navigator index. Each of these contains the information of a external render node for a specific language variant, since things like the node title change depending on the selected variant. Fields which cannot be populated due to insufficient information have been documented. These can have varying effect on the resulting navigation node, particularly the navigator title. Fixes rdar://146123573. Co-authored-by: Sofía Rodríguez * Index external render nodes This method mimics the index method for render nodes [1] by indexing the main node representation, and the Objective-C representation. [1] https://github.com/swiftlang/swift-docc/blob/38ea39df14d0193f52900dbe54b7ae2be0abd856/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift\#L636 Fixes rdar://146123573. Co-authored-by: Sofía Rodríguez * Consume external render nodes During the convert process create the external render nodes from the context external cache and consume them to get them added into the navigator index. Fixes rdar://146123573. Co-authored-by: Sofía Rodríguez * Only add external nodes to index if manually curated in Topics section The NavigationIndex code has some logic about what to do with fallouts (i.e. nodes which are not curated anywhere). It places them at the top level of the navigator, so that they are not lost. This works well for render nodes, but for external render nodes, we don't want them to persist at the top level if they are not referenced anywhere. We'd like them to be excluded if not referenced. This can happen due to the fact that we consume **all** successfully resolved external entities for indexing, which includes external references in other sections of the page such as the Overview section, the abstract and the See also section. Fixes rdar://146123573. * Propagate isExternal value to RenderIndex Now that the `isExternal` property is populated as part of the `NavigatorItem`s, we can now use that value to propagate whether the node `isExternal` in the final `RenderIndex`, something which we couldn't support before. External nodes are now marked as "external": true in `index.json` as expected. Fixes rdar://146123573. --------- Co-authored-by: Sofía Rodríguez --- .../Indexing/Navigator/NavigatorIndex.swift | 33 ++- .../Indexing/Navigator/NavigatorItem.swift | 11 +- .../RenderIndexJSON/RenderIndex.swift | 17 +- .../ConvertActionConverter.swift | 11 +- .../ConvertOutputConsumer.swift | 9 + .../LinkResolver+NavigatorIndex.swift | 148 ++++++++++ .../Convert/ConvertFileWritingConsumer.swift | 7 +- .../Action/Actions/Convert/Indexer.swift | 16 ++ .../DocumentationConverterTests.swift | 6 +- ...recatedDiagnosticsDigestWarningTests.swift | 3 +- .../Indexing/ExternalRenderNodeTests.swift | 272 ++++++++++++++++++ .../TestRenderNodeOutputConsumer.swift | 3 +- 12 files changed, 521 insertions(+), 15 deletions(-) create mode 100644 Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift create mode 100644 Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift index 53b4e99829..63a427ff87 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift @@ -630,6 +630,28 @@ extension NavigatorIndex { } } + /// Index a single render `ExternalRenderNode`. + /// - Parameter renderNode: The render node to be indexed. + package func index(renderNode: ExternalRenderNode, ignoringLanguage: Bool = false) throws { + let navigatorRenderNode = NavigatorExternalRenderNode(renderNode: renderNode) + _ = try index(navigatorRenderNode, traits: nil, isExternal: true) + guard renderNode.identifier.sourceLanguage != .objectiveC else { + return + } + // Check if the render node has an Objective-C representation + guard let objCVariantTrait = renderNode.variants?.flatMap(\.traits).first(where: { trait in + switch trait { + case .interfaceLanguage(let language): + return InterfaceLanguage.from(string: language) == .objc + } + }) else { + return + } + // If this external render node has a variant, we create a "view" into its Objective-C specific data and index that. + let objVariantView = NavigatorExternalRenderNode(renderNode: renderNode, trait: objCVariantTrait) + _ = try index(objVariantView, traits: [objCVariantTrait], isExternal: true) + } + /// Index a single render `RenderNode`. /// - Parameter renderNode: The render node to be indexed. /// - Parameter ignoringLanguage: Whether language variants should be ignored when indexing this render node. @@ -684,7 +706,7 @@ extension NavigatorIndex { } // The private index implementation which indexes a given render node representation - private func index(_ renderNode: any NavigatorIndexableRenderNodeRepresentation, traits: [RenderNode.Variant.Trait]?) throws -> InterfaceLanguage? { + private func index(_ renderNode: any NavigatorIndexableRenderNodeRepresentation, traits: [RenderNode.Variant.Trait]?, isExternal external: Bool = false) throws -> InterfaceLanguage? { guard let navigatorIndex else { throw Error.navigatorIndexIsNil } @@ -781,7 +803,8 @@ extension NavigatorIndex { title: title, platformMask: platformID, availabilityID: UInt64(availabilityID), - icon: renderNode.icon + icon: renderNode.icon, + isExternal: external ) navigationItem.path = identifierPath @@ -812,7 +835,8 @@ extension NavigatorIndex { languageID: language.mask, title: title, platformMask: platformID, - availabilityID: UInt64(Self.availabilityIDWithNoAvailabilities) + availabilityID: UInt64(Self.availabilityIDWithNoAvailabilities), + isExternal: external ) groupItem.path = identifier.path + "#" + fragment @@ -976,7 +1000,8 @@ extension NavigatorIndex { // curation, then they should not be in the navigator. In addition, treat unknown // page types as symbol nodes on the assumption that an unknown page type is a // symbol kind added in a future version of Swift-DocC. - if let node = identifierToNode[nodeID], PageType(rawValue: node.item.pageType)?.isSymbolKind == false { + // Finally, don't add external references to the root; if they are not referenced within the navigation tree, they should be dropped altogether. + if let node = identifierToNode[nodeID], PageType(rawValue: node.item.pageType)?.isSymbolKind == false , !node.item.isExternal { // If an uncurated page has been curated in another language, don't add it to the top-level. if curatedReferences.contains(where: { curatedNodeID in diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift index 78d281dd04..32192682f2 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift @@ -49,6 +49,11 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString var icon: RenderReferenceIdentifier? = nil + /// Whether the item has originated from an external reference. + /// + /// Used for determining whether stray navigation items should remain part of the final navigator. + var isExternal: Bool = false + /** Initialize a `NavigatorItem` with the given data. @@ -61,7 +66,7 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString - path: The path to load the content. - icon: A reference to a custom image for this navigator item. */ - init(pageType: UInt8, languageID: UInt8, title: String, platformMask: UInt64, availabilityID: UInt64, path: String, icon: RenderReferenceIdentifier? = nil) { + init(pageType: UInt8, languageID: UInt8, title: String, platformMask: UInt64, availabilityID: UInt64, path: String, icon: RenderReferenceIdentifier? = nil, isExternal: Bool = false) { self.pageType = pageType self.languageID = languageID self.title = title @@ -69,6 +74,7 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString self.availabilityID = availabilityID self.path = path self.icon = icon + self.isExternal = isExternal } /** @@ -82,13 +88,14 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString - availabilityID: The identifier of the availability information of the page. - icon: A reference to a custom image for this navigator item. */ - public init(pageType: UInt8, languageID: UInt8, title: String, platformMask: UInt64, availabilityID: UInt64, icon: RenderReferenceIdentifier? = nil) { + public init(pageType: UInt8, languageID: UInt8, title: String, platformMask: UInt64, availabilityID: UInt64, icon: RenderReferenceIdentifier? = nil, isExternal: Bool = false) { self.pageType = pageType self.languageID = languageID self.title = title self.platformMask = platformMask self.availabilityID = availabilityID self.icon = icon + self.isExternal = isExternal } // MARK: - Serialization and Deserialization diff --git a/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift b/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift index 060c204b86..5140e35fd3 100644 --- a/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift +++ b/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift @@ -86,7 +86,15 @@ public struct RenderIndex: Codable, Equatable { /// - Parameter named: The name of the new root node public mutating func insertRoot(named: String) { for (languageID, nodes) in interfaceLanguages { - let root = Node(title: named, path: "/documentation", pageType: .framework, isDeprecated: false, children: nodes, icon: nil) + let root = Node( + title: named, + path: "/documentation", + pageType: .framework, + isDeprecated: false, + isExternal: false, + children: nodes, + icon: nil + ) interfaceLanguages[languageID] = [root] } } @@ -236,6 +244,7 @@ extension RenderIndex { path: String, pageType: NavigatorIndex.PageType?, isDeprecated: Bool, + isExternal: Bool, children: [Node], icon: RenderReferenceIdentifier? ) { @@ -243,11 +252,10 @@ extension RenderIndex { self.children = children.isEmpty ? nil : children self.isDeprecated = isDeprecated + self.isExternal = isExternal - // Currently Swift-DocC doesn't support resolving links to external DocC archives + // Currently Swift-DocC doesn't support marking a node as beta in the navigation index // so we default to `false` here. - self.isExternal = false - self.isBeta = false self.icon = icon @@ -318,6 +326,7 @@ extension RenderIndex.Node { path: node.item.path, pageType: NavigatorIndex.PageType(rawValue: node.item.pageType), isDeprecated: isDeprecated, + isExternal: node.item.isExternal, children: node.children.map { RenderIndex.Node.fromNavigatorTreeNode($0, in: navigatorIndex, with: builder) }, diff --git a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift index 2d348d425d..17a5db0a70 100644 --- a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift +++ b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift @@ -34,7 +34,7 @@ package enum ConvertActionConverter { package static func convert( bundle: DocumentationBundle, context: DocumentationContext, - outputConsumer: some ConvertOutputConsumer, + outputConsumer: some ConvertOutputConsumer & ExternalNodeConsumer, sourceRepository: SourceRepository?, emitDigest: Bool, documentationCoverageOptions: DocumentationCoverageOptions @@ -104,6 +104,15 @@ package enum ConvertActionConverter { let resultsSyncQueue = DispatchQueue(label: "Convert Serial Queue", qos: .unspecified, attributes: []) let resultsGroup = DispatchGroup() + // Consume external links and add them into the sidebar. + for externalLink in context.externalCache { + // Here we're associating the external node with the **current** bundle's bundle ID. + // This is needed because nodes are only considered children if the parent and child's bundle ID match. + // Otherwise, the node will be considered as a separate root node and displayed separately. + let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + try outputConsumer.consume(externalRenderNode: externalRenderNode) + } + let renderSignpostHandle = signposter.beginInterval("Render", id: signposter.makeSignpostID(), "Render \(context.knownPages.count) pages") var conversionProblems: [Problem] = context.knownPages.concurrentPerform { identifier, results in diff --git a/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift b/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift index afe7a82720..830404dda6 100644 --- a/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift +++ b/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift @@ -105,3 +105,12 @@ package struct _Deprecated: _DeprecatedConsumeP try consumer.consume(problems: problems) } } + +/// A consumer for nodes generated from external references. +/// +/// Types that conform to this protocol manage what to do with external references, for example index them. +package protocol ExternalNodeConsumer { + /// Consumes a external render node that was generated during a conversion. + /// > Warning: This method might be called concurrently. + func consume(externalRenderNode: ExternalRenderNode) throws +} diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift new file mode 100644 index 0000000000..c23a850f71 --- /dev/null +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift @@ -0,0 +1,148 @@ +/* + 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 SymbolKit + +/// A rendering-friendly representation of a external node. +package struct ExternalRenderNode { + /// Underlying external entity backing this external node. + private var externalEntity: LinkResolver.ExternalEntity + + /// The bundle identifier for this external node. + private var bundleIdentifier: DocumentationBundle.Identifier + + init(externalEntity: LinkResolver.ExternalEntity, bundleIdentifier: DocumentationBundle.Identifier) { + self.externalEntity = externalEntity + self.bundleIdentifier = bundleIdentifier + } + + /// The identifier of the external render node. + package var identifier: ResolvedTopicReference { + ResolvedTopicReference( + bundleID: bundleIdentifier, + path: externalEntity.topicRenderReference.url, + sourceLanguages: externalEntity.sourceLanguages + ) + } + + /// The kind of this documentation node. + var kind: RenderNode.Kind { + externalEntity.topicRenderReference.kind + } + + /// The symbol kind of this documentation node. + var symbolKind: SymbolGraph.Symbol.KindIdentifier? { + // Symbol kind information is not available for external entities + return nil + } + + /// The additional "role" assigned to the symbol, if any + /// + /// This value is `nil` if the referenced page is not a symbol. + var role: String? { + externalEntity.topicRenderReference.role + } + + /// The variants of the title. + var titleVariants: VariantCollection { + externalEntity.topicRenderReference.titleVariants + } + + /// The variants of the abbreviated declaration of the symbol to display in navigation. + var navigatorTitleVariants: VariantCollection<[DeclarationRenderSection.Token]?> { + externalEntity.topicRenderReference.navigatorTitleVariants + } + + /// The variants of the abbreviated declaration of the symbol to display in links. + var fragmentsVariants: VariantCollection<[DeclarationRenderSection.Token]?> { + externalEntity.topicRenderReference.fragmentsVariants + } + + /// Author provided images that represent this page. + var images: [TopicImage] { + externalEntity.topicRenderReference.images + } + + /// The identifier of the external reference. + var externalIdentifier: RenderReferenceIdentifier { + externalEntity.topicRenderReference.identifier + } + + /// List of variants of the same external node for various languages. + var variants: [RenderNode.Variant]? { + externalEntity.sourceLanguages.map { + RenderNode.Variant(traits: [.interfaceLanguage($0.id)], paths: [externalEntity.topicRenderReference.url]) + } + } +} + +/// A language specific representation of an external render node value for building a navigator index. +struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { + var identifier: ResolvedTopicReference + var externalIdentifier: RenderReferenceIdentifier + var kind: RenderNode.Kind + var metadata: ExternalRenderNodeMetadataRepresentation + + // Values that don't affect how the node is rendered in the sidebar. + // These are needed to conform to the navigator indexable protocol. + var references: [String : any RenderReference] = [:] + var sections: [any RenderSection] = [] + var topicSections: [TaskGroupRenderSection] = [] + var defaultImplementationsSections: [TaskGroupRenderSection] = [] + + init(renderNode: ExternalRenderNode, trait: RenderNode.Variant.Trait? = nil) { + // Compute the source language of the node based on the trait to know which variant to apply. + let traitLanguage = if case .interfaceLanguage(let id) = trait { + SourceLanguage(id: id) + } else { + renderNode.identifier.sourceLanguage + } + let traits = trait.map { [$0] } ?? [] + + self.identifier = renderNode.identifier.withSourceLanguages(Set(arrayLiteral: traitLanguage)) + self.kind = renderNode.kind + self.externalIdentifier = renderNode.externalIdentifier + + self.metadata = ExternalRenderNodeMetadataRepresentation( + title: renderNode.titleVariants.value(for: traits), + navigatorTitle: renderNode.navigatorTitleVariants.value(for: traits), + externalID: renderNode.externalIdentifier.identifier, + role: renderNode.role, + symbolKind: renderNode.symbolKind?.identifier, + images: renderNode.images + ) + } +} + +/// A language specific representation of a render metadata value for building an external navigator index. +struct ExternalRenderNodeMetadataRepresentation: NavigatorIndexableRenderMetadataRepresentation { + var title: String? + var navigatorTitle: [DeclarationRenderSection.Token]? + var externalID: String? + var role: String? + var symbolKind: String? + var images: [TopicImage] + + // Values that we have insufficient information to derive. + // These are needed to conform to the navigator indexable metadata protocol. + // + // The fragments that we get as part of the external link are the full declaration fragments. + // These are too verbose for the navigator, so instead of using them, we rely on the title, navigator title and symbol kind instead. + // + // The role heading is used to identify Property Lists. + // The value being missing is used for computing the final navigator title. + // + // The platforms are used for generating the availability index, + // but doesn't affect how the node is rendered in the sidebar. + var fragments: [DeclarationRenderSection.Token]? = nil + var roleHeading: String? = nil + var platforms: [AvailabilityRenderItem]? = nil +} \ No newline at end of file diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift index eb1e43f6dc..9d0370dda3 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift @@ -11,7 +11,7 @@ import Foundation import SwiftDocC -struct ConvertFileWritingConsumer: ConvertOutputConsumer { +struct ConvertFileWritingConsumer: ConvertOutputConsumer, ExternalNodeConsumer { var targetFolder: URL var bundleRootFolder: URL? var fileManager: any FileManagerProtocol @@ -68,6 +68,11 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer { indexer?.index(renderNode) } + func consume(externalRenderNode: ExternalRenderNode) throws { + // Index the external node, if indexing is enabled. + indexer?.index(externalRenderNode) + } + func consume(assetsInBundle bundle: DocumentationBundle) throws { func copyAsset(_ asset: DataAsset, to destinationFolder: URL) throws { for sourceURL in asset.variants.values where !sourceURL.isAbsoluteWebURL { diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/Indexer.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/Indexer.swift index 1097395d89..5a721a8e45 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/Indexer.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/Indexer.swift @@ -62,6 +62,22 @@ extension ConvertAction { }) } + /// Indexes the given external render node and collects any encountered problems. + /// - Parameter renderNode: A ``ExternalRenderNode`` value. + func index(_ renderNode: ExternalRenderNode) { + // Synchronously index the render node. + indexBuilder.sync({ + do { + try $0.index(renderNode: renderNode) + nodeCount += 1 + } catch { + self.problems.append(error.problem(source: renderNode.identifier.url, + severity: .warning, + summaryPrefix: "External render node indexing process failed")) + } + }) + } + /// Finalizes the index and writes it on disk. /// - Returns: Returns a list of problems if any were encountered during indexing. func finalize(emitJSON: Bool, emitLMDB: Bool) -> [Problem] { diff --git a/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift b/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift index 6687305477..cf021a05df 100644 --- a/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift +++ b/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift @@ -17,7 +17,8 @@ import XCTest @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") class DocumentationConverterTests: XCTestCase { /// An empty implementation of `ConvertOutputConsumer` that purposefully does nothing. - struct EmptyConvertOutputConsumer: ConvertOutputConsumer { + struct EmptyConvertOutputConsumer: ConvertOutputConsumer, ExternalNodeConsumer { + // Conformance to ConvertOutputConsumer func consume(renderNode: RenderNode) throws { } func consume(problems: [Problem]) throws { } func consume(assetsInBundle bundle: DocumentationBundle) throws {} @@ -26,6 +27,9 @@ class DocumentationConverterTests: XCTestCase { func consume(assets: [RenderReferenceType: [any RenderReference]]) throws {} func consume(benchmarks: Benchmark) throws {} func consume(documentationCoverageInfo: [CoverageDataEntry]) throws {} + + // Conformance to ExternalNodeConsumer + func consume(externalRenderNode: SwiftDocC.ExternalRenderNode) throws { } } func testThrowsErrorOnConvertingNoBundles() throws { diff --git a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift index 6a39627b41..aaecda11eb 100644 --- a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift +++ b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift @@ -70,7 +70,7 @@ class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { } } -private class TestOutputConsumer: ConvertOutputConsumer { +private class TestOutputConsumer: ConvertOutputConsumer, ExternalNodeConsumer { var problems: [Problem] = [] func consume(problems: [Problem]) throws { @@ -87,4 +87,5 @@ private class TestOutputConsumer: ConvertOutputConsumer { func consume(renderReferenceStore: RenderReferenceStore) throws { } func consume(buildMetadata: BuildMetadata) throws { } func consume(linkResolutionInformation: SerializableLinkResolutionInformation) throws { } + func consume(externalRenderNode: ExternalRenderNode) throws { } } diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift new file mode 100644 index 0000000000..43243e3427 --- /dev/null +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -0,0 +1,272 @@ +/* + 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 XCTest +@_spi(ExternalLinks) @testable import SwiftDocC + +class ExternalRenderNodeTests: XCTestCase { + func generateExternalResover() -> TestMultiResultExternalReferenceResolver { + let externalResolver = TestMultiResultExternalReferenceResolver() + externalResolver.bundleID = "com.test.external" + externalResolver.entitiesToReturn["/path/to/external/swiftArticle"] = .success( + .init( + referencePath: "/path/to/external/swiftArticle", + title: "SwiftArticle", + kind: .article, + language: .swift + ) + ) + externalResolver.entitiesToReturn["/path/to/external/objCArticle"] = .success( + .init( + referencePath: "/path/to/external/objCArticle", + title: "ObjCArticle", + kind: .article, + language: .objectiveC + ) + ) + externalResolver.entitiesToReturn["/path/to/external/swiftSymbol"] = .success( + .init( + referencePath: "/path/to/external/swiftSymbol", + title: "SwiftSymbol", + kind: .class, + language: .swift + ) + ) + externalResolver.entitiesToReturn["/path/to/external/objCSymbol"] = .success( + .init( + referencePath: "/path/to/external/objCSymbol", + title: "ObjCSymbol", + kind: .function, + language: .objectiveC + ) + ) + return externalResolver + } + + func testExternalRenderNode() throws { + + let externalResolver = generateExternalResover() + let (_, bundle, context) = try testBundleAndContext( + copying: "MixedLanguageFramework", + externalResolvers: [externalResolver.bundleID: externalResolver] + ) { url in + let mixedLanguageFrameworkExtension = """ + # ``MixedLanguageFramework`` + + This symbol has a Swift and Objective-C variant. + + ## Topics + + ### External Reference + + - + - + - + - + """ + try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) + } + + var externalRenderNodes = [ExternalRenderNode]() + for externalLink in context.externalCache { + externalRenderNodes.append( + ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + ) + } + externalRenderNodes.sort(by: \.titleVariants.defaultValue) + XCTAssertEqual(externalRenderNodes.count, 4) + + XCTAssertEqual(externalRenderNodes[0].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCArticle") + XCTAssertEqual(externalRenderNodes[0].kind, .article) + XCTAssertEqual(externalRenderNodes[0].symbolKind, nil) + XCTAssertEqual(externalRenderNodes[0].role, "article") + XCTAssertEqual(externalRenderNodes[0].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCArticle") + + XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCSymbol") + XCTAssertEqual(externalRenderNodes[1].kind, .symbol) + XCTAssertEqual(externalRenderNodes[1].symbolKind, nil) + XCTAssertEqual(externalRenderNodes[1].role, "symbol") + XCTAssertEqual(externalRenderNodes[1].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCSymbol") + + XCTAssertEqual(externalRenderNodes[2].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftArticle") + XCTAssertEqual(externalRenderNodes[2].kind, .article) + XCTAssertEqual(externalRenderNodes[2].symbolKind, nil) + XCTAssertEqual(externalRenderNodes[2].role, "article") + XCTAssertEqual(externalRenderNodes[2].externalIdentifier.identifier, "doc://com.test.external/path/to/external/swiftArticle") + + XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftSymbol") + XCTAssertEqual(externalRenderNodes[3].kind, .symbol) + XCTAssertEqual(externalRenderNodes[3].symbolKind, nil) + XCTAssertEqual(externalRenderNodes[3].role, "symbol") + XCTAssertEqual(externalRenderNodes[3].externalIdentifier.identifier, "doc://com.test.external/path/to/external/swiftSymbol") + } + + func testExternalRenderNodeVariantRepresentation() throws { + let renderReferenceIdentifier = RenderReferenceIdentifier(forExternalLink: "doc://com.test.external/path/to/external/symbol") + + // Variants for the title + let swiftTitle = "Swift Symbol" + let occTitle = "Occ Symbol" + + // Variants for the navigator title + let navigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "symbol", kind: .identifier)] + let occNavigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "occ_symbol", kind: .identifier)] + + // Variants for the fragments + let fragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] + let occFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] + + let externalEntity = LinkResolver.ExternalEntity( + topicRenderReference: .init( + identifier: renderReferenceIdentifier, + titleVariants: .init(defaultValue: swiftTitle, objectiveCValue: occTitle), + abstractVariants: .init(defaultValue: []), + url: "/example/path/to/external/symbol", + kind: .symbol, + fragmentsVariants: .init(defaultValue: fragments, objectiveCValue: occFragments), + navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle) + ), + renderReferenceDependencies: .init(), + sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")]) + let externalRenderNode = ExternalRenderNode( + externalEntity: externalEntity, + bundleIdentifier: "com.test.external" + ) + + let swiftNavigatorExternalRenderNode = try XCTUnwrap( + NavigatorExternalRenderNode(renderNode: externalRenderNode) + ) + XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) + XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) + + let objcNavigatorExternalRenderNode = try XCTUnwrap( + NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) + ) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) + } + + func testNavigatorWithExternalNodes() throws { + let externalResolver = generateExternalResover() + let (_, bundle, context) = try testBundleAndContext( + copying: "MixedLanguageFramework", + externalResolvers: [externalResolver.bundleID: externalResolver] + ) { url in + let mixedLanguageFrameworkExtension = """ + # ``MixedLanguageFramework`` + + This symbol has a Swift and Objective-C variant. + + ## Topics + + ### External Reference + + - + - + - + - + """ + try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) + } + let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let targetURL = try createTemporaryDirectory() + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) + builder.setup() + for externalLink in context.externalCache { + let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + try builder.index(renderNode: externalRenderNode) + } + for identifier in context.knownPages { + let entity = try context.entity(with: identifier) + let renderNode = try XCTUnwrap(converter.renderNode(for: entity)) + try builder.index(renderNode: renderNode) + } + builder.finalize() + let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) + + // Verify that there are no uncurated external links at the top level + let swiftTopLevelExternalNodes = renderIndex.interfaceLanguages["swift"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let occTopLevelExternalNodes = renderIndex.interfaceLanguages["occ"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + XCTAssertEqual(swiftTopLevelExternalNodes.count, 0) + XCTAssertEqual(occTopLevelExternalNodes.count, 0) + + // Verify that the curated external links are part of the index. + let swiftExternalNodes = renderIndex.interfaceLanguages["swift"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let occExternalNodes = renderIndex.interfaceLanguages["occ"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + XCTAssertEqual(swiftExternalNodes.count, 2) + XCTAssertEqual(occExternalNodes.count, 2) + XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle", "SwiftSymbol"]) + XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) + XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) + XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) + } + + func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() throws { + let externalResolver = generateExternalResover() + + let (_, bundle, context) = try testBundleAndContext( + copying: "MixedLanguageFramework", + externalResolvers: [externalResolver.bundleID: externalResolver] + ) { url in + let mixedLanguageFrameworkExtension = """ + # ``MixedLanguageFramework`` + + This symbol has a Swift and Objective-C variant. + + It also has an external reference which is not curated in the Topics section: + + + + ## Topics + + ### External Reference + + - + - + """ + try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) + } + let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let targetURL = try createTemporaryDirectory() + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) + builder.setup() + for externalLink in context.externalCache { + let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + try builder.index(renderNode: externalRenderNode) + } + for identifier in context.knownPages { + let entity = try context.entity(with: identifier) + let renderNode = try XCTUnwrap(converter.renderNode(for: entity)) + try builder.index(renderNode: renderNode) + } + builder.finalize() + let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) + + + // Verify that there are no uncurated external links at the top level + let swiftTopLevelExternalNodes = renderIndex.interfaceLanguages["swift"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let occTopLevelExternalNodes = renderIndex.interfaceLanguages["occ"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + XCTAssertEqual(swiftTopLevelExternalNodes.count, 0) + XCTAssertEqual(occTopLevelExternalNodes.count, 0) + + // Verify that the curated external links are part of the index. + let swiftExternalNodes = renderIndex.interfaceLanguages["swift"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let occExternalNodes = renderIndex.interfaceLanguages["occ"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + XCTAssertEqual(swiftExternalNodes.count, 1) + XCTAssertEqual(occExternalNodes.count, 1) + XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle"]) + XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCSymbol"]) + XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) + XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) + } +} diff --git a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift index d1a1088880..4281998cc5 100644 --- a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift +++ b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift @@ -12,7 +12,7 @@ import Foundation @testable import SwiftDocC import XCTest -class TestRenderNodeOutputConsumer: ConvertOutputConsumer { +class TestRenderNodeOutputConsumer: ConvertOutputConsumer, ExternalNodeConsumer { var renderNodes = Synchronized<[RenderNode]>([]) func consume(renderNode: RenderNode) throws { @@ -30,6 +30,7 @@ class TestRenderNodeOutputConsumer: ConvertOutputConsumer { func consume(renderReferenceStore: RenderReferenceStore) throws { } func consume(buildMetadata: BuildMetadata) throws { } func consume(linkResolutionInformation: SerializableLinkResolutionInformation) throws { } + func consume(externalRenderNode: ExternalRenderNode) throws { } } extension TestRenderNodeOutputConsumer { From 89028565d656f4c9c7606092e55c7f1e3df8c629 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Wed, 23 Jul 2025 09:18:47 -0600 Subject: [PATCH 08/90] enforce overload declaration ordering when computing the highlighted differences (#1250) rdar://155975575 --- .../DeclarationsSectionTranslator.swift | 12 ++- .../DeclarationsRenderSectionTests.swift | 95 +++++++++++++++++++ 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift index cc88210346..d54753bd4d 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift @@ -44,13 +44,17 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { /// Fetch the common fragments for the given references, or compute it if necessary. func commonFragments( for mainDeclaration: OverloadDeclaration, - overloadDeclarations: [OverloadDeclaration] + overloadDeclarations: [OverloadDeclaration], + mainDeclarationIndex: Int ) -> [SymbolGraph.Symbol.DeclarationFragments.Fragment] { if let fragments = commonFragments(for: mainDeclaration.reference) { return fragments } - let preProcessedDeclarations = [mainDeclaration.declaration] + overloadDeclarations.map(\.declaration) + var preProcessedDeclarations = overloadDeclarations.map(\.declaration) + // Insert the main declaration according to the display index so the ordering is consistent + // between overloaded symbols + preProcessedDeclarations.insert(mainDeclaration.declaration, at: mainDeclarationIndex) // Collect the "common fragments" so we can highlight the ones that are different // in each declaration @@ -216,7 +220,9 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { // in each declaration let commonFragments = commonFragments( for: (mainDeclaration, renderNode.identifier, nil), - overloadDeclarations: processedOverloadDeclarations) + overloadDeclarations: processedOverloadDeclarations, + mainDeclarationIndex: overloads.displayIndex + ) renderedTokens = translateDeclaration( mainDeclaration, diff --git a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift index b7a887235c..8208f894fa 100644 --- a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift @@ -12,6 +12,7 @@ import Foundation import XCTest @testable import SwiftDocC import SwiftDocCTestUtilities +import SymbolKit class DeclarationsRenderSectionTests: XCTestCase { func testDecodingTokens() throws { @@ -322,6 +323,91 @@ class DeclarationsRenderSectionTests: XCTestCase { } } + func testInconsistentHighlightDiff() throws { + enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) + + // Generate a symbol graph with many overload groups that share declarations. + // The overloaded declarations have two legitimate solutions for their longest common subsequence: + // one that ends in a close-parenthesis, and one that ends in a space. + // By alternating the order in which these declarations appear, + // the computed difference highlighting can differ + // unless the declarations are sorted prior to the calculation. + // Ensure that the overload difference highlighting is consistent for these declarations. + + // init(_ content: MyClass) throws + let declaration1: SymbolGraph.Symbol.DeclarationFragments = .init(declarationFragments: [ + .init(kind: .keyword, spelling: "init", preciseIdentifier: nil), + .init(kind: .text, spelling: "(", preciseIdentifier: nil), + .init(kind: .externalParameter, spelling: "_", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .internalParameter, spelling: "content", preciseIdentifier: nil), + .init(kind: .text, spelling: ": ", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "MyClass", preciseIdentifier: "s:MyClass"), + .init(kind: .text, spelling: ") ", preciseIdentifier: nil), + .init(kind: .keyword, spelling: "throws", preciseIdentifier: nil), + ]) + + // init(_ content: some ConvertibleToMyClass) + let declaration2: SymbolGraph.Symbol.DeclarationFragments = .init(declarationFragments: [ + .init(kind: .keyword, spelling: "init", preciseIdentifier: nil), + .init(kind: .text, spelling: "(", preciseIdentifier: nil), + .init(kind: .externalParameter, spelling: "_", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .internalParameter, spelling: "content", preciseIdentifier: nil), + .init(kind: .text, spelling: ": ", preciseIdentifier: nil), + .init(kind: .keyword, spelling: "some", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "ConvertibleToMyClass", preciseIdentifier: "s:ConvertibleToMyClass"), + .init(kind: .text, spelling: ")", preciseIdentifier: nil), + ]) + let overloadsCount = 10 + let symbols = (0...overloadsCount).flatMap({ index in + let reverseDeclarations = index % 2 != 0 + return [ + makeSymbol( + id: "overload-\(index)-1", + kind: .func, + pathComponents: ["overload-\(index)"], + otherMixins: [reverseDeclarations ? declaration2 : declaration1]), + makeSymbol( + id: "overload-\(index)-2", + kind: .func, + pathComponents: ["overload-\(index)"], + otherMixins: [reverseDeclarations ? declaration1 : declaration2]), + ] + }) + let symbolGraph = makeSymbolGraph(moduleName: "FancierOverloads", symbols: symbols) + + let catalog = Folder(name: "unit-test.docc", content: [ + InfoPlist(displayName: "FancierOverloads", identifier: "com.test.example"), + JSONFile(name: "FancierOverloads.symbols.json", content: symbolGraph), + ]) + + let (bundle, context) = try loadBundle(catalog: catalog) + + func assertDeclarations(for USR: String, file: StaticString = #filePath, line: UInt = #line) throws { + let reference = try XCTUnwrap(context.documentationCache.reference(symbolID: USR), file: file, line: line) + let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol, file: file, line: line) + var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode, file: file, line: line) + let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first, file: file, line: line) + XCTAssertEqual(declarationsSection.declarations.count, 1, file: file, line: line) + let declarations = try XCTUnwrap(declarationsSection.declarations.first, file: file, line: line) + + XCTAssertEqual(declarationsAndHighlights(for: declarations), [ + "init(_ content: MyClass) throws", + " ~~~~~~~~ ~~~~~~", + "init(_ content: some ConvertibleToMyClass)", + " ~~~~ ~~~~~~~~~~~~~~~~~~~~~", + ], file: file, line: line) + } + + for i in 0...overloadsCount { + try assertDeclarations(for: "overload-\(i)-1") + try assertDeclarations(for: "overload-\(i)-2") + } + } + func testDontHighlightWhenOverloadsAreDisabled() throws { let symbolGraphFile = Bundle.module.url( forResource: "FancyOverloads", @@ -403,3 +489,12 @@ func declarationAndHighlights(for tokens: [DeclarationRenderSection.Token]) -> [ tokens.map({ String(repeating: $0.highlight == .changed ? "~" : " ", count: $0.text.count) }).joined() ] } + +func declarationsAndHighlights(for section: DeclarationRenderSection) -> [String] { + guard let otherDeclarations = section.otherDeclarations else { + return [] + } + var declarations = otherDeclarations.declarations.map(\.tokens) + declarations.insert(section.tokens, at: otherDeclarations.displayIndex) + return declarations.flatMap(declarationAndHighlights(for:)) +} From 1b4a1850dd2785a8ebabded139ae0af3551bb029 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Wed, 23 Jul 2025 10:08:34 -0600 Subject: [PATCH 09/90] optionalRequirementOf relationships take precedence over non-optional ones (#1252) rdar://155967920 --- .../SymbolGraphRelationshipsBuilder.swift | 8 ++++- ...SymbolGraphRelationshipsBuilderTests.swift | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift index 749ff60ce1..836b84775d 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift @@ -346,7 +346,13 @@ struct SymbolGraphRelationshipsBuilder { assertionFailure(AssertionMessages.sourceNotFound(edge)) return } - requiredSymbol.isRequired = required + // If both requirementOf and optionalRequirementOf relationships exist + // for the same symbol, let the optional relationship take precedence. + // Optional protocol requirements sometimes appear with both relationships, + // but non-optional requirements do not. + if !required || requiredSymbol.isRequiredVariants.isEmpty { + requiredSymbol.isRequired = required + } } /// Sets a node in the context as an inherited symbol. diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift index 784c6d3199..b16ff2b3f7 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift @@ -186,4 +186,36 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { // Test default implementation was added XCTAssertFalse((documentationCache["A"]!.semantic as! Symbol).isRequired) } + + func testRequiredAndOptionalRequirementRelationships() throws { + do { + let (bundle, _) = try testBundleAndContext() + var documentationCache = DocumentationContext.ContentCache() + let engine = DiagnosticEngine() + + let edge = createSymbols(documentationCache: &documentationCache, bundle: bundle, sourceType: .init(parsedIdentifier: .method, displayName: "Method"), targetType: .init(parsedIdentifier: .protocol, displayName: "Protocol")) + + // Adding the "required" relationship before the "optional" one + SymbolGraphRelationshipsBuilder.addRequirementRelationship(edge: edge, localCache: documentationCache, engine: engine) + SymbolGraphRelationshipsBuilder.addOptionalRequirementRelationship(edge: edge, localCache: documentationCache, engine: engine) + + // Make sure that the "optional" relationship wins + XCTAssertFalse((documentationCache["A"]!.semantic as! Symbol).isRequired) + } + + do { + let (bundle, _) = try testBundleAndContext() + var documentationCache = DocumentationContext.ContentCache() + let engine = DiagnosticEngine() + + let edge = createSymbols(documentationCache: &documentationCache, bundle: bundle, sourceType: .init(parsedIdentifier: .method, displayName: "Method"), targetType: .init(parsedIdentifier: .protocol, displayName: "Protocol")) + + // Adding the "optional" relationship before the "required" one + SymbolGraphRelationshipsBuilder.addOptionalRequirementRelationship(edge: edge, localCache: documentationCache, engine: engine) + SymbolGraphRelationshipsBuilder.addRequirementRelationship(edge: edge, localCache: documentationCache, engine: engine) + + // Make sure that the "optional" relationship still wins + XCTAssertFalse((documentationCache["A"]!.semantic as! Symbol).isRequired) + } + } } From 8cba15373b3a20b8ab212f498d63fef1c4ea3c01 Mon Sep 17 00:00:00 2001 From: Pat Shaughnessy Date: Mon, 28 Jul 2025 16:44:02 +0200 Subject: [PATCH 10/90] RenderNodeTranslator.visitSymbol filters out availability items with a missing domain (#1256) RenderNodeTranslator.visitSymbol filters out availability items which have a missing or invalid domain (platform name). Swift developers can add global availability messages with no specific platform information, for example like this: @available(*, deprecated, message: """ Don't use this function; call some other function instead. """ ) In this scenario, the Swift compiler adds an availability item with the message but no domain (platform name) to the symbol graph file, like this: { "domain": null, "isUnconditionallyDeprecated": true, "message": "Don't use this function; call some other function instead." } To avoid displaying an empty/null platform in the DocC Render user interface, the RenderNodeTranslator needs to filter out these records. Additionally, AvailabilityRenderOrder.compare always returns false for availability items that have no name, leading to an unpredictable sort order when any of the items is missing a platform name, even for availability items that had a valid platform name. --- .../Rendering/RenderNodeTranslator.swift | 9 ++-- .../AvailabilityRenderOrderTests.swift | 44 ++++++++++++++++++- ...derNodeTranslatorSymbolVariantsTests.swift | 4 +- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index 108de80729..15e9ca1753 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -1257,13 +1257,14 @@ public struct RenderNodeTranslator: SemanticVisitor { if availability.obsoletedVersion != nil { return nil } - guard let name = availability.domain.map({ PlatformName(operatingSystemName: $0.rawValue) }), - let currentPlatform = context.configuration.externalMetadata.currentPlatforms?[name.displayName] - else { + // Filter out this availability item if it has a missing or invalid domain. + guard let name = availability.domain.map({ PlatformName(operatingSystemName: $0.rawValue) }) else { + return nil + } + guard let currentPlatform = context.configuration.externalMetadata.currentPlatforms?[name.displayName] else { // No current platform provided by the context return AvailabilityRenderItem(availability, current: nil) } - return AvailabilityRenderItem(availability, current: currentPlatform) } .filter { $0.unconditionallyUnavailable != true } diff --git a/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift b/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift index 0e91aa65e3..9cdd41b948 100644 --- a/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift +++ b/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift @@ -9,6 +9,7 @@ */ import Foundation +import SymbolKit import XCTest @testable import SwiftDocC @@ -18,7 +19,47 @@ class AvailabilityRenderOrderTests: XCTestCase { func testSortingAtRenderTime() throws { let (bundleURL, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in - try? FileManager.default.copyItem(at: self.availabilitySGFURL, to: url.appendingPathComponent("Availability.symbols.json")) + let availabilitySymbolGraphURL = url.appendingPathComponent("Availability.symbols.json") + try? FileManager.default.copyItem(at: self.availabilitySGFURL, to: availabilitySymbolGraphURL) + + // Load the symbol graph fixture + var availabilitySymbolGraph = try JSONDecoder().decode(SymbolGraph.self, from: try Data(contentsOf: availabilitySymbolGraphURL)) + + // There should be at least one symbol in this graph + XCTAssertEqual(1, availabilitySymbolGraph.symbols.count) + if let tuple = availabilitySymbolGraph.symbols.first { + + let key = tuple.key + var symbol = tuple.value + + // The symbol should have availability info specified + XCTAssertNotNil(symbol.availability) + if var alternateSymbols = symbol.mixins[Availability.mixinKey] as? Availability { + + // Create a new availability item which is missing a domain (platform name). + let missingDomain = SymbolGraph.Symbol.Availability.AvailabilityItem( + domain: nil, + introducedVersion: nil, + deprecatedVersion: nil, + obsoletedVersion: nil, + message: "Don't use this function; call some other function instead.", + renamed: nil, + isUnconditionallyDeprecated: true, + isUnconditionallyUnavailable: false, + willEventuallyBeDeprecated: false + ) + + // Append the invalid item and update the symbol + alternateSymbols.availability.insert(missingDomain, at: 4) + symbol.mixins[Availability.mixinKey] = alternateSymbols + } + availabilitySymbolGraph.symbols[key] = symbol + } + + // Update the temporary copy of the fixture + let jsonEncoder = JSONEncoder() + let data = try jsonEncoder.encode(availabilitySymbolGraph) + try data.write(to: availabilitySymbolGraphURL) } defer { try? FileManager.default.removeItem(at: bundleURL) @@ -32,6 +73,7 @@ class AvailabilityRenderOrderTests: XCTestCase { // Verify that all the symbol's availabilities were sorted into the order // they need to appear for rendering (they are not in the symbol graph fixture). // Additionally verify all the platforms have their correctly spelled name including spaces. + // Finally, the invalid item added above should be filtered out. XCTAssertEqual(renderNode.metadata.platforms?.map({ "\($0.name ?? "") \($0.introduced ?? "")" }), [ "iOS 12.0", "iOS App Extension 12.0", "iPadOS 12.0", diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift index 8d8d5dfaa7..006d0522af 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift @@ -147,7 +147,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { configureSymbol: { symbol in symbol.availabilityVariants[.swift] = SymbolGraph.Symbol.Availability(availability: [ SymbolGraph.Symbol.Availability.AvailabilityItem( - domain: nil, + domain: .init(rawValue: "iOS"), introducedVersion: SymbolGraph.SemanticVersion(string: "1.0"), deprecatedVersion: nil, obsoletedVersion: nil, @@ -161,7 +161,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { symbol.availabilityVariants[.objectiveC] = SymbolGraph.Symbol.Availability(availability: [ SymbolGraph.Symbol.Availability.AvailabilityItem( - domain: nil, + domain: .init(rawValue: "iOS"), introducedVersion: SymbolGraph.SemanticVersion(string: "2.0"), deprecatedVersion: nil, obsoletedVersion: nil, From 361a9f87999a389391461d5810b1f184ca957d91 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Mon, 28 Jul 2025 13:06:42 -0600 Subject: [PATCH 11/90] sort platform-specific declarations by their platforms (#1254) rdar://155966155 --- .../DeclarationsSectionTranslator.swift | 23 ++++-- .../DeclarationsRenderSectionTests.swift | 75 +++++++++++++++++++ 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift index d54753bd4d..17caa3947e 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift @@ -187,13 +187,15 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { return declarations } - func sortPlatformNames(_ platforms: [PlatformName?]) -> [PlatformName?] { - platforms.sorted { (lhs, rhs) -> Bool in - guard let lhsValue = lhs, let rhsValue = rhs else { - return lhs == nil - } - return lhsValue.rawValue < rhsValue.rawValue + func comparePlatformNames(_ lhs: PlatformName?, _ rhs: PlatformName?) -> Bool { + guard let lhsValue = lhs, let rhsValue = rhs else { + return lhs == nil } + return lhsValue.rawValue < rhsValue.rawValue + } + + func sortPlatformNames(_ platforms: [PlatformName?]) -> [PlatformName?] { + platforms.sorted(by: comparePlatformNames(_:_:)) } var declarations: [DeclarationRenderSection] = [] @@ -265,6 +267,15 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { } } + declarations.sort { (lhs, rhs) -> Bool in + // We only need to compare the first platform in each list against the + // first platform in any other list, so pull them out here + guard let lhsPlatform = lhs.platforms.first, let rhsPlatform = rhs.platforms.first else { + return lhs.platforms.isEmpty + } + return comparePlatformNames(lhsPlatform, rhsPlatform) + } + return DeclarationsRenderSection(declarations: declarations) } } diff --git a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift index 8208f894fa..7e1201ced7 100644 --- a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift @@ -160,6 +160,81 @@ class DeclarationsRenderSectionTests: XCTestCase { XCTAssert(declarationsSection.declarations.allSatisfy({ $0.platforms == [.iOS, .macOS] })) } + func testPlatformSpecificDeclarations() throws { + // init(_ content: MyClass) throws + let declaration1: SymbolGraph.Symbol.DeclarationFragments = .init(declarationFragments: [ + .init(kind: .keyword, spelling: "init", preciseIdentifier: nil), + .init(kind: .text, spelling: "(", preciseIdentifier: nil), + .init(kind: .externalParameter, spelling: "_", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .internalParameter, spelling: "content", preciseIdentifier: nil), + .init(kind: .text, spelling: ": ", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "MyClass", preciseIdentifier: "s:MyClass"), + .init(kind: .text, spelling: ") ", preciseIdentifier: nil), + .init(kind: .keyword, spelling: "throws", preciseIdentifier: nil), + ]) + + // init(_ content: OtherClass) throws + let declaration2: SymbolGraph.Symbol.DeclarationFragments = .init(declarationFragments: [ + .init(kind: .keyword, spelling: "init", preciseIdentifier: nil), + .init(kind: .text, spelling: "(", preciseIdentifier: nil), + .init(kind: .externalParameter, spelling: "_", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .internalParameter, spelling: "content", preciseIdentifier: nil), + .init(kind: .text, spelling: ": ", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "OtherClass", preciseIdentifier: "s:OtherClass"), + .init(kind: .text, spelling: ") ", preciseIdentifier: nil), + .init(kind: .keyword, spelling: "throws", preciseIdentifier: nil), + ]) + let symbol1 = makeSymbol( + id: "myInit", + kind: .func, + pathComponents: ["myInit"], + otherMixins: [declaration1]) + let symbol2 = makeSymbol( + id: "myInit", + kind: .func, + pathComponents: ["myInit"], + otherMixins: [declaration2]) + let symbolGraph1 = makeSymbolGraph(moduleName: "PlatformSpecificDeclarations", platform: .init(operatingSystem: .init(name: "macos")), symbols: [symbol1]) + let symbolGraph2 = makeSymbolGraph(moduleName: "PlatformSpecificDeclarations", platform: .init(operatingSystem: .init(name: "ios")), symbols: [symbol2]) + + func runAssertions(forwards: Bool) throws { + // Toggling the order of platforms here doesn't necessarily _enforce_ a + // nondeterminism failure in a unit-test environment, but it does make it + // much more likely. Make sure that the order of the platform-specific + // declarations is consistent between runs. + let catalog = Folder(name: "unit-test.docc", content: [ + InfoPlist(displayName: "PlatformSpecificDeclarations", identifier: "com.test.example"), + JSONFile(name: "symbols\(forwards ? "1" : "2").symbols.json", content: symbolGraph1), + JSONFile(name: "symbols\(forwards ? "2" : "1").symbols.json", content: symbolGraph2), + ]) + + let (bundle, context) = try loadBundle(catalog: catalog) + + let reference = ResolvedTopicReference( + bundleID: bundle.id, + path: "/documentation/PlatformSpecificDeclarations/myInit", + sourceLanguage: .swift + ) + let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) + var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) + let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) + XCTAssertEqual(declarationsSection.declarations.count, 2) + + XCTAssertEqual(declarationsSection.declarations[0].platforms, [.iOS]) + XCTAssertEqual(declarationsSection.declarations[0].tokens.map(\.text).joined(), + "init(_ content: OtherClass) throws") + XCTAssertEqual(declarationsSection.declarations[1].platforms, [.macOS]) + XCTAssertEqual(declarationsSection.declarations[1].tokens.map(\.text).joined(), + "init(_ content: MyClass) throws") + } + + try runAssertions(forwards: true) + try runAssertions(forwards: false) + } + func testHighlightDiff() throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) From 01cdb27a36f510de28e51a4f3d03bee623891eea Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Tue, 29 Jul 2025 09:58:45 -0500 Subject: [PATCH 12/90] add initializer to LinkCompletionTools.SymbolInformation that accepts a SymbolGraph.Symbol (#1253) --- .../Symbol Link Resolution/LinkCompletionTools.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift b/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift index a747125410..55f5a64545 100644 --- a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift +++ b/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift @@ -9,6 +9,7 @@ */ import Foundation +public import SymbolKit /// A collection of API for link completion. /// @@ -184,6 +185,15 @@ public enum LinkCompletionTools { self.parameterTypes = parameterTypes self.returnTypes = returnTypes } + + public init(symbol: SymbolGraph.Symbol) { + self.kind = symbol.kind.identifier.identifier + self.symbolIDHash = Self.hash(uniqueSymbolID: symbol.identifier.precise) + if let signature = PathHierarchy.functionSignatureTypeNames(for: symbol) { + self.parameterTypes = signature.parameterTypeNames + self.returnTypes = signature.returnTypeNames + } + } /// Creates a hashed representation of a symbol's unique identifier. /// From 4f207a40574f7cd95a80f74ba941e051131d07c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Wed, 30 Jul 2025 10:10:03 +0200 Subject: [PATCH 13/90] Update DocumentationContext initializer to be async (#1244) * Update DocumentationContext initializer to be async * Fix typo in test helper name * Fix inconsistent indentation in test code --- .../Convert/ConvertService.swift | 147 +++---- .../Infrastructure/DocumentationContext.swift | 2 +- .../Actions/Convert/ConvertAction.swift | 6 +- .../Actions/EmitGeneratedCurationAction.swift | 4 +- .../Benchmark/ExternalTopicsHashTests.swift | 54 ++- .../Benchmark/TopicAnchorHashTests.swift | 39 +- .../Benchmark/TopicGraphHashTests.swift | 31 +- .../GeneratedCurationWriterTests.swift | 32 +- .../NonInclusiveLanguageCheckerTests.swift | 10 +- .../DocumentationContextConverterTests.swift | 14 +- .../Converter/RenderContextTests.swift | 6 +- .../Converter/RenderNodeCodableTests.swift | 4 +- .../TopicRenderReferenceEncoderTests.swift | 10 +- ...recatedDiagnosticsDigestWarningTests.swift | 8 +- .../Diagnostics/DiagnosticTests.swift | 6 +- .../ConvertService/ConvertServiceTests.swift | 14 +- .../Indexing/ExternalRenderNodeTests.swift | 40 +- .../Indexing/IndexingTests.swift | 18 +- .../Indexing/NavigatorIndexTests.swift | 68 +-- .../Indexing/RenderIndexTests.swift | 33 +- .../Infrastructure/AnchorSectionTests.swift | 18 +- .../AutoCapitalizationTests.swift | 14 +- .../AutomaticCurationTests.swift | 120 +++--- ...ext+MixedLanguageLinkResolutionTests.swift | 6 +- ...xt+MixedLanguageSourceLanguagesTests.swift | 18 +- .../DocumentationContext+RootPageTests.swift | 22 +- .../DocumentationContextTests.swift | 406 +++++++++--------- .../DocumentationCuratorTests.swift | 48 +-- .../ExternalPathHierarchyResolverTests.swift | 32 +- .../ExternalReferenceResolverTests.swift | 106 ++--- .../InheritIntroducedAvailabilityTests.swift | 8 +- .../Infrastructure/NodeTagsTests.swift | 6 +- .../PathHierarchyBasedLinkResolverTests.swift | 6 +- .../Infrastructure/PathHierarchyTests.swift | 230 +++++----- .../PresentationURLGeneratorTests.swift | 6 +- .../ReferenceResolverTests.swift | 84 ++-- .../AbsoluteSymbolLinkTests.swift | 12 +- .../DocCSymbolRepresentableTests.swift | 32 +- .../SymbolBreadcrumbTests.swift | 10 +- .../SymbolDisambiguationTests.swift | 42 +- .../SymbolGraph/SymbolGraphLoaderTests.swift | 72 ++-- ...SymbolGraphRelationshipsBuilderTests.swift | 32 +- .../Infrastructure/SymbolReferenceTests.swift | 6 +- .../LinkDestinationSummaryTests.swift | 22 +- .../Model/LineHighlighterTests.swift | 35 +- .../ParametersAndReturnValidatorTests.swift | 98 ++--- ...opertyListPossibleValuesSectionTests.swift | 34 +- .../Model/RenderContentMetadataTests.swift | 30 +- .../RenderHierarchyTranslatorTests.swift | 14 +- .../Model/RenderNodeDiffingBundleTests.swift | 44 +- .../Model/RenderNodeSerializationTests.swift | 16 +- ...aToRenderNodeArticleOnlyCatalogTests.swift | 6 +- .../SemaToRenderNodeDictionaryDataTests.swift | 18 +- .../SemaToRenderNodeHTTPRequestTests.swift | 18 +- .../SemaToRenderNodeMultiLanguageTests.swift | 78 ++-- ...emaToRenderNodeSourceRepositoryTests.swift | 10 +- .../Model/SemaToRenderNodeTests.swift | 294 ++++++------- .../Rendering/AutomaticSeeAlsoTests.swift | 26 +- .../AvailabilityRenderOrderTests.swift | 6 +- .../ConstraintsRenderSectionTests.swift | 34 +- .../DeclarationsRenderSectionTests.swift | 34 +- .../Rendering/DefaultAvailabilityTests.swift | 50 +-- .../DefaultCodeListingSyntaxTests.swift | 8 +- .../Rendering/DeprecationSummaryTests.swift | 30 +- .../DocumentationContentRendererTests.swift | 22 +- .../Rendering/ExternalLinkTitleTests.swift | 30 +- .../Rendering/HeadingAnchorTests.swift | 6 +- .../Rendering/LinkTitleResolverTests.swift | 6 +- .../MentionsRenderSectionTests.swift | 10 +- .../Rendering/PageKindTests.swift | 32 +- .../Rendering/PlatformAvailabilityTests.swift | 38 +- ...ropertyListDetailsRenderSectionTests.swift | 20 +- .../Rendering/RESTSymbolsTests.swift | 25 +- ...enderBlockContent_ThematicBreakTests.swift | 14 +- .../RenderContentCompilerTests.swift | 14 +- .../Rendering/RenderMetadataTests.swift | 18 +- ...derNodeTranslatorSymbolVariantsTests.swift | 154 +++---- .../Rendering/RenderNodeTranslatorTests.swift | 152 +++---- .../SwiftDocCTests/Rendering/RoleTests.swift | 14 +- .../Rendering/SampleDownloadTests.swift | 38 +- .../Rendering/SymbolAvailabilityTests.swift | 18 +- .../Rendering/TermListTests.swift | 14 +- .../ArticleSymbolMentionsTests.swift | 12 +- .../Semantics/ArticleTests.swift | 36 +- .../Semantics/AssessmentsTests.swift | 4 +- .../Semantics/CallToActionTests.swift | 44 +- .../Semantics/ChapterTests.swift | 16 +- .../Semantics/ChoiceTests.swift | 32 +- .../SwiftDocCTests/Semantics/CodeTests.swift | 4 +- .../Semantics/ContentAndMediaTests.swift | 20 +- .../Semantics/DisplayNameTests.swift | 32 +- .../DocumentationExtensionTests.swift | 32 +- .../Semantics/DoxygenTests.swift | 6 +- .../HasAtLeastOneTests.swift | 16 +- .../HasAtMostOneTests.swift | 16 +- .../HasExactlyOneTests.swift | 16 +- .../HasOnlySequentialHeadingsTests.swift | 16 +- .../Semantics/ImageMediaTests.swift | 44 +- .../SwiftDocCTests/Semantics/IntroTests.swift | 12 +- .../Semantics/JustificationTests.swift | 8 +- .../MarkupReferenceResolverTests.swift | 10 +- ...MetadataAlternateRepresentationTests.swift | 14 +- .../Semantics/MetadataAvailabilityTests.swift | 40 +- .../Semantics/MetadataTests.swift | 78 ++-- .../Semantics/MultipleChoiceTests.swift | 24 +- .../Semantics/Options/OptionsTests.swift | 60 +-- .../Semantics/RedirectedTests.swift | 56 +-- .../Semantics/Reference/LinksTests.swift | 22 +- .../Semantics/Reference/RowTests.swift | 28 +- .../Semantics/Reference/SmallTests.swift | 22 +- .../Reference/TabNavigatorTests.swift | 18 +- .../Semantics/ResourcesTests.swift | 12 +- .../Semantics/SectionTests.swift | 4 +- .../Semantics/SnippetTests.swift | 20 +- .../SwiftDocCTests/Semantics/StackTests.swift | 12 +- .../SwiftDocCTests/Semantics/StepTests.swift | 12 +- .../Semantics/SymbolTests.swift | 102 ++--- .../Semantics/TechnologyTests.swift | 4 +- .../SwiftDocCTests/Semantics/TileTests.swift | 22 +- .../Semantics/TutorialArticleTests.swift | 40 +- .../Semantics/TutorialReferenceTests.swift | 12 +- .../Semantics/TutorialTests.swift | 28 +- .../Semantics/VideoMediaTests.swift | 52 +-- .../Semantics/VolumeTests.swift | 12 +- .../Semantics/XcodeRequirementTests.swift | 8 +- .../TestRenderNodeOutputConsumer.swift | 4 +- .../Utility/ListItemExtractorTests.swift | 44 +- .../Utility/XCTestCase+MentionedIn.swift | 6 +- .../XCTestCase+LoadingTestData.swift | 50 +-- .../ConvertActionIndexerTests.swift | 6 +- .../MergeActionTests.swift | 10 +- .../SemanticAnalyzerTests.swift | 18 +- .../XCTestCase+LoadingData.swift | 6 +- 133 files changed, 2305 insertions(+), 2329 deletions(-) diff --git a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift index 3be90ed79e..e03d60e729 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-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 @@ -44,68 +44,68 @@ public struct ConvertService: DocumentationService { _ message: DocumentationServer.Message, completion: @escaping (DocumentationServer.Message) -> () ) { - let conversionResult = retrievePayload(message) - .flatMap(decodeRequest) - .flatMap(convert) - .flatMap(encodeResponse) - - switch conversionResult { - case .success(let response): - completion( - DocumentationServer.Message( - type: Self.convertResponseMessageType, - identifier: "\(message.identifier)-response", - payload: response + Task { + let result = await process(message) + completion(result) + } + } + + public func process(_ message: DocumentationServer.Message) async -> DocumentationServer.Message { + func makeErrorResponse(_ error: ConvertServiceError) -> DocumentationServer.Message { + DocumentationServer.Message( + type: Self.convertResponseErrorMessageType, + identifier: "\(message.identifier)-response-error", + + // Force trying because encoding known messages should never fail. + payload: try! JSONEncoder().encode(error) + ) + } + + guard let payload = message.payload else { + return makeErrorResponse(.missingPayload()) + } + + let request: ConvertRequest + do { + request = try JSONDecoder().decode(ConvertRequest.self, from: payload) + } catch { + return makeErrorResponse(.invalidRequest(underlyingError: error.localizedDescription)) + } + + let renderNodes: [RenderNode] + let renderReferenceStore: RenderReferenceStore? + do { + (renderNodes, renderReferenceStore) = try await convert(request: request, messageIdentifier: message.identifier) + } catch { + return makeErrorResponse(.conversionError(underlyingError: error.localizedDescription)) + } + + do { + let encoder = JSONEncoder() + let encodedResponse = try encoder.encode( + try ConvertResponse( + renderNodes: renderNodes.map(encoder.encode), + renderReferenceStore: renderReferenceStore.map(encoder.encode) ) ) - case .failure(let error): - completion( - DocumentationServer.Message( - type: Self.convertResponseErrorMessageType, - identifier: "\(message.identifier)-response-error", - - // Force trying because encoding known messages should never fail. - payload: try! JSONEncoder().encode(error) - ) + return DocumentationServer.Message( + type: Self.convertResponseMessageType, + identifier: "\(message.identifier)-response", + payload: encodedResponse ) - } - } - - /// Attempts to retrieve the payload from the given message, returning a failure if the payload is missing. - /// - /// - Returns: A result with the message's payload if present, otherwise a ``ConvertServiceError/missingPayload`` - /// failure. - private func retrievePayload( - _ message: DocumentationServer.Message - ) -> Result<(payload: Data, messageIdentifier: String), ConvertServiceError> { - message.payload.map { .success(($0, message.identifier)) } ?? .failure(.missingPayload()) - } - - /// Attempts to decode the given request, returning a failure if decoding failed. - /// - /// - Returns: A result with the decoded request if the decoding succeeded, otherwise a - /// ``ConvertServiceError/invalidRequest`` failure. - private func decodeRequest( - data: Data, - messageIdentifier: String - ) -> Result<(request: ConvertRequest, messageIdentifier: String), ConvertServiceError> { - Result { - return (try JSONDecoder().decode(ConvertRequest.self, from: data), messageIdentifier) - }.mapErrorToConvertServiceError { - .invalidRequest(underlyingError: $0.localizedDescription) + } catch { + return makeErrorResponse(.invalidResponseMessage(underlyingError: error.localizedDescription)) } } /// Attempts to process the given convert request, returning a failure if the conversion failed. /// - /// - Returns: A result with the produced render nodes if the conversion was successful, otherwise a - /// ``ConvertServiceError/conversionError`` failure. + /// - Returns: A result with the produced render nodes if the conversion was successful private func convert( request: ConvertRequest, messageIdentifier: String - ) -> Result<([RenderNode], RenderReferenceStore?), ConvertServiceError> { - Result { + ) async throws -> ([RenderNode], RenderReferenceStore?) { // Update DocC's current feature flags based on the ones provided // in the request. FeatureFlags.current = request.featureFlags @@ -155,7 +155,7 @@ public struct ConvertService: DocumentationService { (bundle, dataProvider) = Self.makeBundleAndInMemoryDataProvider(request) } - let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: configuration) + let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: configuration) // Precompute the render context let renderContext = RenderContext(documentationContext: context, bundle: bundle) @@ -228,30 +228,6 @@ public struct ConvertService: DocumentationService { } return (renderNodes, referenceStore) - }.mapErrorToConvertServiceError { - .conversionError(underlyingError: $0.localizedDescription) - } - } - - /// Encodes a conversion response to send to the client. - /// - /// - Parameter renderNodes: The render nodes that were produced as part of the conversion. - private func encodeResponse( - renderNodes: [RenderNode], - renderReferenceStore: RenderReferenceStore? - ) -> Result { - Result { - let encoder = JSONEncoder() - - return try encoder.encode( - try ConvertResponse( - renderNodes: renderNodes.map(encoder.encode), - renderReferenceStore: renderReferenceStore.map(encoder.encode) - ) - ) - }.mapErrorToConvertServiceError { - .invalidResponseMessage(underlyingError: $0.localizedDescription) - } } /// Takes a base reference store and adds uncurated article references and documentation extensions. @@ -297,25 +273,6 @@ public struct ConvertService: DocumentationService { } } -extension Result { - /// Returns a new result, mapping any failure value using the given transformation if the error is not a conversion error. - /// - /// If the error value is a ``ConvertServiceError``, it is returned as-is. If it's not, the given transformation is called on the - /// error. - /// - /// - Parameter transform: A closure that takes the failure value of the instance. - func mapErrorToConvertServiceError( - _ transform: (any Error) -> ConvertServiceError - ) -> Result { - mapError { error in - switch error { - case let error as ConvertServiceError: return error - default: return transform(error) - } - } - } -} - private extension SymbolGraph.LineList.Line { /// Creates a line given a convert request line. init(_ line: ConvertRequest.Line) { diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 224bd5f287..83a9aabba4 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -323,7 +323,7 @@ public class DocumentationContext { dataProvider: any DataProvider, diagnosticEngine: DiagnosticEngine = .init(), configuration: Configuration = .init() - ) throws { + ) async throws { self.bundle = bundle self.dataProvider = .new(dataProvider) self.diagnosticEngine = diagnosticEngine diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift index ed10772efc..6f0af6647c 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift @@ -288,9 +288,9 @@ public struct ConvertAction: AsyncAction { let indexer = try Indexer(outputURL: temporaryFolder, bundleID: bundle.id) - let context = try signposter.withIntervalSignpost("Register", id: signposter.makeSignpostID()) { - try DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) - } + let registerInterval = signposter.beginInterval("Register", id: signposter.makeSignpostID()) + let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) + signposter.endInterval("Register", registerInterval) let outputConsumer = ConvertFileWritingConsumer( targetFolder: temporaryFolder, diff --git a/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift index c23723c76f..5678ccc081 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.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 @@ -50,7 +50,7 @@ struct EmitGeneratedCurationAction: AsyncAction { additionalSymbolGraphFiles: symbolGraphFiles(in: additionalSymbolGraphDirectory) ) ) - let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider) + let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider) let writer = GeneratedCurationWriter(context: context, catalogURL: catalogURL, outputURL: outputURL) let curation = try writer.generateDefaultCurationContents(fromSymbol: startingPointSymbolLink, depthLimit: depthLimit) diff --git a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift index 20c36ce3f7..447ed860a7 100644 --- a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift +++ b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.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 @@ -37,9 +37,9 @@ class ExternalTopicsGraphHashTests: XCTestCase { } } - func testNoMetricAddedIfNoExternalTopicsAreResolved() throws { + func testNoMetricAddedIfNoExternalTopicsAreResolved() async throws { // Load bundle without using external resolvers - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") XCTAssertTrue(context.externallyResolvedLinks.isEmpty) // Try adding external topics metrics @@ -50,12 +50,12 @@ class ExternalTopicsGraphHashTests: XCTestCase { XCTAssertNil(testBenchmark.metrics.first?.result, "Metric was added but there was no external links or symbols") } - func testExternalLinksSameHash() throws { + func testExternalLinksSameHash() async throws { let externalResolver = self.externalResolver // Add external links and verify the checksum is always the same - let hashes: [String] = try (0...10).map { _ -> MetricValue? in - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in + func computeTopicHash(file: StaticString = #filePath, line: UInt = #line) async throws -> String { + let (_, _, context) = try await self.testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in try """ # ``SideKit/SideClass`` @@ -74,26 +74,24 @@ class ExternalTopicsGraphHashTests: XCTestCase { let testBenchmark = Benchmark() benchmark(add: Benchmark.ExternalTopicsHash(context: context), benchmarkLog: testBenchmark) - // Verify that a metric was added - XCTAssertNotNil(testBenchmark.metrics[0].result) - return testBenchmark.metrics[0].result - } - .compactMap { value -> String? in - guard let value, - case MetricValue.checksum(let hash) = value else { return nil } - return hash + return try TopicAnchorHashTests.extractChecksumHash(from: testBenchmark) } + let expectedHash = try await computeTopicHash() + // Verify the produced topic graph hash is repeatedly the same - XCTAssertTrue(hashes.allSatisfy({ $0 == hashes.first })) + for _ in 0 ..< 10 { + let hash = try await computeTopicHash() + XCTAssertEqual(hash, expectedHash) + } } - func testLinksAndSymbolsSameHash() throws { + func testLinksAndSymbolsSameHash() async throws { let externalResolver = self.externalResolver // Add external links and verify the checksum is always the same - let hashes: [String] = try (0...10).map { _ -> MetricValue? in - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver], externalSymbolResolver: externalSymbolResolver) { url in + func computeTopicHash(file: StaticString = #filePath, line: UInt = #line) async throws -> String { + let (_, _, context) = try await self.testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver], externalSymbolResolver: self.externalSymbolResolver) { url in try """ # ``SideKit/SideClass`` @@ -113,25 +111,23 @@ class ExternalTopicsGraphHashTests: XCTestCase { let testBenchmark = Benchmark() benchmark(add: Benchmark.ExternalTopicsHash(context: context), benchmarkLog: testBenchmark) - // Verify that a metric was added - XCTAssertNotNil(testBenchmark.metrics[0].result) - return testBenchmark.metrics[0].result - } - .compactMap { value -> String? in - guard let value, - case MetricValue.checksum(let hash) = value else { return nil } - return hash + return try TopicAnchorHashTests.extractChecksumHash(from: testBenchmark) } + let expectedHash = try await computeTopicHash() + // Verify the produced topic graph hash is repeatedly the same - XCTAssertTrue(hashes.allSatisfy({ $0 == hashes.first })) + for _ in 0 ..< 10 { + let hash = try await computeTopicHash() + XCTAssertEqual(hash, expectedHash) + } } - func testExternalTopicsDetectsChanges() throws { + func testExternalTopicsDetectsChanges() async throws { let externalResolver = self.externalResolver // Load a bundle with external links - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in try """ # ``SideKit/SideClass`` diff --git a/Tests/SwiftDocCTests/Benchmark/TopicAnchorHashTests.swift b/Tests/SwiftDocCTests/Benchmark/TopicAnchorHashTests.swift index 682dd1429a..72f714b752 100644 --- a/Tests/SwiftDocCTests/Benchmark/TopicAnchorHashTests.swift +++ b/Tests/SwiftDocCTests/Benchmark/TopicAnchorHashTests.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 @@ -12,26 +12,28 @@ import XCTest @testable import SwiftDocC class TopicAnchorHashTests: XCTestCase { - func testAnchorSectionsHash() throws { - let hashes: [String] = try (0...10).map { _ -> MetricValue? in - let (_, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testAnchorSectionsHash() async throws { + func computeTopicHash(file: StaticString = #filePath, line: UInt = #line) async throws -> String { + let (_, context) = try await self.testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") let testBenchmark = Benchmark() benchmark(add: Benchmark.TopicAnchorHash(context: context), benchmarkLog: testBenchmark) - return testBenchmark.metrics[0].result - } - .compactMap { value -> String? in - guard case MetricValue.checksum(let hash)? = value else { return nil } - return hash + + return try Self.extractChecksumHash(from: testBenchmark) } + + let expectedHash = try await computeTopicHash() // Verify the produced topic graph hash is repeatedly the same - XCTAssertTrue(hashes.allSatisfy({ $0 == hashes.first })) + for _ in 0 ..< 10 { + let hash = try await computeTopicHash() + XCTAssertEqual(hash, expectedHash) + } } - func testTopicAnchorsChangedHash() throws { + func testTopicAnchorsChangedHash() async throws { // Verify that the hash changes if we change the topic graph let initialHash: String - let (_, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + let (_, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") do { let testBenchmark = Benchmark() @@ -70,4 +72,17 @@ class TopicAnchorHashTests: XCTestCase { XCTAssertNotEqual(initialHash, modifiedHash) } + static func extractChecksumHash( + from benchmark: Benchmark, + file: StaticString = #filePath, + line: UInt = #line + ) throws -> String { + let hash: String? = switch benchmark.metrics[0].result { + case .checksum(let hash): + hash + default: + nil + } + return try XCTUnwrap(hash, file: file, line: line) + } } diff --git a/Tests/SwiftDocCTests/Benchmark/TopicGraphHashTests.swift b/Tests/SwiftDocCTests/Benchmark/TopicGraphHashTests.swift index d4b5ecbc50..4204122882 100644 --- a/Tests/SwiftDocCTests/Benchmark/TopicGraphHashTests.swift +++ b/Tests/SwiftDocCTests/Benchmark/TopicGraphHashTests.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 @@ -12,27 +12,28 @@ import XCTest @testable import SwiftDocC class TopicGraphHashTests: XCTestCase { - func testTopicGraphSameHash() throws { - let hashes: [String] = try (0...10).map { _ -> MetricValue? in - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testTopicGraphSameHash() async throws { + func computeTopicHash(file: StaticString = #filePath, line: UInt = #line) async throws -> String { + let (_, context) = try await self.testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let testBenchmark = Benchmark() benchmark(add: Benchmark.TopicGraphHash(context: context), benchmarkLog: testBenchmark) - return testBenchmark.metrics[0].result - } - .compactMap { value -> String? in - guard let value, - case MetricValue.checksum(let hash) = value else { return nil } - return hash + + return try TopicAnchorHashTests.extractChecksumHash(from: testBenchmark) } + + let expectedHash = try await computeTopicHash() // Verify the produced topic graph hash is repeatedly the same - XCTAssertTrue(hashes.allSatisfy({ $0 == hashes.first })) + for _ in 0 ..< 10 { + let hash = try await computeTopicHash() + XCTAssertEqual(hash, expectedHash) + } } - func testTopicGraphChangedHash() throws { + func testTopicGraphChangedHash() async throws { // Verify that the hash changes if we change the topic graph let initialHash: String - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") do { let testBenchmark = Benchmark() @@ -78,7 +79,7 @@ class TopicGraphHashTests: XCTestCase { /// Verify that we safely produce the topic graph hash when external symbols /// participate in the documentation hierarchy. rdar://76419740 - func testProducesTopicGraphHashWhenResolvedExternalReferencesInTaskGroups() throws { + func testProducesTopicGraphHashWhenResolvedExternalReferencesInTaskGroups() async throws { let resolver = TestMultiResultExternalReferenceResolver() resolver.entitiesToReturn = [ "/article": .success(.init(referencePath: "/externally/resolved/path/to/article")), @@ -88,7 +89,7 @@ class TopicGraphHashTests: XCTestCase { "/externally/resolved/path/to/article2": .success(.init(referencePath: "/externally/resolved/path/to/article2")), ] - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [ + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [ "com.external.testbundle" : resolver ]) { url in // Add external links to the MyKit Topics. diff --git a/Tests/SwiftDocCTests/Catalog Processing/GeneratedCurationWriterTests.swift b/Tests/SwiftDocCTests/Catalog Processing/GeneratedCurationWriterTests.swift index 472e25e912..a158e95440 100644 --- a/Tests/SwiftDocCTests/Catalog Processing/GeneratedCurationWriterTests.swift +++ b/Tests/SwiftDocCTests/Catalog Processing/GeneratedCurationWriterTests.swift @@ -14,8 +14,8 @@ import XCTest class GeneratedCurationWriterTests: XCTestCase { private let testOutputURL = URL(fileURLWithPath: "/unit-test/output-dir") // Nothing is written to this path in this test - func testWriteTopLevelSymbolCuration() throws { - let (url, _, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testWriteTopLevelSymbolCuration() async throws { + let (url, _, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let writer = try XCTUnwrap(GeneratedCurationWriter(context: context, catalogURL: url, outputURL: testOutputURL)) let contentsToWrite = try writer.generateDefaultCurationContents(depthLimit: 0) @@ -110,8 +110,8 @@ class GeneratedCurationWriterTests: XCTestCase { """) } - func testWriteSymbolCurationFromTopLevelSymbol() throws { - let (url, _, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testWriteSymbolCurationFromTopLevelSymbol() async throws { + let (url, _, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let writer = try XCTUnwrap(GeneratedCurationWriter(context: context, catalogURL: url, outputURL: testOutputURL)) @@ -141,8 +141,8 @@ class GeneratedCurationWriterTests: XCTestCase { """) } - func testWriteSymbolCurationWithLimitedDepth() throws { - let (url, _, context) = try testBundleAndContext(named: "BundleWithSameNameForSymbolAndContainer") + func testWriteSymbolCurationWithLimitedDepth() async throws { + let (url, _, context) = try await testBundleAndContext(named: "BundleWithSameNameForSymbolAndContainer") let writer = try XCTUnwrap(GeneratedCurationWriter(context: context, catalogURL: url, outputURL: testOutputURL)) let depthLevelsToTest = [nil, 0, 1, 2, 3, 4, 5] @@ -252,8 +252,8 @@ class GeneratedCurationWriterTests: XCTestCase { } } - func testSkipsManuallyCuratedPages() throws { - let (url, _, context) = try testBundleAndContext(named: "MixedManualAutomaticCuration") + func testSkipsManuallyCuratedPages() async throws { + let (url, _, context) = try await testBundleAndContext(named: "MixedManualAutomaticCuration") let writer = try XCTUnwrap(GeneratedCurationWriter(context: context, catalogURL: url, outputURL: testOutputURL)) let contentsToWrite = try writer.generateDefaultCurationContents() @@ -282,8 +282,8 @@ class GeneratedCurationWriterTests: XCTestCase { """) } - func testAddsCommentForDisambiguatedLinks() throws { - let (url, _, context) = try testBundleAndContext(named: "OverloadedSymbols") + func testAddsCommentForDisambiguatedLinks() async throws { + let (url, _, context) = try await testBundleAndContext(named: "OverloadedSymbols") let writer = try XCTUnwrap(GeneratedCurationWriter(context: context, catalogURL: url, outputURL: testOutputURL)) let contentsToWrite = try writer.generateDefaultCurationContents(fromSymbol: "OverloadedProtocol") @@ -308,8 +308,8 @@ class GeneratedCurationWriterTests: XCTestCase { """) } - func testLinksSupportNonPathCharacters() throws { - let (url, _, context) = try testBundleAndContext(named: "InheritedOperators") + func testLinksSupportNonPathCharacters() async throws { + let (url, _, context) = try await testBundleAndContext(named: "InheritedOperators") let writer = try XCTUnwrap(GeneratedCurationWriter(context: context, catalogURL: url, outputURL: testOutputURL)) let contentsToWrite = try writer.generateDefaultCurationContents(fromSymbol: "MyNumber") @@ -346,8 +346,8 @@ class GeneratedCurationWriterTests: XCTestCase { """) } - func testGeneratingLanguageSpecificCuration() throws { - let (url, _, context) = try testBundleAndContext(named: "GeometricalShapes") + func testGeneratingLanguageSpecificCuration() async throws { + let (url, _, context) = try await testBundleAndContext(named: "GeometricalShapes") let writer = try XCTUnwrap(GeneratedCurationWriter(context: context, catalogURL: url, outputURL: testOutputURL)) let contentsToWrite = try writer.generateDefaultCurationContents() @@ -439,8 +439,8 @@ class GeneratedCurationWriterTests: XCTestCase { } - func testCustomOutputLocation() throws { - let (url, _, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testCustomOutputLocation() async throws { + let (url, _, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let writer = try XCTUnwrap(GeneratedCurationWriter(context: context, catalogURL: url, outputURL: testOutputURL)) let contentsToWrite = try writer.generateDefaultCurationContents() diff --git a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift index d57d9e7688..2dd37d3c3e 100644 --- a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift +++ b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.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-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 @@ -177,17 +177,17 @@ func aBlackListedFunc() { - item three """ - func testDisabledByDefault() throws { + func testDisabledByDefault() async throws { // Create a test bundle with some non-inclusive content. let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "Root.md", utf8Content: nonInclusiveContent) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 0) // Non-inclusive content is an info-level diagnostic, so it's filtered out. } - func testEnablingTheChecker() throws { + func testEnablingTheChecker() async throws { // The expectations of the checker being run, depending on the diagnostic level // set to to the documentation context for the compilation. let expectations: [(DiagnosticSeverity, Bool)] = [ @@ -203,7 +203,7 @@ func aBlackListedFunc() { ]) var configuration = DocumentationContext.Configuration() configuration.externalMetadata.diagnosticLevel = severity - let (_, context) = try loadBundle(catalog: catalog, diagnosticEngine: .init(filterLevel: severity), configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, diagnosticEngine: .init(filterLevel: severity), configuration: configuration) // Verify that checker diagnostics were emitted or not, depending on the diagnostic level set. XCTAssertEqual(context.problems.contains(where: { $0.diagnostic.identifier == "org.swift.docc.NonInclusiveLanguage" }), enabled) diff --git a/Tests/SwiftDocCTests/Converter/DocumentationContextConverterTests.swift b/Tests/SwiftDocCTests/Converter/DocumentationContextConverterTests.swift index f5200edd8e..381720b5b2 100644 --- a/Tests/SwiftDocCTests/Converter/DocumentationContextConverterTests.swift +++ b/Tests/SwiftDocCTests/Converter/DocumentationContextConverterTests.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 @@ -13,8 +13,8 @@ import XCTest @testable import SwiftDocC class DocumentationContextConverterTests: XCTestCase { - func testRenderNodesAreIdentical() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderNodesAreIdentical() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // We'll use this to convert nodes ad-hoc let perNodeConverter = DocumentationNodeConverter(bundle: bundle, context: context) @@ -40,8 +40,8 @@ class DocumentationContextConverterTests: XCTestCase { } } - func testSymbolLocationsAreOnlyIncludedWhenRequested() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testSymbolLocationsAreOnlyIncludedWhenRequested() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let renderContext = RenderContext(documentationContext: context, bundle: bundle) let fillIntroducedSymbolNode = try XCTUnwrap( @@ -70,8 +70,8 @@ class DocumentationContextConverterTests: XCTestCase { } } - func testSymbolAccessLevelsAreOnlyIncludedWhenRequested() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testSymbolAccessLevelsAreOnlyIncludedWhenRequested() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let renderContext = RenderContext(documentationContext: context, bundle: bundle) let fillIntroducedSymbolNode = try XCTUnwrap( diff --git a/Tests/SwiftDocCTests/Converter/RenderContextTests.swift b/Tests/SwiftDocCTests/Converter/RenderContextTests.swift index 786b321da4..7ac0d4240f 100644 --- a/Tests/SwiftDocCTests/Converter/RenderContextTests.swift +++ b/Tests/SwiftDocCTests/Converter/RenderContextTests.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 @@ -13,8 +13,8 @@ import XCTest @testable import SwiftDocC class RenderContextTests: XCTestCase { - func testCreatesRenderReferences() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testCreatesRenderReferences() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let renderContext = RenderContext(documentationContext: context, bundle: bundle) diff --git a/Tests/SwiftDocCTests/Converter/RenderNodeCodableTests.swift b/Tests/SwiftDocCTests/Converter/RenderNodeCodableTests.swift index 0081736b66..2295ec79f7 100644 --- a/Tests/SwiftDocCTests/Converter/RenderNodeCodableTests.swift +++ b/Tests/SwiftDocCTests/Converter/RenderNodeCodableTests.swift @@ -169,8 +169,8 @@ class RenderNodeCodableTests: XCTestCase { XCTAssertEqual(renderNode.topicSectionsStyle, .list) } - func testEncodeRenderNodeWithCustomTopicSectionStyle() throws { - let (bundle, context) = try testBundleAndContext() + func testEncodeRenderNodeWithCustomTopicSectionStyle() async throws { + let (bundle, context) = try await testBundleAndContext() var problems = [Problem]() let source = """ diff --git a/Tests/SwiftDocCTests/Converter/TopicRenderReferenceEncoderTests.swift b/Tests/SwiftDocCTests/Converter/TopicRenderReferenceEncoderTests.swift index efcda73806..c3014f1983 100644 --- a/Tests/SwiftDocCTests/Converter/TopicRenderReferenceEncoderTests.swift +++ b/Tests/SwiftDocCTests/Converter/TopicRenderReferenceEncoderTests.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 @@ -157,8 +157,8 @@ class TopicRenderReferenceEncoderTests: XCTestCase { /// Verifies that when JSON encoder should sort keys, the custom render reference cache /// respects that setting and prints the referencs in alphabetical order. - func testSortedReferences() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testSortedReferences() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let converter = DocumentationNodeConverter(bundle: bundle, context: context) // Create a JSON encoder @@ -217,8 +217,8 @@ class TopicRenderReferenceEncoderTests: XCTestCase { } // Verifies that there is no extra comma at the end of the references list. - func testRemovesLastReferencesListDelimiter() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRemovesLastReferencesListDelimiter() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let converter = DocumentationNodeConverter(bundle: bundle, context: context) // Create a JSON encoder diff --git a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift index aaecda11eb..2d23726afb 100644 --- a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift +++ b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift @@ -14,7 +14,7 @@ import SwiftDocCTestUtilities import XCTest class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { - func testNoDeprecationWarningWhenThereAreNoOtherWarnings() throws { + func testNoDeprecationWarningWhenThereAreNoOtherWarnings() async throws { let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "Root.md", utf8Content: """ # Root @@ -22,7 +22,7 @@ class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { An empty root page """) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let outputConsumer = TestOutputConsumer() @@ -38,7 +38,7 @@ class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { XCTAssert(outputConsumer.problems.isEmpty, "Unexpected problems: \(outputConsumer.problems.map(\.diagnostic.summary).joined(separator: "\n"))") } - func testDeprecationWarningWhenThereAreOtherWarnings() throws { + func testDeprecationWarningWhenThereAreOtherWarnings() async throws { let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "Root.md", utf8Content: """ # Root @@ -48,7 +48,7 @@ class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { This link will result in a warning: ``NotFound``. """) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let outputConsumer = TestOutputConsumer() diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift index 148b110a96..17d945f5c1 100644 --- a/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.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 @@ -78,8 +78,8 @@ class DiagnosticTests: XCTestCase { } /// Test offsetting diagnostic ranges - func testOffsetDiagnostics() throws { - let (bundle, context) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ + func testOffsetDiagnostics() async throws { + let (bundle, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName")) ])) diff --git a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift index ef50a6e018..2550a52345 100644 --- a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.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 @@ -1487,14 +1487,14 @@ class ConvertServiceTests: XCTestCase { ) } - func testReturnsRenderReferenceStoreWhenRequestedForOnDiskBundleWithUncuratedArticles() throws { + func testReturnsRenderReferenceStoreWhenRequestedForOnDiskBundleWithUncuratedArticles() async throws { #if os(Linux) throw XCTSkip(""" Skipped on Linux due to an issue in Foundation.Codable where dictionaries are sometimes getting encoded as \ arrays. (github.com/apple/swift/issues/57363) """) #else - let (testBundleURL, _, _) = try testBundleAndContext( + let (testBundleURL, _, _) = try await testBundleAndContext( copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [ "sidekit.symbols.json", @@ -1616,14 +1616,14 @@ class ConvertServiceTests: XCTestCase { #endif } - func testNoRenderReferencesToNonLinkableNodes() throws { + func testNoRenderReferencesToNonLinkableNodes() async throws { #if os(Linux) throw XCTSkip(""" Skipped on Linux due to an issue in Foundation.Codable where dictionaries are sometimes getting encoded as \ arrays. (github.com/apple/swift/issues/57363) """) #else - let (testBundleURL, _, _) = try testBundleAndContext( + let (testBundleURL, _, _) = try await testBundleAndContext( copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [ "mykit-iOS.symbols.json", @@ -1658,14 +1658,14 @@ class ConvertServiceTests: XCTestCase { #endif } - func testReturnsRenderReferenceStoreWhenRequestedForOnDiskBundleWithCuratedArticles() throws { + func testReturnsRenderReferenceStoreWhenRequestedForOnDiskBundleWithCuratedArticles() async throws { #if os(Linux) throw XCTSkip(""" Skipped on Linux due to an issue in Foundation.Codable where dictionaries are sometimes getting encoded as \ arrays. (github.com/apple/swift/issues/57363) """) #else - let (testBundleURL, _, _) = try testBundleAndContext( + let (testBundleURL, _, _) = try await testBundleAndContext( // Use a bundle that contains only articles, one of which is declared as the TechnologyRoot and curates the // other articles. copying: "BundleWithTechnologyRoot" diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index 43243e3427..0f220d928b 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -13,24 +13,24 @@ import XCTest @_spi(ExternalLinks) @testable import SwiftDocC class ExternalRenderNodeTests: XCTestCase { - func generateExternalResover() -> TestMultiResultExternalReferenceResolver { + private func generateExternalResolver() -> TestMultiResultExternalReferenceResolver { let externalResolver = TestMultiResultExternalReferenceResolver() externalResolver.bundleID = "com.test.external" externalResolver.entitiesToReturn["/path/to/external/swiftArticle"] = .success( .init( - referencePath: "/path/to/external/swiftArticle", - title: "SwiftArticle", - kind: .article, - language: .swift - ) + referencePath: "/path/to/external/swiftArticle", + title: "SwiftArticle", + kind: .article, + language: .swift + ) ) externalResolver.entitiesToReturn["/path/to/external/objCArticle"] = .success( .init( - referencePath: "/path/to/external/objCArticle", - title: "ObjCArticle", - kind: .article, - language: .objectiveC - ) + referencePath: "/path/to/external/objCArticle", + title: "ObjCArticle", + kind: .article, + language: .objectiveC + ) ) externalResolver.entitiesToReturn["/path/to/external/swiftSymbol"] = .success( .init( @@ -51,10 +51,10 @@ class ExternalRenderNodeTests: XCTestCase { return externalResolver } - func testExternalRenderNode() throws { + func testExternalRenderNode() async throws { - let externalResolver = generateExternalResover() - let (_, bundle, context) = try testBundleAndContext( + let externalResolver = generateExternalResolver() + let (_, bundle, context) = try await testBundleAndContext( copying: "MixedLanguageFramework", externalResolvers: [externalResolver.bundleID: externalResolver] ) { url in @@ -154,9 +154,9 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) } - func testNavigatorWithExternalNodes() throws { - let externalResolver = generateExternalResover() - let (_, bundle, context) = try testBundleAndContext( + func testNavigatorWithExternalNodes() async throws { + let externalResolver = generateExternalResolver() + let (_, bundle, context) = try await testBundleAndContext( copying: "MixedLanguageFramework", externalResolvers: [externalResolver.bundleID: externalResolver] ) { url in @@ -210,10 +210,10 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) } - func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() throws { - let externalResolver = generateExternalResover() + func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() async throws { + let externalResolver = generateExternalResolver() - let (_, bundle, context) = try testBundleAndContext( + let (_, bundle, context) = try await testBundleAndContext( copying: "MixedLanguageFramework", externalResolvers: [externalResolver.bundleID: externalResolver] ) { url in diff --git a/Tests/SwiftDocCTests/Indexing/IndexingTests.swift b/Tests/SwiftDocCTests/Indexing/IndexingTests.swift index a5381bd043..758f370cb0 100644 --- a/Tests/SwiftDocCTests/Indexing/IndexingTests.swift +++ b/Tests/SwiftDocCTests/Indexing/IndexingTests.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 @@ -14,8 +14,8 @@ import XCTest class IndexingTests: XCTestCase { // MARK: - Tutorial - func testTutorial() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testTutorial() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let tutorialReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift) let node = try context.entity(with: tutorialReference) let tutorial = node.semantic as! Tutorial @@ -88,8 +88,8 @@ class IndexingTests: XCTestCase { // MARK: - Article - func testArticle() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testArticle() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let articleReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/tutorials/Test-Bundle/TestTutorialArticle", sourceLanguage: .swift) let node = try context.entity(with: articleReference) let article = node.semantic as! TutorialArticle @@ -186,8 +186,8 @@ class IndexingTests: XCTestCase { XCTAssertEqual("Hello, world!", aside.rawIndexableTextContent(references: [:])) } - func testRootPageIndexingRecord() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRootPageIndexingRecord() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let articleReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", sourceLanguage: .swift) let node = try context.entity(with: articleReference) let article = node.semantic as! Symbol @@ -206,8 +206,8 @@ class IndexingTests: XCTestCase { indexingRecords[0]) } - func testSymbolIndexingRecord() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testSymbolIndexingRecord() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in // Modify the documentaion to have default availability for MyKit so that there is platform availability // information for MyProtocol (both in the render node and in the indexing record. let plistURL = url.appendingPathComponent("Info.plist") diff --git a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift index c81a569971..9c64514d31 100644 --- a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.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 @@ -396,8 +396,8 @@ Root XCTAssertNotNil(builder.navigatorIndex) } - func testNavigatorIndexGeneration() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNavigatorIndexGeneration() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) var results = Set() @@ -455,7 +455,7 @@ Root assertEqualDumps(results.first ?? "", try testTree(named: "testNavigatorIndexGeneration")) } - func testNavigatorIndexGenerationWithCyclicCuration() throws { + func testNavigatorIndexGenerationWithCyclicCuration() async throws { // This is a documentation hierarchy where every page exist in more than one place in the navigator, // through a mix of automatic and manual curation, with a cycle between the two "leaf" nodes: // @@ -586,7 +586,7 @@ Root """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) @@ -633,8 +633,8 @@ Root """) } - func testNavigatorWithDifferentSwiftAndObjectiveCHierarchies() throws { - let (_, bundle, context) = try testBundleAndContext(named: "GeometricalShapes") + func testNavigatorWithDifferentSwiftAndObjectiveCHierarchies() async throws { + let (_, bundle, context) = try await testBundleAndContext(named: "GeometricalShapes") let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) @@ -831,8 +831,8 @@ Root try FileManager.default.removeItem(at: targetURL) } - func testDoesNotCurateUncuratedPagesInLanguageThatAreCuratedInAnotherLanguage() throws { - let navigatorIndex = try generatedNavigatorIndex(for: "MixedLanguageFramework", bundleIdentifier: "org.swift.mixedlanguageframework") + func testDoesNotCurateUncuratedPagesInLanguageThatAreCuratedInAnotherLanguage() async throws { + let navigatorIndex = try await generatedNavigatorIndex(for: "MixedLanguageFramework", bundleIdentifier: "org.swift.mixedlanguageframework") XCTAssertEqual( navigatorIndex.navigatorTree.root.children @@ -864,8 +864,8 @@ Root ) } - func testMultiCuratesChildrenOfMultiCuratedPages() throws { - let navigatorIndex = try generatedNavigatorIndex(for: "MultiCuratedSubtree", bundleIdentifier: "org.swift.MultiCuratedSubtree") + func testMultiCuratesChildrenOfMultiCuratedPages() async throws { + let navigatorIndex = try await generatedNavigatorIndex(for: "MultiCuratedSubtree", bundleIdentifier: "org.swift.MultiCuratedSubtree") XCTAssertEqual( navigatorIndex.navigatorTree.root.dumpTree(), @@ -894,8 +894,8 @@ Root ) } - func testNavigatorIndexUsingPageTitleGeneration() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNavigatorIndexUsingPageTitleGeneration() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) var results = Set() @@ -943,8 +943,8 @@ Root assertEqualDumps(results.first ?? "", try testTree(named: "testNavigatorIndexPageTitleGeneration")) } - func testNavigatorIndexGenerationNoPaths() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNavigatorIndexGenerationNoPaths() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let converter = DocumentationNodeConverter(bundle: bundle, context: context) var results = Set() @@ -1000,8 +1000,8 @@ Root assertEqualDumps(results.first ?? "", try testTree(named: "testNavigatorIndexGeneration")) } - func testNavigatorIndexGenerationWithLanguageGrouping() throws { - let navigatorIndex = try generatedNavigatorIndex(for: "LegacyBundle_DoNotUseInNewTests", bundleIdentifier: testBundleIdentifier) + func testNavigatorIndexGenerationWithLanguageGrouping() async throws { + let navigatorIndex = try await generatedNavigatorIndex(for: "LegacyBundle_DoNotUseInNewTests", bundleIdentifier: testBundleIdentifier) XCTAssertEqual(navigatorIndex.availabilityIndex.platforms, [.watchOS, .macCatalyst, .iOS, .tvOS, .macOS, .iPadOS]) XCTAssertEqual(navigatorIndex.availabilityIndex.versions(for: .iOS), Set([ @@ -1020,8 +1020,8 @@ Root } - func testNavigatorIndexGenerationWithCuratedFragment() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNavigatorIndexGenerationWithCuratedFragment() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) var results = Set() @@ -1083,8 +1083,8 @@ Root assertEqualDumps(results.first ?? "", try testTree(named: "testNavigatorIndexGeneration")) } - func testNavigatorIndexAvailabilityGeneration() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNavigatorIndexAvailabilityGeneration() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) @@ -1187,8 +1187,8 @@ Root XCTAssertNil(availabilityDB.get(type: String.self, forKey: "content")) } - func testCustomIconsInNavigator() throws { - let (bundle, context) = try testBundleAndContext(named: "BookLikeContent") // This content has a @PageImage with the "icon" purpose + func testCustomIconsInNavigator() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BookLikeContent") // This content has a @PageImage with the "icon" purpose let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) @@ -1213,8 +1213,8 @@ Root ]) } - func testNavigatorIndexDifferentHasherGeneration() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNavigatorIndexDifferentHasherGeneration() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) @@ -1662,8 +1662,8 @@ Root #endif } - func testNavigatorIndexAsReadOnlyFile() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNavigatorIndexAsReadOnlyFile() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let converter = DocumentationNodeConverter(bundle: bundle, context: context) let targetURL = try createTemporaryDirectory() @@ -1902,8 +1902,8 @@ Root ) } - func testAnonymousTopicGroups() throws { - let navigatorIndex = try generatedNavigatorIndex( + func testAnonymousTopicGroups() async throws { + let navigatorIndex = try await generatedNavigatorIndex( for: "AnonymousTopicGroups", bundleIdentifier: "org.swift.docc.example" ) @@ -1923,10 +1923,10 @@ Root ) } - func testNavigatorDoesNotContainOverloads() throws { + func testNavigatorDoesNotContainOverloads() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let navigatorIndex = try generatedNavigatorIndex( + let navigatorIndex = try await generatedNavigatorIndex( for: "OverloadedSymbols", bundleIdentifier: "com.shapes.ShapeKit") @@ -1977,8 +1977,8 @@ Root ) } - func generatedNavigatorIndex(for testBundleName: String, bundleIdentifier: String) throws -> NavigatorIndex { - let (bundle, context) = try testBundleAndContext(named: testBundleName) + func generatedNavigatorIndex(for testBundleName: String, bundleIdentifier: String) async throws -> NavigatorIndex { + let (bundle, context) = try await testBundleAndContext(named: testBundleName) let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) @@ -2004,7 +2004,7 @@ Root expectation.fulfill() } } - wait(for: [expectation], timeout: 10.0) + await fulfillment(of: [expectation], timeout: 10.0) return navigatorIndex } diff --git a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift index 5cc0df3c62..0481fb8237 100644 --- a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.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 @@ -14,7 +14,7 @@ import SwiftDocCTestUtilities @testable import SwiftDocC final class RenderIndexTests: XCTestCase { - func testTestBundleRenderIndexGeneration() throws { + func testTestBundleRenderIndexGeneration() async throws { let expectedIndexURL = try XCTUnwrap( Bundle.module.url( forResource: "TestBundle-RenderIndex", @@ -22,16 +22,14 @@ final class RenderIndexTests: XCTestCase { subdirectory: "Test Resources" ) ) - - try XCTAssertEqual( - generatedRenderIndex(for: "LegacyBundle_DoNotUseInNewTests", with: "org.swift.docc.example"), - RenderIndex.fromURL(expectedIndexURL) - ) + let renderIndex = try await generatedRenderIndex(for: "LegacyBundle_DoNotUseInNewTests", with: "org.swift.docc.example") + try XCTAssertEqual(renderIndex, RenderIndex.fromURL(expectedIndexURL)) } - func testRenderIndexGenerationForBundleWithTechnologyRoot() throws { + func testRenderIndexGenerationForBundleWithTechnologyRoot() async throws { + let renderIndex = try await generatedRenderIndex(for: "BundleWithTechnologyRoot", with: "org.swift.docc.example") try XCTAssertEqual( - generatedRenderIndex(for: "BundleWithTechnologyRoot", with: "org.swift.docc.example"), + renderIndex, RenderIndex.fromString(#""" { "interfaceLanguages": { @@ -62,8 +60,8 @@ final class RenderIndexTests: XCTestCase { ) } - func testRenderIndexGenerationForMixedLanguageFramework() throws { - let renderIndex = try generatedRenderIndex(for: "MixedLanguageFramework", with: "org.swift.MixedLanguageFramework") + func testRenderIndexGenerationForMixedLanguageFramework() async throws { + let renderIndex = try await generatedRenderIndex(for: "MixedLanguageFramework", with: "org.swift.MixedLanguageFramework") XCTAssertEqual( renderIndex, @@ -629,7 +627,7 @@ final class RenderIndexTests: XCTestCase { try assertRoundTripCoding(renderIndexFromJSON) } - func testRenderIndexGenerationWithDeprecatedSymbol() throws { + func testRenderIndexGenerationWithDeprecatedSymbol() async throws { let swiftWithDeprecatedSymbolGraphFile = Bundle.module.url( forResource: "Deprecated", withExtension: "symbols.json", @@ -650,7 +648,7 @@ final class RenderIndexTests: XCTestCase { ) try bundle.write(to: bundleDirectory) - let (_, loadedBundle, context) = try loadBundle(from: bundleDirectory) + let (_, loadedBundle, context) = try await loadBundle(from: bundleDirectory) XCTAssertEqual( try generatedRenderIndex(for: loadedBundle, withIdentifier: "com.test.example", withContext: context), @@ -682,9 +680,10 @@ final class RenderIndexTests: XCTestCase { """#)) } - func testRenderIndexGenerationWithCustomIcon() throws { + func testRenderIndexGenerationWithCustomIcon() async throws { + let renderIndex = try await generatedRenderIndex(for: "BookLikeContent", with: "org.swift.docc.Book") try XCTAssertEqual( - generatedRenderIndex(for: "BookLikeContent", with: "org.swift.docc.Book"), + renderIndex, RenderIndex.fromString(#""" { "interfaceLanguages" : { @@ -737,8 +736,8 @@ final class RenderIndexTests: XCTestCase { ) } - func generatedRenderIndex(for testBundleName: String, with bundleIdentifier: String) throws -> RenderIndex { - let (bundle, context) = try testBundleAndContext(named: testBundleName) + func generatedRenderIndex(for testBundleName: String, with bundleIdentifier: String) async throws -> RenderIndex { + let (bundle, context) = try await testBundleAndContext(named: testBundleName) return try generatedRenderIndex(for: bundle, withIdentifier: bundleIdentifier, withContext: context) } diff --git a/Tests/SwiftDocCTests/Infrastructure/AnchorSectionTests.swift b/Tests/SwiftDocCTests/Infrastructure/AnchorSectionTests.swift index 95741340c4..968cca594f 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AnchorSectionTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AnchorSectionTests.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 @@ -16,8 +16,8 @@ import Markdown class AnchorSectionTests: XCTestCase { - func testResolvingArticleSubsections() throws { - let (bundle, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testResolvingArticleSubsections() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") // Verify the sub-sections of the article have been collected in the context [ @@ -74,8 +74,8 @@ class AnchorSectionTests: XCTestCase { XCTAssertEqual(sectionReference.url, "/documentation/technologyx/article#Article-Sub-Section") } - func testResolvingSymbolSubsections() throws { - let (bundle, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testResolvingSymbolSubsections() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") // Verify the sub-sections of the article have been collected in the context [ @@ -132,8 +132,8 @@ class AnchorSectionTests: XCTestCase { XCTAssertEqual(sectionReference.url, "/documentation/coolframework/coolclass#Symbol-Sub-Section") } - func testResolvingRootPageSubsections() throws { - let (bundle, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testResolvingRootPageSubsections() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") // Verify the sub-sections of the article have been collected in the context [ @@ -190,8 +190,8 @@ class AnchorSectionTests: XCTestCase { XCTAssertEqual(sectionReference.url, "/documentation/coolframework#Module-Sub-Section") } - func testWarnsWhenCuratingSections() throws { - let (_, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testWarnsWhenCuratingSections() async throws { + let (_, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") // The module page has 3 section links in a Topics group, // the context should contain the three warnings about those links diff --git a/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift b/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift index 3eb8c06178..8eb5ce13c3 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.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 @@ -42,7 +42,7 @@ class AutoCapitalizationTests: XCTestCase { // MARK: End-to-end integration tests - func testParametersCapitalization() throws { + func testParametersCapitalization() async throws { let symbolGraph = makeSymbolGraph( docComment: """ Some symbol description. @@ -60,7 +60,7 @@ class AutoCapitalizationTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 0) @@ -88,7 +88,7 @@ class AutoCapitalizationTests: XCTestCase { [SwiftDocC.RenderBlockContent.paragraph(SwiftDocC.RenderBlockContent.Paragraph(inlineContent: [SwiftDocC.RenderInlineContent.text("a`nother invalid capitalization")]))]]) } - func testIndividualParametersCapitalization() throws { + func testIndividualParametersCapitalization() async throws { let symbolGraph = makeSymbolGraph( docComment: """ Some symbol description. @@ -105,7 +105,7 @@ class AutoCapitalizationTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 0) @@ -133,7 +133,7 @@ class AutoCapitalizationTests: XCTestCase { [SwiftDocC.RenderBlockContent.paragraph(SwiftDocC.RenderBlockContent.Paragraph(inlineContent: [SwiftDocC.RenderInlineContent.text("a`nother invalid capitalization")]))]]) } - func testReturnsCapitalization() throws { + func testReturnsCapitalization() async throws { let symbolGraph = makeSymbolGraph( docComment: """ Some symbol description. @@ -146,7 +146,7 @@ class AutoCapitalizationTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 0) diff --git a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift index 1d5761ee54..c66d094540 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.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 @@ -19,7 +19,7 @@ class AutomaticCurationTests: XCTestCase { .filter { $0.symbolGeneratesPage() } .categorize(where: { $0.identifier.hasSuffix(".extension") }) - func testAutomaticTopicsGenerationForSameModuleTypes() throws { + func testAutomaticTopicsGenerationForSameModuleTypes() async throws { for kind in availableNonExtensionSymbolKinds { let containerID = "some-container-id" let memberID = "some-member-id" @@ -38,13 +38,13 @@ class AutomaticCurationTests: XCTestCase { )) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) try assertRenderedPage(atPath: "/documentation/ModuleName/SomeClass", containsAutomaticTopicSectionFor: kind, context: context, bundle: bundle) } } - func testAutomaticTopicsGenerationForExtensionSymbols() throws { + func testAutomaticTopicsGenerationForExtensionSymbols() async throws { // The extended module behavior is already verified for each extended symbol kind in the module. for kind in availableExtensionSymbolKinds where kind != .extendedModule { let containerID = "some-container-id" @@ -83,7 +83,7 @@ class AutomaticCurationTests: XCTestCase { )), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) try assertRenderedPage(atPath: "/documentation/ModuleName", containsAutomaticTopicSectionFor: .extendedModule, context: context, bundle: bundle) try assertRenderedPage(atPath: "/documentation/ModuleName/ExtendedModule", containsAutomaticTopicSectionFor: kind, context: context, bundle: bundle) @@ -116,8 +116,8 @@ class AutomaticCurationTests: XCTestCase { ) } - func testAutomaticTopicsSkippingCustomCuratedSymbols() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in + func testAutomaticTopicsSkippingCustomCuratedSymbols() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in // Curate some of members of SideClass in an API collection try """ # Some API collection @@ -156,7 +156,7 @@ class AutomaticCurationTests: XCTestCase { }).isEmpty) } - func testMergingAutomaticTopics() throws { + func testMergingAutomaticTopics() async throws { let allExpectedChildren = [ "doc://org.swift.docc.example/documentation/SideKit/SideClass/Element", "doc://org.swift.docc.example/documentation/SideKit/SideClass/Value(_:)", @@ -172,7 +172,7 @@ class AutomaticCurationTests: XCTestCase { for curatedIndices in variationsOfChildrenToCurate { let manualCuration = curatedIndices.map { "- <\(allExpectedChildren[$0])>" }.joined(separator: "\n") - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try """ # ``SideKit/SideClass`` @@ -230,8 +230,8 @@ class AutomaticCurationTests: XCTestCase { } } - func testSeeAlsoSectionForAutomaticallyCuratedTopics() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testSeeAlsoSectionForAutomaticallyCuratedTopics() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: url.appendingPathComponent("sidekit.symbols.json"))) // Copy `SideClass` a handful of times @@ -411,7 +411,7 @@ class AutomaticCurationTests: XCTestCase { } } - func testTopLevelSymbolsAreNotAutomaticallyCuratedIfManuallyCuratedElsewhere() throws { + func testTopLevelSymbolsAreNotAutomaticallyCuratedIfManuallyCuratedElsewhere() async throws { // A symbol graph that defines symbol hierarchy of: // TestBed -> A // -> B -> C @@ -421,7 +421,7 @@ class AutomaticCurationTests: XCTestCase { forResource: "TopLevelCuration.symbols", withExtension: "json", subdirectory: "Test Resources")! // Create a test bundle copy with the symbol graph from above - let (bundleURL, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in + let (bundleURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in try? FileManager.default.copyItem(at: topLevelCurationSGFURL, to: url.appendingPathComponent("TopLevelCuration.symbols.json")) } defer { @@ -455,8 +455,8 @@ class AutomaticCurationTests: XCTestCase { } } - func testNoAutoCuratedMixedLanguageDuplicates() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "MixedLanguageFramework") { url in + func testNoAutoCuratedMixedLanguageDuplicates() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "MixedLanguageFramework") { url in // Load the existing Obj-C symbol graph from this fixture. let path = "symbol-graphs/clang/MixedLanguageFramework.symbols.json" @@ -504,8 +504,8 @@ class AutomaticCurationTests: XCTestCase { ) } - func testRelevantLanguagesAreAutoCuratedInMixedLanguageFramework() throws { - let (bundle, context) = try testBundleAndContext(named: "MixedLanguageFramework") + func testRelevantLanguagesAreAutoCuratedInMixedLanguageFramework() async throws { + let (bundle, context) = try await testBundleAndContext(named: "MixedLanguageFramework") let frameworkDocumentationNode = try context.entity( with: ResolvedTopicReference( @@ -574,11 +574,11 @@ class AutomaticCurationTests: XCTestCase { ) } - func testIvarsAndMacrosAreCuratedProperly() throws { + func testIvarsAndMacrosAreCuratedProperly() async throws { let whatsitSymbols = Bundle.module.url( forResource: "Whatsit-Objective-C.symbols", withExtension: "json", subdirectory: "Test Resources")! - let (bundleURL, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (bundleURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try? FileManager.default.copyItem(at: whatsitSymbols, to: url.appendingPathComponent("Whatsit-Objective-C.symbols.json")) } defer { @@ -635,11 +635,11 @@ class AutomaticCurationTests: XCTestCase { ) } - func testTypeSubscriptsAreCuratedProperly() throws { + func testTypeSubscriptsAreCuratedProperly() async throws { let symbolURL = Bundle.module.url( forResource: "TypeSubscript.symbols", withExtension: "json", subdirectory: "Test Resources")! - let (bundleURL, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (bundleURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try? FileManager.default.copyItem(at: symbolURL, to: url.appendingPathComponent("TypeSubscript.symbols.json")) } defer { @@ -670,8 +670,8 @@ class AutomaticCurationTests: XCTestCase { ) } - func testCPlusPlusSymbolsAreCuratedProperly() throws { - let (bundle, context) = try testBundleAndContext(named: "CxxSymbols") + func testCPlusPlusSymbolsAreCuratedProperly() async throws { + let (bundle, context) = try await testBundleAndContext(named: "CxxSymbols") let rootDocumentationNode = try context.entity( with: .init( @@ -702,8 +702,8 @@ class AutomaticCurationTests: XCTestCase { // Ensures that manually curated sample code articles are not also // automatically curated. - func testSampleCodeArticlesRespectManualCuration() throws { - let renderNode = try renderNode(atPath: "/documentation/SomeSample", fromTestBundleNamed: "SampleBundle") + func testSampleCodeArticlesRespectManualCuration() async throws { + let renderNode = try await renderNode(atPath: "/documentation/SomeSample", fromTestBundleNamed: "SampleBundle") guard renderNode.topicSections.count == 2 else { XCTFail("Expected to find '2' topic sections. Found: \(renderNode.topicSections.count.description.singleQuoted).") @@ -731,10 +731,10 @@ class AutomaticCurationTests: XCTestCase { ) } - func testOverloadedSymbolsAreCuratedUnderGroup() throws { + func testOverloadedSymbolsAreCuratedUnderGroup() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let protocolRenderNode = try renderNode( + let protocolRenderNode = try await renderNode( atPath: "/documentation/ShapeKit/OverloadedProtocol", fromTestBundleNamed: "OverloadedSymbols") @@ -748,7 +748,7 @@ class AutomaticCurationTests: XCTestCase { "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)" ]) - let overloadGroupRenderNode = try renderNode( + let overloadGroupRenderNode = try await renderNode( atPath: "/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)", fromTestBundleNamed: "OverloadedSymbols") @@ -758,10 +758,10 @@ class AutomaticCurationTests: XCTestCase { ) } - func testAutomaticCurationHandlesOverloadsWithLanguageFilters() throws { + func testAutomaticCurationHandlesOverloadsWithLanguageFilters() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let (bundle, context) = try testBundleAndContext(named: "OverloadedSymbols") + let (bundle, context) = try await testBundleAndContext(named: "OverloadedSymbols") let protocolDocumentationNode = try context.entity( with: .init( @@ -799,10 +799,10 @@ class AutomaticCurationTests: XCTestCase { try assertAutomaticCuration(variants: [.swift]) } - func testAutomaticCurationDropsOverloadGroupWhenOverloadsAreCurated() throws { + func testAutomaticCurationDropsOverloadGroupWhenOverloadsAreCurated() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let (_, bundle, context) = try testBundleAndContext(copying: "OverloadedSymbols") { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "OverloadedSymbols") { url in try """ # ``OverloadedProtocol`` @@ -848,7 +848,7 @@ class AutomaticCurationTests: XCTestCase { ]) } - func testCuratingTopLevelSymbolUnderModuleStopsAutomaticCuration() throws { + func testCuratingTopLevelSymbolUnderModuleStopsAutomaticCuration() async throws { let catalog = Folder(name: "Something.docc", content: [ JSONFile(name: "Something.symbols.json", content: makeSymbolGraph(moduleName: "Something", symbols: [ makeSymbol(id: "first-symbol-id", kind: .class, pathComponents: ["FirstClass"]), @@ -862,7 +862,7 @@ class AutomaticCurationTests: XCTestCase { - ``SecondClass`` """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -873,7 +873,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssertFalse(secondNode.shouldAutoCurateInCanonicalLocation, "This symbol is manually curated under its module") } - func testCuratingTopLevelSymbolUnderAPICollectionInModuleStopsAutomaticCuration() throws { + func testCuratingTopLevelSymbolUnderAPICollectionInModuleStopsAutomaticCuration() async throws { let catalog = Folder(name: "Something.docc", content: [ JSONFile(name: "Something.symbols.json", content: makeSymbolGraph(moduleName: "Something", symbols: [ makeSymbol(id: "first-symbol-id", kind: .class, pathComponents: ["FirstClass"]), @@ -894,7 +894,7 @@ class AutomaticCurationTests: XCTestCase { - """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -908,7 +908,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssertFalse(apiCollectionNode.shouldAutoCurateInCanonicalLocation, "Any curation of non-symbols stops automatic curation") } - func testCuratingTopLevelSymbolUnderOtherTopLevelSymbolStopsAutomaticCuration() throws { + func testCuratingTopLevelSymbolUnderOtherTopLevelSymbolStopsAutomaticCuration() async throws { let catalog = Folder(name: "Something.docc", content: [ JSONFile(name: "Something.symbols.json", content: makeSymbolGraph(moduleName: "Something", symbols: [ makeSymbol(id: "first-symbol-id", kind: .class, pathComponents: ["FirstClass"]), @@ -922,7 +922,7 @@ class AutomaticCurationTests: XCTestCase { - ``SecondClass`` """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -933,7 +933,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssertFalse(secondNode.shouldAutoCurateInCanonicalLocation, "Curating a top-level symbol under another top-level symbol stops automatic curation") } - func testCuratingTopLevelSymbolUnderOtherTopLevelSymbolAPICollectionStopsAutomaticCuration() throws { + func testCuratingTopLevelSymbolUnderOtherTopLevelSymbolAPICollectionStopsAutomaticCuration() async throws { let catalog = Folder(name: "Something.docc", content: [ JSONFile(name: "Something.symbols.json", content: makeSymbolGraph(moduleName: "Something", symbols: [ makeSymbol(id: "first-symbol-id", kind: .class, pathComponents: ["FirstClass"]), @@ -954,7 +954,7 @@ class AutomaticCurationTests: XCTestCase { - """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -965,7 +965,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssertFalse(secondNode.shouldAutoCurateInCanonicalLocation, "Curating a top-level symbol under another top-level symbol's API collection stops automatic curation") } - func testCuratingTopLevelSymbolUnderDeeperThanTopLevelDoesNotStopAutomaticCuration() throws { + func testCuratingTopLevelSymbolUnderDeeperThanTopLevelDoesNotStopAutomaticCuration() async throws { let catalog = Folder(name: "Something.docc", content: [ JSONFile(name: "Something.symbols.json", content: makeSymbolGraph(moduleName: "Something", symbols: [ makeSymbol(id: "first-symbol-id", kind: .class, pathComponents: ["FirstClass"]), @@ -982,7 +982,7 @@ class AutomaticCurationTests: XCTestCase { - ``SecondClass`` """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -995,7 +995,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssert(secondNode.shouldAutoCurateInCanonicalLocation, "Curating a top-level symbol deeper than top-level doesn't stops automatic curation") } - func testCuratingMemberOutsideCanonicalContainerDoesNotStopAutomaticCuration() throws { + func testCuratingMemberOutsideCanonicalContainerDoesNotStopAutomaticCuration() async throws { let catalog = Folder(name: "Something.docc", content: [ JSONFile(name: "Something.symbols.json", content: makeSymbolGraph(moduleName: "Something", symbols: [ makeSymbol(id: "first-symbol-id", kind: .class, pathComponents: ["FirstClass"]), @@ -1011,7 +1011,7 @@ class AutomaticCurationTests: XCTestCase { - ``FirstClass/firstMember`` """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -1022,7 +1022,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssert(memberNode.shouldAutoCurateInCanonicalLocation, "Curation of member outside its canonical container's hierarchy doesn't stop automatic curation") } - func testCuratingMemberUnderAPICollectionOutsideCanonicalContainerDoesNotStopAutomaticCuration() throws { + func testCuratingMemberUnderAPICollectionOutsideCanonicalContainerDoesNotStopAutomaticCuration() async throws { let catalog = Folder(name: "Something.docc", content: [ JSONFile(name: "Something.symbols.json", content: makeSymbolGraph(moduleName: "Something", symbols: [ makeSymbol(id: "first-symbol-id", kind: .class, pathComponents: ["FirstClass"]), @@ -1045,7 +1045,7 @@ class AutomaticCurationTests: XCTestCase { - """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -1056,7 +1056,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssert(memberNode.shouldAutoCurateInCanonicalLocation, "Curation of member outside its canonical container's hierarchy doesn't stop automatic curation") } - func testCuratingMemberInCanonicalContainerStopsAutomaticCuration() throws { + func testCuratingMemberInCanonicalContainerStopsAutomaticCuration() async throws { let outerContainerID = "outer-container-symbol-id" let innerContainerID = "inner-container-symbol-id" let memberID = "some-member-symbol-id" @@ -1077,7 +1077,7 @@ class AutomaticCurationTests: XCTestCase { - ``FirstClass/firstMember`` """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -1088,7 +1088,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssertFalse(memberNode.shouldAutoCurateInCanonicalLocation) } - func testCuratingMemberInLevelsOfAPICollectionsStopsAutomaticCuration() throws { + func testCuratingMemberInLevelsOfAPICollectionsStopsAutomaticCuration() async throws { let outerContainerID = "outer-container-symbol-id" let innerContainerID = "inner-container-symbol-id" let memberID = "some-member-symbol-id" @@ -1123,7 +1123,7 @@ class AutomaticCurationTests: XCTestCase { - """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -1134,7 +1134,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssertFalse(memberNode.shouldAutoCurateInCanonicalLocation) } - func testCuratingMemberUnderOtherMemberDoesNotStopAutomaticCuration() throws { + func testCuratingMemberUnderOtherMemberDoesNotStopAutomaticCuration() async throws { let outerContainerID = "outer-container-symbol-id" let innerContainerID = "inner-container-symbol-id" let memberID = "some-member-symbol-id" @@ -1156,7 +1156,7 @@ class AutomaticCurationTests: XCTestCase { - ``OuterClass/someMember`` """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -1170,7 +1170,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssert(memberNode.shouldAutoCurateInCanonicalLocation, "Curating a member under another member doesn't stop automatic curation") } - func testCuratingArticleAnywhereStopAutomaticCuration() throws { + func testCuratingArticleAnywhereStopAutomaticCuration() async throws { let catalog = Folder(name: "Something.docc", content: [ JSONFile(name: "Something.symbols.json", content: makeSymbolGraph(moduleName: "Something", symbols: [ makeSymbol(id: "first-symbol-id", kind: .class, pathComponents: ["FirstClass"]), @@ -1195,7 +1195,7 @@ class AutomaticCurationTests: XCTestCase { # First article """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -1210,7 +1210,7 @@ class AutomaticCurationTests: XCTestCase { XCTAssertFalse(secondArticleNode.shouldAutoCurateInCanonicalLocation) } - func testAutomaticallyCuratedSymbolTopicsAreMergedWithManuallyCuratedTopics() throws { + func testAutomaticallyCuratedSymbolTopicsAreMergedWithManuallyCuratedTopics() async throws { for kind in availableNonExtensionSymbolKinds { let containerID = "some-container-id" let memberID = "some-member-id" @@ -1242,7 +1242,7 @@ class AutomaticCurationTests: XCTestCase { """), ]) let catalogURL = try exampleDocumentation.write(inside: createTemporaryDirectory()) - let (_, bundle, context) = try loadBundle(from: catalogURL) + let (_, bundle, context) = try await loadBundle(from: catalogURL) let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName/SomeClass", sourceLanguage: .swift)) @@ -1266,7 +1266,7 @@ class AutomaticCurationTests: XCTestCase { } } - func testAutomaticallyCuratedArticlesAreSortedByTitle() throws { + func testAutomaticallyCuratedArticlesAreSortedByTitle() async throws { // Test bundle with articles where file names and titles are in different orders let catalog = Folder(name: "TestBundle.docc", content: [ JSONFile(name: "TestModule.symbols.json", content: makeSymbolGraph(moduleName: "TestModule")), @@ -1285,7 +1285,7 @@ class AutomaticCurationTests: XCTestCase { ]) // Load the bundle - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") // Get the module and its automatic curation groups @@ -1309,7 +1309,7 @@ class AutomaticCurationTests: XCTestCase { // autoCuratedArticles are sorted by title in a case-insensitive manner // this test verifies that the sorting is correct even when the file names have different cases - func testAutomaticallyCuratedArticlesAreSortedByTitleDifferentCases() throws { + func testAutomaticallyCuratedArticlesAreSortedByTitleDifferentCases() async throws { // In the catalog, the articles are named with the same letter, different cases, // and other articles are added as well @@ -1351,7 +1351,7 @@ class AutomaticCurationTests: XCTestCase { ]) // Load the bundle - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") // Get the module and its automatic curation groups diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+MixedLanguageLinkResolutionTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+MixedLanguageLinkResolutionTests.swift index 06d2eb6ac7..24fc0d736f 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+MixedLanguageLinkResolutionTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+MixedLanguageLinkResolutionTests.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 @@ -13,8 +13,8 @@ import XCTest class DocumentationContext_MixedLanguageLinkResolutionTests: XCTestCase { - func testResolvingLinksWhenSymbolHasSameNameInBothLanguages() throws { - let (_, _, context) = try testBundleAndContext(copying: "MixedLanguageFrameworkComplexLinks") { url in + func testResolvingLinksWhenSymbolHasSameNameInBothLanguages() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "MixedLanguageFrameworkComplexLinks") { url in let swiftSymbolGraph = url.appendingPathComponent("symbol-graph/swift/ObjCLinks.symbols.json") try String(contentsOf: swiftSymbolGraph) .replacingOccurrences(of: "FooSwift", with: "FooObjC") diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+MixedLanguageSourceLanguagesTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+MixedLanguageSourceLanguagesTests.swift index 17d1606726..2206ad29ea 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+MixedLanguageSourceLanguagesTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+MixedLanguageSourceLanguagesTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -12,22 +12,22 @@ import XCTest @testable import SwiftDocC class DocumentationContext_MixedLanguageSourceLanguagesTests: XCTestCase { - func testArticleAvailableSourceLanguagesIsSwiftInSwiftModule() throws { - try assertArticleAvailableSourceLanguages( + func testArticleAvailableSourceLanguagesIsSwiftInSwiftModule() async throws { + try await assertArticleAvailableSourceLanguages( moduleAvailableLanguages: [.swift], expectedArticleDefaultLanguage: .swift ) } - func testArticleAvailableSourceLanguagesIsMixedLanguageInMixedLanguageModule() throws { - try assertArticleAvailableSourceLanguages( + func testArticleAvailableSourceLanguagesIsMixedLanguageInMixedLanguageModule() async throws { + try await assertArticleAvailableSourceLanguages( moduleAvailableLanguages: [.swift, .objectiveC], expectedArticleDefaultLanguage: .swift ) } - func testArticleAvailableSourceLanguagesIsObjectiveCInObjectiveCModule() throws { - try assertArticleAvailableSourceLanguages( + func testArticleAvailableSourceLanguagesIsObjectiveCInObjectiveCModule() async throws { + try await assertArticleAvailableSourceLanguages( moduleAvailableLanguages: [.objectiveC], expectedArticleDefaultLanguage: .objectiveC ) @@ -38,13 +38,13 @@ class DocumentationContext_MixedLanguageSourceLanguagesTests: XCTestCase { expectedArticleDefaultLanguage: SourceLanguage, file: StaticString = #filePath, line: UInt = #line - ) throws { + ) async throws { precondition( moduleAvailableLanguages.allSatisfy { [.swift, .objectiveC].contains($0) }, "moduleAvailableLanguages can only contain Swift and Objective-C as languages." ) - let (_, _, context) = try testBundleAndContext(copying: "MixedLanguageFramework") { url in + let (_, _, context) = try await testBundleAndContext(copying: "MixedLanguageFramework") { url in try """ # MyArticle diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift index aab9b5b857..9523dc0966 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.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 @@ -14,8 +14,8 @@ import SymbolKit import SwiftDocCTestUtilities class DocumentationContext_RootPageTests: XCTestCase { - func testArticleOnlyCatalogWithExplicitTechnologyRoot() throws { - let (_, context) = try loadBundle(catalog: + func testArticleOnlyCatalogWithExplicitTechnologyRoot() async throws { + let (_, context) = try await loadBundle(catalog: Folder(name: "no-sgf-test.docc", content: [ // Root page for the collection TextFile(name: "ReleaseNotes.md", utf8Content: """ @@ -50,8 +50,8 @@ class DocumentationContext_RootPageTests: XCTestCase { ["/documentation/TestBundle/ReleaseNotes-1.2"]) } - func testWarnsAboutExtensionFileTechnologyRoot() throws { - let (_, context) = try loadBundle(catalog: + func testWarnsAboutExtensionFileTechnologyRoot() async throws { + let (_, context) = try await loadBundle(catalog: Folder(name: "no-sgf-test.docc", content: [ // Root page for the collection TextFile(name: "ReleaseNotes.md", utf8Content: """ @@ -84,8 +84,8 @@ class DocumentationContext_RootPageTests: XCTestCase { XCTAssertEqual(solution.replacements.first?.range.upperBound.line, 3) } - func testSingleArticleWithoutTechnologyRootDirective() throws { - let (_, context) = try loadBundle(catalog: + func testSingleArticleWithoutTechnologyRootDirective() async throws { + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: [ TextFile(name: "Article.md", utf8Content: """ # My article @@ -101,8 +101,8 @@ class DocumentationContext_RootPageTests: XCTestCase { XCTAssertEqual(context.problems.count, 0) } - func testMultipleArticlesWithoutTechnologyRootDirective() throws { - let (_, context) = try loadBundle(catalog: + func testMultipleArticlesWithoutTechnologyRootDirective() async throws { + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: [ TextFile(name: "First.md", utf8Content: """ # My first article @@ -135,8 +135,8 @@ class DocumentationContext_RootPageTests: XCTestCase { XCTAssertEqual(context.problems.count, 0) } - func testMultipleArticlesWithoutTechnologyRootDirectiveWithOneMatchingTheCatalogName() throws { - let (_, context) = try loadBundle(catalog: + func testMultipleArticlesWithoutTechnologyRootDirectiveWithOneMatchingTheCatalogName() async throws { + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: [ TextFile(name: "Something.md", utf8Content: """ # Some article diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index b9e67fcb4c..2271df2821 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.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 @@ -39,10 +39,10 @@ class DocumentationContextTests: XCTestCase { // This test checks unregistration of workspace data providers which is deprecated // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - func testResolve() throws { + func testResolve() async throws { let workspace = DocumentationWorkspace() let context = try DocumentationContext(dataProvider: workspace) - let bundle = try testBundle(named: "LegacyBundle_DoNotUseInNewTests") + let bundle = try await testBundle(named: "LegacyBundle_DoNotUseInNewTests") let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) try workspace.registerProvider(dataProvider) @@ -82,8 +82,8 @@ class DocumentationContextTests: XCTestCase { } } - func testLoadEntity() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testLoadEntity() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let identifier = ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift) @@ -416,13 +416,13 @@ class DocumentationContextTests: XCTestCase { XCTAssertEqual(expectedDump, node.markup.debugDescription(), diffDescription(lhs: expectedDump, rhs: node.markup.debugDescription())) } - func testThrowsErrorForMissingResource() throws { - let (_, context) = try testBundleAndContext() + func testThrowsErrorForMissingResource() async throws { + let (_, context) = try await testBundleAndContext() XCTAssertThrowsError(try context.resource(with: ResourceReference(bundleID: "com.example.missing", path: "/missing.swift")), "Expected requesting an unknown file to result in an error.") } - func testThrowsErrorForQualifiedImagePaths() throws { - let (bundle, context) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ + func testThrowsErrorForQualifiedImagePaths() async throws { + let (bundle, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ DataFile(name: "figure1.jpg", data: Data()) ])) let id = bundle.id @@ -434,8 +434,8 @@ class DocumentationContextTests: XCTestCase { XCTAssertThrowsError(try context.resource(with: imageFigure), "Images should be registered (and referred to) by their name, not by their path.") } - func testResourceExists() throws { - let (bundle, context) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ + func testResourceExists() async throws { + let (bundle, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ DataFile(name: "figure1.jpg", data: Data()), DataFile(name: "introposter.jpg", data: Data()), ])) @@ -475,7 +475,7 @@ class DocumentationContextTests: XCTestCase { ) } - func testURLs() throws { + func testURLs() async throws { let exampleDocumentation = Folder(name: "unit-test.docc", content: [ Folder(name: "Symbols", content: []), Folder(name: "Resources", content: [ @@ -504,7 +504,7 @@ class DocumentationContextTests: XCTestCase { ]) // Parse this test content - let (_, context) = try loadBundle(catalog: exampleDocumentation) + let (_, context) = try await loadBundle(catalog: exampleDocumentation) // Verify all the reference identifiers for this content XCTAssertEqual(context.knownIdentifiers.count, 3) @@ -518,8 +518,8 @@ class DocumentationContextTests: XCTestCase { ]) } - func testRegisteredImages() throws { - let (bundle, context) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ + func testRegisteredImages() async throws { + let (bundle, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ DataFile(name: "figure1.jpg", data: Data()), DataFile(name: "figure1.png", data: Data()), DataFile(name: "figure1~dark.png", data: Data()), @@ -558,8 +558,8 @@ class DocumentationContextTests: XCTestCase { ) } - func testExternalAssets() throws { - let (bundle, context) = try testBundleAndContext() + func testExternalAssets() async throws { + let (bundle, context) = try await testBundleAndContext() let image = context.resolveAsset(named: "https://example.com/figure.png", in: bundle.rootReference) XCTAssertNotNil(image) @@ -576,8 +576,8 @@ class DocumentationContextTests: XCTestCase { XCTAssertEqual(video.variants, [DataTraitCollection(userInterfaceStyle: .light, displayScale: .standard): URL(string: "https://example.com/introvideo.mp4")!]) } - func testDownloadAssets() throws { - let (bundle, context) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ + func testDownloadAssets() async throws { + let (bundle, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ DataFile(name: "intro.png", data: Data()), DataFile(name: "project.zip", data: Data()), @@ -680,8 +680,8 @@ class DocumentationContextTests: XCTestCase { XCTAssertEqual(identifiers.count, identifierSet.count, "Found duplicate identifiers.") } - func testDetectsReferenceCollision() throws { - let (_, context) = try testBundleAndContext(named: "TestBundleWithDupe") + func testDetectsReferenceCollision() async throws { + let (_, context) = try await testBundleAndContext(named: "TestBundleWithDupe") let problemWithDuplicate = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.DuplicateReference" } @@ -692,8 +692,8 @@ class DocumentationContextTests: XCTestCase { } - func testDetectsMultipleMarkdownFilesWithSameName() throws { - let (_, context) = try testBundleAndContext(named: "TestBundleWithDupMD") + func testDetectsMultipleMarkdownFilesWithSameName() async throws { + let (_, context) = try await testBundleAndContext(named: "TestBundleWithDupMD") let problemWithDuplicateReference = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.DuplicateReference" } @@ -706,7 +706,7 @@ class DocumentationContextTests: XCTestCase { XCTAssertEqual(localizedSummarySecond, "Redeclaration of \'overview.md\'; this file will be skipped") } - func testUsesMultipleDocExtensionFilesWithSameName() throws { + func testUsesMultipleDocExtensionFilesWithSameName() async throws { // Generate 2 different symbols with the same name. let someSymbol = makeSymbol(id: "someEnumSymbol-id", kind: .init(rawValue: "enum"), pathComponents: ["SomeDirectory", "MyEnum"]) @@ -751,7 +751,7 @@ class DocumentationContextTests: XCTestCase { ) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) // Since documentation extensions' filenames have no impact on the URL of pages, we should not see warnings enforcing unique filenames for them. let problemWithDuplicateReference = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.DuplicateReference" } @@ -767,7 +767,7 @@ class DocumentationContextTests: XCTestCase { XCTAssertEqual(anotherEnumSymbol.abstract?.plainText, "A documentation extension for an unrelated enum.", "The abstract should be from the symbol's documentation extension.") } - func testGraphChecks() throws { + func testGraphChecks() async throws { var configuration = DocumentationContext.Configuration() configuration.topicAnalysisConfiguration.additionalChecks.append( { (context, reference) -> [Problem] in @@ -780,7 +780,7 @@ class DocumentationContextTests: XCTestCase { # Some root page """) ]) - let (_, context) = try loadBundle(catalog: catalog, configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, configuration: configuration) /// Checks if the custom check added problems to the context. let testProblems = context.problems.filter({ (problem) -> Bool in @@ -804,7 +804,7 @@ class DocumentationContextTests: XCTestCase { } } - func testIgnoresUnknownMarkupFiles() throws { + func testIgnoresUnknownMarkupFiles() async throws { let testCatalog = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ InfoPlist(displayName: "TestIgnoresUnknownMarkupFiles", identifier: "com.example.documentation"), Folder(name: "Resources", content: [ @@ -813,13 +813,13 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let (_, context) = try loadBundle(catalog: testCatalog) + let (_, context) = try await loadBundle(catalog: testCatalog) XCTAssertEqual(context.knownPages.map { $0.path }, ["/tutorials/TestIgnoresUnknownMarkupFiles/Article1"]) XCTAssertTrue(context.problems.map { $0.diagnostic.identifier }.contains("org.swift.docc.Article.Title.NotFound")) } - func testLoadsSymbolData() throws { + func testLoadsSymbolData() async throws { let testCatalog = Folder(name: "TestIgnoresUnknownMarkupFiles.docc", content: [ InfoPlist(displayName: "TestIgnoresUnknownMarkupFiles", identifier: "com.example.documentation"), Folder(name: "Resources", content: [ @@ -835,7 +835,7 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let (_, context) = try loadBundle(catalog: testCatalog) + let (_, context) = try await loadBundle(catalog: testCatalog) // Symbols are loaded XCTAssertFalse(context.documentationCache.isEmpty) @@ -1057,7 +1057,7 @@ class DocumentationContextTests: XCTestCase { """) } - func testMergesMultipleSymbolDeclarations() throws { + func testMergesMultipleSymbolDeclarations() async throws { let graphContentiOS = try String(contentsOf: Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")! .appendingPathComponent("mykit-iOS.symbols.json")) @@ -1078,7 +1078,7 @@ class DocumentationContextTests: XCTestCase { ]), ]) - let (_, context) = try loadBundle(catalog: testCatalog) + let (_, context) = try await loadBundle(catalog: testCatalog) // MyClass is loaded guard let myClass = context.documentationCache["s:5MyKit0A5ClassC"], @@ -1094,7 +1094,7 @@ class DocumentationContextTests: XCTestCase { XCTAssertNotNil(myClassSymbol.declaration[[PlatformName(operatingSystemName: "ios"), PlatformName(operatingSystemName: "macos")]] ?? myClassSymbol.declaration[[PlatformName(operatingSystemName: "macos"), PlatformName(operatingSystemName: "ios")]]) } - func testMergedMultipleSymbolDeclarationsIncludesPlatformSpecificSymbols() throws { + func testMergedMultipleSymbolDeclarationsIncludesPlatformSpecificSymbols() async throws { let iOSGraphURL = Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")! .appendingPathComponent("mykit-iOS.symbols.json") @@ -1135,7 +1135,7 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let (_, context) = try loadBundle(catalog: testCatalog) + let (_, context) = try await loadBundle(catalog: testCatalog) // MyFunction is loaded XCTAssertNotNil(context.documentationCache[myFunctionSymbolPreciseIdentifier], "myFunction which only exist on iOS should be found in the graph") @@ -1148,7 +1148,7 @@ class DocumentationContextTests: XCTestCase { ) } - func testResolvesSymbolsBetweenSymbolGraphs() throws { + func testResolvesSymbolsBetweenSymbolGraphs() async throws { let testCatalog = Folder(name: "CrossGraphResolving.docc", content: [ InfoPlist(displayName: "CrossGraphResolving", identifier: "com.example.documentation"), Folder(name: "Resources", content: [ @@ -1163,7 +1163,7 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let (_, context) = try loadBundle(catalog: testCatalog) + let (_, context) = try await loadBundle(catalog: testCatalog) // SideClass is loaded guard let sideClass = context.documentationCache["s:7SideKit0A5ClassC"], @@ -1178,7 +1178,7 @@ class DocumentationContextTests: XCTestCase { }) } - func testLoadsDeclarationWithNoOS() throws { + func testLoadsDeclarationWithNoOS() async throws { var graphContentiOS = try String(contentsOf: Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")! .appendingPathComponent("mykit-iOS.symbols.json")) @@ -1194,7 +1194,7 @@ class DocumentationContextTests: XCTestCase { ]) ]) - let (_, context) = try loadBundle(catalog: testCatalog) + let (_, context) = try await loadBundle(catalog: testCatalog) // MyClass is loaded guard let myClass = context.documentationCache["s:5MyKit0A5ClassC"], @@ -1207,7 +1207,7 @@ class DocumentationContextTests: XCTestCase { XCTAssertNotNil(myClassSymbol.declaration[[nil]]) } - func testDetectsDuplicateSymbolArticles() throws { + func testDetectsDuplicateSymbolArticles() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ makeSymbol(id: "some-symbol-id", kind: .class, pathComponents: ["SomeClass"]) @@ -1226,7 +1226,7 @@ class DocumentationContextTests: XCTestCase { """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let duplicateExtensionProblems = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.DuplicateMarkdownTitleSymbolReferences" } let diagnostic = try XCTUnwrap(duplicateExtensionProblems.first).diagnostic @@ -1240,7 +1240,7 @@ class DocumentationContextTests: XCTestCase { XCTAssert(missingMarkupURLs.isEmpty, "\(missingMarkupURLs.map(\.lastPathComponent).sorted()) isn't mentioned in the diagnostic.") } - func testCanResolveArticleFromTutorial() throws { + func testCanResolveArticleFromTutorial() async throws { struct TestData { let symbolGraphNames: [String] @@ -1283,7 +1283,7 @@ class DocumentationContextTests: XCTestCase { """), ] + testData.symbolGraphFiles) - let (bundle, context) = try loadBundle(catalog: testCatalog) + let (bundle, context) = try await loadBundle(catalog: testCatalog) let renderContext = RenderContext(documentationContext: context, bundle: bundle) let identifier = ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestOverview", sourceLanguage: .swift) @@ -1305,8 +1305,8 @@ class DocumentationContextTests: XCTestCase { } } - func testCuratesSymbolsAndArticlesCorrectly() throws { - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testCuratesSymbolsAndArticlesCorrectly() async throws { + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Sort the edges for each node to get consistent results, no matter the order that the symbols were processed. for (source, targets) in context.topicGraph.edges { @@ -1397,11 +1397,11 @@ let expected = """ return (node, tgNode) } - func testSortingBreadcrumbsOfEqualDistanceToRoot() throws { + func testSortingBreadcrumbsOfEqualDistanceToRoot() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName")) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) /// @@ -1433,11 +1433,11 @@ let expected = """ XCTAssertEqual(["/documentation/SomeModuleName", "/documentation/SomeModuleName/DDD"], canonicalPathFFF.map({ $0.path })) } - func testSortingBreadcrumbsOfDifferentDistancesToRoot() throws { + func testSortingBreadcrumbsOfDifferentDistancesToRoot() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName")) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) let moduleTopicNode = try XCTUnwrap(context.topicGraph.nodeWithReference(moduleReference)) @@ -1475,13 +1475,13 @@ let expected = """ } // Verify that a symbol that has no parents in the symbol graph is automatically curated under the module node. - func testRootSymbolsAreCuratedInModule() throws { + func testRootSymbolsAreCuratedInModule() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName", symbols: [ makeSymbol(id: "some-class-id", kind: .class, pathComponents: ["SomeClass"]), ])), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) // Verify the node is a child of the module node when the graph is loaded. let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -1491,9 +1491,9 @@ let expected = """ } /// Tests whether tutorial curated multiple times gets the correct breadcrumbs and hierarchy. - func testCurateTutorialMultipleTimes() throws { + func testCurateTutorialMultipleTimes() async throws { // Curate "TestTutorial" under MyKit as well as TechnologyX. - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in let myKitURL = root.appendingPathComponent("documentation/mykit.md") let text = try String(contentsOf: myKitURL).replacingOccurrences(of: "## Topics", with: """ ## Topics @@ -1518,9 +1518,9 @@ let expected = """ XCTAssertEqual(paths, [["/documentation/MyKit"], ["/documentation/MyKit", "/documentation/Test-Bundle/article"], ["/tutorials/TestOverview", "/tutorials/TestOverview/$volume", "/tutorials/TestOverview/Chapter-1"]]) } - func testNonOverloadPaths() throws { + func testNonOverloadPaths() async throws { // Add some symbol collisions to graph - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in let sideKitURL = root.appendingPathComponent("sidekit.symbols.json") let text = try String(contentsOf: sideKitURL).replacingOccurrences(of: "\"symbols\" : [", with: """ "symbols" : [ @@ -1571,11 +1571,11 @@ let expected = """ XCTAssertNoThrow(try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass/test-swift.var", sourceLanguage: .swift))) } - func testModuleLanguageFallsBackToSwiftIfItHasNoSymbols() throws { + func testModuleLanguageFallsBackToSwiftIfItHasNoSymbols() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName")), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual( context.soleRootModuleReference.map { context.sourceLanguages(for: $0) }, @@ -1584,9 +1584,9 @@ let expected = """ ) } - func testOverloadPlusNonOverloadCollisionPaths() throws { + func testOverloadPlusNonOverloadCollisionPaths() async throws { // Add some symbol collisions to graph - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in let sideKitURL = root.appendingPathComponent("sidekit.symbols.json") let text = try String(contentsOf: sideKitURL).replacingOccurrences(of: "\"symbols\" : [", with: """ "symbols" : [ @@ -1656,14 +1656,14 @@ let expected = """ XCTAssertNoThrow(try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass/test-959hd", sourceLanguage: .swift))) } - func testUnknownSymbolKind() throws { + func testUnknownSymbolKind() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName", symbols: [ makeSymbol(id: "some-symbol-id", kind: .init(identifier: "blip-blop"), pathComponents: ["SomeUnknownSymbol"]), ])), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) // Get the node, verify its kind is unknown @@ -1671,8 +1671,8 @@ let expected = """ XCTAssertEqual(node.kind, .unknown) } - func testCuratingSymbolsWithSpecialCharacters() throws { - let (_, _, context) = try testBundleAndContext(copying: "InheritedOperators") { root in + func testCuratingSymbolsWithSpecialCharacters() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "InheritedOperators") { root in try """ # ``Operators/MyNumber`` @@ -1710,8 +1710,8 @@ let expected = """ XCTAssertEqual(unresolvedTopicProblems.map(\.diagnostic.summary), [], "All links should resolve without warnings") } - func testOperatorReferences() throws { - let (_, context) = try testBundleAndContext(named: "InheritedOperators") + func testOperatorReferences() async throws { + let (_, context) = try await testBundleAndContext(named: "InheritedOperators") let pageIdentifiersAndNames = Dictionary(uniqueKeysWithValues: try context.knownPages.map { reference in (key: reference.path, value: try context.entity(with: reference).name.description) @@ -1745,7 +1745,7 @@ let expected = """ XCTAssertEqual("/=(_:_:)", pageIdentifiersAndNames["/documentation/Operators/MyNumber/_=(_:_:)"]) } - func testFileNamesWithDifferentPunctuation() throws { + func testFileNamesWithDifferentPunctuation() async throws { let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "Hello-world.md", utf8Content: """ @@ -1779,7 +1779,7 @@ let expected = """ """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.map(\.diagnostic.summary), ["Redeclaration of 'Hello world.md'; this file will be skipped"]) @@ -1792,7 +1792,7 @@ let expected = """ ]) } - func testSpecialCharactersInLinks() throws { + func testSpecialCharactersInLinks() async throws { let catalog = Folder(name: "special-characters.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph( moduleName: "SomeModuleName", @@ -1853,7 +1853,7 @@ let expected = """ """), ]) let bundleURL = try catalog.write(inside: createTemporaryDirectory()) - let (_, bundle, context) = try loadBundle(from: bundleURL) + let (_, bundle, context) = try await loadBundle(from: bundleURL) let problems = context.problems XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") @@ -1982,9 +1982,9 @@ let expected = """ ) } - func testNonOverloadCollisionFromExtension() throws { + func testNonOverloadCollisionFromExtension() async throws { // Add some symbol collisions to graph - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: ["mykit-iOS.symbols.json"]) { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: ["mykit-iOS.symbols.json"]) { root in let sideKitURL = root.appendingPathComponent("something@SideKit.symbols.json") let text = """ { @@ -2046,7 +2046,7 @@ let expected = """ XCTAssertNoThrow(try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/sideClass-swift.var", sourceLanguage: .swift))) } - func testUnresolvedSidecarDiagnostics() throws { + func testUnresolvedSidecarDiagnostics() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( moduleName: "ModuleName", @@ -2068,7 +2068,7 @@ let expected = """ """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let unmatchedSidecarProblem = try XCTUnwrap(context.problems.first(where: { $0.diagnostic.identifier == "org.swift.docc.SymbolUnmatched" })) XCTAssertNotNil(unmatchedSidecarProblem) @@ -2084,7 +2084,7 @@ let expected = """ XCTAssertEqual(unmatchedSidecarDiagnostic.severity, .warning) } - func testExtendingSymbolWithSpaceInName() throws { + func testExtendingSymbolWithSpaceInName() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( moduleName: "ModuleName", @@ -2106,7 +2106,7 @@ let expected = """ """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary).joined(separator: "\n"))") @@ -2116,7 +2116,7 @@ let expected = """ XCTAssertEqual((node.semantic as? Symbol)?.abstract?.plainText, "Extend a symbol with a space in its name.") } - func testDeprecationSummaryWithLocalLink() throws { + func testDeprecationSummaryWithLocalLink() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( moduleName: "ModuleName", @@ -2146,7 +2146,7 @@ let expected = """ """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems:\n\(context.problems.map(\.diagnostic.summary).joined(separator: "\n"))") @@ -2169,7 +2169,7 @@ let expected = """ } } - func testUncuratedArticleDiagnostics() throws { + func testUncuratedArticleDiagnostics() async throws { let catalog = Folder(name: "unit-test.docc", content: [ // This setup only happens if the developer manually mixes symbol inputs from different builds JSONFile(name: "FirstModuleName.symbols.json", content: makeSymbolGraph(moduleName: "FirstModuleName")), @@ -2184,7 +2184,7 @@ let expected = """ """), ]) - let (bundle, context) = try loadBundle(catalog: catalog, diagnosticEngine: .init(filterLevel: .information)) + let (bundle, context) = try await loadBundle(catalog: catalog, diagnosticEngine: .init(filterLevel: .information)) XCTAssertNil(context.soleRootModuleReference) let curationDiagnostics = context.problems.filter({ $0.diagnostic.identifier == "org.swift.docc.ArticleUncurated" }).map(\.diagnostic) @@ -2194,9 +2194,9 @@ let expected = """ XCTAssertEqual(sidecarDiagnostic.severity, .information) } - func testUpdatesReferencesForChildrenOfCollisions() throws { + func testUpdatesReferencesForChildrenOfCollisions() async throws { // Add some symbol collisions to graph - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in let sideKitURL = root.appendingPathComponent("sidekit.symbols.json") var text = try String(contentsOf: sideKitURL) @@ -2340,11 +2340,11 @@ let expected = """ XCTAssertEqual(context.documentationCache.reference(symbolID: "s:5MyKit0A5MyProtocol0Afunc()DefaultImp")?.path, "/documentation/SideKit/SideProtocol/func()-2dxqn") } - func testResolvingArticleLinkBeforeCuratingIt() throws { + func testResolvingArticleLinkBeforeCuratingIt() async throws { var newArticle1URL: URL! // Add an article without curating it anywhere - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Curate MyKit -> new-article1 let myKitURL = root.appendingPathComponent("documentation").appendingPathComponent("mykit.md") try """ @@ -2379,8 +2379,8 @@ let expected = """ XCTAssertEqual(context.problems.filter { $0.diagnostic.source?.path.hasSuffix(newArticle1URL.lastPathComponent) == true }.count, 0) } - func testPrefersNonSymbolsInDocLink() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "SymbolsWithSameNameAsModule") { url in + func testPrefersNonSymbolsInDocLink() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "SymbolsWithSameNameAsModule") { url in // This bundle has a top-level struct named "Wrapper". Adding an article named "Wrapper.md" introduces a possibility for a link collision try """ # An article @@ -2421,9 +2421,9 @@ let expected = """ } // Modules that are being extended should not have their own symbol in the current bundle's graph. - func testNoSymbolForTertiarySymbolGraphModules() throws { + func testNoSymbolForTertiarySymbolGraphModules() async throws { // Add an article without curating it anywhere - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Create an extension only symbol graph. let tertiaryURL = root.appendingPathComponent("Tertiary@MyKit.symbols.json") try """ @@ -2456,8 +2456,8 @@ let expected = """ XCTAssertNil(try? context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/Tertiary", sourceLanguage: .swift))) } - func testDeclarationTokenKinds() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testDeclarationTokenKinds() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let myFunc = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) @@ -2487,7 +2487,7 @@ let expected = """ } // Test reference resolving in symbol graph docs - func testReferenceResolvingDiagnosticsInSourceDocs() throws { + func testReferenceResolvingDiagnosticsInSourceDocs() async throws { for (source, expectedDiagnosticSource) in [ ("file:///path/to/file.swift", "file:///path/to/file.swift"), // Test the scenario where the symbol graph file contains invalid URLs (rdar://77335208). @@ -2584,7 +2584,7 @@ let expected = """ try text.write(to: referencesURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - let (_, _, context) = try loadBundle(from: targetURL) + let (_, _, context) = try await loadBundle(from: targetURL) guard context.problems.count == 5 else { XCTFail("Expected 5 problems during reference resolving; got \(context.problems.count)") @@ -2604,8 +2604,8 @@ let expected = """ } } - func testNavigatorTitle() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNavigatorTitle() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") func renderNodeForPath(path: String) throws -> (DocumentationNode, RenderNode) { let reference = ResolvedTopicReference(bundleID: bundle.id, path: path, sourceLanguage: .swift) let node = try context.entity(with: reference) @@ -2643,7 +2643,7 @@ let expected = """ } } - func testCrossSymbolGraphPathCollisions() throws { + func testCrossSymbolGraphPathCollisions() async throws { // Create temp folder let tempURL = try createTemporaryDirectory() @@ -2659,7 +2659,7 @@ let expected = """ ]).write(inside: tempURL) // Load test bundle - let (_, _, context) = try loadBundle(from: catalogURL) + let (_, _, context) = try await loadBundle(from: catalogURL) let referenceForPath: (String) -> ResolvedTopicReference = { path in return ResolvedTopicReference(bundleID: "com.test.collisions", path: "/documentation" + path, sourceLanguage: .swift) @@ -2678,7 +2678,7 @@ let expected = """ XCTAssertNotNil(try context.entity(with: referenceForPath("/Collisions/SharedStruct/iOSVar"))) } - func testLinkToSymbolWithoutPage() throws { + func testLinkToSymbolWithoutPage() async throws { let inheritedDefaultImplementationsSGF = Bundle.module.url( forResource: "InheritedDefaultImplementations.symbols", withExtension: "json", @@ -2705,18 +2705,18 @@ let expected = """ ] ).write(inside: createTemporaryDirectory()) - let (_, _, context) = try loadBundle(from: testBundle) + let (_, _, context) = try await loadBundle(from: testBundle) let problem = try XCTUnwrap(context.problems.first(where: { $0.diagnostic.identifier == "org.swift.docc.unresolvedTopicReference" })) XCTAssertEqual(problem.diagnostic.summary, "'FirstTarget/Comparable/localDefaultImplementation()' has no page and isn't available for linking.") } - func testContextCachesReferences() throws { + func testContextCachesReferences() async throws { let bundleID: DocumentationBundle.Identifier = #function // Verify there is no pool bucket for the bundle we're about to test XCTAssertNil(ResolvedTopicReference._numberOfCachedReferences(bundleID: bundleID)) - let (_, _, _) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { rootURL in + let (_, _, _) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { rootURL in let infoPlistURL = rootURL.appendingPathComponent("Info.plist", isDirectory: false) try! String(contentsOf: infoPlistURL) .replacingOccurrences(of: "org.swift.docc.example", with: bundleID.rawValue) @@ -2743,8 +2743,8 @@ let expected = """ ResolvedTopicReference.purgePool(for: bundleID) } - func testAbstractAfterMetadataDirective() throws { - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testAbstractAfterMetadataDirective() async throws { + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Get the SideKit/SideClass/init() node and verify it has an abstract and no discussion. // We're verifying that the metadata directive between the title and the abstract didn't cause @@ -2759,7 +2759,7 @@ let expected = """ } /// rdar://69242313 - func testLinkResolutionDoesNotSkipSymbolGraph() throws { + func testLinkResolutionDoesNotSkipSymbolGraph() async throws { let tempURL = try createTemporaryDirectory() let bundleURL = try Folder(name: "Missing.docc", content: [ @@ -2769,7 +2769,7 @@ let expected = """ subdirectory: "Test Resources")!), ]).write(inside: tempURL) - let (_, _, context) = try XCTUnwrap(loadBundle(from: bundleURL)) + let (_, _, context) = try await loadBundle(from: bundleURL) // MissingDocs contains a struct that has a link to a non-existent type. // If there are no problems, that indicates that symbol graph link @@ -2794,8 +2794,8 @@ let expected = """ XCTAssertThrowsError(try DocumentationNode(reference: reference, article: semanticArticle)) } - func testTaskGroupsPersistInitialRangesFromMarkup() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testTaskGroupsPersistInitialRangesFromMarkup() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Verify task group ranges are persisted for symbol docs let symbolReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit", sourceLanguage: .swift) @@ -2831,8 +2831,8 @@ let expected = """ /// Tests that diagnostics raised during link resolution for symbols have the correct source URLs /// - Bug: rdar://63288817 - func testDiagnosticsForSymbolsHaveCorrectSource() throws { - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testDiagnosticsForSymbolsHaveCorrectSource() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in let extensionFile = """ # ``SideKit/SideClass/myFunction()`` @@ -2874,7 +2874,7 @@ let expected = """ XCTAssertEqual(extensionFileChunks.count, 1) } - func testLinkResolutionDiagnosticsEmittedForTechnologyPages() throws { + func testLinkResolutionDiagnosticsEmittedForTechnologyPages() async throws { let tempURL = try createTemporaryDirectory() let bundleURL = try Folder(name: "module-links.docc", content: [ @@ -2902,14 +2902,14 @@ let expected = """ """), ]).write(inside: tempURL) - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) let problems = context.diagnosticEngine.problems let linkResolutionProblems = problems.filter { $0.diagnostic.source?.relativePath.hasSuffix("sidekit.md") == true } XCTAssertEqual(linkResolutionProblems.count, 1) XCTAssertEqual(linkResolutionProblems.first?.diagnostic.identifier, "org.swift.docc.unresolvedTopicReference") } - func testLinkDiagnosticsInSynthesizedTechnologyRoots() throws { + func testLinkDiagnosticsInSynthesizedTechnologyRoots() async throws { // Verify that when synthesizing a technology root, links are resolved in the roots content. // Also, if an article is promoted to a root, verify that any existing metadata is preserved. @@ -2942,7 +2942,7 @@ let expected = """ - """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.map(\.diagnostic.summary), [ "'NotFoundSymbol' doesn't exist at '/Root'", @@ -2987,7 +2987,7 @@ let expected = """ """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.map(\.diagnostic.summary), [ "'NotFoundSymbol' doesn't exist at '/CatalogName'", @@ -3032,7 +3032,7 @@ let expected = """ """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.map(\.diagnostic.summary).sorted(), [ "'NotFoundArticle' doesn't exist at '/CatalogName/Second'", @@ -3045,7 +3045,7 @@ let expected = """ XCTAssertNotNil(rootPage.metadata?.technologyRoot) } - func testResolvingLinksToHeaders() throws { + func testResolvingLinksToHeaders() async throws { let tempURL = try createTemporaryDirectory() let bundleURL = try Folder(name: "module-links.docc", content: [ @@ -3110,7 +3110,7 @@ let expected = """ """), ]).write(inside: tempURL) - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) let articleReference = try XCTUnwrap(context.knownPages.first) let node = try context.entity(with: articleReference) @@ -3135,8 +3135,8 @@ let expected = """ XCTAssertEqual(node.anchorSections.dropLast().last?.reference.absoluteString, "doc://com.test.docc/documentation/article#Emoji-%F0%9F%92%BB") } - func testResolvingLinksToTopicSections() throws { - let (_, context) = try loadBundle(catalog: + func testResolvingLinksToTopicSections() async throws { + let (_, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName")), @@ -3277,10 +3277,10 @@ let expected = """ ]) } - func testExtensionCanUseLanguageSpecificRelativeLinks() throws { + func testExtensionCanUseLanguageSpecificRelativeLinks() async throws { // This test uses a symbol with different names in Swift and Objective-C, each with a member that's only available in that language. let symbolID = "some-symbol-id" - let (_, context) = try loadBundle(catalog: + let (_, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ Folder(name: "swift", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( @@ -3393,7 +3393,7 @@ let expected = """ ]) } - func testWarnOnMultipleMarkdownExtensions() throws { + func testWarnOnMultipleMarkdownExtensions() async throws { let fileContent = """ # ``MyKit/MyClass/myFunction()`` @@ -3426,7 +3426,7 @@ let expected = """ let bundleURL = try exampleDocumentation.write(inside: tempURL) // Parse this test content - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) let identifier = "org.swift.docc.DuplicateMarkdownTitleSymbolReferences" let duplicateMarkdownProblems = context.problems.filter({ $0.diagnostic.identifier == identifier }) @@ -3439,10 +3439,10 @@ let expected = """ /// This test verifies that collision nodes and children of collision nodes are correctly /// matched with their documentation extension files. Besides verifying the correct content /// it verifies also that the curation in these doc extensions is reflected in the topic graph. - func testMatchesCorrectlyDocExtensionToChildOfCollisionTopic() throws { + func testMatchesCorrectlyDocExtensionToChildOfCollisionTopic() async throws { let fifthTestMemberPath = "ShapeKit/OverloadedParentStruct-1jr3p/fifthTestMember" - let (_, bundle, context) = try testBundleAndContext(copying: "OverloadedSymbols") { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "OverloadedSymbols") { url in // Add an article to be curated from collided nodes' doc extensions. try """ # New Article @@ -3509,8 +3509,8 @@ let expected = """ XCTAssertTrue(tgNode2.contains(articleReference)) } - func testMatchesDocumentationExtensionsAsSymbolLinks() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in + func testMatchesDocumentationExtensionsAsSymbolLinks() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in // Two colliding symbols that differ by capitalization. try """ # ``MixedFramework/CollisionsWithDifferentCapitalization/someThing`` @@ -3620,8 +3620,8 @@ let expected = """ } } - func testMatchesDocumentationExtensionsWithSourceLanguageSpecificLinks() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in + func testMatchesDocumentationExtensionsWithSourceLanguageSpecificLinks() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in // typedef NS_OPTIONS(NSInteger, MyObjectiveCOption) { // MyObjectiveCOptionNone = 0, // MyObjectiveCOptionFirst = 1 << 0, @@ -3722,8 +3722,8 @@ let expected = """ } } - func testMatchesDocumentationExtensionsRelativeToModule() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in + func testMatchesDocumentationExtensionsRelativeToModule() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in // Top level symbols, omitting the module name try """ # ``MyStruct/myStructProperty`` @@ -3765,8 +3765,8 @@ let expected = """ } } - func testCurationOfSymbolsWithSameNameAsModule() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "SymbolsWithSameNameAsModule") { url in + func testCurationOfSymbolsWithSameNameAsModule() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "SymbolsWithSameNameAsModule") { url in // Top level symbols, omitting the module name try """ # ``Something`` @@ -3795,8 +3795,8 @@ let expected = """ } } - func testMultipleDocumentationExtensionMatchDiagnostic() throws { - let (_, _, context) = try testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in + func testMultipleDocumentationExtensionMatchDiagnostic() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in // typedef NS_OPTIONS(NSInteger, MyObjectiveCOption) { // MyObjectiveCOptionNone = 0, // MyObjectiveCOptionFirst = 1 << 0, @@ -3852,7 +3852,7 @@ let expected = """ XCTAssertNotEqual(methodMultipleMatchProblem.diagnostic.source, methodMultipleMatchProblem.diagnostic.notes.first?.source, "The warning and the note should refer to different documentation extension files") } - func testAutomaticallyCuratesArticles() throws { + func testAutomaticallyCuratesArticles() async throws { let articleOne = TextFile(name: "Article1.md", utf8Content: """ # Article 1 @@ -3886,7 +3886,7 @@ let expected = """ articleOne, articleTwo, ]).write(inside: tempURL) - let (_, bundle, context) = try loadBundle(from: bundleURL) + let (_, bundle, context) = try await loadBundle(from: bundleURL) let identifiers = context.problems.map(\.diagnostic.identifier) XCTAssertFalse(identifiers.contains(where: { $0 == "org.swift.docc.ArticleUncurated" })) @@ -3926,7 +3926,7 @@ let expected = """ articleOne, articleTwo, ]).write(inside: tempURL) - let (_, bundle, context) = try loadBundle(from: bundleURL) + let (_, bundle, context) = try await loadBundle(from: bundleURL) let rootReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Module", sourceLanguage: .swift) let docNode = try context.entity(with: rootReference) @@ -3936,7 +3936,7 @@ let expected = """ } } - func testAutomaticTaskGroupsPlacedAfterManualCuration() throws { + func testAutomaticTaskGroupsPlacedAfterManualCuration() async throws { let tempURL = try createTemporaryDirectory() let bundleURL = try Folder(name: "Module.docc", content: [ @@ -3969,7 +3969,7 @@ let expected = """ - """), ]).write(inside: tempURL) - let (_, bundle, context) = try loadBundle(from: bundleURL) + let (_, bundle, context) = try await loadBundle(from: bundleURL) let rootReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Module", sourceLanguage: .swift) let docNode = try context.entity(with: rootReference) @@ -3991,8 +3991,8 @@ let expected = """ } // Verifies if the context resolves linkable nodes. - func testLinkableNodes() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testLinkableNodes() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try "# Article1".write(to: url.appendingPathComponent("resolvable-article.md"), atomically: true, encoding: .utf8) let myKitURL = url.appendingPathComponent("documentation").appendingPathComponent("mykit.md") try String(contentsOf: myKitURL) @@ -4012,9 +4012,9 @@ let expected = """ } // Verifies if the context fails to resolve non-resolvable nodes. - func testNonLinkableNodes() throws { + func testNonLinkableNodes() async throws { // Create a bundle with variety absolute and relative links and symbol links to a non linkable node. - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try """ # ``SideKit/SideClass`` Abstract. @@ -4135,7 +4135,7 @@ let expected = """ /// Verify we resolve a relative link to the article if we have /// an article, a tutorial, and a symbol with the *same* names. - func testResolvePrecedenceArticleOverTutorialOverSymbol() throws { + func testResolvePrecedenceArticleOverTutorialOverSymbol() async throws { // Verify resolves correctly between a bundle with an article and a tutorial. do { let infoPlistURL = try XCTUnwrap(Bundle.module.url(forResource: "Info+Availability", withExtension: "plist", subdirectory: "Test Resources")) @@ -4150,7 +4150,7 @@ let expected = """ try testBundle.write(to: tempFolderURL) // Load the bundle - let (_, bundle, context) = try loadBundle(from: tempFolderURL) + let (_, bundle, context) = try await loadBundle(from: tempFolderURL) // Verify the context contains the conflicting topic names // Article XCTAssertNotNil(context.documentationCache[ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Test-Bundle/Test", sourceLanguage: .swift)]) @@ -4187,7 +4187,7 @@ let expected = """ try testBundle.write(to: tempFolderURL) // Load the bundle - let (_, bundle, context) = try loadBundle(from: tempFolderURL) + let (_, bundle, context) = try await loadBundle(from: tempFolderURL) // Verify the context contains the conflicting topic names // Article XCTAssertNotNil(context.documentationCache[ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Test-Bundle/Test", sourceLanguage: .swift)]) @@ -4215,7 +4215,7 @@ let expected = """ } } - func testResolvePrecedenceSymbolInBackticks() throws { + func testResolvePrecedenceSymbolInBackticks() async throws { // Verify resolves correctly a double-backtick link. do { let infoPlistURL = try XCTUnwrap(Bundle.module.url(forResource: "Info+Availability", withExtension: "plist", subdirectory: "Test Resources")) @@ -4248,7 +4248,7 @@ let expected = """ try testBundle.write(to: tempFolderURL) // Load the bundle - let (_, bundle, context) = try loadBundle(from: tempFolderURL) + let (_, bundle, context) = try await loadBundle(from: tempFolderURL) let symbolReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Minimal_docs/Test", sourceLanguage: .swift) let moduleReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Minimal_docs", sourceLanguage: .swift) @@ -4301,7 +4301,7 @@ let expected = """ } } - func testSymbolMatchingModuleName() throws { + func testSymbolMatchingModuleName() async throws { // Verify as top-level symbol with name matching the module name // does not trip the context when building the topic graph do { @@ -4318,7 +4318,7 @@ let expected = """ try testBundle.write(to: tempFolderURL) // Load the bundle - let (_, bundle, context) = try loadBundle(from: tempFolderURL) + let (_, bundle, context) = try await loadBundle(from: tempFolderURL) // Verify the module and symbol node kinds. let symbolReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Minimal_docs/Minimal_docs", sourceLanguage: .swift) @@ -4350,7 +4350,7 @@ let expected = """ /// public func method(_ param: String) { } /// } /// ``` - func testWarningForUnresolvableLinksInInheritedDocs() throws { + func testWarningForUnresolvableLinksInInheritedDocs() async throws { // Create temp folder let tempURL = try createTemporaryDirectory() @@ -4363,7 +4363,7 @@ let expected = """ ]).write(inside: tempURL) // Load the test bundle - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) // Get the emitted diagnostic and verify it contains a solution and replacement fix-it. let problem = try XCTUnwrap(context.problems.first(where: { p in @@ -4392,8 +4392,8 @@ let expected = """ XCTAssertEqual(problem.possibleSolutions[0].replacements[0].replacement, "") } - func testCustomModuleKind() throws { - let (bundle, context) = try testBundleAndContext(named: "BundleWithExecutableModuleKind") + func testCustomModuleKind() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BundleWithExecutableModuleKind") XCTAssertEqual(bundle.info.defaultModuleKind, "Executable") let moduleSymbol = try XCTUnwrap(context.documentationCache["ExampleDocumentedExecutable"]?.symbol) @@ -4403,7 +4403,7 @@ let expected = """ /// Verifies that the number of symbols registered in the documentation context is consistent with /// the number of symbols in the symbol graph files. - func testSymbolsCountIsConsistentWithSymbolGraphData() throws { + func testSymbolsCountIsConsistentWithSymbolGraphData() async throws { let exampleDocumentation = Folder(name: "unit-test.docc", content: [ Folder(name: "Symbols", content: [ JSONFile( @@ -4434,7 +4434,7 @@ let expected = """ InfoPlist(displayName: "TestBundle", identifier: "com.test.example") ]) - let (_, context) = try loadBundle(catalog: exampleDocumentation) + let (_, context) = try await loadBundle(catalog: exampleDocumentation) XCTAssertEqual( context.documentationCache.count, @@ -4443,7 +4443,7 @@ let expected = """ ) } - func testDocumentationExtensionURLForReferenceReturnsURLForSymbolReference() throws { + func testDocumentationExtensionURLForReferenceReturnsURLForSymbolReference() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName", symbols: [ makeSymbol(id: "some-symbol-id", kind: .class, pathComponents: ["SomeClass"]) @@ -4454,7 +4454,7 @@ let expected = """ """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) XCTAssertEqual( @@ -4463,8 +4463,8 @@ let expected = """ ) } - func testDocumentationExtensionURLForReferenceReturnsNilForTutorialReference() throws { - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + func testDocumentationExtensionURLForReferenceReturnsNilForTutorialReference() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") XCTAssertNil( context.documentationExtensionURL( @@ -4479,14 +4479,14 @@ let expected = """ ) } - func testAddingProtocolExtensionMemberConstraint() throws { + func testAddingProtocolExtensionMemberConstraint() async throws { // This fixture contains a protocol extension: // extension Swift.Collection { // public func fixture() -> String { // return "collection" // } // } - let (_, _, context) = try testBundleAndContext(copying: "ModuleWithProtocolExtensions") + let (_, _, context) = try await testBundleAndContext(copying: "ModuleWithProtocolExtensions") // The member function of the protocol extension // should have a constraint: Self is Collection @@ -4524,14 +4524,14 @@ let expected = """ XCTAssertEqual(constraint.rightTypeName, "Hashable") } - func testDiagnosticLocations() throws { + func testDiagnosticLocations() async throws { // The ObjCFrameworkWithInvalidLink.docc test bundle contains symbol // graphs for both Obj-C and Swift, built after setting: // "Build Multi-Language Documentation for Objective-C Only Targets" = true. // One doc comment in the Obj-C header file contains an invalid doc // link on line 24, columns 56-63: // "Log a hello world message. This line contains an ``invalid`` link." - let (_, context) = try testBundleAndContext(named: "ObjCFrameworkWithInvalidLink") + let (_, context) = try await testBundleAndContext(named: "ObjCFrameworkWithInvalidLink") let problems = context.problems if FeatureFlags.current.isParametersAndReturnsValidationEnabled { XCTAssertEqual(4, problems.count) @@ -4547,7 +4547,7 @@ let expected = """ XCTAssertEqual(start.. """.utf8)) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) for kindID in overloadableKindIDs { @@ -4887,7 +4887,7 @@ let expected = """ } // The overload behavior doesn't apply to symbol kinds that don't support overloading - func testContextDoesNotRecognizeNonOverloadableSymbolKinds() throws { + func testContextDoesNotRecognizeNonOverloadableSymbolKinds() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) let nonOverloadableKindIDs = SymbolGraph.Symbol.KindIdentifier.allCases.filter { !$0.isOverloadableKind } @@ -4907,7 +4907,7 @@ let expected = """ )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) for kindID in nonOverloadableKindIDs { // Find the 4 symbols of this specific kind @@ -4923,7 +4923,7 @@ let expected = """ } } - func testWarnsOnUnknownPlistFeatureFlag() throws { + func testWarnsOnUnknownPlistFeatureFlag() async throws { let catalog = Folder(name: "unit-test.docc", content: [ DataFile(name: "Info.plist", data: Data(""" @@ -4938,7 +4938,7 @@ let expected = """ """.utf8)) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let unknownFeatureFlagProblems = context.problems.filter({ $0.diagnostic.identifier == "org.swift.docc.UnknownBundleFeatureFlag" }) XCTAssertEqual(unknownFeatureFlagProblems.count, 1) @@ -4948,7 +4948,7 @@ let expected = """ XCTAssertEqual(problem.diagnostic.summary, "Unknown feature flag in Info.plist: 'NonExistentFeature'") } - func testUnknownFeatureFlagSuggestsOtherFlags() throws { + func testUnknownFeatureFlagSuggestsOtherFlags() async throws { let catalog = Folder(name: "unit-test.docc", content: [ DataFile(name: "Info.plist", data: Data(""" @@ -4963,7 +4963,7 @@ let expected = """ """.utf8)) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let unknownFeatureFlagProblems = context.problems.filter({ $0.diagnostic.identifier == "org.swift.docc.UnknownBundleFeatureFlag" }) XCTAssertEqual(unknownFeatureFlagProblems.count, 1) @@ -4975,7 +4975,7 @@ let expected = """ "Unknown feature flag in Info.plist: 'ExperimenalOverloadedSymbolPresentation'. Possible suggestions: 'ExperimentalOverloadedSymbolPresentation'") } - func testContextGeneratesUnifiedOverloadGroupsAcrossPlatforms() throws { + func testContextGeneratesUnifiedOverloadGroupsAcrossPlatforms() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first) @@ -4998,7 +4998,7 @@ let expected = """ ])), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) let overloadGroupNode: DocumentationNode @@ -5048,7 +5048,7 @@ let expected = """ } } - func testContextGeneratesOverloadGroupsWhenOnePlatformHasNoOverloads() throws { + func testContextGeneratesOverloadGroupsWhenOnePlatformHasNoOverloads() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first) @@ -5075,7 +5075,7 @@ let expected = """ ])), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) let overloadGroupNode: DocumentationNode @@ -5128,7 +5128,7 @@ let expected = """ /// Ensure that overload groups are correctly loaded into the path hierarchy and create nodes, /// even when they came from an extension symbol graph. - func testContextGeneratesOverloadGroupsForExtensionGraphOverloads() throws { + func testContextGeneratesOverloadGroupsForExtensionGraphOverloads() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first) @@ -5150,7 +5150,7 @@ let expected = """ ])), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) let overloadGroupNode: DocumentationNode @@ -5198,7 +5198,7 @@ let expected = """ } } - func testContextGeneratesOverloadGroupsForDisjointOverloads() throws { + func testContextGeneratesOverloadGroupsForDisjointOverloads() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first) @@ -5219,7 +5219,7 @@ let expected = """ makeSymbol(id: "symbol-2", kind: symbolKind, pathComponents: ["SymbolName"]), ])), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) let overloadGroupNode: DocumentationNode @@ -5267,7 +5267,7 @@ let expected = """ } } - func testContextDiagnosesInsufficientDisambiguationWithCorrectRange() throws { + func testContextDiagnosesInsufficientDisambiguationWithCorrectRange() async throws { // This test deliberately does not turn on the overloads feature // to ensure the symbol link below does not accidentally resolve correctly. for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind { @@ -5297,7 +5297,7 @@ let expected = """ """) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let problems = context.problems.sorted(by: \.diagnostic.summary) XCTAssertEqual(problems.count, 1) @@ -5321,7 +5321,7 @@ let expected = """ } } - func testContextDiagnosesIncorrectDisambiguationWithCorrectRange() throws { + func testContextDiagnosesIncorrectDisambiguationWithCorrectRange() async throws { // This test deliberately does not turn on the overloads feature // to ensure the symbol link below does not accidentally resolve correctly. for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind { @@ -5351,7 +5351,7 @@ let expected = """ """) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let problems = context.problems.sorted(by: \.diagnostic.summary) XCTAssertEqual(problems.count, 1) @@ -5373,7 +5373,7 @@ let expected = """ } } - func testContextDiagnosesIncorrectSymbolNameWithCorrectRange() throws { + func testContextDiagnosesIncorrectSymbolNameWithCorrectRange() async throws { // This test deliberately does not turn on the overloads feature // to ensure the symbol link below does not accidentally resolve correctly. for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind { @@ -5403,7 +5403,7 @@ let expected = """ """) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let problems = context.problems.sorted(by: \.diagnostic.summary) XCTAssertEqual(problems.count, 1) @@ -5425,13 +5425,13 @@ let expected = """ } } - func testResolveExternalLinkFromTechnologyRoot() throws { + func testResolveExternalLinkFromTechnologyRoot() async throws { enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled) let externalModuleName = "ExternalModuleName" - func makeExternalDependencyFiles() throws -> (SerializableLinkResolutionInformation, [LinkDestinationSummary]) { - let (bundle, context) = try loadBundle( + func makeExternalDependencyFiles() async throws -> (SerializableLinkResolutionInformation, [LinkDestinationSummary]) { + let (bundle, context) = try await loadBundle( catalog: Folder(name: "Dependency.docc", content: [ JSONFile(name: "\(externalModuleName).symbols.json", content: makeSymbolGraph(moduleName: externalModuleName)), TextFile(name: "Extension.md", utf8Content: """ @@ -5465,14 +5465,14 @@ let expected = """ """), ]) - let (linkResolutionInformation, linkSummaries) = try makeExternalDependencyFiles() + let (linkResolutionInformation, linkSummaries) = try await makeExternalDependencyFiles() var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.dependencyArchives = [ URL(fileURLWithPath: "/path/to/SomeDependency.doccarchive") ] - let (bundle, context) = try loadBundle( + let (bundle, context) = try await loadBundle( catalog: catalog, otherFileSystemDirectories: [ Folder(name: "path", content: [ @@ -5515,8 +5515,8 @@ let expected = """ XCTAssertEqual(externalRenderReference.abstract, [.text("Some description of this module.")]) } - func testResolvesAlternateDeclarations() throws { - let (bundle, context) = try loadBundle(catalog: Folder( + func testResolvesAlternateDeclarations() async throws { + let (bundle, context) = try await loadBundle(catalog: Folder( name: "unit-test.docc", content: [ TextFile(name: "Symbol.md", utf8Content: """ @@ -5593,8 +5593,8 @@ let expected = """ XCTAssertEqual(problem.diagnostic.summary, "Can't resolve 'MissingSymbol'") } - func testDiagnosesSymbolAlternateDeclarations() throws { - let (_, context) = try loadBundle(catalog: Folder( + func testDiagnosesSymbolAlternateDeclarations() async throws { + let (_, context) = try await loadBundle(catalog: Folder( name: "unit-test.docc", content: [ TextFile(name: "Symbol.md", utf8Content: """ @@ -5664,8 +5664,8 @@ let expected = """ XCTAssertEqual(solution.replacements.first?.replacement, "") } - func testDiagnosesArticleAlternateDeclarations() throws { - let (_, context) = try loadBundle(catalog: Folder( + func testDiagnosesArticleAlternateDeclarations() async throws { + let (_, context) = try await loadBundle(catalog: Folder( name: "unit-test.docc", content: [ TextFile(name: "Symbol.md", utf8Content: """ diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift index 0149024be8..84321b58f9 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift @@ -27,8 +27,8 @@ class DocumentationCuratorTests: XCTestCase { } } - func testCrawl() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testCrawl() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var crawler = DocumentationCurator(in: context, bundle: bundle) let mykit = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", sourceLanguage: .swift)) @@ -74,8 +74,8 @@ class DocumentationCuratorTests: XCTestCase { ) } - func testCrawlDiagnostics() throws { - let (tempCatalogURL, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testCrawlDiagnostics() async throws { + let (tempCatalogURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in let extensionFile = url.appendingPathComponent("documentation/myfunction.md") try """ @@ -136,8 +136,8 @@ class DocumentationCuratorTests: XCTestCase { """) } - func testCyclicCurationDiagnostic() throws { - let (_, context) = try loadBundle(catalog: + func testCyclicCurationDiagnostic() async throws { + let (_, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ // A number of articles with this cyclic curation: // @@ -201,7 +201,7 @@ class DocumentationCuratorTests: XCTestCase { XCTAssertEqual(curationProblem.possibleSolutions.map(\.summary), ["Remove '- '"]) } - func testCurationInUncuratedAPICollection() throws { + func testCurationInUncuratedAPICollection() async throws { // Everything should behave the same when an API Collection is automatically curated as when it is explicitly curated for shouldCurateAPICollection in [true, false] { let assertionMessageDescription = "when the API collection is \(shouldCurateAPICollection ? "explicitly curated" : "auto-curated as an article under the module")." @@ -228,7 +228,7 @@ class DocumentationCuratorTests: XCTestCase { - ``NotFound`` """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssertEqual( context.problems.map(\.diagnostic.summary), [ @@ -285,8 +285,8 @@ class DocumentationCuratorTests: XCTestCase { } } - func testModuleUnderTechnologyRoot() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "SourceLocations") { url in + func testModuleUnderTechnologyRoot() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "SourceLocations") { url in try """ # Root curating a module @@ -318,8 +318,8 @@ class DocumentationCuratorTests: XCTestCase { XCTAssertEqual(crawler.problems.count, 0) } - func testCuratorDoesNotRelateNodesWhenArticleLinksContainExtraPathComponents() throws { - let (bundle, context) = try loadBundle(catalog: + func testCuratorDoesNotRelateNodesWhenArticleLinksContainExtraPathComponents() async throws { + let (bundle, context) = try await loadBundle(catalog: Folder(name: "CatalogName.docc", content: [ TextFile(name: "Root.md", utf8Content: """ # Root @@ -417,8 +417,8 @@ class DocumentationCuratorTests: XCTestCase { ]) } - func testModuleUnderAncestorOfTechnologyRoot() throws { - let (_, _, context) = try testBundleAndContext(copying: "SourceLocations") { url in + func testModuleUnderAncestorOfTechnologyRoot() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "SourceLocations") { url in try """ # Root with ancestor curating a module @@ -458,8 +458,8 @@ class DocumentationCuratorTests: XCTestCase { XCTAssertEqual(root.path, "/documentation/Root") } - func testSymbolLinkResolving() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testSymbolLinkResolving() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let crawler = DocumentationCurator(in: context, bundle: bundle) @@ -511,8 +511,8 @@ class DocumentationCuratorTests: XCTestCase { } } - func testLinkResolving() throws { - let (sourceRoot, bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testLinkResolving() async throws { + let (sourceRoot, bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var crawler = DocumentationCurator(in: context, bundle: bundle) @@ -566,8 +566,8 @@ class DocumentationCuratorTests: XCTestCase { } } - func testGroupLinkValidation() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { root in + func testGroupLinkValidation() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { root in // Create a sidecar with invalid group links try! """ # ``SideKit`` @@ -660,8 +660,8 @@ class DocumentationCuratorTests: XCTestCase { /// +-- SecondLevelNesting (Manually curated) /// +-- MyArticle ( <--- This should be crawled even if we've mixed manual and automatic curation) /// ``` - func testMixedManualAndAutomaticCuration() throws { - let (bundle, context) = try testBundleAndContext(named: "MixedManualAutomaticCuration") + func testMixedManualAndAutomaticCuration() async throws { + let (bundle, context) = try await testBundleAndContext(named: "MixedManualAutomaticCuration") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TestBed/TopClass/NestedEnum/SecondLevelNesting", sourceLanguage: .swift) let entity = try context.entity(with: reference) @@ -696,8 +696,8 @@ class DocumentationCuratorTests: XCTestCase { /// In case a symbol has automatically curated children and is manually curated multiple times, /// the hierarchy should be created as it's authored. rdar://75453839 - func testMultipleManualCurationIsPreserved() throws { - let (bundle, context) = try testBundleAndContext(named: "MixedManualAutomaticCuration") + func testMultipleManualCurationIsPreserved() async throws { + let (bundle, context) = try await testBundleAndContext(named: "MixedManualAutomaticCuration") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TestBed/DoublyManuallyCuratedClass/type()", sourceLanguage: .swift) diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift index ebf394ab15..7af1fa06eb 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -23,8 +23,8 @@ class ExternalPathHierarchyResolverTests: XCTestCase { // These tests resolve absolute symbol links in both a local and external context to verify that external links work the same local links. - func testUnambiguousAbsolutePaths() throws { - let linkResolvers = try makeLinkResolversForTestBundle(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testUnambiguousAbsolutePaths() async throws { + let linkResolvers = try await makeLinkResolversForTestBundle(named: "MixedLanguageFrameworkWithLanguageRefinements") try linkResolvers.assertSuccessfullyResolves(authoredLink: "/MixedFramework") @@ -408,8 +408,8 @@ class ExternalPathHierarchyResolverTests: XCTestCase { ) } - func testAmbiguousPaths() throws { - let linkResolvers = try makeLinkResolversForTestBundle(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testAmbiguousPaths() async throws { + let linkResolvers = try await makeLinkResolversForTestBundle(named: "MixedLanguageFrameworkWithLanguageRefinements") // public enum CollisionsWithDifferentKinds { // case something @@ -577,8 +577,8 @@ class ExternalPathHierarchyResolverTests: XCTestCase { ) } - func testRedundantDisambiguations() throws { - let linkResolvers = try makeLinkResolversForTestBundle(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testRedundantDisambiguations() async throws { + let linkResolvers = try await makeLinkResolversForTestBundle(named: "MixedLanguageFrameworkWithLanguageRefinements") try linkResolvers.assertSuccessfullyResolves(authoredLink: "/MixedFramework") @@ -685,7 +685,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { ) } - func testSymbolLinksInDeclarationsAndRelationships() throws { + func testSymbolLinksInDeclarationsAndRelationships() async throws { // Build documentation for the dependency first let symbols = [("First", .class), ("Second", .protocol), ("Third", .struct), ("Fourth", .enum)].map { (name: String, kind: SymbolGraph.Symbol.KindIdentifier) in return SymbolGraph.Symbol( @@ -699,7 +699,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { ) } - let (dependencyBundle, dependencyContext) = try loadBundle( + let (dependencyBundle, dependencyContext) = try await loadBundle( catalog: Folder(name: "Dependency.docc", content: [ InfoPlist(identifier: "com.example.dependency"), // This isn't necessary but makes it easier to distinguish the identifier from the module name in the external references. JSONFile(name: "Dependency.symbols.json", content: makeSymbolGraph(moduleName: "Dependency", symbols: symbols)) @@ -724,7 +724,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { configuration.externalDocumentationConfiguration.dependencyArchives = [URL(fileURLWithPath: "/Dependency.doccarchive")] // After building the dependency, - let (mainBundle, mainContext) = try loadBundle( + let (mainBundle, mainContext) = try await loadBundle( catalog: Folder(name: "Main.docc", content: [ JSONFile(name: "Main.symbols.json", content: makeSymbolGraph( moduleName: "Main", @@ -851,10 +851,10 @@ class ExternalPathHierarchyResolverTests: XCTestCase { } } - func testOverloadGroupSymbolsResolveWithoutHash() throws { + func testOverloadGroupSymbolsResolveWithoutHash() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let linkResolvers = try makeLinkResolversForTestBundle(named: "OverloadedSymbols") + let linkResolvers = try await makeLinkResolversForTestBundle(named: "OverloadedSymbols") // The enum case should continue to resolve by kind, since it has no hash collision try linkResolvers.assertSuccessfullyResolves(authoredLink: "/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-swift.enum.case") @@ -872,7 +872,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { ) } - func testBetaInformationPreserved() throws { + func testBetaInformationPreserved() async throws { let platformMetadata = [ "macOS": PlatformVersion(VersionTriplet(1, 0, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(2, 0, 0), beta: true), @@ -884,7 +884,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalMetadata.currentPlatforms = platformMetadata - let linkResolvers = try makeLinkResolversForTestBundle(named: "AvailabilityBetaBundle", configuration: configuration) + let linkResolvers = try await makeLinkResolversForTestBundle(named: "AvailabilityBetaBundle", configuration: configuration) // MyClass is only available on beta platforms (macos=1.0.0, watchos=2.0.0, tvos=3.0.0, ios=4.0.0) try linkResolvers.assertBetaStatus(authoredLink: "/MyKit/MyClass", isBeta: true) @@ -989,9 +989,9 @@ class ExternalPathHierarchyResolverTests: XCTestCase { } } - private func makeLinkResolversForTestBundle(named testBundleName: String, configuration: DocumentationContext.Configuration = .init()) throws -> LinkResolvers { + private func makeLinkResolversForTestBundle(named testBundleName: String, configuration: DocumentationContext.Configuration = .init()) async throws -> LinkResolvers { let bundleURL = try XCTUnwrap(Bundle.module.url(forResource: testBundleName, withExtension: "docc", subdirectory: "Test Bundles")) - let (_, bundle, context) = try loadBundle(from: bundleURL, configuration: configuration) + let (_, bundle, context) = try await loadBundle(from: bundleURL, configuration: configuration) let localResolver = try XCTUnwrap(context.linkResolver.localResolver) diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index e5ed51ddb4..2c55f82aa7 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.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 @@ -57,8 +57,8 @@ class ExternalReferenceResolverTests: XCTestCase { } } - func testResolveExternalReference() throws { - let (_, bundle, context) = try testBundleAndContext( + func testResolveExternalReference() async throws { + let (_, bundle, context) = try await testBundleAndContext( copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : TestExternalReferenceResolver()] ) { url in @@ -86,7 +86,7 @@ class ExternalReferenceResolverTests: XCTestCase { // Asserts that an external reference from a source language not locally included // in the current DocC catalog is still included in any rendered topic groups that // manually curate it. (94406023) - func testExternalReferenceInOtherLanguageIsIncludedInTopicGroup() throws { + func testExternalReferenceInOtherLanguageIsIncludedInTopicGroup() async throws { let externalResolver = TestExternalReferenceResolver() externalResolver.bundleID = "com.test.external" externalResolver.expectedReferencePath = "/path/to/external/api" @@ -96,7 +96,7 @@ class ExternalReferenceResolverTests: XCTestCase { // Set the language of the externally resolved entity to 'data'. externalResolver.resolvedEntityLanguage = .data - let (_, bundle, context) = try testBundleAndContext( + let (_, bundle, context) = try await testBundleAndContext( copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver] ) { url in @@ -156,9 +156,9 @@ class ExternalReferenceResolverTests: XCTestCase { // This test verifies the behavior of a deprecated functionality (changing external documentation sources after registering the documentation) // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - func testResolvesReferencesExternallyOnlyWhenFallbackResolversAreSet() throws { + func testResolvesReferencesExternallyOnlyWhenFallbackResolversAreSet() async throws { let workspace = DocumentationWorkspace() - let bundle = try testBundle(named: "LegacyBundle_DoNotUseInNewTests") + let bundle = try await testBundle(named: "LegacyBundle_DoNotUseInNewTests") let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) try workspace.registerProvider(dataProvider) let context = try DocumentationContext(dataProvider: workspace) @@ -219,15 +219,15 @@ class ExternalReferenceResolverTests: XCTestCase { } } - func testLoadEntityForExternalReference() throws { - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : TestExternalReferenceResolver()]) + func testLoadEntityForExternalReference() async throws { + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : TestExternalReferenceResolver()]) let identifier = ResolvedTopicReference(bundleID: "com.external.testbundle", path: "/externally/resolved/path", sourceLanguage: .swift) XCTAssertThrowsError(try context.entity(with: ResolvedTopicReference(bundleID: "some.other.bundle", path: identifier.path, sourceLanguage: .swift))) XCTAssertThrowsError(try context.entity(with: identifier)) } - func testRenderReferenceHasSymbolKind() throws { + func testRenderReferenceHasSymbolKind() async throws { let fixtures: [(DocumentationNode.Kind, RenderNode.Kind)] = [ (.class, .symbol), (.structure, .symbol), @@ -251,7 +251,7 @@ class ExternalReferenceResolverTests: XCTestCase { externalResolver.resolvedEntityTitle = "ClassName" externalResolver.resolvedEntityKind = resolvedEntityKind - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) let converter = DocumentationNodeConverter(bundle: bundle, context: context) let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) @@ -281,7 +281,7 @@ class ExternalReferenceResolverTests: XCTestCase { } } - func testReferenceFromRenderedPageHasFragments() throws { + func testReferenceFromRenderedPageHasFragments() async throws { let externalResolver = TestExternalReferenceResolver() externalResolver.bundleID = "com.test.external" externalResolver.expectedReferencePath = "/path/to/external/symbol" @@ -293,7 +293,7 @@ class ExternalReferenceResolverTests: XCTestCase { .init(kind: .identifier, spelling: "ClassName", preciseIdentifier: nil), ]) - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in try """ # ``SideKit/SideClass`` @@ -328,7 +328,7 @@ class ExternalReferenceResolverTests: XCTestCase { ]) } - func testExternalReferenceWithDifferentResolvedPath() throws { + func testExternalReferenceWithDifferentResolvedPath() async throws { let externalResolver = TestExternalReferenceResolver() externalResolver.bundleID = "com.test.external" // Return a different path for this resolved reference @@ -350,7 +350,7 @@ class ExternalReferenceResolverTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = [externalResolver.bundleID: externalResolver] - let (bundle, context) = try loadBundle(catalog: tempFolder, configuration: configuration) + let (bundle, context) = try await loadBundle(catalog: tempFolder, configuration: configuration) let converter = DocumentationNodeConverter(bundle: bundle, context: context) let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/article", sourceLanguage: .swift)) @@ -378,14 +378,14 @@ class ExternalReferenceResolverTests: XCTestCase { } } - func testSampleCodeReferenceHasSampleCodeRole() throws { + func testSampleCodeReferenceHasSampleCodeRole() async throws { let externalResolver = TestExternalReferenceResolver() externalResolver.bundleID = "com.test.external" externalResolver.expectedReferencePath = "/path/to/external/sample" externalResolver.resolvedEntityTitle = "Name of Sample" externalResolver.resolvedEntityKind = .sampleCode - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in try """ # ``SideKit/SideClass`` @@ -417,7 +417,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(sampleRenderReference.role, RenderMetadata.Role.sampleCode.rawValue) } - func testExternalTopicWithTopicImage() throws { + func testExternalTopicWithTopicImage() async throws { let externalResolver = TestMultiResultExternalReferenceResolver() externalResolver.bundleID = "com.test.external" @@ -473,7 +473,7 @@ class ExternalReferenceResolverTests: XCTestCase { ), ] - let (_, bundle, context) = try testBundleAndContext(copying: "SampleBundle", excludingPaths: ["MySample.md", "MyLocalSample.md"], externalResolvers: [externalResolver.bundleID: externalResolver]) { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "SampleBundle", excludingPaths: ["MySample.md", "MyLocalSample.md"], externalResolvers: [externalResolver.bundleID: externalResolver]) { url in try """ # SomeSample @@ -585,7 +585,7 @@ class ExternalReferenceResolverTests: XCTestCase { } // Tests that external references are included in task groups, rdar://72119391 - func testResolveExternalReferenceInTaskGroups() throws { + func testResolveExternalReferenceInTaskGroups() async throws { let resolver = TestMultiResultExternalReferenceResolver() resolver.entitiesToReturn = [ "/article": .success(.init(referencePath: "/externally/resolved/path/to/article")), @@ -595,7 +595,7 @@ class ExternalReferenceResolverTests: XCTestCase { "/externally/resolved/path/to/article2": .success(.init(referencePath: "/externally/resolved/path/to/article2")), ] - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [ + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [ "com.external.testbundle" : resolver ]) { url in // Add external links to the MyKit Topics. @@ -625,9 +625,9 @@ class ExternalReferenceResolverTests: XCTestCase { } // Tests that external references are resolved in tutorial content - func testResolveExternalReferenceInTutorials() throws { + func testResolveExternalReferenceInTutorials() async throws { let resolver = TestExternalReferenceResolver() - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.bundle": resolver, "com.external.testbundle": resolver], configureBundle: { (bundleURL) in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.bundle": resolver, "com.external.testbundle": resolver], configureBundle: { (bundleURL) in // Replace TestTutorial.tutorial with a copy that includes a bunch of external links try FileManager.default.removeItem(at: bundleURL.appendingPathComponent("TestTutorial.tutorial")) try FileManager.default.copyItem( @@ -671,7 +671,7 @@ class ExternalReferenceResolverTests: XCTestCase { } // Tests that external references are included in task groups, rdar://72119391 - func testExternalResolverIsNotPassedReferencesItDidNotResolve() throws { + func testExternalResolverIsNotPassedReferencesItDidNotResolve() async throws { final class CallCountingReferenceResolver: ExternalDocumentationSource { var referencesAskedToResolve: Set = [] @@ -716,7 +716,7 @@ class ExternalReferenceResolverTests: XCTestCase { // Copy the test bundle and add external links to the MyKit See Also. // We're using a See Also group, because external links aren't rendered in Topics groups. - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : resolver]) { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : resolver]) { url in try """ # ``MyKit`` MyKit module root symbol @@ -793,7 +793,7 @@ class ExternalReferenceResolverTests: XCTestCase { } /// Tests that the external resolving handles correctly fragments in URLs. - func testExternalReferenceWithFragment() throws { + func testExternalReferenceWithFragment() async throws { // Configure an external resolver let resolver = TestExternalReferenceResolver() @@ -802,7 +802,7 @@ class ExternalReferenceResolverTests: XCTestCase { resolver.expectedFragment = "67890" // Prepare a test bundle - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : resolver], externalSymbolResolver: nil, configureBundle: { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : resolver], externalSymbolResolver: nil, configureBundle: { url in // Add external link with fragment let myClassMDURL = url.appendingPathComponent("documentation").appendingPathComponent("myclass.md") try String(contentsOf: myClassMDURL) @@ -829,7 +829,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(linkReference.absoluteString, "doc://com.external.testbundle/externally/resolved/path#67890") } - func testExternalArticlesAreIncludedInAllVariantsTopicsSection() throws { + func testExternalArticlesAreIncludedInAllVariantsTopicsSection() async throws { let externalResolver = TestMultiResultExternalReferenceResolver() externalResolver.bundleID = "com.test.external" @@ -869,7 +869,7 @@ class ExternalReferenceResolverTests: XCTestCase { ) ) - let (_, bundle, context) = try testBundleAndContext( + let (_, bundle, context) = try await testBundleAndContext( copying: "MixedLanguageFramework", externalResolvers: [externalResolver.bundleID: externalResolver] ) { url in @@ -921,7 +921,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertFalse(objCTopicIDs.contains("doc://com.test.external/path/to/external/swiftSymbol")) } - func testDeprecationSummaryWithExternalLink() throws { + func testDeprecationSummaryWithExternalLink() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( moduleName: "ModuleName", @@ -963,7 +963,7 @@ class ExternalReferenceResolverTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = [resolver.bundleID: resolver] - let (bundle, context) = try loadBundle(catalog: catalog, configuration: configuration) + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) XCTAssert(context.problems.isEmpty, "Unexpected problems:\n\(context.problems.map(\.diagnostic.summary).joined(separator: "\n"))") @@ -986,7 +986,7 @@ class ExternalReferenceResolverTests: XCTestCase { } } - func testExternalLinkInGeneratedSeeAlso() throws { + func testExternalLinkInGeneratedSeeAlso() async throws { let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "Root.md", utf8Content: """ # Root @@ -1020,7 +1020,7 @@ class ExternalReferenceResolverTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = [resolver.bundleID: resolver] - let (bundle, context) = try loadBundle(catalog: catalog, configuration: configuration) + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -1067,7 +1067,7 @@ class ExternalReferenceResolverTests: XCTestCase { } } - func testExternalLinkInAuthoredSeeAlso() throws { + func testExternalLinkInAuthoredSeeAlso() async throws { let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "Root.md", utf8Content: """ # Root @@ -1088,7 +1088,7 @@ class ExternalReferenceResolverTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = [resolver.bundleID: resolver] - let (bundle, context) = try loadBundle(catalog: catalog, configuration: configuration) + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -1107,7 +1107,7 @@ class ExternalReferenceResolverTests: XCTestCase { ]) } - func testParametersWithExternalLink() throws { + func testParametersWithExternalLink() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.swift.symbols.json", content: makeSymbolGraph( @@ -1174,7 +1174,7 @@ class ExternalReferenceResolverTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = [resolver.bundleID: resolver] - let (bundle, context) = try loadBundle(catalog: catalog, configuration: configuration) + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) XCTAssert(context.problems.isEmpty, "Unexpected problems:\n\(context.problems.map(\.diagnostic.summary).joined(separator: "\n"))") @@ -1203,9 +1203,9 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(externalLinks.count, 4, "Did not resolve the 4 expected external links.") } - func exampleDocumentation(copying bundleName: String, documentationExtension: TextFile, path: String, file: StaticString = #filePath, line: UInt = #line) throws -> Symbol { + func exampleDocumentation(copying bundleName: String, documentationExtension: TextFile, path: String, file: StaticString = #filePath, line: UInt = #line) async throws -> Symbol { let externalResolver = TestExternalReferenceResolver() - let (_, bundle, context) = try testBundleAndContext( + let (_, bundle, context) = try await testBundleAndContext( copying: bundleName, externalResolvers: [externalResolver.bundleID: externalResolver] ) { url in @@ -1228,7 +1228,7 @@ class ExternalReferenceResolverTests: XCTestCase { return symbol } - func testDictionaryKeysWithExternalLink() throws { + func testDictionaryKeysWithExternalLink() async throws { // Create some example documentation using the symbol graph file located under // Tests/SwiftDocCTests/Test Bundles/DictionaryData.docc, and the following @@ -1248,7 +1248,7 @@ class ExternalReferenceResolverTests: XCTestCase { - monthOfBirth: 1 - genre: Classic Rock """) - let symbol = try exampleDocumentation( + let symbol = try await exampleDocumentation( copying: "DictionaryData", documentationExtension: documentationExtension, path: "/documentation/DictionaryData/Artist" @@ -1280,7 +1280,7 @@ class ExternalReferenceResolverTests: XCTestCase { // Create some example documentation using the symbol graph file located under // Tests/SwiftDocCTests/Test Bundles/HTTPRequests.docc, and the following // documentation extension markup. - func exampleRESTDocumentation(file: StaticString = #filePath, line: UInt = #line) throws -> Symbol { + func exampleRESTDocumentation(file: StaticString = #filePath, line: UInt = #line) async throws -> Symbol { let documentationExtension = TextFile( name: "GetArtist.md", utf8Content: """ @@ -1307,7 +1307,7 @@ class ExternalReferenceResolverTests: XCTestCase { - 204: Another response with a link: . - 887: Bad value. """) - return try exampleDocumentation( + return try await exampleDocumentation( copying: "HTTPRequests", documentationExtension: documentationExtension, path: "/documentation/HTTPRequests/Get_Artist", @@ -1317,11 +1317,11 @@ class ExternalReferenceResolverTests: XCTestCase { } - func testHTTPParametersWithExternalLink() throws { + func testHTTPParametersWithExternalLink() async throws { // Get the variant of the example symbol that has no interface language, meaning it was // generated by the markup above. - let symbol = try exampleRESTDocumentation() + let symbol = try await exampleRESTDocumentation() let section = try XCTUnwrap(symbol.httpParametersSection) XCTAssertEqual(section.parameters.count, 3) @@ -1343,11 +1343,11 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(externalLinkCount, 2, "Did not resolve the 2 expected external links.") } - func testHTTPBodyWithExternalLink() throws { + func testHTTPBodyWithExternalLink() async throws { // Get the variant of the example symbol that has no interface language, meaning it was // generated by the markup above. - let symbol = try exampleRESTDocumentation() + let symbol = try await exampleRESTDocumentation() let section = try XCTUnwrap(symbol.httpBodySection) // Check that the two keys with external links in the markup above were found @@ -1356,11 +1356,11 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(value, "Simple body with a link: .") } - func testHTTPBodyParametersWithExternalLink() throws { + func testHTTPBodyParametersWithExternalLink() async throws { // Get the variant of the example symbol that has no interface language, meaning it was // generated by the markup above. - let symbol = try exampleRESTDocumentation() + let symbol = try await exampleRESTDocumentation() let section = try XCTUnwrap(symbol.httpBodySection) XCTAssertEqual(section.body.parameters.count, 3) @@ -1383,11 +1383,11 @@ class ExternalReferenceResolverTests: XCTestCase { } - func testHTTPResponsesWithExternalLink() throws { + func testHTTPResponsesWithExternalLink() async throws { // Get the variant of the example symbol that has no interface language, meaning it was // generated by the markup above. - let symbol = try exampleRESTDocumentation() + let symbol = try await exampleRESTDocumentation() let section = try XCTUnwrap(symbol.httpResponsesSection) XCTAssertEqual(section.responses.count, 3) @@ -1409,7 +1409,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(externalLinkCount, 2, "Did not resolve the 2 expected external links.") } - func testPossibleValuesWithExternalLink() throws { + func testPossibleValuesWithExternalLink() async throws { // Create some example documentation using the symbol graph file located under // Tests/SwiftDocCTests/Test Bundles/DictionaryData.docc, and the following @@ -1427,7 +1427,7 @@ class ExternalReferenceResolverTests: XCTestCase { - Classic Rock: Something about classic rock with a link: . - Folk: Something about folk music with a link: . """) - let symbol = try exampleDocumentation( + let symbol = try await exampleDocumentation( copying: "DictionaryData", documentationExtension: documentationExtension, path: "/documentation/DictionaryData/Genre" diff --git a/Tests/SwiftDocCTests/Infrastructure/InheritIntroducedAvailabilityTests.swift b/Tests/SwiftDocCTests/Infrastructure/InheritIntroducedAvailabilityTests.swift index fdf3147144..77bf00bc07 100644 --- a/Tests/SwiftDocCTests/Infrastructure/InheritIntroducedAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/InheritIntroducedAvailabilityTests.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 @@ -37,9 +37,9 @@ class InheritIntroducedAvailabilityTests: XCTestCase { var testBundle: DocumentationBundle! var context: DocumentationContext! - override func setUpWithError() throws { - try super.setUpWithError() - (testBundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + override func setUp() async throws { + try await super.setUp() + (testBundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") } override func tearDown() { diff --git a/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift b/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift index a3598257b7..37157b55c9 100644 --- a/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.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 @@ -13,7 +13,7 @@ import XCTest import SwiftDocCTestUtilities class NodeTagsTests: XCTestCase { - func testSPIMetadata() throws { + func testSPIMetadata() async throws { let spiSGURL = Bundle.module.url( forResource: "SPI.symbols", withExtension: "json", subdirectory: "Test Resources")! @@ -24,7 +24,7 @@ class NodeTagsTests: XCTestCase { let tempURL = try createTemporaryDirectory().appendingPathComponent("unit-tests.docc") try bundleFolder.write(to: tempURL) - let (_, bundle, context) = try loadBundle(from: tempURL) + let (_, bundle, context) = try await loadBundle(from: tempURL) // Verify that `Test` is marked as SPI. let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Minimal_docs/Test", sourceLanguage: .swift) diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyBasedLinkResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyBasedLinkResolverTests.swift index b546fe6160..4c82a540a7 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyBasedLinkResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyBasedLinkResolverTests.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 @@ -13,10 +13,10 @@ import XCTest class PathHierarchyBasedLinkResolverTests: XCTestCase { - func testOverloadedSymbolsWithOverloadGroups() throws { + func testOverloadedSymbolsWithOverloadGroups() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let (_, context) = try testBundleAndContext(named: "OverloadedSymbols") + let (_, context) = try await testBundleAndContext(named: "OverloadedSymbols") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) // Returns nil for all non-overload groups diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index 4c9c1b00dd..2f3a3014ed 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -16,8 +16,8 @@ import Markdown class PathHierarchyTests: XCTestCase { - func testFindingUnambiguousAbsolutePaths() throws { - let (_, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testFindingUnambiguousAbsolutePaths() async throws { + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("/MixedFramework", in: tree, asSymbolID: "MixedFramework") @@ -286,10 +286,10 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("/MixedFramework/MyTypedObjectiveCExtensibleEnumSecond", in: tree, asSymbolID: "c:@MyTypedObjectiveCExtensibleEnumSecond") } - func testAmbiguousPaths() throws { + func testAmbiguousPaths() async throws { enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled) - let (_, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let tree = context.linkResolver.localResolver.pathHierarchy // Symbol name not found. Suggestions only include module names (search is not relative to a known page) @@ -574,8 +574,8 @@ class PathHierarchyTests: XCTestCase { try assertPathNotFound("MixedFramework/MyTypedObjectiveCExtensibleEnum-typealias/second", in: tree) } - func testRedundantKindDisambiguation() throws { - let (_, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testRedundantKindDisambiguation() async throws { + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("/MixedFramework-module", in: tree, asSymbolID: "MixedFramework") @@ -626,8 +626,8 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("/MixedFramework/myTopLevelVariable-var", in: tree, asSymbolID: "s:14MixedFramework18myTopLevelVariableSbvp") } - func testBothRedundantDisambiguations() throws { - let (_, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testBothRedundantDisambiguations() async throws { + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("/MixedFramework-module-9r7pl", in: tree, asSymbolID: "MixedFramework") @@ -678,7 +678,7 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("/MixedFramework-module-9r7pl/myTopLevelVariable-var-520ez", in: tree, asSymbolID: "s:14MixedFramework18myTopLevelVariableSbvp") } - func testDefaultImplementationWithCollidingTargetSymbol() throws { + func testDefaultImplementationWithCollidingTargetSymbol() async throws { // ---- Inner // public protocol Something { @@ -691,7 +691,7 @@ class PathHierarchyTests: XCTestCase { // ---- Outer // @_exported import Inner // public typealias Something = Inner.Something - let (_, context) = try testBundleAndContext(named: "DefaultImplementationsWithExportedImport") + let (_, context) = try await testBundleAndContext(named: "DefaultImplementationsWithExportedImport") let tree = context.linkResolver.localResolver.pathHierarchy // The @_export imported protocol can be found @@ -712,8 +712,8 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("DefaultImplementationsWithExportedImport/Something-protocol/doSomething()-scj9", in: tree, asSymbolID: "s:5Inner9SomethingPAAE02doB0yyF") } - func testDisambiguatedPaths() throws { - let (_, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testDisambiguatedPaths() async throws { + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -849,8 +849,8 @@ class PathHierarchyTests: XCTestCase { "/MixedFramework/CollisionsWithDifferentSubscriptArguments/subscript(_:)-757cj") } - func testDisambiguatedOperatorPaths() throws { - let (_, context) = try testBundleAndContext(named: "InheritedOperators") + func testDisambiguatedOperatorPaths() async throws { + let (_, context) = try await testBundleAndContext(named: "InheritedOperators") let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -919,8 +919,8 @@ class PathHierarchyTests: XCTestCase { } - func testFindingRelativePaths() throws { - let (_, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testFindingRelativePaths() async throws { + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let tree = context.linkResolver.localResolver.pathHierarchy let moduleID = try tree.find(path: "/MixedFramework", onlyFindSymbols: true) @@ -1091,8 +1091,8 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(try tree.findSymbol(path: "second", parent: myTypedExtensibleEnumID).identifier.precise, "c:@MyTypedObjectiveCExtensibleEnumSecond") } - func testPathWithDocumentationPrefix() throws { - let (_, context) = try testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") + func testPathWithDocumentationPrefix() async throws { + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFrameworkWithLanguageRefinements") let tree = context.linkResolver.localResolver.pathHierarchy let moduleID = try tree.find(path: "/MixedFramework", onlyFindSymbols: true) @@ -1106,8 +1106,8 @@ class PathHierarchyTests: XCTestCase { assertParsedPathComponents("/documentation/MixedFramework/MyEnum", [("documentation", nil), ("MixedFramework", nil), ("MyEnum", nil)]) } - func testUnrealisticMixedTestCatalog() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testUnrealisticMixedTestCatalog() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let linkResolver = try XCTUnwrap(context.linkResolver.localResolver) let tree = try XCTUnwrap(linkResolver.pathHierarchy) @@ -1172,8 +1172,8 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(tree.lookup[symbolPageTaskGroupID]!.name, "Task-Group-Exercising-Symbol-Links") } - func testMixedLanguageFramework() throws { - let (_, context) = try testBundleAndContext(named: "MixedLanguageFramework") + func testMixedLanguageFramework() async throws { + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFramework") let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("MixedLanguageFramework/Bar/myStringFunction(_:)", in: tree, asSymbolID: "c:objc(cs)Bar(cm)myStringFunction:error:") @@ -1226,8 +1226,8 @@ class PathHierarchyTests: XCTestCase { "/MixedLanguageFramework/SwiftOnlyStruct/tada()") } - func testArticleAndSymbolCollisions() throws { - let (_, _, context) = try testBundleAndContext(copying: "MixedLanguageFramework") { url in + func testArticleAndSymbolCollisions() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "MixedLanguageFramework") { url in try """ # An article @@ -1243,8 +1243,8 @@ class PathHierarchyTests: XCTestCase { XCTAssertNil(articleNode.symbol, "General documentation link find the article") } - func testArticleSelfAnchorLinks() throws { - let (_, _, context) = try testBundleAndContext(copying: "MixedLanguageFramework") { url in + func testArticleSelfAnchorLinks() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "MixedLanguageFramework") { url in try """ # ArticleWithHeading @@ -1266,8 +1266,8 @@ class PathHierarchyTests: XCTestCase { XCTAssertNotNil(anchorLinkNode) } - func testOverloadedSymbols() throws { - let (_, context) = try testBundleAndContext(named: "OverloadedSymbols") + func testOverloadedSymbols() async throws { + let (_, context) = try await testBundleAndContext(named: "OverloadedSymbols") let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -1328,10 +1328,10 @@ class PathHierarchyTests: XCTestCase { ]) } - func testOverloadedSymbolsWithOverloadGroups() throws { + func testOverloadedSymbolsWithOverloadGroups() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let (_, context) = try testBundleAndContext(named: "OverloadedSymbols") + let (_, context) = try await testBundleAndContext(named: "OverloadedSymbols") let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -1931,7 +1931,7 @@ class PathHierarchyTests: XCTestCase { } } - func testParameterDisambiguationWithAnyType() throws { + func testParameterDisambiguationWithAnyType() async throws { // Create two overloads with different parameter types let parameterTypes: [SymbolGraph.Symbol.DeclarationFragments.Fragment] = [ // Any (swift) @@ -1956,7 +1956,7 @@ class PathHierarchyTests: XCTestCase { )) })), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy XCTAssert(context.problems.isEmpty, "Unexpected problems \(context.problems.map(\.diagnostic.summary))") @@ -1988,7 +1988,7 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("doSomething(with:)-9kd0v", in: tree, asSymbolID: "some-function-id-AnyObject") } - func testReturnDisambiguationWithAnyType() throws { + func testReturnDisambiguationWithAnyType() async throws { // Create two overloads with different return types let returnTypes: [SymbolGraph.Symbol.DeclarationFragments.Fragment] = [ // Any (swift) @@ -2005,7 +2005,7 @@ class PathHierarchyTests: XCTestCase { )) })), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy XCTAssert(context.problems.isEmpty, "Unexpected problems \(context.problems.map(\.diagnostic.summary))") @@ -2037,10 +2037,10 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("doSomething()-9kd0v", in: tree, asSymbolID: "some-function-id-AnyObject") } - func testOverloadGroupSymbolsResolveLinksWithoutHash() throws { + func testOverloadGroupSymbolsResolveLinksWithoutHash() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let (_, context) = try testBundleAndContext(named: "OverloadedSymbols") + let (_, context) = try await testBundleAndContext(named: "OverloadedSymbols") let tree = context.linkResolver.localResolver.pathHierarchy // The enum case should continue to resolve by kind, since it has no hash collision @@ -2056,9 +2056,9 @@ class PathHierarchyTests: XCTestCase { } - func testAmbiguousPathsForOverloadedGroupSymbols() throws { + func testAmbiguousPathsForOverloadedGroupSymbols() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let (_, context) = try testBundleAndContext(named: "OverloadedSymbols") + let (_, context) = try await testBundleAndContext(named: "OverloadedSymbols") let tree = context.linkResolver.localResolver.pathHierarchy try assertPathRaisesErrorMessage("/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)-abc123", in: tree, context: context, expectedErrorMessage: """ 'abc123' isn't a disambiguation for 'fourthTestMemberName(test:)' at '/ShapeKit/OverloadedProtocol' @@ -2075,7 +2075,7 @@ class PathHierarchyTests: XCTestCase { } } - func testDoesNotSuggestBundleNameForSymbolLink() throws { + func testDoesNotSuggestBundleNameForSymbolLink() async throws { let exampleDocumentation = Folder(name: "Something.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName")), @@ -2089,7 +2089,7 @@ class PathHierarchyTests: XCTestCase { """), ]) let catalogURL = try exampleDocumentation.write(inside: createTemporaryDirectory()) - let (_, _, context) = try loadBundle(from: catalogURL) + let (_, _, context) = try await loadBundle(from: catalogURL) let tree = context.linkResolver.localResolver.pathHierarchy // This link is intentionally misspelled @@ -2101,8 +2101,8 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(linkProblem.possibleSolutions.map(\.summary), ["Replace 'ModuleNaem' with 'ModuleName'"]) } - func testSymbolsWithSameNameAsModule() throws { - let (_, context) = try testBundleAndContext(named: "SymbolsWithSameNameAsModule") + func testSymbolsWithSameNameAsModule() async throws { + let (_, context) = try await testBundleAndContext(named: "SymbolsWithSameNameAsModule") let tree = context.linkResolver.localResolver.pathHierarchy // /* in a module named "Something "*/ @@ -2148,7 +2148,7 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(try tree.findSymbol(path: "Something/second", parent: topLevelSymbolID).identifier.precise, "s:9SomethingAAV6secondSivp") } - func testSymbolsWithSameNameAsExtendedModule() throws { + func testSymbolsWithSameNameAsExtendedModule() async throws { // ---- Inner // public struct InnerStruct {} // public class InnerClass {} @@ -2163,7 +2163,7 @@ class PathHierarchyTests: XCTestCase { // public extension InnerClass { // func something() {} // } - let (_, context) = try testBundleAndContext(named: "ShadowExtendedModuleWithLocalSymbol") + let (_, context) = try await testBundleAndContext(named: "ShadowExtendedModuleWithLocalSymbol") let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("Outer/Inner", in: tree, collisions: [ @@ -2191,7 +2191,7 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("Inner/InnerClass/something()", in: tree, asSymbolID: "s:5Inner0A5ClassC5OuterE9somethingyyF") } - func testExtensionSymbolsWithSameNameAsExtendedModule() throws { + func testExtensionSymbolsWithSameNameAsExtendedModule() async throws { // ---- ExtendedModule // public struct SomeStruct { // public struct SomeNestedStruct {} @@ -2238,7 +2238,7 @@ class PathHierarchyTests: XCTestCase { ), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -2263,7 +2263,7 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("ExtendedModule/SomeStruct/SomeNestedStruct/doSomething()", in: tree, asSymbolID: extendedMethodSymbolID) } - func testContinuesSearchingIfNonSymbolMatchesSymbolLink() throws { + func testContinuesSearchingIfNonSymbolMatchesSymbolLink() async throws { let exampleDocumentation = Folder(name: "CatalogName.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ makeSymbol(id: "some-class-id", kind: .class, pathComponents: ["SomeClass"]) @@ -2282,7 +2282,7 @@ class PathHierarchyTests: XCTestCase { """), ]) let catalogURL = try exampleDocumentation.write(inside: createTemporaryDirectory()) - let (_, _, context) = try loadBundle(from: catalogURL) + let (_, _, context) = try await loadBundle(from: catalogURL) let tree = context.linkResolver.localResolver.pathHierarchy XCTAssert(context.problems.isEmpty, "Unexpected problems \(context.problems.map(\.diagnostic.summary))") @@ -2306,7 +2306,7 @@ class PathHierarchyTests: XCTestCase { } } - func testDiagnosticDoesNotSuggestReplacingPartOfSymbolName() throws { + func testDiagnosticDoesNotSuggestReplacingPartOfSymbolName() async throws { let exampleDocumentation = Folder(name: "CatalogName.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ makeSymbol(id: "some-class-id-1", kind: .class, pathComponents: ["SomeClass-(Something)"]), @@ -2314,7 +2314,7 @@ class PathHierarchyTests: XCTestCase { ])), ]) let catalogURL = try exampleDocumentation.write(inside: createTemporaryDirectory()) - let (_, _, context) = try loadBundle(from: catalogURL) + let (_, _, context) = try await loadBundle(from: catalogURL) let tree = context.linkResolver.localResolver.pathHierarchy XCTAssert(context.problems.isEmpty, "Unexpected problems \(context.problems.map(\.diagnostic.summary))") @@ -2337,8 +2337,8 @@ class PathHierarchyTests: XCTestCase { } } - func testSnippets() throws { - let (_, context) = try testBundleAndContext(named: "Snippets") + func testSnippets() async throws { + let (_, context) = try await testBundleAndContext(named: "Snippets") let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("/Snippets/Snippets/MySnippet", in: tree, asSymbolID: "$snippet__Test.Snippets.MySnippet") @@ -2362,8 +2362,8 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(try tree.findSymbol(path: "/Snippets/Snippets/MySnippet", parent: sliceArticleID).identifier.precise, "$snippet__Test.Snippets.MySnippet") } - func testInheritedOperators() throws { - let (_, context) = try testBundleAndContext(named: "InheritedOperators") + func testInheritedOperators() async throws { + let (_, context) = try await testBundleAndContext(named: "InheritedOperators") let tree = context.linkResolver.localResolver.pathHierarchy // public struct MyNumber: SignedNumeric, Comparable, Equatable, Hashable { @@ -2464,8 +2464,8 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(repeatedHumanReadablePaths.keys.sorted(), [], "Every path should be unique") } - func testSameNameForSymbolAndContainer() throws { - let (_, context) = try testBundleAndContext(named: "BundleWithSameNameForSymbolAndContainer") + func testSameNameForSymbolAndContainer() async throws { + let (_, context) = try await testBundleAndContext(named: "BundleWithSameNameForSymbolAndContainer") let tree = context.linkResolver.localResolver.pathHierarchy // public struct Something { @@ -2498,8 +2498,8 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(try tree.findSymbol(path: "Something/SomethingElse", parent: moduleID).absolutePath, "Something/SomethingElse") } - func testPrefersNonSymbolsWhenOnlyFindSymbolIsFalse() throws { - let (_, _, context) = try testBundleAndContext(copying: "SymbolsWithSameNameAsModule") { url in + func testPrefersNonSymbolsWhenOnlyFindSymbolIsFalse() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "SymbolsWithSameNameAsModule") { url in // This bundle has a top-level struct named "Wrapper". Adding an article named "Wrapper.md" introduces a possibility for a link collision try """ # An article @@ -2530,7 +2530,7 @@ class PathHierarchyTests: XCTestCase { XCTAssertNotNil(symbolMatch.symbol, "Should have found the struct") } - func testOneSymbolPathsWithKnownDisambiguation() throws { + func testOneSymbolPathsWithKnownDisambiguation() async throws { let exampleDocumentation = Folder(name: "MyKit.docc", content: [ CopyOfFile(original: Bundle.module.url(forResource: "mykit-one-symbol.symbols", withExtension: "json", subdirectory: "Test Resources")!), InfoPlist(displayName: "MyKit", identifier: "com.test.MyKit"), @@ -2539,7 +2539,7 @@ class PathHierarchyTests: XCTestCase { let bundleURL = try exampleDocumentation.write(inside: tempURL) do { - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("/MyKit/MyClass/myFunction()", in: tree, asSymbolID: "s:5MyKit0A5ClassC10myFunctionyyF") @@ -2558,7 +2558,7 @@ class PathHierarchyTests: XCTestCase { configuration.convertServiceConfiguration.knownDisambiguatedSymbolPathComponents = [ "s:5MyKit0A5ClassC10myFunctionyyF": ["MyClass-swift.class", "myFunction()"] ] - let (_, _, context) = try loadBundle(from: bundleURL, configuration: configuration) + let (_, _, context) = try await loadBundle(from: bundleURL, configuration: configuration) let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("/MyKit/MyClass-swift.class/myFunction()", in: tree, asSymbolID: "s:5MyKit0A5ClassC10myFunctionyyF") @@ -2577,7 +2577,7 @@ class PathHierarchyTests: XCTestCase { configuration.convertServiceConfiguration.knownDisambiguatedSymbolPathComponents = [ "s:5MyKit0A5ClassC10myFunctionyyF": ["MyClass-swift.class-hash", "myFunction()"] ] - let (_, _, context) = try loadBundle(from: bundleURL, configuration: configuration) + let (_, _, context) = try await loadBundle(from: bundleURL, configuration: configuration) let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("/MyKit/MyClass-swift.class-hash/myFunction()", in: tree, asSymbolID: "s:5MyKit0A5ClassC10myFunctionyyF") @@ -2593,7 +2593,7 @@ class PathHierarchyTests: XCTestCase { } } - func testArticleWithDisambiguationLookingName() throws { + func testArticleWithDisambiguationLookingName() async throws { let exampleDocumentation = Folder(name: "MyKit.docc", content: [ CopyOfFile(original: Bundle.module.url(forResource: "BaseKit.symbols", withExtension: "json", subdirectory: "Test Resources")!), InfoPlist(displayName: "BaseKit", identifier: "com.test.BaseKit"), @@ -2617,7 +2617,7 @@ class PathHierarchyTests: XCTestCase { let bundleURL = try exampleDocumentation.write(inside: tempURL) do { - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map { DiagnosticConsoleWriter.formattedDescription(for: $0) })") let tree = context.linkResolver.localResolver.pathHierarchy @@ -2633,8 +2633,8 @@ class PathHierarchyTests: XCTestCase { } } - func testGeometricalShapes() throws { - let (_, context) = try testBundleAndContext(named: "GeometricalShapes") + func testGeometricalShapes() async throws { + let (_, context) = try await testBundleAndContext(named: "GeometricalShapes") let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths().values.sorted() @@ -2657,7 +2657,7 @@ class PathHierarchyTests: XCTestCase { ]) } - func testPartialSymbolGraphPaths() throws { + func testPartialSymbolGraphPaths() async throws { let symbolPaths = [ ["A", "B", "C"], ["A", "B", "C2"], @@ -2675,7 +2675,7 @@ class PathHierarchyTests: XCTestCase { let tempURL = try createTemporaryDirectory() let bundleURL = try exampleDocumentation.write(inside: tempURL) - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathNotFound("/Module/A", in: tree) @@ -2697,7 +2697,7 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(paths["X.Y2.Z.W"], "/Module/X/Y2/Z/W") } - func testMixedLanguageSymbolWithSameKindAndAddedMemberFromExtendingModule() throws { + func testMixedLanguageSymbolWithSameKindAndAddedMemberFromExtendingModule() async throws { let containerID = "some-container-symbol-id" let memberID = "some-member-symbol-id" @@ -2731,7 +2731,7 @@ class PathHierarchyTests: XCTestCase { ]) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -2739,7 +2739,7 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(paths[memberID], "/ModuleName/ContainerName/MemberName") } - func testMixedLanguageSymbolWithDifferentKindsAndAddedMemberFromExtendingModule() throws { + func testMixedLanguageSymbolWithDifferentKindsAndAddedMemberFromExtendingModule() async throws { let containerID = "some-container-symbol-id" let memberID = "some-member-symbol-id" @@ -2773,7 +2773,7 @@ class PathHierarchyTests: XCTestCase { ]) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -2781,7 +2781,7 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(paths[memberID], "/ModuleName/ContainerName/MemberName") } - func testLanguageRepresentationsWithDifferentCapitalization() throws { + func testLanguageRepresentationsWithDifferentCapitalization() async throws { let containerID = "some-container-symbol-id" let memberID = "some-member-symbol-id" @@ -2813,7 +2813,7 @@ class PathHierarchyTests: XCTestCase { ]) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -2821,7 +2821,7 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(paths[memberID], "/ModuleName/ContainerName/memberName") // The Swift spelling is preferred } - func testLanguageRepresentationsWithDifferentParentKinds() throws { + func testLanguageRepresentationsWithDifferentParentKinds() async throws { enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled) let containerID = "some-container-symbol-id" @@ -2861,7 +2861,7 @@ class PathHierarchyTests: XCTestCase { }) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let resolvedSwiftContainerID = try tree.find(path: "/ModuleName/ContainerName-struct", onlyFindSymbols: true) @@ -2905,7 +2905,7 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(paths[memberID], "/ModuleName/ContainerName/MemberName") } - func testMixedLanguageSymbolAndItsExtendingModuleWithDifferentContainerNames() throws { + func testMixedLanguageSymbolAndItsExtendingModuleWithDifferentContainerNames() async throws { let containerID = "some-container-symbol-id" let memberID = "some-member-symbol-id" @@ -2939,7 +2939,7 @@ class PathHierarchyTests: XCTestCase { ]) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -2947,7 +2947,7 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(paths[memberID], "/ModuleName/SwiftContainerName/MemberName") } - func testOptionalMemberUnderCorrectContainer() throws { + func testOptionalMemberUnderCorrectContainer() async throws { let containerID = "some-container-symbol-id" let otherID = "some-other-symbol-id" let memberID = "some-member-symbol-id" @@ -2966,7 +2966,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths(includeDisambiguationForUnambiguousChildren: true) @@ -2975,7 +2975,7 @@ class PathHierarchyTests: XCTestCase { XCTAssertEqual(paths[memberID], "/ModuleName/ContainerName-qwwf/MemberName1") } - func testLinkToTopicSection() throws { + func testLinkToTopicSection() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( moduleName: "ModuleName", @@ -3020,7 +3020,7 @@ class PathHierarchyTests: XCTestCase { """) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let moduleID = try tree.find(path: "/ModuleName", onlyFindSymbols: true) @@ -3071,7 +3071,7 @@ class PathHierarchyTests: XCTestCase { ], "The hierarchy only computes paths for symbols, not for headings or topic sections") } - func testModuleAndCollidingTechnologyRootHasPathsForItsSymbols() throws { + func testModuleAndCollidingTechnologyRootHasPathsForItsSymbols() async throws { let symbolID = "some-symbol-id" let catalog = Folder(name: "unit-test.docc", content: [ @@ -3094,14 +3094,14 @@ class PathHierarchyTests: XCTestCase { """) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths(includeDisambiguationForUnambiguousChildren: true) XCTAssertEqual(paths[symbolID], "/ModuleName/SymbolName") } - func testSameDefaultImplementationOnMultiplePlatforms() throws { + func testSameDefaultImplementationOnMultiplePlatforms() async throws { let protocolID = "some-protocol-symbol-id" let protocolRequirementID = "some-protocol-requirement-symbol-id" let defaultImplementationID = "some-default-implementation-symbol-id" @@ -3127,7 +3127,7 @@ class PathHierarchyTests: XCTestCase { makeSymbolGraphFile(platformName: "PlatformTwo"), ]) - let (_, context) = try loadBundle(catalog: multiPlatformCatalog) + let (_, context) = try await loadBundle(catalog: multiPlatformCatalog) let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -3138,21 +3138,21 @@ class PathHierarchyTests: XCTestCase { let singlePlatformCatalog = Folder(name: "unit-test.docc", content: [ makeSymbolGraphFile(platformName: "PlatformOne"), ]) - let (_, singlePlatformContext) = try loadBundle(catalog: singlePlatformCatalog) + let (_, singlePlatformContext) = try await loadBundle(catalog: singlePlatformCatalog) let singlePlatformPaths = singlePlatformContext.linkResolver.localResolver.pathHierarchy.caseInsensitiveDisambiguatedPaths() XCTAssertEqual(paths[protocolRequirementID], singlePlatformPaths[protocolRequirementID]) XCTAssertEqual(paths[defaultImplementationID], singlePlatformPaths[defaultImplementationID]) } - func testMultiPlatformModuleWithExtension() throws { - let (_, context) = try testBundleAndContext(named: "MultiPlatformModuleWithExtension") + func testMultiPlatformModuleWithExtension() async throws { + let (_, context) = try await testBundleAndContext(named: "MultiPlatformModuleWithExtension") let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("/MainModule/TopLevelProtocol/extensionMember(_:)", in: tree, asSymbolID: "extensionMember1") try assertFindsPath("/MainModule/TopLevelProtocol/InnerStruct/extensionMember(_:)", in: tree, asSymbolID: "extensionMember2") } - func testMissingRequiredMemberOfSymbolGraphRelationshipInOneLanguageAcrossManyPlatforms() throws { + func testMissingRequiredMemberOfSymbolGraphRelationshipInOneLanguageAcrossManyPlatforms() async throws { // We make a best-effort attempt to create a valid path hierarchy, even if the symbol graph inputs are not valid. // If the symbol graph files define container and member symbols without the required memberOf relationships we still try to match them up. @@ -3179,7 +3179,7 @@ class PathHierarchyTests: XCTestCase { }) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let container = try tree.findNode(path: "/ModuleName/ContainerName-struct", onlyFindSymbols: true) @@ -3198,7 +3198,7 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("/ModuleName/ContainerName", in: tree, asSymbolID: containerID) } - func testInvalidSymbolGraphWithNoMemberOfRelationshipsDesptiteDeepHierarchyAcrossManyPlatforms() throws { + func testInvalidSymbolGraphWithNoMemberOfRelationshipsDesptiteDeepHierarchyAcrossManyPlatforms() async throws { // We make a best-effort attempt to create a valid path hierarchy, even if the symbol graph inputs are not valid. // If the symbol graph files define a deep hierarchy, with the same symbol names but different symbol kinds across different, we try to match them up by language. @@ -3237,7 +3237,7 @@ class PathHierarchyTests: XCTestCase { }) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let swiftSpecificNode = try tree.findNode(path: "/ModuleName/OuterContainerName-struct/MiddleContainerName-struct/InnerContainerName-struct/swiftSpecificMember()", onlyFindSymbols: true, parent: nil) @@ -3286,7 +3286,7 @@ class PathHierarchyTests: XCTestCase { } } - func testMissingReferencedContainerSymbolOnSomePlatforms() throws { + func testMissingReferencedContainerSymbolOnSomePlatforms() async throws { // We make a best-effort attempt to create a valid path hierarchy, even if the symbol graph inputs are not valid. // If some platforms are missing the local container symbol from a `memberOf` relationship, but other platforms with the same relationship define that symbol, @@ -3321,14 +3321,14 @@ class PathHierarchyTests: XCTestCase { )) }) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertFindsPath("/ModuleName/ContainerName/memberName", in: tree, asSymbolID: memberID) try assertFindsPath("/ModuleName/ContainerName", in: tree, asSymbolID: containerID) } - func testMinimalTypeDisambiguationForClosureParameterWithVoidReturnType() throws { + func testMinimalTypeDisambiguationForClosureParameterWithVoidReturnType() async throws { // Create a `doSomething(with:and:)` function with a `String` parameter (same in every overload) and a `(TYPE)->()` closure parameter. func makeSymbolOverload(closureParameterType: SymbolGraph.Symbol.DeclarationFragments.Fragment) -> SymbolGraph.Symbol { makeSymbol( @@ -3371,7 +3371,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let link = "/ModuleName/doSomething(with:and:)" @@ -3395,7 +3395,7 @@ class PathHierarchyTests: XCTestCase { } } - func testMissingMemberOfAnonymousStructInsideUnion() throws { + func testMissingMemberOfAnonymousStructInsideUnion() async throws { let outerContainerID = "some-outer-container-symbol-id" let innerContainerID = "some-inner-container-symbol-id" let memberID = "some-member-symbol-id" @@ -3448,7 +3448,7 @@ class PathHierarchyTests: XCTestCase { }) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let paths = tree.caseInsensitiveDisambiguatedPaths() @@ -3465,8 +3465,8 @@ class PathHierarchyTests: XCTestCase { try assertPathNotFound("/ModuleName/Outer-struct/inner/member", in: tree) } - func testLinksToCxxOperators() throws { - let (_, context) = try testBundleAndContext(named: "CxxOperators") + func testLinksToCxxOperators() async throws { + let (_, context) = try await testBundleAndContext(named: "CxxOperators") let tree = context.linkResolver.localResolver.pathHierarchy // MyClass operator+() const; // unary plus @@ -3698,7 +3698,7 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("/CxxOperators/MyClass/operator,", in: tree, asSymbolID: "c:@S@MyClass@F@operator,#&$@S@MyClass#") } - func testMinimalTypeDisambiguation() throws { + func testMinimalTypeDisambiguation() async throws { enum DeclToken: ExpressibleByStringLiteral { case text(String) case internalParameter(String) @@ -3779,7 +3779,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(first:second:third:)", in: tree, collisions: [ @@ -3829,7 +3829,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(first:second:third:)", in: tree, collisions: [ @@ -3879,7 +3879,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(first:second:)", in: tree, collisions: [ @@ -3916,7 +3916,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(with:)", in: tree, collisions: [ @@ -3958,7 +3958,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(first:second:third:)", in: tree, collisions: [ @@ -4057,7 +4057,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(first:second:third:fourth:fifth:sixth:)", in: tree, collisions: [ @@ -4123,7 +4123,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(first:second:)", in: tree, collisions: [ @@ -4173,7 +4173,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(first:)", in: tree, collisions: [ @@ -4218,7 +4218,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(...)", in: tree, collisions: [ @@ -4245,7 +4245,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(...)", in: tree, collisions: [ @@ -4272,7 +4272,7 @@ class PathHierarchyTests: XCTestCase { )) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy try assertPathCollision("ModuleName/doSomething(...)", in: tree, collisions: [ @@ -4431,7 +4431,7 @@ class PathHierarchyTests: XCTestCase { assertParsedPathComponents("operator[]-(std::string&)->std::string&", [("operator[]", .typeSignature(parameterTypes: ["std::string&"], returnTypes: ["std::string&"]))]) } - func testResolveExternalLinkFromTechnologyRoot() throws { + func testResolveExternalLinkFromTechnologyRoot() async throws { enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled) let catalog = Folder(name: "unit-test.docc", content: [ @@ -4442,7 +4442,7 @@ class PathHierarchyTests: XCTestCase { """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let tree = context.linkResolver.localResolver.pathHierarchy let rootIdentifier = try XCTUnwrap(tree.modules.first?.identifier) diff --git a/Tests/SwiftDocCTests/Infrastructure/PresentationURLGeneratorTests.swift b/Tests/SwiftDocCTests/Infrastructure/PresentationURLGeneratorTests.swift index 7f36a61e9a..96adc54726 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PresentationURLGeneratorTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PresentationURLGeneratorTests.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 @@ -13,8 +13,8 @@ import Foundation @testable import SwiftDocC class PresentationURLGeneratorTests: XCTestCase { - func testInternalURLs() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testInternalURLs() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let generator = PresentationURLGenerator(context: context, baseURL: URL(string: "https://host:1024/webPrefix")!) // Test resolved tutorial reference diff --git a/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift index a2a06b12d3..3067845fe9 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift @@ -14,7 +14,7 @@ import Markdown import SymbolKit class ReferenceResolverTests: XCTestCase { - func testResolvesMediaForIntro() throws { + func testResolvesMediaForIntro() async throws { let source = """ @Intro( title: x) { @@ -24,7 +24,7 @@ class ReferenceResolverTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var problems = [Problem]() let intro = Intro(from: directive, source: nil, for: bundle, problems: &problems)! @@ -33,7 +33,7 @@ class ReferenceResolverTests: XCTestCase { XCTAssertEqual(resolver.problems.count, 1) } - func testResolvesMediaForContentAndMedia() throws { + func testResolvesMediaForContentAndMedia() async throws { let source = """ @ContentAndMedia { Blah blah. @@ -43,7 +43,7 @@ class ReferenceResolverTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var problems = [Problem]() let contentAndMedia = ContentAndMedia(from: directive, source: nil, for: bundle, problems: &problems)! @@ -52,7 +52,7 @@ class ReferenceResolverTests: XCTestCase { XCTAssertEqual(resolver.problems.count, 1) } - func testResolvesExternalLinks() throws { + func testResolvesExternalLinks() async throws { let source = """ @Intro(title: "Technology X") { Info at: . @@ -60,7 +60,7 @@ class ReferenceResolverTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var problems = [Problem]() let intro = Intro(from: directive, source: nil, for: bundle, problems: &problems)! @@ -77,8 +77,8 @@ class ReferenceResolverTests: XCTestCase { } // Tests all reference syntax formats to a child symbol - func testReferencesToChildFromFramework() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testReferencesToChildFromFramework() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit`` @@ -110,8 +110,8 @@ class ReferenceResolverTests: XCTestCase { } // Test relative paths to non-child symbol - func testReferencesToGrandChildFromFramework() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testReferencesToGrandChildFromFramework() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit`` @@ -136,8 +136,8 @@ class ReferenceResolverTests: XCTestCase { } // Test references to a sibling symbol - func testReferencesToSiblingFromFramework() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testReferencesToSiblingFromFramework() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit/SideClass/myFunction()`` @@ -162,8 +162,8 @@ class ReferenceResolverTests: XCTestCase { } // Test references to symbols in root paths - func testReferencesToTutorial() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testReferencesToTutorial() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit/SideClass/myFunction()`` @@ -188,8 +188,8 @@ class ReferenceResolverTests: XCTestCase { } // Test references to technology pages - func testReferencesToTechnologyPages() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testReferencesToTechnologyPages() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit/SideClass/myFunction()`` @@ -213,8 +213,8 @@ class ReferenceResolverTests: XCTestCase { } // Test external references - func testExternalReferencesConsiderBundleIdentifier() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testExternalReferencesConsiderBundleIdentifier() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit/SideClass/myFunction()`` @@ -306,7 +306,7 @@ class ReferenceResolverTests: XCTestCase { } } - func testRegisteredButUncuratedArticles() throws { + func testRegisteredButUncuratedArticles() async throws { var referencingArticleURL: URL! var uncuratedArticleFile: URL! @@ -321,7 +321,7 @@ class ReferenceResolverTests: XCTestCase { """ // TestBundle has more than one module, so automatic registration and curation won't happen - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in referencingArticleURL = root.appendingPathComponent("article.md") try source.write(to: referencingArticleURL, atomically: true, encoding: .utf8) @@ -345,8 +345,8 @@ class ReferenceResolverTests: XCTestCase { XCTAssertEqual(referencingFileDiagnostics.filter({ $0.identifier == "org.swift.docc.unresolvedTopicReference" }).count, 1) } - func testRelativeReferencesToExtensionSymbols() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "BundleWithRelativePathAmbiguity") { root in + func testRelativeReferencesToExtensionSymbols() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "BundleWithRelativePathAmbiguity") { root in // We don't want the external target to be part of the archive as that is not // officially supported yet. try FileManager.default.removeItem(at: root.appendingPathComponent("Dependency.symbols.json")) @@ -406,8 +406,8 @@ class ReferenceResolverTests: XCTestCase { } } - func testCuratedExtensionRemovesEmptyPage() throws { - let (bundle, context) = try testBundleAndContext(named: "ModuleWithSingleExtension") + func testCuratedExtensionRemovesEmptyPage() async throws { + let (bundle, context) = try await testBundleAndContext(named: "ModuleWithSingleExtension") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithSingleExtension", sourceLanguage: .swift)) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) @@ -424,8 +424,8 @@ class ReferenceResolverTests: XCTestCase { XCTAssertNoThrow(try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithSingleExtension/Swift/Array/asdf", sourceLanguage: .swift))) } - func testCuratedExtensionWithDanglingReference() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "ModuleWithSingleExtension") { root in + func testCuratedExtensionWithDanglingReference() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "ModuleWithSingleExtension") { root in let topLevelArticle = root.appendingPathComponent("ModuleWithSingleExtension.md") try FileManager.default.removeItem(at: topLevelArticle) @@ -464,8 +464,8 @@ class ReferenceResolverTests: XCTestCase { ]) } - func testCuratedExtensionWithDanglingReferenceToFragment() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "ModuleWithSingleExtension") { root in + func testCuratedExtensionWithDanglingReferenceToFragment() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "ModuleWithSingleExtension") { root in let topLevelArticle = root.appendingPathComponent("ModuleWithSingleExtension.md") try FileManager.default.removeItem(at: topLevelArticle) @@ -492,8 +492,8 @@ class ReferenceResolverTests: XCTestCase { XCTAssertFalse(context.knownPages.contains(where: { $0 == extendedStructure })) } - func testCuratedExtensionWithDocumentationExtension() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "ModuleWithSingleExtension") { root in + func testCuratedExtensionWithDocumentationExtension() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "ModuleWithSingleExtension") { root in let topLevelArticle = root.appendingPathComponent("ModuleWithSingleExtension.md") try FileManager.default.removeItem(at: topLevelArticle) @@ -521,8 +521,8 @@ class ReferenceResolverTests: XCTestCase { XCTAssert(context.knownPages.contains(where: { $0 == extendedStructure })) } - func testCuratedExtensionWithAdditionalConformance() throws { - let (bundle, context) = try testBundleAndContext(named: "ModuleWithConformanceAndExtension") + func testCuratedExtensionWithAdditionalConformance() async throws { + let (bundle, context) = try await testBundleAndContext(named: "ModuleWithConformanceAndExtension") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithConformanceAndExtension/MyProtocol", sourceLanguage: .swift)) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) @@ -537,8 +537,8 @@ class ReferenceResolverTests: XCTestCase { XCTAssert(renderReference is UnresolvedRenderReference) } - func testExtensionWithEmptyDeclarationFragments() throws { - let (bundle, context) = try testBundleAndContext(named: "ModuleWithEmptyDeclarationFragments") + func testExtensionWithEmptyDeclarationFragments() async throws { + let (bundle, context) = try await testBundleAndContext(named: "ModuleWithEmptyDeclarationFragments") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithEmptyDeclarationFragments", sourceLanguage: .swift)) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) @@ -549,7 +549,7 @@ class ReferenceResolverTests: XCTestCase { XCTAssertEqual(renderNode.topicSections.count, 0) } - func testUnresolvedTutorialReferenceIsWarning() throws { + func testUnresolvedTutorialReferenceIsWarning() async throws { let source = """ @Chapter(name: "SwiftUI Essentials") { @@ -560,7 +560,7 @@ class ReferenceResolverTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var problems = [Problem]() let chapter = try XCTUnwrap(Chapter(from: directive, source: nil, for: bundle, problems: &problems)) @@ -571,7 +571,7 @@ class ReferenceResolverTests: XCTestCase { XCTAssertEqual(resolver.problems.filter({ $0.diagnostic.severity == .warning }).count, 1) } - func testResolvesArticleContent() throws { + func testResolvesArticleContent() async throws { let source = """ # An Article @@ -580,7 +580,7 @@ class ReferenceResolverTests: XCTestCase { Discussion link to ``SideKit``. """ - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) let article = try XCTUnwrap(Article(markup: document, metadata: nil, redirects: nil, options: [:])) @@ -611,8 +611,8 @@ class ReferenceResolverTests: XCTestCase { XCTAssertTrue(foundSymbolDiscussionLink) } - func testForwardsSymbolPropertiesThatAreUnmodifiedDuringLinkResolution() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testForwardsSymbolPropertiesThatAreUnmodifiedDuringLinkResolution() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var resolver = ReferenceResolver(context: context, bundle: bundle) @@ -736,7 +736,7 @@ class ReferenceResolverTests: XCTestCase { } } - func testEmitsDiagnosticsForEachDocumentationChunk() throws { + func testEmitsDiagnosticsForEachDocumentationChunk() async throws { let moduleReference = ResolvedTopicReference(bundleID: "com.example.test", path: "/documentation/ModuleName", sourceLanguage: .swift) let reference = ResolvedTopicReference(bundleID: "com.example.test", path: "/documentation/ModuleName/Something", sourceLanguage: .swift) @@ -768,7 +768,7 @@ class ReferenceResolverTests: XCTestCase { mixins: [:] ) - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() let documentationExtensionContent = """ # ``Something`` diff --git a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/AbsoluteSymbolLinkTests.swift b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/AbsoluteSymbolLinkTests.swift index 009caf03fd..e6386f511f 100644 --- a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/AbsoluteSymbolLinkTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/AbsoluteSymbolLinkTests.swift @@ -225,8 +225,8 @@ class AbsoluteSymbolLinkTests: XCTestCase { } } - func testCompileSymbolGraphAndValidateLinks() throws { - let (_, _, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testCompileSymbolGraphAndValidateLinks() async throws { + let (_, _, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let expectedDescriptions = [ // doc://org.swift.docc.example/documentation/FillIntroduced: """ @@ -533,8 +533,8 @@ class AbsoluteSymbolLinkTests: XCTestCase { } } - func testCompileOverloadedSymbolGraphAndValidateLinks() throws { - let (_, _, context) = try testBundleAndContext(named: "OverloadedSymbols") + func testCompileOverloadedSymbolGraphAndValidateLinks() async throws { + let (_, _, context) = try await testBundleAndContext(named: "OverloadedSymbols") let expectedDescriptions = [ // doc://com.shapes.ShapeKit/documentation/ShapeKit: @@ -853,8 +853,8 @@ class AbsoluteSymbolLinkTests: XCTestCase { } } - func testLinkComponentStringConversion() throws { - let (_, _, context) = try testBundleAndContext(named: "OverloadedSymbols") + func testLinkComponentStringConversion() async throws { + let (_, _, context) = try await testBundleAndContext(named: "OverloadedSymbols") let bundlePathComponents = context.documentationCache.allReferences .flatMap(\.pathComponents) diff --git a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/DocCSymbolRepresentableTests.swift b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/DocCSymbolRepresentableTests.swift index d18bf2da17..614cf9c378 100644 --- a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/DocCSymbolRepresentableTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/DocCSymbolRepresentableTests.swift @@ -19,8 +19,8 @@ import SymbolKit // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") class DocCSymbolRepresentableTests: XCTestCase { - func testDisambiguatedByType() throws { - try performOverloadSymbolDisambiguationTest( + func testDisambiguatedByType() async throws { + try await performOverloadSymbolDisambiguationTest( correctLink: """ doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedStruct/secondTestMemberName-swift.property """, @@ -34,8 +34,8 @@ class DocCSymbolRepresentableTests: XCTestCase { ) } - func testOverloadedByCaseInsensitivity() throws { - try performOverloadSymbolDisambiguationTest( + func testOverloadedByCaseInsensitivity() async throws { + try await performOverloadSymbolDisambiguationTest( correctLink: """ doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedByCaseStruct/ThirdTestMemberName-5vyx9 """, @@ -48,8 +48,8 @@ class DocCSymbolRepresentableTests: XCTestCase { ) } - func testProtocolMemberWithUSRHash() throws { - try performOverloadSymbolDisambiguationTest( + func testProtocolMemberWithUSRHash() async throws { + try await performOverloadSymbolDisambiguationTest( correctLink: """ doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)-961zx """, @@ -62,8 +62,8 @@ class DocCSymbolRepresentableTests: XCTestCase { ) } - func testFunctionWithKindIdentifierAndUSRHash() throws { - try performOverloadSymbolDisambiguationTest( + func testFunctionWithKindIdentifierAndUSRHash() async throws { + try await performOverloadSymbolDisambiguationTest( correctLink: """ doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-14g8s """, @@ -77,8 +77,8 @@ class DocCSymbolRepresentableTests: XCTestCase { ) } - func testSymbolWithNoDisambiguation() throws { - try performOverloadSymbolDisambiguationTest( + func testSymbolWithNoDisambiguation() async throws { + try await performOverloadSymbolDisambiguationTest( correctLink: """ doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/firstMember """, @@ -93,8 +93,8 @@ class DocCSymbolRepresentableTests: XCTestCase { ) } - func testAmbiguousProtocolMember() throws { - try performOverloadSymbolDisambiguationTest( + func testAmbiguousProtocolMember() async throws { + try await performOverloadSymbolDisambiguationTest( correctLink: """ doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/firstMember """, @@ -114,9 +114,9 @@ class DocCSymbolRepresentableTests: XCTestCase { incorrectLinks: [String], symbolTitle: String, expectedNumberOfAmbiguousSymbols: Int - ) throws { + ) async throws { // Build a bundle with an unusual number of overloaded symbols - let (_, _, context) = try testBundleAndContext(named: "OverloadedSymbols") + let (_, _, context) = try await testBundleAndContext(named: "OverloadedSymbols") // Collect the overloaded symbols nodes from the built bundle let ambiguousSymbols = context.documentationCache @@ -169,8 +169,8 @@ class DocCSymbolRepresentableTests: XCTestCase { } } - func testLinkComponentInitialization() throws { - let (_, _, context) = try testBundleAndContext(named: "OverloadedSymbols") + func testLinkComponentInitialization() async throws { + let (_, _, context) = try await testBundleAndContext(named: "OverloadedSymbols") var count = 0 for (reference, documentationNode) in context.documentationCache { diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolBreadcrumbTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolBreadcrumbTests.swift index 0bfbd29004..d1d8f14fae 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolBreadcrumbTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolBreadcrumbTests.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 @@ -12,8 +12,8 @@ import XCTest @testable import SwiftDocC class SymbolBreadcrumbTests: XCTestCase { - func testLanguageSpecificBreadcrumbs() throws { - let (bundle, context) = try testBundleAndContext(named: "GeometricalShapes") + func testLanguageSpecificBreadcrumbs() async throws { + let (bundle, context) = try await testBundleAndContext(named: "GeometricalShapes") let resolver = try XCTUnwrap(context.linkResolver.localResolver) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -96,8 +96,8 @@ class SymbolBreadcrumbTests: XCTestCase { } } - func testMixedLanguageSpecificBreadcrumbs() throws { - let (bundle, context) = try testBundleAndContext(named: "MixedLanguageFramework") + func testMixedLanguageSpecificBreadcrumbs() async throws { + let (bundle, context) = try await testBundleAndContext(named: "MixedLanguageFramework") let resolver = try XCTUnwrap(context.linkResolver.localResolver) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift index b838e90664..93f2cd2435 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -14,8 +14,8 @@ import SymbolKit class SymbolDisambiguationTests: XCTestCase { - func testPathCollisionWithDifferentTypesInSameLanguage() throws { - let references = try disambiguatedReferencesForSymbols( + func testPathCollisionWithDifferentTypesInSameLanguage() async throws { + let references = try await disambiguatedReferencesForSymbols( swift: [ TestSymbolData(preciseID: "first", pathComponents: ["Something", "first"], kind: .property), TestSymbolData(preciseID: "second", pathComponents: ["Something", "First"], kind: .struct), @@ -36,8 +36,8 @@ class SymbolDisambiguationTests: XCTestCase { ) } - func testPathCollisionWithDifferentArgumentTypesInSameLanguage() throws { - let references = try disambiguatedReferencesForSymbols( + func testPathCollisionWithDifferentArgumentTypesInSameLanguage() async throws { + let references = try await disambiguatedReferencesForSymbols( swift: [ // The argument type isn't represented in the symbol name in the path components TestSymbolData(preciseID: "first", pathComponents: ["Something", "first(_:)"], kind: .method), @@ -59,8 +59,8 @@ class SymbolDisambiguationTests: XCTestCase { ) } - func testSameSymbolWithDifferentKindsInDifferentLanguages() throws { - let references = try disambiguatedReferencesForSymbols( + func testSameSymbolWithDifferentKindsInDifferentLanguages() async throws { + let references = try await disambiguatedReferencesForSymbols( swift: [ TestSymbolData(preciseID: "first", pathComponents: ["Something", "First"], kind: .enum), ], @@ -77,8 +77,8 @@ class SymbolDisambiguationTests: XCTestCase { ) } - func testDifferentSymbolsWithDifferentKindsInDifferentLanguages() throws { - let references = try disambiguatedReferencesForSymbols( + func testDifferentSymbolsWithDifferentKindsInDifferentLanguages() async throws { + let references = try await disambiguatedReferencesForSymbols( swift: [ TestSymbolData(preciseID: "first", pathComponents: ["Something", "First"], kind: .struct), ], @@ -98,8 +98,8 @@ class SymbolDisambiguationTests: XCTestCase { ) } - func testSameSymbolWithDifferentNamesInDifferentLanguages() throws { - let references = try disambiguatedReferencesForSymbols( + func testSameSymbolWithDifferentNamesInDifferentLanguages() async throws { + let references = try await disambiguatedReferencesForSymbols( swift: [ TestSymbolData(preciseID: "first", pathComponents: ["Something", "first(one:two:)"], kind: .method), ], @@ -116,8 +116,8 @@ class SymbolDisambiguationTests: XCTestCase { ) } - func testOneVariantOfMultiLanguageSymbolCollidesWithDifferentTypeSymbol() throws { - let references = try disambiguatedReferencesForSymbols( + func testOneVariantOfMultiLanguageSymbolCollidesWithDifferentTypeSymbol() async throws { + let references = try await disambiguatedReferencesForSymbols( swift: [ TestSymbolData(preciseID: "instance-method", pathComponents: ["Something", "first(one:two:)"], kind: .method), TestSymbolData(preciseID: "type-method", pathComponents: ["Something", "first(one:two:)"], kind: .typeMethod), @@ -139,8 +139,8 @@ class SymbolDisambiguationTests: XCTestCase { ) } - func testStructAndEnumAndTypeAliasCollisionOfSameSymbol() throws { - let references = try disambiguatedReferencesForSymbols( + func testStructAndEnumAndTypeAliasCollisionOfSameSymbol() async throws { + let references = try await disambiguatedReferencesForSymbols( swift: [ TestSymbolData(preciseID: "first", pathComponents: ["Something", "First"], kind: .struct), ], @@ -161,8 +161,8 @@ class SymbolDisambiguationTests: XCTestCase { ) } - func testTripleCollisionWithBothSameTypeAndDifferentType() throws { - let references = try disambiguatedReferencesForSymbols( + func testTripleCollisionWithBothSameTypeAndDifferentType() async throws { + let references = try await disambiguatedReferencesForSymbols( swift: [ TestSymbolData(preciseID: "first", pathComponents: ["Something", "first(_:_:)"], kind: .method), TestSymbolData(preciseID: "second", pathComponents: ["Something", "first(_:_:)"], kind: .typeMethod), @@ -188,8 +188,8 @@ class SymbolDisambiguationTests: XCTestCase { ) } - func testMixedLanguageFramework() throws { - let (bundle, context) = try testBundleAndContext(named: "MixedLanguageFramework") + func testMixedLanguageFramework() async throws { + let (bundle, context) = try await testBundleAndContext(named: "MixedLanguageFramework") var loader = SymbolGraphLoader(bundle: bundle, dataLoader: { try context.contentsOfURL($0, in: $1) }) try loader.loadAll() @@ -265,7 +265,7 @@ class SymbolDisambiguationTests: XCTestCase { let kind: SymbolGraph.Symbol.KindIdentifier } - private func disambiguatedReferencesForSymbols(swift swiftSymbols: [TestSymbolData], objectiveC objectiveCSymbols: [TestSymbolData]) throws -> [SymbolGraph.Symbol.Identifier : ResolvedTopicReference] { + private func disambiguatedReferencesForSymbols(swift swiftSymbols: [TestSymbolData], objectiveC objectiveCSymbols: [TestSymbolData]) async throws -> [SymbolGraph.Symbol.Identifier : ResolvedTopicReference] { let graph = SymbolGraph( metadata: SymbolGraph.Metadata( formatVersion: SymbolGraph.SemanticVersion(major: 1, minor: 1, patch: 1), @@ -335,7 +335,7 @@ class SymbolDisambiguationTests: XCTestCase { objcSymbolGraphURL: try JSONEncoder().encode(graph2), ], fallback: nil) - let context = try DocumentationContext(bundle: bundle, dataProvider: provider) + let context = try await DocumentationContext(bundle: bundle, dataProvider: provider) return context.linkResolver.localResolver.referencesForSymbols(in: ["SymbolDisambiguationTests": unified], bundle: bundle, context: context) } diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift index 4a97349758..668dc47c58 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.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 @@ -146,9 +146,9 @@ class SymbolGraphLoaderTests: XCTestCase { } /// Tests if we detect correctly a Mac Catalyst graph - func testLoadingiOSAndCatalystGraphs() throws { - func testBundleCopy(iOSSymbolGraphName: String, catalystSymbolGraphName: String) throws -> (URL, DocumentationBundle, DocumentationContext) { - return try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", configureBundle: { bundleURL in + func testLoadingiOSAndCatalystGraphs() async throws { + func testBundleCopy(iOSSymbolGraphName: String, catalystSymbolGraphName: String) async throws -> (URL, DocumentationBundle, DocumentationContext) { + return try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", configureBundle: { bundleURL in // Create an iOS symbol graph file let iOSGraphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") let renamediOSGraphURL = bundleURL.appendingPathComponent(iOSSymbolGraphName) @@ -177,7 +177,7 @@ class SymbolGraphLoaderTests: XCTestCase { do { // We rename the iOS graph file to contain a "@" which makes it being loaded after main symbol graphs // to simulate the loading order we want to test. - let (_, _, context) = try testBundleCopy(iOSSymbolGraphName: "faux@MyKit.symbols.json", catalystSymbolGraphName: "MyKit.symbols.json") + let (_, _, context) = try await testBundleCopy(iOSSymbolGraphName: "faux@MyKit.symbols.json", catalystSymbolGraphName: "MyKit.symbols.json") guard let availability = (context.documentationCache["s:5MyKit0A5ClassC"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 's:5MyKit0A5ClassC'") @@ -197,7 +197,7 @@ class SymbolGraphLoaderTests: XCTestCase { do { // We rename the Mac Catalyst graph file to contain a "@" which makes it being loaded after main symbol graphs // to simulate the loading order we want to test. - let (_, _, context) = try testBundleCopy(iOSSymbolGraphName: "MyKit.symbols.json", catalystSymbolGraphName: "faux@MyKit.symbols.json") + let (_, _, context) = try await testBundleCopy(iOSSymbolGraphName: "MyKit.symbols.json", catalystSymbolGraphName: "faux@MyKit.symbols.json") guard let availability = (context.documentationCache["s:5MyKit0A5ClassC"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 's:5MyKit0A5ClassC'") @@ -213,8 +213,8 @@ class SymbolGraphLoaderTests: XCTestCase { } // Tests if main and bystanders graphs are loaded - func testLoadingModuleBystanderExtensions() throws { - let (_, bundle, _) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in + func testLoadingModuleBystanderExtensions() async throws { + let (_, bundle, _) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in let bystanderSymbolGraphURL = Bundle.module.url( forResource: "MyKit@Foundation@_MyKit_Foundation.symbols", withExtension: "json", subdirectory: "Test Resources")! try FileManager.default.copyItem(at: bystanderSymbolGraphURL, to: url.appendingPathComponent("MyKit@Foundation@_MyKit_Foundation.symbols.json")) @@ -381,7 +381,7 @@ class SymbolGraphLoaderTests: XCTestCase { ) } - func testDefaulAvailabilityWhenMissingSGFs() throws { + func testDefaulAvailabilityWhenMissingSGFs() async throws { // Symbol from SGF let symbol = """ { @@ -453,7 +453,7 @@ class SymbolGraphLoaderTests: XCTestCase { let infoPlistURL = targetURL.appendingPathComponent("Info.plist") try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - var (_, _, context) = try loadBundle(from: targetURL) + var (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -478,7 +478,7 @@ class SymbolGraphLoaderTests: XCTestCase { """ try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - (_, _, context) = try loadBundle(from: targetURL) + (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -519,7 +519,7 @@ class SymbolGraphLoaderTests: XCTestCase { """ try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - (_, _, context) = try loadBundle(from: targetURL) + (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -531,7 +531,7 @@ class SymbolGraphLoaderTests: XCTestCase { XCTAssertTrue(availability.first(where: { $0.domain?.rawValue == "iPadOS" })?.introducedVersion == SymbolGraph.SemanticVersion(major: 8, minor: 0, patch: 0)) } - func testFallbackAvailabilityVersion() throws { + func testFallbackAvailabilityVersion() async throws { // Symbol from SG let symbol = """ { @@ -582,7 +582,7 @@ class SymbolGraphLoaderTests: XCTestCase { let symbolGraphURL = targetURL.appendingPathComponent("MyModule.symbols.json") try symbolGraphString.write(to: symbolGraphURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - let (_, _, context) = try loadBundle(from: targetURL) + let (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -595,7 +595,7 @@ class SymbolGraphLoaderTests: XCTestCase { XCTAssertEqual(availability.first(where: { $0.domain?.rawValue == "iPadOS" })?.introducedVersion, SymbolGraph.SemanticVersion(major: 12, minor: 0, patch: 0)) } - func testFallbackPlatformsDontOverrideSourceAvailability() throws { + func testFallbackPlatformsDontOverrideSourceAvailability() async throws { // Symbol from SG let symbolGraphStringiOS = makeSymbolGraphString( moduleName: "MyModule", @@ -687,7 +687,7 @@ class SymbolGraphLoaderTests: XCTestCase { try symbolGraphStringiOS.write(to: targetURL.appendingPathComponent("MyModule-ios.symbols.json"), atomically: true, encoding: .utf8) try symbolGraphStringCatalyst.write(to: targetURL.appendingPathComponent("MyModule-catalyst.symbols.json"), atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - let (_, _, context) = try loadBundle(from: targetURL) + let (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -702,7 +702,7 @@ class SymbolGraphLoaderTests: XCTestCase { } - func testDefaultAvailabilityDontOverrideSourceAvailability() throws { + func testDefaultAvailabilityDontOverrideSourceAvailability() async throws { // Symbol from SGF let iosSymbolGraphString = makeSymbolGraphString( moduleName: "MyModule", @@ -824,7 +824,7 @@ class SymbolGraphLoaderTests: XCTestCase { let infoPlistURL = targetURL.appendingPathComponent("Info.plist") try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - let (_, _, context) = try loadBundle(from: targetURL) + let (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -838,7 +838,7 @@ class SymbolGraphLoaderTests: XCTestCase { XCTAssertEqual(availability.first(where: { $0.domain?.rawValue == "iPadOS" })?.introducedVersion, SymbolGraph.SemanticVersion(major: 12, minor: 0, patch: 0)) } - func testDefaultAvailabilityFillSourceAvailability() throws { + func testDefaultAvailabilityFillSourceAvailability() async throws { // Symbol from SGF let symbol = """ { @@ -921,7 +921,7 @@ class SymbolGraphLoaderTests: XCTestCase { let infoPlistURL = targetURL.appendingPathComponent("Info.plist") try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - let (_, _, context) = try loadBundle(from: targetURL) + let (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -933,7 +933,7 @@ class SymbolGraphLoaderTests: XCTestCase { XCTAssertEqual(availability.first(where: { $0.domain?.rawValue == "macCatalyst" })?.introducedVersion, SymbolGraph.SemanticVersion(major: 7, minor: 0, patch: 0)) } - func testUnconditionallyunavailablePlatforms() throws { + func testUnconditionallyunavailablePlatforms() async throws { // Create an empty bundle let targetURL = try createTemporaryDirectory(named: "test.docc") // Symbol from SGF @@ -1050,7 +1050,7 @@ class SymbolGraphLoaderTests: XCTestCase { let infoPlistURL = targetURL.appendingPathComponent("Info.plist") try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - var (_, _, context) = try loadBundle(from: targetURL) + var (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -1070,7 +1070,7 @@ class SymbolGraphLoaderTests: XCTestCase { platform: """ """ ).write(to: targetURL.appendingPathComponent("MyModule-catalyst.symbols.json"), atomically: true, encoding: .utf8) - (_, _, context) = try loadBundle(from: targetURL) + (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -1084,7 +1084,7 @@ class SymbolGraphLoaderTests: XCTestCase { XCTAssertNil(availability.first(where: { $0.domain?.rawValue == "iPadOS" })) } - func testSymbolUnavailablePerPlatform() throws { + func testSymbolUnavailablePerPlatform() async throws { // Create an empty bundle let targetURL = try createTemporaryDirectory(named: "test.docc") // Symbol from SGF @@ -1192,7 +1192,7 @@ class SymbolGraphLoaderTests: XCTestCase { """ ).write(to: targetURL.appendingPathComponent("MyModule-catalyst.symbols.json"), atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - var (_, _, context) = try loadBundle(from: targetURL) + var (_, _, context) = try await loadBundle(from: targetURL) guard let availabilityFoo = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -1245,7 +1245,7 @@ class SymbolGraphLoaderTests: XCTestCase { // Create info list let infoPlistURL = targetURL.appendingPathComponent("Info.plist") try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) - (_, _, context) = try loadBundle(from: targetURL) + (_, _, context) = try await loadBundle(from: targetURL) guard let availabilityFoo = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -1262,7 +1262,7 @@ class SymbolGraphLoaderTests: XCTestCase { XCTAssertNotNil(availabilityBar.first(where: { $0.domain?.rawValue == "macCatalyst" })) } - func testDefaultModuleAvailability() throws { + func testDefaultModuleAvailability() async throws { // Create an empty bundle let targetURL = try createTemporaryDirectory(named: "test.docc") // Symbol from SGF @@ -1319,7 +1319,7 @@ class SymbolGraphLoaderTests: XCTestCase { """ let infoPlistURL = targetURL.appendingPathComponent("Info.plist") try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) - let (_, _, context) = try loadBundle(from: targetURL) + let (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -1328,7 +1328,7 @@ class SymbolGraphLoaderTests: XCTestCase { XCTAssertEqual(availability.first(where: { $0.domain?.rawValue == "macOS" })?.introducedVersion, SymbolGraph.SemanticVersion(major: 10, minor: 0, patch: 0)) } - func testCanonicalPlatformNameUniformity() throws { + func testCanonicalPlatformNameUniformity() async throws { let testBundle = Folder(name: "TestBundle.docc", content: [ TextFile(name: "Info.plist", utf8Content: """ @@ -1444,7 +1444,7 @@ class SymbolGraphLoaderTests: XCTestCase { ]) let tempURL = try createTemporaryDirectory() let bundleURL = try testBundle.write(inside: tempURL) - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -1456,7 +1456,7 @@ class SymbolGraphLoaderTests: XCTestCase { XCTAssertTrue(availability.filter({ $0.domain?.rawValue == "maccatalyst" }).count == 0) } - func testFallbackOverrideDefaultAvailability() throws { + func testFallbackOverrideDefaultAvailability() async throws { // Symbol from SG let symbolGraphStringiOS = makeSymbolGraphString( moduleName: "MyModule", @@ -1568,14 +1568,14 @@ class SymbolGraphLoaderTests: XCTestCase { try symbolGraphStringCatalyst.write(to: targetURL.appendingPathComponent("MyModule-catalyst.symbols.json"), atomically: true, encoding: .utf8) try infoPlist.write(to: targetURL.appendingPathComponent("Info.plist"), atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - let (_, _, context) = try loadBundle(from: targetURL) + let (_, _, context) = try await loadBundle(from: targetURL) let availability = try XCTUnwrap((context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability) // Verify we fallback to iOS even if there's default availability for the Catalyst platform. XCTAssertNotNil(availability.first(where: { $0.domain?.rawValue == "iOS" })) XCTAssertEqual(availability.first(where: { $0.domain?.rawValue == "macCatalyst" })?.introducedVersion, SymbolGraph.SemanticVersion(major: 12, minor: 0, patch: 0)) } - func testDefaultAvailabilityWhenMissingFallbackPlatform() throws { + func testDefaultAvailabilityWhenMissingFallbackPlatform() async throws { // Symbol from SG let symbolGraphStringCatalyst = makeSymbolGraphString( moduleName: "MyModule", @@ -1641,7 +1641,7 @@ class SymbolGraphLoaderTests: XCTestCase { try symbolGraphStringCatalyst.write(to: targetURL.appendingPathComponent("MyModule-catalyst.symbols.json"), atomically: true, encoding: .utf8) try infoPlist.write(to: targetURL.appendingPathComponent("Info.plist"), atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - let (_, _, context) = try loadBundle(from: targetURL) + let (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@A'") return @@ -1653,7 +1653,7 @@ class SymbolGraphLoaderTests: XCTestCase { } - func testDefaultAvailabilityWhenSymbolIsNotAvailableForThatPlatform() throws { + func testDefaultAvailabilityWhenSymbolIsNotAvailableForThatPlatform() async throws { // Symbol from SGF let symbolTVOS = """ { @@ -1769,7 +1769,7 @@ class SymbolGraphLoaderTests: XCTestCase { let infoPlistURL = targetURL.appendingPathComponent("Info.plist") try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - let (_, _, context) = try loadBundle(from: targetURL) + let (_, _, context) = try await loadBundle(from: targetURL) guard let availability = (context.documentationCache["c:@F@Bar"]?.semantic as? Symbol)?.availability?.availability else { XCTFail("Did not find availability for symbol 'c:@F@Bar'") return diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift index b16ff2b3f7..e90ca0f967 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.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,8 +50,8 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { private let swiftSelector = UnifiedSymbolGraph.Selector(interfaceLanguage: "swift", platform: nil) - func testImplementsRelationship() throws { - let (bundle, context) = try testBundleAndContext() + func testImplementsRelationship() async throws { + let (bundle, context) = try await testBundleAndContext() var documentationCache = DocumentationContext.ContentCache() let engine = DiagnosticEngine() @@ -64,8 +64,8 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { XCTAssertFalse((documentationCache["B"]!.semantic as! Symbol).defaultImplementations.implementations.isEmpty) } - func testConformsRelationship() throws { - let (bundle, _) = try testBundleAndContext() + func testConformsRelationship() async throws { + let (bundle, _) = try await testBundleAndContext() var documentationCache = DocumentationContext.ContentCache() let engine = DiagnosticEngine() @@ -93,8 +93,8 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { XCTAssertEqual(conforming.destinations.first?.url?.absoluteString, "doc://com.example.test/documentation/SomeModuleName/A") } - func testInheritanceRelationship() throws { - let (bundle, _) = try testBundleAndContext() + func testInheritanceRelationship() async throws { + let (bundle, _) = try await testBundleAndContext() var documentationCache = DocumentationContext.ContentCache() let engine = DiagnosticEngine() @@ -122,8 +122,8 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { XCTAssertEqual(inherited.destinations.first?.url?.absoluteString, "doc://com.example.test/documentation/SomeModuleName/A") } - func testInheritanceRelationshipFromOtherFramework() throws { - let (bundle, _) = try testBundleAndContext() + func testInheritanceRelationshipFromOtherFramework() async throws { + let (bundle, _) = try await testBundleAndContext() var documentationCache = DocumentationContext.ContentCache() let engine = DiagnosticEngine() @@ -159,8 +159,8 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { }), "Could not fallback for parent in inherits from relationship") } - func testRequirementRelationship() throws { - let (bundle, _) = try testBundleAndContext() + func testRequirementRelationship() async throws { + let (bundle, _) = try await testBundleAndContext() var documentationCache = DocumentationContext.ContentCache() let engine = DiagnosticEngine() @@ -173,8 +173,8 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { XCTAssertTrue((documentationCache["A"]!.semantic as! Symbol).isRequired) } - func testOptionalRequirementRelationship() throws { - let (bundle, _) = try testBundleAndContext() + func testOptionalRequirementRelationship() async throws { + let (bundle, _) = try await testBundleAndContext() var documentationCache = DocumentationContext.ContentCache() let engine = DiagnosticEngine() @@ -187,9 +187,9 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { XCTAssertFalse((documentationCache["A"]!.semantic as! Symbol).isRequired) } - func testRequiredAndOptionalRequirementRelationships() throws { + func testRequiredAndOptionalRequirementRelationships() async throws { do { - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var documentationCache = DocumentationContext.ContentCache() let engine = DiagnosticEngine() @@ -204,7 +204,7 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { } do { - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var documentationCache = DocumentationContext.ContentCache() let engine = DiagnosticEngine() diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift index f91081fb56..49d4985b93 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.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 @@ -105,7 +105,7 @@ class SymbolReferenceTests: XCTestCase { } } - func testCreatesUniquePathsForOverloadSymbols() throws { + func testCreatesUniquePathsForOverloadSymbols() async throws { let testCatalog = Folder(name: "TestCreatesUniquePathsForOverloadSymbols.docc", content: [ InfoPlist(displayName: "TestCreatesUniquePathsForOverloadSymbols", identifier: "com.example.documentation"), Folder(name: "Resources", content: [ @@ -198,7 +198,7 @@ class SymbolReferenceTests: XCTestCase { ]), ]) - let (_, context) = try loadBundle(catalog: testCatalog) + let (_, context) = try await loadBundle(catalog: testCatalog) // The overloads are sorted and all dupes get a hash suffix. XCTAssertEqual( diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 572f2ea2a3..8d1752b75b 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.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 @@ -93,8 +93,8 @@ class ExternalLinkableTests: XCTestCase { InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), ]) - func testSummaryOfTutorialPage() throws { - let (bundle, context) = try loadBundle(catalog: catalogHierarchy) + func testSummaryOfTutorialPage() async throws { + let (bundle, context) = try await loadBundle(catalog: catalogHierarchy) let converter = DocumentationNodeConverter(bundle: bundle, context: context) @@ -150,8 +150,8 @@ class ExternalLinkableTests: XCTestCase { XCTAssertEqual(summaries, decoded) } - func testSymbolSummaries() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testSymbolSummaries() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let converter = DocumentationNodeConverter(bundle: bundle, context: context) do { let symbolReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyClass", sourceLanguage: .swift) @@ -312,8 +312,8 @@ class ExternalLinkableTests: XCTestCase { } } - func testTopicImageReferences() throws { - let (url, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testTopicImageReferences() async throws { + let (url, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in let extensionFile = """ # ``MyKit/MyClass/myFunction()`` @@ -430,8 +430,8 @@ class ExternalLinkableTests: XCTestCase { } } - func testVariantSummaries() throws { - let (bundle, context) = try testBundleAndContext(named: "MixedLanguageFramework") + func testVariantSummaries() async throws { + let (bundle, context) = try await testBundleAndContext(named: "MixedLanguageFramework") let converter = DocumentationNodeConverter(bundle: bundle, context: context) // Check a symbol that's represented as a class in both Swift and Objective-C @@ -647,7 +647,7 @@ class ExternalLinkableTests: XCTestCase { } /// Ensure that the task group link summary for overload group pages doesn't overwrite any manual curation. - func testOverloadSymbolsWithManualCuration() throws { + func testOverloadSymbolsWithManualCuration() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) let symbolGraph = SymbolGraph.init( @@ -723,7 +723,7 @@ class ExternalLinkableTests: XCTestCase { JSONFile(name: "MyModule.symbols.json", content: symbolGraph), InfoPlist(displayName: "MyModule", identifier: "com.example.mymodule") ]) - let (bundle, context) = try loadBundle(catalog: catalogHierarchy) + let (bundle, context) = try await loadBundle(catalog: catalogHierarchy) let converter = DocumentationNodeConverter(bundle: bundle, context: context) diff --git a/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift b/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift index 85112fa852..c63d951a60 100644 --- a/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift +++ b/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-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 @@ -47,9 +47,9 @@ class LineHighlighterTests: XCTestCase { ]) } - func highlights(tutorialFile: TextFile, codeFiles: [TextFile]) throws -> [LineHighlighter.Result] { + func highlights(tutorialFile: TextFile, codeFiles: [TextFile]) async throws -> [LineHighlighter.Result] { let catalog = Self.makeCatalog(tutorial: tutorialFile, codeFiles: codeFiles) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let tutorialReference = ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Line-Highlighter-Tests/Tutorial", fragment: nil, sourceLanguage: .swift) let tutorial = try context.entity(with: tutorialReference).semantic as! Tutorial @@ -57,7 +57,7 @@ class LineHighlighterTests: XCTestCase { return LineHighlighter(context: context, tutorialSection: section, tutorialReference: tutorialReference).highlights } - func testNoSteps() throws { + func testNoSteps() async throws { let tutorialFile = TextFile(name: "Tutorial.tutorial", utf8Content: """ @Tutorial(time: 20, projectFiles: nothing.zip) { @XcodeRequirement(title: "Xcode X.Y Beta Z", destination: "https://www.example.com/download") @@ -70,10 +70,11 @@ class LineHighlighterTests: XCTestCase { @Assessments } """) - XCTAssertTrue(try highlights(tutorialFile: tutorialFile, codeFiles: []).isEmpty) + let highlights = try await highlights(tutorialFile: tutorialFile, codeFiles: []) + XCTAssertTrue(highlights.isEmpty) } - func testOneStep() throws { + func testOneStep() async throws { let tutorialFile = TextFile(name: "Tutorial.tutorial", utf8Content: """ @Tutorial(title: "No Steps", time: 20, projectFiles: nothing.zip) { @XcodeRequirement(title: "Xcode X.Y Beta Z", destination: "https://www.example.com/download") @@ -92,7 +93,7 @@ class LineHighlighterTests: XCTestCase { @Assessments """) let code1 = TextFile(name: "code1.swift", utf8Content: "func foo() {}") - let results = try highlights(tutorialFile: tutorialFile, codeFiles: [code1]) + let results = try await highlights(tutorialFile: tutorialFile, codeFiles: [code1]) XCTAssertEqual(1, results.count) results.first.map { result in XCTAssertEqual(ResourceReference(bundleID: LineHighlighterTests.bundleID, path: code1.name), result.file) @@ -100,7 +101,7 @@ class LineHighlighterTests: XCTestCase { } } - func testOneStepWithPrevious() throws { + func testOneStepWithPrevious() async throws { let tutorialFile = TextFile(name: "Tutorial.tutorial", utf8Content: """ @Tutorial(title: "No Steps", time: 20, projectFiles: nothing.zip) { @XcodeRequirement(title: "Xcode X.Y Beta Z", destination: "https://www.example.com/download") @@ -121,7 +122,7 @@ class LineHighlighterTests: XCTestCase { """) let code0 = TextFile(name: "code0.swift", utf8Content: "func foo() {}") let code1 = TextFile(name: "code1.swift", utf8Content: "func foo() {}\nfunc bar() {}") - let results = try highlights(tutorialFile: tutorialFile, codeFiles: [code0, code1]) + let results = try await highlights(tutorialFile: tutorialFile, codeFiles: [code0, code1]) XCTAssertEqual(1, results.count) results.first.map { result in XCTAssertEqual(ResourceReference(bundleID: LineHighlighterTests.bundleID, path: code1.name), result.file) @@ -134,7 +135,7 @@ class LineHighlighterTests: XCTestCase { } } - func testNameMismatch() throws { + func testNameMismatch() async throws { let tutorialFile = TextFile(name: "Tutorial.tutorial", utf8Content: """ @Tutorial(title: "No Steps", time: 20, projectFiles: nothing.zip) { @XcodeRequirement(title: "Xcode X.Y Beta Z", destination: "https://www.example.com/download") @@ -160,7 +161,7 @@ class LineHighlighterTests: XCTestCase { let code1 = TextFile(name: "code1.swift", utf8Content: "func foo() {}") let code2 = TextFile(name: "code2.swift", utf8Content: "func foo() {}\nfunc bar() {}") - let results = try highlights(tutorialFile: tutorialFile, codeFiles: [code1, code2]) + let results = try await highlights(tutorialFile: tutorialFile, codeFiles: [code1, code2]) XCTAssertEqual(2, results.count) XCTAssertEqual(ResourceReference(bundleID: LineHighlighterTests.bundleID, path: code1.name), results[0].file) @@ -170,7 +171,7 @@ class LineHighlighterTests: XCTestCase { XCTAssertTrue(results[1].highlights.isEmpty) } - func testResetDiffAtStart() throws { + func testResetDiffAtStart() async throws { let tutorialFile = TextFile(name: "Tutorial.tutorial", utf8Content: """ @Tutorial(title: "No Steps", time: 20, projectFiles: nothing.zip) { @XcodeRequirement(title: "Xcode X.Y Beta Z", destination: "https://www.example.com/download") @@ -190,7 +191,7 @@ class LineHighlighterTests: XCTestCase { """) let code0 = TextFile(name: "code0.swift", utf8Content: "func foo() {}") let code1 = TextFile(name: "code1.swift", utf8Content: "func foo() {}\nfunc bar() {}") - let results = try highlights(tutorialFile: tutorialFile, codeFiles: [code0, code1]) + let results = try await highlights(tutorialFile: tutorialFile, codeFiles: [code0, code1]) XCTAssertEqual(1, results.count) results.first.map { result in XCTAssertEqual(ResourceReference(bundleID: LineHighlighterTests.bundleID, path: code1.name), result.file) @@ -198,7 +199,7 @@ class LineHighlighterTests: XCTestCase { } } - func testResetDiff() throws { + func testResetDiff() async throws { let tutorialFile = TextFile(name: "Tutorial.tutorial", utf8Content: """ @Tutorial(title: "No Steps", time: 20, projectFiles: nothing.zip) { @XcodeRequirement(title: "Xcode X.Y Beta Z", destination: "https://www.example.com/download") @@ -222,7 +223,7 @@ class LineHighlighterTests: XCTestCase { """) let code1 = TextFile(name: "code1.swift", utf8Content: "func foo() {}") let code2 = TextFile(name: "code2.swift", utf8Content: "func foo() {}\nfunc bar() {}") - let results = try highlights(tutorialFile: tutorialFile, codeFiles: [code1, code2]) + let results = try await highlights(tutorialFile: tutorialFile, codeFiles: [code1, code2]) XCTAssertEqual(2, results.count) @@ -233,7 +234,7 @@ class LineHighlighterTests: XCTestCase { XCTAssertTrue(results[1].highlights.isEmpty) } - func testPreviousOverride() throws { + func testPreviousOverride() async throws { let tutorialFile = TextFile(name: "Tutorial.tutorial", utf8Content: """ @Tutorial(title: "No Steps", time: 20, projectFiles: nothing.zip) { @XcodeRequirement(title: "Xcode X.Y Beta Z", destination: "https://www.example.com/download") @@ -261,7 +262,7 @@ class LineHighlighterTests: XCTestCase { let code0 = TextFile(name: "code0.swift", utf8Content: "") let code1 = TextFile(name: "code1.swift", utf8Content: "func foo() {}") let code2 = TextFile(name: "code2.swift", utf8Content: "func foo() {}\nfunc bar() {}") - let results = try highlights(tutorialFile: tutorialFile, codeFiles: [code0, code1, code2]) + let results = try await highlights(tutorialFile: tutorialFile, codeFiles: [code0, code1, code2]) XCTAssertEqual(2, results.count) diff --git a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift index 5f93f56ead..9a9316e543 100644 --- a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift +++ b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -17,8 +17,8 @@ import SwiftDocCTestUtilities class ParametersAndReturnValidatorTests: XCTestCase { - func testFiltersParameters() throws { - let (bundle, context) = try testBundleAndContext(named: "ErrorParameters") + func testFiltersParameters() async throws { + let (bundle, context) = try await testBundleAndContext(named: "ErrorParameters") // /// - Parameters: // /// - someValue: Some value. @@ -111,7 +111,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { } } - func testExtendsReturnValueDocumentation() throws { + func testExtendsReturnValueDocumentation() async throws { for (returnValueDescription, expectsExtendedDocumentation) in [ // Expects to extend the documentation ("Returns some value.", true), @@ -153,7 +153,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { ]) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -177,8 +177,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { } } - func testParametersWithAlternateSignatures() throws { - let (_, _, context) = try testBundleAndContext(copying: "AlternateDeclarations") { url in + func testParametersWithAlternateSignatures() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "AlternateDeclarations") { url in try """ # ``MyClass/present(completion:)`` @@ -207,8 +207,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { XCTAssertEqual(swiftReturnsContent, "Description of the return value that’s available for some other alternatives.") } - func testParameterDiagnosticsInDocumentationExtension() throws { - let (url, _, context) = try testBundleAndContext(copying: "ErrorParameters") { url in + func testParameterDiagnosticsInDocumentationExtension() async throws { + let (url, _, context) = try await testBundleAndContext(copying: "ErrorParameters") { url in try """ # ``MyClassInObjectiveC/doSomethingWith:error:`` @@ -292,8 +292,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { } } - func testFunctionsThatCorrespondToPropertiesInAnotherLanguage() throws { - let (_, _, context) = try testBundleAndContext(named: "GeometricalShapes") + func testFunctionsThatCorrespondToPropertiesInAnotherLanguage() async throws { + let (_, _, context) = try await testBundleAndContext(named: "GeometricalShapes") XCTAssertEqual(context.problems.map(\.diagnostic.summary), []) let reference = try XCTUnwrap(context.knownPages.first(where: { $0.lastPathComponent == "isEmpty" })) @@ -320,8 +320,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { XCTAssertEqual(objcReturnsContent, "`YES` if the specified circle is empty; otherwise, `NO`.") } - func testCanDocumentInitializerReturnValue() throws { - let (_, _, context) = try testBundleAndContext(copying: "GeometricalShapes") { url in + func testCanDocumentInitializerReturnValue() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "GeometricalShapes") { url in try """ # ``Circle/init(center:radius:)`` @@ -351,7 +351,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { ]) } - func testNoParameterDiagnosticWithoutFunctionSignature() throws { + func testNoParameterDiagnosticWithoutFunctionSignature() async throws { var symbolGraph = makeSymbolGraph(docComment: """ Some function description @@ -365,12 +365,12 @@ class ParametersAndReturnValidatorTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 0) } - func testNoParameterDiagnosticWithoutDocumentationComment() throws { + func testNoParameterDiagnosticWithoutDocumentationComment() async throws { let symbolGraph = makeSymbolGraph(docComment: """ Some function description @@ -380,12 +380,12 @@ class ParametersAndReturnValidatorTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 0) } - func testMissingParametersInDocCommentDiagnostics() throws { + func testMissingParametersInDocCommentDiagnostics() async throws { let symbolGraph = makeSymbolGraph(docComment: """ Some function description @@ -396,7 +396,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 2) let endOfParameterSectionLocation = SourceLocation(line: start.line + 5, column: start.character + 40, source: symbolURL) @@ -425,7 +425,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { XCTAssertEqual(otherMissingParameterProblem.possibleSolutions.first?.replacements.first?.replacement, "\n/// - fourthParameter: <#parameter description#>") } - func testMissingSeparateParametersInDocCommentDiagnostics() throws { + func testMissingSeparateParametersInDocCommentDiagnostics() async throws { let symbolGraph = makeSymbolGraph(docComment: """ Some function description @@ -435,7 +435,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 2) let endOfParameterSectionLocation = SourceLocation(line: start.line + 4, column: start.character + 48, source: symbolURL) @@ -464,7 +464,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { XCTAssertEqual(otherMissingParameterProblem.possibleSolutions.first?.replacements.first?.replacement, "\n///- Parameter fourthParameter: <#parameter description#>") } - func testFunctionWithOnlyErrorParameter() throws { + func testFunctionWithOnlyErrorParameter() async throws { let catalog = Folder(name: "unit-test.docc", content: [ Folder(name: "swift", content: [ @@ -490,7 +490,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { )) ]) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -508,7 +508,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { XCTAssertEqual(returnsSections[.objectiveC]?.content.map({ $0.format() }).joined(), "Some return value description.") } - func testFunctionWithDifferentSignaturesOnDifferentPlatforms() throws { + func testFunctionWithDifferentSignaturesOnDifferentPlatforms() async throws { let catalog = Folder(name: "unit-test.docc", content: [ // One parameter, void return @@ -550,7 +550,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { """) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -567,7 +567,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { XCTAssertEqual(returnSections[.objectiveC]?.content.map({ $0.format() }).joined(), "Some description of the return value that is only available on platform 3.") } - func testFunctionWithErrorParameterButVoidType() throws { + func testFunctionWithErrorParameterButVoidType() async throws { let catalog = Folder(name: "unit-test.docc", content: [ Folder(name: "swift", content: [ @@ -594,7 +594,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { ]) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -613,8 +613,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { XCTAssertNil(returnsSections[.objectiveC]) } - func testWarningForDocumentingExternalParameterNames() throws { - let warningOutput = try warningOutputRaisedFrom( + func testWarningForDocumentingExternalParameterNames() async throws { + let warningOutput = try await warningOutputRaisedFrom( docComment: """ Some function description @@ -633,8 +633,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { """) } - func testWarningForDocumentingVoidReturn() throws { - let warningOutput = try warningOutputRaisedFrom( + func testWarningForDocumentingVoidReturn() async throws { + let warningOutput = try await warningOutputRaisedFrom( docComment: """ Some function description @@ -654,8 +654,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { """) } - func testWarningForParameterDocumentedTwice() throws { - let warningOutput = try warningOutputRaisedFrom( + func testWarningForParameterDocumentedTwice() async throws { + let warningOutput = try await warningOutputRaisedFrom( docComment: """ Some function description @@ -676,8 +676,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { """) } - func testWarningForExtraDocumentedParameter() throws { - let warningOutput = try warningOutputRaisedFrom( + func testWarningForExtraDocumentedParameter() async throws { + let warningOutput = try await warningOutputRaisedFrom( docComment: """ Some function description @@ -697,8 +697,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { """) } - func testWarningForUndocumentedParameter() throws { - let missingFirstWarningOutput = try warningOutputRaisedFrom( + func testWarningForUndocumentedParameter() async throws { + let missingFirstWarningOutput = try await warningOutputRaisedFrom( docComment: """ Some function description @@ -717,7 +717,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { """) - let missingSecondWarningOutput = try warningOutputRaisedFrom( + let missingSecondWarningOutput = try await warningOutputRaisedFrom( docComment: """ Some function description @@ -736,8 +736,8 @@ class ParametersAndReturnValidatorTests: XCTestCase { """) } - func testDoesNotWarnAboutInheritedDocumentation() throws { - let warningOutput = try warningOutputRaisedFrom( + func testDoesNotWarnAboutInheritedDocumentation() async throws { + let warningOutput = try await warningOutputRaisedFrom( docComment: """ Some function description @@ -751,7 +751,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { XCTAssertEqual(warningOutput, "") } - func testDocumentingTwoUnnamedParameters() throws { + func testDocumentingTwoUnnamedParameters() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( docComment: """ @@ -768,7 +768,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { )) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -787,7 +787,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { XCTAssertNil(returnsSections[.objectiveC]) } - func testDocumentingMixedNamedAndUnnamedParameters() throws { + func testDocumentingMixedNamedAndUnnamedParameters() async throws { // This test verifies the behavior of documenting two named parameters and one unnamed parameter. // // It checks different combinations of which parameter is unnamed: @@ -828,7 +828,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { returnValue: .init(kind: .typeIdentifier, spelling: "Void", preciseIdentifier: "s:s4Voida") )) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -852,10 +852,10 @@ class ParametersAndReturnValidatorTests: XCTestCase { } } - func testWarningsForMissingOrExtraUnnamedParameters() throws { + func testWarningsForMissingOrExtraUnnamedParameters() async throws { let returnValue = SymbolKit.SymbolGraph.Symbol.DeclarationFragments.Fragment(kind: .typeIdentifier, spelling: "void", preciseIdentifier: "c:v") - let tooFewParametersOutput = try warningOutputRaisedFrom( + let tooFewParametersOutput = try await warningOutputRaisedFrom( docComment: """ Some function description @@ -882,7 +882,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { | ╰─suggestion: Document unnamed parameter #3 """) - let tooManyParametersOutput = try warningOutputRaisedFrom( + let tooManyParametersOutput = try await warningOutputRaisedFrom( docComment: """ Some function description @@ -913,7 +913,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { returnValue: SymbolGraph.Symbol.DeclarationFragments.Fragment, file: StaticString = #filePath, line: UInt = #line - ) throws -> String { + ) async throws -> String { let fileSystem = try TestFileSystem(folders: [ Folder(name: "path", content: [ Folder(name: "to", content: [ @@ -941,7 +941,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { let (bundle, dataProvider) = try DocumentationContext.InputsProvider(fileManager: fileSystem) .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/unit-test.docc"), options: .init()) - _ = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine) + _ = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine) diagnosticEngine.flush() return logStorage.text.trimmingCharacters(in: .newlines) diff --git a/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift b/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift index f551be022d..4547e14f40 100644 --- a/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift +++ b/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.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 @@ -17,9 +17,9 @@ import SwiftDocCTestUtilities class PropertyListPossibleValuesSectionTests: XCTestCase { - func testPossibleValuesDiagnostics() throws { + func testPossibleValuesDiagnostics() async throws { // Check that a problem is emitted when extra possible values are documented. - var (url, _, context) = try testBundleAndContext(copying: "DictionaryData") { url in + var (url, _, context) = try await testBundleAndContext(copying: "DictionaryData") { url in try """ # ``Month`` @@ -44,7 +44,7 @@ class PropertyListPossibleValuesSectionTests: XCTestCase { } // Check that no problems are emitted if no extra possible values are documented. - (url, _, context) = try testBundleAndContext(copying: "DictionaryData") { url in + (url, _, context) = try await testBundleAndContext(copying: "DictionaryData") { url in try """ # ``Month`` @@ -61,7 +61,7 @@ class PropertyListPossibleValuesSectionTests: XCTestCase { } // Check that a problem is emitted with possible solutions. - (url, _, context) = try testBundleAndContext(copying: "DictionaryData") { url in + (url, _, context) = try await testBundleAndContext(copying: "DictionaryData") { url in try """ # ``Month`` @@ -81,8 +81,8 @@ class PropertyListPossibleValuesSectionTests: XCTestCase { } } - func testAbsenceOfPossibleValues() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "DictionaryData") + func testAbsenceOfPossibleValues() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "DictionaryData") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/DictionaryData/Artist", sourceLanguage: .swift)) let converter = DocumentationNodeConverter(bundle: bundle, context: context) @@ -90,8 +90,8 @@ class PropertyListPossibleValuesSectionTests: XCTestCase { XCTAssertNil(converter.convert(node).primaryContentSections.first(where: { $0.kind == .possibleValues}) as? PossibleValuesRenderSection) } - func testUndocumentedPossibleValues() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "DictionaryData") + func testUndocumentedPossibleValues() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "DictionaryData") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/DictionaryData/Month", sourceLanguage: .swift)) let converter = DocumentationNodeConverter(bundle: bundle, context: context) let possibleValuesSection = try XCTUnwrap(converter.convert(node).primaryContentSections.first(where: { $0.kind == .possibleValues}) as? PossibleValuesRenderSection) @@ -101,8 +101,8 @@ class PropertyListPossibleValuesSectionTests: XCTestCase { XCTAssertEqual(possibleValues.map { $0.name }, ["January", "February", "March"]) } - func testDocumentedPossibleValuesMatchSymbolGraphPossibleValues() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "DictionaryData") { url in + func testDocumentedPossibleValuesMatchSymbolGraphPossibleValues() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "DictionaryData") { url in try """ # ``Month`` @@ -125,8 +125,8 @@ class PropertyListPossibleValuesSectionTests: XCTestCase { XCTAssertEqual(possibleValues.map { $0.value }, ["January", "February", "March"]) } - func testDocumentedPossibleValues() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "DictionaryData") { url in + func testDocumentedPossibleValues() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "DictionaryData") { url in try """ # ``Month`` @@ -149,8 +149,8 @@ class PropertyListPossibleValuesSectionTests: XCTestCase { XCTAssertEqual(documentedPossibleValue.contents.count , 1) } - func testUnresolvedLinkWarnings() throws { - let (_, _, context) = try testBundleAndContext(copying: "DictionaryData") { url in + func testUnresolvedLinkWarnings() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "DictionaryData") { url in try """ # ``Month`` @@ -171,8 +171,8 @@ class PropertyListPossibleValuesSectionTests: XCTestCase { XCTAssertTrue(problemDiagnosticsSummary.contains("\'NotFoundSymbol\' doesn\'t exist at \'/DictionaryData/Month\'")) } - func testResolvedLins() throws { - let (_, _, context) = try testBundleAndContext(copying: "DictionaryData") { url in + func testResolvedLins() async throws { + let (_, _, context) = try await testBundleAndContext(copying: "DictionaryData") { url in try """ # ``Month`` diff --git a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift index e4d2ec5a9b..23da7c1241 100644 --- a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.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 @@ -66,8 +66,8 @@ class RenderContentMetadataTests: XCTestCase { XCTAssertEqual(metadata, roundtripListing.metadata) } - func testRenderingTables() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderingTables() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ @@ -107,8 +107,8 @@ class RenderContentMetadataTests: XCTestCase { } } - func testRenderingTableSpans() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderingTableSpans() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ @@ -160,8 +160,8 @@ class RenderContentMetadataTests: XCTestCase { try assertRoundTripCoding(renderedTable) } - func testRenderingTableColumnAlignments() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderingTableColumnAlignments() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ @@ -202,8 +202,8 @@ class RenderContentMetadataTests: XCTestCase { } /// Verifies that a table with `nil` alignments and a table with all-unset alignments still compare as equal. - func testRenderedTableEquality() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderedTableEquality() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ @@ -228,8 +228,8 @@ class RenderContentMetadataTests: XCTestCase { } /// Verifies that two tables with otherwise-identical contents but different column alignments compare as unequal. - func testRenderedTableInequality() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderedTableInequality() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let decodedTableWithUnsetColumns: RenderBlockContent.Table @@ -275,8 +275,8 @@ class RenderContentMetadataTests: XCTestCase { XCTAssertNotEqual(decodedTableWithUnsetColumns, decodedTableWithLeftColumns) } - func testStrikethrough() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testStrikethrough() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ @@ -298,8 +298,8 @@ class RenderContentMetadataTests: XCTestCase { } } - func testHeadingAnchorShouldBeEncoded() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testHeadingAnchorShouldBeEncoded() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ diff --git a/Tests/SwiftDocCTests/Model/RenderHierarchyTranslatorTests.swift b/Tests/SwiftDocCTests/Model/RenderHierarchyTranslatorTests.swift index 7621008ed4..6fe80c94f4 100644 --- a/Tests/SwiftDocCTests/Model/RenderHierarchyTranslatorTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderHierarchyTranslatorTests.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 @@ -12,8 +12,8 @@ import XCTest class RenderHierarchyTranslatorTests: XCTestCase { - func test() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func test() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let technologyReference = ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestOverview", sourceLanguage: .swift) var translator = RenderHierarchyTranslator(context: context, bundle: bundle) @@ -87,9 +87,9 @@ class RenderHierarchyTranslatorTests: XCTestCase { XCTAssertEqual(assessments.reference.identifier, "doc://org.swift.docc.example/tutorials/Test-Bundle/TestTutorial#Check-Your-Understanding") } - func testMultiplePaths() throws { + func testMultiplePaths() async throws { // Curate "TestTutorial" under MyKit as well as TechnologyX. - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in let myKitURL = root.appendingPathComponent("documentation/mykit.md") let text = try String(contentsOf: myKitURL).replacingOccurrences(of: "## Topics", with: """ ## Topics @@ -128,8 +128,8 @@ class RenderHierarchyTranslatorTests: XCTestCase { ]) } - func testLanguageSpecificHierarchies() throws { - let (bundle, context) = try testBundleAndContext(named: "GeometricalShapes") + func testLanguageSpecificHierarchies() async throws { + let (bundle, context) = try await testBundleAndContext(named: "GeometricalShapes") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) // An inner function to assert the rendered hierarchy values for a given reference diff --git a/Tests/SwiftDocCTests/Model/RenderNodeDiffingBundleTests.swift b/Tests/SwiftDocCTests/Model/RenderNodeDiffingBundleTests.swift index 452763cc21..35c8d22a56 100644 --- a/Tests/SwiftDocCTests/Model/RenderNodeDiffingBundleTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderNodeDiffingBundleTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -15,7 +15,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { let testBundleName = "LegacyBundle_DoNotUseInNewTests" let testBundleID: DocumentationBundle.Identifier = "org.swift.docc.example" - func testDiffSymbolFromBundleWithDiscussionSectionRemoved() throws { + func testDiffSymbolFromBundleWithDiscussionSectionRemoved() async throws { let pathToSymbol = "/documentation/MyKit" let modification = { (url: URL) in @@ -28,7 +28,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { try text.write(to: symbolURL, atomically: true, encoding: .utf8) } - let differences = try getDiffsFromModifiedDocument(bundleName: testBundleName, + let differences = try await getDiffsFromModifiedDocument(bundleName: testBundleName, bundleID: testBundleID, topicReferencePath: pathToSymbol, modification: modification) @@ -41,7 +41,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { valueType: RenderInlineContent.self) } - func testDiffArticleFromBundleWithTopicSectionAdded() throws { + func testDiffArticleFromBundleWithTopicSectionAdded() async throws { let pathToArticle = "/documentation/Test-Bundle/article" let modification = { (url: URL) in @@ -56,7 +56,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { try text.write(to: articleURL, atomically: true, encoding: .utf8) } - let differences = try getDiffsFromModifiedDocument(bundleName: testBundleName, + let differences = try await getDiffsFromModifiedDocument(bundleName: testBundleName, bundleID: testBundleID, topicReferencePath: pathToArticle, modification: modification) @@ -76,7 +76,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { valueType: TaskGroupRenderSection.self) } - func testDiffArticleFromBundleWithSeeAlsoSectionRemoved() throws { + func testDiffArticleFromBundleWithSeeAlsoSectionRemoved() async throws { let pathToArticle = "/documentation/Test-Bundle/article" let modification = { (url: URL) in @@ -89,7 +89,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { try text.write(to: articleURL, atomically: true, encoding: .utf8) } - let differences = try getDiffsFromModifiedDocument(bundleName: testBundleName, + let differences = try await getDiffsFromModifiedDocument(bundleName: testBundleName, bundleID: testBundleID, topicReferencePath: pathToArticle, modification: modification) @@ -108,7 +108,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { valueType: RenderInlineContent.self) } - func testDiffSymbolFromBundleWithTopicSectionRemoved() throws { + func testDiffSymbolFromBundleWithTopicSectionRemoved() async throws { let pathToSymbol = "/documentation/MyKit" let modification = { (url: URL) in @@ -121,7 +121,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { try text.write(to: symbolURL, atomically: true, encoding: .utf8) } - let differences = try getDiffsFromModifiedDocument(bundleName: testBundleName, + let differences = try await getDiffsFromModifiedDocument(bundleName: testBundleName, bundleID: testBundleID, topicReferencePath: pathToSymbol, modification: modification) @@ -140,7 +140,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { valueType: RenderInlineContent.self) } - func testDiffSymbolFromBundleWithAbstractUpdated() throws { + func testDiffSymbolFromBundleWithAbstractUpdated() async throws { let pathToSymbol = "/documentation/MyKit/MyClass" let newAbstractValue = "MyClass new abstract." @@ -150,7 +150,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { try text.write(to: symbolURL, atomically: true, encoding: .utf8) } - let differences = try getDiffsFromModifiedDocument(bundleName: testBundleName, + let differences = try await getDiffsFromModifiedDocument(bundleName: testBundleName, bundleID: testBundleID, topicReferencePath: pathToSymbol, modification: modification) @@ -172,7 +172,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { valueType: AnyRenderReference.self) } - func testDiffSymbolFromBundleWithDeprecationAdded() throws { + func testDiffSymbolFromBundleWithDeprecationAdded() async throws { let pathToSymbol = "/documentation/MyKit/MyProtocol" let newDeprecationValue = "This protocol has been deprecated." @@ -188,7 +188,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { try text.write(to: symbolURL, atomically: true, encoding: .utf8) } - let differences = try getDiffsFromModifiedDocument(bundleName: testBundleName, + let differences = try await getDiffsFromModifiedDocument(bundleName: testBundleName, bundleID: testBundleID, topicReferencePath: pathToSymbol, modification: modification) @@ -211,7 +211,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { valueType: Bool.self) } - func testDiffSymbolFromBundleWithDisplayNameDirectiveAdded() throws { + func testDiffSymbolFromBundleWithDisplayNameDirectiveAdded() async throws { let pathToSymbol = "/documentation/MyKit" let newTitleValue = "My Kit" @@ -227,7 +227,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { try text.write(to: symbolURL, atomically: true, encoding: .utf8) } - let differences = try getDiffsFromModifiedDocument(bundleName: testBundleName, + let differences = try await getDiffsFromModifiedDocument(bundleName: testBundleName, bundleID: testBundleID, topicReferencePath: pathToSymbol, modification: modification) @@ -247,7 +247,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { valueType: RenderMetadata.Module.self) } - func testDiffArticleFromBundleWithDownloadDirectiveAdded() throws { + func testDiffArticleFromBundleWithDownloadDirectiveAdded() async throws { let pathToArticle = "/documentation/Test-Bundle/article" let modification = { (url: URL) in @@ -263,7 +263,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { try text.write(to: articleURL, atomically: true, encoding: .utf8) } - let differences = try getDiffsFromModifiedDocument(bundleName: testBundleName, + let differences = try await getDiffsFromModifiedDocument(bundleName: testBundleName, bundleID: testBundleID, topicReferencePath: pathToArticle, modification: modification) @@ -283,8 +283,8 @@ class RenderNodeDiffingBundleTests: XCTestCase { valueType: String.self) } - func testNoDiffsWhenReconvertingSameBundle() throws { - let (bundle, context) = try testBundleAndContext(named: testBundleName) + func testNoDiffsWhenReconvertingSameBundle() async throws { + let (bundle, context) = try await testBundleAndContext(named: testBundleName) let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) @@ -302,8 +302,8 @@ class RenderNodeDiffingBundleTests: XCTestCase { bundleID: DocumentationBundle.Identifier, topicReferencePath: String, modification: @escaping (URL) throws -> () - ) throws -> JSONPatchDifferences { - let (bundleOriginal, contextOriginal) = try testBundleAndContext(named: bundleName) + ) async throws -> JSONPatchDifferences { + let (bundleOriginal, contextOriginal) = try await testBundleAndContext(named: bundleName) let nodeOriginal = try contextOriginal.entity(with: ResolvedTopicReference(bundleID: bundleID, path: topicReferencePath, sourceLanguage: .swift)) @@ -313,7 +313,7 @@ class RenderNodeDiffingBundleTests: XCTestCase { let renderNodeOriginal = try XCTUnwrap(converter.renderNode(for: nodeOriginal)) // Make copy of the bundle on disk, modify the document, and write it - let (_, bundleModified, contextModified) = try testBundleAndContext(copying: bundleName) { url in + let (_, bundleModified, contextModified) = try await testBundleAndContext(copying: bundleName) { url in try modification(url) } let nodeModified = try contextModified.entity(with: ResolvedTopicReference(bundleID: bundleID, diff --git a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift index a333c82977..d539531a31 100644 --- a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift @@ -91,8 +91,8 @@ class RenderNodeSerializationTests: XCTestCase { checkRoundTrip(inputNode) } - func testBundleRoundTrip() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testBundleRoundTrip() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) guard let tutorialDirective = node.markup as? BlockDirective else { @@ -114,8 +114,8 @@ class RenderNodeSerializationTests: XCTestCase { checkRoundTrip(renderNode) } - func testTutorialArticleRoundTrip() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testTutorialArticleRoundTrip() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorialArticle", sourceLanguage: .swift)) guard let articleDirective = node.markup as? BlockDirective else { @@ -137,10 +137,10 @@ class RenderNodeSerializationTests: XCTestCase { checkRoundTrip(renderNode) } - func testAssetReferenceDictionary() throws { + func testAssetReferenceDictionary() async throws { typealias JSONDictionary = [String: Any] - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) guard let tutorialDirective = node.markup as? BlockDirective else { @@ -191,8 +191,8 @@ class RenderNodeSerializationTests: XCTestCase { } } - func testDiffAvailability() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testDiffAvailability() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorialArticle", sourceLanguage: .swift)) guard let articleDirective = node.markup as? BlockDirective else { diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeArticleOnlyCatalogTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeArticleOnlyCatalogTests.swift index 0dcdb8052e..4a994fe222 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeArticleOnlyCatalogTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeArticleOnlyCatalogTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -12,8 +12,8 @@ import XCTest @testable import SwiftDocC class SemaToRenderNodeArticleOnlyCatalogTests: XCTestCase { - func testDoesNotEmitVariantsForPagesInArticleOnlyCatalog() throws { - for renderNode in try renderNodeConsumer(for: "BundleWithTechnologyRoot").allRenderNodes() { + func testDoesNotEmitVariantsForPagesInArticleOnlyCatalog() async throws { + for renderNode in try await renderNodeConsumer(for: "BundleWithTechnologyRoot").allRenderNodes() { XCTAssertNil(renderNode.variants) } } diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeDictionaryDataTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeDictionaryDataTests.swift index b17d400fc3..b0335f9745 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeDictionaryDataTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeDictionaryDataTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -14,8 +14,8 @@ import SymbolKit import XCTest class SemaToRenderNodeDictionaryDataTests: XCTestCase { - func testBaseRenderNodeFromDictionaryData() throws { - let (_, context) = try testBundleAndContext(named: "DictionaryData") + func testBaseRenderNodeFromDictionaryData() async throws { + let (_, context) = try await testBundleAndContext(named: "DictionaryData") let expectedPageUSRsAndLangs: [String : Set] = [ // Artist dictionary - ``Artist``: @@ -75,8 +75,8 @@ class SemaToRenderNodeDictionaryDataTests: XCTestCase { } } - func testFrameworkRenderNodeHasExpectedContent() throws { - let outputConsumer = try renderNodeConsumer(for: "DictionaryData") + func testFrameworkRenderNodeHasExpectedContent() async throws { + let outputConsumer = try await renderNodeConsumer(for: "DictionaryData") let frameworkRenderNode = try outputConsumer.renderNode( withIdentifier: "DictionaryData" ) @@ -152,8 +152,8 @@ class SemaToRenderNodeDictionaryDataTests: XCTestCase { ) } - func testDictionaryRenderNodeHasExpectedContent() throws { - let outputConsumer = try renderNodeConsumer(for: "DictionaryData") + func testDictionaryRenderNodeHasExpectedContent() async throws { + let outputConsumer = try await renderNodeConsumer(for: "DictionaryData") let artistRenderNode = try outputConsumer.renderNode(withIdentifier: "data:test:Artist") assertExpectedContent( @@ -245,8 +245,8 @@ class SemaToRenderNodeDictionaryDataTests: XCTestCase { XCTAssert((nameProperty.attributes ?? []).isEmpty) } - func testTypeRenderNodeHasExpectedContent() throws { - let outputConsumer = try renderNodeConsumer(for: "DictionaryData") + func testTypeRenderNodeHasExpectedContent() async throws { + let outputConsumer = try await renderNodeConsumer(for: "DictionaryData") let genreRenderNode = try outputConsumer.renderNode(withIdentifier: "data:test:Genre") let type1 = DeclarationRenderSection.Token(fragment: SymbolGraph.Symbol.DeclarationFragments.Fragment(kind: .text, spelling: "string", preciseIdentifier: nil), identifier: nil) diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeHTTPRequestTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeHTTPRequestTests.swift index 7e014be213..6ebf753a32 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeHTTPRequestTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeHTTPRequestTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -14,8 +14,8 @@ import SymbolKit import XCTest class SemaToRenderNodeHTTPRequestTests: XCTestCase { - func testBaseRenderNodeFromHTTPRequest() throws { - let (_, context) = try testBundleAndContext(named: "HTTPRequests") + func testBaseRenderNodeFromHTTPRequest() async throws { + let (_, context) = try await testBundleAndContext(named: "HTTPRequests") let expectedPageUSRsAndLanguages: [String : Set] = [ // Get Artist endpoint - ``Get_Artist``: @@ -77,8 +77,8 @@ class SemaToRenderNodeHTTPRequestTests: XCTestCase { } } - func testFrameworkRenderNodeHasExpectedContent() throws { - let outputConsumer = try renderNodeConsumer(for: "HTTPRequests") + func testFrameworkRenderNodeHasExpectedContent() async throws { + let outputConsumer = try await renderNodeConsumer(for: "HTTPRequests") let frameworkRenderNode = try outputConsumer.renderNode( withIdentifier: "HTTPRequests" ) @@ -144,8 +144,8 @@ class SemaToRenderNodeHTTPRequestTests: XCTestCase { ) } - func testRestGetRequestRenderNodeHasExpectedContent() throws { - let outputConsumer = try renderNodeConsumer(for: "HTTPRequests") + func testRestGetRequestRenderNodeHasExpectedContent() async throws { + let outputConsumer = try await renderNodeConsumer(for: "HTTPRequests") let getArtistRenderNode = try outputConsumer.renderNode(withIdentifier: "rest:test:get:v1/artists/{}") assertExpectedContent( @@ -229,8 +229,8 @@ class SemaToRenderNodeHTTPRequestTests: XCTestCase { } } - func testRestPostRequestRenderNodeHasExpectedContent() throws { - let outputConsumer = try renderNodeConsumer(for: "HTTPRequests") + func testRestPostRequestRenderNodeHasExpectedContent() async throws { + let outputConsumer = try await renderNodeConsumer(for: "HTTPRequests") let getArtistRenderNode = try outputConsumer.renderNode(withIdentifier: "rest:test:post:v1/artists") assertExpectedContent( diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift index ed0809eaf8..02638bd029 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.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 @@ -15,8 +15,8 @@ import SwiftDocCTestUtilities import XCTest class SemaToRenderNodeMixedLanguageTests: XCTestCase { - func testBaseRenderNodeFromMixedLanguageFramework() throws { - let (_, context) = try testBundleAndContext(named: "MixedLanguageFramework") + func testBaseRenderNodeFromMixedLanguageFramework() async throws { + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFramework") for (_, documentationNode) in context.documentationCache where documentationNode.kind.isSymbol { let symbolUSR = try XCTUnwrap((documentationNode.semantic as? Symbol)?.externalID) @@ -78,8 +78,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { } } - func assertOutputsMultiLanguageRenderNodes(variantInterfaceLanguage: String) throws { - let outputConsumer = try renderNodeConsumer( + func assertOutputsMultiLanguageRenderNodes(variantInterfaceLanguage: String) async throws { + let outputConsumer = try await renderNodeConsumer( for: "MixedLanguageFramework", configureBundle: { bundleURL in // Update the clang symbol graph with the Objective-C identifier given in variantInterfaceLanguage. @@ -198,20 +198,20 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testOutputsMultiLanguageRenderNodesWithOccIdentifier() throws { - try assertOutputsMultiLanguageRenderNodes(variantInterfaceLanguage: "occ") + func testOutputsMultiLanguageRenderNodesWithOccIdentifier() async throws { + try await assertOutputsMultiLanguageRenderNodes(variantInterfaceLanguage: "occ") } - func testOutputsMultiLanguageRenderNodesWithObjectiveCIdentifier() throws { - try assertOutputsMultiLanguageRenderNodes(variantInterfaceLanguage: "objective-c") + func testOutputsMultiLanguageRenderNodesWithObjectiveCIdentifier() async throws { + try await assertOutputsMultiLanguageRenderNodes(variantInterfaceLanguage: "objective-c") } - func testOutputsMultiLanguageRenderNodesWithCIdentifier() throws { - try assertOutputsMultiLanguageRenderNodes(variantInterfaceLanguage: "c") + func testOutputsMultiLanguageRenderNodesWithCIdentifier() async throws { + try await assertOutputsMultiLanguageRenderNodes(variantInterfaceLanguage: "c") } - func testFrameworkRenderNodeHasExpectedContentAcrossLanguages() throws { - let outputConsumer = try renderNodeConsumer(for: "MixedLanguageFramework") + func testFrameworkRenderNodeHasExpectedContentAcrossLanguages() async throws { + let outputConsumer = try await renderNodeConsumer(for: "MixedLanguageFramework") let mixedLanguageFrameworkRenderNode = try outputConsumer.renderNode( withIdentifier: "MixedLanguageFramework" ) @@ -347,8 +347,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testObjectiveCAuthoredRenderNodeHasExpectedContentAcrossLanguages() throws { - let outputConsumer = try renderNodeConsumer(for: "MixedLanguageFramework") + func testObjectiveCAuthoredRenderNodeHasExpectedContentAcrossLanguages() async throws { + let outputConsumer = try await renderNodeConsumer(for: "MixedLanguageFramework") let fooRenderNode = try outputConsumer.renderNode(withIdentifier: "c:@E@Foo") assertExpectedContent( @@ -448,8 +448,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testSymbolLinkWorkInMultipleLanguages() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "MixedLanguageFramework") { url in + func testSymbolLinkWorkInMultipleLanguages() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "MixedLanguageFramework") { url in try """ # ``MixedLanguageFramework/Bar`` @@ -501,8 +501,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ], "Both spellings of the symbol link should resolve to the canonical reference.") } - func testArticleInMixedLanguageFramework() throws { - let outputConsumer = try renderNodeConsumer(for: "MixedLanguageFramework") { url in + func testArticleInMixedLanguageFramework() async throws { + let outputConsumer = try await renderNodeConsumer(for: "MixedLanguageFramework") { url in try """ # MyArticle @@ -564,8 +564,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testAPICollectionInMixedLanguageFramework() throws { - let outputConsumer = try renderNodeConsumer(for: "MixedLanguageFramework") + func testAPICollectionInMixedLanguageFramework() async throws { + let outputConsumer = try await renderNodeConsumer(for: "MixedLanguageFramework") let articleRenderNode = try outputConsumer.renderNode(withTitle: "APICollection") @@ -629,8 +629,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testGeneratedImplementationsCollectionIsCuratedInAllAvailableLanguages() throws { - let outputConsumer = try renderNodeConsumer(for: "MixedLanguageFramework") + func testGeneratedImplementationsCollectionIsCuratedInAllAvailableLanguages() async throws { + let outputConsumer = try await renderNodeConsumer(for: "MixedLanguageFramework") let protocolRenderNode = try outputConsumer.renderNode(withTitle: "MixedLanguageClassConformingToProtocol") @@ -653,8 +653,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testGeneratedImplementationsCollectionDoesNotCurateInAllUnavailableLanguages() throws { - let outputConsumer = try renderNodeConsumer( + func testGeneratedImplementationsCollectionDoesNotCurateInAllUnavailableLanguages() async throws { + let outputConsumer = try await renderNodeConsumer( for: "MixedLanguageFramework", configureBundle: { bundleURL in // Update the clang symbol graph to remove the protocol method requirement, so that it's effectively @@ -696,8 +696,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testAutomaticSeeAlsoOnlyShowsAPIsAvailableInParentsLanguageForSymbol() throws { - let outputConsumer = try renderNodeConsumer(for: "MixedLanguageFramework") + func testAutomaticSeeAlsoOnlyShowsAPIsAvailableInParentsLanguageForSymbol() async throws { + let outputConsumer = try await renderNodeConsumer(for: "MixedLanguageFramework") // Swift-only symbol. XCTAssertEqual( @@ -766,8 +766,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testMultiLanguageChildOfSingleParentSymbolIsCuratedInMultiLanguage() throws { - let outputConsumer = try renderNodeConsumer( + func testMultiLanguageChildOfSingleParentSymbolIsCuratedInMultiLanguage() async throws { + let outputConsumer = try await renderNodeConsumer( for: "MixedLanguageFrameworkSingleLanguageParent" ) @@ -793,8 +793,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testMultiLanguageSymbolWithLanguageSpecificRelationships() throws { - let outputConsumer = try renderNodeConsumer( + func testMultiLanguageSymbolWithLanguageSpecificRelationships() async throws { + let outputConsumer = try await renderNodeConsumer( for: "MixedLanguageFrameworkWithLanguageSpecificRelationships" ) @@ -821,8 +821,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testMultiLanguageSymbolWithLanguageSpecificProtocolRequirements() throws { - let outputConsumer = try renderNodeConsumer( + func testMultiLanguageSymbolWithLanguageSpecificProtocolRequirements() async throws { + let outputConsumer = try await renderNodeConsumer( for: "MixedLanguageFrameworkWithLanguageSpecificRelationships" ) @@ -841,8 +841,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { XCTAssert(objectiveCSymbol.relationshipSections.isEmpty) } - func testArticlesWithSupportedLanguagesDirective() throws { - let outputConsumer = try renderNodeConsumer( + func testArticlesWithSupportedLanguagesDirective() async throws { + let outputConsumer = try await renderNodeConsumer( for: "MixedLanguageFrameworkWithArticlesUsingSupportedLanguages" ) @@ -879,8 +879,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { ) } - func testArticlesAreIncludedInAllVariantsTopicsSection() throws { - let outputConsumer = try renderNodeConsumer( + func testArticlesAreIncludedInAllVariantsTopicsSection() async throws { + let outputConsumer = try await renderNodeConsumer( for: "MixedLanguageFramework", configureBundle: { bundleURL in try """ @@ -961,8 +961,8 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { XCTAssertFalse(objCTopicIDs.contains("doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/SwiftOnlyStruct")) } - func testAutomaticSeeAlsoSectionElementLimit() throws { - let (bundle, context) = try loadBundle(catalog: + func testAutomaticSeeAlsoSectionElementLimit() async throws { + let (bundle, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: (1...50).map { makeSymbol(id: "symbol-id-\($0)", kind: .class, pathComponents: ["SymbolName\($0)"]) diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeSourceRepositoryTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeSourceRepositoryTests.swift index 0725e946b8..a0aa7c3c8d 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeSourceRepositoryTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeSourceRepositoryTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022-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 @@ -14,8 +14,8 @@ import SymbolKit import XCTest class SemaToRenderNodeSourceRepositoryTests: XCTestCase { - func testDoesNotEmitsSourceRepositoryInformationWhenNoSourceIsGiven() throws { - let outputConsumer = try renderNodeConsumer( + func testDoesNotEmitsSourceRepositoryInformationWhenNoSourceIsGiven() async throws { + let outputConsumer = try await renderNodeConsumer( for: "SourceLocations", sourceRepository: nil ) @@ -23,8 +23,8 @@ class SemaToRenderNodeSourceRepositoryTests: XCTestCase { XCTAssertNil(try outputConsumer.renderNode(withTitle: "MyStruct").metadata.remoteSource) } - func testEmitsSourceRepositoryInformationForSymbolsWhenPresent() throws { - let outputConsumer = try renderNodeConsumer( + func testEmitsSourceRepositoryInformationForSymbolsWhenPresent() async throws { + let outputConsumer = try await renderNodeConsumer( for: "SourceLocations", sourceRepository: SourceRepository.github( checkoutPath: "/path/to/checkout", diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index cc1d497b58..49d48a2458 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -15,8 +15,8 @@ import SymbolKit import SwiftDocCTestUtilities class SemaToRenderNodeTests: XCTestCase { - func testCompileTutorial() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testCompileTutorial() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) guard let tutorialDirective = node.markup as? BlockDirective else { @@ -400,8 +400,8 @@ class SemaToRenderNodeTests: XCTestCase { ) } - func testTutorialBackgroundComesFromImageOrVideoPoster() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testTutorialBackgroundComesFromImageOrVideoPoster() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") func assertTutorialWithPath(_ tutorialPath: String, hasBackground backgroundIdentifier: String) throws { let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: tutorialPath, sourceLanguage: .swift)) @@ -430,8 +430,8 @@ class SemaToRenderNodeTests: XCTestCase { try assertTutorialWithPath("/tutorials/Test-Bundle/TestTutorial2", hasBackground: "introposter2.png") } - func testCompileTutorialArticle() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testCompileTutorialArticle() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorialArticle", sourceLanguage: .swift)) let article = node.semantic as! TutorialArticle @@ -491,13 +491,13 @@ class SemaToRenderNodeTests: XCTestCase { ]) } - func testCompileOverviewWithNoVolumes() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testCompileOverviewWithNoVolumes() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") try assertCompileOverviewWithNoVolumes(bundle: bundle, context: context) } - func testCompileOverviewWithEmptyChapter() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testCompileOverviewWithEmptyChapter() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try """ @Tutorials(name: "Technology X") { @Intro(title: "Technology X") { @@ -717,8 +717,8 @@ class SemaToRenderNodeTests: XCTestCase { ) } - func testCompileOverviewWithVolumes() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testCompileOverviewWithVolumes() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in let overviewURL = root.appendingPathComponent("TestOverview.tutorial") let text = """ @Tutorials(name: "Technology X") { @@ -926,8 +926,8 @@ class SemaToRenderNodeTests: XCTestCase { ) } - func testCompileSymbol() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testCompileSymbol() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in // Remove the SideClass sub heading to match the expectations of this test let graphURL = url.appendingPathComponent("sidekit.symbols.json") var graph = try JSONDecoder().decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -1172,7 +1172,7 @@ class SemaToRenderNodeTests: XCTestCase { ) } - func testCompileSymbolWithExternalReferences() throws { + func testCompileSymbolWithExternalReferences() async throws { class TestSymbolResolver: GlobalExternalSymbolResolver { func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { let reference = ResolvedTopicReference(bundleID: "com.test.external.symbols", path: "/\(preciseIdentifier)", sourceLanguage: .objectiveC) @@ -1225,7 +1225,7 @@ class SemaToRenderNodeTests: XCTestCase { let testBundleURL = Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")! - let (_, bundle, context) = try loadBundle( + let (_, bundle, context) = try await loadBundle( from: testBundleURL, externalResolvers: ["com.test.external": TestReferenceResolver()], externalSymbolResolver: TestSymbolResolver() @@ -1321,11 +1321,11 @@ class SemaToRenderNodeTests: XCTestCase { ) } - func testRenderConstraints() throws { + func testRenderConstraints() async throws { // Check for constraints in render node - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol @@ -1377,8 +1377,8 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertEqual(constraintsString, "Label is Text, Observer inherits NSObject, and S conforms to StringProtocol.") } - func testRenderConditionalConstraintsOnConformingType() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderConditionalConstraintsOnConformingType() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) @@ -1399,8 +1399,8 @@ class SemaToRenderNodeTests: XCTestCase { }.joined(), "Element conforms to Equatable.") } - func testRenderConditionalConstraintsOnProtocol() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderConditionalConstraintsOnProtocol() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) @@ -1421,8 +1421,8 @@ class SemaToRenderNodeTests: XCTestCase { }.joined(), "Element conforms to Equatable.") } - func testRenderReferenceResolving() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderReferenceResolving() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) // Compile docs and verify contents @@ -1486,8 +1486,8 @@ class SemaToRenderNodeTests: XCTestCase { ]) } - func testAvailabilityMetadata() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testAvailabilityMetadata() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) // Compile docs and verify contents @@ -1528,14 +1528,14 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertEqual(platforms[5].introduced, "6.0") } - func testAvailabilityFromCurrentPlatformOverridesExistingValue() throws { + func testAvailabilityFromCurrentPlatformOverridesExistingValue() async throws { // The `MyClass` symbol has availability information for all platforms. Copy the symbol graph for each platform and override only the // availability for that platform to verify that the end result preferred the information for each platform. let allPlatformsNames: [(platformName: String, operatingSystemName: String)] = [("iOS", "ios"), ("macOS", "macosx"), ("watchOS", "watchos"), ("tvOS", "tvos")] // Override with both a low and a high value for version in [SymbolGraph.SemanticVersion(major: 1, minor: 1, patch: 1), SymbolGraph.SemanticVersion(major: 99, minor: 99, patch: 99)] { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in // Duplicate the symbol graph let myKitURL = url.appendingPathComponent("mykit-iOS.symbols.json") let myClassUSR = "s:5MyKit0A5ClassC" @@ -1586,8 +1586,8 @@ class SemaToRenderNodeTests: XCTestCase { } } - func testMediaReferencesWithSpaces() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testMediaReferencesWithSpaces() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TutorialMediaWithSpaces", sourceLanguage: .swift)) guard let tutorialDirective = node.markup as? BlockDirective else { @@ -1611,7 +1611,7 @@ class SemaToRenderNodeTests: XCTestCase { renderNode.references.keys.filter({ !$0.hasPrefix("doc://") }).sorted()) } - func testUnexpectedDirectivesAreDropped() throws { + func testUnexpectedDirectivesAreDropped() async throws { let source = """ This is some text. @@ -1648,7 +1648,7 @@ Document @1:1-11:19 """, markup.debugDescription(options: .printSourceLocations)) - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestTutorial", sourceLanguage: .swift)) let renderContent = try XCTUnwrap(markup.children.reduce(into: [], { result, item in result.append(contentsOf: contentTranslator.visit(item))}) as? [RenderBlockContent]) let expectedContent: [RenderBlockContent] = [ @@ -1667,7 +1667,7 @@ Document @1:1-11:19 XCTAssertEqual(expectedContent, renderContent) } - func testTaskLists() throws { + func testTaskLists() async throws { let source = """ This is some text. @@ -1690,7 +1690,7 @@ Document """, markup.debugDescription()) - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestTutorial", sourceLanguage: .swift)) let renderContent = try XCTUnwrap(markup.children.reduce(into: [], { result, item in result.append(contentsOf: contentTranslator.visit(item))}) as? [RenderBlockContent]) let expectedContent: [RenderBlockContent] = [ @@ -1705,7 +1705,7 @@ Document XCTAssertEqual(expectedContent, renderContent) } - func testInlineHTMLDoesNotCrashTranslator() throws { + func testInlineHTMLDoesNotCrashTranslator() async throws { let markupSource = """ # Test @@ -1715,14 +1715,14 @@ Document let document = Document(parsing: markupSource, options: []) let node = DocumentationNode(reference: ResolvedTopicReference(bundleID: "org.swift.docc", path: "/blah", sourceLanguage: .swift), kind: .article, sourceLanguage: .swift, name: .conceptual(title: "Title"), markup: document, semantic: Semantic()) - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) XCTAssertNotNil(translator.visit(MarkupContainer(document.children))) } - func testCompileSymbolMetadata() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testCompileSymbolMetadata() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift)) // Compile docs and verify contents @@ -1770,8 +1770,8 @@ Document ]) } - func testArticleRoleHeadings() throws { - try assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: "Article", content: """ + func testArticleRoleHeadings() async throws { + try await assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: "Article", content: """ # Article 2 This is article 2. @@ -1779,8 +1779,8 @@ Document ) } - func testArticleRoleHeadingsWithAutomaticTitleHeadingDisabled() throws { - try assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: nil, content: """ + func testArticleRoleHeadingsWithAutomaticTitleHeadingDisabled() async throws { + try await assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: nil, content: """ # Article 2 @Options { @@ -1792,8 +1792,8 @@ Document ) } - func testArticleRoleHeadingsWithAutomaticTitleHeadingForPageKind() throws { - try assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: "Article", content: """ + func testArticleRoleHeadingsWithAutomaticTitleHeadingForPageKind() async throws { + try await assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: "Article", content: """ # Article 2 @Options { @@ -1805,8 +1805,8 @@ Document ) } - func testAPICollectionRoleHeading() throws { - try assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: nil, content: """ + func testAPICollectionRoleHeading() async throws { + try await assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: nil, content: """ # Article 2 This is article 2. @@ -1819,7 +1819,7 @@ Document ) } - private func renderNodeForArticleInTestBundle(content: String) throws -> RenderNode { + private func renderNodeForArticleInTestBundle(content: String) async throws -> RenderNode { // Overwrite the article so we can test the article eyebrow for articles without task groups let sourceURL = Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")! @@ -1829,7 +1829,7 @@ Document try content.write(to: targetURL.appendingPathComponent("article2.md"), atomically: true, encoding: .utf8) - let (_, bundle, context) = try loadBundle(from: targetURL) + let (_, bundle, context) = try await loadBundle(from: targetURL) let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Test-Bundle/article2", sourceLanguage: .swift)) let article = node.semantic as! Article var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) @@ -1840,16 +1840,16 @@ Document Asserts if `expectedRoleHeading` does not match the parsed render node's `roleHeading` after it's parsed. Uses 'TestBundle's documentation as a base for compiling, overwriting 'article2' with `content`. */ - private func assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: String?, content: String, file: StaticString = #filePath, line: UInt = #line) throws { - let renderNode = try renderNodeForArticleInTestBundle(content: content) + private func assertRoleHeadingForArticleInTestBundle(expectedRoleHeading: String?, content: String, file: StaticString = #filePath, line: UInt = #line) async throws { + let renderNode = try await renderNodeForArticleInTestBundle(content: content) XCTAssertEqual(expectedRoleHeading, renderNode.metadata.roleHeading, file: (file), line: line) } - func testDisablingAutomaticArticleSubheadingGeneration() throws { + func testDisablingAutomaticArticleSubheadingGeneration() async throws { // Assert that by default, articles include an "Overview" heading even if it's not authored. do { - let articleRenderNode = try renderNodeForArticleInTestBundle( + let articleRenderNode = try await renderNodeForArticleInTestBundle( content: """ # Article 2 @@ -1873,7 +1873,7 @@ Document // Assert that disabling the automatic behavior with the option directive works as expected. do { - let articleRenderNode = try renderNodeForArticleInTestBundle( + let articleRenderNode = try await renderNodeForArticleInTestBundle( content: """ # Article 2 @@ -1900,7 +1900,7 @@ Document } /// Verifies we emit the correct warning for external links in topic task groups. - func testWarnForExternalLinksInTopicTaskGroups() throws { + func testWarnForExternalLinksInTopicTaskGroups() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName", symbols: [ ])), @@ -1916,7 +1916,7 @@ Document """), ]) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.filter({ $0.diagnostic.identifier == "org.swift.docc.InvalidDocumentationLink" }).count, 1) XCTAssertNotNil(context.problems.first(where: { problem -> Bool in @@ -1925,8 +1925,8 @@ Document })) } - func testRendersBetaViolators() throws { - func makeTestBundle(currentPlatforms: [String : PlatformVersion]?, file: StaticString = #filePath, line: UInt = #line, referencePath: String) throws -> (DocumentationBundle, DocumentationContext, ResolvedTopicReference) { + func testRendersBetaViolators() async throws { + func makeTestBundle(currentPlatforms: [String : PlatformVersion]?, file: StaticString = #filePath, line: UInt = #line, referencePath: String) async throws -> (DocumentationBundle, DocumentationContext, ResolvedTopicReference) { var configuration = DocumentationContext.Configuration() // Add missing platforms if their fallback platform is present. var currentPlatforms = currentPlatforms ?? [:] @@ -1935,7 +1935,7 @@ Document } configuration.externalMetadata.currentPlatforms = currentPlatforms - let (_, bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) + let (_, bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) let reference = ResolvedTopicReference(bundleID: bundle.id, path: referencePath, sourceLanguage: .swift) return (bundle, context, reference) @@ -1943,7 +1943,7 @@ Document // Not a beta platform do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: nil, referencePath: "/documentation/MyKit/globalFunction(_:considering:)") + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: nil, referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) let renderNode = DocumentationNodeConverter(bundle: bundle, context: context).convert(node) @@ -1956,7 +1956,7 @@ Document do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "Custom Name": PlatformVersion(VersionTriplet(100, 0, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) @@ -1969,7 +1969,7 @@ Document // Different platform is beta do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "tvOS": PlatformVersion(VersionTriplet(100, 0, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") @@ -1983,7 +1983,7 @@ Document // Beta platform but *not* matching the introduced version do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(100, 0, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") @@ -1997,7 +1997,7 @@ Document // Beta platform matching the introduced version do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") @@ -2011,7 +2011,7 @@ Document // Beta platform earlier than the introduced version do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 14, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") @@ -2025,7 +2025,7 @@ Document // Set only some platforms to beta & the exact version globalFunction is being introduced at do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(9, 0, 0), beta: true), "tvOS": PlatformVersion(VersionTriplet(1, 0, 0), beta: true), @@ -2040,7 +2040,7 @@ Document // Set all platforms to beta & the exact version globalFunction is being introduced at to test beta SDK documentation do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(6, 0, 0), beta: true), "tvOS": PlatformVersion(VersionTriplet(13, 0, 0), beta: true), @@ -2056,7 +2056,7 @@ Document // Set all platforms to beta where the symbol is available, // some platforms not beta but the symbol is not available there. - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(6, 0, 0), beta: true), "tvOS": PlatformVersion(VersionTriplet(13, 0, 0), beta: true), @@ -2088,7 +2088,7 @@ Document // Set all platforms to beta & the exact version MyClass is being introduced. // Expect the symbol to no be in beta sinceit does not have an introduced version for iOS do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(6, 0, 0), beta: true), "tvOS": PlatformVersion(VersionTriplet(13, 0, 0), beta: true), @@ -2104,7 +2104,7 @@ Document // Set all platforms as unconditionally unavailable and test that the symbol is not marked as beta. do { - let (bundle, context, reference) = try makeTestBundle(currentPlatforms: [ + let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ "iOS": PlatformVersion(VersionTriplet(100, 0, 0), beta: true) ], referencePath: "/documentation/MyKit/MyClass") let node = try context.entity(with: reference) @@ -2116,8 +2116,8 @@ Document } } - func testRendersDeprecatedViolator() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRendersDeprecatedViolator() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Make the referenced symbol deprecated do { @@ -2139,8 +2139,8 @@ Document XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/MyKit/MyClass/myFunction()"] as? TopicRenderReference)?.isDeprecated, true) } - func testDoesNotRenderDeprecatedViolator() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testDoesNotRenderDeprecatedViolator() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Make the referenced symbol deprecated do { @@ -2163,8 +2163,8 @@ Document XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/MyKit/MyClass/myFunction()"] as? TopicRenderReference)?.isDeprecated, false) } - func testRendersDeprecatedViolatorForUnconditionallyDeprecatedReference() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRendersDeprecatedViolatorForUnconditionallyDeprecatedReference() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Make the referenced symbol deprecated do { @@ -2187,10 +2187,10 @@ Document XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/MyKit/MyClass/myFunction()"] as? TopicRenderReference)?.isDeprecated, true) } - func testRenderMetadataFragments() throws { + func testRenderMetadataFragments() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol @@ -2209,8 +2209,8 @@ Document ]) } - func testRenderMetadataExtendedModule() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testRenderMetadataExtendedModule() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = try XCTUnwrap(node.semantic as? Symbol) @@ -2220,8 +2220,8 @@ Document XCTAssertEqual(renderNode.metadata.extendedModule, "MyKit") } - func testDefaultImplementations() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testDefaultImplementations() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Verify that the render reference to a required symbol includes the 'required' key and the number of default implementations provided. do { @@ -2253,8 +2253,8 @@ Document } } - func testDefaultImplementationsNotListedInTopics() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testDefaultImplementationsNotListedInTopics() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Verify that a required symbol does not include default implementations in Topics groups do { @@ -2269,10 +2269,10 @@ Document } } - func testNoStringMetadata() throws { + func testNoStringMetadata() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol @@ -2298,10 +2298,10 @@ Document XCTAssertEqual(extra, roundtripMetadata as? [String]) } - func testRenderDeclarations() throws { + func testRenderDeclarations() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol @@ -2318,9 +2318,9 @@ Document XCTAssertEqual(section.declarations.first?.languages, ["swift"]) } - func testDocumentationRenderReferenceRoles() throws { + func testDocumentationRenderReferenceRoles() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol @@ -2338,9 +2338,9 @@ Document XCTAssertEqual(roleFor("doc://org.swift.docc.example/documentation/Test-Bundle/Default-Code-Listing-Syntax"), "article") } - func testTutorialsRenderReferenceRoles() throws { + func testTutorialsRenderReferenceRoles() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestOverview", sourceLanguage: .swift)) let symbol = node.semantic as! TutorialTableOfContents @@ -2357,9 +2357,9 @@ Document XCTAssertEqual(roleFor("doc://org.swift.docc.example/tutorials/TestOverview"), "overview") } - func testRemovingTrailingNewLinesInDeclaration() throws { + func testRemovingTrailingNewLinesInDeclaration() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/globalFunction(_:considering:)", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol @@ -2380,9 +2380,9 @@ Document XCTAssertEqual(renderNode.metadata.navigatorTitle?.count, 10) } - func testRenderManualSeeAlsoInArticles() throws { + func testRenderManualSeeAlsoInArticles() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Test-Bundle/article", sourceLanguage: .swift)) let article = node.semantic as! Article @@ -2403,9 +2403,9 @@ Document XCTAssertEqual(link.titleInlineContent, [.text("Website")]) } - func testSafeSectionAnchorNames() throws { + func testSafeSectionAnchorNames() async throws { // Check that heading's anchor was safe-ified - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) @@ -2424,8 +2424,8 @@ Document }) } - func testDuplicateNavigatorTitleIsRemoved() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testDuplicateNavigatorTitleIsRemoved() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/globalFunction(_:considering:)", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) @@ -2440,8 +2440,8 @@ Document XCTAssertNil(renderReference.navigatorTitle) } - func testNonDuplicateNavigatorTitleIsRendered() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNonDuplicateNavigatorTitleIsRendered() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) @@ -2483,8 +2483,8 @@ Document .aside(.init(style: .init(rawValue: "Throws"), content: [.paragraph(.init(inlineContent: [.text("A serious error.")]))])), ] - func testBareTechnology() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testBareTechnology() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try """ @Tutorials(name: "<#text#>") { @Intro(title: "<#text#>") { @@ -2538,8 +2538,8 @@ Document } } - func testBareTutorial() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testBareTutorial() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try """ @Tutorial(time: <#number#>, projectFiles: <#.zip#>) { @Intro(title: "<#text#>") { @@ -2614,10 +2614,10 @@ Document } /// Ensures we render our supported asides from symbol-graph content correctly, whether as a blockquote or as a list item. - func testRenderAsides() throws { + func testRenderAsides() async throws { let asidesSGFURL = Bundle.module.url( forResource: "Asides.symbols", withExtension: "json", subdirectory: "Test Resources")! - let (bundleURL, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in + let (bundleURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in try? FileManager.default.copyItem(at: asidesSGFURL, to: url.appendingPathComponent("Asides.symbols.json")) } defer { @@ -2644,8 +2644,8 @@ Document } /// Tests parsing origin data from symbol graph. - func testOriginMetadata() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testOriginMetadata() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) @@ -2658,11 +2658,11 @@ Document } /// Tests that we inherit docs by default from within the same module. - func testDocInheritanceInsideModule() throws { + func testDocInheritanceInsideModule() async throws { let sgURL = Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols", withExtension: "json", subdirectory: "Test Bundles")! - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in // Replace the out-of-bundle origin with a symbol from the same bundle. try String(contentsOf: sgURL) .replacingOccurrences(of: #"identifier" : "s:OriginalUSR"#, with: #"identifier" : "s:5MyKit0A5MyProtocol0Afunc()"#) @@ -2684,11 +2684,11 @@ Document } /// Tests that we don't inherit docs by default from within the same bundle but not module. - func testDocInheritanceInsideBundleButNotModule() throws { + func testDocInheritanceInsideBundleButNotModule() async throws { let sgURL = Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols", withExtension: "json", subdirectory: "Test Bundles")! - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in // Replace the out-of-bundle origin with a symbol from the same bundle but // from the MyKit module. try String(contentsOf: sgURL) @@ -2710,8 +2710,8 @@ Document } } /// Tests that we generated an automatic abstract and remove source docs. - func testDisabledDocInheritance() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testDisabledDocInheritance() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Verify that the inherited docs which should be ignored are not reference resolved. // Verify inherited docs are reference resolved and their problems are recorded. @@ -2741,8 +2741,8 @@ Document } /// Tests doc extensions are matched to inherited symbols - func testInheritedSymbolDocExtension() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + func testInheritedSymbolDocExtension() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try? """ # ``SideKit/SideClass/Element/inherited()`` Doc extension abstract. @@ -2783,7 +2783,7 @@ Document } /// Tests that authored documentation for inherited symbols isn't removed. - func testInheritedSymbolWithAuthoredDocComment() throws { + func testInheritedSymbolWithAuthoredDocComment() async throws { struct TestData { let docCommentJSON: String let expectedRenderedAbstract: [RenderInlineContent] @@ -2885,7 +2885,7 @@ Document for testData in testData { let sgURL = Bundle.module.url(forResource: "LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols", withExtension: "json", subdirectory: "Test Bundles")! - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in // Replace the out-of-bundle origin with a symbol from the same bundle but // from the MyKit module. var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: sgURL)) @@ -2912,14 +2912,14 @@ Document } /// Tests that we inherit docs when the feature is enabled. - func testEnabledDocInheritance() throws { + func testEnabledDocInheritance() async throws { let bundleURL = Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")! var configuration = DocumentationContext.Configuration() configuration.externalMetadata.inheritDocs = true - let (_, bundle, context) = try loadBundle(from: bundleURL, configuration: configuration) + let (_, bundle, context) = try await loadBundle(from: bundleURL, configuration: configuration) // Verify that we don't reference resolve inherited docs. XCTAssertFalse(context.diagnosticEngine.problems.contains(where: { problem in @@ -2958,8 +2958,8 @@ Document } // Verifies that undocumented symbol gets a nil abstract. - func testNonDocumentedSymbolNilAbstract() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testNonDocumentedSymbolNilAbstract() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/globalFunction(_:considering:)", sourceLanguage: .swift) let node = try context.entity(with: reference) @@ -3066,8 +3066,8 @@ Document } /// Tests links to symbols that have deprecation summary in markdown appear deprecated. - func testLinkToDeprecatedSymbolViaDirectiveIsDeprecated() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + func testLinkToDeprecatedSymbolViaDirectiveIsDeprecated() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try """ # ``MyKit/MyProtocol`` @DeprecationSummary { @@ -3086,8 +3086,8 @@ Document XCTAssertTrue(reference.isDeprecated) } - func testCustomSymbolDisplayNames() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + func testCustomSymbolDisplayNames() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try """ # ``MyKit`` @@ -3187,7 +3187,7 @@ Document } /// Tests that we correctly resolve links in automatic inherited API Collections. - func testInheritedAPIGroupsInCollidedParents() throws { + func testInheritedAPIGroupsInCollidedParents() async throws { // Loads a symbol graph which has a property `b` and a struct `B` that // collide path-wise and `B` has inherited children: @@ -3196,7 +3196,7 @@ Document // │ ╰ doc://com.test.TestBed/documentation/Minimal_docs/A/B-swift.struct/Equatable-Implementations // │ ╰ doc://com.test.TestBed/documentation/Minimal_docs/A/B-swift.struct/!=(_:_:) // ╰ doc://com.test.TestBed/documentation/Minimal_docs/A/b-swift.property - let (bundle, context) = try testBundleAndContext(named: "InheritedUnderCollision") + let (bundle, context) = try await testBundleAndContext(named: "InheritedUnderCollision") // Verify that the inherited symbol got a path that accounts for the collision between // the struct `B` and the property `b`. @@ -3216,8 +3216,8 @@ Document XCTAssertEqual(inheritedSymbolReference.absoluteString, groupReference.absoluteString) } - func testVisitTutorialMediaWithoutExtension() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testVisitTutorialMediaWithoutExtension() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try """ @Tutorials(name: "Technology X") { @Intro(title: "Technology X") { @@ -3262,8 +3262,8 @@ Document XCTAssertNil(renderNode.references["introposter"] as? ImageReference) } - func testTopicsSectionWithAnonymousTopicGroup() throws { - let (_, bundle, context) = try testBundleAndContext( + func testTopicsSectionWithAnonymousTopicGroup() async throws { + let (_, bundle, context) = try await testBundleAndContext( copying: "LegacyBundle_DoNotUseInNewTests", configureBundle: { url in try """ @@ -3308,7 +3308,7 @@ Document ) } - func testTopicsSectionWithSingleAnonymousTopicGroup() throws { + func testTopicsSectionWithSingleAnonymousTopicGroup() async throws { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName", symbols: [ makeSymbol(id: "some-class-id", kind: .class, pathComponents: ["SomeClass"]), @@ -3327,7 +3327,7 @@ Document """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let articleReference = ResolvedTopicReference( @@ -3353,8 +3353,8 @@ Document ) } - func testLanguageSpecificTopicSections() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in + func testLanguageSpecificTopicSections() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in try """ # ``MixedFramework/MyObjectiveCClassObjectiveCName`` @@ -3413,7 +3413,7 @@ Document ]) } - func testLanguageSpecificTopicSectionDoesNotAppearInAutomaticSeeAlso() throws { + func testLanguageSpecificTopicSectionDoesNotAppearInAutomaticSeeAlso() async throws { let catalog = Folder(name: "Something.docc", content: [ JSONFile(name: "Something-swift.symbols.json", content: makeSymbolGraph(moduleName: "Something", symbols: (1...4).map { makeSymbol(id: "symbol-id-\($0)", language: .swift, kind: .class, pathComponents: ["SomeClass\($0)"]) @@ -3445,7 +3445,7 @@ Document - ``SomeClass4`` """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "\(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -3488,7 +3488,7 @@ Document try assertExpectedTopicSections(XCTUnwrap(contextConverter.renderNode(for: documentationNode))) } - func testTopicSectionWithUnsupportedDirectives() throws { + func testTopicSectionWithUnsupportedDirectives() async throws { let exampleDocumentation = Folder(name: "unit-test.docc", content: [ TextFile(name: "root.md", utf8Content: """ # Main article @@ -3519,7 +3519,7 @@ Document let tempURL = try createTemporaryDirectory() let bundleURL = try exampleDocumentation.write(inside: tempURL) - let (_, bundle, context) = try loadBundle(from: bundleURL, diagnosticEngine: .init() /* no diagnostic consumers */) + let (_, bundle, context) = try await loadBundle(from: bundleURL, diagnosticEngine: .init() /* no diagnostic consumers */) let reference = try XCTUnwrap(context.soleRootModuleReference) @@ -3538,8 +3538,8 @@ Document ]) } - func testAutomaticCurationForRefinedSymbols() throws { - let (_, bundle, context) = try testBundleAndContext(named: "GeometricalShapes") + func testAutomaticCurationForRefinedSymbols() async throws { + let (_, bundle, context) = try await testBundleAndContext(named: "GeometricalShapes") do { let root = try XCTUnwrap(context.soleRootModuleReference) @@ -3613,7 +3613,7 @@ Document } } - func testThematicBreak() throws { + func testThematicBreak() async throws { let source = """ --- @@ -3624,7 +3624,7 @@ Document XCTAssertEqual(markup.childCount, 1) - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) @@ -3636,7 +3636,7 @@ Document XCTAssertEqual(expectedContent, renderContent) } - func testSymbolWithEmptyName() throws { + func testSymbolWithEmptyName() async throws { // Symbols _should_ have names, but due to bugs there's cases when anonymous C structs don't. let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( @@ -3657,7 +3657,7 @@ Document )) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.knownPages.map(\.path).sorted(), [ "/documentation/ModuleName", diff --git a/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift b/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift index 2ce82d8703..5ed6806cc6 100644 --- a/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift +++ b/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.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 @@ -17,8 +17,8 @@ class AutomaticSeeAlsoTests: XCTestCase { /// Test that a symbol with no authored See Also and with no curated siblings /// does not have a See Also section. - func testNoSeeAlso() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testNoSeeAlso() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Extension that curates `SideClass` try """ # ``SideKit`` @@ -40,8 +40,8 @@ class AutomaticSeeAlsoTests: XCTestCase { /// Test that a symbol with authored See Also and with no curated siblings /// does include an authored See Also section - func testAuthoredSeeAlso() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testAuthoredSeeAlso() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Extension that curates `SideClass` try """ # ``SideKit`` @@ -76,8 +76,8 @@ class AutomaticSeeAlsoTests: XCTestCase { /// Test that a symbol with authored See Also and with curated siblings /// does include both in See Also with authored section first - func testAuthoredAndAutomaticSeeAlso() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testAuthoredAndAutomaticSeeAlso() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Extension that curates `SideClass` try """ # ``SideKit`` @@ -137,8 +137,8 @@ class AutomaticSeeAlsoTests: XCTestCase { // Duplicate of the `testAuthoredAndAutomaticSeeAlso()` test above // but with automatic see also creation disabled - func testAuthoredSeeAlsoWithDisabledAutomaticSeeAlso() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testAuthoredSeeAlsoWithDisabledAutomaticSeeAlso() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit`` @@ -200,8 +200,8 @@ class AutomaticSeeAlsoTests: XCTestCase { // Duplicate of the `testAuthoredAndAutomaticSeeAlso()` test above // but with automatic see also creation globally disabled - func testAuthoredSeeAlsoWithGloballyDisabledAutomaticSeeAlso() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + func testAuthoredSeeAlsoWithGloballyDisabledAutomaticSeeAlso() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit`` @@ -257,7 +257,7 @@ class AutomaticSeeAlsoTests: XCTestCase { } } - func testSeeAlsoWithSymbolAndTutorial() throws { + func testSeeAlsoWithSymbolAndTutorial() async throws { let exampleDocumentation = Folder(name: "MyKit.docc", content: [ CopyOfFile(original: Bundle.module.url(forResource: "mykit-one-symbol.symbols", withExtension: "json", subdirectory: "Test Resources")!), @@ -283,7 +283,7 @@ class AutomaticSeeAlsoTests: XCTestCase { let tempURL = try createTemporaryDirectory() let bundleURL = try exampleDocumentation.write(inside: tempURL) - let (_, bundle, context) = try loadBundle(from: bundleURL) + let (_, bundle, context) = try await loadBundle(from: bundleURL) // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "MyKit", path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) diff --git a/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift b/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift index 9cdd41b948..da55f59b47 100644 --- a/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift +++ b/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.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 @@ -17,8 +17,8 @@ class AvailabilityRenderOrderTests: XCTestCase { let availabilitySGFURL = Bundle.module.url( forResource: "Availability.symbols", withExtension: "json", subdirectory: "Test Resources")! - func testSortingAtRenderTime() throws { - let (bundleURL, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in + func testSortingAtRenderTime() async throws { + let (bundleURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in let availabilitySymbolGraphURL = url.appendingPathComponent("Availability.symbols.json") try? FileManager.default.copyItem(at: self.availabilitySGFURL, to: availabilitySymbolGraphURL) diff --git a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift index f517383200..cf83b6b49f 100644 --- a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.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 @@ -18,8 +18,8 @@ fileprivate let jsonEncoder = JSONEncoder() class ConstraintsRenderSectionTests: XCTestCase { - func testSingleConstraint() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + func testSingleConstraint() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -48,8 +48,8 @@ class ConstraintsRenderSectionTests: XCTestCase { XCTAssertEqual(renderNode.metadata.conformance?.constraints.map(flattenInlineElements).joined(), "Label is Text.") } - func testSingleRedundantConstraint() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + func testSingleRedundantConstraint() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -77,8 +77,8 @@ class ConstraintsRenderSectionTests: XCTestCase { XCTAssertNil(renderNode.metadata.conformance) } - func testSingleRedundantConstraintForLeaves() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + func testSingleRedundantConstraintForLeaves() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -106,8 +106,8 @@ class ConstraintsRenderSectionTests: XCTestCase { XCTAssertNil(renderNode.metadata.conformance) } - func testPreservesNonRedundantConstraints() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + func testPreservesNonRedundantConstraints() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -136,8 +136,8 @@ class ConstraintsRenderSectionTests: XCTestCase { XCTAssertEqual(renderNode.metadata.conformance?.constraints.map(flattenInlineElements).joined(), "Element is MyClass.") } - func testGroups2Constraints() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + func testGroups2Constraints() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -166,8 +166,8 @@ class ConstraintsRenderSectionTests: XCTestCase { XCTAssertEqual(renderNode.metadata.conformance?.constraints.map(flattenInlineElements).joined(), "Element conforms to MyProtocol and Equatable.") } - func testGroups3Constraints() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + func testGroups3Constraints() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -197,8 +197,8 @@ class ConstraintsRenderSectionTests: XCTestCase { XCTAssertEqual(renderNode.metadata.conformance?.constraints.map(flattenInlineElements).joined(), "Element conforms to MyProtocol, Equatable, and Hashable.") } - func testRenderReferences() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + func testRenderReferences() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -235,8 +235,8 @@ class ConstraintsRenderSectionTests: XCTestCase { XCTAssertEqual(renderReference.conformance?.constraints.map(flattenInlineElements).joined(), "Element conforms to MyProtocol and Equatable.") } - func testRenderReferencesWithNestedTypeInSelf() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + func testRenderReferencesWithNestedTypeInSelf() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) diff --git a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift index 7e1201ced7..42c72aef04 100644 --- a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.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 @@ -133,8 +133,8 @@ class DeclarationsRenderSectionTests: XCTestCase { try assertRoundTripCoding(value) } - func testAlternateDeclarations() throws { - let (bundle, context) = try testBundleAndContext(named: "AlternateDeclarations") + func testAlternateDeclarations() async throws { + let (bundle, context) = try await testBundleAndContext(named: "AlternateDeclarations") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/AlternateDeclarations/MyClass/present(completion:)", @@ -160,7 +160,7 @@ class DeclarationsRenderSectionTests: XCTestCase { XCTAssert(declarationsSection.declarations.allSatisfy({ $0.platforms == [.iOS, .macOS] })) } - func testPlatformSpecificDeclarations() throws { + func testPlatformSpecificDeclarations() async throws { // init(_ content: MyClass) throws let declaration1: SymbolGraph.Symbol.DeclarationFragments = .init(declarationFragments: [ .init(kind: .keyword, spelling: "init", preciseIdentifier: nil), @@ -199,7 +199,7 @@ class DeclarationsRenderSectionTests: XCTestCase { let symbolGraph1 = makeSymbolGraph(moduleName: "PlatformSpecificDeclarations", platform: .init(operatingSystem: .init(name: "macos")), symbols: [symbol1]) let symbolGraph2 = makeSymbolGraph(moduleName: "PlatformSpecificDeclarations", platform: .init(operatingSystem: .init(name: "ios")), symbols: [symbol2]) - func runAssertions(forwards: Bool) throws { + func runAssertions(forwards: Bool) async throws { // Toggling the order of platforms here doesn't necessarily _enforce_ a // nondeterminism failure in a unit-test environment, but it does make it // much more likely. Make sure that the order of the platform-specific @@ -210,7 +210,7 @@ class DeclarationsRenderSectionTests: XCTestCase { JSONFile(name: "symbols\(forwards ? "2" : "1").symbols.json", content: symbolGraph2), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let reference = ResolvedTopicReference( bundleID: bundle.id, @@ -231,11 +231,11 @@ class DeclarationsRenderSectionTests: XCTestCase { "init(_ content: MyClass) throws") } - try runAssertions(forwards: true) - try runAssertions(forwards: false) + try await runAssertions(forwards: true) + try await runAssertions(forwards: false) } - func testHighlightDiff() throws { + func testHighlightDiff() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) let symbolGraphFile = Bundle.module.url( @@ -249,7 +249,7 @@ class DeclarationsRenderSectionTests: XCTestCase { CopyOfFile(original: symbolGraphFile), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) // Make sure that type decorators like arrays, dictionaries, and optionals are correctly highlighted. do { @@ -398,7 +398,7 @@ class DeclarationsRenderSectionTests: XCTestCase { } } - func testInconsistentHighlightDiff() throws { + func testInconsistentHighlightDiff() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) // Generate a symbol graph with many overload groups that share declarations. @@ -458,7 +458,7 @@ class DeclarationsRenderSectionTests: XCTestCase { JSONFile(name: "FancierOverloads.symbols.json", content: symbolGraph), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) func assertDeclarations(for USR: String, file: StaticString = #filePath, line: UInt = #line) throws { let reference = try XCTUnwrap(context.documentationCache.reference(symbolID: USR), file: file, line: line) @@ -483,7 +483,7 @@ class DeclarationsRenderSectionTests: XCTestCase { } } - func testDontHighlightWhenOverloadsAreDisabled() throws { + func testDontHighlightWhenOverloadsAreDisabled() async throws { let symbolGraphFile = Bundle.module.url( forResource: "FancyOverloads", withExtension: "symbols.json", @@ -495,7 +495,7 @@ class DeclarationsRenderSectionTests: XCTestCase { CopyOfFile(original: symbolGraphFile), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) for hash in ["7eht8", "8p1lo", "858ja"] { let reference = ResolvedTopicReference( @@ -514,7 +514,7 @@ class DeclarationsRenderSectionTests: XCTestCase { } } - func testOverloadConformanceDataIsSavedWithDeclarations() throws { + func testOverloadConformanceDataIsSavedWithDeclarations() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) let symbolGraphFile = Bundle.module.url( @@ -528,7 +528,7 @@ class DeclarationsRenderSectionTests: XCTestCase { CopyOfFile(original: symbolGraphFile), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) // MyClass // - myFunc() where T: Equatable @@ -565,7 +565,7 @@ func declarationAndHighlights(for tokens: [DeclarationRenderSection.Token]) -> [ ] } -func declarationsAndHighlights(for section: DeclarationRenderSection) -> [String] { +private func declarationsAndHighlights(for section: DeclarationRenderSection) -> [String] { guard let otherDeclarations = section.otherDeclarations else { return [] } diff --git a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift index 0900cd8db3..597ecbd05b 100644 --- a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.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 @@ -17,8 +17,8 @@ import SwiftDocCTestUtilities class DefaultAvailabilityTests: XCTestCase { // Test whether missing default availability key correctly produces nil availability - func testBundleWithoutDefaultAvailability() throws { - let bundle = try testBundle(named: "BundleWithoutAvailability") + func testBundleWithoutDefaultAvailability() async throws { + let bundle = try await testBundle(named: "BundleWithoutAvailability") XCTAssertNil(bundle.info.defaultAvailability) } @@ -32,9 +32,9 @@ class DefaultAvailabilityTests: XCTestCase { ] // Test whether the default availability is loaded from Info.plist and applied during render time - func testBundleWithDefaultAvailability() throws { + func testBundleWithDefaultAvailability() async throws { // Copy an Info.plist with default availability - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { (url) in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { (url) in try? FileManager.default.removeItem(at: url.appendingPathComponent("Info.plist")) try? FileManager.default.copyItem(at: self.infoPlistAvailabilityURL, to: url.appendingPathComponent("Info.plist")) @@ -101,9 +101,9 @@ class DefaultAvailabilityTests: XCTestCase { } // Test whether the default availability is merged with beta status from the command line - func testBundleWithDefaultAvailabilityInBetaDocs() throws { + func testBundleWithDefaultAvailabilityInBetaDocs() async throws { // Beta status for the docs (which would normally be set via command line argument) - try assertRenderedPlatformsFor(currentPlatforms: [ + try await assertRenderedPlatformsFor(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 1), beta: true), "Mac Catalyst": PlatformVersion(VersionTriplet(13, 5, 0), beta: true), ], equal: [ @@ -112,7 +112,7 @@ class DefaultAvailabilityTests: XCTestCase { ]) // Repeat the assertions, but use an earlier platform version this time - try assertRenderedPlatformsFor(currentPlatforms: [ + try await assertRenderedPlatformsFor(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 14, 1), beta: true), "Mac Catalyst": PlatformVersion(VersionTriplet(13, 5, 0), beta: true), ], equal: [ @@ -121,7 +121,7 @@ class DefaultAvailabilityTests: XCTestCase { ]) } - private func assertRenderedPlatformsFor(currentPlatforms: [String : PlatformVersion], equal expected: [String], file: StaticString = #filePath, line: UInt = #line) throws { + private func assertRenderedPlatformsFor(currentPlatforms: [String : PlatformVersion], equal expected: [String], file: StaticString = #filePath, line: UInt = #line) async throws { var configuration = DocumentationContext.Configuration() configuration.externalMetadata.currentPlatforms = currentPlatforms @@ -131,7 +131,7 @@ class DefaultAvailabilityTests: XCTestCase { JSONFile(name: "MyKit.symbols.json", content: makeSymbolGraph(moduleName: "MyKit")), ]) - let (bundle, context) = try loadBundle(catalog: catalog, configuration: configuration) + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) let reference = try XCTUnwrap(context.soleRootModuleReference, file: file, line: line) // Test whether we: @@ -146,9 +146,9 @@ class DefaultAvailabilityTests: XCTestCase { // Test whether when Mac Catalyst availability is missing we fall back on // Mac Catalyst info.plist availability and not on iOS availability. - func testBundleWithMissingCatalystAvailability() throws { + func testBundleWithMissingCatalystAvailability() async throws { // Beta status for both iOS and Mac Catalyst - try assertRenderedPlatformsFor(currentPlatforms: [ + try await assertRenderedPlatformsFor(currentPlatforms: [ "iOS": PlatformVersion(VersionTriplet(13, 5, 0), beta: true), "Mac Catalyst": PlatformVersion(VersionTriplet(13, 5, 0), beta: true), ], equal: [ @@ -157,7 +157,7 @@ class DefaultAvailabilityTests: XCTestCase { ]) // Public status for Mac Catalyst - try assertRenderedPlatformsFor(currentPlatforms: [ + try await assertRenderedPlatformsFor(currentPlatforms: [ "Mac Catalyst": PlatformVersion(VersionTriplet(13, 5, 0), beta: false), ], equal: [ "Mac Catalyst 13.5", @@ -165,19 +165,19 @@ class DefaultAvailabilityTests: XCTestCase { ]) // Verify that a bug rendering availability as beta when no platforms are provided is fixed. - try assertRenderedPlatformsFor(currentPlatforms: [:], equal: [ + try await assertRenderedPlatformsFor(currentPlatforms: [:], equal: [ "Mac Catalyst 13.5", "macOS 10.15.1", ]) } // Test whether the default availability is not beta when not matching current target platform - func testBundleWithDefaultAvailabilityNotInBetaDocs() throws { + func testBundleWithDefaultAvailabilityNotInBetaDocs() async throws { var configuration = DocumentationContext.Configuration() // Set a beta status for the docs (which would normally be set via command line argument) configuration.externalMetadata.currentPlatforms = ["macOS": PlatformVersion(VersionTriplet(10, 16, 0), beta: true)] - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) { (url) in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) { (url) in // Copy an Info.plist with default availability of macOS 10.15.1 try? FileManager.default.removeItem(at: url.appendingPathComponent("Info.plist")) try? FileManager.default.copyItem(at: self.infoPlistAvailabilityURL, to: url.appendingPathComponent("Info.plist")) @@ -198,11 +198,11 @@ class DefaultAvailabilityTests: XCTestCase { } // Test that a symbol is unavailable and default availability does not precede the "unavailable" attribute. - func testUnavailableAvailability() throws { + func testUnavailableAvailability() async throws { var configuration = DocumentationContext.Configuration() // Set a beta status for the docs (which would normally be set via command line argument) configuration.externalMetadata.currentPlatforms = ["iOS": PlatformVersion(VersionTriplet(14, 0, 0), beta: true)] - let (_, bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) + let (_, bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) do { let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyClass/myFunction()", fragment: nil, sourceLanguage: .swift) @@ -328,8 +328,8 @@ class DefaultAvailabilityTests: XCTestCase { // Test that setting default availability doesn't prevent symbols with "universal" deprecation // (i.e. a platform of '*' and unconditional deprecation) from showing up as deprecated. - func testUniversalDeprecationWithDefaultAvailability() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "BundleWithLonelyDeprecationDirective", excludingPaths: []) { (url) in + func testUniversalDeprecationWithDefaultAvailability() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "BundleWithLonelyDeprecationDirective", excludingPaths: []) { (url) in try? FileManager.default.removeItem(at: url.appendingPathComponent("Info.plist")) try? FileManager.default.copyItem(at: self.infoPlistAvailabilityURL, to: url.appendingPathComponent("Info.plist")) } @@ -545,7 +545,7 @@ class DefaultAvailabilityTests: XCTestCase { ) } - func testInheritDefaultAvailabilityOptions() throws { + func testInheritDefaultAvailabilityOptions() async throws { func makeInfoPlist( defaultAvailability: String ) -> String { @@ -565,7 +565,7 @@ class DefaultAvailabilityTests: XCTestCase { } func setupContext( defaultAvailability: String - ) throws -> (DocumentationBundle, DocumentationContext) { + ) async throws -> (DocumentationBundle, DocumentationContext) { // Create an empty bundle let targetURL = try createTemporaryDirectory(named: "test.docc") // Create symbol graph @@ -576,7 +576,7 @@ class DefaultAvailabilityTests: XCTestCase { let infoPlist = makeInfoPlist(defaultAvailability: defaultAvailability) try infoPlist.write(to: infoPlistURL, atomically: true, encoding: .utf8) // Load the bundle & reference resolve symbol graph docs - let (_, bundle, context) = try loadBundle(from: targetURL) + let (_, bundle, context) = try await loadBundle(from: targetURL) return (bundle, context) } @@ -641,7 +641,7 @@ class DefaultAvailabilityTests: XCTestCase { // Don't use default availability version. - var (bundle, context) = try setupContext( + var (bundle, context) = try await setupContext( defaultAvailability: """ name @@ -674,7 +674,7 @@ class DefaultAvailabilityTests: XCTestCase { XCTAssertEqual(renderNode.metadata.platforms?.first?.introduced, nil) // Add an extra default availability to test behaviour when mixin in source with default behaviour. - (bundle, context) = try setupContext(defaultAvailability: """ + (bundle, context) = try await setupContext(defaultAvailability: """ name iOS diff --git a/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift b/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift index e7ad1f13bc..5f13c40852 100644 --- a/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.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 @@ -23,7 +23,9 @@ class DefaultCodeBlockSyntaxTests: XCTestCase { var testBundleWithLanguageDefault: DocumentationBundle! var testBundleWithoutLanguageDefault: DocumentationBundle! - override func setUpWithError() throws { + override func setUp() async throws { + try await super.setUp() + func renderSection(for bundle: DocumentationBundle, in context: DocumentationContext) throws -> ContentRenderSection { let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/Test-Bundle/Default-Code-Listing-Syntax", fragment: nil, sourceLanguage: .swift) @@ -34,7 +36,7 @@ class DefaultCodeBlockSyntaxTests: XCTestCase { return renderNode.primaryContentSections.first! as! ContentRenderSection } - let (_, bundleWithLanguageDefault, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + let (_, bundleWithLanguageDefault, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") testBundleWithLanguageDefault = bundleWithLanguageDefault diff --git a/Tests/SwiftDocCTests/Rendering/DeprecationSummaryTests.swift b/Tests/SwiftDocCTests/Rendering/DeprecationSummaryTests.swift index 8bede3a237..9b6e4864fc 100644 --- a/Tests/SwiftDocCTests/Rendering/DeprecationSummaryTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DeprecationSummaryTests.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 @@ -30,8 +30,8 @@ class DeprecationSummaryTests: XCTestCase { /// This test verifies that a symbol's deprecation summary comes from its sidecar doc /// and it's preferred over the original deprecation note in the code docs. - func testAuthoredDeprecatedSummary() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testAuthoredDeprecatedSummary() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/init()", sourceLanguage: .swift)) // Compile docs and verify contents @@ -43,8 +43,8 @@ class DeprecationSummaryTests: XCTestCase { } /// Test for a warning when symbol is not deprecated - func testIncorrectlyAuthoredDeprecatedSummary() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in + func testIncorrectlyAuthoredDeprecatedSummary() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in // Add a sidecar file with wrong deprecated summary try """ # ``SideKit/SideClass`` @@ -79,8 +79,8 @@ class DeprecationSummaryTests: XCTestCase { /// This test verifies that a symbol's deprecation summary comes from its documentation extension file /// and it's preferred over the original deprecation note in the code docs. /// (r69719494) - func testAuthoredDeprecatedSummaryAsSoleItemInFile() throws { - let (bundle, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testAuthoredDeprecatedSummaryAsSoleItemInFile() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") let node = try context.entity( with: ResolvedTopicReference( bundleID: bundle.id, @@ -111,8 +111,8 @@ class DeprecationSummaryTests: XCTestCase { ]) } - func testSymbolDeprecatedSummary() throws { - let (bundle, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testSymbolDeprecatedSummary() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") let node = try context.entity( with: ResolvedTopicReference( bundleID: bundle.id, @@ -133,8 +133,8 @@ class DeprecationSummaryTests: XCTestCase { ]) } - func testDeprecationOverride() throws { - let (bundle, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testDeprecationOverride() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") let node = try context.entity( with: ResolvedTopicReference( bundleID: bundle.id, @@ -162,8 +162,8 @@ class DeprecationSummaryTests: XCTestCase { ]) } - func testDeprecationSummaryInDiscussionSection() throws { - let (bundle, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testDeprecationSummaryInDiscussionSection() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") let node = try context.entity( with: ResolvedTopicReference( bundleID: bundle.id, @@ -191,8 +191,8 @@ class DeprecationSummaryTests: XCTestCase { ]) } - func testDeprecationSummaryWithMultiLineCommentSymbol() throws { - let (bundle, context) = try testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + func testDeprecationSummaryWithMultiLineCommentSymbol() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") let node = try context.entity( with: ResolvedTopicReference( bundleID: bundle.id, diff --git a/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift b/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift index 6204727875..09ca5e064a 100644 --- a/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.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 @@ -14,8 +14,8 @@ import Markdown @testable import SwiftDocC class DocumentationContentRendererTests: XCTestCase { - func testReplacesTypeIdentifierSubHeadingFragmentWithIdentifierForSwift() throws { - let subHeadingFragments = try makeDocumentationContentRenderer() + func testReplacesTypeIdentifierSubHeadingFragmentWithIdentifierForSwift() async throws { + let subHeadingFragments = try await makeDocumentationContentRenderer() .subHeadingFragments(for: nodeWithSubheadingAndNavigatorVariants) XCTAssertEqual( @@ -45,8 +45,8 @@ class DocumentationContentRendererTests: XCTestCase { ) } - func testDoesNotReplaceSubHeadingFragmentsForOtherLanguagesThanSwift() throws { - let subHeadingFragments = try makeDocumentationContentRenderer() + func testDoesNotReplaceSubHeadingFragmentsForOtherLanguagesThanSwift() async throws { + let subHeadingFragments = try await makeDocumentationContentRenderer() .subHeadingFragments(for: nodeWithSubheadingAndNavigatorVariants) guard case .replace(let fragments) = subHeadingFragments.variants.first?.patch.first else { @@ -73,8 +73,8 @@ class DocumentationContentRendererTests: XCTestCase { ) } - func testReplacesTypeIdentifierNavigatorFragmentWithIdentifierForSwift() throws { - let navigatorFragments = try makeDocumentationContentRenderer() + func testReplacesTypeIdentifierNavigatorFragmentWithIdentifierForSwift() async throws { + let navigatorFragments = try await makeDocumentationContentRenderer() .navigatorFragments(for: nodeWithSubheadingAndNavigatorVariants) XCTAssertEqual( @@ -104,8 +104,8 @@ class DocumentationContentRendererTests: XCTestCase { ) } - func testDoesNotReplacesNavigatorFragmentsForOtherLanguagesThanSwift() throws { - let navigatorFragments = try makeDocumentationContentRenderer() + func testDoesNotReplacesNavigatorFragmentsForOtherLanguagesThanSwift() async throws { + let navigatorFragments = try await makeDocumentationContentRenderer() .navigatorFragments(for: nodeWithSubheadingAndNavigatorVariants) guard case .replace(let fragments) = navigatorFragments.variants.first?.patch.first else { @@ -138,8 +138,8 @@ private extension DocumentationDataVariantsTrait { } private extension DocumentationContentRendererTests { - func makeDocumentationContentRenderer() throws -> DocumentationContentRenderer { - let (bundle, context) = try testBundleAndContext() + func makeDocumentationContentRenderer() async throws -> DocumentationContentRenderer { + let (bundle, context) = try await testBundleAndContext() return DocumentationContentRenderer(documentationContext: context, bundle: bundle) } diff --git a/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift b/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift index cd8d2481bf..704d560817 100644 --- a/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.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 @@ -13,7 +13,7 @@ import XCTest import Markdown class ExternalLinkTitleTests: XCTestCase { - private func getTranslatorAndBlockContentForMarkup(_ markupSource: String) throws -> (translator: RenderNodeTranslator, content: [RenderBlockContent]) { + private func getTranslatorAndBlockContentForMarkup(_ markupSource: String) async throws -> (translator: RenderNodeTranslator, content: [RenderBlockContent]) { let document = Document(parsing: markupSource, options: [.parseBlockDirectives, .parseSymbolLinks]) let testReference = ResolvedTopicReference(bundleID: "org.swift.docc", path: "/test", sourceLanguage: .swift) let node = DocumentationNode(reference: testReference, @@ -24,21 +24,21 @@ class ExternalLinkTitleTests: XCTestCase { semantic: Semantic()) - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) let result = translator.visit(MarkupContainer(document.children)) as! [RenderBlockContent] return (translator, result) } - func testPlainTextExternalLinkTitle() throws { + func testPlainTextExternalLinkTitle() async throws { let markupSource = """ # Test This is a plain text link: [Example](https://www.example.com). """ - let (translator, content) = try getTranslatorAndBlockContentForMarkup(markupSource) + let (translator, content) = try await getTranslatorAndBlockContentForMarkup(markupSource) guard case let .paragraph(firstParagraph) = content[1] else { XCTFail("Unexpected render tree.") @@ -58,14 +58,14 @@ class ExternalLinkTitleTests: XCTestCase { XCTAssertEqual(linkReference?.titleInlineContent, expectedLinkTitle, "Plain text title should have been rendered.") } - func testEmphasisExternalLinkTitle() throws { + func testEmphasisExternalLinkTitle() async throws { let markupSource = """ # Test This is an emphasized text link: [*Apple*](https://www.example.com). """ - let (translator, content) = try getTranslatorAndBlockContentForMarkup(markupSource) + let (translator, content) = try await getTranslatorAndBlockContentForMarkup(markupSource) guard case let .paragraph(firstParagraph) = content[1] else { XCTFail("Unexpected render tree.") @@ -85,14 +85,14 @@ class ExternalLinkTitleTests: XCTestCase { XCTAssertEqual(linkReference?.titleInlineContent, expectedLinkTitle, "Emphasized text title should have been rendered.") } - func testStrongExternalLinkTitle() throws { + func testStrongExternalLinkTitle() async throws { let markupSource = """ # Test This is a strong text link: [**Apple**](https://www.example.com). """ - let (translator, content) = try getTranslatorAndBlockContentForMarkup(markupSource) + let (translator, content) = try await getTranslatorAndBlockContentForMarkup(markupSource) guard case let .paragraph(firstParagraph) = content[1] else { XCTFail("Unexpected render tree.") @@ -112,14 +112,14 @@ class ExternalLinkTitleTests: XCTestCase { XCTAssertEqual(linkReference?.titleInlineContent, expectedLinkTitle, "Strong text title should have been rendered.") } - func testCodeVoiceExternalLinkTitle() throws { + func testCodeVoiceExternalLinkTitle() async throws { let markupSource = """ # Test This is a code voice text link: [`Apple`](https://www.example.com). """ - let (translator, content) = try getTranslatorAndBlockContentForMarkup(markupSource) + let (translator, content) = try await getTranslatorAndBlockContentForMarkup(markupSource) guard case let .paragraph(firstParagraph) = content[1] else { XCTFail("Unexpected render tree.") @@ -139,14 +139,14 @@ class ExternalLinkTitleTests: XCTestCase { XCTAssertEqual(linkReference?.titleInlineContent, expectedLinkTitle, "Code voice text title should have been rendered.") } - func testMixedExternalLinkTitle() throws { + func testMixedExternalLinkTitle() async throws { let markupSource = """ # Test This is a mixed text link: [**This** *is* a `fancy` _link_ title.](https://www.example.com). """ - let (translator, content) = try getTranslatorAndBlockContentForMarkup(markupSource) + let (translator, content) = try await getTranslatorAndBlockContentForMarkup(markupSource) guard case let .paragraph(firstParagraph) = content[1] else { XCTFail("Unexpected render tree.") @@ -174,7 +174,7 @@ class ExternalLinkTitleTests: XCTestCase { } - func testMultipleLinksWithEqualURL() throws { + func testMultipleLinksWithEqualURL() async throws { let markupSource = """ # Test @@ -182,7 +182,7 @@ class ExternalLinkTitleTests: XCTestCase { This is an emphasized text link: [*Apple*](https://www.example.com). """ - let (translator, content) = try getTranslatorAndBlockContentForMarkup(markupSource) + let (translator, content) = try await getTranslatorAndBlockContentForMarkup(markupSource) guard case let .paragraph(firstParagraph) = content[1] else { XCTFail("Unexpected render tree.") diff --git a/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift b/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift index bee43f167c..7565ed4c4a 100644 --- a/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.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 @@ -14,7 +14,7 @@ import XCTest import SwiftDocCTestUtilities class HeadingAnchorTests: XCTestCase { - func testEncodeHeadingAnchor() throws { + func testEncodeHeadingAnchor() async throws { let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "Root.md", utf8Content: """ @@ -34,7 +34,7 @@ class HeadingAnchorTests: XCTestCase { """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let reference = try XCTUnwrap(context.soleRootModuleReference) let node = try context.entity(with: reference) diff --git a/Tests/SwiftDocCTests/Rendering/LinkTitleResolverTests.swift b/Tests/SwiftDocCTests/Rendering/LinkTitleResolverTests.swift index 2ec636ac11..a4f52acfbd 100644 --- a/Tests/SwiftDocCTests/Rendering/LinkTitleResolverTests.swift +++ b/Tests/SwiftDocCTests/Rendering/LinkTitleResolverTests.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 @@ -13,8 +13,8 @@ import XCTest @testable import SwiftDocC class LinkTitleResolverTests: XCTestCase { - func testSymbolTitleResolving() throws { - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testSymbolTitleResolving() async throws { + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let resolver = LinkTitleResolver(context: context, source: nil) guard let reference = context.knownIdentifiers.filter({ ref -> Bool in return ref.path.hasSuffix("MyProtocol") diff --git a/Tests/SwiftDocCTests/Rendering/MentionsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/MentionsRenderSectionTests.swift index f61244750e..3f981b3ca0 100644 --- a/Tests/SwiftDocCTests/Rendering/MentionsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/MentionsRenderSectionTests.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 @@ -15,9 +15,9 @@ import XCTest class MentionsRenderSectionTests: XCTestCase { /// Verify that the Mentioned In section is present when a symbol is mentioned, /// pointing to the correct article. - func testMentionedInSectionFull() throws { + func testMentionedInSectionFull() async throws { enableFeatureFlag(\.isMentionedInEnabled) - let (bundle, context) = try createMentionedInTestBundle() + let (bundle, context) = try await createMentionedInTestBundle() let identifier = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/MentionedIn/MyClass", @@ -38,9 +38,9 @@ class MentionsRenderSectionTests: XCTestCase { } /// If there are no qualifying mentions of a symbol, the Mentioned In section should not appear. - func testMentionedInSectionEmpty() throws { + func testMentionedInSectionEmpty() async throws { enableFeatureFlag(\.isMentionedInEnabled) - let (bundle, context) = try createMentionedInTestBundle() + let (bundle, context) = try await createMentionedInTestBundle() let identifier = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/MentionedIn/MyClass/myFunction()", diff --git a/Tests/SwiftDocCTests/Rendering/PageKindTests.swift b/Tests/SwiftDocCTests/Rendering/PageKindTests.swift index 0df89e7d21..2f8c794c85 100644 --- a/Tests/SwiftDocCTests/Rendering/PageKindTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PageKindTests.swift @@ -15,8 +15,8 @@ import XCTest class PageKindTests: XCTestCase { - private func generateRenderNodeFromBundle(bundleName: String, resolvedTopicPath: String) throws -> RenderNode { - let (bundle, context) = try testBundleAndContext(named: bundleName) + private func generateRenderNodeFromBundle(bundleName: String, resolvedTopicPath: String) async throws -> RenderNode { + let (bundle, context) = try await testBundleAndContext(named: bundleName) let reference = ResolvedTopicReference( bundleID: bundle.id, path: resolvedTopicPath, @@ -27,8 +27,8 @@ class PageKindTests: XCTestCase { return try XCTUnwrap(translator.visitArticle(article) as? RenderNode) } - func testPageKindSampleCode() throws { - let renderNode = try generateRenderNodeFromBundle( + func testPageKindSampleCode() async throws { + let renderNode = try await generateRenderNodeFromBundle( bundleName: "SampleBundle", resolvedTopicPath: "/documentation/SampleBundle/MyLocalSample" ) @@ -36,8 +36,8 @@ class PageKindTests: XCTestCase { XCTAssertEqual(renderNode.metadata.roleHeading, Metadata.PageKind.Kind.sampleCode.titleHeading) } - func testPageKindArticle() throws { - let renderNode = try generateRenderNodeFromBundle( + func testPageKindArticle() async throws { + let renderNode = try await generateRenderNodeFromBundle( bundleName: "SampleBundle", resolvedTopicPath: "/documentation/SampleBundle/MySample" ) @@ -46,8 +46,8 @@ class PageKindTests: XCTestCase { XCTAssertEqual(renderNode.metadata.roleHeading, Metadata.PageKind.Kind.article.titleHeading) } - func testPageKindDefault() throws { - let renderNode = try generateRenderNodeFromBundle( + func testPageKindDefault() async throws { + let renderNode = try await generateRenderNodeFromBundle( bundleName: "AvailabilityBundle", resolvedTopicPath: "/documentation/AvailabilityBundle/ComplexAvailable" ) @@ -55,8 +55,8 @@ class PageKindTests: XCTestCase { XCTAssertEqual(renderNode.metadata.roleHeading, "Article") } - func testPageKindReference() throws { - let renderNode = try generateRenderNodeFromBundle( + func testPageKindReference() async throws { + let renderNode = try await generateRenderNodeFromBundle( bundleName: "SampleBundle", resolvedTopicPath: "/documentation/SomeSample" ) @@ -64,7 +64,7 @@ class PageKindTests: XCTestCase { XCTAssertEqual(sampleReference.role, RenderMetadata.Role.sampleCode.rawValue) } - func testValidMetadataWithOnlyPageKind() throws { + func testValidMetadataWithOnlyPageKind() async throws { let source = """ @Metadata { @PageKind(article) @@ -75,7 +75,7 @@ class PageKindTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "SampleBundle") + let (bundle, _) = try await testBundleAndContext(named: "SampleBundle") directive.map { directive in var problems = [Problem]() @@ -90,8 +90,8 @@ class PageKindTests: XCTestCase { // Verify that we assign the `Collection` role to the root article of a // documentation catalog that contains only one article. - func testRoleForSingleArticleCatalog() throws { - let renderNode = try generateRenderNodeFromBundle( + func testRoleForSingleArticleCatalog() async throws { + let renderNode = try await generateRenderNodeFromBundle( bundleName: "BundleWithSingleArticle", resolvedTopicPath: "/documentation/Article" ) @@ -100,8 +100,8 @@ class PageKindTests: XCTestCase { // Verify we assign the `Collection` role to the root article of an article-only // documentation catalog that doesn't include manual curation - func testRoleForArticleOnlyCatalogWithNoCuration() throws { - let renderNode = try generateRenderNodeFromBundle( + func testRoleForArticleOnlyCatalogWithNoCuration() async throws { + let renderNode = try await generateRenderNodeFromBundle( bundleName: "BundleWithArticlesNoCurated", resolvedTopicPath: "/documentation/Article" ) diff --git a/Tests/SwiftDocCTests/Rendering/PlatformAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/PlatformAvailabilityTests.swift index c36bc68f3d..59e48df45a 100644 --- a/Tests/SwiftDocCTests/Rendering/PlatformAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PlatformAvailabilityTests.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 @@ -33,8 +33,8 @@ class PlatformAvailabilityTests: XCTestCase { } /// Ensure that adding `@Available` directives in an article causes the final RenderNode to contain the appropriate availability data. - func testPlatformAvailabilityFromArticle() throws { - let (bundle, context) = try testBundleAndContext(named: "AvailabilityBundle") + func testPlatformAvailabilityFromArticle() async throws { + let (bundle, context) = try await testBundleAndContext(named: "AvailabilityBundle") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/AvailableArticle", @@ -52,8 +52,8 @@ class PlatformAvailabilityTests: XCTestCase { } /// Ensure that adding `@Available` directives in an extension file overrides the symbol's availability. - func testPlatformAvailabilityFromExtension() throws { - let (bundle, context) = try testBundleAndContext(named: "AvailabilityBundle") + func testPlatformAvailabilityFromExtension() async throws { + let (bundle, context) = try await testBundleAndContext(named: "AvailabilityBundle") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/MyKit/MyClass", @@ -70,8 +70,8 @@ class PlatformAvailabilityTests: XCTestCase { XCTAssert(iosAvailability.isBeta != true) } - func testMultiplePlatformAvailabilityFromArticle() throws { - let (bundle, context) = try testBundleAndContext(named: "AvailabilityBundle") + func testMultiplePlatformAvailabilityFromArticle() async throws { + let (bundle, context) = try await testBundleAndContext(named: "AvailabilityBundle") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/AvailabilityBundle/ComplexAvailable", @@ -98,8 +98,8 @@ class PlatformAvailabilityTests: XCTestCase { }) } - func testArbitraryPlatformAvailability() throws { - let (bundle, context) = try testBundleAndContext(named: "AvailabilityBundle") + func testArbitraryPlatformAvailability() async throws { + let (bundle, context) = try await testBundleAndContext(named: "AvailabilityBundle") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/AvailabilityBundle/ArbitraryPlatforms", @@ -123,8 +123,8 @@ class PlatformAvailabilityTests: XCTestCase { } // Test that the Info.plist default availability does not affect the deprecated/unavailable availabilities provided by the symbol graph. - func testAvailabilityParserWithInfoPlistDefaultAvailability() throws { - let (bundle, context) = try testBundleAndContext(named: "AvailabilityOverrideBundle") + func testAvailabilityParserWithInfoPlistDefaultAvailability() async throws { + let (bundle, context) = try await testBundleAndContext(named: "AvailabilityOverrideBundle") let reference = ResolvedTopicReference( bundleID: bundle.id, @@ -160,11 +160,11 @@ class PlatformAvailabilityTests: XCTestCase { } /// Ensure that adding `@Available` directives for platform versions marked as beta in an article causes the final RenderNode to contain the appropriate availability data. - func testBetaPlatformAvailabilityFromArticle() throws { + func testBetaPlatformAvailabilityFromArticle() async throws { let platformMetadata = [ "iOS": PlatformVersion(VersionTriplet(16, 0, 0), beta: true), ] - let (bundle, context) = try testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) + let (bundle, context) = try await testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/AvailableArticle", @@ -181,13 +181,13 @@ class PlatformAvailabilityTests: XCTestCase { XCTAssert(iosAvailability.isBeta == true) } - func testMultipleBetaPlatformAvailabilityFromArticle() throws { + func testMultipleBetaPlatformAvailabilityFromArticle() async throws { let platformMetadata = [ "iOS": PlatformVersion(VersionTriplet(15, 0, 0), beta: true), "macOS": PlatformVersion(VersionTriplet(12, 0, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(7, 0, 0), beta: true), ] - let (bundle, context) = try testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) + let (bundle, context) = try await testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/AvailabilityBundle/ComplexAvailable", @@ -215,11 +215,11 @@ class PlatformAvailabilityTests: XCTestCase { } /// Ensure that adding `@Available` directives in an extension file overrides the symbol's availability. - func testBetaPlatformAvailabilityFromExtension() throws { + func testBetaPlatformAvailabilityFromExtension() async throws { let platformMetadata = [ "iOS": PlatformVersion(VersionTriplet(16, 0, 0), beta: true), ] - let (bundle, context) = try testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) + let (bundle, context) = try await testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/MyKit/MyClass", @@ -237,11 +237,11 @@ class PlatformAvailabilityTests: XCTestCase { } - func testBundleWithConfiguredPlatforms(named testBundleName: String, platformMetadata: [String : PlatformVersion]) throws -> (DocumentationBundle, DocumentationContext) { + func testBundleWithConfiguredPlatforms(named testBundleName: String, platformMetadata: [String : PlatformVersion]) async throws -> (DocumentationBundle, DocumentationContext) { let bundleURL = try XCTUnwrap(Bundle.module.url(forResource: testBundleName, withExtension: "docc", subdirectory: "Test Bundles")) var configuration = DocumentationContext.Configuration() configuration.externalMetadata.currentPlatforms = platformMetadata - let (_, bundle, context) = try loadBundle(from: bundleURL, configuration: configuration) + let (_, bundle, context) = try await loadBundle(from: bundleURL, configuration: configuration) return (bundle, context) } diff --git a/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift index 7963b6d151..bfe5eddd00 100644 --- a/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.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 @@ -16,9 +16,9 @@ import SwiftDocCTestUtilities class PropertyListDetailsRenderSectionTests: XCTestCase { - func testDecoding() throws { + func testDecoding() async throws { - func getPlistDetailsSection(arrayMode: any CustomStringConvertible, baseType: any CustomStringConvertible, rawKey: any CustomStringConvertible) throws -> PropertyListDetailsRenderSection { + func getPlistDetailsSection(arrayMode: any CustomStringConvertible, baseType: any CustomStringConvertible, rawKey: any CustomStringConvertible) async throws -> PropertyListDetailsRenderSection { let symbolJSON = """ { "accessLevel" : "public", @@ -55,7 +55,7 @@ class PropertyListDetailsRenderSectionTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "MyModule.symbols.json", utf8Content: symbolGraphString) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let node = try XCTUnwrap(context.documentationCache["plist:propertylistkey"]) let converter = DocumentationNodeConverter(bundle: bundle, context: context) let renderNode = converter.convert(node) @@ -63,8 +63,9 @@ class PropertyListDetailsRenderSectionTests: XCTestCase { } // Assert that the Details section is correctly generated when passing valid values into the plistDetails JSON object. + let withArrayMode = try await getPlistDetailsSection(arrayMode: true, baseType: "\"string\"", rawKey: "\"property-list-key\"") XCTAssertEqual( - try getPlistDetailsSection(arrayMode: true, baseType: "\"string\"", rawKey: "\"property-list-key\""), + withArrayMode, PropertyListDetailsRenderSection( details: PropertyListDetailsRenderSection.Details( rawKey: "property-list-key", @@ -76,8 +77,9 @@ class PropertyListDetailsRenderSectionTests: XCTestCase { ) ) + let withoutArrayMode = try await getPlistDetailsSection(arrayMode: false, baseType: "\"string\"", rawKey: "\"property-list-key\"") XCTAssertEqual( - try getPlistDetailsSection(arrayMode: false, baseType: "\"string\"", rawKey: "\"property-list-key\""), + withoutArrayMode, PropertyListDetailsRenderSection( details: PropertyListDetailsRenderSection.Details( rawKey: "property-list-key", @@ -91,12 +93,14 @@ class PropertyListDetailsRenderSectionTests: XCTestCase { // Assert that the Details section does not decode unsupported values. do { - _ = try getPlistDetailsSection(arrayMode: true, baseType: true, rawKey: "\"property-list-key\"") + _ = try await getPlistDetailsSection(arrayMode: true, baseType: true, rawKey: "\"property-list-key\"") + XCTFail("Didn't raise an error") } catch { XCTAssertTrue(error.localizedDescription.contains("isn’t in the correct format")) } do { - _ = try getPlistDetailsSection(arrayMode: true, baseType: "\"string\"", rawKey: 1) + _ = try await getPlistDetailsSection(arrayMode: true, baseType: "\"string\"", rawKey: 1) + XCTFail("Didn't raise an error") } catch { XCTAssertTrue(error.localizedDescription.contains("isn’t in the correct format")) } diff --git a/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift b/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift index 4ab337313e..8bb0b2c915 100644 --- a/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.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 @@ -312,9 +312,8 @@ class RESTSymbolsTests: XCTestCase { AssertRoundtrip(for: object) } - func testReferenceOfEntitlementWithKeyName() throws { - - func createDocumentationTopicRenderReferenceForSymbol(keyCustomName: String?, extraFiles: [TextFile] = []) throws -> TopicRenderReference { + func testReferenceOfEntitlementWithKeyName() async throws { + func createDocumentationTopicRenderReferenceForSymbol(keyCustomName: String?, extraFiles: [TextFile] = []) async throws -> TopicRenderReference { let someSymbol = makeSymbol( id: "plist-key-symbolname", kind: .init(rawValue: "enum"), @@ -331,7 +330,7 @@ class RESTSymbolsTests: XCTestCase { )), ] + extraFiles ) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let moduleReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName", sourceLanguage: .swift) let moduleSymbol = try XCTUnwrap((try context.entity(with: moduleReference)).semantic as? Symbol) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: moduleReference) @@ -340,21 +339,23 @@ class RESTSymbolsTests: XCTestCase { } // The symbol has a custom title. - var propertyListKeyNames = try XCTUnwrap(createDocumentationTopicRenderReferenceForSymbol(keyCustomName: "Symbol Custom Title").propertyListKeyNames) + let topicReferenceWithCustomKeyName = try await createDocumentationTopicRenderReferenceForSymbol(keyCustomName: "Symbol Custom Title") + var propertyListKeyNames = try XCTUnwrap(topicReferenceWithCustomKeyName.propertyListKeyNames) // Check that the reference contains the key symbol name. XCTAssertEqual(propertyListKeyNames.titleStyle, .useRawKey) XCTAssertEqual(propertyListKeyNames.rawKey, "plist-key-symbolname") XCTAssertEqual(propertyListKeyNames.displayName, "Symbol Custom Title") // The symbol does not have a custom title. - propertyListKeyNames = try XCTUnwrap(createDocumentationTopicRenderReferenceForSymbol(keyCustomName: nil).propertyListKeyNames) + let topicReferenceWithoutCustomKeyName = try await createDocumentationTopicRenderReferenceForSymbol(keyCustomName: nil) + propertyListKeyNames = try XCTUnwrap(topicReferenceWithoutCustomKeyName.propertyListKeyNames) // Check that the reference does not contain the key symbol name. XCTAssertEqual(propertyListKeyNames.titleStyle, .useRawKey) XCTAssertEqual(propertyListKeyNames.rawKey, "plist-key-symbolname") XCTAssertNil(propertyListKeyNames.displayName) // The symbol has a custom title and is extended via markdown. - var referenceNode = try XCTUnwrap(createDocumentationTopicRenderReferenceForSymbol( + var referenceNode = try await createDocumentationTopicRenderReferenceForSymbol( keyCustomName: "Symbol Custom Title", extraFiles: [ TextFile(name: "plist-key-symbolname.md", utf8Content: @@ -365,7 +366,7 @@ class RESTSymbolsTests: XCTestCase { """ ) ] - )) + ) propertyListKeyNames = try XCTUnwrap(referenceNode.propertyListKeyNames) // Check that the reference contains the raw key and title matches the // key name. @@ -375,7 +376,7 @@ class RESTSymbolsTests: XCTestCase { XCTAssertEqual(propertyListKeyNames.displayName, "Symbol Custom Title") // The symbol has a custom title and is the markdown defines a `Display Name` directive. - referenceNode = try XCTUnwrap(createDocumentationTopicRenderReferenceForSymbol( + referenceNode = try await createDocumentationTopicRenderReferenceForSymbol( keyCustomName: "Symbol Custom Title", extraFiles: [ TextFile(name: "plist-key-symbolname.md", utf8Content: @@ -390,7 +391,7 @@ class RESTSymbolsTests: XCTestCase { """ ) ] - )) + ) propertyListKeyNames = try XCTUnwrap(referenceNode.propertyListKeyNames) // Check that the reference contains the raw key and the title matches the // markdown display name. @@ -400,7 +401,7 @@ class RESTSymbolsTests: XCTestCase { XCTAssertEqual(propertyListKeyNames.displayName, "Symbol Custom Title") // The symbol does not have a custom title and is extended via markdown using a `Display Name` directive. - referenceNode = try createDocumentationTopicRenderReferenceForSymbol( + referenceNode = try await createDocumentationTopicRenderReferenceForSymbol( keyCustomName: nil, extraFiles: [ TextFile(name: "plist-key-symbolname.md", utf8Content: diff --git a/Tests/SwiftDocCTests/Rendering/RenderBlockContent_ThematicBreakTests.swift b/Tests/SwiftDocCTests/Rendering/RenderBlockContent_ThematicBreakTests.swift index 3fe7834d70..0ec1d9c8db 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderBlockContent_ThematicBreakTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderBlockContent_ThematicBreakTests.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 @@ -25,7 +25,7 @@ class RenderBlockContent_ThematicBreakTests: XCTestCase { } // MARK: - Thematic Break Markdown Variants - func testThematicBreakVariants() throws { + func testThematicBreakVariants() async throws { let source = """ --- @@ -38,7 +38,7 @@ class RenderBlockContent_ThematicBreakTests: XCTestCase { XCTAssertEqual(markup.childCount, 3) - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) @@ -52,7 +52,7 @@ class RenderBlockContent_ThematicBreakTests: XCTestCase { XCTAssertEqual(expectedContent, renderContent) } - func testThematicBreakVariantsWithSpaces() throws { + func testThematicBreakVariantsWithSpaces() async throws { let source = """ - - - @@ -65,7 +65,7 @@ class RenderBlockContent_ThematicBreakTests: XCTestCase { XCTAssertEqual(markup.childCount, 3) - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) @@ -79,7 +79,7 @@ class RenderBlockContent_ThematicBreakTests: XCTestCase { XCTAssertEqual(expectedContent, renderContent) } - func testThematicBreakMoreThanThreeCharacters() throws { + func testThematicBreakMoreThanThreeCharacters() async throws { let source = """ ---- @@ -95,7 +95,7 @@ class RenderBlockContent_ThematicBreakTests: XCTestCase { XCTAssertEqual(markup.childCount, 6) - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) diff --git a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift index 788d7c7386..8a23b1324a 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -14,8 +14,8 @@ import Markdown import XCTest class RenderContentCompilerTests: XCTestCase { - func testLinkOverrideTitle() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testLinkOverrideTitle() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ @@ -132,8 +132,8 @@ class RenderContentCompilerTests: XCTestCase { } } - func testLineBreak() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testLineBreak() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" @@ -197,8 +197,8 @@ class RenderContentCompilerTests: XCTestCase { } } - func testThematicBreak() throws { - let (bundle, context) = try testBundleAndContext() + func testThematicBreak() async throws { + let (bundle, context) = try await testBundleAndContext() var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) diff --git a/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift b/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift index d27d65ed5f..e580a089af 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.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 @@ -69,11 +69,11 @@ class RenderMetadataTests: XCTestCase { XCTAssertEqual(metadata.symbolKind, "plum") } - func testAllPagesHaveTitleMetadata() throws { + func testAllPagesHaveTitleMetadata() async throws { var typesOfPages = [Tutorial.self, TutorialTableOfContents.self, Article.self, TutorialArticle.self, Symbol.self] for bundleName in ["LegacyBundle_DoNotUseInNewTests"] { - let (bundle, context) = try testBundleAndContext(named: bundleName) + let (bundle, context) = try await testBundleAndContext(named: bundleName) let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) @@ -92,8 +92,8 @@ class RenderMetadataTests: XCTestCase { /// Test that a bystanders symbol graph is loaded, symbols are merged into the main module /// and the bystanders are included in the render node metadata. - func testRendersBystandersFromSymbolGraph() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in + func testRendersBystandersFromSymbolGraph() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in let bystanderSymbolGraphURL = Bundle.module.url( forResource: "MyKit@Foundation@_MyKit_Foundation.symbols", withExtension: "json", subdirectory: "Test Resources")! try FileManager.default.copyItem(at: bystanderSymbolGraphURL, to: url.appendingPathComponent("MyKit@Foundation@_MyKit_Foundation.symbols.json")) @@ -116,8 +116,8 @@ class RenderMetadataTests: XCTestCase { /// Test that when a bystanders symbol graph is loaded that extends a different module, that /// those symbols correctly report the modules when rendered. - func testRendersBystanderExtensionsFromSymbolGraph() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in + func testRendersBystanderExtensionsFromSymbolGraph() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in let baseSymbolGraphURL = Bundle.module.url( forResource: "BaseKit.symbols", withExtension: "json", subdirectory: "Test Resources")! try FileManager.default.copyItem(at: baseSymbolGraphURL, to: url.appendingPathComponent("BaseKit.symbols.json")) @@ -141,8 +141,8 @@ class RenderMetadataTests: XCTestCase { XCTAssertEqual(renderNode.metadata.modules?.first?.relatedModules, ["BaseKit"]) } - func testRendersExtensionSymbolsWithBystanderModules() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "BundleWithRelativePathAmbiguity") { root in + func testRendersExtensionSymbolsWithBystanderModules() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "BundleWithRelativePathAmbiguity") { root in // We don't want the external target to be part of the archive as that is not // officially supported yet. try FileManager.default.removeItem(at: root.appendingPathComponent("Dependency.symbols.json")) diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift index 006d0522af..31504330f9 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.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 @@ -17,8 +17,8 @@ import SwiftDocCTestUtilities class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { - func testIdentifierVariants() throws { - try assertMultiVariantSymbol( + func testIdentifierVariants() async throws { + try await assertMultiVariantSymbol( configureContext: { context, resolvedTopicReference in var documentationNode = try XCTUnwrap(context.documentationCache[resolvedTopicReference]) documentationNode.availableSourceLanguages = [.swift, .objectiveC] @@ -33,8 +33,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testMultipleModules() throws { - try assertMultiVariantSymbol( + func testMultipleModules() async throws { + try await assertMultiVariantSymbol( configureContext: { context, resolvedTopicReference in let moduleReference = ResolvedTopicReference(bundleID: resolvedTopicReference.bundleID, path: "/documentation/MyKit", sourceLanguage: .swift) context.documentationCache[moduleReference]?.name = .conceptual(title: "Custom Module Title") @@ -49,8 +49,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testMultipleModulesWithBystanderModule() throws { - try assertMultiVariantSymbol( + func testMultipleModulesWithBystanderModule() async throws { + try await assertMultiVariantSymbol( configureContext: { context, resolvedTopicReference in let moduleReference = ResolvedTopicReference(bundleID: resolvedTopicReference.bundleID, path: "/documentation/MyKit", sourceLanguage: .swift) context.documentationCache[moduleReference]?.name = .conceptual(title: "Custom Module Title") @@ -77,8 +77,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { } /// Make sure that when a symbol has `crossImportOverlayModule` information, that module name is used instead of its `moduleReference`. - func testMultipleModulesWithDifferentBystanderModule() throws { - try assertMultiVariantSymbol( + func testMultipleModulesWithDifferentBystanderModule() async throws { + try await assertMultiVariantSymbol( configureContext: { context, resolvedTopicReference in let moduleReference = ResolvedTopicReference(bundleID: resolvedTopicReference.bundleID, path: "/documentation/MyKit", sourceLanguage: .swift) context.documentationCache[moduleReference]?.name = .conceptual(title: "Extended Module Title") @@ -104,8 +104,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testExtendedModuleVariants() throws { - try assertMultiVariantSymbol( + func testExtendedModuleVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in let newConstraint = SymbolGraph.Symbol.Swift.GenericConstraint( kind: SymbolGraph.Symbol.Swift.GenericConstraint.Kind.sameType, @@ -123,8 +123,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testPlatformsVariantsDefaultAvailability() throws { - try assertMultiVariantSymbol( + func testPlatformsVariantsDefaultAvailability() async throws { + try await assertMultiVariantSymbol( configureContext: { context, resolvedTopicReference in let moduleReference = ResolvedTopicReference(bundleID: resolvedTopicReference.bundleID, path: "/documentation/MyKit", sourceLanguage: .swift) context.documentationCache[moduleReference]?.name = .conceptual(title: "Custom Module Title") @@ -142,8 +142,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testPlatformsVariantsCustomAvailability() throws { - try assertMultiVariantSymbol( + func testPlatformsVariantsCustomAvailability() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.availabilityVariants[.swift] = SymbolGraph.Symbol.Availability(availability: [ SymbolGraph.Symbol.Availability.AvailabilityItem( @@ -184,8 +184,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testRequiredVariants() throws { - try assertMultiVariantSymbol( + func testRequiredVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.isRequiredVariants[.swift] = false symbol.isRequiredVariants[.objectiveC] = true @@ -199,8 +199,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testRoleHeadingVariants() throws { - try assertMultiVariantSymbol( + func testRoleHeadingVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.roleHeadingVariants[.swift] = "Swift Title" symbol.roleHeadingVariants[.objectiveC] = "Objective-C Title" @@ -214,8 +214,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testTitleVariants() throws { - try assertMultiVariantSymbol( + func testTitleVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.titleVariants[.swift] = "Swift Title" symbol.titleVariants[.objectiveC] = "Objective-C Title" @@ -229,8 +229,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testExternalIDVariants() throws { - try assertMultiVariantSymbol( + func testExternalIDVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.roleHeadingVariants[.swift] = "Swift Title" symbol.roleHeadingVariants[.objectiveC] = "Objective-C Title" @@ -244,8 +244,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testSymbolKindVariants() throws { - try assertMultiVariantSymbol( + func testSymbolKindVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.kindVariants[.swift] = .init(rawIdentifier: "swift.method", displayName: "Swift Kind") symbol.kindVariants[.objectiveC] = .init(rawIdentifier: "objc.func", displayName: "Objective-C Kind") @@ -259,8 +259,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testFragmentsVariants() throws { - try assertMultiVariantSymbol( + func testFragmentsVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.subHeadingVariants[.swift] = [ .init(kind: .keyword, spelling: "swift", preciseIdentifier: nil) @@ -291,8 +291,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testNavigatorTitleVariants() throws { - try assertMultiVariantSymbol( + func testNavigatorTitleVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.navigatorVariants[.swift] = [ .init(kind: .keyword, spelling: "swift", preciseIdentifier: nil) @@ -320,7 +320,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testVariants() throws { + func testVariants() async throws { let expectedVariants = [ RenderNode.Variant( traits: [.interfaceLanguage("swift")], @@ -332,7 +332,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ), ] - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureContext: { context, resolvedTopicReference in var documentationNode = try XCTUnwrap(context.documentationCache[resolvedTopicReference]) documentationNode.availableSourceLanguages = [.swift, .objectiveC] @@ -347,8 +347,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testAbstractVariants() throws { - try assertMultiVariantSymbol( + func testAbstractVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.abstractSectionVariants[.swift] = AbstractSection( paragraph: Paragraph(Text("Swift abstract")) @@ -367,14 +367,14 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testDeclarationsSectionVariants() throws { + func testDeclarationsSectionVariants() async throws { func declarationSection(in renderNode: RenderNode) throws -> DeclarationRenderSection { try XCTUnwrap( (renderNode.primaryContentSections.first as? DeclarationsRenderSection)?.declarations.first ) } - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.declarationVariants[.swift] = [ [.macOS]: SymbolGraph.Symbol.DeclarationFragments( @@ -413,7 +413,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testReturnsSectionVariants() throws { + func testReturnsSectionVariants() async throws { func returnsSection(in renderNode: RenderNode) throws -> ContentRenderSection { let returnsSectionIndex = 1 @@ -425,7 +425,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { return try XCTUnwrap(renderNode.primaryContentSections[returnsSectionIndex] as? ContentRenderSection) } - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.returnsSectionVariants[.swift] = ReturnsSection( content: [Paragraph(Text("Swift Returns Section"))] @@ -458,7 +458,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testParametersSectionVariants() throws { + func testParametersSectionVariants() async throws { func parametersSection(in renderNode: RenderNode) throws -> ParametersRenderSection { let parametersSectionIndex = 1 @@ -470,7 +470,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { return try XCTUnwrap(renderNode.primaryContentSections[parametersSectionIndex] as? ParametersRenderSection) } - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.parametersSectionVariants[.swift] = ParametersSection( parameters: [Parameter(name: "Swift parameter", contents: [])] @@ -501,7 +501,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testDictionaryKeysSection() throws { + func testDictionaryKeysSection() async throws { let keySymbol = makeSymbol(id: "some-key", language: .data, kind: .dictionaryKey, pathComponents: ["SomeDictionary", "SomeKey"]) let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ @@ -510,7 +510,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ])) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) let dictionaryReference = moduleReference.appendingPath("SomeDictionary") @@ -550,12 +550,12 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { } } - func testDiscussionSectionVariants() throws { + func testDiscussionSectionVariants() async throws { func discussionSection(in renderNode: RenderNode) throws -> ContentRenderSection { return try XCTUnwrap(renderNode.primaryContentSections.mapFirst { $0 as? ContentRenderSection }) } - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.discussionVariants[.swift] = DiscussionSection( content: [Paragraph(Text("Swift Discussion"))] @@ -590,7 +590,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testSourceFileURIVariants() throws { + func testSourceFileURIVariants() async throws { func makeLocation(uri: String) throws -> SymbolGraph.Symbol.Location { let location = """ { @@ -605,7 +605,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { return try JSONDecoder().decode(SymbolGraph.Symbol.Location.self, from: location) } - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.locationVariants[.swift] = try makeLocation(uri: "Swift URI") symbol.locationVariants[.objectiveC] = try makeLocation(uri: "Objective-C URI") @@ -622,8 +622,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testSymbolAccessLevelVariants() throws { - try assertMultiVariantSymbol( + func testSymbolAccessLevelVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.accessLevelVariants[.swift] = "Swift access level" symbol.accessLevelVariants[.objectiveC] = "Objective-C access level" @@ -640,8 +640,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testRelationshipSectionsVariants() throws { - try assertMultiVariantSymbol( + func testRelationshipSectionsVariants() async throws { + try await assertMultiVariantSymbol( configureContext: { context, _ in // Set up an Objective-C title for MyProtocol. @@ -691,8 +691,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testDoesNotEmitObjectiveCRelationshipsForTopicThatOnlyHasSwiftRelationships() throws { - try assertMultiVariantSymbol( + func testDoesNotEmitObjectiveCRelationshipsForTopicThatOnlyHasSwiftRelationships() async throws { + try await assertMultiVariantSymbol( configureContext: { context, _ in // Set up an Objective-C title for MyProtocol. @@ -732,8 +732,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testTopicsSectionVariants() throws { - try assertMultiVariantSymbol( + func testTopicsSectionVariants() async throws { + try await assertMultiVariantSymbol( configureContext: { context, reference in try makeSymbolAvailableInSwiftAndObjectiveC( symbolPath: "/documentation/MyKit/MyProtocol", @@ -777,8 +777,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testEncodesNilTopicsSectionsForArticleVariantIfDefaultIsNonEmpty() throws { - try assertMultiVariantArticle( + func testEncodesNilTopicsSectionsForArticleVariantIfDefaultIsNonEmpty() async throws { + try await assertMultiVariantArticle( configureArticle: { article in article.automaticTaskGroups = [] article.topics = makeTopicsSection( @@ -814,8 +814,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testEncodesNilTopicsSectionsForSymbolVariantIfDefaultIsNonEmpty() throws { - try assertMultiVariantSymbol( + func testEncodesNilTopicsSectionsForSymbolVariantIfDefaultIsNonEmpty() async throws { + try await assertMultiVariantSymbol( assertOriginalRenderNode: { renderNode in XCTAssertEqual(renderNode.topicSections.count, 6) }, @@ -834,8 +834,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testTopicsSectionVariantsNoUserProvidedTopics() throws { - try assertMultiVariantSymbol( + func testTopicsSectionVariantsNoUserProvidedTopics() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.automaticTaskGroupsVariants[.fallback] = [] symbol.topicsVariants[.fallback] = nil @@ -861,7 +861,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testDefaultImplementationsSectionsVariants() throws { + func testDefaultImplementationsSectionsVariants() async throws { func createDefaultImplementationsSection(path: String) -> DefaultImplementationsSection { DefaultImplementationsSection( targetFallbacks: [:], @@ -882,7 +882,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.defaultImplementationsVariants[.swift] = createDefaultImplementationsSection( path: "/documentation/MyKit/MyProtocol" @@ -923,7 +923,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testSeeAlsoSectionVariants() throws { + func testSeeAlsoSectionVariants() async throws { func makeSeeAlsoSection(destination: String) -> SeeAlsoSection { SeeAlsoSection(content: [ UnorderedList( @@ -932,7 +932,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ]) } - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureContext: { context, reference in try makeSymbolAvailableInSwiftAndObjectiveC( symbolPath: "/documentation/MyKit/MyProtocol", @@ -972,7 +972,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testDoesNotEmitObjectiveCSeeAlsoIfEmpty() throws { + func testDoesNotEmitObjectiveCSeeAlsoIfEmpty() async throws { func makeSeeAlsoSection(destination: String) -> SeeAlsoSection { SeeAlsoSection(content: [ UnorderedList( @@ -981,7 +981,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ]) } - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.seeAlsoVariants[.swift] = makeSeeAlsoSection( destination: "doc://org.swift.docc.example/documentation/MyKit/MyProtocol" @@ -996,8 +996,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - func testDeprecationSummaryVariants() throws { - try assertMultiVariantSymbol( + func testDeprecationSummaryVariants() async throws { + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.deprecatedSummaryVariants[.swift] = DeprecatedSection( text: "Swift Deprecation Variant" @@ -1047,8 +1047,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { /// The `DeprecatedInOneLanguageOnly` catalog defines a symbol `MyClass` which has availability /// annotations in Swift but not in Objective-C. This test verifies that the Swift render node for `MyClass` does /// indeed include availability information, but that the Objective-C one doesn't. - func testDoesNotInheritAvailabilityFromOtherLanguage() throws { - try assertMultiVariantSymbol( + func testDoesNotInheritAvailabilityFromOtherLanguage() async throws { + try await assertMultiVariantSymbol( bundleName: "DeprecatedInOneLanguageOnly", assertOriginalRenderNode: { renderNode in XCTAssert(renderNode.metadata.platforms?.isEmpty == false) @@ -1062,7 +1062,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { } /// Tests that deprecation summaries only show up on variants of pages that are actually deprecated. - func testIncludesDeprecationSummaryOnlyInDeprecatedVariantOfSymbol() throws { + func testIncludesDeprecationSummaryOnlyInDeprecatedVariantOfSymbol() async throws { let deprecatedOnOnePlatform = SymbolGraph.Symbol.Availability.AvailabilityItem( domain: .init(rawValue: SymbolGraph.Symbol.Availability.Domain.macOS), introducedVersion: .init(major: 15, minor: 0, patch: 0), @@ -1088,7 +1088,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) for deprecatedAvailability in [deprecatedOnOnePlatform, unconditionallyDeprecated] { - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureSymbol: { symbol in symbol.deprecatedSummaryVariants[.swift] = DeprecatedSection( text: "Deprecation summary" @@ -1113,7 +1113,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { } } - func testTopicRenderReferenceVariants() throws { + func testTopicRenderReferenceVariants() async throws { func myFunctionReference(in renderNode: RenderNode) throws -> TopicRenderReference { return try XCTUnwrap( renderNode.references[ @@ -1122,7 +1122,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } - try assertMultiVariantSymbol( + try await assertMultiVariantSymbol( configureContext: { context, _ in // Set up a symbol with variants. @@ -1166,8 +1166,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { assertOriginalRenderNode: (RenderNode) throws -> (), assertAfterApplyingVariant: (RenderNode) throws -> () = { _ in }, assertDataAfterApplyingVariant: (Data) throws -> () = { _ in } - ) throws { - let (_, bundle, context) = try testBundleAndContext(copying: bundleName) + ) async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: bundleName) let identifier = ResolvedTopicReference( bundleID: bundle.id, @@ -1203,8 +1203,8 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { assertOriginalRenderNode: (RenderNode) throws -> (), assertAfterApplyingVariant: (RenderNode) throws -> () = { _ in }, assertDataAfterApplyingVariant: (Data) throws -> () = { _ in } - ) throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + ) async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") let identifier = ResolvedTopicReference( bundleID: bundle.id, diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift index 12b479f98b..c2fe00d4fe 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift @@ -16,8 +16,8 @@ import Markdown import SymbolKit class RenderNodeTranslatorTests: XCTestCase { - private func findDiscussion(forSymbolPath: String, configureBundle: ((URL) throws -> Void)? = nil) throws -> ContentRenderSection? { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", configureBundle: configureBundle) + private func findDiscussion(forSymbolPath: String, configureBundle: ((URL) throws -> Void)? = nil) async throws -> ContentRenderSection? { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", configureBundle: configureBundle) let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: forSymbolPath, sourceLanguage: .swift)) @@ -33,8 +33,8 @@ class RenderNodeTranslatorTests: XCTestCase { return discussion } - private func findParagraph(withPrefix: String, forSymbolPath: String) throws -> [RenderInlineContent]? { - guard let discussion = try findDiscussion(forSymbolPath: forSymbolPath) else { + private func findParagraph(withPrefix: String, forSymbolPath: String) async throws -> [RenderInlineContent]? { + guard let discussion = try await findDiscussion(forSymbolPath: forSymbolPath) else { return nil } @@ -59,8 +59,8 @@ class RenderNodeTranslatorTests: XCTestCase { return paragraph } - func testResolvingSymbolLinks() throws { - guard let paragraph = try findParagraph(withPrefix: "Exercise links to symbols", forSymbolPath: "/documentation/MyKit/MyProtocol") else { + func testResolvingSymbolLinks() async throws { + guard let paragraph = try await findParagraph(withPrefix: "Exercise links to symbols", forSymbolPath: "/documentation/MyKit/MyProtocol") else { XCTFail("Failed to fetch test content") return } @@ -78,8 +78,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(references.count, 2) } - func testExternalSymbolLink() throws { - guard let paragraph = try findParagraph(withPrefix: "Exercise unresolved symbols", forSymbolPath: "/documentation/MyKit/MyProtocol") else { + func testExternalSymbolLink() async throws { + guard let paragraph = try await findParagraph(withPrefix: "Exercise unresolved symbols", forSymbolPath: "/documentation/MyKit/MyProtocol") else { XCTFail("Failed to fetch test content") return } @@ -97,8 +97,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(references.count, 1) } - func testOrderedAndUnorderedList() throws { - guard let discussion = try findDiscussion(forSymbolPath: "/documentation/MyKit/MyProtocol") else { + func testOrderedAndUnorderedList() async throws { + guard let discussion = try await findDiscussion(forSymbolPath: "/documentation/MyKit/MyProtocol") else { return } @@ -144,8 +144,8 @@ class RenderNodeTranslatorTests: XCTestCase { })) } - func testAutomaticOverviewAndDiscussionHeadings() throws { - guard let myFunctionDiscussion = try findDiscussion(forSymbolPath: "/documentation/MyKit/MyClass/myFunction()", configureBundle: { url in + func testAutomaticOverviewAndDiscussionHeadings() async throws { + guard let myFunctionDiscussion = try await findDiscussion(forSymbolPath: "/documentation/MyKit/MyClass/myFunction()", configureBundle: { url in let sidecarURL = url.appendingPathComponent("/documentation/myFunction.md") try """ # ``MyKit/MyClass/myFunction()`` @@ -164,7 +164,7 @@ class RenderNodeTranslatorTests: XCTestCase { ] ) - guard let myClassDiscussion = try findDiscussion(forSymbolPath: "/documentation/MyKit/MyClass", configureBundle: { url in + guard let myClassDiscussion = try await findDiscussion(forSymbolPath: "/documentation/MyKit/MyClass", configureBundle: { url in let sidecarURL = url.appendingPathComponent("/documentation/myclass.md") XCTAssert(FileManager.default.fileExists(atPath: sidecarURL.path), "Make sure that this overrides the existing file.") try """ @@ -222,8 +222,8 @@ class RenderNodeTranslatorTests: XCTestCase { } } - func testArticleRoles() throws { - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testArticleRoles() async throws { + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() // Verify article's role @@ -279,9 +279,9 @@ class RenderNodeTranslatorTests: XCTestCase { } // Verifies that links to sections include their container's abstract rdar://72110558 - func testSectionAbstracts() throws { + func testSectionAbstracts() async throws { // Create an article including a link to a tutorial section - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in try """ # Article Article abstract @@ -302,8 +302,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(renderReference.abstract.first?.plainText, "This is the tutorial abstract.") } - func testEmptyTaskGroupsNotRendered() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testEmptyTaskGroupsNotRendered() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let source = """ @@ -364,8 +364,8 @@ class RenderNodeTranslatorTests: XCTestCase { } /// Tests the ordering of automatic groups for symbols - func testAutomaticTaskGroupsOrderingInSymbols() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + func testAutomaticTaskGroupsOrderingInSymbols() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try """ # ``SideKit/SideClass`` SideClass abstract @@ -491,8 +491,8 @@ class RenderNodeTranslatorTests: XCTestCase { } /// Tests the ordering of automatic groups for articles - func testAutomaticTaskGroupsOrderingInArticles() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + func testAutomaticTaskGroupsOrderingInArticles() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try """ # Article Article abstract @@ -598,8 +598,8 @@ class RenderNodeTranslatorTests: XCTestCase { } /// Tests the ordering of automatic groups in defining protocol - func testOrderingOfAutomaticGroupsInDefiningProtocol() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + func testOrderingOfAutomaticGroupsInDefiningProtocol() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in // }) @@ -644,12 +644,12 @@ class RenderNodeTranslatorTests: XCTestCase { } /// Verify that symbols with ellipsis operators don't get curated into an unnamed protocol implementation section. - func testAutomaticImplementationsWithExtraDots() throws { + func testAutomaticImplementationsWithExtraDots() async throws { let fancyProtocolSGFURL = Bundle.module.url( forResource: "FancyProtocol.symbols", withExtension: "json", subdirectory: "Test Resources")! // Create a test bundle copy with the symbol graph from above - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in try? FileManager.default.copyItem(at: fancyProtocolSGFURL, to: url.appendingPathComponent("FancyProtocol.symbols.json")) } @@ -674,7 +674,7 @@ class RenderNodeTranslatorTests: XCTestCase { } - func testAutomaticImplementationsWithExtraDotsFromExternalModule() throws { + func testAutomaticImplementationsWithExtraDotsFromExternalModule() async throws { let inheritedDefaultImplementationsFromExternalModuleSGF = Bundle.module.url( forResource: "InheritedDefaultImplementationsFromExternalModule.symbols", withExtension: "json", @@ -689,21 +689,21 @@ class RenderNodeTranslatorTests: XCTestCase { ] ).write(inside: createTemporaryDirectory()) - try assertDefaultImplementationCollectionTitles( + try await assertDefaultImplementationCollectionTitles( in: try loadRenderNode(at: "/documentation/SecondTarget/FancyProtocolConformer", in: testBundle), [ "FancyProtocol Implementations", ] ) - try assertDefaultImplementationCollectionTitles( + try await assertDefaultImplementationCollectionTitles( in: try loadRenderNode(at: "/documentation/SecondTarget/OtherFancyProtocolConformer", in: testBundle), [ "OtherFancyProtocol Implementations", ] ) - try assertDefaultImplementationCollectionTitles( + try await assertDefaultImplementationCollectionTitles( in: try loadRenderNode(at: "/documentation/SecondTarget/FooConformer", in: testBundle), [ "Foo Implementations", @@ -711,7 +711,7 @@ class RenderNodeTranslatorTests: XCTestCase { ) } - func testAutomaticImplementationsFromCurrentModuleWithMixOfDocCoverage() throws { + func testAutomaticImplementationsFromCurrentModuleWithMixOfDocCoverage() async throws { let inheritedDefaultImplementationsSGF = Bundle.module.url( forResource: "InheritedDefaultImplementations.symbols", withExtension: "json", @@ -732,14 +732,14 @@ class RenderNodeTranslatorTests: XCTestCase { ] ).write(inside: createTemporaryDirectory()) - try assertDefaultImplementationCollectionTitles( + try await assertDefaultImplementationCollectionTitles( in: try loadRenderNode(at: "/documentation/FirstTarget/Bar", in: testBundle), [ "Foo Implementations", ] ) - try assertDefaultImplementationCollectionTitles( + try await assertDefaultImplementationCollectionTitles( in: try loadRenderNode(at: "/documentation/FirstTarget/OtherStruct", in: testBundle), [ "Comparable Implementations", @@ -747,7 +747,7 @@ class RenderNodeTranslatorTests: XCTestCase { ] ) - try assertDefaultImplementationCollectionTitles( + try await assertDefaultImplementationCollectionTitles( in: try loadRenderNode(at: "/documentation/FirstTarget/SomeStruct", in: testBundle), [ "Comparable Implementations", @@ -758,7 +758,7 @@ class RenderNodeTranslatorTests: XCTestCase { ) } - func testAutomaticImplementationsFromMultiPlatformSymbolGraphs() throws { + func testAutomaticImplementationsFromMultiPlatformSymbolGraphs() async throws { let inheritedDefaultImplementationsSGF = Bundle.module.url( forResource: "InheritedDefaultImplementations.symbols", withExtension: "json", @@ -807,14 +807,14 @@ class RenderNodeTranslatorTests: XCTestCase { ] ).write(inside: createTemporaryDirectory()) - try assertDefaultImplementationCollectionTitles( + try await assertDefaultImplementationCollectionTitles( in: try loadRenderNode(at: "/documentation/FirstTarget/Bar", in: testBundle), [ "Foo Implementations", ] ) - try assertDefaultImplementationCollectionTitles( + try await assertDefaultImplementationCollectionTitles( in: try loadRenderNode(at: "/documentation/FirstTarget/OtherStruct", in: testBundle), [ "Comparable Implementations", @@ -822,7 +822,7 @@ class RenderNodeTranslatorTests: XCTestCase { ] ) - try assertDefaultImplementationCollectionTitles( + try await assertDefaultImplementationCollectionTitles( in: try loadRenderNode(at: "/documentation/FirstTarget/SomeStruct", in: testBundle), [ "Comparable Implementations", @@ -853,8 +853,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(references.map(\.title), expectedTitles, file: file, line: line) } - func loadRenderNode(at path: String, in bundleURL: URL) throws -> RenderNode { - let (_, bundle, context) = try loadBundle(from: bundleURL) + func loadRenderNode(at path: String, in bundleURL: URL) async throws -> RenderNode { + let (_, bundle, context) = try await loadBundle(from: bundleURL) let reference = ResolvedTopicReference(bundleID: bundle.id, path: path, sourceLanguage: .swift) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) @@ -863,8 +863,8 @@ class RenderNodeTranslatorTests: XCTestCase { return try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) } - func testAutomaticTaskGroupTopicsAreSorted() throws { - let (bundle, context) = try testBundleAndContext(named: "DefaultImplementations") + func testAutomaticTaskGroupTopicsAreSorted() async throws { + let (bundle, context) = try await testBundleAndContext(named: "DefaultImplementations") let structReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/DefaultImplementations/Foo", sourceLanguage: .swift) let structNode = try context.entity(with: structReference) let symbol = try XCTUnwrap(structNode.semantic as? Symbol) @@ -883,9 +883,9 @@ class RenderNodeTranslatorTests: XCTestCase { } // Verifies we don't render links to non linkable nodes. - func testNonLinkableNodes() throws { + func testNonLinkableNodes() async throws { // Create a bundle with variety absolute and relative links and symbol links to a non linkable node. - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try """ # ``SideKit/SideClass`` Abstract. @@ -922,10 +922,10 @@ class RenderNodeTranslatorTests: XCTestCase { } // Verifies we support rendering links in abstracts. - func testLinkInAbstract() throws { + func testLinkInAbstract() async throws { do { // First verify that `SideKit` page does not contain render reference to `SideKit/SideClass/Element`. - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit", sourceLanguage: .swift) let node = try context.entity(with: reference) @@ -940,7 +940,7 @@ class RenderNodeTranslatorTests: XCTestCase { do { // Create a bundle with a link in abstract, then verify the render reference is present in `SideKit` render node references. - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try """ # ``SideKit/SideClass`` This is a link to . @@ -959,8 +959,8 @@ class RenderNodeTranslatorTests: XCTestCase { } } - func testSnippetToCodeListing() throws { - let (bundle, context) = try testBundleAndContext(named: "Snippets") + func testSnippetToCodeListing() async throws { + let (bundle, context) = try await testBundleAndContext(named: "Snippets") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Snippets/Snippets", sourceLanguage: .swift) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) @@ -989,8 +989,8 @@ class RenderNodeTranslatorTests: XCTestCase { } } - func testSnippetSliceToCodeListing() throws { - let (bundle, context) = try testBundleAndContext(named: "Snippets") + func testSnippetSliceToCodeListing() async throws { + let (bundle, context) = try await testBundleAndContext(named: "Snippets") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Snippets/Snippets", sourceLanguage: .swift) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) @@ -1013,8 +1013,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(l.code, ["func foo() {}"]) } - func testNestedSnippetSliceToCodeListing() throws { - let (bundle, context) = try testBundleAndContext(named: "Snippets") + func testNestedSnippetSliceToCodeListing() async throws { + let (bundle, context) = try await testBundleAndContext(named: "Snippets") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Snippets/Snippets", sourceLanguage: .swift) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) @@ -1044,8 +1044,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(l.code, ["middle()"]) } - func testSnippetSliceTrimsIndentation() throws { - let (bundle, context) = try testBundleAndContext(named: "Snippets") + func testSnippetSliceTrimsIndentation() async throws { + let (bundle, context) = try await testBundleAndContext(named: "Snippets") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Snippets/SliceIndentation", sourceLanguage: .swift) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) @@ -1069,8 +1069,8 @@ class RenderNodeTranslatorTests: XCTestCase { } - func testRowAndColumn() throws { - let (bundle, context) = try testBundleAndContext(named: "BookLikeContent") + func testRowAndColumn() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BookLikeContent") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/BestBook/MyArticle", @@ -1098,8 +1098,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(row.columns.last?.content.count, 3) } - func testSmall() throws { - let (bundle, context) = try testBundleAndContext(named: "BookLikeContent") + func testSmall() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BookLikeContent") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/BestBook/MyArticle", @@ -1126,8 +1126,8 @@ class RenderNodeTranslatorTests: XCTestCase { ) } - func testTabNavigator() throws { - let (bundle, context) = try testBundleAndContext(named: "BookLikeContent") + func testTabNavigator() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BookLikeContent") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/BestBook/TabNavigatorArticle", @@ -1163,8 +1163,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(tabNavigator.tabs[2].content.count, 1) } - func testRenderNodeMetadata() throws { - let (bundle, context) = try testBundleAndContext(named: "BookLikeContent") + func testRenderNodeMetadata() async throws { + let (bundle, context) = try await testBundleAndContext(named: "BookLikeContent") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/BestBook/MyArticle", @@ -1239,8 +1239,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(roundTrippedArticle.metadata.role, "article") } - func testPageColorMetadataInSymbolExtension() throws { - let (bundle, context) = try testBundleAndContext(named: "MixedManualAutomaticCuration") + func testPageColorMetadataInSymbolExtension() async throws { + let (bundle, context) = try await testBundleAndContext(named: "MixedManualAutomaticCuration") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/TestBed", @@ -1255,8 +1255,8 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(roundTrippedSymbol.metadata.color?.standardColorIdentifier, "purple") } - func testTitleHeadingMetadataInSymbolExtension() throws { - let (bundle, context) = try testBundleAndContext(named: "MixedManualAutomaticCuration") + func testTitleHeadingMetadataInSymbolExtension() async throws { + let (bundle, context) = try await testBundleAndContext(named: "MixedManualAutomaticCuration") let reference = ResolvedTopicReference( bundleID: bundle.id, path: "/documentation/TestBed", @@ -1272,7 +1272,7 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(roundTrippedSymbol.metadata.role, "collection") } - func testExpectedRoleHeadingIsAssigned() throws { + func testExpectedRoleHeadingIsAssigned() async throws { let catalog = Folder( name: "unit-test.docc", content: [ @@ -1334,7 +1334,7 @@ class RenderNodeTranslatorTests: XCTestCase { ), ] ) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) func renderNodeArticleFromReferencePath( referencePath: String @@ -1362,7 +1362,7 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(renderNode.metadata.roleHeading, "Sample Code") } - func testExpectedRoleHeadingWhenAutomaticRoleHeadingIsDisabled() throws { + func testExpectedRoleHeadingWhenAutomaticRoleHeadingIsDisabled() async throws { let catalog = Folder( name: "unit-test.docc", content: [ @@ -1426,7 +1426,7 @@ class RenderNodeTranslatorTests: XCTestCase { ), ] ) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) func renderNodeArticleFromReferencePath( referencePath: String @@ -1458,10 +1458,10 @@ class RenderNodeTranslatorTests: XCTestCase { XCTAssertEqual(renderNode.metadata.roleHeading, "Sample Code") } - func testEncodesOverloadsInRenderNode() throws { + func testEncodesOverloadsInRenderNode() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let (bundle, context) = try testBundleAndContext(named: "OverloadedSymbols") + let (bundle, context) = try await testBundleAndContext(named: "OverloadedSymbols") let overloadPreciseIdentifiers = ["s:8ShapeKit14OverloadedEnumO19firstTestMemberNameySdSiF", "s:8ShapeKit14OverloadedEnumO19firstTestMemberNameySdSfF", @@ -1496,8 +1496,8 @@ class RenderNodeTranslatorTests: XCTestCase { } } - func testAlternateRepresentationsRenderedAsVariants() throws { - let (bundle, context) = try loadBundle(catalog: Folder( + func testAlternateRepresentationsRenderedAsVariants() async throws { + let (bundle, context) = try await loadBundle(catalog: Folder( name: "unit-test.docc", content: [ TextFile(name: "Symbol.md", utf8Content: """ diff --git a/Tests/SwiftDocCTests/Rendering/RoleTests.swift b/Tests/SwiftDocCTests/Rendering/RoleTests.swift index 2bea97e3de..f05fe42e11 100644 --- a/Tests/SwiftDocCTests/Rendering/RoleTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RoleTests.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 @@ -24,8 +24,8 @@ class RoleTests: XCTestCase { "/documentation/SideKit/SideClass/init()": "symbol", ] - func testNodeRoles() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + func testNodeRoles() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") // Compile docs and verify contents for (path, expectedRole) in expectedRoles { @@ -42,8 +42,8 @@ class RoleTests: XCTestCase { } } - func testDocumentationRenderReferenceRoles() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + func testDocumentationRenderReferenceRoles() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", fragment: nil, sourceLanguage: .swift) let node = try context.entity(with: identifier) @@ -55,8 +55,8 @@ class RoleTests: XCTestCase { XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/Test-Bundle/article2"] as? TopicRenderReference)?.role, "collectionGroup") } - func testTutorialsRenderReferenceRoles() throws { - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + func testTutorialsRenderReferenceRoles() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/tutorials/Test-Bundle/TestTutorial", fragment: nil, sourceLanguage: .swift) let node = try context.entity(with: identifier) diff --git a/Tests/SwiftDocCTests/Rendering/SampleDownloadTests.swift b/Tests/SwiftDocCTests/Rendering/SampleDownloadTests.swift index d139af629b..39b7ab7d61 100644 --- a/Tests/SwiftDocCTests/Rendering/SampleDownloadTests.swift +++ b/Tests/SwiftDocCTests/Rendering/SampleDownloadTests.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 @@ -74,8 +74,8 @@ class SampleDownloadTests: XCTestCase { XCTAssertEqual(text, "You can experiment with the code. Just use WiFi Access on your Mac to download WiFi access sample code.") } - func testParseSampleDownload() throws { - let renderNode = try renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MySample") + func testParseSampleDownload() async throws { + let renderNode = try await renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MySample") let sampleCodeDownload = try XCTUnwrap(renderNode.sampleDownload) guard case .reference(identifier: let ident, isActive: true, overridingTitle: "Download", overridingTitleInlineContent: nil) = sampleCodeDownload.action else { @@ -85,8 +85,8 @@ class SampleDownloadTests: XCTestCase { XCTAssertEqual(ident.identifier, "https://example.com/sample.zip") } - func testParseSampleLocalDownload() throws { - let renderNode = try renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MyLocalSample") + func testParseSampleLocalDownload() async throws { + let renderNode = try await renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MyLocalSample") let sampleCodeDownload = try XCTUnwrap(renderNode.sampleDownload) guard case .reference(identifier: let ident, isActive: true, overridingTitle: "Download", overridingTitleInlineContent: nil) = sampleCodeDownload.action else { @@ -96,8 +96,8 @@ class SampleDownloadTests: XCTestCase { XCTAssertEqual(ident.identifier, "plus.svg") } - func testSampleDownloadRoundtrip() throws { - let renderNode = try renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MySample") + func testSampleDownloadRoundtrip() async throws { + let renderNode = try await renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MySample") let encoder = JSONEncoder() let decoder = JSONDecoder() @@ -125,8 +125,8 @@ class SampleDownloadTests: XCTestCase { XCTAssertEqual(origIdent, decodedIdent) } - private func renderNodeFromSampleBundle(at referencePath: String) throws -> RenderNode { - let (bundle, context) = try testBundleAndContext(named: "SampleBundle") + private func renderNodeFromSampleBundle(at referencePath: String) async throws -> RenderNode { + let (bundle, context) = try await testBundleAndContext(named: "SampleBundle") let reference = ResolvedTopicReference( bundleID: bundle.id, path: referencePath, @@ -137,8 +137,8 @@ class SampleDownloadTests: XCTestCase { return try XCTUnwrap(translator.visitArticle(article) as? RenderNode) } - func testSampleDownloadRelativeURL() throws { - let renderNode = try renderNodeFromSampleBundle(at: "/documentation/SampleBundle/RelativeURLSample") + func testSampleDownloadRelativeURL() async throws { + let renderNode = try await renderNodeFromSampleBundle(at: "/documentation/SampleBundle/RelativeURLSample") let sampleCodeDownload = try XCTUnwrap(renderNode.sampleDownload) guard case .reference(identifier: let ident, isActive: true, overridingTitle: "Download", overridingTitleInlineContent: nil) = sampleCodeDownload.action else { XCTFail("Unexpected action in callToAction") @@ -152,8 +152,8 @@ class SampleDownloadTests: XCTestCase { XCTAssertEqual(downloadReference.url.description, "files/ExternalSample.zip") } - func testExternalLocationRoundtrip() throws { - let renderNode = try renderNodeFromSampleBundle(at: "/documentation/SampleBundle/RelativeURLSample") + func testExternalLocationRoundtrip() async throws { + let renderNode = try await renderNodeFromSampleBundle(at: "/documentation/SampleBundle/RelativeURLSample") let sampleCodeDownload = try XCTUnwrap(renderNode.sampleDownload) guard case .reference(identifier: let ident, isActive: true, overridingTitle: "Download", overridingTitleInlineContent: nil) = sampleCodeDownload.action else { XCTFail("Unexpected action in callToAction") @@ -178,8 +178,8 @@ class SampleDownloadTests: XCTestCase { XCTAssertEqual(firstJson, finalJson) } - func testExternalLinkOnSampleCodePage() throws { - let renderNode = try renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MyExternalSample") + func testExternalLinkOnSampleCodePage() async throws { + let renderNode = try await renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MyExternalSample") let sampleCodeDownload = try XCTUnwrap(renderNode.sampleDownload) guard case .reference(identifier: let identifier, isActive: true, overridingTitle: "View Source", overridingTitleInlineContent: nil) = sampleCodeDownload.action else { XCTFail("Unexpected action in callToAction") @@ -191,8 +191,8 @@ class SampleDownloadTests: XCTestCase { XCTAssertEqual(reference.url.description, "https://www.example.com/source-repository.git") } - func testExternalLinkOnRegularArticlePage() throws { - let renderNode = try renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MyArticle") + func testExternalLinkOnRegularArticlePage() async throws { + let renderNode = try await renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MyArticle") let sampleCodeDownload = try XCTUnwrap(renderNode.sampleDownload) guard case .reference(identifier: let identifier, isActive: true, overridingTitle: "Visit", overridingTitleInlineContent: nil) = sampleCodeDownload.action else { XCTFail("Unexpected action in callToAction") @@ -265,10 +265,10 @@ class SampleDownloadTests: XCTestCase { XCTAssertEqual(decodedReference.url, newURL) } - func testProjectFilesForCallToActionDirectives() throws { + func testProjectFilesForCallToActionDirectives() async throws { // Make sure that the `projectFiles()` method correctly returns the DownloadReference // created by the `@CallToAction` directive. - let renderNode = try renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MySample") + let renderNode = try await renderNodeFromSampleBundle(at: "/documentation/SampleBundle/MySample") let downloadReference = try XCTUnwrap(renderNode.projectFiles()) XCTAssertEqual(downloadReference.url.description, "https://example.com/sample.zip") } diff --git a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift index a0c0b129e9..bdabcf4425 100644 --- a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift @@ -21,7 +21,7 @@ class SymbolAvailabilityTests: XCTestCase { symbolGraphOperatingSystemPlatformName: String, symbols: [SymbolGraph.Symbol], symbolName: String - ) throws -> [SymbolGraph.Symbol.Availability.AvailabilityItem] { + ) async throws -> [SymbolGraph.Symbol.Availability.AvailabilityItem] { let catalog = Folder( name: "unit-test.docc", content: [ @@ -36,7 +36,7 @@ class SymbolAvailabilityTests: XCTestCase { )), ] ) - let (_, context) = try loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let reference = try XCTUnwrap(context.soleRootModuleReference).appendingPath(symbolName) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) return try XCTUnwrap(symbol.availability?.availability) @@ -48,7 +48,7 @@ class SymbolAvailabilityTests: XCTestCase { symbolGraphEnvironmentName: String? = nil, symbols: [SymbolGraph.Symbol], symbolName: String - ) throws -> [AvailabilityRenderItem] { + ) async throws -> [AvailabilityRenderItem] { let catalog = Folder( name: "unit-test.docc", content: [ @@ -63,16 +63,16 @@ class SymbolAvailabilityTests: XCTestCase { )), ] ) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let reference = try XCTUnwrap(context.soleRootModuleReference).appendingPath(symbolName) let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: reference.path, sourceLanguage: .swift)) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) return try XCTUnwrap((translator.visit(node.semantic as! Symbol) as! RenderNode).metadata.platformsVariants.defaultValue) } - func testSymbolGraphSymbolWithoutDeprecatedVersionAndIntroducedVersion() throws { + func testSymbolGraphSymbolWithoutDeprecatedVersionAndIntroducedVersion() async throws { - var availability = try renderNodeAvailability( + var availability = try await renderNodeAvailability( defaultAvailability: [], symbolGraphOperatingSystemPlatformName: "ios", symbols: [ @@ -93,7 +93,7 @@ class SymbolAvailabilityTests: XCTestCase { "Mac Catalyst - 1.2.3", ]) - availability = try renderNodeAvailability( + availability = try await renderNodeAvailability( defaultAvailability: [ DefaultAvailability.ModuleAvailability(platformName: PlatformName(operatingSystemName: "iOS"), platformVersion: "1.2.3") ], @@ -122,9 +122,9 @@ class SymbolAvailabilityTests: XCTestCase { ]) } - func testSymbolGraphSymbolWithObsoleteVersion() throws { + func testSymbolGraphSymbolWithObsoleteVersion() async throws { - let availability = try renderNodeAvailability( + let availability = try await renderNodeAvailability( defaultAvailability: [], symbolGraphOperatingSystemPlatformName: "ios", symbols: [ diff --git a/Tests/SwiftDocCTests/Rendering/TermListTests.swift b/Tests/SwiftDocCTests/Rendering/TermListTests.swift index fb6340b207..bb03e0c289 100644 --- a/Tests/SwiftDocCTests/Rendering/TermListTests.swift +++ b/Tests/SwiftDocCTests/Rendering/TermListTests.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 @@ -47,7 +47,7 @@ class TermListTests: XCTestCase { XCTAssertEqual(l.items.count, 4) } - func testLinksAndCodeVoiceAsTerms() throws { + func testLinksAndCodeVoiceAsTerms() async throws { let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "Article.md", utf8Content: """ @@ -86,7 +86,7 @@ class TermListTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = ["com.external.testbundle": resolver] - let (bundle, context) = try loadBundle(catalog: catalog, configuration: configuration) + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/unit-test/Article", sourceLanguage: .swift) let entity = try context.entity(with: reference) @@ -154,14 +154,14 @@ class TermListTests: XCTestCase { } } - func testRenderingListWithAllTermListItems() throws { + func testRenderingListWithAllTermListItems() async throws { let jsonFixtureItems = try discussionContents(fileName: "term-lists-2") guard jsonFixtureItems.count == 1 else { XCTFail("Discussion section didn't have expected number of contents") return } - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ @@ -197,14 +197,14 @@ class TermListTests: XCTestCase { XCTAssertEqual(jsonFixtureItems, result) } - func testRenderingListWithInterleavedListItems() throws { + func testRenderingListWithInterleavedListItems() async throws { let jsonFixtureItems = try discussionContents(fileName: "term-lists-3") guard jsonFixtureItems.count == 4 else { XCTFail("Discussion section didn't have expected number of contents") return } - let (bundle, context) = try testBundleAndContext() + let (bundle, context) = try await testBundleAndContext() var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ diff --git a/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift b/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift index 14cd755e81..856a3908be 100644 --- a/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.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 @@ -9,7 +9,7 @@ */ import XCTest -@testable import SwiftDocC +@testable @preconcurrency import SwiftDocC import Markdown import SwiftDocCTestUtilities import SymbolKit @@ -86,8 +86,8 @@ class ArticleSymbolMentionsTests: XCTestCase { } } - func testSymbolLinkCollectorEnabled() throws { - let (bundle, context) = try createMentionedInTestBundle() + func testSymbolLinkCollectorEnabled() async throws { + let (bundle, context) = try await createMentionedInTestBundle() // The test bundle currently only has one article with symbol mentions // in the abstract/discussion. @@ -108,7 +108,7 @@ class ArticleSymbolMentionsTests: XCTestCase { XCTAssertEqual(mentioningArticle, gottenArticle) } - func testSymbolLinkCollectorDisabled() throws { + func testSymbolLinkCollectorDisabled() async throws { let currentFeatureFlags = FeatureFlags.current addTeardownBlock { FeatureFlags.current = currentFeatureFlags @@ -116,7 +116,7 @@ class ArticleSymbolMentionsTests: XCTestCase { FeatureFlags.current.isMentionedInEnabled = false - let (bundle, context) = try createMentionedInTestBundle() + let (bundle, context) = try await createMentionedInTestBundle() XCTAssertTrue(context.articleSymbolMentions.mentions.isEmpty) let mentionedSymbol = ResolvedTopicReference( diff --git a/Tests/SwiftDocCTests/Semantics/ArticleTests.swift b/Tests/SwiftDocCTests/Semantics/ArticleTests.swift index 9c32d5d4a0..18968e24c1 100644 --- a/Tests/SwiftDocCTests/Semantics/ArticleTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ArticleTests.swift @@ -13,7 +13,7 @@ import XCTest import Markdown class ArticleTests: XCTestCase { - func testValid() throws { + func testValid() async throws { let source = """ # This is my article @@ -22,7 +22,7 @@ class ArticleTests: XCTestCase { Here's an overview. """ let document = Document(parsing: source, options: []) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article) @@ -33,7 +33,7 @@ class ArticleTests: XCTestCase { XCTAssertEqual((article?.discussion?.content ?? []).map { $0.detachedFromParent.format() }.joined(separator: "\n"), "Here’s an overview.") } - func testWithExplicitOverviewHeading() throws { + func testWithExplicitOverviewHeading() async throws { let source = """ # This is my article @@ -44,7 +44,7 @@ class ArticleTests: XCTestCase { Here's an overview. """ let document = Document(parsing: source, options: []) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article) @@ -62,7 +62,7 @@ class ArticleTests: XCTestCase { } } - func testWithExplicitCustomHeading() throws { + func testWithExplicitCustomHeading() async throws { let source = """ # This is my article @@ -73,7 +73,7 @@ class ArticleTests: XCTestCase { Here's an overview. """ let document = Document(parsing: source, options: []) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article) @@ -92,12 +92,12 @@ class ArticleTests: XCTestCase { } } - func testOnlyTitleArticle() throws { + func testOnlyTitleArticle() async throws { let source = """ # This is my article """ let document = Document(parsing: source, options: []) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article) @@ -108,14 +108,14 @@ class ArticleTests: XCTestCase { XCTAssertNil(article?.discussion) } - func testNoAbstract() throws { + func testNoAbstract() async throws { let source = """ # This is my article - This is not an abstract. """ let document = Document(parsing: source, options: []) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article) @@ -126,14 +126,14 @@ class ArticleTests: XCTestCase { XCTAssertEqual((article?.discussion?.content ?? []).map { $0.detachedFromParent.format() }.joined(separator: "\n"), "- This is not an abstract.") } - func testSolutionForTitleMissingIndentation() throws { + func testSolutionForTitleMissingIndentation() async throws { let source = """ My article This is my article """ let document = Document(parsing: source, options: []) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) @@ -148,12 +148,12 @@ class ArticleTests: XCTestCase { XCTAssertEqual(replacement.replacement, "# My article") } - func testSolutionForEmptyArticle() throws { + func testSolutionForEmptyArticle() async throws { let source = """ """ let document = Document(parsing: source, options: []) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) @@ -168,7 +168,7 @@ class ArticleTests: XCTestCase { XCTAssertEqual(replacement.replacement, "# <#Title#>") } - func testArticleWithDuplicateOptions() throws { + func testArticleWithDuplicateOptions() async throws { let source = """ # Article @@ -185,7 +185,7 @@ class ArticleTests: XCTestCase { Here's an overview. """ let document = Document(parsing: source, options: [.parseBlockDirectives]) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article) @@ -209,7 +209,7 @@ class ArticleTests: XCTestCase { XCTAssertEqual(article?.options[.local]?.automaticSeeAlsoEnabled, false) } - func testDisplayNameDirectiveIsRemoved() throws { + func testDisplayNameDirectiveIsRemoved() async throws { let source = """ # Root @@ -222,7 +222,7 @@ class ArticleTests: XCTestCase { Adding @DisplayName to an article will result in a warning. """ let document = Document(parsing: source, options: [.parseBlockDirectives]) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) diff --git a/Tests/SwiftDocCTests/Semantics/AssessmentsTests.swift b/Tests/SwiftDocCTests/Semantics/AssessmentsTests.swift index 52194e04fc..0d7e254769 100644 --- a/Tests/SwiftDocCTests/Semantics/AssessmentsTests.swift +++ b/Tests/SwiftDocCTests/Semantics/AssessmentsTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class AssessmentsTests: XCTestCase { - func testEmptyAndLonely() throws { + func testEmptyAndLonely() async throws { let source = "@Assessments" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() diff --git a/Tests/SwiftDocCTests/Semantics/CallToActionTests.swift b/Tests/SwiftDocCTests/Semantics/CallToActionTests.swift index e44c14ac82..3c59b82ba7 100644 --- a/Tests/SwiftDocCTests/Semantics/CallToActionTests.swift +++ b/Tests/SwiftDocCTests/Semantics/CallToActionTests.swift @@ -15,13 +15,13 @@ import Markdown @testable import SwiftDocC class CallToActionTests: XCTestCase { - func testInvalidWithNoArguments() throws { + func testInvalidWithNoArguments() async throws { let source = "@CallToAction" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "SampleBundle") + let (bundle, _) = try await testBundleAndContext(named: "SampleBundle") directive.map { directive in var problems = [Problem]() @@ -35,13 +35,13 @@ class CallToActionTests: XCTestCase { } } - func testInvalidWithoutLink() throws { - func assertMissingLink(source: String) throws { + func testInvalidWithoutLink() async throws { + func assertMissingLink(source: String) async throws { let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "SampleBundle") + let (bundle, _) = try await testBundleAndContext(named: "SampleBundle") directive.map { directive in var problems = [Problem]() @@ -53,17 +53,17 @@ class CallToActionTests: XCTestCase { XCTAssertTrue(diagnosticIdentifiers.contains("org.swift.docc.\(CallToAction.self).missingLink")) } } - try assertMissingLink(source: "@CallToAction(label: \"Button\")") - try assertMissingLink(source: "@CallToAction(purpose: download)") + try await assertMissingLink(source: "@CallToAction(label: \"Button\")") + try await assertMissingLink(source: "@CallToAction(purpose: download)") } - func testInvalidWithoutLabel() throws { - func assertMissingLabel(source: String) throws { + func testInvalidWithoutLabel() async throws { + func assertMissingLabel(source: String) async throws { let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "SampleBundle") + let (bundle, _) = try await testBundleAndContext(named: "SampleBundle") directive.map { directive in var problems = [Problem]() @@ -75,17 +75,17 @@ class CallToActionTests: XCTestCase { XCTAssertTrue(diagnosticIdentifiers.contains("org.swift.docc.\(CallToAction.self).missingLabel")) } } - try assertMissingLabel(source: "@CallToAction(url: \"https://example.com/sample.zip\"") - try assertMissingLabel(source: "@CallToAction(file: \"Downloads/plus.svg\"") + try await assertMissingLabel(source: "@CallToAction(url: \"https://example.com/sample.zip\"") + try await assertMissingLabel(source: "@CallToAction(file: \"Downloads/plus.svg\"") } - func testInvalidTooManyLinks() throws { + func testInvalidTooManyLinks() async throws { let source = "@CallToAction(url: \"https://example.com/sample.zip\", file: \"Downloads/plus.svg\", purpose: download)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "SampleBundle") + let (bundle, _) = try await testBundleAndContext(named: "SampleBundle") directive.map { directive in var problems = [Problem]() @@ -98,13 +98,13 @@ class CallToActionTests: XCTestCase { } } - func testValidDirective() throws { - func assertValidDirective(source: String) throws { + func testValidDirective() async throws { + func assertValidDirective(source: String) async throws { let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "SampleBundle") + let (bundle, _) = try await testBundleAndContext(named: "SampleBundle") directive.map { directive in var problems = [Problem]() @@ -131,17 +131,17 @@ class CallToActionTests: XCTestCase { for link in validLinks { for label in validLabels { - try assertValidDirective(source: "@CallToAction(\(link), \(label))") + try await assertValidDirective(source: "@CallToAction(\(link), \(label))") } } } - func testDefaultLabel() throws { - func assertExpectedLabel(source: String, expectedDefaultLabel: String, expectedSampleCodeLabel: String) throws { + func testDefaultLabel() async throws { + func assertExpectedLabel(source: String, expectedDefaultLabel: String, expectedSampleCodeLabel: String) async throws { let document = Document(parsing: source, options: .parseBlockDirectives) let directive = try XCTUnwrap(document.child(at: 0) as? BlockDirective) - let (bundle, _) = try testBundleAndContext(named: "SampleBundle") + let (bundle, _) = try await testBundleAndContext(named: "SampleBundle") var problems = [Problem]() XCTAssertEqual(CallToAction.directiveName, directive.name) @@ -173,7 +173,7 @@ class CallToActionTests: XCTestCase { for (arg, defaultLabel, sampleCodeLabel) in validLabels { let directive = "@CallToAction(file: \"Downloads/plus.svg\", \(arg))" - try assertExpectedLabel( + try await assertExpectedLabel( source: directive, expectedDefaultLabel: defaultLabel, expectedSampleCodeLabel: sampleCodeLabel diff --git a/Tests/SwiftDocCTests/Semantics/ChapterTests.swift b/Tests/SwiftDocCTests/Semantics/ChapterTests.swift index f47f64ed6c..ddff9733fb 100644 --- a/Tests/SwiftDocCTests/Semantics/ChapterTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ChapterTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class ChapterTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = """ @Chapter """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let chapter = Chapter(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(chapter) @@ -31,7 +31,7 @@ class ChapterTests: XCTestCase { XCTAssert(problems.map { $0.diagnostic.severity }.allSatisfy { $0 == .warning }) } - func testMultipleMedia() throws { + func testMultipleMedia() async throws { let chapterName = "Chapter 1" let source = """ @Chapter(name: "\(chapterName)") { @@ -42,7 +42,7 @@ class ChapterTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let chapter = Chapter(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertEqual(1, problems.count) @@ -61,7 +61,7 @@ class ChapterTests: XCTestCase { } } - func testValid() throws { + func testValid() async throws { let chapterName = "Chapter 1" let source = """ @Chapter(name: "\(chapterName)") { @@ -71,7 +71,7 @@ class ChapterTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let chapter = Chapter(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertTrue(problems.isEmpty) @@ -82,8 +82,8 @@ class ChapterTests: XCTestCase { } } - func testDuplicateTutorialReferences() throws { - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testDuplicateTutorialReferences() async throws { + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") /* The test bundle contains the duplicate tutorial references in TestOverview: diff --git a/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift b/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift index 12902ede40..7be45b6176 100644 --- a/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift @@ -14,13 +14,13 @@ import Markdown import SwiftDocCTestUtilities class ChoiceTests: XCTestCase { - func testInvalidEmpty() throws { + func testInvalidEmpty() async throws { let source = "@Choice" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -35,7 +35,7 @@ class ChoiceTests: XCTestCase { } } - func testInvalidMissingContent() throws { + func testInvalidMissingContent() async throws { let source = """ @Choice(isCorrect: true) { @Justification { @@ -47,7 +47,7 @@ class ChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -60,7 +60,7 @@ class ChoiceTests: XCTestCase { } } - func testInvalidMissingJustification() throws { + func testInvalidMissingJustification() async throws { let source = """ @Choice(isCorrect: true) { This is some content. @@ -70,7 +70,7 @@ class ChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -84,7 +84,7 @@ class ChoiceTests: XCTestCase { } } - func testInvalidMissingIsCorrect() throws { + func testInvalidMissingIsCorrect() async throws { let source = """ @Choice { This is some content. @@ -96,7 +96,7 @@ class ChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -110,7 +110,7 @@ class ChoiceTests: XCTestCase { } } - func testInvalidIsCorrect() throws { + func testInvalidIsCorrect() async throws { let source = """ @Choice(isCorrect: blah) { This is some content. @@ -122,7 +122,7 @@ class ChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -139,7 +139,7 @@ class ChoiceTests: XCTestCase { } } - func testValidParagraph() throws { + func testValidParagraph() async throws { let source = """ @Choice(isCorrect: true) { This is some content. @@ -152,7 +152,7 @@ class ChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -172,7 +172,7 @@ Choice @1:1-6:2 isCorrect: true } } - func testValidCode() throws { + func testValidCode() async throws { let source = """ @Choice(isCorrect: true) { ```swift @@ -188,7 +188,7 @@ Choice @1:1-6:2 isCorrect: true let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -208,7 +208,7 @@ Choice @1:1-9:2 isCorrect: true } } - func testValidImage() throws { + func testValidImage() async throws { let source = """ @Choice(isCorrect: true) { @Image(source: blah.png, alt: blah) @@ -222,7 +222,7 @@ Choice @1:1-9:2 isCorrect: true let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try loadBundle(catalog: Folder(name: "unit-test.docc", content: [ + let (bundle, _) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ InfoPlist(identifier: "org.swift.docc.example"), DataFile(name: "blah.png", data: Data()), ])) diff --git a/Tests/SwiftDocCTests/Semantics/CodeTests.swift b/Tests/SwiftDocCTests/Semantics/CodeTests.swift index 7971bc2e05..136d1796ba 100644 --- a/Tests/SwiftDocCTests/Semantics/CodeTests.swift +++ b/Tests/SwiftDocCTests/Semantics/CodeTests.swift @@ -15,11 +15,11 @@ import XCTest import Markdown class CodeTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Code" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let code = Code(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(code) diff --git a/Tests/SwiftDocCTests/Semantics/ContentAndMediaTests.swift b/Tests/SwiftDocCTests/Semantics/ContentAndMediaTests.swift index cbc1267921..8ba8c47a86 100644 --- a/Tests/SwiftDocCTests/Semantics/ContentAndMediaTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ContentAndMediaTests.swift @@ -13,20 +13,20 @@ import XCTest import Markdown class ContentAndMediaTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = """ @ContentAndMedia { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let contentAndMedia = ContentAndMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(contentAndMedia) XCTAssertEqual(0, problems.count) } - func testValid() throws { + func testValid() async throws { let source = """ @ContentAndMedia { @@ -37,7 +37,7 @@ class ContentAndMediaTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let contentAndMedia = ContentAndMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(contentAndMedia) @@ -47,7 +47,7 @@ class ContentAndMediaTests: XCTestCase { } } - func testTrailingMiddleMediaPosition() throws { + func testTrailingMiddleMediaPosition() async throws { let source = """ @ContentAndMedia { @@ -58,7 +58,7 @@ class ContentAndMediaTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let contentAndMedia = ContentAndMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(contentAndMedia) @@ -68,7 +68,7 @@ class ContentAndMediaTests: XCTestCase { } } - func testTrailingMediaPosition() throws { + func testTrailingMediaPosition() async throws { let source = """ @ContentAndMedia { @@ -81,7 +81,7 @@ class ContentAndMediaTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let contentAndMedia = ContentAndMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(contentAndMedia) @@ -91,7 +91,7 @@ class ContentAndMediaTests: XCTestCase { } } - func testDeprecatedArguments() throws { + func testDeprecatedArguments() async throws { let source = """ @ContentAndMedia(layout: horizontal, eyebrow: eyebrow, title: title) { @@ -102,7 +102,7 @@ class ContentAndMediaTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let contentAndMedia = ContentAndMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(contentAndMedia) diff --git a/Tests/SwiftDocCTests/Semantics/DisplayNameTests.swift b/Tests/SwiftDocCTests/Semantics/DisplayNameTests.swift index acefd33686..3f927018cd 100644 --- a/Tests/SwiftDocCTests/Semantics/DisplayNameTests.swift +++ b/Tests/SwiftDocCTests/Semantics/DisplayNameTests.swift @@ -15,11 +15,11 @@ import XCTest import Markdown class DisplayNameTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@DisplayName" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let displayName = DisplayName(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(displayName) @@ -28,11 +28,11 @@ class DisplayNameTests: XCTestCase { XCTAssertEqual("org.swift.docc.HasArgument.unlabeled", problems.first?.diagnostic.identifier) } - func testUnlabeledArgumentValue() throws { + func testUnlabeledArgumentValue() async throws { let source = "@DisplayName(\"Custom Symbol Name\")" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let displayName = DisplayName(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(displayName) @@ -40,11 +40,11 @@ class DisplayNameTests: XCTestCase { XCTAssertEqual(displayName?.style, .conceptual) } - func testConceptualStyleArgumentValue() throws { + func testConceptualStyleArgumentValue() async throws { let source = "@DisplayName(\"Custom Symbol Name\", style: conceptual)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let displayName = DisplayName(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(displayName) @@ -52,11 +52,11 @@ class DisplayNameTests: XCTestCase { XCTAssertEqual(displayName?.style, .conceptual) } - func testSymbolStyleArgumentValue() throws { + func testSymbolStyleArgumentValue() async throws { let source = "@DisplayName(\"Custom Symbol Name\", style: symbol)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let displayName = DisplayName(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(displayName) @@ -64,11 +64,11 @@ class DisplayNameTests: XCTestCase { XCTAssertEqual(displayName?.style, .symbol) } - func testUnknownStyleArgumentValue() throws { + func testUnknownStyleArgumentValue() async throws { let source = "@DisplayName(\"Custom Symbol Name\", style: somethingUnknown)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let displayName = DisplayName(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(displayName) @@ -77,11 +77,11 @@ class DisplayNameTests: XCTestCase { XCTAssertEqual("org.swift.docc.HasArgument.style.ConversionFailed", problems.first?.diagnostic.identifier) } - func testExtraArguments() throws { + func testExtraArguments() async throws { let source = "@DisplayName(\"Custom Symbol Name\", argument: value)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let displayName = DisplayName(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(displayName, "Even if there are warnings we can create a displayName value") @@ -90,7 +90,7 @@ class DisplayNameTests: XCTestCase { XCTAssertEqual("org.swift.docc.UnknownArgument", problems.first?.diagnostic.identifier) } - func testExtraDirective() throws { + func testExtraDirective() async throws { let source = """ @DisplayName(\"Custom Symbol Name\") { @Image @@ -98,7 +98,7 @@ class DisplayNameTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let displayName = DisplayName(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(displayName, "Even if there are warnings we can create a DisplayName value") @@ -108,7 +108,7 @@ class DisplayNameTests: XCTestCase { XCTAssertEqual("org.swift.docc.DisplayName.NoInnerContentAllowed", problems.last?.diagnostic.identifier) } - func testExtraContent() throws { + func testExtraContent() async throws { let source = """ @DisplayName(\"Custom Symbol Name\") { Some text @@ -116,7 +116,7 @@ class DisplayNameTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let displayName = DisplayName(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(displayName, "Even if there are warnings we can create a DisplayName value") diff --git a/Tests/SwiftDocCTests/Semantics/DocumentationExtensionTests.swift b/Tests/SwiftDocCTests/Semantics/DocumentationExtensionTests.swift index 9cbd234b3a..6c6d5a5d9c 100644 --- a/Tests/SwiftDocCTests/Semantics/DocumentationExtensionTests.swift +++ b/Tests/SwiftDocCTests/Semantics/DocumentationExtensionTests.swift @@ -15,11 +15,11 @@ import XCTest import Markdown class DocumentationExtensionTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@DocumentationExtension" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let options = DocumentationExtension(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(options) @@ -28,11 +28,11 @@ class DocumentationExtensionTests: XCTestCase { XCTAssertEqual("org.swift.docc.HasArgument.mergeBehavior", problems.first?.diagnostic.identifier) } - func testAppendArgumentValue() throws { + func testAppendArgumentValue() async throws { let source = "@DocumentationExtension(mergeBehavior: append)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let options = DocumentationExtension(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(options) @@ -41,11 +41,11 @@ class DocumentationExtensionTests: XCTestCase { XCTAssertEqual(options?.behavior, .append) } - func testOverrideArgumentValue() throws { + func testOverrideArgumentValue() async throws { let source = "@DocumentationExtension(mergeBehavior: override)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let options = DocumentationExtension(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(options) @@ -53,11 +53,11 @@ class DocumentationExtensionTests: XCTestCase { XCTAssertEqual(options?.behavior, .override) } - func testUnknownArgumentValue() throws { + func testUnknownArgumentValue() async throws { let source = "@DocumentationExtension(mergeBehavior: somethingUnknown )" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let options = DocumentationExtension(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(options) @@ -66,11 +66,11 @@ class DocumentationExtensionTests: XCTestCase { XCTAssertEqual("org.swift.docc.HasArgument.mergeBehavior.ConversionFailed", problems.first?.diagnostic.identifier) } - func testExtraArguments() throws { + func testExtraArguments() async throws { let source = "@DocumentationExtension(mergeBehavior: override, argument: value)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let options = DocumentationExtension(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(options, "Even if there are warnings we can create an options value") @@ -79,7 +79,7 @@ class DocumentationExtensionTests: XCTestCase { XCTAssertEqual("org.swift.docc.UnknownArgument", problems.first?.diagnostic.identifier) } - func testExtraDirective() throws { + func testExtraDirective() async throws { let source = """ @DocumentationExtension(mergeBehavior: override) { @Image @@ -87,7 +87,7 @@ class DocumentationExtensionTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let options = DocumentationExtension(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(options, "Even if there are warnings we can create a DocumentationExtension value") @@ -97,7 +97,7 @@ class DocumentationExtensionTests: XCTestCase { XCTAssertEqual("org.swift.docc.DocumentationExtension.NoInnerContentAllowed", problems.last?.diagnostic.identifier) } - func testExtraContent() throws { + func testExtraContent() async throws { let source = """ @DocumentationExtension(mergeBehavior: override) { Some text @@ -105,7 +105,7 @@ class DocumentationExtensionTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let options = DocumentationExtension(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(options, "Even if there are warnings we can create a DocumentationExtension value") @@ -114,14 +114,14 @@ class DocumentationExtensionTests: XCTestCase { XCTAssertEqual("org.swift.docc.DocumentationExtension.NoInnerContentAllowed", problems.first?.diagnostic.identifier) } - func testIncorrectArgumentLabel() throws { + func testIncorrectArgumentLabel() async throws { let source = """ @DocumentationExtension(merge: override) """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let options = DocumentationExtension(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(options) diff --git a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift b/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift index 99b7d85950..bd62af73f1 100644 --- a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift +++ b/Tests/SwiftDocCTests/Semantics/DoxygenTests.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 @@ -16,7 +16,7 @@ import SwiftDocCTestUtilities @testable import SymbolKit class DoxygenTests: XCTestCase { - func testDoxygenDiscussionAndNote() throws { + func testDoxygenDiscussionAndNote() async throws { let documentationLines: [SymbolGraph.LineList.Line] = """ This is an abstract. @abstract This is description with abstract. @@ -88,7 +88,7 @@ class DoxygenTests: XCTestCase { )), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName/SomeClass", sourceLanguage: .swift) // Verify the expected content in the in-memory model diff --git a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtLeastOneTests.swift b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtLeastOneTests.swift index 81037df4ab..7e73ab2cc2 100644 --- a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtLeastOneTests.swift +++ b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtLeastOneTests.swift @@ -49,13 +49,13 @@ final class TestChild: Semantic, DirectiveConvertible { } class HasAtLeastOneTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Parent" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() do { var problems = [Problem]() @@ -83,7 +83,7 @@ class HasAtLeastOneTests: XCTestCase { } } - func testOne() throws { + func testOne() async throws { let source = """ @Parent { @Child @@ -94,7 +94,7 @@ class HasAtLeastOneTests: XCTestCase { var problems = [Problem]() XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in let (matches, remainder) = Semantic.Analyses.HasAtLeastOne(severityIfNotFound: .error).analyze(directive, children: directive.children, source: nil, for: bundle, problems: &problems) @@ -104,7 +104,7 @@ class HasAtLeastOneTests: XCTestCase { XCTAssertTrue(problems.isEmpty) } - func testMany() throws { + func testMany() async throws { let source = """ @Parent { @Child @@ -117,7 +117,7 @@ class HasAtLeastOneTests: XCTestCase { var problems = [Problem]() XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in let (matches, remainder) = Semantic.Analyses.HasAtLeastOne(severityIfNotFound: .error).analyze(directive, children: directive.children, source: nil, for: bundle, problems: &problems) @@ -127,7 +127,7 @@ class HasAtLeastOneTests: XCTestCase { XCTAssertTrue(problems.isEmpty) } - func testAlternateDirectiveTitle() throws { + func testAlternateDirectiveTitle() async throws { let source = """ @AlternateParent { @AlternateChild @@ -138,7 +138,7 @@ class HasAtLeastOneTests: XCTestCase { var problems = [Problem]() XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in let (matches, remainder) = Semantic.Analyses.HasAtLeastOne(severityIfNotFound: .error).analyze(directive, children: directive.children, source: nil, for: bundle, problems: &problems) diff --git a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtMostOneTests.swift b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtMostOneTests.swift index 95482570fe..6a90fb619a 100644 --- a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtMostOneTests.swift +++ b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasAtMostOneTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class HasAtMostOneTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Parent" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -30,7 +30,7 @@ class HasAtMostOneTests: XCTestCase { } } - func testHasOne() throws { + func testHasOne() async throws { let source = """ @Parent { @Child @@ -40,7 +40,7 @@ class HasAtMostOneTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -51,7 +51,7 @@ class HasAtMostOneTests: XCTestCase { } } - func testHasMany() throws { + func testHasMany() async throws { let source = """ @Parent { @Child @@ -63,7 +63,7 @@ class HasAtMostOneTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -80,7 +80,7 @@ class HasAtMostOneTests: XCTestCase { } } - func testAlternateDirectiveTitle() throws { + func testAlternateDirectiveTitle() async throws { let source = """ @AlternateParent { @AlternateChild @@ -90,7 +90,7 @@ class HasAtMostOneTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() diff --git a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasExactlyOneTests.swift b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasExactlyOneTests.swift index 9bd2646077..b64e8c7f19 100644 --- a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasExactlyOneTests.swift +++ b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasExactlyOneTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class HasExactlyOneTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Parent" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -40,7 +40,7 @@ class HasExactlyOneTests: XCTestCase { } } - func testHasOne() throws { + func testHasOne() async throws { let source = """ @Parent { @Child @@ -50,7 +50,7 @@ class HasExactlyOneTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -61,7 +61,7 @@ class HasExactlyOneTests: XCTestCase { } } - func testHasMany() throws { + func testHasMany() async throws { let source = """ @Parent { @Child @@ -73,7 +73,7 @@ class HasExactlyOneTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -92,7 +92,7 @@ class HasExactlyOneTests: XCTestCase { } } - func testAlternateDirectiveTitle() throws { + func testAlternateDirectiveTitle() async throws { let source = """ @AlternateParent { @AlternateChild @@ -102,7 +102,7 @@ class HasExactlyOneTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() diff --git a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlySequentialHeadingsTests.swift b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlySequentialHeadingsTests.swift index 6658d8621d..85b351d8be 100644 --- a/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlySequentialHeadingsTests.swift +++ b/Tests/SwiftDocCTests/Semantics/General Purpose Analyses/HasOnlySequentialHeadingsTests.swift @@ -15,7 +15,7 @@ import Markdown class HasOnlySequentialHeadingsTests: XCTestCase { private let containerDirective = BlockDirective(name: "TestContainer") - func testNoHeadings() throws { + func testNoHeadings() async throws { let source = """ asdf @@ -27,7 +27,7 @@ some more *stuff* """ let document = Document(parsing: source, options: .parseBlockDirectives) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems: [Problem] = [] Semantic.Analyses.HasOnlySequentialHeadings(severityIfFound: .warning, startingFromLevel: 2).analyze(containerDirective, children: document.children, source: nil, for: bundle, problems: &problems) @@ -35,7 +35,7 @@ some more *stuff* XCTAssertTrue(problems.isEmpty) } - func testValidHeadings() throws { + func testValidHeadings() async throws { let source = """ ## H2 ### H3 @@ -50,7 +50,7 @@ some more *stuff* """ let document = Document(parsing: source, options: .parseBlockDirectives) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems: [Problem] = [] Semantic.Analyses.HasOnlySequentialHeadings(severityIfFound: .warning, startingFromLevel: 2).analyze(containerDirective, children: document.children, source: nil, for: bundle, problems: &problems) @@ -58,14 +58,14 @@ some more *stuff* XCTAssertTrue(problems.isEmpty) } - func testHeadingLevelTooLow() throws { + func testHeadingLevelTooLow() async throws { let source = """ # H1 # H1 """ let document = Document(parsing: source, options: .parseBlockDirectives) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems: [Problem] = [] Semantic.Analyses.HasOnlySequentialHeadings(severityIfFound: .warning, startingFromLevel: 2).analyze(containerDirective, children: document.children, source: nil, for: bundle, problems: &problems) @@ -77,7 +77,7 @@ some more *stuff* ]) } - func testHeadingSkipsLevel() throws { + func testHeadingSkipsLevel() async throws { let source = """ ## H2 #### H4 @@ -86,7 +86,7 @@ some more *stuff* """ let document = Document(parsing: source, options: .parseBlockDirectives) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems: [Problem] = [] Semantic.Analyses.HasOnlySequentialHeadings(severityIfFound: .warning, startingFromLevel: 2).analyze(containerDirective, children: document.children, source: nil, for: bundle, problems: &problems) diff --git a/Tests/SwiftDocCTests/Semantics/ImageMediaTests.swift b/Tests/SwiftDocCTests/Semantics/ImageMediaTests.swift index f850ded4f8..a3606604dd 100644 --- a/Tests/SwiftDocCTests/Semantics/ImageMediaTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ImageMediaTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class ImageMediaTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = """ @Image """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let image = ImageMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(image) @@ -30,7 +30,7 @@ class ImageMediaTests: XCTestCase { ], problems.map { $0.diagnostic.identifier }) } - func testValid() throws { + func testValid() async throws { let imageSource = "/path/to/image" let alt = "This is an image" let source = """ @@ -38,7 +38,7 @@ class ImageMediaTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let image = ImageMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(image) @@ -49,7 +49,7 @@ class ImageMediaTests: XCTestCase { } } - func testSpacesInSource() throws { + func testSpacesInSource() async throws { for imageSource in ["my image.png", "my%20image.png"] { let alt = "This is an image" let source = """ @@ -57,7 +57,7 @@ class ImageMediaTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let image = ImageMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(image) @@ -69,13 +69,13 @@ class ImageMediaTests: XCTestCase { } } - func testIncorrectArgumentLabels() throws { + func testIncorrectArgumentLabels() async throws { let source = """ @Image(imgSource: "/img/path", altText: "Text") """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let image = ImageMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(image) @@ -94,9 +94,9 @@ class ImageMediaTests: XCTestCase { } } - func testRenderImageDirectiveInReferenceMarkup() throws { + func testRenderImageDirectiveInReferenceMarkup() async throws { do { - let (renderedContent, problems, image) = try parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { """ @Image(source: "figure1") """ @@ -120,7 +120,7 @@ class ImageMediaTests: XCTestCase { } do { - let (renderedContent, problems, image) = try parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { """ @Image(source: "unknown-image") """ @@ -133,8 +133,8 @@ class ImageMediaTests: XCTestCase { } } - func testRenderImageDirectiveWithCaption() throws { - let (renderedContent, problems, image) = try parseDirective(ImageMedia.self, in: "BookLikeContent") { + func testRenderImageDirectiveWithCaption() async throws { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { """ @Image(source: "figure1") { This is my caption. @@ -159,8 +159,8 @@ class ImageMediaTests: XCTestCase { ) } - func testImageDirectiveDiagnosesDeviceFrameByDefault() throws { - let (renderedContent, problems, image) = try parseDirective(ImageMedia.self, in: "BookLikeContent") { + func testImageDirectiveDiagnosesDeviceFrameByDefault() async throws { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { """ @Image(source: "figure1", deviceFrame: phone) """ @@ -183,10 +183,10 @@ class ImageMediaTests: XCTestCase { ) } - func testRenderImageDirectiveWithDeviceFrame() throws { + func testRenderImageDirectiveWithDeviceFrame() async throws { enableFeatureFlag(\.isExperimentalDeviceFrameSupportEnabled) - let (renderedContent, problems, image) = try parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { """ @Image(source: "figure1", deviceFrame: phone) """ @@ -209,10 +209,10 @@ class ImageMediaTests: XCTestCase { ) } - func testRenderImageDirectiveWithDeviceFrameAndCaption() throws { + func testRenderImageDirectiveWithDeviceFrameAndCaption() async throws { enableFeatureFlag(\.isExperimentalDeviceFrameSupportEnabled) - let (renderedContent, problems, image) = try parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { """ @Image(source: "figure1", deviceFrame: laptop) { This is my caption. @@ -237,9 +237,9 @@ class ImageMediaTests: XCTestCase { ) } - func testImageDirectiveDoesNotResolveVideoReference() throws { + func testImageDirectiveDoesNotResolveVideoReference() async throws { // First check that the Video exists - let (_, videoProblems, _) = try parseDirective(VideoMedia.self, in: "LegacyBundle_DoNotUseInNewTests") { + let (_, videoProblems, _) = try await parseDirective(VideoMedia.self, in: "LegacyBundle_DoNotUseInNewTests") { """ @Video(source: "introvideo") """ @@ -248,7 +248,7 @@ class ImageMediaTests: XCTestCase { XCTAssertEqual(videoProblems, []) // Then check that it doesn't resolve as an image - let (renderedContent, imageProblems, image) = try parseDirective(ImageMedia.self, in: "LegacyBundle_DoNotUseInNewTests") { + let (renderedContent, imageProblems, image) = try await parseDirective(ImageMedia.self, in: "LegacyBundle_DoNotUseInNewTests") { """ @Image(source: "introvideo") """ diff --git a/Tests/SwiftDocCTests/Semantics/IntroTests.swift b/Tests/SwiftDocCTests/Semantics/IntroTests.swift index c395f64943..6b38105a28 100644 --- a/Tests/SwiftDocCTests/Semantics/IntroTests.swift +++ b/Tests/SwiftDocCTests/Semantics/IntroTests.swift @@ -13,11 +13,11 @@ import XCTest import Markdown class IntroTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Intro" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let intro = Intro(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(intro) @@ -26,7 +26,7 @@ class IntroTests: XCTestCase { XCTAssertEqual("org.swift.docc.HasArgument.title", problems[0].diagnostic.identifier) } - func testValid() throws { + func testValid() async throws { let videoPath = "/path/to/video" let imagePath = "/path/to/image" let posterPath = "/path/to/poster" @@ -42,7 +42,7 @@ class IntroTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let intro = Intro(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(intro) @@ -56,7 +56,7 @@ class IntroTests: XCTestCase { } } - func testIncorrectArgumentLabel() throws { + func testIncorrectArgumentLabel() async throws { let source = """ @Intro(titleText: "Title") { Here is a paragraph. @@ -68,7 +68,7 @@ class IntroTests: XCTestCase { let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let intro = Intro(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(intro) diff --git a/Tests/SwiftDocCTests/Semantics/JustificationTests.swift b/Tests/SwiftDocCTests/Semantics/JustificationTests.swift index 544afc629e..247169b9f9 100644 --- a/Tests/SwiftDocCTests/Semantics/JustificationTests.swift +++ b/Tests/SwiftDocCTests/Semantics/JustificationTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class JustificationTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Justification" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -34,7 +34,7 @@ class JustificationTests: XCTestCase { } } - func testValid() throws { + func testValid() async throws { let source = """ @Justification(reaction: "Correct!") { Here is some content. @@ -44,7 +44,7 @@ class JustificationTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() diff --git a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift index 5ffdbf18d4..b996285011 100644 --- a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022-2023 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 @@ -13,8 +13,8 @@ import XCTest import Markdown class MarkupReferenceResolverTests: XCTestCase { - func testArbitraryReferenceInComment() throws { - let (bundle, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + func testArbitraryReferenceInComment() async throws { + let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let source = """ @Comment { ``hello`` and ``world`` are 2 arbitrary symbol links. @@ -28,8 +28,8 @@ class MarkupReferenceResolverTests: XCTestCase { XCTAssertEqual(0, resolver.problems.count) } - func testDuplicatedDiagnosticForExtensionFile() throws { - let (_, context) = try testBundleAndContext(named: "ExtensionArticleBundle") + func testDuplicatedDiagnosticForExtensionFile() async throws { + let (_, context) = try await testBundleAndContext(named: "ExtensionArticleBundle") // Before #733, symbols with documentation extension files emitted duplicated problems: // - one with a source location in the in-source documentation comment // - one with a source location in the documentation extension file. diff --git a/Tests/SwiftDocCTests/Semantics/MetadataAlternateRepresentationTests.swift b/Tests/SwiftDocCTests/Semantics/MetadataAlternateRepresentationTests.swift index 9fc696e6eb..ea3d2647c9 100644 --- a/Tests/SwiftDocCTests/Semantics/MetadataAlternateRepresentationTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MetadataAlternateRepresentationTests.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 @@ -15,9 +15,9 @@ import Markdown @testable import SwiftDocC class MetadataAlternateRepresentationTests: XCTestCase { - func testValidLocalLink() throws { + func testValidLocalLink() async throws { for link in ["``MyClass/property``", "MyClass/property"] { - let (problems, metadata) = try parseDirective(Metadata.self) { + let (problems, metadata) = try await parseDirective(Metadata.self) { """ @Metadata { @AlternateRepresentation(\(link)) @@ -33,8 +33,8 @@ class MetadataAlternateRepresentationTests: XCTestCase { } } - func testValidExternalLinkReference() throws { - let (problems, metadata) = try parseDirective(Metadata.self) { + func testValidExternalLinkReference() async throws { + let (problems, metadata) = try await parseDirective(Metadata.self) { """ @Metadata { @AlternateRepresentation("doc://com.example/documentation/MyClass/property") @@ -49,8 +49,8 @@ class MetadataAlternateRepresentationTests: XCTestCase { XCTAssertEqual(alternateRepresentation.reference.url, URL(string: "doc://com.example/documentation/MyClass/property")) } - func testInvalidTopicReference() throws { - let (problems, _) = try parseDirective(Metadata.self) { + func testInvalidTopicReference() async throws { + let (problems, _) = try await parseDirective(Metadata.self) { """ @Metadata { @AlternateRepresentation("doc://") diff --git a/Tests/SwiftDocCTests/Semantics/MetadataAvailabilityTests.swift b/Tests/SwiftDocCTests/Semantics/MetadataAvailabilityTests.swift index 5d7afb5996..52292a079e 100644 --- a/Tests/SwiftDocCTests/Semantics/MetadataAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MetadataAvailabilityTests.swift @@ -15,10 +15,10 @@ import Markdown @testable import SwiftDocC class MetadataAvailabilityTests: XCTestCase { - func testInvalidWithNoArguments() throws { + func testInvalidWithNoArguments() async throws { let source = "@Available" - try assertDirective(Metadata.Availability.self, source: source) { directive, problems in + try await assertDirective(Metadata.Availability.self, source: source) { directive, problems in XCTAssertNil(directive) XCTAssertEqual(2, problems.count) @@ -32,7 +32,7 @@ class MetadataAvailabilityTests: XCTestCase { } } - func testInvalidDuplicateIntroduced() throws { + func testInvalidDuplicateIntroduced() async throws { for platform in Metadata.Availability.Platform.defaultCases { let source = """ @Metadata { @@ -40,7 +40,7 @@ class MetadataAvailabilityTests: XCTestCase { @Available(\(platform.rawValue), introduced: \"2.0\") } """ - try assertDirective(Metadata.self, source: source) { directive, problems in + try await assertDirective(Metadata.self, source: source) { directive, problems in XCTAssertEqual(2, problems.count) let diagnosticIdentifiers = Set(problems.map { $0.diagnostic.identifier }) XCTAssertEqual(diagnosticIdentifiers, ["org.swift.docc.\(Metadata.Availability.self).DuplicateIntroduced"]) @@ -48,7 +48,7 @@ class MetadataAvailabilityTests: XCTestCase { } } - func testInvalidIntroducedFormat() throws { + func testInvalidIntroducedFormat() async throws { let source = """ @Metadata { @TechnologyRoot @@ -63,7 +63,7 @@ class MetadataAvailabilityTests: XCTestCase { } """ - try assertDirective(Metadata.self, source: source) { directive, problems in + try await assertDirective(Metadata.self, source: source) { directive, problems in XCTAssertEqual(8, problems.count) let diagnosticIdentifiers = Set(problems.map { $0.diagnostic.identifier }) let diagnosticExplanations = Set(problems.map { $0.diagnostic.explanation }) @@ -74,7 +74,7 @@ class MetadataAvailabilityTests: XCTestCase { } } - func testValidSemanticVersionFormat() throws { + func testValidSemanticVersionFormat() async throws { let source = """ @Metadata { @Available(iOS, introduced: \"3.5.2\", deprecated: \"5.6.7\") @@ -83,7 +83,7 @@ class MetadataAvailabilityTests: XCTestCase { } """ - try assertDirective(Metadata.self, source: source) { directive, problems in + try await assertDirective(Metadata.self, source: source) { directive, problems in XCTAssertEqual(0, problems.count) let directive = try XCTUnwrap(directive) @@ -113,7 +113,7 @@ class MetadataAvailabilityTests: XCTestCase { } } - func testValidIntroducedDirective() throws { + func testValidIntroducedDirective() async throws { // Assemble all the combinations of arguments you could give let validArguments: [String] = [ "deprecated: \"1.0\"", @@ -135,13 +135,13 @@ class MetadataAvailabilityTests: XCTestCase { for platform in checkPlatforms { for args in validArgumentsWithVersion { - try assertValidAvailability(source: "@Available(\(platform), \(args))") + try await assertValidAvailability(source: "@Available(\(platform), \(args))") } } } /// Basic validity test for giving several directives. - func testMultipleAvailabilityDirectives() throws { + func testMultipleAvailabilityDirectives() async throws { let source = """ @Metadata { @Available(macOS, introduced: "11.0") @@ -150,15 +150,15 @@ class MetadataAvailabilityTests: XCTestCase { @Available("My Package", introduced: "0.1", deprecated: "1.0") } """ - try assertValidMetadata(source: source) + try await assertValidMetadata(source: source) } - func assertDirective(_ type: Directive.Type, source: String, assertion assert: (Directive?, [Problem]) throws -> Void) throws { + func assertDirective(_ type: Directive.Type, source: String, assertion assert: (Directive?, [Problem]) throws -> Void) async throws { let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "AvailabilityBundle") + let (bundle, _) = try await testBundleAndContext(named: "AvailabilityBundle") try directive.map { directive in var problems = [Problem]() @@ -168,18 +168,18 @@ class MetadataAvailabilityTests: XCTestCase { } } - func assertValidDirective(_ type: Directive.Type, source: String) throws { - try assertDirective(type, source: source) { directive, problems in + func assertValidDirective(_ type: Directive.Type, source: String) async throws { + try await assertDirective(type, source: source) { directive, problems in XCTAssertNotNil(directive) XCTAssert(problems.isEmpty) } } - func assertValidAvailability(source: String) throws { - try assertValidDirective(Metadata.Availability.self, source: source) + func assertValidAvailability(source: String) async throws { + try await assertValidDirective(Metadata.Availability.self, source: source) } - func assertValidMetadata(source: String) throws { - try assertValidDirective(Metadata.self, source: source) + func assertValidMetadata(source: String) async throws { + try await assertValidDirective(Metadata.self, source: source) } } diff --git a/Tests/SwiftDocCTests/Semantics/MetadataTests.swift b/Tests/SwiftDocCTests/Semantics/MetadataTests.swift index c807d451ad..8f7f713a82 100644 --- a/Tests/SwiftDocCTests/Semantics/MetadataTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MetadataTests.swift @@ -15,11 +15,11 @@ import XCTest import Markdown class MetadataTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Metadata" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata, "Even if a Metadata directive is empty we can create it") @@ -29,11 +29,11 @@ class MetadataTests: XCTestCase { XCTAssertNotNil(problems.first?.possibleSolutions.first) } - func testUnexpectedArgument() throws { + func testUnexpectedArgument() async throws { let source = "@Metadata(argument: value)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata, "Even if there are warnings we can create a metadata value") @@ -42,7 +42,7 @@ class MetadataTests: XCTestCase { XCTAssertEqual("org.swift.docc.Metadata.NoConfiguration", problems.last?.diagnostic.identifier) } - func testUnexpectedDirective() throws { + func testUnexpectedDirective() async throws { let source = """ @Metadata { @Image @@ -50,7 +50,7 @@ class MetadataTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata, "Even if there are warnings we can create a Metadata value") @@ -61,7 +61,7 @@ class MetadataTests: XCTestCase { } - func testExtraContent() throws { + func testExtraContent() async throws { let source = """ @Metadata { Some text @@ -69,7 +69,7 @@ class MetadataTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata, "Even if there are warnings we can create a Metadata value") @@ -80,7 +80,7 @@ class MetadataTests: XCTestCase { // MARK: - Supported metadata directives - func testDocumentationExtensionSupport() throws { + func testDocumentationExtensionSupport() async throws { let source = """ @Metadata { @DocumentationExtension(mergeBehavior: override) @@ -88,7 +88,7 @@ class MetadataTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata) @@ -96,7 +96,7 @@ class MetadataTests: XCTestCase { XCTAssertEqual(metadata?.documentationOptions?.behavior, .override) } - func testRepeatDocumentationExtension() throws { + func testRepeatDocumentationExtension() async throws { let source = """ @Metadata { @DocumentationExtension(mergeBehavior: append) @@ -105,7 +105,7 @@ class MetadataTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata) @@ -117,7 +117,7 @@ class MetadataTests: XCTestCase { XCTAssertEqual(metadata?.documentationOptions?.behavior, .append) } - func testDisplayNameSupport() throws { + func testDisplayNameSupport() async throws { let source = """ @Metadata { @DisplayName("Custom Name") @@ -125,7 +125,7 @@ class MetadataTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata) @@ -134,7 +134,7 @@ class MetadataTests: XCTestCase { XCTAssertEqual(metadata?.displayName?.name, "Custom Name") } - func testTitleHeadingSupport() throws { + func testTitleHeadingSupport() async throws { let source = """ @Metadata { @TitleHeading("Custom Heading") @@ -142,7 +142,7 @@ class MetadataTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata) @@ -151,7 +151,7 @@ class MetadataTests: XCTestCase { XCTAssertEqual(metadata?.titleHeading?.heading, "Custom Heading") } - func testCustomMetadataSupport() throws { + func testCustomMetadataSupport() async throws { let source = """ @Metadata { @CustomMetadata(key: "country", value: "Belgium") @@ -160,7 +160,7 @@ class MetadataTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata) @@ -168,7 +168,7 @@ class MetadataTests: XCTestCase { XCTAssertEqual(problems.count, 0) } - func testRedirectSupport() throws { + func testRedirectSupport() async throws { let source = """ @Metadata { @Redirected(from: "some/other/path") @@ -176,7 +176,7 @@ class MetadataTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(metadata) @@ -186,7 +186,7 @@ class MetadataTests: XCTestCase { // MARK: - Metadata Support - func testArticleSupportsMetadata() throws { + func testArticleSupportsMetadata() async throws { let source = """ # Plain article @@ -197,7 +197,7 @@ class MetadataTests: XCTestCase { The abstract of this article """ let document = Document(parsing: source, options: .parseBlockDirectives) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Metadata child.") @@ -208,7 +208,7 @@ class MetadataTests: XCTestCase { XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got:\n \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } - func testSymbolArticleSupportsMetadataDisplayName() throws { + func testSymbolArticleSupportsMetadataDisplayName() async throws { let source = """ # ``SomeSymbol`` @@ -219,7 +219,7 @@ class MetadataTests: XCTestCase { The abstract of this documentation extension """ let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Metadata child with a DisplayName child.") @@ -232,7 +232,7 @@ class MetadataTests: XCTestCase { XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got:\n \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } - func testArticleDoesNotSupportsMetadataDisplayName() throws { + func testArticleDoesNotSupportsMetadataDisplayName() async throws { let source = """ # Article title @@ -243,7 +243,7 @@ class MetadataTests: XCTestCase { The abstract of this documentation extension """ let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Metadata child with a DisplayName child.") @@ -268,7 +268,7 @@ class MetadataTests: XCTestCase { XCTAssertEqual(solution.replacements.last?.replacement, "# Custom Name") } - func testArticleSupportsMetadataTitleHeading() throws { + func testArticleSupportsMetadataTitleHeading() async throws { let source = """ # Article title @@ -279,7 +279,7 @@ class MetadataTests: XCTestCase { The abstract of this documentation extension """ let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Metadata child with a TitleHeading child.") @@ -293,7 +293,7 @@ class MetadataTests: XCTestCase { XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got:\n \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } - func testDuplicateMetadata() throws { + func testDuplicateMetadata() async throws { let source = """ # Article title @@ -307,7 +307,7 @@ class MetadataTests: XCTestCase { The abstract of this documentation extension """ let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Metadata child with a DisplayName child.") @@ -323,8 +323,8 @@ class MetadataTests: XCTestCase { ) } - func testPageImageSupport() throws { - let (problems, metadata) = try parseMetadataFromSource( + func testPageImageSupport() async throws { + let (problems, metadata) = try await parseMetadataFromSource( """ # Article title @@ -353,8 +353,8 @@ class MetadataTests: XCTestCase { XCTAssertEqual(slothImage?.alt, "A sloth on a branch.") } - func testDuplicatePageImage() throws { - let (problems, _) = try parseMetadataFromSource( + func testDuplicatePageImage() async throws { + let (problems, _) = try await parseMetadataFromSource( """ # Article title @@ -376,9 +376,9 @@ class MetadataTests: XCTestCase { ) } - func testPageColorSupport() throws { + func testPageColorSupport() async throws { do { - let (problems, metadata) = try parseMetadataFromSource( + let (problems, metadata) = try await parseMetadataFromSource( """ # Article title @@ -395,7 +395,7 @@ class MetadataTests: XCTestCase { } do { - let (problems, metadata) = try parseMetadataFromSource( + let (problems, metadata) = try await parseMetadataFromSource( """ # Article title @@ -416,9 +416,9 @@ class MetadataTests: XCTestCase { _ source: String, file: StaticString = #filePath, line: UInt = #line - ) throws -> (problems: [String], metadata: Metadata) { + ) async throws -> (problems: [String], metadata: Metadata) { let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) diff --git a/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift b/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift index 853a9bb92a..87ac419248 100644 --- a/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class MultipleChoiceTests: XCTestCase { - func testInvalidEmpty() throws { + func testInvalidEmpty() async throws { let source = "@MultipleChoice" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -34,7 +34,7 @@ class MultipleChoiceTests: XCTestCase { } } - func testInvalidTooFewChoices() throws { + func testInvalidTooFewChoices() async throws { let source = """ @MultipleChoice { What is your favorite color? @@ -53,7 +53,7 @@ class MultipleChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") try directive.map { directive in var problems = [Problem]() @@ -70,7 +70,7 @@ class MultipleChoiceTests: XCTestCase { } } - func testInvalidCodeAndImage() throws { + func testInvalidCodeAndImage() async throws { let source = """ @MultipleChoice { Question 1 @@ -101,7 +101,7 @@ class MultipleChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -133,7 +133,7 @@ MultipleChoice @1:1-24:2 title: 'SwiftDocC.MarkupContainer' } - func testValidNoCodeOrMedia() throws { + func testValidNoCodeOrMedia() async throws { let source = """ @MultipleChoice { Question 1 @@ -158,7 +158,7 @@ MultipleChoice @1:1-24:2 title: 'SwiftDocC.MarkupContainer' let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -185,7 +185,7 @@ MultipleChoice @1:1-18:2 title: 'SwiftDocC.MarkupContainer' } } - func testValidCode() throws { + func testValidCode() async throws { let source = """ @MultipleChoice { Question 1 @@ -214,7 +214,7 @@ MultipleChoice @1:1-18:2 title: 'SwiftDocC.MarkupContainer' let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -246,7 +246,7 @@ MultipleChoice @1:1-22:2 title: 'SwiftDocC.MarkupContainer' } - func testMultipleCorrectAnswers() throws { + func testMultipleCorrectAnswers() async throws { let source = """ @MultipleChoice { Question 1 @@ -274,7 +274,7 @@ MultipleChoice @1:1-22:2 title: 'SwiftDocC.MarkupContainer' let document = Document(parsing: source, options: .parseBlockDirectives) let directive = try XCTUnwrap(document.child(at: 0) as? BlockDirective) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() XCTAssertEqual(MultipleChoice.directiveName, directive.name) diff --git a/Tests/SwiftDocCTests/Semantics/Options/OptionsTests.swift b/Tests/SwiftDocCTests/Semantics/Options/OptionsTests.swift index a573683e95..49fe9608f5 100644 --- a/Tests/SwiftDocCTests/Semantics/Options/OptionsTests.swift +++ b/Tests/SwiftDocCTests/Semantics/Options/OptionsTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -15,8 +15,8 @@ import XCTest import Markdown class OptionsTests: XCTestCase { - func testDefaultOptions() throws { - let (problems, options) = try parseDirective(Options.self) { + func testDefaultOptions() async throws { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @@ -33,9 +33,9 @@ class OptionsTests: XCTestCase { XCTAssertEqual(unwrappedOptions.scope, .local) } - func testOptionsParameters() throws { + func testOptionsParameters() async throws { do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options(scope: global) { @@ -48,7 +48,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options(scope: local) { @@ -61,7 +61,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options(scope: global, random: foo) { @@ -79,9 +79,9 @@ class OptionsTests: XCTestCase { } } - func testAutomaticSeeAlso() throws { + func testAutomaticSeeAlso() async throws { do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticSeeAlso(disabled) @@ -94,7 +94,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticSeeAlso(enabled) @@ -107,7 +107,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticSeeAlso(foo) @@ -128,9 +128,9 @@ class OptionsTests: XCTestCase { } } - func testTopicsVisualStyle() throws { + func testTopicsVisualStyle() async throws { do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @TopicsVisualStyle(detailedGrid) @@ -143,7 +143,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @TopicsVisualStyle(compactGrid) @@ -156,7 +156,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @TopicsVisualStyle(list) @@ -169,7 +169,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @TopicsVisualStyle(hidden) @@ -182,7 +182,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticSeeAlso(foo) @@ -203,9 +203,9 @@ class OptionsTests: XCTestCase { } } - func testAutomaticTitleHeading() throws { + func testAutomaticTitleHeading() async throws { do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticTitleHeading(disabled) @@ -218,7 +218,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticTitleHeading(enabled) @@ -231,7 +231,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticTitleHeading(foo) @@ -252,8 +252,8 @@ class OptionsTests: XCTestCase { } } - func testMixOfOptions() throws { - let (problems, options) = try parseDirective(Options.self) { + func testMixOfOptions() async throws { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticTitleHeading(enabled) @@ -271,8 +271,8 @@ class OptionsTests: XCTestCase { XCTAssertEqual(options?.automaticArticleSubheadingEnabled, true) } - func testUnsupportedChild() throws { - let (problems, options) = try parseDirective(Options.self) { + func testUnsupportedChild() async throws { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticTitleHeading(enabled) @@ -295,9 +295,9 @@ class OptionsTests: XCTestCase { ) } - func testAutomaticArticleSubheading() throws { + func testAutomaticArticleSubheading() async throws { do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { } @@ -310,7 +310,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticArticleSubheading(randomArgument) @@ -324,7 +324,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticArticleSubheading(disabled) @@ -338,7 +338,7 @@ class OptionsTests: XCTestCase { } do { - let (problems, options) = try parseDirective(Options.self) { + let (problems, options) = try await parseDirective(Options.self) { """ @Options { @AutomaticArticleSubheading(enabled) diff --git a/Tests/SwiftDocCTests/Semantics/RedirectedTests.swift b/Tests/SwiftDocCTests/Semantics/RedirectedTests.swift index 6ceffb037a..ccf72c18fe 100644 --- a/Tests/SwiftDocCTests/Semantics/RedirectedTests.swift +++ b/Tests/SwiftDocCTests/Semantics/RedirectedTests.swift @@ -15,11 +15,11 @@ import XCTest import Markdown class RedirectedTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Redirected" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let redirected = Redirect(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(redirected) @@ -28,12 +28,12 @@ class RedirectedTests: XCTestCase { XCTAssertEqual("org.swift.docc.HasArgument.from", problems.first?.diagnostic.identifier) } - func testValid() throws { + func testValid() async throws { let oldPath = "/old/path/to/this/page" let source = "@Redirected(from: \(oldPath))" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let redirected = Redirect(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(redirected) @@ -41,12 +41,12 @@ class RedirectedTests: XCTestCase { XCTAssertEqual(redirected?.oldPath.path, oldPath) } - func testExtraArguments() throws { + func testExtraArguments() async throws { let oldPath = "/old/path/to/this/page" let source = "@Redirected(from: \(oldPath), argument: value)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let redirected = Redirect(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(redirected, "Even if there are warnings we can create a Redirected value") @@ -55,7 +55,7 @@ class RedirectedTests: XCTestCase { XCTAssertEqual("org.swift.docc.UnknownArgument", problems.first?.diagnostic.identifier) } - func testExtraDirective() throws { + func testExtraDirective() async throws { let oldPath = "/old/path/to/this/page" let source = """ @Redirected(from: \(oldPath)) { @@ -64,7 +64,7 @@ class RedirectedTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let redirected = Redirect(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(redirected, "Even if there are warnings we can create a Redirected value") @@ -74,7 +74,7 @@ class RedirectedTests: XCTestCase { XCTAssertEqual("org.swift.docc.Redirected.NoInnerContentAllowed", problems.last?.diagnostic.identifier) } - func testExtraContent() throws { + func testExtraContent() async throws { let oldPath = "/old/path/to/this/page" let source = """ @Redirected(from: \(oldPath)) { @@ -83,7 +83,7 @@ class RedirectedTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let redirected = Redirect(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(redirected, "Even if there are warnings we can create a Redirected value") @@ -94,7 +94,7 @@ class RedirectedTests: XCTestCase { // MARK: - Redirect support - func testTechnologySupportsRedirect() throws { + func testTechnologySupportsRedirect() async throws { let source = """ @Tutorials(name: "Technology X") { @Intro(title: "Technology X") { @@ -107,7 +107,7 @@ class RedirectedTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tutorialTableOfContents = TutorialTableOfContents(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tutorialTableOfContents, "A tutorial table-of-contents value can be created with a Redirected child.") @@ -118,7 +118,7 @@ class RedirectedTests: XCTestCase { XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } - func testVolumeAndChapterSupportsRedirect() throws { + func testVolumeAndChapterSupportsRedirect() async throws { let source = """ @Volume(name: "Name of this volume") { @Image(source: image.png, alt: image) @@ -139,14 +139,14 @@ class RedirectedTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let volume = Volume(from: directive, source: nil, for: bundle, 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.summary })") } - func testTutorialAndSectionsSupportsRedirect() throws { + func testTutorialAndSectionsSupportsRedirect() async throws { let source = """ @Tutorial(time: 20, projectFiles: project.zip) { @Intro(title: "Basic Augmented Reality App") { @@ -204,7 +204,7 @@ class RedirectedTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tutorial = Tutorial(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tutorial, "A Tutorial value can be created with a Redirected child.") @@ -215,7 +215,7 @@ class RedirectedTests: XCTestCase { XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } - func testTutorialArticleSupportsRedirect() throws { + func testTutorialArticleSupportsRedirect() async throws { let source = """ @Article(time: 20) { @Intro(title: "Making an Augmented Reality App") { @@ -232,7 +232,7 @@ class RedirectedTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let article = TutorialArticle(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article, "A TutorialArticle value can be created with a Redirected child.") @@ -243,7 +243,7 @@ class RedirectedTests: XCTestCase { XCTAssert(analyzer.problems.isEmpty, "Expected no problems. Got \(DiagnosticConsoleWriter.formattedDescription(for: analyzer.problems))") } - func testResourcesSupportsRedirect() throws { + func testResourcesSupportsRedirect() async throws { let source = """ @Resources(technology: doc:/TestOverview) { Find the tools and a comprehensive set of resources for creating AR experiences on iOS. @@ -281,14 +281,14 @@ class RedirectedTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let article = Resources(from: directive, source: nil, for: bundle, 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.summary })") } - func testArticleSupportsRedirect() throws { + func testArticleSupportsRedirect() async throws { let source = """ # Plain article @@ -302,7 +302,7 @@ class RedirectedTests: XCTestCase { ![full width image](referenced-article-image.png) """ let document = Document(parsing: source, options: .parseBlockDirectives) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Redirected child.") @@ -321,7 +321,7 @@ class RedirectedTests: XCTestCase { ], oldPaths) } - func testArticleSupportsRedirectInMetadata() throws { + func testArticleSupportsRedirectInMetadata() async throws { let source = """ # Plain article @@ -337,7 +337,7 @@ class RedirectedTests: XCTestCase { ![full width image](referenced-article-image.png) """ let document = Document(parsing: source, options: .parseBlockDirectives) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Redirected child.") @@ -356,7 +356,7 @@ class RedirectedTests: XCTestCase { ], oldPaths) } - func testArticleSupportsBothRedirects() throws { + func testArticleSupportsBothRedirects() async throws { let source = """ # Plain article @@ -374,7 +374,7 @@ class RedirectedTests: XCTestCase { ![full width image](referenced-article-image.png) """ let document = Document(parsing: source, options: .parseBlockDirectives) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let article = Article(from: document, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(article, "An Article value can be created with a Redirected child.") @@ -394,11 +394,11 @@ class RedirectedTests: XCTestCase { ], oldPaths) } - func testIncorrectArgumentLabel() throws { + func testIncorrectArgumentLabel() async throws { let source = "@Redirected(fromURL: /old/path)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let redirected = Redirect(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(redirected) diff --git a/Tests/SwiftDocCTests/Semantics/Reference/LinksTests.swift b/Tests/SwiftDocCTests/Semantics/Reference/LinksTests.swift index f2238b08d1..8cc0d75e80 100644 --- a/Tests/SwiftDocCTests/Semantics/Reference/LinksTests.swift +++ b/Tests/SwiftDocCTests/Semantics/Reference/LinksTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -15,9 +15,9 @@ import XCTest import Markdown class LinksTests: XCTestCase { - func testMissingBasicRequirements() throws { + func testMissingBasicRequirements() async throws { do { - let (renderedContent, problems, links) = try parseDirective(Links.self, in: "BookLikeContent") { + let (renderedContent, problems, links) = try await parseDirective(Links.self, in: "BookLikeContent") { """ @Links(visualStyle: compactGrid) """ @@ -34,7 +34,7 @@ class LinksTests: XCTestCase { } do { - let (renderedContent, problems, links) = try parseDirective(Links.self, in: "BookLikeContent") { + let (renderedContent, problems, links) = try await parseDirective(Links.self, in: "BookLikeContent") { """ @Links { - @@ -55,9 +55,9 @@ class LinksTests: XCTestCase { } } - func testInvalidBodyContent() throws { + func testInvalidBodyContent() async throws { do { - let (renderedContent, problems, links) = try parseDirective(Links.self, in: "BookLikeContent") { + let (renderedContent, problems, links) = try await parseDirective(Links.self, in: "BookLikeContent") { """ @Links(visualStyle: compactGrid) { This is a paragraph of text in 'Links' directive. @@ -82,7 +82,7 @@ class LinksTests: XCTestCase { } do { - let (renderedContent, problems, links) = try parseDirective(Links.self, in: "BookLikeContent") { + let (renderedContent, problems, links) = try await parseDirective(Links.self, in: "BookLikeContent") { """ @Links(visualStyle: compactGrid) { This is a paragraph of text in 'Links' directive. @@ -116,7 +116,7 @@ class LinksTests: XCTestCase { } do { - let (renderedContent, problems, links) = try parseDirective(Links.self, in: "BookLikeContent") { + let (renderedContent, problems, links) = try await parseDirective(Links.self, in: "BookLikeContent") { """ @Links(visualStyle: compactGrid) { - Link with some trailing content. @@ -147,9 +147,9 @@ class LinksTests: XCTestCase { } } - func testLinkResolution() throws { + func testLinkResolution() async throws { do { - let (renderedContent, problems, links) = try parseDirective(Links.self, in: "BookLikeContent") { + let (renderedContent, problems, links) = try await parseDirective(Links.self, in: "BookLikeContent") { """ @Links(visualStyle: compactGrid) { - @@ -185,7 +185,7 @@ class LinksTests: XCTestCase { } do { - let (renderedContent, problems, links) = try parseDirective(Links.self, in: "LegacyBundle_DoNotUseInNewTests") { + let (renderedContent, problems, links) = try await parseDirective(Links.self, in: "LegacyBundle_DoNotUseInNewTests") { """ @Links(visualStyle: compactGrid) { - ``MyKit/MyClass`` diff --git a/Tests/SwiftDocCTests/Semantics/Reference/RowTests.swift b/Tests/SwiftDocCTests/Semantics/Reference/RowTests.swift index 0c13264955..79ffd45816 100644 --- a/Tests/SwiftDocCTests/Semantics/Reference/RowTests.swift +++ b/Tests/SwiftDocCTests/Semantics/Reference/RowTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -15,8 +15,8 @@ import XCTest import Markdown class RowTests: XCTestCase { - func testNoColumns() throws { - let (renderBlockContent, problems, row) = try parseDirective(Row.self) { + func testNoColumns() async throws { + let (renderBlockContent, problems, row) = try await parseDirective(Row.self) { """ @Row """ @@ -36,9 +36,9 @@ class RowTests: XCTestCase { ) } - func testInvalidParameters() throws { + func testInvalidParameters() async throws { do { - let (renderBlockContent, problems, row) = try parseDirective(Row.self) { + let (renderBlockContent, problems, row) = try await parseDirective(Row.self) { """ @Row(columns: 3) { @Column(what: true) { @@ -81,7 +81,7 @@ class RowTests: XCTestCase { } do { - let (_, problems, row) = try parseDirective(Row.self) { + let (_, problems, row) = try await parseDirective(Row.self) { """ @Row(numberOfColumns: 3) { @Column(size: 3) { @@ -107,9 +107,9 @@ class RowTests: XCTestCase { } } - func testInvalidChildren() throws { + func testInvalidChildren() async throws { do { - let (renderBlockContent, problems, row) = try parseDirective(Row.self) { + let (renderBlockContent, problems, row) = try await parseDirective(Row.self) { """ @Row { @Row { @@ -142,7 +142,7 @@ class RowTests: XCTestCase { } do { - let (renderBlockContent, problems, row) = try parseDirective(Row.self) { + let (renderBlockContent, problems, row) = try await parseDirective(Row.self) { """ @Row { @Column { @@ -175,7 +175,7 @@ class RowTests: XCTestCase { } do { - let (renderBlockContent, problems, row) = try parseDirective(Row.self) { + let (renderBlockContent, problems, row) = try await parseDirective(Row.self) { """ @Row { @@ -199,8 +199,8 @@ class RowTests: XCTestCase { } } - func testEmptyColumn() throws { - let (renderBlockContent, problems, row) = try parseDirective(Row.self) { + func testEmptyColumn() async throws { + let (renderBlockContent, problems, row) = try await parseDirective(Row.self) { """ @Row { @Column @@ -236,8 +236,8 @@ class RowTests: XCTestCase { ) } - func testNestedRowAndColumns() throws { - let (renderBlockContent, problems, row) = try parseDirective(Row.self) { + func testNestedRowAndColumns() async throws { + let (renderBlockContent, problems, row) = try await parseDirective(Row.self) { """ @Row { @Column { diff --git a/Tests/SwiftDocCTests/Semantics/Reference/SmallTests.swift b/Tests/SwiftDocCTests/Semantics/Reference/SmallTests.swift index 1500e22ad0..904f330eb2 100644 --- a/Tests/SwiftDocCTests/Semantics/Reference/SmallTests.swift +++ b/Tests/SwiftDocCTests/Semantics/Reference/SmallTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -15,8 +15,8 @@ import XCTest import Markdown class SmallTests: XCTestCase { - func testNoContent() throws { - let (renderBlockContent, problems, small) = try parseDirective(Small.self) { + func testNoContent() async throws { + let (renderBlockContent, problems, small) = try await parseDirective(Small.self) { """ @Small """ @@ -32,9 +32,9 @@ class SmallTests: XCTestCase { XCTAssertEqual(renderBlockContent, []) } - func testHasContent() throws { + func testHasContent() async throws { do { - let (renderBlockContent, problems, small) = try parseDirective(Small.self) { + let (renderBlockContent, problems, small) = try await parseDirective(Small.self) { """ @Small { This is my copyright text. @@ -56,7 +56,7 @@ class SmallTests: XCTestCase { } do { - let (renderBlockContent, problems, small) = try parseDirective(Small.self) { + let (renderBlockContent, problems, small) = try await parseDirective(Small.self) { """ @Small { This is my copyright text. @@ -85,7 +85,7 @@ class SmallTests: XCTestCase { } do { - let (renderBlockContent, problems, small) = try parseDirective(Small.self) { + let (renderBlockContent, problems, small) = try await parseDirective(Small.self) { """ @Small { This is my *formatted* `copyright` **text**. @@ -115,9 +115,9 @@ class SmallTests: XCTestCase { } } - func testEmitsWarningWhenContainsStructuredMarkup() throws { + func testEmitsWarningWhenContainsStructuredMarkup() async throws { do { - let (renderBlockContent, problems, small) = try parseDirective(Small.self) { + let (renderBlockContent, problems, small) = try await parseDirective(Small.self) { """ @Small { This is my copyright text. @@ -143,9 +143,9 @@ class SmallTests: XCTestCase { } } - func testSmallInsideOfColumn() throws { + func testSmallInsideOfColumn() async throws { do { - let (renderBlockContent, problems, row) = try parseDirective(Row.self) { + let (renderBlockContent, problems, row) = try await parseDirective(Row.self) { """ @Row { @Column { diff --git a/Tests/SwiftDocCTests/Semantics/Reference/TabNavigatorTests.swift b/Tests/SwiftDocCTests/Semantics/Reference/TabNavigatorTests.swift index 73b8e928bd..835610146d 100644 --- a/Tests/SwiftDocCTests/Semantics/Reference/TabNavigatorTests.swift +++ b/Tests/SwiftDocCTests/Semantics/Reference/TabNavigatorTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -15,8 +15,8 @@ import XCTest import Markdown class TabNavigatorTests: XCTestCase { - func testNoTabs() throws { - let (renderBlockContent, problems, tabNavigator) = try parseDirective(TabNavigator.self) { + func testNoTabs() async throws { + let (renderBlockContent, problems, tabNavigator) = try await parseDirective(TabNavigator.self) { """ @TabNavigator """ @@ -36,8 +36,8 @@ class TabNavigatorTests: XCTestCase { ) } - func testEmptyTab() throws { - let (renderBlockContent, problems, tabNavigator) = try parseDirective(TabNavigator.self) { + func testEmptyTab() async throws { + let (renderBlockContent, problems, tabNavigator) = try await parseDirective(TabNavigator.self) { """ @TabNavigator { @Tab("hiya") { @@ -63,8 +63,8 @@ class TabNavigatorTests: XCTestCase { } - func testInvalidParametersAndContent() throws { - let (renderBlockContent, problems, tabNavigator) = try parseDirective(TabNavigator.self) { + func testInvalidParametersAndContent() async throws { + let (renderBlockContent, problems, tabNavigator) = try await parseDirective(TabNavigator.self) { """ @TabNavigator(tabs: 3) { @Tab("hi") { @@ -127,8 +127,8 @@ class TabNavigatorTests: XCTestCase { ) } - func testNestedStructuredMarkup() throws { - let (renderBlockContent, problems, tabNavigator) = try parseDirective(TabNavigator.self) { + func testNestedStructuredMarkup() async throws { + let (renderBlockContent, problems, tabNavigator) = try await parseDirective(TabNavigator.self) { """ @TabNavigator { @Tab("hi") { diff --git a/Tests/SwiftDocCTests/Semantics/ResourcesTests.swift b/Tests/SwiftDocCTests/Semantics/ResourcesTests.swift index 48e218fc2d..e80b876609 100644 --- a/Tests/SwiftDocCTests/Semantics/ResourcesTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ResourcesTests.swift @@ -13,11 +13,11 @@ import XCTest import Markdown class ResourcesTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@\(Resources.directiveName)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let resources = Resources(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(resources) @@ -33,7 +33,7 @@ class ResourcesTests: XCTestCase { XCTAssert(problems.map { $0.diagnostic.severity }.allSatisfy { $0 == .warning }) } - func testValid() throws { + func testValid() async throws { let source = """ @\(Resources.directiveName) { Find the tools and a comprehensive set of resources for creating AR experiences on iOS. @@ -67,7 +67,7 @@ class ResourcesTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let resources = Resources(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(resources) @@ -92,7 +92,7 @@ Resources @1:1-29:2 } } - func testMissingLinksWarning() throws { + func testMissingLinksWarning() async throws { let source = """ @\(Resources.directiveName) { Find the tools and a comprehensive set of resources for creating AR experiences on iOS. @@ -120,7 +120,7 @@ Resources @1:1-29:2 """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let resources = Resources(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(resources) diff --git a/Tests/SwiftDocCTests/Semantics/SectionTests.swift b/Tests/SwiftDocCTests/Semantics/SectionTests.swift index 9852a10b49..fbbd367833 100644 --- a/Tests/SwiftDocCTests/Semantics/SectionTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SectionTests.swift @@ -15,11 +15,11 @@ import XCTest import Markdown class TutorialSectionTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Section" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let section = TutorialSection(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(section) diff --git a/Tests/SwiftDocCTests/Semantics/SnippetTests.swift b/Tests/SwiftDocCTests/Semantics/SnippetTests.swift index cc5750d3fd..25e4bde699 100644 --- a/Tests/SwiftDocCTests/Semantics/SnippetTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SnippetTests.swift @@ -15,8 +15,8 @@ import XCTest import Markdown class SnippetTests: XCTestCase { - func testNoPath() throws { - let (bundle, _) = try testBundleAndContext(named: "Snippets") + func testNoPath() async throws { + let (bundle, _) = try await testBundleAndContext(named: "Snippets") let source = """ @Snippet() """ @@ -29,8 +29,8 @@ class SnippetTests: XCTestCase { XCTAssertEqual("org.swift.docc.HasArgument.path", problems[0].diagnostic.identifier) } - func testHasInnerContent() throws { - let (bundle, _) = try testBundleAndContext(named: "Snippets") + func testHasInnerContent() async throws { + let (bundle, _) = try await testBundleAndContext(named: "Snippets") let source = """ @Snippet(path: "path/to/snippet") { This content shouldn't be here. @@ -45,8 +45,8 @@ class SnippetTests: XCTestCase { XCTAssertEqual("org.swift.docc.Snippet.NoInnerContentAllowed", problems[0].diagnostic.identifier) } - func testLinkResolves() throws { - let (bundle, _) = try testBundleAndContext(named: "Snippets") + func testLinkResolves() async throws { + let (bundle, _) = try await testBundleAndContext(named: "Snippets") let source = """ @Snippet(path: "Test/Snippets/MySnippet") """ @@ -59,8 +59,8 @@ class SnippetTests: XCTestCase { XCTAssertTrue(problems.isEmpty) } - func testUnresolvedSnippetPathDiagnostic() throws { - let (bundle, context) = try testBundleAndContext(named: "Snippets") + func testUnresolvedSnippetPathDiagnostic() async throws { + let (bundle, context) = try await testBundleAndContext(named: "Snippets") let source = """ @Snippet(path: "Test/Snippets/DoesntExist") """ @@ -73,8 +73,8 @@ class SnippetTests: XCTestCase { } } - func testSliceResolves() throws { - let (bundle, _) = try testBundleAndContext(named: "Snippets") + func testSliceResolves() async throws { + let (bundle, _) = try await testBundleAndContext(named: "Snippets") let source = """ @Snippet(path: "Test/Snippets/MySnippet", slice: "foo") """ diff --git a/Tests/SwiftDocCTests/Semantics/StackTests.swift b/Tests/SwiftDocCTests/Semantics/StackTests.swift index 92665f6594..0e432bc69d 100644 --- a/Tests/SwiftDocCTests/Semantics/StackTests.swift +++ b/Tests/SwiftDocCTests/Semantics/StackTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class StackTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Stack" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -34,7 +34,7 @@ class StackTests: XCTestCase { } } - func testValid() throws { + func testValid() async throws { let source = """ @Stack { @ContentAndMedia { @@ -48,7 +48,7 @@ class StackTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -59,7 +59,7 @@ class StackTests: XCTestCase { } } - func testTooManyChildren() throws { + func testTooManyChildren() async throws { var source = "@Stack {" for _ in 0...Stack.childrenLimit { source += """ @@ -78,7 +78,7 @@ class StackTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() diff --git a/Tests/SwiftDocCTests/Semantics/StepTests.swift b/Tests/SwiftDocCTests/Semantics/StepTests.swift index 9fec08b2e2..d4a4029105 100644 --- a/Tests/SwiftDocCTests/Semantics/StepTests.swift +++ b/Tests/SwiftDocCTests/Semantics/StepTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class StepTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = """ @Step """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let step = Step(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertEqual([ @@ -32,7 +32,7 @@ class StepTests: XCTestCase { } } - func testValid() throws { + func testValid() async throws { let source = """ @Step { This is the step's content. @@ -46,7 +46,7 @@ class StepTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let step = Step(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertTrue(problems.isEmpty) @@ -83,7 +83,7 @@ Step @1:1-9:2 } } - func testExtraneousContent() throws { + func testExtraneousContent() async throws { let source = """ @Step { This is the step's content. @@ -104,7 +104,7 @@ Step @1:1-9:2 """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let step = Step(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertEqual(2, problems.count) diff --git a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift index 5b59db8a3a..69dfd86fb3 100644 --- a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift @@ -16,8 +16,8 @@ import SwiftDocCTestUtilities class SymbolTests: XCTestCase { - func testDocCommentWithoutArticle() throws { - let (withoutArticle, problems) = try makeDocumentationNodeSymbol( + func testDocCommentWithoutArticle() async throws { + let (withoutArticle, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -43,9 +43,9 @@ class SymbolTests: XCTestCase { XCTAssertNil(withoutArticle.topics) } - func testOverridingInSourceDocumentationWithEmptyArticle() throws { + func testOverridingInSourceDocumentationWithEmptyArticle() async throws { // The article heading—which should always be the symbol link header—is not considered part of the article's content - let (withArticleOverride, problems) = try makeDocumentationNodeSymbol( + let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -75,8 +75,8 @@ class SymbolTests: XCTestCase { "The article did override the topics section.") } - func testOverridingInSourceDocumentationWithDetailedArticle() throws { - let (withArticleOverride, problems) = try makeDocumentationNodeSymbol( + func testOverridingInSourceDocumentationWithDetailedArticle() async throws { + let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -138,9 +138,9 @@ class SymbolTests: XCTestCase { } } - func testAppendingInSourceDocumentationWithArticle() throws { + func testAppendingInSourceDocumentationWithArticle() async throws { // The article heading—which should always be the symbol link header—is not considered part of the article's content - let (withEmptyArticleOverride, problems) = try makeDocumentationNodeSymbol( + let (withEmptyArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -169,13 +169,13 @@ class SymbolTests: XCTestCase { XCTAssertNil(withEmptyArticleOverride.topics) } - func testAppendingArticleToInSourceDocumentation() throws { + func testAppendingArticleToInSourceDocumentation() async throws { // When no DocumentationExtension behavior is specified, the default behavior is "append to doc comment". let withAndWithoutAppendConfiguration = ["", "@Metadata { \n @DocumentationExtension(mergeBehavior: append) \n }"] // Append curation to doc comment for metadata in withAndWithoutAppendConfiguration { - let (withArticleOverride, problems) = try makeDocumentationNodeSymbol( + let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -221,7 +221,7 @@ class SymbolTests: XCTestCase { // Append overview and curation to doc comment for metadata in withAndWithoutAppendConfiguration { - let (withArticleOverride, problems) = try makeDocumentationNodeSymbol( + let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -274,7 +274,7 @@ class SymbolTests: XCTestCase { // Append overview and curation to doc comment for metadata in withAndWithoutAppendConfiguration { - let (withArticleOverride, problems) = try makeDocumentationNodeSymbol( + let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -327,7 +327,7 @@ class SymbolTests: XCTestCase { // Append with only abstract in doc comment for metadata in withAndWithoutAppendConfiguration { - let (withArticleOverride, problems) = try makeDocumentationNodeSymbol( + let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. """, @@ -381,7 +381,7 @@ class SymbolTests: XCTestCase { // Append by extending overview and adding parameters for metadata in withAndWithoutAppendConfiguration { - let (withArticleOverride, problems) = try makeDocumentationNodeSymbol( + let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -421,7 +421,7 @@ class SymbolTests: XCTestCase { // Append by extending the overview (with parameters in the doc comment) for metadata in withAndWithoutAppendConfiguration { - let (withArticleOverride, problems) = try makeDocumentationNodeSymbol( + let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -460,8 +460,8 @@ class SymbolTests: XCTestCase { } } - func testRedirectFromArticle() throws { - let (withRedirectInArticle, problems) = try makeDocumentationNodeSymbol( + func testRedirectFromArticle() async throws { + let (withRedirectInArticle, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -477,8 +477,8 @@ class SymbolTests: XCTestCase { XCTAssertEqual(withRedirectInArticle.redirects?.map { $0.oldPath.absoluteString }, ["some/previous/path/to/this/symbol"]) } - func testWarningWhenDocCommentContainsUnsupportedDirective() throws { - let (withRedirectInArticle, problems) = try makeDocumentationNodeSymbol( + func testWarningWhenDocCommentContainsUnsupportedDirective() async throws { + let (withRedirectInArticle, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -496,8 +496,8 @@ class SymbolTests: XCTestCase { XCTAssertEqual(problems.first?.diagnostic.range?.lowerBound.column, 1) } - func testNoWarningWhenDocCommentContainsDirective() throws { - let (_, problems) = try makeDocumentationNodeSymbol( + func testNoWarningWhenDocCommentContainsDirective() async throws { + let (_, problems) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -510,7 +510,7 @@ class SymbolTests: XCTestCase { XCTAssertTrue(problems.isEmpty) } - func testNoWarningWhenDocCommentContainsDoxygen() throws { + func testNoWarningWhenDocCommentContainsDoxygen() async throws { let tempURL = try createTemporaryDirectory() let bundleURL = try Folder(name: "Inheritance.docc", content: [ @@ -520,18 +520,18 @@ class SymbolTests: XCTestCase { subdirectory: "Test Resources")!), ]).write(inside: tempURL) - let (_, _, context) = try loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) let problems = context.diagnosticEngine.problems XCTAssertEqual(problems.count, 0) } - func testParseDoxygen() throws { + func testParseDoxygen() async throws { let deckKitSymbolGraph = Bundle.module.url( forResource: "DeckKit-Objective-C", withExtension: "symbols.json", subdirectory: "Test Resources" )! - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try? FileManager.default.copyItem(at: deckKitSymbolGraph, to: url.appendingPathComponent("DeckKit.symbols.json")) } let symbol = try XCTUnwrap(context.documentationCache["c:objc(cs)PlayingCard(cm)newWithRank:ofSuit:"]?.semantic as? Symbol) @@ -548,8 +548,8 @@ class SymbolTests: XCTestCase { XCTAssertEqual(symbol.returnsSection?.content.map({ $0.format() }), ["A new card with the given configuration."]) } - func testUnresolvedReferenceWarningsInDocumentationExtension() throws { - let (url, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + func testUnresolvedReferenceWarningsInDocumentationExtension() async throws { + let (url, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in let myKitDocumentationExtensionComment = """ # ``MyKit/MyClass`` @@ -940,7 +940,7 @@ class SymbolTests: XCTestCase { """) } - func testUnresolvedReferenceWarningsInDocComment() throws { + func testUnresolvedReferenceWarningsInDocComment() async throws { let docComment = """ A cool API to call. @@ -959,7 +959,7 @@ class SymbolTests: XCTestCase { - """ - let (_, _, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: url.appendingPathComponent("mykit-iOS.symbols.json"))) let myFunctionUSR = "s:5MyKit0A5ClassC10myFunctionyyF" @@ -1036,8 +1036,8 @@ class SymbolTests: XCTestCase { XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.summary == "No external resolver registered for 'com.test.external'." })) } - func testTopicSectionInDocComment() throws { - let (withArticleOverride, problems) = try makeDocumentationNodeSymbol( + func testTopicSectionInDocComment() async throws { + let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( docComment: """ This is an abstract. @@ -1111,8 +1111,8 @@ class SymbolTests: XCTestCase { XCTAssertEqual(engine.problems.count, 0) } - func testAddingConstraintsToSymbol() throws { - let (withoutArticle, _) = try makeDocumentationNodeSymbol( + func testAddingConstraintsToSymbol() async throws { + let (withoutArticle, _) = try await makeDocumentationNodeSymbol( docComment: """ A cool API to call. @@ -1199,8 +1199,8 @@ class SymbolTests: XCTestCase { XCTAssertEqual(1, withoutArticle.declarationVariants[trait]!.count) } - func testParsesMetadataDirectiveFromDocComment() throws { - let (node, problems) = try makeDocumentationNodeForSymbol( + func testParsesMetadataDirectiveFromDocComment() async throws { + let (node, problems) = try await makeDocumentationNodeForSymbol( docComment: """ The symbol's abstract. @@ -1218,8 +1218,8 @@ class SymbolTests: XCTestCase { XCTAssertEqual(availability.introduced.description, "1.2.3") } - func testEmitsWarningsInMetadataDirectives() throws { - let (_, problems) = try makeDocumentationNodeForSymbol( + func testEmitsWarningsInMetadataDirectives() async throws { + let (_, problems) = try await makeDocumentationNodeForSymbol( docComment: """ The symbol's abstract. @@ -1239,8 +1239,8 @@ class SymbolTests: XCTestCase { XCTAssertEqual(diagnostic.range?.lowerBound.column, 1) } - func testEmitsWarningForDuplicateMetadata() throws { - let (node, problems) = try makeDocumentationNodeForSymbol( + func testEmitsWarningForDuplicateMetadata() async throws { + let (node, problems) = try await makeDocumentationNodeForSymbol( docComment: """ The symbol's abstract. @@ -1270,8 +1270,8 @@ class SymbolTests: XCTestCase { XCTAssertEqual(availability.platform, .other("Platform from documentation extension")) } - func testEmitsWarningsForInvalidMetadataChildrenInDocumentationComments() throws { - let (_, problems) = try makeDocumentationNodeForSymbol( + func testEmitsWarningsForInvalidMetadataChildrenInDocumentationComments() async throws { + let (_, problems) = try await makeDocumentationNodeForSymbol( docComment: """ The symbol's abstract. @@ -1313,8 +1313,8 @@ class SymbolTests: XCTestCase { ) } - func testParsesDeprecationSummaryDirectiveFromDocComment() throws { - let (node, problems) = try makeDocumentationNodeForSymbol( + func testParsesDeprecationSummaryDirectiveFromDocComment() async throws { + let (node, problems) = try await makeDocumentationNodeForSymbol( docComment: """ The symbol's abstract. @@ -1339,8 +1339,8 @@ class SymbolTests: XCTestCase { ) } - func testAllowsCommentDirectiveInDocComment() throws { - let (_, problems) = try makeDocumentationNodeForSymbol( + func testAllowsCommentDirectiveInDocComment() async throws { + let (_, problems) = try await makeDocumentationNodeForSymbol( docComment: """ The symbol's abstract. @@ -1481,8 +1481,8 @@ class SymbolTests: XCTestCase { XCTAssertEqual(lines.linesWithoutLeadingWhitespace(), linesWithoutLeadingWhitespace) } - func testLeadingWhitespaceInDocComment() throws { - let (semanticWithLeadingWhitespace, problems) = try makeDocumentationNodeSymbol( + func testLeadingWhitespaceInDocComment() async throws { + let (semanticWithLeadingWhitespace, problems) = try await makeDocumentationNodeSymbol( docComment: """ This is an abstract. @@ -1513,9 +1513,9 @@ class SymbolTests: XCTestCase { diagnosticEngineFilterLevel: DiagnosticSeverity = .warning, file: StaticString = #filePath, line: UInt = #line - ) throws -> (DocumentationNode, [Problem]) { + ) async throws -> (DocumentationNode, [Problem]) { let myFunctionUSR = "s:5MyKit0A5ClassC10myFunctionyyF" - let (_, bundle, context) = try testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: url.appendingPathComponent("mykit-iOS.symbols.json"))) let newDocComment = self.makeLineList( @@ -1575,8 +1575,8 @@ class SymbolTests: XCTestCase { articleContent: String?, file: StaticString = #filePath, line: UInt = #line - ) throws -> (Symbol, [Problem]) { - let (node, problems) = try makeDocumentationNodeForSymbol( + ) async throws -> (Symbol, [Problem]) { + let (node, problems) = try await makeDocumentationNodeForSymbol( docComment: docComment, articleContent: articleContent, file: file, diff --git a/Tests/SwiftDocCTests/Semantics/TechnologyTests.swift b/Tests/SwiftDocCTests/Semantics/TechnologyTests.swift index 55a11b9471..e3507fc4b6 100644 --- a/Tests/SwiftDocCTests/Semantics/TechnologyTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TechnologyTests.swift @@ -15,11 +15,11 @@ import XCTest import Markdown class TechnologyTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Tutorials" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let technology = TutorialTableOfContents(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(technology) diff --git a/Tests/SwiftDocCTests/Semantics/TileTests.swift b/Tests/SwiftDocCTests/Semantics/TileTests.swift index ef133b8731..8fb8b7b148 100644 --- a/Tests/SwiftDocCTests/Semantics/TileTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TileTests.swift @@ -14,7 +14,7 @@ import Markdown class TileTests: XCTestCase { - func testComplex() throws { + func testComplex() async throws { let directiveNamesAndTitles = [ (Tile.DirectiveNames.documentation, Tile.Semantics.Title.documentation), (Tile.DirectiveNames.sampleCode, Tile.Semantics.Title.sampleCode), @@ -26,7 +26,7 @@ class TileTests: XCTestCase { let source = "@\(directiveName)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tile) @@ -49,7 +49,7 @@ class TileTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tile) @@ -62,7 +62,7 @@ class TileTests: XCTestCase { } } - func testGeneric() throws { + func testGeneric() async throws { let directiveNamesAndTitles = [ (Tile.DirectiveNames.downloads, Tile.Semantics.Title.downloads), (Tile.DirectiveNames.videos, Tile.Semantics.Title.videos), @@ -75,7 +75,7 @@ class TileTests: XCTestCase { let source = "@\(directiveName)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tile) @@ -97,7 +97,7 @@ class TileTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tile) @@ -110,7 +110,7 @@ class TileTests: XCTestCase { } } - func testDestination() throws { + func testDestination() async throws { do { let destination = URL(string: "https://www.example.com/documentation/technology")! let source = """ @@ -120,7 +120,7 @@ class TileTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) // Destination is set. @@ -136,7 +136,7 @@ class TileTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) // Destination is nil. @@ -145,11 +145,11 @@ class TileTests: XCTestCase { } } - func testUnknownTile() throws { + func testUnknownTile() async throws { let source = "@UnknownTile" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(tile) diff --git a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift index ea5ccf6fcc..17635d3408 100644 --- a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class TutorialArticleTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Article" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -35,7 +35,7 @@ class TutorialArticleTests: XCTestCase { } } - func testSimpleNoIntro() throws { + func testSimpleNoIntro() async throws { let source = """ @Article { ## The first section @@ -56,7 +56,7 @@ class TutorialArticleTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -75,7 +75,7 @@ TutorialArticle @1:1-13:2 } /// Tests that we parse correctly and emit proper warnings when the author provides non-sequential headers. - func testHeaderMix() throws { + func testHeaderMix() async throws { let source = """ @Article { ## The first section @@ -106,7 +106,7 @@ TutorialArticle @1:1-13:2 let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -124,7 +124,7 @@ TutorialArticle @1:1-23:2 } } - func testIntroAndContent() throws { + func testIntroAndContent() async throws { let source = """ @Article(time: 20) { @@ -155,7 +155,7 @@ TutorialArticle @1:1-23:2 let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -176,7 +176,7 @@ TutorialArticle @1:1-23:2 title: 'Basic Augmented Reality App' time: '20' } } - func testLayouts() throws { + func testLayouts() async throws { let source = """ @Article { @@ -265,7 +265,7 @@ TutorialArticle @1:1-23:2 title: 'Basic Augmented Reality App' time: '20' let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -311,7 +311,7 @@ TutorialArticle @1:1-81:2 } } - func testAssessment() throws { + func testAssessment() async throws { let source = """ @Article(time: 20) { @Intro(title: "Basic Augmented Reality App") { @@ -361,7 +361,7 @@ TutorialArticle @1:1-81:2 let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -393,12 +393,12 @@ TutorialArticle @1:1-42:2 title: 'Basic Augmented Reality App' time: '20' } } - func testAnalyzeNode() throws { + func testAnalyzeNode() async throws { let title = "unreferenced-tutorial" let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TopicGraphTests", path: "/\(title)", sourceLanguage: .swift) let node = TopicGraph.Node(reference: reference, kind: .tutorialTableOfContents, source: .file(url: URL(fileURLWithPath: "/path/to/\(title)")), title: title) - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") context.topicGraph.addNode(node) let engine = DiagnosticEngine() @@ -412,12 +412,12 @@ TutorialArticle @1:1-42:2 title: 'Basic Augmented Reality App' time: '20' XCTAssertTrue(source.isFileURL) } - func testAnalyzeExternalNode() throws { + func testAnalyzeExternalNode() async throws { let title = "unreferenced-tutorial" let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TopicGraphTests", path: "/\(title)", sourceLanguage: .swift) let node = TopicGraph.Node(reference: reference, kind: .tutorialTableOfContents, source: .external, title: title) - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") context.topicGraph.addNode(node) let engine = DiagnosticEngine() @@ -430,14 +430,14 @@ TutorialArticle @1:1-42:2 title: 'Basic Augmented Reality App' time: '20' XCTAssertNil(problem.diagnostic.source) } - func testAnalyzeFragmentNode() throws { + func testAnalyzeFragmentNode() async throws { let title = "unreferenced-tutorial" let url = URL(fileURLWithPath: "/path/to/\(title)") let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TopicGraphTests", path: "/\(title)", sourceLanguage: .swift) let range = SourceLocation(line: 1, column: 1, source: url).. TopicGraph.Node { let url = URL(fileURLWithPath: "/path/to/\(title)") let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TutorialArticleTests", path: "/\(title)", sourceLanguage: .swift) @@ -459,7 +459,7 @@ TutorialArticle @1:1-42:2 title: 'Basic Augmented Reality App' time: '20' return TopicGraph.Node(reference: reference, kind: kind, source: .range(range, url: url) , title: title) } - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let tutorialArticleNode = node(withTitle: "tutorial-article", ofKind: .tutorialArticle) diff --git a/Tests/SwiftDocCTests/Semantics/TutorialReferenceTests.swift b/Tests/SwiftDocCTests/Semantics/TutorialReferenceTests.swift index 38d10354bb..d7f35bb485 100644 --- a/Tests/SwiftDocCTests/Semantics/TutorialReferenceTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TutorialReferenceTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class TutorialReferenceTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = """ @TutorialReference """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tutorialReference = TutorialReference(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(tutorialReference) @@ -30,14 +30,14 @@ class TutorialReferenceTests: XCTestCase { } } - func testValid() throws { + func testValid() async throws { let tutorialLink = "doc:MyTutorial" let source = """ @TutorialReference(tutorial: "\(tutorialLink)") """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tutorialReference = TutorialReference(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tutorialReference) @@ -50,14 +50,14 @@ class TutorialReferenceTests: XCTestCase { XCTAssertTrue(problems.isEmpty) } - func testMissingPath() throws { + func testMissingPath() async throws { let tutorialLink = "doc:" let source = """ @TutorialReference(tutorial: "\(tutorialLink)") """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") var problems = [Problem]() let tutorialReference = TutorialReference(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(tutorialReference) diff --git a/Tests/SwiftDocCTests/Semantics/TutorialTests.swift b/Tests/SwiftDocCTests/Semantics/TutorialTests.swift index fb545712a1..816b99cd21 100644 --- a/Tests/SwiftDocCTests/Semantics/TutorialTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TutorialTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class TutorialTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@Tutorial" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -38,7 +38,7 @@ class TutorialTests: XCTestCase { } } - func testValid() throws { + func testValid() async throws { let source = """ @Tutorial(time: 20) { @XcodeRequirement(title: "Xcode X.Y Beta Z", destination: "https://www.example.com/download") @@ -195,7 +195,7 @@ class TutorialTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -280,7 +280,7 @@ Tutorial @1:1-150:2 projectFiles: nil } } - func testDuplicateSectionTitle() throws { + func testDuplicateSectionTitle() async throws { let source = """ @Tutorial(time: 20) { @XcodeRequirement(title: "Xcode X.Y Beta Z", destination: "https://www.example.com/download") @@ -354,7 +354,7 @@ Tutorial @1:1-150:2 projectFiles: nil let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") directive.map { directive in var problems = [Problem]() @@ -368,12 +368,12 @@ Tutorial @1:1-150:2 projectFiles: nil } } - func testAnalyzeNode() throws { + func testAnalyzeNode() async throws { let title = "unreferenced-tutorial" let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TopicGraphTests", path: "/\(title)", sourceLanguage: .swift) let node = TopicGraph.Node(reference: reference, kind: .tutorialTableOfContents, source: .file(url: URL(fileURLWithPath: "/path/to/\(title)")), title: title) - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") context.topicGraph.addNode(node) let engine = DiagnosticEngine() @@ -387,12 +387,12 @@ Tutorial @1:1-150:2 projectFiles: nil XCTAssertTrue(source.isFileURL) } - func testAnalyzeExternalNode() throws { + func testAnalyzeExternalNode() async throws { let title = "unreferenced-tutorial" let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TopicGraphTests", path: "/\(title)", sourceLanguage: .swift) let node = TopicGraph.Node(reference: reference, kind: .tutorialTableOfContents, source: .external, title: title) - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") context.topicGraph.addNode(node) let engine = DiagnosticEngine() @@ -405,14 +405,14 @@ Tutorial @1:1-150:2 projectFiles: nil XCTAssertNil(problem.diagnostic.source) } - func testAnalyzeFragmentNode() throws { + func testAnalyzeFragmentNode() async throws { let title = "unreferenced-tutorial" let url = URL(fileURLWithPath: "/path/to/\(title)") let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TopicGraphTests", path: "/\(title)", sourceLanguage: .swift) let range = SourceLocation(line: 1, column: 1, source: url).. TopicGraph.Node { let url = URL(fileURLWithPath: "/path/to/\(title)") let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TutorialArticleTests", path: "/\(title)", sourceLanguage: .swift) @@ -434,7 +434,7 @@ Tutorial @1:1-150:2 projectFiles: nil return TopicGraph.Node(reference: reference, kind: kind, source: .range(range, url: url) , title: title) } - let (_, context) = try testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let tutorialNode = node(withTitle: "tutorial-article", ofKind: .tutorial) diff --git a/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift b/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift index eacae09d49..010fb3125a 100644 --- a/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift +++ b/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift @@ -14,13 +14,13 @@ import Markdown import SwiftDocCTestUtilities class VideoMediaTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = """ @Video """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let video = VideoMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(video) @@ -31,7 +31,7 @@ class VideoMediaTests: XCTestCase { } } - func testValid() throws { + func testValid() async throws { let videoSource = "/path/to/video" let poster = "/path/to/poster" let source = """ @@ -39,7 +39,7 @@ class VideoMediaTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let video = VideoMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(video) @@ -50,7 +50,7 @@ class VideoMediaTests: XCTestCase { } } - func testSpacesInSourceAndPoster() throws { + func testSpacesInSourceAndPoster() async throws { for videoSource in ["my image.mov", "my%20image.mov"] { let poster = videoSource.replacingOccurrences(of: ".mov", with: ".png") let source = """ @@ -58,7 +58,7 @@ class VideoMediaTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let video = VideoMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(video) @@ -70,14 +70,14 @@ class VideoMediaTests: XCTestCase { } } - func testIncorrectArgumentLabels() throws { + func testIncorrectArgumentLabels() async throws { let source = """ @Video(sourceURL: "/video/path", posterURL: "/poster/path") """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let video = VideoMedia(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(video) @@ -100,10 +100,10 @@ class VideoMediaTests: XCTestCase { DataFile(name: "introvideo~dark.mp4", data: Data()), ]) - func testRenderVideoDirectiveInReferenceMarkup() throws { + func testRenderVideoDirectiveInReferenceMarkup() async throws { do { - let (renderedContent, problems, video, _) = try parseDirective(VideoMedia.self, catalog: catalog) { + let (renderedContent, problems, video, _) = try await parseDirective(VideoMedia.self, catalog: catalog) { """ @Video(source: "introvideo") """ @@ -125,7 +125,7 @@ class VideoMediaTests: XCTestCase { } do { - let (renderedContent, problems, video, _) = try parseDirective(VideoMedia.self, catalog: catalog) { + let (renderedContent, problems, video, _) = try await parseDirective(VideoMedia.self, catalog: catalog) { """ @Video(source: "unknown-video") """ @@ -139,7 +139,7 @@ class VideoMediaTests: XCTestCase { } do { - let (renderedContent, problems, video, _) = try parseDirective(VideoMedia.self, catalog: catalog) { + let (renderedContent, problems, video, _) = try await parseDirective(VideoMedia.self, catalog: catalog) { """ @Video(source: "introvideo", poster: "unknown-poster") """ @@ -161,8 +161,8 @@ class VideoMediaTests: XCTestCase { } } - func testRenderVideoDirectiveWithCaption() throws { - let (renderedContent, problems, video, _) = try parseDirective(VideoMedia.self, catalog: catalog) { + func testRenderVideoDirectiveWithCaption() async throws { + let (renderedContent, problems, video, _) = try await parseDirective(VideoMedia.self, catalog: catalog) { """ @Video(source: "introvideo") { This is my caption. @@ -185,8 +185,8 @@ class VideoMediaTests: XCTestCase { ) } - func testRenderVideoDirectiveWithCaptionAndPosterImage() throws { - let (renderedContent, problems, video, references) = try parseDirective(VideoMedia.self, catalog: catalog) { + func testRenderVideoDirectiveWithCaptionAndPosterImage() async throws { + let (renderedContent, problems, video, references) = try await parseDirective(VideoMedia.self, catalog: catalog) { """ @Video(source: "introvideo", alt: "An introductory video", poster: "introposter") { This is my caption. @@ -217,8 +217,8 @@ class VideoMediaTests: XCTestCase { XCTAssertTrue(references.keys.contains("introposter")) } - func testVideoMediaDiagnosesDeviceFrameByDefault() throws { - let (renderedContent, problems, video, _) = try parseDirective(VideoMedia.self, catalog: catalog) { + func testVideoMediaDiagnosesDeviceFrameByDefault() async throws { + let (renderedContent, problems, video, _) = try await parseDirective(VideoMedia.self, catalog: catalog) { """ @Video(source: "introvideo", deviceFrame: watch) """ @@ -239,10 +239,10 @@ class VideoMediaTests: XCTestCase { ) } - func testRenderVideoDirectiveWithDeviceFrame() throws { + func testRenderVideoDirectiveWithDeviceFrame() async throws { enableFeatureFlag(\.isExperimentalDeviceFrameSupportEnabled) - let (renderedContent, problems, video, _) = try parseDirective(VideoMedia.self, catalog: catalog) { + let (renderedContent, problems, video, _) = try await parseDirective(VideoMedia.self, catalog: catalog) { """ @Video(source: "introvideo", deviceFrame: watch) """ @@ -263,10 +263,10 @@ class VideoMediaTests: XCTestCase { ) } - func testRenderVideoDirectiveWithCaptionAndDeviceFrame() throws { + func testRenderVideoDirectiveWithCaptionAndDeviceFrame() async throws { enableFeatureFlag(\.isExperimentalDeviceFrameSupportEnabled) - let (renderedContent, problems, video, references) = try parseDirective(VideoMedia.self, catalog: catalog) { + let (renderedContent, problems, video, references) = try await parseDirective(VideoMedia.self, catalog: catalog) { """ @Video(source: "introvideo", alt: "An introductory video", poster: "introposter", deviceFrame: laptop) { This is my caption. @@ -297,11 +297,11 @@ class VideoMediaTests: XCTestCase { XCTAssertTrue(references.keys.contains("introposter")) } - func testVideoDirectiveDoesNotResolveImageMedia() throws { + func testVideoDirectiveDoesNotResolveImageMedia() async throws { // The rest of the test in this file will fail if 'introposter' and 'introvideo' // do not exist. We just reverse them here to make sure the reference resolving is // media-type specific. - let (renderedContent, problems, video, _) = try parseDirective(VideoMedia.self, catalog: catalog) { + let (renderedContent, problems, video, _) = try await parseDirective(VideoMedia.self, catalog: catalog) { """ @Video(source: "introposter", poster: "introvideo") """ @@ -320,13 +320,13 @@ class VideoMediaTests: XCTestCase { XCTAssertEqual(renderedContent, []) } - func testVideoDirectiveWithAltText() throws { + func testVideoDirectiveWithAltText() async throws { let source = """ @Video(source: "introvideo", alt: "A short video of a sloth jumping down from a branch and smiling.") """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try loadBundle( + let (bundle, context) = try await loadBundle( catalog: Folder(name: "unit-test.docc", content: [ DataFile(name: "introvideo.mov", data: Data()) ]) diff --git a/Tests/SwiftDocCTests/Semantics/VolumeTests.swift b/Tests/SwiftDocCTests/Semantics/VolumeTests.swift index 5c23045a5b..827ab38f7d 100644 --- a/Tests/SwiftDocCTests/Semantics/VolumeTests.swift +++ b/Tests/SwiftDocCTests/Semantics/VolumeTests.swift @@ -14,13 +14,13 @@ import Markdown import SwiftDocCTestUtilities class VolumeTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = """ @Volume """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let volume = Volume(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(volume) @@ -33,7 +33,7 @@ class VolumeTests: XCTestCase { ], problems.map { $0.diagnostic.identifier }) } - func testValid() throws { + func testValid() async throws { let name = "Always Be Voluming" let expectedContent = "Here is some content explaining what this volume is." let source = """ @@ -51,7 +51,7 @@ class VolumeTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let volume = Volume(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(volume) @@ -62,7 +62,7 @@ class VolumeTests: XCTestCase { } } - func testChapterWithSameName() throws { + func testChapterWithSameName() async throws { let name = "Always Be Voluming" let catalog = Folder(name: "unit-test.docc", content: [ @@ -97,7 +97,7 @@ class VolumeTests: XCTestCase { """) ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) let node = try context.entity( with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestOverview", sourceLanguage: .swift) ) diff --git a/Tests/SwiftDocCTests/Semantics/XcodeRequirementTests.swift b/Tests/SwiftDocCTests/Semantics/XcodeRequirementTests.swift index 240d2b780a..f477495bf8 100644 --- a/Tests/SwiftDocCTests/Semantics/XcodeRequirementTests.swift +++ b/Tests/SwiftDocCTests/Semantics/XcodeRequirementTests.swift @@ -13,13 +13,13 @@ import XCTest import Markdown class XcodeRequirementTests: XCTestCase { - func testEmpty() throws { + func testEmpty() async throws { let source = "@XcodeRequirement" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() @@ -38,7 +38,7 @@ class XcodeRequirementTests: XCTestCase { } } - func testValid() throws { + func testValid() async throws { let title = "Xcode 10.2 Beta 3" let destination = "https://www.example.com/download" let source = """ @@ -48,7 +48,7 @@ class XcodeRequirementTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try testBundleAndContext() + let (bundle, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() diff --git a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift index 4281998cc5..67c0f5e3a7 100644 --- a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift +++ b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift @@ -88,8 +88,8 @@ extension XCTestCase { for bundleName: String, sourceRepository: SourceRepository? = nil, configureBundle: ((URL) throws -> Void)? = nil - ) throws -> TestRenderNodeOutputConsumer { - let (_, bundle, context) = try testBundleAndContext( + ) async throws -> TestRenderNodeOutputConsumer { + let (_, bundle, context) = try await testBundleAndContext( copying: bundleName, configureBundle: configureBundle ) diff --git a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift index d653800be1..59fe23e12b 100644 --- a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift +++ b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.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 @@ -74,8 +74,8 @@ class ListItemExtractorTests: XCTestCase { XCTAssert(extractedTags("- PossibleValue: Missing value name.").possiblePropertyListValues.isEmpty) } - func testExtractingTags() throws { - try assertExtractsRichContentFor( + func testExtractingTags() async throws { + try await assertExtractsRichContentFor( tagName: "Returns", findModelContent: { semantic in semantic.returnsSection?.content @@ -83,7 +83,7 @@ class ListItemExtractorTests: XCTestCase { renderContentSectionTitle: "Return Value" ) - try assertExtractsRichContentFor( + try await assertExtractsRichContentFor( tagName: "Note", isAside: true, findModelContent: { semantic in @@ -105,7 +105,7 @@ class ListItemExtractorTests: XCTestCase { }) ) - try assertExtractsRichContentFor( + try await assertExtractsRichContentFor( tagName: "Precondition", isAside: true, findModelContent: { semantic in @@ -127,7 +127,7 @@ class ListItemExtractorTests: XCTestCase { }) ) - try assertExtractsRichContentFor( + try await assertExtractsRichContentFor( tagName: "Parameter someParameterName", findModelContent: { semantic in semantic.parametersSection?.parameters.first?.contents @@ -140,7 +140,7 @@ class ListItemExtractorTests: XCTestCase { }) ) - try assertExtractsRichContentOutlineFor( + try await assertExtractsRichContentOutlineFor( tagName: "Parameters", findModelContent: { semantic in semantic.parametersSection?.parameters.first?.contents @@ -156,7 +156,7 @@ class ListItemExtractorTests: XCTestCase { // Dictionary and HTTP tags are filtered out from the rendering without symbol information. // These test helpers can't easily set up a bundle that supports general tags, REST tags, and HTTP tags. - try assertExtractsRichContentFor( + try await assertExtractsRichContentFor( tagName: "DictionaryKey someKey", findModelContent: { semantic in semantic.dictionaryKeysSection?.dictionaryKeys.first?.contents @@ -164,7 +164,7 @@ class ListItemExtractorTests: XCTestCase { renderVerification: .skip ) - try assertExtractsRichContentOutlineFor( + try await assertExtractsRichContentOutlineFor( tagName: "DictionaryKeys", findModelContent: { semantic in semantic.dictionaryKeysSection?.dictionaryKeys.first?.contents @@ -172,7 +172,7 @@ class ListItemExtractorTests: XCTestCase { renderVerification: .skip ) - try assertExtractsRichContentFor( + try await assertExtractsRichContentFor( tagName: "HTTPResponse 200", findModelContent: { semantic in semantic.httpResponsesSection?.responses.first?.contents @@ -180,7 +180,7 @@ class ListItemExtractorTests: XCTestCase { renderVerification: .skip ) - try assertExtractsRichContentOutlineFor( + try await assertExtractsRichContentOutlineFor( tagName: "HTTPResponses", findModelContent: { semantic in semantic.httpResponsesSection?.responses.first?.contents @@ -188,7 +188,7 @@ class ListItemExtractorTests: XCTestCase { renderVerification: .skip ) - try assertExtractsRichContentFor( + try await assertExtractsRichContentFor( tagName: "httpBody", findModelContent: { semantic in semantic.httpBodySection?.body.contents @@ -196,7 +196,7 @@ class ListItemExtractorTests: XCTestCase { renderVerification: .skip ) - try assertExtractsRichContentFor( + try await assertExtractsRichContentFor( tagName: "HTTPParameter someParameter", findModelContent: { semantic in semantic.httpParametersSection?.parameters.first?.contents @@ -204,7 +204,7 @@ class ListItemExtractorTests: XCTestCase { renderVerification: .skip ) - try assertExtractsRichContentOutlineFor( + try await assertExtractsRichContentOutlineFor( tagName: "HTTPParameters", findModelContent: { semantic in semantic.httpParametersSection?.parameters.first?.contents @@ -212,7 +212,7 @@ class ListItemExtractorTests: XCTestCase { renderVerification: .skip ) - try assertExtractsRichContentFor( + try await assertExtractsRichContentFor( tagName: "HTTPBodyParameter someParameter", findModelContent: { semantic in semantic.httpBodySection?.body.parameters.first?.contents @@ -220,7 +220,7 @@ class ListItemExtractorTests: XCTestCase { renderVerification: .skip ) - try assertExtractsRichContentOutlineFor( + try await assertExtractsRichContentOutlineFor( tagName: "HTTPBodyParameters", findModelContent: { semantic in semantic.httpBodySection?.body.parameters.first?.contents @@ -237,8 +237,8 @@ class ListItemExtractorTests: XCTestCase { renderContentSectionTitle: String, file: StaticString = #filePath, line: UInt = #line - ) throws { - try assertExtractsRichContentFor( + ) async throws { + try await assertExtractsRichContentFor( tagName: tagName, isAside: false, findModelContent: findModelContent, @@ -270,10 +270,10 @@ class ListItemExtractorTests: XCTestCase { renderVerification: RenderVerification, file: StaticString = #filePath, line: UInt = #line - ) throws { + ) async throws { // Build documentation for a module page with one tagged item with a lot of different - let (bundle, context) = try loadBundle( + let (bundle, context) = try await loadBundle( catalog: Folder(name: "Something.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName")), TextFile(name: "Extension.md", utf8Content: """ @@ -336,10 +336,10 @@ class ListItemExtractorTests: XCTestCase { renderVerification: RenderVerification, file: StaticString = #filePath, line: UInt = #line - ) throws { + ) async throws { // Build documentation for a module page with one tagged item with a lot of different - let (bundle, context) = try loadBundle( + let (bundle, context) = try await loadBundle( catalog: Folder(name: "Something.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName")), TextFile(name: "Extension.md", utf8Content: """ diff --git a/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift b/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift index b11e588e88..a7306396b9 100644 --- a/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift +++ b/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.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 @@ -15,7 +15,7 @@ import SymbolKit extension XCTestCase { /// Creates a test bundle for testing "Mentioned In" features. - func createMentionedInTestBundle() throws -> (DocumentationBundle, DocumentationContext) { + func createMentionedInTestBundle() async throws -> (DocumentationBundle, DocumentationContext) { let catalog = Folder(name: "MentionedIn.docc", content: [ JSONFile(name: "MentionedIn.symbols.json", content: makeSymbolGraph( moduleName: "MentionedIn", @@ -72,7 +72,7 @@ extension XCTestCase { """), ]) - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) return (bundle, context) } } diff --git a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift index 6937a6c6e5..754a05314c 100644 --- a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift +++ b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift @@ -24,7 +24,7 @@ extension XCTestCase { fallbackResolver: (any ConvertServiceFallbackResolver)? = nil, diagnosticEngine: DiagnosticEngine = .init(filterLevel: .hint), configuration: DocumentationContext.Configuration = .init() - ) throws -> (URL, DocumentationBundle, DocumentationContext) { + ) async throws -> (URL, DocumentationBundle, DocumentationContext) { var configuration = configuration configuration.externalDocumentationConfiguration.sources = externalResolvers configuration.externalDocumentationConfiguration.globalSymbolResolver = externalSymbolResolver @@ -34,7 +34,7 @@ extension XCTestCase { let (bundle, dataProvider) = try DocumentationContext.InputsProvider() .inputsAndDataProvider(startingPoint: catalogURL, options: .init()) - let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) + let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) return (catalogURL, bundle, context) } @@ -51,13 +51,13 @@ extension XCTestCase { otherFileSystemDirectories: [Folder] = [], diagnosticEngine: DiagnosticEngine = .init(), configuration: DocumentationContext.Configuration = .init() - ) throws -> (DocumentationBundle, DocumentationContext) { + ) async throws -> (DocumentationBundle, DocumentationContext) { let fileSystem = try TestFileSystem(folders: [catalog] + otherFileSystemDirectories) let (bundle, dataProvider) = try DocumentationContext.InputsProvider(fileManager: fileSystem) .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/\(catalog.name)"), options: .init()) - let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) + let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) return (bundle, context) } @@ -77,7 +77,7 @@ extension XCTestCase { diagnosticEngine: DiagnosticEngine = .init(filterLevel: .hint), configuration: DocumentationContext.Configuration = .init(), configureBundle: ((URL) throws -> Void)? = nil - ) throws -> (URL, DocumentationBundle, DocumentationContext) { + ) async throws -> (URL, DocumentationBundle, DocumentationContext) { let sourceURL = try testCatalogURL(named: name) let sourceExists = FileManager.default.fileExists(atPath: sourceURL.path) @@ -96,7 +96,7 @@ extension XCTestCase { // Do any additional setup to the custom bundle - adding, modifying files, etc try configureBundle?(bundleURL) - return try loadBundle( + return try await loadBundle( from: bundleURL, externalResolvers: externalResolvers, externalSymbolResolver: externalSymbolResolver, @@ -111,25 +111,25 @@ extension XCTestCase { externalResolvers: [DocumentationBundle.Identifier: any ExternalDocumentationSource] = [:], fallbackResolver: (any ConvertServiceFallbackResolver)? = nil, configuration: DocumentationContext.Configuration = .init() - ) throws -> (URL, DocumentationBundle, DocumentationContext) { + ) async throws -> (URL, DocumentationBundle, DocumentationContext) { let catalogURL = try testCatalogURL(named: name) - return try loadBundle(from: catalogURL, externalResolvers: externalResolvers, fallbackResolver: fallbackResolver, configuration: configuration) + return try await loadBundle(from: catalogURL, externalResolvers: externalResolvers, fallbackResolver: fallbackResolver, configuration: configuration) } - func testBundleAndContext(named name: String, externalResolvers: [DocumentationBundle.Identifier: any ExternalDocumentationSource] = [:]) throws -> (DocumentationBundle, DocumentationContext) { - let (_, bundle, context) = try testBundleAndContext(named: name, externalResolvers: externalResolvers) + func testBundleAndContext(named name: String, externalResolvers: [DocumentationBundle.Identifier: any ExternalDocumentationSource] = [:]) async throws -> (DocumentationBundle, DocumentationContext) { + let (_, bundle, context) = try await testBundleAndContext(named: name, externalResolvers: externalResolvers) return (bundle, context) } - func renderNode(atPath path: String, fromTestBundleNamed testBundleName: String) throws -> RenderNode { - let (bundle, context) = try testBundleAndContext(named: testBundleName) + func renderNode(atPath path: String, fromTestBundleNamed testBundleName: String) async throws -> RenderNode { + let (bundle, context) = try await testBundleAndContext(named: testBundleName) let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: path, sourceLanguage: .swift)) var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) return try XCTUnwrap(translator.visit(node.semantic) as? RenderNode) } - func testBundle(named name: String) throws -> DocumentationBundle { - let (bundle, _) = try testBundleAndContext(named: name) + func testBundle(named name: String) async throws -> DocumentationBundle { + let (bundle, _) = try await testBundleAndContext(named: name) return bundle } @@ -140,7 +140,7 @@ extension XCTestCase { return try inputProvider.makeInputs(contentOf: catalogURL, options: .init()) } - func testBundleAndContext() throws -> (bundle: DocumentationBundle, context: DocumentationContext) { + func testBundleAndContext() async throws -> (bundle: DocumentationBundle, context: DocumentationContext) { let bundle = DocumentationBundle( info: DocumentationBundle.Info( displayName: "Test", @@ -152,7 +152,7 @@ extension XCTestCase { miscResourceURLs: [] ) - let context = try DocumentationContext(bundle: bundle, dataProvider: TestFileSystem(folders: [])) + let context = try await DocumentationContext(bundle: bundle, dataProvider: TestFileSystem(folders: [])) return (bundle, context) } @@ -161,8 +161,8 @@ extension XCTestCase { content: () -> String, file: StaticString = #filePath, line: UInt = #line - ) throws -> (problemIdentifiers: [String], directive: Directive?) { - let (bundle, _) = try testBundleAndContext() + ) async throws -> (problemIdentifiers: [String], directive: Directive?) { + let (bundle, _) = try await testBundleAndContext() let source = URL(fileURLWithPath: "/path/to/test-source-\(ProcessInfo.processInfo.globallyUniqueString)") let document = Document(parsing: content(), source: source, options: .parseBlockDirectives) @@ -193,13 +193,13 @@ extension XCTestCase { content: () -> String, file: StaticString = #filePath, line: UInt = #line - ) throws -> ( + ) async throws -> ( renderBlockContent: [RenderBlockContent], problemIdentifiers: [String], directive: Directive?, collectedReferences: [String : any RenderReference] ) { - let (bundle, context) = try loadBundle(catalog: catalog) + let (bundle, context) = try await loadBundle(catalog: catalog) return try parseDirective(directive, bundle: bundle, context: context, content: content, file: file, line: line) } @@ -209,8 +209,8 @@ extension XCTestCase { content: () -> String, file: StaticString = #filePath, line: UInt = #line - ) throws -> (renderBlockContent: [RenderBlockContent], problemIdentifiers: [String], directive: Directive?) { - let (renderedContent, problems, directive, _) = try parseDirective( + ) async throws -> (renderBlockContent: [RenderBlockContent], problemIdentifiers: [String], directive: Directive?) { + let (renderedContent, problems, directive, _) = try await parseDirective( directive, in: bundleName, content: content, @@ -227,7 +227,7 @@ extension XCTestCase { content: () -> String, file: StaticString = #filePath, line: UInt = #line - ) throws -> ( + ) async throws -> ( renderBlockContent: [RenderBlockContent], problemIdentifiers: [String], directive: Directive?, @@ -237,9 +237,9 @@ extension XCTestCase { let context: DocumentationContext if let bundleName { - (bundle, context) = try testBundleAndContext(named: bundleName) + (bundle, context) = try await testBundleAndContext(named: bundleName) } else { - (bundle, context) = try testBundleAndContext() + (bundle, context) = try await testBundleAndContext() } return try parseDirective(directive, bundle: bundle, context: context, content: content, file: file, line: line) } diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift index 031e9f6a10..39fa7a46d8 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-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 @@ -16,11 +16,11 @@ import Foundation class ConvertActionIndexerTests: XCTestCase { // Tests the standalone indexer - func testConvertActionIndexer() throws { + func testConvertActionIndexer() async throws { let (bundle, dataProvider) = try DocumentationContext.InputsProvider() .inputsAndDataProvider(startingPoint: testCatalogURL(named: "LegacyBundle_DoNotUseInNewTests"), options: .init()) - let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider) + let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider) let converter = DocumentationNodeConverter(bundle: bundle, context: context) // Add /documentation/MyKit to the index, verify the tree dump diff --git a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift index e03f03330d..0853d154e4 100644 --- a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.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 @@ -849,7 +849,7 @@ class MergeActionTests: XCTestCase { let baseOutputDir = URL(fileURLWithPath: "/path/to/some-output-dir") try fileSystem.createDirectory(at: baseOutputDir, withIntermediateDirectories: true) - func convertCatalog(named name: String, file: StaticString = #filePath, line: UInt = #line) throws -> URL { + func convertCatalog(named name: String, file: StaticString = #filePath, line: UInt = #line) async throws -> URL { let catalog = Folder(name: "\(name).docc", content: [ TextFile(name: "\(name).md", utf8Content: """ # My root @@ -882,7 +882,7 @@ class MergeActionTests: XCTestCase { "\(name.lowercased())-card.png", ]) - let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: .init()) + let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: .init()) XCTAssert( context.problems.filter { $0.diagnostic.identifier != "org.swift.docc.SummaryContainsLink" }.isEmpty, @@ -933,8 +933,8 @@ class MergeActionTests: XCTestCase { return outputPath } - let firstArchiveDir = try convertCatalog(named: "First") - let secondArchiveDir = try convertCatalog(named: "Second") + let firstArchiveDir = try await convertCatalog(named: "First") + let secondArchiveDir = try await convertCatalog(named: "Second") let combinedArchiveDir = URL(fileURLWithPath: "/Output.doccarchive") let action = MergeAction( diff --git a/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift b/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift index 745ce771cb..034b935152 100644 --- a/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift @@ -54,14 +54,14 @@ class SemanticAnalyzerTests: XCTestCase { InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), ]) - func testDoNotCrashOnInvalidContent() throws { - let (bundle, context) = try loadBundle(catalog: catalogHierarchy) + func testDoNotCrashOnInvalidContent() async throws { + let (bundle, context) = try await loadBundle(catalog: catalogHierarchy) XCTAssertThrowsError(try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/Oops", sourceLanguage: .swift))) } - func testWarningsAboutDirectiveSupport() throws { - func problemsConvertingTestContent(withFileExtension fileExtension: String) throws -> (unsupportedTopLevelChildProblems: [Problem], missingTopLevelChildProblems: [Problem]) { + func testWarningsAboutDirectiveSupport() async throws { + func problemsConvertingTestContent(withFileExtension fileExtension: String) async throws -> (unsupportedTopLevelChildProblems: [Problem], missingTopLevelChildProblems: [Problem]) { let catalogHierarchy = Folder(name: "SemanticAnalyzerTests.docc", content: [ TextFile(name: "FileWithDirective.\(fileExtension)", utf8Content: """ @Article @@ -73,7 +73,7 @@ class SemanticAnalyzerTests: XCTestCase { """), InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), ]) - let (_, context) = try loadBundle(catalog: catalogHierarchy) + let (_, context) = try await loadBundle(catalog: catalogHierarchy) return ( context.problems.filter({ $0.diagnostic.identifier == "org.swift.docc.unsupportedTopLevelChild" }), @@ -82,7 +82,7 @@ class SemanticAnalyzerTests: XCTestCase { } do { - let problems = try problemsConvertingTestContent(withFileExtension: "md") + let problems = try await problemsConvertingTestContent(withFileExtension: "md") XCTAssertEqual(problems.missingTopLevelChildProblems.count, 0) XCTAssertEqual(problems.unsupportedTopLevelChildProblems.count, 1) @@ -95,7 +95,7 @@ class SemanticAnalyzerTests: XCTestCase { } do { - let problems = try problemsConvertingTestContent(withFileExtension: "tutorial") + let problems = try await problemsConvertingTestContent(withFileExtension: "tutorial") XCTAssertEqual(problems.missingTopLevelChildProblems.count, 1) XCTAssertEqual(problems.unsupportedTopLevelChildProblems.count, 0) @@ -109,8 +109,8 @@ class SemanticAnalyzerTests: XCTestCase { } } - func testDoesNotWarnOnEmptyTutorials() throws { - let (bundle, _) = try loadBundle(catalog: catalogHierarchy) + func testDoesNotWarnOnEmptyTutorials() async throws { + let (bundle, _) = try await loadBundle(catalog: catalogHierarchy) let document = Document(parsing: "", options: .parseBlockDirectives) var analyzer = SemanticAnalyzer(source: URL(string: "/empty.tutorial"), bundle: bundle) diff --git a/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift b/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift index f6da874d1d..cc50e4ca1f 100644 --- a/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift +++ b/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.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 @@ -25,13 +25,13 @@ extension XCTestCase { catalog: Folder, otherFileSystemDirectories: [Folder] = [], configuration: DocumentationContext.Configuration = .init() - ) throws -> (DocumentationBundle, DocumentationContext) { + ) async throws -> (DocumentationBundle, DocumentationContext) { let fileSystem = try TestFileSystem(folders: [catalog] + otherFileSystemDirectories) let (bundle, dataProvider) = try DocumentationContext.InputsProvider(fileManager: fileSystem) .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/\(catalog.name)"), options: .init()) - let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: configuration) + let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: configuration) return (bundle, context) } From 71032cd7b74c0de6f82f3e96beba3dad90b9b24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 1 Aug 2025 09:27:35 +0200 Subject: [PATCH 14/90] Add a utility to update the years in license comments of modified files (#1261) * Add a utility to update the years in license comments of modified files * Move license updating utility to a dedicated sub directory * Use `Regex` instead of `NSRegularExpression` --- bin/update-license-comments/Package.swift | 24 +++ .../main.swift | 141 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 bin/update-license-comments/Package.swift create mode 100644 bin/update-license-comments/Sources/update-license-for-modified-files/main.swift diff --git a/bin/update-license-comments/Package.swift b/bin/update-license-comments/Package.swift new file mode 100644 index 0000000000..69e55f230a --- /dev/null +++ b/bin/update-license-comments/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version: 6.1 +/* + 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 PackageDescription + +let package = Package( + name: "update-license-for-modified-files", + platforms: [ + .macOS(.v13) + ], + targets: [ + .executableTarget( + name: "update-license-for-modified-files" + ), + ] +) diff --git a/bin/update-license-comments/Sources/update-license-for-modified-files/main.swift b/bin/update-license-comments/Sources/update-license-for-modified-files/main.swift new file mode 100644 index 0000000000..9cdcc74d05 --- /dev/null +++ b/bin/update-license-comments/Sources/update-license-for-modified-files/main.swift @@ -0,0 +1,141 @@ +/* + 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 + +// Determine what changes to consider + +enum DiffStrategy { + case stagedFiles + case comparingTo(treeish: String) +} + +let arguments = ProcessInfo.processInfo.arguments.dropFirst() +let diffStrategy: DiffStrategy +switch arguments.first { + case "-h", "--help": + print(""" + OVERVIEW: Update the year in the license comment of modified files + + USAGE: swift run update-license-for-modified-files [--staged | ] + + To update the year for staged, but not yet committed files, run: + swift run update-license-for-modified-files --staged + + To update the year for all already committed changes that are different from the 'main' branch, run: + swift run update-license-for-modified-files + + To update the year for the already committed changes in the last commit, run: + swift run update-license-for-modified-files HEAD~ + + You can specify any other branch or commit for this argument but I don't know if there's a real use case for doing so. + """) + exit(0) + + case nil: + diffStrategy = .comparingTo(treeish: "main") + case "--staged", "--cached": + diffStrategy = .stagedFiles + case let treeish?: + diffStrategy = .comparingTo(treeish: treeish) +} + +// Find which files are modified + +let repoURL: URL = { + let url = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() // main.swift + .deletingLastPathComponent() // update-license-for-modified-files + .deletingLastPathComponent() // Sources + .deletingLastPathComponent() // update-license-comments + .deletingLastPathComponent() // bin + guard FileManager.default.fileExists(atPath: url.appendingPathComponent("Package.swift").path) else { + fatalError("The path to the Swift-DocC source root has changed. This should only happen if the 'update-license-comments' sources have moved relative to the Swift-DocC repo.") + } + return url +}() + +let modifiedFiles = try findModifiedFiles(in: repoURL, strategy: diffStrategy) + +// Update the years in the license comment where necessary + +// An optional lower range of years for the license comment (including the hyphen) +// │ The upper range of years for the license comment +// │ │ The markdown files don't have a "." but the Swift files do +// │ │ │ The markdown files capitalize the P but the Swift files don't +// │ │ │ │ +// ╭─────┴──────╮╭────┴─────╮ ╭┴╮ ╭┴─╮ +let licenseRegex = /Copyright \(c\) (20[0-9]{2}-)?(20[0-9]{2}) Apple Inc\.? and the Swift [Pp]roject authors/ + +let currentYear = Calendar.current.component(.year, from: .now) + +for file in modifiedFiles { + guard var content = try? String(contentsOf: file, encoding: .utf8), + let licenseMatch = try? licenseRegex.firstMatch(in: content) + else { + // Didn't encounter a license comment in this file, do nothing + continue + } + + let upperYearSubstring = licenseMatch.2 + guard let upperYear = Int(upperYearSubstring) else { + print("Couldn't find license year in \(content[licenseMatch.range])") + continue + } + + guard upperYear < currentYear else { + // The license for this file is already up to date. No need to update it. + continue + } + + if licenseMatch.1 == nil { + // The existing license comment only contains a single year. Add the new year after + content.insert(contentsOf: "-\(currentYear)", at: upperYearSubstring.endIndex) + } else { + // The existing license comment contains both a start year and an end year. Update the second year. + content.replaceSubrange(upperYearSubstring.startIndex ..< upperYearSubstring.endIndex, with: "\(currentYear)") + } + try content.write(to: file, atomically: true, encoding: .utf8) +} + +// MARK: Modified files + +private func findModifiedFiles(in repoURL: URL, strategy: DiffStrategy) throws -> [URL] { + let diffCommand = Process() + diffCommand.currentDirectoryURL = repoURL + diffCommand.executableURL = URL(fileURLWithPath: "/usr/bin/git") + + let comparisonFlag: String = switch strategy { + case .stagedFiles: + "--cached" + case .comparingTo(let treeish): + treeish + } + + diffCommand.arguments = ["diff", "--name-only", comparisonFlag] + + let output = Pipe() + diffCommand.standardOutput = output + + try diffCommand.run() + + guard let outputData = try output.fileHandleForReading.readToEnd(), + let outputString = String(data: outputData, encoding: .utf8) + else { + return [] + } + + return outputString + .components(separatedBy: .newlines) + .compactMap { line in + guard !line.isEmpty else { return nil } + return repoURL.appendingPathComponent(line, isDirectory: false) + } +} From 1e69ac1c87131b08b84975fd4fed3f16298a88af Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Fri, 1 Aug 2025 16:39:15 -0600 Subject: [PATCH 15/90] prefer longer doc comments when selecting commented symbols (#1258) rdar://156595902 --- .../Symbol/UnifiedSymbol+Extensions.swift | 42 +++++- .../DocumentationContextTests.swift | 122 +++++++++++++++++- 2 files changed, 158 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftDocC/Semantics/Symbol/UnifiedSymbol+Extensions.swift b/Sources/SwiftDocC/Semantics/Symbol/UnifiedSymbol+Extensions.swift index 594a4972e0..daf3b97558 100644 --- a/Sources/SwiftDocC/Semantics/Symbol/UnifiedSymbol+Extensions.swift +++ b/Sources/SwiftDocC/Semantics/Symbol/UnifiedSymbol+Extensions.swift @@ -93,11 +93,31 @@ extension UnifiedSymbolGraph.Symbol { /// Returns the primary symbol selector to use as documentation source. var documentedSymbolSelector: UnifiedSymbolGraph.Selector? { - // We'll prioritize the first documented 'swift' symbol, if we have - // one. - return docComment.keys.first { selector in - return selector.interfaceLanguage == "swift" - } ?? docComment.keys.first + // Prioritize the longest doc comment with a "swift" selector, + // if there is one. + return docComment.min(by: { lhs, rhs in + if (lhs.key.interfaceLanguage == "swift") != (rhs.key.interfaceLanguage == "swift") { + // sort swift selectors before non-swift ones + return lhs.key.interfaceLanguage == "swift" + } + + // if the comments are equal, bail early without iterating them again + guard lhs.value != rhs.value else { + return false + } + + let lhsLength = lhs.value.lines.totalCount + let rhsLength = rhs.value.lines.totalCount + + if lhsLength == rhsLength { + // if the comments are the same length, just sort them lexicographically + return lhs.value.lines.isLexicographicallyBefore(rhs.value.lines) + } else { + // otherwise, sort by the length of the doc comment, + // so that `min` returns the longest comment + return lhsLength > rhsLength + } + })?.key } func identifier(forLanguage interfaceLanguage: String) -> SymbolGraph.Symbol.Identifier { @@ -115,3 +135,15 @@ extension UnifiedSymbolGraph.Symbol { } } } + +extension [SymbolGraph.LineList.Line] { + fileprivate var totalCount: Int { + return reduce(into: 0) { result, line in + result += line.text.count + } + } + + fileprivate func isLexicographicallyBefore(_ other: Self) -> Bool { + self.lexicographicallyPrecedes(other) { $0.text < $1.text } + } +} diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index 2271df2821..2d55fa1a10 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -1056,7 +1056,127 @@ class DocumentationContextTests: XCTestCase { └─ Text "Return value" """) } - + + func testLoadsConflictingDocComments() async throws { + let macOSSymbolGraph = makeSymbolGraph( + moduleName: "TestProject", + platform: .init(operatingSystem: .init(name: "macOS")), + symbols: [ + makeSymbol( + id: "TestSymbol", + kind: .func, + pathComponents: ["TestSymbol"], + docComment: "This is a comment.", + otherMixins: [ + SymbolGraph.Symbol.DeclarationFragments(declarationFragments: [ + .init( + kind: .text, + spelling: "TestSymbol Mac", + preciseIdentifier: nil) + ]) + ]) + ]) + let iOSSymbolGraph = makeSymbolGraph( + moduleName: "TestProject", + platform: .init(operatingSystem: .init(name: "iOS")), + symbols: [ + makeSymbol( + id: "TestSymbol", + kind: .func, + pathComponents: ["TestSymbol"], + docComment: "This is a longer comment that should be shown instead.", + otherMixins: [ + SymbolGraph.Symbol.DeclarationFragments(declarationFragments: [ + .init( + kind: .text, + spelling: "TestSymbol iOS", + preciseIdentifier: nil) + ]) + ]) + ]) + + for forwards in [true, false] { + let catalog = Folder(name: "unit-test.docc", content: [ + InfoPlist(displayName: "TestProject", identifier: "com.test.example"), + JSONFile(name: "symbols\(forwards ? "1" : "2").symbols.json", content:macOSSymbolGraph), + JSONFile(name: "symbols\(forwards ? "2" : "1").symbols.json", content: iOSSymbolGraph), + ]) + + let (bundle, context) = try await loadBundle(catalog: catalog) + + let reference = ResolvedTopicReference( + bundleID: bundle.id, + path: "/documentation/TestProject/TestSymbol", + sourceLanguage: .swift + ) + let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) + let abstract = try XCTUnwrap(symbol.abstractSection) + XCTAssertEqual( + abstract.paragraph.plainText, + "This is a longer comment that should be shown instead.") + } + } + + func testLoadsConflictingDocCommentsOfSameLength() async throws { + let macOSSymbolGraph = makeSymbolGraph( + moduleName: "TestProject", + platform: .init(operatingSystem: .init(name: "macOS")), + symbols: [ + makeSymbol( + id: "TestSymbol", + kind: .func, + pathComponents: ["TestSymbol"], + docComment: "Comment A.", + otherMixins: [ + SymbolGraph.Symbol.DeclarationFragments(declarationFragments: [ + .init( + kind: .text, + spelling: "TestSymbol Mac", + preciseIdentifier: nil) + ]) + ]) + ]) + let iOSSymbolGraph = makeSymbolGraph( + moduleName: "TestProject", + platform: .init(operatingSystem: .init(name: "iOS")), + symbols: [ + makeSymbol( + id: "TestSymbol", + kind: .func, + pathComponents: ["TestSymbol"], + docComment: "Comment B.", + otherMixins: [ + SymbolGraph.Symbol.DeclarationFragments(declarationFragments: [ + .init( + kind: .text, + spelling: "TestSymbol iOS", + preciseIdentifier: nil) + ]) + ]) + ]) + + for forwards in [true, false] { + let catalog = Folder(name: "unit-test.docc", content: [ + InfoPlist(displayName: "TestProject", identifier: "com.test.example"), + JSONFile(name: "symbols\(forwards ? "1" : "2").symbols.json", content:macOSSymbolGraph), + JSONFile(name: "symbols\(forwards ? "2" : "1").symbols.json", content: iOSSymbolGraph), + ]) + + let (bundle, context) = try await loadBundle(catalog: catalog) + + let reference = ResolvedTopicReference( + bundleID: bundle.id, + path: "/documentation/TestProject/TestSymbol", + sourceLanguage: .swift + ) + let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) + let abstract = try XCTUnwrap(symbol.abstractSection) + XCTAssertEqual( + abstract.paragraph.plainText, + "Comment A.") + } + } + func testMergesMultipleSymbolDeclarations() async throws { let graphContentiOS = try String(contentsOf: Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")! From 69f27f2d5076b9878a81c82728fa5a5c300fcb8c Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Wed, 6 Aug 2025 08:46:19 -0600 Subject: [PATCH 16/90] sort Default Implementations section contents when rendering (#1272) rdar://156311548 --- .../Sections/DefaultImplementations.swift | 2 +- ...SymbolGraphRelationshipsBuilderTests.swift | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftDocC/Model/Section/Sections/DefaultImplementations.swift b/Sources/SwiftDocC/Model/Section/Sections/DefaultImplementations.swift index 3898796a6c..bcc78a18fa 100644 --- a/Sources/SwiftDocC/Model/Section/Sections/DefaultImplementations.swift +++ b/Sources/SwiftDocC/Model/Section/Sections/DefaultImplementations.swift @@ -65,7 +65,7 @@ public struct DefaultImplementationsSection { return ImplementationsGroup( heading: "\(groupName)Implementations", - references: grouped[name]!.map { $0.reference } + references: grouped[name]!.map { $0.reference }.sorted(by: \.description) ) } } diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift index e90ca0f967..329a53f3b9 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift @@ -64,6 +64,50 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase { XCTAssertFalse((documentationCache["B"]!.semantic as! Symbol).defaultImplementations.implementations.isEmpty) } + func testMultipleImplementsRelationships() async throws { + let (bundle, context) = try await testBundleAndContext() + var documentationCache = DocumentationContext.ContentCache() + let engine = DiagnosticEngine() + + let identifierA = SymbolGraph.Symbol.Identifier(precise: "A", interfaceLanguage: SourceLanguage.swift.id) + let identifierB = SymbolGraph.Symbol.Identifier(precise: "B", interfaceLanguage: SourceLanguage.swift.id) + let identifierC = SymbolGraph.Symbol.Identifier(precise: "C", interfaceLanguage: SourceLanguage.swift.id) + + let symbolRefA = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SomeModuleName/A", sourceLanguage: .swift) + let symbolRefB = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SomeModuleName/B", sourceLanguage: .swift) + let symbolRefC = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SomeModuleName/C", sourceLanguage: .swift) + let moduleRef = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SomeModuleName", sourceLanguage: .swift) + + let symbolA = SymbolGraph.Symbol(identifier: identifierA, names: SymbolGraph.Symbol.Names(title: "A", navigator: nil, subHeading: nil, prose: nil), pathComponents: ["SomeModuleName", "A"], docComment: nil, accessLevel: .init(rawValue: "public"), kind: SymbolGraph.Symbol.Kind(parsedIdentifier: .func, displayName: "Function"), mixins: [:]) + let symbolB = SymbolGraph.Symbol(identifier: identifierB, names: SymbolGraph.Symbol.Names(title: "B", navigator: nil, subHeading: nil, prose: nil), pathComponents: ["SomeModuleName", "B"], docComment: nil, accessLevel: .init(rawValue: "public"), kind: SymbolGraph.Symbol.Kind(parsedIdentifier: .func, displayName: "Function"), mixins: [:]) + let symbolC = SymbolGraph.Symbol(identifier: identifierC, names: SymbolGraph.Symbol.Names(title: "C", navigator: nil, subHeading: nil, prose: nil), pathComponents: ["SomeModuleName", "C"], docComment: nil, accessLevel: .init(rawValue: "public"), kind: SymbolGraph.Symbol.Kind(parsedIdentifier: .func, displayName: "Function"), mixins: [:]) + + documentationCache.add( + DocumentationNode(reference: symbolRefA, symbol: symbolA, platformName: "macOS", moduleReference: moduleRef, article: nil, engine: engine), + reference: symbolRefA, + symbolID: "A" + ) + documentationCache.add( + DocumentationNode(reference: symbolRefB, symbol: symbolB, platformName: "macOS", moduleReference: moduleRef, article: nil, engine: engine), + reference: symbolRefB, + symbolID: "B" + ) + documentationCache.add( + DocumentationNode(reference: symbolRefC, symbol: symbolC, platformName: "macOS", moduleReference: moduleRef, article: nil, engine: engine), + reference: symbolRefC, + symbolID: "C" + ) + XCTAssert(engine.problems.isEmpty) + + let edge1 = SymbolGraph.Relationship(source: identifierB.precise, target: identifierA.precise, kind: .defaultImplementationOf, targetFallback: nil) + let edge2 = SymbolGraph.Relationship(source: identifierC.precise, target: identifierA.precise, kind: .defaultImplementationOf, targetFallback: nil) + + SymbolGraphRelationshipsBuilder.addImplementationRelationship(edge: edge1, selector: swiftSelector, in: bundle, context: context, localCache: documentationCache, engine: engine) + SymbolGraphRelationshipsBuilder.addImplementationRelationship(edge: edge2, selector: swiftSelector, in: bundle, context: context, localCache: documentationCache, engine: engine) + + XCTAssertEqual((documentationCache["A"]!.semantic as! Symbol).defaultImplementations.groups.first?.references.map(\.url?.lastPathComponent), ["B", "C"]) + } + func testConformsRelationship() async throws { let (bundle, _) = try await testBundleAndContext() var documentationCache = DocumentationContext.ContentCache() From 1a01b5405ff00ea6b3e707653692d9bfbdbc4f7d Mon Sep 17 00:00:00 2001 From: Marcus Ortiz Date: Thu, 7 Aug 2025 09:24:24 -0700 Subject: [PATCH 17/90] Add yml to table of supported langs for syntax highlighting. (#1270) Co-authored-by: Joseph Heck --- .../formatting-your-documentation-content.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/docc/DocCDocumentation.docc/formatting-your-documentation-content.md b/Sources/docc/DocCDocumentation.docc/formatting-your-documentation-content.md index 640a6ca3e6..07d44ee0c9 100644 --- a/Sources/docc/DocCDocumentation.docc/formatting-your-documentation-content.md +++ b/Sources/docc/DocCDocumentation.docc/formatting-your-documentation-content.md @@ -181,6 +181,7 @@ have one or more aliases. | shell | console, shellsession | | swift | | | xml | html, xhtml, rss, atom, xjb, xsd, xsl, plist, wsf, svg | +| yaml | yml | ### Add Bulleted, Numbered, and Term Lists From caeef6b77da5eff7c98ba9476107b45c44808c1c Mon Sep 17 00:00:00 2001 From: Prajwal Nadig Date: Fri, 8 Aug 2025 15:50:10 +0100 Subject: [PATCH 18/90] Generate concurrency-safe code in test bundle (#1274) With the new concurrency features in Swift 6, there are also a set of checks that ensure that the code is safe to run concurrently. Public static mutable variables are considered unsafe unless they are specifically of a type that conforms to `Sendable`. This patch changes the test bundle generation to use `let` instead of `var` in the case of `public static` properties. --- .../Sources/make-test-bundle/Node/PropertyNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/make-test-bundle/Sources/make-test-bundle/Node/PropertyNode.swift b/bin/make-test-bundle/Sources/make-test-bundle/Node/PropertyNode.swift index 13ae2c18ca..d3c23dd386 100644 --- a/bin/make-test-bundle/Sources/make-test-bundle/Node/PropertyNode.swift +++ b/bin/make-test-bundle/Sources/make-test-bundle/Node/PropertyNode.swift @@ -53,7 +53,7 @@ class PropertyNode: TypeMemberNode { if isDynamic { result += "\(levelString) \(kindString) var \(name.lowercased()): \(propertyType) { return \(propertyType)(\(propertyValue)) }\n" } else { - result += "\(levelString) \(kindString) var \(name.lowercased()): \(propertyType) = \(propertyValue)\n" + result += "\(levelString) \(kindString) \(kind == .static ? "let" : "var") \(name.lowercased()): \(propertyType) = \(propertyValue)\n" } if kind == .interface { From 7a3058bed9abfd3a9c2cc27c90acb726e5e0953e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 11 Aug 2025 11:03:10 +0200 Subject: [PATCH 19/90] Fix single "Objective-C" typo in developer facing documentation (#1259) rdar://154206938 --- .../Semantics/Metadata/AlternateRepresentation.swift | 4 ++-- Sources/docc/DocCDocumentation.docc/DocC.symbols.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDocC/Semantics/Metadata/AlternateRepresentation.swift b/Sources/SwiftDocC/Semantics/Metadata/AlternateRepresentation.swift index de96860517..520f5d7245 100644 --- a/Sources/SwiftDocC/Semantics/Metadata/AlternateRepresentation.swift +++ b/Sources/SwiftDocC/Semantics/Metadata/AlternateRepresentation.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 @@ -18,7 +18,7 @@ public import Markdown /// /// Whenever possible, prefer to define alternative language representations for a symbol by using in-source annotations /// such as the `@objc` and `@_objcImplementation` attributes in Swift, -/// or the `NS_SWIFT_NAME` macro in Objective C. +/// or the `NS_SWIFT_NAME` macro in Objective-C. /// /// If your source language doesn’t have a mechanism for specifying alternate representations or if your intended alternate representation isn't compatible with those attributes, /// you can use the `@AlternateRepresentation` directive to specify another symbol that should be considered an alternate representation of the documented symbol. diff --git a/Sources/docc/DocCDocumentation.docc/DocC.symbols.json b/Sources/docc/DocCDocumentation.docc/DocC.symbols.json index 1d87e25a69..77def53196 100644 --- a/Sources/docc/DocCDocumentation.docc/DocC.symbols.json +++ b/Sources/docc/DocCDocumentation.docc/DocC.symbols.json @@ -93,7 +93,7 @@ "text" : "such as the `@objc` and `@_objcImplementation` attributes in Swift," }, { - "text" : "or the `NS_SWIFT_NAME` macro in Objective C." + "text" : "or the `NS_SWIFT_NAME` macro in Objective-C." }, { "text" : "" From ea5c14a7df1fd33c5dcc53c94468ff0d66f24350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 11 Aug 2025 11:07:50 +0200 Subject: [PATCH 20/90] Remove miscellaneous unused test code (#1262) * Remove no-longer-relevant `allTests` definition * Remove unused imports in tests * Pass unused `file` and `line` parameters in test helper * Remove unused `file` and `line` parameters from test helper * Remove two unused test helpers * Remove three unused properties in tests --- .../DocumentationBundleTests.swift | 3 +-- .../InheritIntroducedAvailabilityTests.swift | 4 +-- .../NodeURLGeneratorTests.swift | 4 +-- .../ParametersAndReturnValidatorTests.swift | 4 +-- .../SwiftDocCTests/Model/TaskGroupTests.swift | 6 +---- .../Rendering/SymbolAvailabilityTests.swift | 26 ------------------- Tests/SwiftDocCTests/Utility/LMDBTests.swift | 6 +---- .../SemanticVersion+ComparableTests.swift | 3 +-- .../MergeSubcommandTests.swift | 3 +-- .../DirectoryMonitorTests.swift | 4 +-- .../FolderStructureTests.swift | 3 +-- .../HTMLTemplateDirectory.swift | 3 +-- .../PreviewHTTPHandlerTests.swift | 3 +-- .../DefaultRequestHandlerTests.swift | 3 +-- .../ErrorRequestHandlerTests.swift | 3 +-- .../FileRequestHandlerTests.swift | 5 +--- .../SwiftDocCUtilitiesTests/SignalTests.swift | 3 +-- .../StaticHostingBaseTest.swift | 4 +-- .../Utility/FileTests.swift | 4 +-- 19 files changed, 19 insertions(+), 75 deletions(-) diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationBundleTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationBundleTests.swift index 8d03e4f305..4671760ea2 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationBundleTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationBundleTests.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 @@ -9,7 +9,6 @@ */ import XCTest -@testable import SwiftDocC class DocumentationBundleTests: XCTestCase { // Test whether the bundle correctly loads a documentation source folder. diff --git a/Tests/SwiftDocCTests/Infrastructure/InheritIntroducedAvailabilityTests.swift b/Tests/SwiftDocCTests/Infrastructure/InheritIntroducedAvailabilityTests.swift index 77bf00bc07..1602d4dee3 100644 --- a/Tests/SwiftDocCTests/Infrastructure/InheritIntroducedAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/InheritIntroducedAvailabilityTests.swift @@ -34,16 +34,14 @@ class InheritIntroducedAvailabilityTests: XCTestCase { typealias Domain = SymbolGraph.Symbol.Availability.Domain typealias Version = SymbolGraph.SemanticVersion - var testBundle: DocumentationBundle! var context: DocumentationContext! override func setUp() async throws { try await super.setUp() - (testBundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") } override func tearDown() { - testBundle = nil context = nil super.tearDown() } diff --git a/Tests/SwiftDocCTests/Infrastructure/NodeURLGeneratorTests.swift b/Tests/SwiftDocCTests/Infrastructure/NodeURLGeneratorTests.swift index 422f2e2859..55da5f9f3b 100644 --- a/Tests/SwiftDocCTests/Infrastructure/NodeURLGeneratorTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/NodeURLGeneratorTests.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 @@ -14,8 +14,6 @@ import XCTest @testable import SwiftDocC class NodeURLGeneratorTests: XCTestCase { - let generator = NodeURLGenerator() - let unchangedURLs = [ URL(string: "doc://com.bundle/folder-prefix/type/symbol")!, URL(string: "doc://com.bundle/fol.der-pref.ix./type-swift.class/symbol.name.")!, diff --git a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift index 9a9316e543..912ac6fdb3 100644 --- a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift +++ b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift @@ -910,9 +910,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { docComment: String, docCommentModuleName: String? = "ModuleName", parameters: [(name: String, externalName: String?)], - returnValue: SymbolGraph.Symbol.DeclarationFragments.Fragment, - file: StaticString = #filePath, - line: UInt = #line + returnValue: SymbolGraph.Symbol.DeclarationFragments.Fragment ) async throws -> String { let fileSystem = try TestFileSystem(folders: [ Folder(name: "path", content: [ diff --git a/Tests/SwiftDocCTests/Model/TaskGroupTests.swift b/Tests/SwiftDocCTests/Model/TaskGroupTests.swift index 46207219fc..c8e66f8453 100644 --- a/Tests/SwiftDocCTests/Model/TaskGroupTests.swift +++ b/Tests/SwiftDocCTests/Model/TaskGroupTests.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 @@ -68,10 +68,6 @@ class SectionExtractionTests: XCTestCase { } } - private func testNode(with document: Document) -> DocumentationNode { - return DocumentationNode(reference: ResolvedTopicReference(bundleID: "org.swift.docc", path: "/blah", sourceLanguage: .swift), kind: .article, sourceLanguage: .swift, name: .conceptual(title: "Title"), markup: document, semantic: Semantic()) - } - func testSection() { // Empty -> nil do { diff --git a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift index bdabcf4425..239d352230 100644 --- a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift @@ -16,32 +16,6 @@ import SwiftDocCTestUtilities class SymbolAvailabilityTests: XCTestCase { - private func symbolAvailability( - defaultAvailability: [DefaultAvailability.ModuleAvailability] = [], - symbolGraphOperatingSystemPlatformName: String, - symbols: [SymbolGraph.Symbol], - symbolName: String - ) async throws -> [SymbolGraph.Symbol.Availability.AvailabilityItem] { - let catalog = Folder( - name: "unit-test.docc", - content: [ - InfoPlist(defaultAvailability: [ - "ModuleName": defaultAvailability - ]), - JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( - moduleName: "ModuleName", - platform: SymbolGraph.Platform(architecture: nil, vendor: nil, operatingSystem: SymbolGraph.OperatingSystem(name: symbolGraphOperatingSystemPlatformName), environment: nil), - symbols: symbols, - relationships: [] - )), - ] - ) - let (_, context) = try await loadBundle(catalog: catalog) - let reference = try XCTUnwrap(context.soleRootModuleReference).appendingPath(symbolName) - let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - return try XCTUnwrap(symbol.availability?.availability) - } - private func renderNodeAvailability( defaultAvailability: [DefaultAvailability.ModuleAvailability] = [], symbolGraphOperatingSystemPlatformName: String, diff --git a/Tests/SwiftDocCTests/Utility/LMDBTests.swift b/Tests/SwiftDocCTests/Utility/LMDBTests.swift index 5461909586..dd50b29604 100644 --- a/Tests/SwiftDocCTests/Utility/LMDBTests.swift +++ b/Tests/SwiftDocCTests/Utility/LMDBTests.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 @@ -258,10 +258,6 @@ final class SwiftLMDBTests: XCTestCase { XCTAssertEqual(value, [1,2,3,4,5,6,7,8,9,10,11,12,13,14]) #endif } - - static var allTests = [ - ("testVersion", testVersion), - ] } // MARK: - Custom Objects diff --git a/Tests/SwiftDocCTests/Utility/SemanticVersion+ComparableTests.swift b/Tests/SwiftDocCTests/Utility/SemanticVersion+ComparableTests.swift index 83875cc263..e68dfba3db 100644 --- a/Tests/SwiftDocCTests/Utility/SemanticVersion+ComparableTests.swift +++ b/Tests/SwiftDocCTests/Utility/SemanticVersion+ComparableTests.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 @@ -10,7 +10,6 @@ import XCTest import SymbolKit -@testable import SwiftDocC class SemanticVersion_ComparableTests: XCTestCase { private typealias Version = SymbolGraph.SemanticVersion diff --git a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.swift b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.swift index 3260ed4d2d..85731b8e9c 100644 --- a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.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 @@ -11,7 +11,6 @@ import XCTest import ArgumentParser @testable import SwiftDocCUtilities -@testable import SwiftDocC import SwiftDocCTestUtilities class MergeSubcommandTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift b/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift index 10bd4ac73b..555d258222 100644 --- a/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.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 @@ -91,7 +91,7 @@ class DirectoryMonitorTests: XCTestCase { /// - Warning: Please do not overuse this method as it takes 10s of wait time and can potentially slow down running the test suite. private func monitorNoUpdates(url: URL, testBlock: @escaping () throws -> Void, file: StaticString = #filePath, line: UInt = #line) throws { let monitor = try DirectoryMonitor(root: url) { rootURL, url in - XCTFail("Did produce file update event for a hidden file") + XCTFail("Did produce file update event for a hidden file", file: file, line: line) } try monitor.start() diff --git a/Tests/SwiftDocCUtilitiesTests/FolderStructureTests.swift b/Tests/SwiftDocCUtilitiesTests/FolderStructureTests.swift index 8a9c6ad396..148da86252 100644 --- a/Tests/SwiftDocCUtilitiesTests/FolderStructureTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/FolderStructureTests.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 @@ -9,7 +9,6 @@ */ import XCTest -@testable import SwiftDocC import SwiftDocCTestUtilities class FolderStructureTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.swift b/Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.swift index 09ee4a1797..e3f25527c0 100644 --- a/Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.swift +++ b/Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.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 @@ -9,7 +9,6 @@ */ import Foundation -@testable import SwiftDocC import SwiftDocCTestUtilities /// A folder that represents a fake html-build directory for testing. diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift index 0a142f0372..95878b0e7f 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.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 @@ -11,7 +11,6 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import SwiftDocC @testable import SwiftDocCUtilities import SwiftDocCTestUtilities diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift index e0321e1a83..634ba02b9e 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.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 @@ -11,7 +11,6 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import SwiftDocC @testable import SwiftDocCUtilities import SwiftDocCTestUtilities diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift index d73d8d8cc1..552b51e715 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.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 @@ -11,7 +11,6 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import SwiftDocC @testable import SwiftDocCUtilities import NIO diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift index 2f7b3202f1..9d4fefa8e2 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.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 @@ -11,7 +11,6 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import SwiftDocC @testable import SwiftDocCUtilities import SwiftDocCTestUtilities @@ -19,8 +18,6 @@ import NIO import NIOHTTP1 class FileRequestHandlerTests: XCTestCase { - let fileIO = NonBlockingFileIO(threadPool: NIOThreadPool(numberOfThreads: 2)) - private func verifyAsset(root: URL, path: String, body: String, type: String, file: StaticString = #filePath, line: UInt = #line) throws { let request = makeRequestHead(uri: path) let factory = FileRequestHandler(rootURL: root) diff --git a/Tests/SwiftDocCUtilitiesTests/SignalTests.swift b/Tests/SwiftDocCUtilitiesTests/SignalTests.swift index 564d0bc938..2bcd26c593 100644 --- a/Tests/SwiftDocCUtilitiesTests/SignalTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/SignalTests.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 @@ -9,7 +9,6 @@ */ import XCTest -@testable import SwiftDocCUtilities class SignalTests: XCTestCase { #if os(macOS) || os(Linux) || os(Android) diff --git a/Tests/SwiftDocCUtilitiesTests/StaticHostingBaseTest.swift b/Tests/SwiftDocCUtilitiesTests/StaticHostingBaseTest.swift index 3e50fae03f..430ccd8b9f 100644 --- a/Tests/SwiftDocCUtilitiesTests/StaticHostingBaseTest.swift +++ b/Tests/SwiftDocCUtilitiesTests/StaticHostingBaseTest.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 @@ -10,8 +10,6 @@ import XCTest import Foundation -@testable import SwiftDocC -@testable import SwiftDocCUtilities class StaticHostingBaseTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/FileTests.swift b/Tests/SwiftDocCUtilitiesTests/Utility/FileTests.swift index c8f3f2ead0..cfabe527a4 100644 --- a/Tests/SwiftDocCUtilitiesTests/Utility/FileTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/Utility/FileTests.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 @@ -9,8 +9,6 @@ */ import XCTest -@testable import SwiftDocC -@testable import SwiftDocCUtilities import SwiftDocCTestUtilities class FileTests: XCTestCase { From 9b5724923abf56607d6b99e8122809062245e9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 11 Aug 2025 11:12:04 +0200 Subject: [PATCH 21/90] Remove unused private code from the symbol graph loader (#1263) --- .../Symbol Graph/SymbolGraphLoader.swift | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift index 50baa2529c..1975f58ea1 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift @@ -188,33 +188,7 @@ struct SymbolGraphLoader { } // Alias to declutter code - typealias AvailabilityItem = SymbolGraph.Symbol.Availability.AvailabilityItem - - /// Cache default availability items as we create them on demand. - private var cachedAvailabilityItems = [DefaultAvailability.ModuleAvailability: AvailabilityItem]() - - /// Returns a symbol graph availability item, given a module availability. - /// - returns: An availability item, or `nil` if the input data is invalid. - private func availabilityItem(for defaultAvailability: DefaultAvailability.ModuleAvailability) -> AvailabilityItem? { - if let cached = cachedAvailabilityItems[defaultAvailability] { - return cached - } - return AvailabilityItem(defaultAvailability) - } - - private func loadSymbolGraph(at url: URL) throws -> (SymbolGraph, isMainSymbolGraph: Bool) { - // This is a private method, the `url` key is known to exist - var symbolGraph = symbolGraphs[url]! - let (moduleName, isMainSymbolGraph) = Self.moduleNameFor(symbolGraph, at: url) - - if !isMainSymbolGraph && symbolGraph.module.bystanders == nil { - // If this is an extending another module, change the module name to match the extended module. - // This makes the symbols in this graph have a path that starts with the extended module's name. - symbolGraph.module.name = moduleName - } - - return (symbolGraph, isMainSymbolGraph) - } + private typealias AvailabilityItem = SymbolGraph.Symbol.Availability.AvailabilityItem /// Adds the missing fallback and default availability information to the unified symbol graph /// in case it didn't exists in the loaded symbol graphs. From 9f464bc38f913f2163cf74da0c0f70ec3c31ef29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 11 Aug 2025 11:19:29 +0200 Subject: [PATCH 22/90] Remove unused internal properties (#1264) --- .../Link Resolution/LinkResolver+NavigatorIndex.swift | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift index c23a850f71..a0875fbde1 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift @@ -61,11 +61,6 @@ package struct ExternalRenderNode { externalEntity.topicRenderReference.navigatorTitleVariants } - /// The variants of the abbreviated declaration of the symbol to display in links. - var fragmentsVariants: VariantCollection<[DeclarationRenderSection.Token]?> { - externalEntity.topicRenderReference.fragmentsVariants - } - /// Author provided images that represent this page. var images: [TopicImage] { externalEntity.topicRenderReference.images @@ -87,7 +82,6 @@ package struct ExternalRenderNode { /// A language specific representation of an external render node value for building a navigator index. struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { var identifier: ResolvedTopicReference - var externalIdentifier: RenderReferenceIdentifier var kind: RenderNode.Kind var metadata: ExternalRenderNodeMetadataRepresentation @@ -109,7 +103,6 @@ struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { self.identifier = renderNode.identifier.withSourceLanguages(Set(arrayLiteral: traitLanguage)) self.kind = renderNode.kind - self.externalIdentifier = renderNode.externalIdentifier self.metadata = ExternalRenderNodeMetadataRepresentation( title: renderNode.titleVariants.value(for: traits), @@ -145,4 +138,4 @@ struct ExternalRenderNodeMetadataRepresentation: NavigatorIndexableRenderMetadat var fragments: [DeclarationRenderSection.Token]? = nil var roleHeading: String? = nil var platforms: [AvailabilityRenderItem]? = nil -} \ No newline at end of file +} From 49c6c227b05c9ce918e99c38c92b613d46b1cda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 11 Aug 2025 11:25:53 +0200 Subject: [PATCH 23/90] Remove unused property and parameter from generated doc topic code (#1265) --- .../GeneratedDocumentationTopics.swift | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift index 003f6b64aa..4ceb41fa38 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.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 @@ -27,8 +27,6 @@ enum GeneratedDocumentationTopics { struct APICollection { /// The title of the collection. var title: String - /// A reference to the parent of the collection. - let parentReference: ResolvedTopicReference /// A list of topic references for the collection. var identifiers = [ResolvedTopicReference]() } @@ -39,8 +37,7 @@ enum GeneratedDocumentationTopics { /// - childReference: The inherited symbol reference. /// - reference: The parent type reference. /// - originDisplayName: The origin display name as provided by the symbol graph. - /// - extendedModuleName: Extended module name. - mutating func add(_ childReference: ResolvedTopicReference, to reference: ResolvedTopicReference, childSymbol: SymbolGraph.Symbol, originDisplayName: String, originSymbol: SymbolGraph.Symbol?, extendedModuleName: String) throws { + mutating func add(_ childReference: ResolvedTopicReference, to reference: ResolvedTopicReference, childSymbol: SymbolGraph.Symbol, originDisplayName: String, originSymbol: SymbolGraph.Symbol?) throws { let fromType: String let typeSimpleName: String if let originSymbol, originSymbol.pathComponents.count > 1 { @@ -89,7 +86,7 @@ enum GeneratedDocumentationTopics { // Create a new default implementations provider, if needed. if !implementingTypes[reference]!.inheritedFromTypeName.keys.contains(fromType) { - implementingTypes[reference]!.inheritedFromTypeName[fromType] = Collections.APICollection(title: "\(typeSimpleName) Implementations", parentReference: reference) + implementingTypes[reference]!.inheritedFromTypeName[fromType] = Collections.APICollection(title: "\(typeSimpleName) Implementations") } // Add the default implementation. @@ -247,13 +244,13 @@ enum GeneratedDocumentationTopics { let child = context.documentationCache[relationship.source], // Get the child symbol let childSymbol = child.symbol, - // Get the swift extension data - let extends = childSymbol[mixin: SymbolGraph.Symbol.Swift.Extension.self] + // Check that there is Swift extension information + childSymbol[mixin: SymbolGraph.Symbol.Swift.Extension.self] != nil { let originSymbol = context.documentationCache[origin.identifier]?.symbol // Add the inherited symbol to the index. - try inheritanceIndex.add(child.reference, to: parent.reference, childSymbol: childSymbol, originDisplayName: origin.displayName, originSymbol: originSymbol, extendedModuleName: extends.extendedModule) + try inheritanceIndex.add(child.reference, to: parent.reference, childSymbol: childSymbol, originDisplayName: origin.displayName, originSymbol: originSymbol) } } From 4a871f8f2f261b8017946b3731c4f8970978ec4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 11 Aug 2025 11:32:23 +0200 Subject: [PATCH 24/90] Formalize LinkCompletionTools as public API (#1267) --- .../Convert/Symbol Link Resolution/LinkCompletionTools.swift | 1 - .../Symbol Link Resolution/LinkCompletionToolsTests.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift b/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift index 55f5a64545..ac1a36f20c 100644 --- a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift +++ b/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift @@ -21,7 +21,6 @@ public import SymbolKit /// - Third, determine the minimal unique disambiguation for each completion suggestion using ``suggestedDisambiguation(forCollidingSymbols:)`` /// /// > Tip: You can use ``SymbolInformation/hash(uniqueSymbolID:)`` to compute the hashed symbol identifiers needed for steps 2 and 3 above. -@_spi(LinkCompletion) // LinkCompletionTools isn't stable API yet public enum LinkCompletionTools { // MARK: Parsing diff --git a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/LinkCompletionToolsTests.swift b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/LinkCompletionToolsTests.swift index 76d963527b..d267afc81f 100644 --- a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/LinkCompletionToolsTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/LinkCompletionToolsTests.swift @@ -10,7 +10,7 @@ import Foundation import XCTest -@_spi(LinkCompletion) @testable import SwiftDocC +@testable import SwiftDocC class LinkCompletionToolsTests: XCTestCase { func testParsingLinkStrings() { From a4b9902aaaa1b9d09a714190b793c84526b523f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 15 Aug 2025 13:56:46 +0200 Subject: [PATCH 25/90] Fix warnings about retroactive conformance to protocols in 'generate-symbol-graph' tool (#1275) --- Sources/generate-symbol-graph/main.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/generate-symbol-graph/main.swift b/Sources/generate-symbol-graph/main.swift index 1f8b038702..b6717febd5 100644 --- a/Sources/generate-symbol-graph/main.swift +++ b/Sources/generate-symbol-graph/main.swift @@ -61,7 +61,7 @@ func directiveUSR(_ directiveName: String) -> String { // The `@retroactive` attribute is new in the Swift 6 compiler. The backwards compatible syntax for a retroactive conformance is fully-qualified types. // // This conformance it only relevant to the `generate-symbol-graph` script. -extension SymbolKit.SymbolGraph.Symbol.DeclarationFragments.Fragment: Swift.ExpressibleByStringInterpolation { +extension SymbolKit.SymbolGraph.Symbol.DeclarationFragments.Fragment: Swift.ExpressibleByStringInterpolation, Swift.ExpressibleByUnicodeScalarLiteral, Swift.ExpressibleByExtendedGraphemeClusterLiteral, Swift.ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(kind: .text, spelling: value, preciseIdentifier: nil) } @@ -79,7 +79,7 @@ extension SymbolKit.SymbolGraph.Symbol.DeclarationFragments.Fragment: Swift.Expr // The `@retroactive` attribute is new in the Swift 6 compiler. The backwards compatible syntax for a retroactive conformance is fully-qualified types. // // This conformance it only relevant to the `generate-symbol-graph` script. -extension SymbolKit.SymbolGraph.LineList.Line: Swift.ExpressibleByStringInterpolation { +extension SymbolKit.SymbolGraph.LineList.Line: Swift.ExpressibleByStringInterpolation, Swift.ExpressibleByUnicodeScalarLiteral, Swift.ExpressibleByExtendedGraphemeClusterLiteral, Swift.ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(text: value, range: nil) } From 9cd9bc30c15941005ec73178d1005e169fd3ab03 Mon Sep 17 00:00:00 2001 From: Agisilaos Tsarampoulidis Date: Tue, 19 Aug 2025 11:03:25 +0200 Subject: [PATCH 26/90] Add solutions to remove invalid metadata directives in documentation comments (#1277) * Add solutions to remove invalid metadata directives in documentation comments - Implement solutions for org.swift.docc.Metadata.InvalidInDocumentationComment diagnostics - Each invalid directive now has a solution that removes the directive - Follows the same pattern as other directive removal solutions in the codebase - Added comprehensive tests to verify solutions are provided correctly - Fixes issue #1111 * Fix code duplication in validateForUseInDocumentationComment method - Create diagnostic once instead of duplicating it - Use conditional solutions array based on range availability - Single Problem creation path for better maintainability * Add range verification test for invalid metadata directive solutions - Verify replacement range matches problem's diagnostic range - Ensures solution targets the correct content - Improves test coverage for range accuracy * Move test comments into assertion messages for better clarity - Move explanatory comments into assertion message parameters - Provides better context when tests fail - Makes tests more self-documenting * Fix code duplication in validateForUseInDocumentationComment method - Create diagnostic once instead of duplicating it - Use conditional solutions array based on range availability - Single Problem creation path for better maintainability --- .../Semantics/Metadata/Metadata.swift | 29 ++++++++----- .../Semantics/SymbolTests.swift | 43 +++++++++++++++++++ 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift b/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift index 4f767e38a0..907c190506 100644 --- a/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift +++ b/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift @@ -224,18 +224,25 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible { problems.append( contentsOf: namesAndRanges.map { (name, range) in - Problem( - diagnostic: Diagnostic( - source: symbolSource, - severity: .warning, - range: range, - identifier: "org.swift.docc.\(Metadata.directiveName).Invalid\(name)InDocumentationComment", - summary: "Invalid use of \(name.singleQuoted) directive in documentation comment; configuration will be ignored", - explanation: "Specify this configuration in a documentation extension file" - - // TODO: It would be nice to offer a solution here that removes the directive for you (#1111, rdar://140846407) - ) + let diagnostic = Diagnostic( + source: symbolSource, + severity: .warning, + range: range, + identifier: "org.swift.docc.\(Metadata.directiveName).Invalid\(name)InDocumentationComment", + summary: "Invalid use of \(name.singleQuoted) directive in documentation comment; configuration will be ignored", + explanation: "Specify this configuration in a documentation extension file" ) + + let solutions: [Solution] = range.map { range in + [Solution( + summary: "Remove invalid \(name.singleQuoted) directive", + replacements: [ + Replacement(range: range, replacement: "") + ] + )] + } ?? [] + + return Problem(diagnostic: diagnostic, possibleSolutions: solutions) } ) diff --git a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift index 69dfd86fb3..099c73df45 100644 --- a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift @@ -1311,6 +1311,19 @@ class SymbolTests: XCTestCase { "org.swift.docc.Metadata.InvalidRedirectedInDocumentationComment", ] ) + + // Verify that each problem has exactly one solution to remove the directive + for problem in problems { + XCTAssertEqual(problem.possibleSolutions.count, 1, "Each invalid metadata directive should have exactly one solution") + + let solution = try XCTUnwrap(problem.possibleSolutions.first) + XCTAssertTrue(solution.summary.hasPrefix("Remove invalid"), "Solution summary should start with 'Remove invalid'") + XCTAssertEqual(solution.replacements.count, 1, "Solution should have exactly one replacement") + + let replacement = try XCTUnwrap(solution.replacements.first) + XCTAssertEqual(replacement.replacement, "", "Replacement should be empty string to remove the directive") + XCTAssertNotNil(replacement.range, "Replacement should have a valid range") + } } func testParsesDeprecationSummaryDirectiveFromDocComment() async throws { @@ -1352,6 +1365,36 @@ class SymbolTests: XCTestCase { XCTAssert(problems.isEmpty) } + func testSolutionForInvalidMetadataDirectiveRemovesDirective() async throws { + let (_, problems) = try await makeDocumentationNodeForSymbol( + docComment: """ + The symbol's abstract. + + @Metadata { + @DisplayName("Invalid Display Name") + } + """, + articleContent: nil + ) + + XCTAssertEqual(problems.count, 1) + let problem = try XCTUnwrap(problems.first) + + XCTAssertEqual(problem.diagnostic.identifier, "org.swift.docc.Metadata.InvalidDisplayNameInDocumentationComment") + XCTAssertEqual(problem.possibleSolutions.count, 1) + + let solution = try XCTUnwrap(problem.possibleSolutions.first) + XCTAssertEqual(solution.summary, "Remove invalid 'DisplayName' directive") + XCTAssertEqual(solution.replacements.count, 1) + + let replacement = try XCTUnwrap(solution.replacements.first) + XCTAssertEqual(replacement.replacement, "", "Replacement should be empty string to remove the directive") + XCTAssertNotNil(replacement.range, "Replacement should have a valid range") + + // Verify that the replacement range covers the expected content + XCTAssertEqual(replacement.range, problem.diagnostic.range, "Replacement range should match the problem's diagnostic range to ensure it removes the entire @DisplayName directive") + } + // MARK: - Leading Whitespace in Doc Comments func testWithoutLeadingWhitespace() { From 2cff0b04bd64d9be5803b0abacdbd4078157cadc Mon Sep 17 00:00:00 2001 From: Joseph Heck Date: Thu, 21 Aug 2025 08:21:53 -0700 Subject: [PATCH 27/90] [docs] Add basic documentation on using Snippets to DocC documentation (#1166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial work on snippets documentation Co-authored-by: Joe Heck Co-authored-by: Jacob Hearst * consolidate copyright statements at bottom of markdown files and add a newline to the end of the file * enables Snippet directive code for documentation, and updates the generated symbolgraph with that change * updating with an overview * fixing license header for check script * Update Sources/docc/DocCDocumentation.docc/DocC Documentation.md Co-authored-by: Pat Shaughnessy * Update Sources/docc/DocCDocumentation.docc/snippets.md Co-authored-by: Pat Shaughnessy * Update Sources/docc/DocCDocumentation.docc/Reference Syntax/API Reference Syntax/Snippet.md Co-authored-by: Pat Shaughnessy * renaming article per feedback * update documentation to explicitly call out adding swift-docc-plugin dependency * Add an ASCII diagram of an example Swift package directory structure, to make it clear where the snippets go, and also where the documentation catalog goes. Also show a concrete example of how to reference a snippet. Fix a typo. * Include a full markdown example containing @Snippet * applying updates based on David's feedback * co-locates snippet API ref content to source class rather than using an extension page * revising note about snippets vs. docc catalogs and target linkage * Update Sources/docc/DocCDocumentation.docc/adding-code-snippets-to-your-content.md Co-authored-by: David Rönnqvist * removing section about markdown comments inside snippets * collapsing some of the demo material down per David's feedback --------- Co-authored-by: Jacob Hearst Co-authored-by: Pat Shaughnessy Co-authored-by: David Rönnqvist --- .../Semantics/Snippets/Snippet.swift | 21 +- .../DocC Documentation.md | 1 + .../DocCDocumentation.docc/DocC.symbols.json | 207 ++++++++++++++++++ .../adding-code-snippets-to-your-content.md | 203 +++++++++++++++++ 4 files changed, 429 insertions(+), 3 deletions(-) create mode 100644 Sources/docc/DocCDocumentation.docc/adding-code-snippets-to-your-content.md diff --git a/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift b/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift index a2188a3c82..417f4c5dc3 100644 --- a/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift +++ b/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift @@ -12,8 +12,25 @@ import Foundation public import Markdown import SymbolKit +/// Embeds a code example from the project's code snippets. +/// +/// ```markdown +/// @Snippet(path: "my-package/Snippets/example-snippet", slice: "setup") +/// ``` +/// +/// Place the `Snippet` directive to embed a code example from the project's snippet directory. +/// The path that references the snippet is identified with three parts: +/// +/// 1. The package name as defined in `Package.swift` +/// +/// 2. The directory path to the snippet file, starting with "Snippets". +/// +/// 3. The name of your snippet file without the `.swift` extension +/// +/// If the snippet had slices annotated within it, an individual slice of the snippet can be referenced with the `slice` option. +/// Without the option defined, the directive embeds the entire snippet. public final class Snippet: Semantic, AutomaticDirectiveConvertible { - public static let introducedVersion = "5.6" + public static let introducedVersion = "5.7" public let originalMarkup: BlockDirective /// The path components of a symbol link that would be used to resolve a reference to a snippet, @@ -30,8 +47,6 @@ public final class Snippet: Semantic, AutomaticDirectiveConvertible { "slice" : \Snippet._slice, ] - static var hiddenFromDocumentation = true - @available(*, deprecated, message: "Do not call directly. Required for 'AutomaticDirectiveConvertible'.") init(originalMarkup: BlockDirective) { self.originalMarkup = originalMarkup diff --git a/Sources/docc/DocCDocumentation.docc/DocC Documentation.md b/Sources/docc/DocCDocumentation.docc/DocC Documentation.md index acff62f201..4cda0406f4 100644 --- a/Sources/docc/DocCDocumentation.docc/DocC Documentation.md +++ b/Sources/docc/DocCDocumentation.docc/DocC Documentation.md @@ -25,6 +25,7 @@ DocC syntax — called _documentation markup_ — is a custom variant of Markdow - - - +- - ### Structure and Formatting diff --git a/Sources/docc/DocCDocumentation.docc/DocC.symbols.json b/Sources/docc/DocCDocumentation.docc/DocC.symbols.json index 77def53196..71145763e5 100644 --- a/Sources/docc/DocCDocumentation.docc/DocC.symbols.json +++ b/Sources/docc/DocCDocumentation.docc/DocC.symbols.json @@ -5246,6 +5246,213 @@ "Small" ] }, + { + "accessLevel" : "public", + "availability" : [ + { + "domain" : "Swift-DocC", + "introduced" : { + "major" : 5, + "minor" : 7, + "patch" : 0 + } + } + ], + "declarationFragments" : [ + { + "kind" : "typeIdentifier", + "spelling" : "@" + }, + { + "kind" : "typeIdentifier", + "spelling" : "Snippet" + }, + { + "kind" : "text", + "spelling" : "(" + }, + { + "kind" : "identifier", + "spelling" : "path" + }, + { + "kind" : "text", + "spelling" : ": " + }, + { + "kind" : "typeIdentifier", + "spelling" : "String" + }, + { + "kind" : "text", + "spelling" : ", " + }, + { + "kind" : "identifier", + "spelling" : "slice" + }, + { + "kind" : "text", + "spelling" : ": " + }, + { + "kind" : "typeIdentifier", + "spelling" : "String" + }, + { + "kind" : "text", + "spelling" : "?" + }, + { + "kind" : "text", + "spelling" : ")" + } + ], + "docComment" : { + "lines" : [ + { + "text" : "Embeds a code example from the project's code snippets." + }, + { + "text" : "" + }, + { + "text" : "```markdown" + }, + { + "text" : "@Snippet(path: \"my-package\/Snippets\/example-snippet\", slice: \"setup\")" + }, + { + "text" : "```" + }, + { + "text" : "" + }, + { + "text" : "Place the `Snippet` directive to embed a code example from the project's snippet directory." + }, + { + "text" : "The path that references the snippet is identified with three parts:" + }, + { + "text" : "" + }, + { + "text" : "1. The package name as defined in `Package.swift`" + }, + { + "text" : "" + }, + { + "text" : "2. The directory path to the snippet file, starting with \"Snippets\"." + }, + { + "text" : "" + }, + { + "text" : "3. The name of your snippet file without the `.swift` extension" + }, + { + "text" : "" + }, + { + "text" : "If the snippet had slices annotated within it, an individual slice of the snippet can be referenced with the `slice` option." + }, + { + "text" : "Without the option defined, the directive embeds the entire snippet." + }, + { + "text" : "- Parameters:" + }, + { + "text" : " - path: The path components of a symbol link that would be used to resolve a reference to a snippet," + }, + { + "text" : " only occurring as a block directive argument." + }, + { + "text" : " **(required)**" + }, + { + "text" : " - slice: An optional named range to limit the lines shown." + }, + { + "text" : " **(optional)**" + } + ] + }, + "identifier" : { + "interfaceLanguage" : "swift", + "precise" : "__docc_universal_symbol_reference_$Snippet" + }, + "kind" : { + "displayName" : "Directive", + "identifier" : "class" + }, + "names" : { + "navigator" : [ + { + "kind" : "attribute", + "spelling" : "@" + }, + { + "kind" : "identifier", + "preciseIdentifier" : "__docc_universal_symbol_reference_$Snippet", + "spelling" : "Snippet" + } + ], + "subHeading" : [ + { + "kind" : "identifier", + "spelling" : "@" + }, + { + "kind" : "identifier", + "spelling" : "Snippet" + }, + { + "kind" : "text", + "spelling" : "(" + }, + { + "kind" : "identifier", + "spelling" : "path" + }, + { + "kind" : "text", + "spelling" : ": " + }, + { + "kind" : "typeIdentifier", + "spelling" : "String" + }, + { + "kind" : "text", + "spelling" : ", " + }, + { + "kind" : "identifier", + "spelling" : "slice" + }, + { + "kind" : "text", + "spelling" : ": " + }, + { + "kind" : "typeIdentifier", + "spelling" : "String" + }, + { + "kind" : "text", + "spelling" : ")" + } + ], + "title" : "Snippet" + }, + "pathComponents" : [ + "Snippet" + ] + }, { "accessLevel" : "public", "availability" : [ diff --git a/Sources/docc/DocCDocumentation.docc/adding-code-snippets-to-your-content.md b/Sources/docc/DocCDocumentation.docc/adding-code-snippets-to-your-content.md new file mode 100644 index 0000000000..c654ef01d9 --- /dev/null +++ b/Sources/docc/DocCDocumentation.docc/adding-code-snippets-to-your-content.md @@ -0,0 +1,203 @@ +# Adding Code Snippets to your Content + +@Metadata { + @Available("Swift", introduced: "5.7") + @TitleHeading("Article") + } + +Create and include code snippets to illustrate and provide examples of how to use your API. + +## Overview + + +DocC supports code listings in your code, as described in . +In addition to code listings written directly in the markup, Swift Package Manager and DocC supports compiler verified code examples called "snippets". + +Swift Package Manager looks for, and builds, any code included in the `Snippets` directory for your package. +DocC supports referencing all, or parts, of those files to present as code listings. +In addition to snippets presenting your code examples, you can run snippets directly on the command line. +This allows you to verify that code examples, referenced in your documentation, continue to compile as you evolve you app or library. + +### Add the Swift DocC plugin + +To generate or preview documentation with snippets, add [swift-docc-plugin](https://github.com/apple/swift-docc-plugin) as a dependency to your package. + +For example, use the command: + +```bash +swift package add-dependency https://github.com/apple/swift-docc-plugin --from 1.1.0 +``` + +Or edit your `Package.swift` to add the dependency: + +``` +let package = Package( + // name, platforms, products, etc. + dependencies: [ + // other dependencies + .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), + ], + targets: [ + // targets + ] +) +``` + +### Create a code snippet + +Swift Package Manager expects to find your code examples in the directory `Snippets` at the top of your project, parallel to the file `Package.swift` and the directory `Sources`. +At the root of your project, create the directory `Snippets`. +Within the `Snippets` directory, create a file with your code snippet. + +Your Swift package directory structure should resemble this: + +``` +YourProject + ├── Package.swift + ├── Snippets + │   └── example-snippet.swift + ├── Sources + │   └── YourProject + │   └── YourProject.swift +etc... +``` + +> Note: Snippets are a package-wide resource located in a "Snippets" directory next to the package's "Sources" and "Tests" directories. + +The following example illustrates a code example in the file `Snippets/example-snippet.swift`: + +```swift +import Foundation + +print("Hello") +``` + +Your snippets can import targets defined in your local package, as well as products from its direct dependencies. +Each snippet is its own unit and can't access code from other snippet files. + +Every time you build your project, the Swift Package Manager compiles any code snippets, and then fails if the build if they are unable to compile. + +### Run the snippet + +You and consumers of your library can run your snippets from the command line using `swift run snippet-name` where "snippet-name" corresponds to a file name in your Snippets directory without the ".swift" file extension. + +Run the earlier code example file named `example-snippet.swift` using the following command: + +```bash +swift run example-snippet +``` + +### Embed the snippet + +To embed your snippet in an article or within the symbol reference pages, use the `@Snippet` directive. +```markdown +@Snippet(path: "my-package/Snippets/example-snippet") +``` + +The `path` argument has three parts: + +1. The package name as defined in `Package.swift` + +2. The directory path to the snippet file, starting with "Snippets". + +3. The name of your snippet file without the `.swift` extension + +A snippet reference displays as a block between other paragraphs. +In the example package above, the `YourProject.md` file might contain this markdown: + +```markdown +# ``YourProject`` + +Add a single sentence or sentence fragment, which DocC uses as the page’s abstract or summary. + +## Overview + +Add one or more paragraphs that introduce your content overview. + +@Snippet(path: "YourProject/Snippets/example-snippet") +``` + +If your snippet code requires setup — like imports or variable definitions — that distract from the snippet's main focus, you can add `// snippet.hide` and `// snippet.show` lines in the snippet code to exclude the lines in between from displaying in your documentation. +These comments act as a toggle to hide or show content from the snippet. + +```swift +print("Hello") + +// snippet.hide + +print("Hidden") + +// snippet.show + +print("Shown") +``` + +Hide segments of your snippet for content such as license footers, test code, or unique setup code. +Generally, it is useful for things that you wouldn't want the reader to use as a starting point. + +### Preview your content + +Use the [swift-docc-plugin](https://github.com/swiftlang/swift-docc-plugin) to preview content that includes snippets. +To run the preview, use the following command from a terminal. +Replace `YourTarget` with a target from your package to preview: + +```bash +swift package --disable-sandbox preview-documentation --target YourTarget +``` + +### Slice up your snippet to break it up in your content. + +Long snippets dropped into documentation can result in a wall of text that is harder to parse and understand. +Instead, annotate non-overlapping slices in the snippet, which allows you to reference and embed the slice portion of the example code. + +Annotating slices in a snippet looks similiar to annotating `snippet.show` and `snippet.hide`. +You define the slice's identity in the comment, and that slice continues until the next instance of `// snippet.end` appears on a new line. +When selecting your identifiers, use URL-compatible path characters. + +For example, to start a slice with an ID of `setup`, add the following comment on a new line. + +```swift +// snippet.setup +``` + +Then end the `setup` slice with: + +```swift +// snippet.end +``` + +Adding a new slice identifier automatically terminates an earlier slice. +For example, the follow code examples are effectively the same: + +```swift +// snippet.setup +var item = MyObject.init() +// snippet.end + +// snipppet.configure +item.size = 3 +// snippet.end +``` + +```swift +// snippet.setup +var item = MyObject.init() + +// snipppet.configure +item.size = 3 +``` + +Use the `@Snippet` directive with the `slice` parameter to embed that slice as sample code on your documentation. +Extending the earlier snippet example, the slice `setup` would be referenced with + +```markdown +@Snippet(path: "my-package/Snippets/example-snippet", slice: "setup") +``` + +## Topics + +### Directives + +- ``Snippet`` + + From 5ba14367ffdba5d4f2fca281a225125ab3d4d2b2 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 3 Sep 2025 14:29:26 -0700 Subject: [PATCH 28/90] [Tests] NFC: Disable a test that is causing CI failures (#1288) --- Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift b/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift index e580a089af..d07e2e629f 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift @@ -117,6 +117,8 @@ class RenderMetadataTests: XCTestCase { /// Test that when a bystanders symbol graph is loaded that extends a different module, that /// those symbols correctly report the modules when rendered. func testRendersBystanderExtensionsFromSymbolGraph() async throws { + throw XCTSkip("Fails in CI. rdar://159615046") + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in let baseSymbolGraphURL = Bundle.module.url( forResource: "BaseKit.symbols", withExtension: "json", subdirectory: "Test Resources")! From 64418aac4d08acf4262982a859b07804ffc9644c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 15 Sep 2025 10:09:45 +0200 Subject: [PATCH 29/90] Avoid using legacy test data in most (but not all) SymbolTests tests (#1278) --- .../Semantics/SymbolTests.swift | 332 +++++++++--------- 1 file changed, 169 insertions(+), 163 deletions(-) diff --git a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift index 099c73df45..c0157c6a26 100644 --- a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift @@ -25,10 +25,10 @@ class SymbolTests: XCTestCase { - name: A parameter - Returns: Return value """, - articleContent: nil + extensionFileContent: nil ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") XCTAssertEqual(withoutArticle.abstract?.format(), "A cool API to call.") XCTAssertEqual((withoutArticle.discussion?.content ?? []).map { $0.format() }.joined(), "") @@ -53,20 +53,18 @@ class SymbolTests: XCTestCase { - name: A parameter - Returns: Return value """, - articleContent: """ - # Leading heading is ignored - + extensionFileContent: """ @Metadata { @DocumentationExtension(mergeBehavior: override) } """ ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") XCTAssertNil(withArticleOverride.abstract, - "The article overrides—and removes—the abstract from the in-source documenation") + "The article overrides—and removes—the abstract from the in-source documentation") XCTAssertNil(withArticleOverride.discussion, - "The article overries the discussion.") + "The article overrides the discussion.") XCTAssertNil(withArticleOverride.parametersSection?.parameters, "The article overrides—and removes—the parameter section from the in-source documentation.") XCTAssertEqual((withArticleOverride.returnsSection?.content ?? []).map { $0.format() }.joined(), "", @@ -84,9 +82,7 @@ class SymbolTests: XCTestCase { - name: A parameter - Returns: Return value """, - articleContent: """ - # This is my article - + extensionFileContent: """ @Metadata { @DocumentationExtension(mergeBehavior: override) } @@ -106,15 +102,18 @@ class SymbolTests: XCTestCase { ### Name of a topic - - ``MyKit`` - - ``MyKit/MyClass`` + - ``ModuleName`` + - ``ModuleName/SomeClass`` """ ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.map(\.diagnostic.summary), [ + "Organizing the module 'ModuleName' under 'ModuleName/SomeClass/someMethod(name:)' isn't allowed", + "Organizing 'ModuleName/SomeClass' under 'ModuleName/SomeClass/someMethod(name:)' forms a cycle", + ]) XCTAssertEqual(withArticleOverride.abstract?.plainText, "This is an abstract.", - "The article overrides the abstract from the in-source documenation") + "The article overrides the abstract from the in-source documentation") XCTAssertEqual((withArticleOverride.discussion?.content ?? []).filter({ markup -> Bool in return !(markup.isEmpty) && !(markup is BlockDirective) }).map { $0.format().trimmingLines() }, ["This is a multi-paragraph overview.", "It continues here."], @@ -128,7 +127,7 @@ class SymbolTests: XCTestCase { } XCTAssertEqual((withArticleOverride.returnsSection?.content ?? []).map { $0.format() }, ["Return value is explained here."], - "The article overries—and removes—the return section from the in-source documentation.") + "The article overrides—and removes—the return section from the in-source documentation.") if let topicContent = withArticleOverride.topics?.content, let heading = topicContent.first as? Heading, let topics = topicContent.last as? UnorderedList { XCTAssertEqual(heading.plainText, "Name of a topic") @@ -148,11 +147,9 @@ class SymbolTests: XCTestCase { - name: A parameter - Returns: Return value """, - articleContent: """ - # Leading heading is ignored - """ + extensionFileContent: "" // just the H1 symbol link and no other content ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") XCTAssertEqual(withEmptyArticleOverride.abstract?.format(), "A cool API to call.") XCTAssertEqual((withEmptyArticleOverride.discussion?.content.filter({ markup -> Bool in @@ -173,6 +170,22 @@ class SymbolTests: XCTestCase { // When no DocumentationExtension behavior is specified, the default behavior is "append to doc comment". let withAndWithoutAppendConfiguration = ["", "@Metadata { \n @DocumentationExtension(mergeBehavior: append) \n }"] + func verifyExtensionProblem(_ problems: [Problem], forMetadata metadata: String, file: StaticString = #filePath, line: UInt = #line) { + XCTAssertEqual( + !metadata.isEmpty, + problems.map(\.diagnostic.summary).contains("'DocumentationExtension' doesn't change default configuration and has no effect"), + "When there is a \"append\" extension configuration, there should be a warning about it.", + file: file, line: line + ) + } + func verifyProblems(_ problems: [Problem], forMetadata metadata: String, file: StaticString = #filePath, line: UInt = #line) { + verifyExtensionProblem(problems, forMetadata: metadata, file: file, line: line) + XCTAssertEqual(problems.suffix(2).map(\.diagnostic.summary), [ + "Organizing the module 'ModuleName' under 'ModuleName/SomeClass/someMethod(name:)' isn't allowed", + "Organizing 'ModuleName/SomeClass' under 'ModuleName/SomeClass/someMethod(name:)' forms a cycle", + ], file: file, line: line) + } + // Append curation to doc comment for metadata in withAndWithoutAppendConfiguration { let (withArticleOverride, problems) = try await makeDocumentationNodeSymbol( @@ -183,21 +196,18 @@ class SymbolTests: XCTestCase { - name: A parameter - Returns: Return value """, - articleContent: """ - # This is my article - + extensionFileContent: """ \(metadata) ## Topics ### Name of a topic - - ``MyKit`` - - ``MyKit/MyClass`` - + - ``ModuleName`` + - ``ModuleName/SomeClass`` """ ) - XCTAssert(problems.isEmpty) + verifyProblems(problems, forMetadata: metadata) XCTAssertEqual(withArticleOverride.abstract?.format(), "A cool API to call.") XCTAssertEqual((withArticleOverride.discussion?.content.filter({ markup -> Bool in @@ -229,9 +239,7 @@ class SymbolTests: XCTestCase { - name: A parameter - Returns: Return value """, - articleContent: """ - # This is my article - + extensionFileContent: """ \(metadata) This is a multi-paragraph overview. @@ -242,19 +250,18 @@ class SymbolTests: XCTestCase { ### Name of a topic - - ``MyKit`` - - ``MyKit/MyClass`` - + - ``ModuleName`` + - ``ModuleName/SomeClass`` """ ) - XCTAssert(problems.isEmpty) + verifyProblems(problems, forMetadata: metadata) XCTAssertEqual(withArticleOverride.abstract?.format(), "A cool API to call.") XCTAssertEqual((withArticleOverride.discussion?.content.filter({ markup -> Bool in return !(markup.isEmpty) && !(markup is BlockDirective) }) ?? []).map { $0.format().trimmingLines() }, ["This is a multi-paragraph overview.", "It continues here."], - "The article overries—and adds—a discussion.") + "The article overrides—and adds—a discussion.") if let parameter = withArticleOverride.parametersSection?.parameters.first, withArticleOverride.parametersSection?.parameters.count == 1 { XCTAssertEqual(parameter.name, "name") @@ -282,9 +289,7 @@ class SymbolTests: XCTestCase { - name: A parameter - Returns: Return value """, - articleContent: """ - # This is my article - + extensionFileContent: """ \(metadata) This is a multi-paragraph overview. @@ -295,19 +300,18 @@ class SymbolTests: XCTestCase { ### Name of a topic - - ``MyKit`` - - ``MyKit/MyClass`` - + - ``ModuleName`` + - ``ModuleName/SomeClass`` """ ) - XCTAssert(problems.isEmpty) + verifyProblems(problems, forMetadata: metadata) XCTAssertEqual(withArticleOverride.abstract?.format(), "A cool API to call.") XCTAssertEqual((withArticleOverride.discussion?.content.filter({ markup -> Bool in return !(markup.isEmpty) && !(markup is BlockDirective) }) ?? []).map { $0.format().trimmingLines() }, ["This is a multi-paragraph overview.", "It continues here."], - "The article overries—and adds—a discussion.") + "The article overrides—and adds—a discussion.") if let parameter = withArticleOverride.parametersSection?.parameters.first, withArticleOverride.parametersSection?.parameters.count == 1 { XCTAssertEqual(parameter.name, "name") @@ -331,9 +335,7 @@ class SymbolTests: XCTestCase { docComment: """ A cool API to call. """, - articleContent: """ - # This is my article - + extensionFileContent: """ \(metadata) This is a multi-paragraph overview. @@ -349,19 +351,18 @@ class SymbolTests: XCTestCase { ### Name of a topic - - ``MyKit`` - - ``MyKit/MyClass`` - + - ``ModuleName`` + - ``ModuleName/SomeClass`` """ ) - XCTAssert(problems.isEmpty) + verifyProblems(problems, forMetadata: metadata) XCTAssertEqual(withArticleOverride.abstract?.format(), "A cool API to call.") XCTAssertEqual((withArticleOverride.discussion?.content.filter({ markup -> Bool in return !(markup.isEmpty) && !(markup is BlockDirective) }) ?? []).map { $0.format().trimmingLines() }, ["This is a multi-paragraph overview.", "It continues here."], - "The article overries—and adds—a discussion.") + "The article overrides—and adds—a discussion.") if let parameter = withArticleOverride.parametersSection?.parameters.first, withArticleOverride.parametersSection?.parameters.count == 1 { XCTAssertEqual(parameter.name, "name") @@ -387,9 +388,7 @@ class SymbolTests: XCTestCase { The overview stats in the doc comment. """, - articleContent: """ - # This is my article - + extensionFileContent: """ \(metadata) And continues here in the article. @@ -399,14 +398,14 @@ class SymbolTests: XCTestCase { - Returns: Return value """ ) - XCTAssert(problems.isEmpty) + verifyExtensionProblem(problems, forMetadata: metadata) XCTAssertEqual(withArticleOverride.abstract?.format(), "A cool API to call.") XCTAssertEqual((withArticleOverride.discussion?.content.filter({ markup -> Bool in return !(markup.isEmpty) && !(markup is BlockDirective) }) ?? []).map { $0.format().trimmingLines() }, ["The overview stats in the doc comment.", "And continues here in the article."], - "The article overries—and adds—a discussion.") + "The article overrides—and adds—a discussion.") if let parameter = withArticleOverride.parametersSection?.parameters.first, withArticleOverride.parametersSection?.parameters.count == 1 { XCTAssertEqual(parameter.name, "name") @@ -431,22 +430,20 @@ class SymbolTests: XCTestCase { - name: A parameter - Returns: Return value """, - articleContent: """ - # This is my article - + extensionFileContent: """ \(metadata) This continues the overview from the doc comment. """ ) - XCTAssert(problems.isEmpty) + verifyExtensionProblem(problems, forMetadata: metadata) XCTAssertEqual(withArticleOverride.abstract?.format(), "A cool API to call.") XCTAssertEqual((withArticleOverride.discussion?.content.filter({ markup -> Bool in return !(markup.isEmpty) && !(markup is BlockDirective) }) ?? []).map { $0.format().trimmingLines() }, ["The overview starts in the doc comment.", "This continues the overview from the doc comment."], - "The article overries—and adds—a discussion.") + "The article overrides—and adds—a discussion.") if let parameter = withArticleOverride.parametersSection?.parameters.first, withArticleOverride.parametersSection?.parameters.count == 1 { XCTAssertEqual(parameter.name, "name") @@ -466,13 +463,11 @@ class SymbolTests: XCTestCase { A cool API to call. """, - articleContent: """ - # This is my article - + extensionFileContent: """ @Redirected(from: "some/previous/path/to/this/symbol") """ ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") XCTAssertEqual(withRedirectInArticle.redirects?.map { $0.oldPath.absoluteString }, ["some/previous/path/to/this/symbol"]) } @@ -484,16 +479,14 @@ class SymbolTests: XCTestCase { @Redirected(from: "some/previous/path/to/this/symbol") """, - articleContent: """ - # This is my article - """ + extensionFileContent: nil ) XCTAssertFalse(problems.isEmpty) XCTAssertEqual(withRedirectInArticle.redirects, nil) XCTAssertEqual(problems.first?.diagnostic.identifier, "org.swift.docc.UnsupportedDocCommentDirective") - XCTAssertEqual(problems.first?.diagnostic.range?.lowerBound.line, 3) - XCTAssertEqual(problems.first?.diagnostic.range?.lowerBound.column, 1) + XCTAssertEqual(problems.first?.diagnostic.range?.lowerBound.line, 14) + XCTAssertEqual(problems.first?.diagnostic.range?.lowerBound.column, 18) } func testNoWarningWhenDocCommentContainsDirective() async throws { @@ -503,11 +496,9 @@ class SymbolTests: XCTestCase { @Snippet(from: "Snippets/Snippets/MySnippet") """, - articleContent: """ - # This is my article - """ + extensionFileContent: nil ) - XCTAssertTrue(problems.isEmpty) + XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") } func testNoWarningWhenDocCommentContainsDoxygen() async throws { @@ -1054,17 +1045,20 @@ class SymbolTests: XCTestCase { ### Name of a topic - - ``MyKit`` - - ``MyKit/MyClass`` + - ``ModuleName`` + - ``ModuleName/SomeClass`` """, - articleContent: nil + extensionFileContent: nil ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.map(\.diagnostic.summary), [ + "Organizing the module 'ModuleName' under 'ModuleName/SomeClass/someMethod(name:)' isn't allowed", + "Organizing 'ModuleName/SomeClass' under 'ModuleName/SomeClass/someMethod(name:)' forms a cycle", + ]) XCTAssertEqual(withArticleOverride.abstract?.format(), "This is an abstract.", - "The article overrides the abstract from the in-source documenation") + "The article overrides the abstract from the in-source documentation") XCTAssertEqual((withArticleOverride.discussion?.content ?? []).map { $0.detachedFromParent.format() }, ["This is a multi-paragraph overview.", "It continues here."], - "The article overries—and adds—a discussion.") + "The article overrides—and adds—a discussion.") if let parameter = withArticleOverride.parametersSection?.parameters.first, withArticleOverride.parametersSection?.parameters.count == 1 { XCTAssertEqual(parameter.name, "name") @@ -1074,7 +1068,7 @@ class SymbolTests: XCTestCase { } XCTAssertEqual((withArticleOverride.returnsSection?.content ?? []).map { $0.format() }, ["Return value is explained here."], - "The article overries—and removes—the return section from the in-source documentation.") + "The article overrides—and removes—the return section from the in-source documentation.") if let topicContent = withArticleOverride.topics?.content, let heading = topicContent.first as? Heading, let topics = topicContent.last as? UnorderedList { XCTAssertEqual(heading.detachedFromParent.format(), "### Name of a topic") @@ -1085,7 +1079,7 @@ class SymbolTests: XCTestCase { } func testCreatesSourceURLFromLocationMixin() throws { - let identifer = SymbolGraph.Symbol.Identifier(precise: "s:5MyKit0A5ClassC10myFunctionyyF", interfaceLanguage: "swift") + let identifier = SymbolGraph.Symbol.Identifier(precise: "s:5MyKit0A5ClassC10myFunctionyyF", interfaceLanguage: "swift") let names = SymbolGraph.Symbol.Names(title: "", navigator: nil, subHeading: nil, prose: nil) let pathComponents = ["path", "to", "my file.swift"] let range = SymbolGraph.LineList.SourceRange( @@ -1095,7 +1089,7 @@ class SymbolTests: XCTestCase { let line = SymbolGraph.LineList.Line(text: "@Image this is a known directive", range: range) let docComment = SymbolGraph.LineList([line]) let symbol = SymbolGraph.Symbol( - identifier: identifer, + identifier: identifier, names: names, pathComponents: pathComponents, docComment: docComment, @@ -1112,17 +1106,31 @@ class SymbolTests: XCTestCase { } func testAddingConstraintsToSymbol() async throws { - let (withoutArticle, _) = try await makeDocumentationNodeSymbol( - docComment: """ + let myFunctionUSR = "s:5MyKit0A5ClassC10myFunctionyyF" + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: url.appendingPathComponent("mykit-iOS.symbols.json"))) + + let newDocComment = self.makeLineList( + docComment: """ A cool API to call. - Parameters: - name: A parameter - Returns: Return value """, - articleContent: nil - ) + moduleName: nil, + startOffset: .init(line: 0, character: 0), + url: URL(string: "file:///tmp/File.swift")! + ) + + // The `guard` statement` below will handle the `nil` case by failing the test and + graph.symbols[myFunctionUSR]?.docComment = newDocComment + + let newGraphData = try JSONEncoder().encode(graph) + try newGraphData.write(to: url.appendingPathComponent("mykit-iOS.symbols.json")) + } + let withoutArticle = try XCTUnwrap(context.documentationCache[myFunctionUSR]?.semantic as? Symbol) // The original symbol has 3 generic constraints: // { // "extendedModule": "MyKit", @@ -1208,10 +1216,10 @@ class SymbolTests: XCTestCase { @Available(customOS, introduced: 1.2.3) } """, - articleContent: nil + extensionFileContent: nil ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") let availability = try XCTUnwrap(node.metadata?.availability.first) XCTAssertEqual(availability.platform, .other("customOS")) @@ -1225,8 +1233,8 @@ class SymbolTests: XCTestCase { @Metadata """, - docCommentLineOffset: 12, - articleContent: nil, + docCommentLineStart: 12, + extensionFileContent: nil, diagnosticEngineFilterLevel: .information ) @@ -1234,9 +1242,9 @@ class SymbolTests: XCTestCase { let diagnostic = try XCTUnwrap(problems.first).diagnostic XCTAssertEqual(diagnostic.identifier, "org.swift.docc.Metadata.NoConfiguration") - XCTAssertEqual(diagnostic.source?.absoluteString, "file:///tmp/File.swift") + XCTAssertEqual(diagnostic.source?.path, "/Users/username/path/to/SomeFile.swift") XCTAssertEqual(diagnostic.range?.lowerBound.line, 15) - XCTAssertEqual(diagnostic.range?.lowerBound.column, 1) + XCTAssertEqual(diagnostic.range?.lowerBound.column, 18) } func testEmitsWarningForDuplicateMetadata() async throws { @@ -1248,10 +1256,8 @@ class SymbolTests: XCTestCase { @Available("Platform from doc comment", introduced: 1.2.3) } """, - docCommentLineOffset: 12, - articleContent: """ - # Title - + docCommentLineStart: 12, + extensionFileContent: """ @Metadata { @Available("Platform from documentation extension", introduced: 1.2.3) } @@ -1262,9 +1268,9 @@ class SymbolTests: XCTestCase { let diagnostic = try XCTUnwrap(problems.first).diagnostic XCTAssertEqual(diagnostic.identifier, "org.swift.docc.DuplicateMetadata") - XCTAssertEqual(diagnostic.source?.absoluteString, "file:///tmp/File.swift") + XCTAssertEqual(diagnostic.source?.path, "/Users/username/path/to/SomeFile.swift") XCTAssertEqual(diagnostic.range?.lowerBound.line, 15) - XCTAssertEqual(diagnostic.range?.lowerBound.column, 1) + XCTAssertEqual(diagnostic.range?.lowerBound.column, 18) let availability = try XCTUnwrap(node.metadata?.availability.first) XCTAssertEqual(availability.platform, .other("Platform from documentation extension")) @@ -1293,7 +1299,7 @@ class SymbolTests: XCTestCase { @Redirected(from: "old/path/to/this/page") } """, - articleContent: nil + extensionFileContent: nil ) XCTAssertEqual( @@ -1309,11 +1315,13 @@ class SymbolTests: XCTestCase { "org.swift.docc.Metadata.InvalidPageColorInDocumentationComment", "org.swift.docc.Metadata.InvalidTitleHeadingInDocumentationComment", "org.swift.docc.Metadata.InvalidRedirectedInDocumentationComment", + + "org.swift.docc.unresolvedResource", // For the "test" asset that doesn't exist. ] ) // Verify that each problem has exactly one solution to remove the directive - for problem in problems { + for problem in problems where problem.diagnostic.identifier.hasPrefix("org.swift.docc.Metadata.") { XCTAssertEqual(problem.possibleSolutions.count, 1, "Each invalid metadata directive should have exactly one solution") let solution = try XCTUnwrap(problem.possibleSolutions.first) @@ -1335,10 +1343,10 @@ class SymbolTests: XCTestCase { This is the deprecation summary. } """, - articleContent: nil + extensionFileContent: nil ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") XCTAssertEqual( (node.semantic as? Symbol)? @@ -1359,10 +1367,10 @@ class SymbolTests: XCTestCase { @Comment(This is a comment) """, - articleContent: nil + extensionFileContent: nil ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") } func testSolutionForInvalidMetadataDirectiveRemovesDirective() async throws { @@ -1374,7 +1382,7 @@ class SymbolTests: XCTestCase { @DisplayName("Invalid Display Name") } """, - articleContent: nil + extensionFileContent: nil ) XCTAssertEqual(problems.count, 1) @@ -1533,9 +1541,9 @@ class SymbolTests: XCTestCase { It continues here. """, - articleContent: nil + extensionFileContent: nil ) - XCTAssert(problems.isEmpty) + XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") XCTAssertEqual(semanticWithLeadingWhitespace.abstract?.format(), "This is an abstract.") let lines = semanticWithLeadingWhitespace.discussion?.content.map{ $0.format() } ?? [] let expectedDiscussion = """ @@ -1549,79 +1557,77 @@ class SymbolTests: XCTestCase { // MARK: - Helpers - func makeDocumentationNodeForSymbol( + private func makeDocumentationNodeForSymbol( docComment: String, - docCommentLineOffset: Int = 0, - articleContent: String?, + docCommentLineStart: Int = 11, // an arbitrary non-zero start line + extensionFileContent: String?, diagnosticEngineFilterLevel: DiagnosticSeverity = .warning, file: StaticString = #filePath, line: UInt = #line ) async throws -> (DocumentationNode, [Problem]) { - let myFunctionUSR = "s:5MyKit0A5ClassC10myFunctionyyF" - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in - var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: url.appendingPathComponent("mykit-iOS.symbols.json"))) - - let newDocComment = self.makeLineList( - docComment: docComment, - moduleName: nil, - startOffset: .init( - line: docCommentLineOffset, - character: 0 - ), - url: URL(string: "file:///tmp/File.swift")! + let classUSR = "some-class-id" + let methodUSR = "some-method-id" + var catalogContent: [any File] = [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph( + moduleName: "ModuleName", + symbols: [ + makeSymbol(id: classUSR, kind: .class, pathComponents: ["SomeClass"]), + + makeSymbol( + id: methodUSR, + kind: .method, + pathComponents: ["SomeClass", "someMethod(name:)"], + docComment: docComment, + location: ( + position: .init(line: docCommentLineStart, character: 17), // an arbitrary non-zero start column/character + url: URL(fileURLWithPath: "/Users/username/path/to/SomeFile.swift") + ), + signature: .init( + parameters: [ + .init(name: "name", externalName: nil, declarationFragments: [ + .init(kind: .internalParameter, spelling: "name", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "String", preciseIdentifier: "s:SS") + ], children: []) + ], + returns: [ + .init(kind: .typeIdentifier, spelling: "ReturnValue", preciseIdentifier: "return-value-id") + ] + ) + ) + ], + relationships: [ + .init(source: methodUSR, target: classUSR, kind: .memberOf, targetFallback: nil) + ] + )) + ] + if let extensionFileContent { + catalogContent.append( + TextFile(name: "Extension.md", utf8Content: """ + # ``SomeClass/someMethod(name:)`` + + \(extensionFileContent) + """) ) - - // The `guard` statement` below will handle the `nil` case by failing the test and - graph.symbols[myFunctionUSR]?.docComment = newDocComment - - let newGraphData = try JSONEncoder().encode(graph) - try newGraphData.write(to: url.appendingPathComponent("mykit-iOS.symbols.json")) - } - - guard let original = context.documentationCache[myFunctionUSR], - let unifiedSymbol = original.unifiedSymbol, - let symbolSemantic = original.semantic as? Symbol - else { - XCTFail("Couldn't find the expected symbol", file: (file), line: line) - enum TestHelperError: Error { case missingExpectedMyFuctionSymbol } - throw TestHelperError.missingExpectedMyFuctionSymbol - } - - let article: Article? = articleContent.flatMap { - let document = Document(parsing: $0, options: .parseBlockDirectives) - var problems = [Problem]() - let article = Article(from: document, source: nil, for: bundle, problems: &problems) - XCTAssertNotNil(article, "The sidecar Article couldn't be created.", file: (file), line: line) - return article } - let engine = DiagnosticEngine(filterLevel: diagnosticEngineFilterLevel) - - var node = DocumentationNode( - reference: original.reference, - unifiedSymbol: unifiedSymbol, - moduleData: unifiedSymbol.modules.first!.value, - moduleReference: symbolSemantic.moduleReference - ) + let diagnosticEngine = DiagnosticEngine(filterLevel: diagnosticEngineFilterLevel) + let (_, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: catalogContent), diagnosticEngine: diagnosticEngine) - node.initializeSymbolContent( - documentationExtension: article, - engine: engine, - bundle: bundle - ) + let node = try XCTUnwrap(context.documentationCache[methodUSR], file: file, line: line) - return (node, engine.problems) + return (node, context.problems) } - func makeDocumentationNodeSymbol( + private func makeDocumentationNodeSymbol( docComment: String, - articleContent: String?, + extensionFileContent: String?, file: StaticString = #filePath, line: UInt = #line ) async throws -> (Symbol, [Problem]) { let (node, problems) = try await makeDocumentationNodeForSymbol( docComment: docComment, - articleContent: articleContent, + extensionFileContent: extensionFileContent, file: file, line: line ) From 023852579f5d13149eebcd509692fe79a02c4633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 15 Sep 2025 10:16:43 +0200 Subject: [PATCH 30/90] Remove API that was deprecated during the 6.1 release (#1266) * Delay removing the deprecated 'diagnostics.json file by 1 minor release * Keep old deprecated CLI flag for backwards compatibility * Remove API that was deprecated during the 6.1 release * Reduce duplication in updated test case * Simplify providing data to symbol graph loader * Remove unused API to remove nodes from path hierarchy --- .../Convert/ConvertService.swift | 11 +- .../AbsoluteSymbolLink.swift | 343 ------- .../DocCSymbolRepresentable.swift | 232 ----- .../Navigator/NavigatorIndex+Ext.swift | 75 -- .../Indexing/Navigator/NavigatorIndex.swift | 75 +- .../Bundle Assets/DataAssetManager.swift | 13 +- .../DocumentationContext+Deprecated.swift | 47 - .../ConvertOutputConsumer.swift | 12 +- .../Infrastructure/DocumentationBundle.swift | 25 - .../Infrastructure/DocumentationContext.swift | 240 +---- .../DocumentationConverter.swift | 486 ---------- .../OutOfProcessReferenceResolver.swift | 14 +- .../Link Resolution/LinkResolver.swift | 13 +- .../Link Resolution/PathHierarchy.swift | 17 - .../PathHierarchyBasedLinkResolver.swift | 15 +- .../Infrastructure/NodeURLGenerator.swift | 7 +- .../Symbol Graph/SymbolGraphLoader.swift | 17 +- .../Workspace/DocumentationBundle+Info.swift | 68 +- .../Workspace/DocumentationWorkspace.swift | 148 --- .../DocumentationWorkspaceDataProvider.swift | 38 +- .../Workspace/FileSystemProvider.swift | 66 -- .../Workspace/GeneratedDataProvider.swift | 149 --- ...leSystemDataProvider+BundleDiscovery.swift | 180 ---- .../LocalFileSystemDataProvider.swift | 57 -- .../PrebuiltLocalFileSystemDataProvider.swift | 35 - .../LinkTargets/LinkDestinationSummary.swift | 6 +- Sources/SwiftDocC/Model/BuildMetadata.swift | 15 +- Sources/SwiftDocC/Model/Identifier.swift | 25 +- Sources/SwiftDocC/Model/Kind.swift | 14 +- .../DocumentationContentRenderer.swift | 1 - .../Model/Rendering/LinkTitleResolver.swift | 8 +- .../Model/Rendering/RenderNode.swift | 8 +- .../Rendering/RenderNodeTranslator.swift | 2 +- .../Rendering/RenderReferenceStore.swift | 7 +- .../Semantics/Article/MarkupConvertible.swift | 15 - .../Semantics/DirectiveConvertable.swift | 13 - .../AutomaticDirectiveConvertible.swift | 5 - .../ExternalReferenceWalker.swift | 7 +- .../General Purpose Analyses/Extract.swift | 10 - .../HasAtLeastOne.swift | 5 - .../HasAtMostOne.swift | 6 - .../General Purpose Analyses/HasContent.swift | 5 - .../HasExactlyOne.swift | 25 - .../HasOnlyKnownArguments.swift | 5 - .../HasOnlyKnownDirectives.swift | 5 - .../HasOnlySequentialHeadings.swift | 5 - .../Semantics/ReferenceResolver.swift | 7 +- .../Semantics/SemanticAnalysis.swift | 76 -- .../SwiftDocC/Semantics/Symbol/Symbol.swift | 41 +- .../Technology/TutorialTableOfContents.swift | 3 - .../SwiftDocC/Semantics/TechnologyBound.swift | 16 - .../Semantics/Visitor/SemanticVisitor.swift | 13 +- .../Semantics/Walker/SemanticWalker.swift | 4 +- .../Walker/Walkers/SemanticTreeDumper.swift | 7 +- .../SwiftDocC.docc/Resources/Diagnostics.json | 2 +- Sources/SwiftDocC/Utility/FeatureFlags.swift | 19 - .../Convert/ConvertFileWritingConsumer.swift | 9 +- .../ArgumentParsing/Subcommands/Convert.swift | 12 +- .../DocumentationConverterTests.swift | 48 - ...recatedDiagnosticsDigestWarningTests.swift | 5 +- .../Infrastructure/BundleDiscoveryTests.swift | 93 +- .../DocumentationContextTests.swift | 75 -- .../DocumentationWorkspaceTests.swift | 206 ----- .../ExternalReferenceResolverTests.swift | 66 -- .../GeneratedDataProvider.swift | 154 ---- .../DocumentationInputsProviderTests.swift | 41 +- .../AbsoluteSymbolLinkTests.swift | 870 ------------------ .../DocCSymbolRepresentableTests.swift | 205 ----- .../SymbolDisambiguationTests.swift | 2 +- .../SymbolGraph/SymbolGraphLoaderTests.swift | 4 +- .../ConvertActionTests.swift | 6 +- 71 files changed, 108 insertions(+), 4451 deletions(-) delete mode 100644 Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/AbsoluteSymbolLink.swift delete mode 100644 Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/DocCSymbolRepresentable.swift delete mode 100644 Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift delete mode 100644 Sources/SwiftDocC/Infrastructure/Context/Deprecated/DocumentationContext+Deprecated.swift delete mode 100644 Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift delete mode 100644 Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspace.swift delete mode 100644 Sources/SwiftDocC/Infrastructure/Workspace/FileSystemProvider.swift delete mode 100644 Sources/SwiftDocC/Infrastructure/Workspace/GeneratedDataProvider.swift delete mode 100644 Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider+BundleDiscovery.swift delete mode 100644 Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider.swift delete mode 100644 Sources/SwiftDocC/Infrastructure/Workspace/PrebuiltLocalFileSystemDataProvider.swift delete mode 100644 Sources/SwiftDocC/Semantics/SemanticAnalysis.swift delete mode 100644 Sources/SwiftDocC/Semantics/TechnologyBound.swift delete mode 100644 Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift delete mode 100644 Tests/SwiftDocCTests/Infrastructure/DocumentationWorkspaceTests.swift delete mode 100644 Tests/SwiftDocCTests/Infrastructure/GeneratedDataProvider.swift delete mode 100644 Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/AbsoluteSymbolLinkTests.swift delete mode 100644 Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/DocCSymbolRepresentableTests.swift diff --git a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift index e03d60e729..fc22a5c356 100644 --- a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift +++ b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift @@ -243,7 +243,8 @@ public struct ConvertService: DocumentationService { .compactMap { (value, isDocumentationExtensionContent) -> (ResolvedTopicReference, RenderReferenceStore.TopicContent)? in let (topicReference, article) = value - guard let bundle = context.bundle, bundle.id == topicReference.bundleID else { return nil } + let bundle = context.bundle + guard bundle.id == topicReference.bundleID else { return nil } let renderer = DocumentationContentRenderer(documentationContext: context, bundle: bundle) let documentationNodeKind: DocumentationNode.Kind = isDocumentationExtensionContent ? .unknownSymbol : .article @@ -293,3 +294,11 @@ private extension SymbolGraph.LineList.Line { ) } } + +private extension DocumentationNode { + func meetsExpandedDocumentationRequirements(_ requirements: ConvertRequest.ExpandedDocumentationRequirements) -> Bool { + guard let symbol else { return false } + + return requirements.accessControlLevels.contains(symbol.accessLevel.rawValue) && (!symbol.names.title.starts(with: "_") || requirements.canBeUnderscored) + } +} diff --git a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/AbsoluteSymbolLink.swift b/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/AbsoluteSymbolLink.swift deleted file mode 100644 index e4d962db07..0000000000 --- a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/AbsoluteSymbolLink.swift +++ /dev/null @@ -1,343 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import Foundation -import SymbolKit - -/// An absolute link to a symbol. -/// -/// You can use this model to validate a symbol link and access its different parts. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -public struct AbsoluteSymbolLink: CustomStringConvertible { - /// The identifier for the documentation bundle this link is from. - public let bundleID: String - - /// The name of the module that contains this symbol link. - /// - Note: This could be a link to the module itself. - public let module: String - - /// The top level symbol in this documentation link. - /// - /// If this symbol represents a module (see ``representsModule``), then - /// this is just the module and can be ignored. Otherwise, it's the top level symbol within - /// the module. - public let topLevelSymbol: LinkComponent - - /// The ordered path components, excluding the module and top level symbol. - public let basePathComponents: [LinkComponent] - - /// A Boolean value that is true if this is a link to a module. - public let representsModule: Bool - - /// Create a new documentation symbol link from a path. - /// - /// Expects an absolute symbol link structured like one of the following: - /// - doc://org.swift.docc.example/documentation/ModuleName - /// - doc://org.swift.docc.example/documentation/ModuleName/TypeName - /// - doc://org.swift.docc.example/documentation/ModuleName/ParentType/Test-swift.class/testFunc()-k2k9d - /// - doc://org.swift.docc.example/documentation/ModuleName/ClassName/functionName(firstParameter:secondParameter:) - public init?(string: String) { - // Begin by constructing a validated URL from the given string. - // Normally symbol links would be validated with `init(symbolPath:)` but since this is expected - // to be an absolute URL we parse it with `init(parsing:)` instead. - guard let validatedURL = ValidatedURL(parsingExact: string)?.requiring(scheme: ResolvedTopicReference.urlScheme) else { - return nil - } - - // All absolute documentation links include the bundle identifier as their host. - guard let bundleID = validatedURL.components.host, !bundleID.isEmpty else { - return nil - } - self.bundleID = bundleID - - var pathComponents = validatedURL.url.pathComponents - - // Swift's URL interprets the following link "doc://org.swift.docc.example/documentation/ModuleName" - // to have a sole "/" as its first path component. We'll just remove it if it's there. - if pathComponents.first == "/" { - pathComponents.removeFirst() - } - - // Swift-DocC requires absolute symbol links to be prepended with "documentation" - guard pathComponents.first == NodeURLGenerator.Path.documentationFolderName else { - return nil - } - - // Swift-DocC requires absolute symbol links to be prepended with "documentation" - // as their first path component but that's not actually part of the symbol's path - // so we drop it here. - pathComponents.removeFirst() - - // Now that we've cleaned up the link, confirm that it's non-empty - guard !pathComponents.isEmpty else { - return nil - } - - // Validate and construct the link component that represents the module - guard let moduleLinkComponent = LinkComponent(string: pathComponents.removeFirst()) else { - return nil - } - - // We don't allow modules to have disambiguation suffixes - guard !moduleLinkComponent.hasDisambiguationSuffix else { - return nil - } - self.module = moduleLinkComponent.name - - // Next we'll attempt to construct the link component for the top level symbol - // within the module. - if pathComponents.isEmpty { - // There are no more path components, so this is a link to the module itself - self.topLevelSymbol = moduleLinkComponent - self.representsModule = true - } else if let topLevelSymbol = LinkComponent(string: pathComponents.removeFirst()) { - // We were able to build a valid link component for the next path component - // so we'll set the top level symbol value and indicate that this is not a link - // to the module - self.topLevelSymbol = topLevelSymbol - self.representsModule = false - } else { - return nil - } - - // Finally we transform the remaining path components into link components - basePathComponents = pathComponents.compactMap { componentName in - LinkComponent(string: componentName) - } - - // If any of the path components were invalid, we want to mark the entire link as invalid - guard basePathComponents.count == pathComponents.count else { - return nil - } - } - - public var description: String { - """ - { - bundleID: \(bundleID.singleQuoted), - module: \(module.singleQuoted), - topLevelSymbol: \(topLevelSymbol), - representsModule: \(representsModule), - basePathComponents: [\(basePathComponents.map(\.description).joined(separator: ", "))] - } - """ - } -} - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension AbsoluteSymbolLink { - /// A component of a symbol link. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - public struct LinkComponent: CustomStringConvertible { - /// The name of the symbol represented by the link component. - public let name: String - - /// The suffix used to disambiguate this symbol from other symbol's - /// that share the same name. - public let disambiguationSuffix: DisambiguationSuffix - - var hasDisambiguationSuffix: Bool { - disambiguationSuffix != .none - } - - init(name: String, disambiguationSuffix: DisambiguationSuffix) { - self.name = name - self.disambiguationSuffix = disambiguationSuffix - } - - /// Creates an absolute symbol component from a raw string. - /// - /// For example, the input string can be `"foo-swift.var"`. - public init?(string: String) { - // Check to see if this component includes a disambiguation suffix - if string.contains("-") { - // Split the path component into its name and disambiguation suffix. - // It's important to limit to a single split since the disambiguation - // suffix itself could also contain a '-'. - var splitPathComponent = string.split(separator: "-", maxSplits: 1) - guard splitPathComponent.count == 2 else { - // This catches a trailing '-' in the suffix (which we don't allow) - // since a split would then result in a single path component. - return nil - } - - // Set the name from the first half of the split - name = String(splitPathComponent.removeFirst()) - - // The disambiguation suffix is formed from the second half - let disambiguationSuffixString = String(splitPathComponent.removeLast()) - - // Attempt to parse and validate a disambiguation suffix - guard let disambiguationSuffix = DisambiguationSuffix( - string: disambiguationSuffixString - ) else { - // Invalid disambiguation suffix, so we just return nil - return nil - } - - guard disambiguationSuffix != .none else { - // Since a "-" was included, we expect the disambiguation - // suffix to be non-nil. - return nil - } - - self.disambiguationSuffix = disambiguationSuffix - } else { - // The path component had no "-" so we just set the name - // as the entire path component - name = string - disambiguationSuffix = .none - } - } - - public var description: String { - """ - (name: \(name.singleQuoted), suffix: \(disambiguationSuffix)) - """ - } - - /// A string representation of this link component. - public var asLinkComponentString: String { - "\(name)\(disambiguationSuffix.asLinkSuffixString)" - } - } -} - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension AbsoluteSymbolLink.LinkComponent { - /// A suffix attached to a documentation link to disambiguate it from other symbols - /// that share the same base name. - public enum DisambiguationSuffix: Equatable, CustomStringConvertible { - /// The link is not disambiguated. - case none - - /// The symbol's kind. - case kindIdentifier(String) - - /// A hash of the symbol's precise identifier. - case preciseIdentifierHash(String) - - /// The symbol's kind and precise identifier. - /// - /// See ``kindIdentifier(_:)`` and ``preciseIdentifierHash(_:)`` for details. - case kindAndPreciseIdentifier( - kindIdentifier: String, preciseIdentifierHash: String - ) - - private static func isKnownSymbolKindIdentifier(identifier: String) -> Bool { - return SymbolGraph.Symbol.KindIdentifier.isKnownIdentifier(identifier) - } - - /// Creates a disambiguation suffix based on the given kind and precise - /// identifiers. - init(kindIdentifier: String?, preciseIdentifier: String?) { - if let kindIdentifier, let preciseIdentifier { - self = .kindAndPreciseIdentifier( - kindIdentifier: kindIdentifier, - preciseIdentifierHash: preciseIdentifier.stableHashString - ) - } else if let kindIdentifier { - self = .kindIdentifier(kindIdentifier) - } else if let preciseIdentifier { - self = .preciseIdentifierHash(preciseIdentifier.stableHashString) - } else { - self = .none - } - } - - /// Creates a symbol path component disambiguation suffix from the given string. - init?(string: String) { - guard !string.isEmpty else { - self = .none - return - } - - // We begin by splitting the given string in - // case this disambiguation suffix includes both an id hash - // and a kind identifier. - let splitSuffix = string.split(separator: "-") - - if splitSuffix.count == 1 && splitSuffix[0] == string { - // The string didn't contain a "-" so now we check - // to see if the hash is a known symbol kind identifier. - if Self.isKnownSymbolKindIdentifier(identifier: string) { - self = .kindIdentifier(string) - } else { - // Since we've confirmed that it's not a symbol kind identifier - // we now assume its a hash of the symbol's precise identifier. - self = .preciseIdentifierHash(string) - } - } else if splitSuffix.count == 2 { - // The string did contain a "-" so we now know the exact format - // it should be in. - // - // We expect the symbol kind identifier to come first, followed - // by a hash of the symbol's precise identifier. - if Self.isKnownSymbolKindIdentifier(identifier: String(splitSuffix[0])) { - self = .kindAndPreciseIdentifier( - kindIdentifier: String(splitSuffix[0]), - preciseIdentifierHash: String(splitSuffix[1]) - ) - } else { - // We were unable to validate the given symbol kind identifier - // so this is an invalid format for a disambiguation suffix. - return nil - } - } else { - // Unexpected number or configuration of "-" in the given string - // so we just return nil. - return nil - } - } - - public var description: String { - switch self { - case .none: - return "(none)" - case .kindIdentifier(let kindIdentifier): - return "(kind: \(kindIdentifier.singleQuoted))" - case .preciseIdentifierHash(let preciseIdentifierHash): - return "(idHash: \(preciseIdentifierHash.singleQuoted))" - case .kindAndPreciseIdentifier( - kindIdentifier: let kindIdentifier, - preciseIdentifierHash: let preciseIdentifierHash - ): - return "(kind: \(kindIdentifier.singleQuoted), idHash: \(preciseIdentifierHash.singleQuoted))" - } - } - - /// A string representation of the given disambiguation suffix. - /// - /// This value will include the preceding "-" character if necessary. - /// For example, if this is a ``kindAndPreciseIdentifier(kindIdentifier:preciseIdentifierHash:)`` value, - /// the following might be returned: - /// - /// ``` - /// -swift.var-h73kj - /// ``` - /// - /// However, if this is a ``none``, an empty string will be returned. - public var asLinkSuffixString: String { - switch self { - case .none: - return "" - case .kindIdentifier(let kindIdentifier): - return "-\(kindIdentifier)" - case .preciseIdentifierHash(let preciseIdentifierHash): - return "-\(preciseIdentifierHash)" - case .kindAndPreciseIdentifier( - kindIdentifier: let kindIdentifier, - preciseIdentifierHash: let preciseIdentifierHash - ): - return "-\(kindIdentifier)-\(preciseIdentifierHash)" - } - } - } -} diff --git a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/DocCSymbolRepresentable.swift b/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/DocCSymbolRepresentable.swift deleted file mode 100644 index a31c77b110..0000000000 --- a/Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/DocCSymbolRepresentable.swift +++ /dev/null @@ -1,232 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import Foundation -public import SymbolKit - -/// A type that can be converted to a DocC symbol. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -public protocol DocCSymbolRepresentable: Equatable { - /// A namespaced, unique identifier for the kind of symbol. - /// - /// For example, a Swift class might use `swift.class`. - var kindIdentifier: String? { get } - - /// A unique identifier for this symbol. - /// - /// For Swift, this is the USR. - var preciseIdentifier: String? { get } - - /// The case-sensitive title of this symbol as would be used in documentation. - /// - /// > Note: DocC embeds function parameter information directly in the title. - /// > For example: `functionName(parameterName:secondParameter)` - /// > or `functionName(_:firstNamedParameter)`. - var title: String { get } -} - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -public extension DocCSymbolRepresentable { - /// The given symbol information as a symbol link component. - /// - /// The component will include a disambiguation suffix - /// based on the included information in the symbol. For example, if the symbol - /// includes a kind identifier and a precise identifier, both - /// will be represented in the link component. - var asLinkComponent: AbsoluteSymbolLink.LinkComponent { - AbsoluteSymbolLink.LinkComponent( - name: title, - disambiguationSuffix: .init( - kindIdentifier: kindIdentifier, - preciseIdentifier: preciseIdentifier - ) - ) - } -} - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension AbsoluteSymbolLink.LinkComponent { - /// Given an array of symbols that are overloads for the symbol represented - /// by this link component, returns those that are precisely identified by the component. - /// - /// If the link is not specific enough to disambiguate between the given symbols, - /// this function will return an empty array. - public func disambiguateBetweenOverloadedSymbols( - _ overloadedSymbols: [SymbolType] - ) -> [SymbolType] { - // First confirm that we were given symbols to disambiguate - guard !overloadedSymbols.isEmpty else { - return [] - } - - // Pair each overloaded symbol with its required disambiguation - // suffix. This will tell us what kind of disambiguation suffix the - // link should have. - let overloadedSymbolsWithSuffixes = zip( - overloadedSymbols, overloadedSymbols.requiredDisambiguationSuffixes - ) - - // Next we filter the given symbols for those that are precise matches - // for the component. - let matchingSymbols = overloadedSymbolsWithSuffixes.filter { (symbol, _) in - // Filter the results by those that are fully represented by the element. - // This includes checking case sensitivity and disambiguation suffix. - // This _should_ always return a single element but we can't be entirely sure. - return fullyRepresentsSymbol(symbol) - } - - // We now check all the returned matching symbols to confirm that - // the current link has the correct disambiguation suffix - for (_, (shouldAddIdHash, shouldAddKind)) in matchingSymbols { - if shouldAddIdHash && shouldAddKind { - guard case .kindAndPreciseIdentifier = disambiguationSuffix else { - return [] - } - } else if shouldAddIdHash { - guard case .preciseIdentifierHash = disambiguationSuffix else { - return [] - } - } else if shouldAddKind { - guard case .kindIdentifier = disambiguationSuffix else { - return [] - } - } else { - guard case .none = disambiguationSuffix else { - return [] - } - } - } - - // Since we've validated that the link has the correct - // disambiguation suffix, we now return all matching symbols. - return matchingSymbols.map(\.0) - } - - /// Returns true if the given symbol is fully represented by the - /// symbol link. - /// - /// This means that the element has the same name (case-sensitive) - /// and, if the symbol link has a disambiguation suffix, the given element has the same - /// type or usr. - private func fullyRepresentsSymbol( - _ symbol: some DocCSymbolRepresentable - ) -> Bool { - guard name == symbol.title else { - return false - } - - switch self.disambiguationSuffix { - case .none: - return true - case .kindIdentifier(let kindIdentifier): - return symbol.kindIdentifier == kindIdentifier - case .preciseIdentifierHash(let preciseIdentifierHash): - return symbol.preciseIdentifier?.stableHashString == preciseIdentifierHash - case .kindAndPreciseIdentifier( - kindIdentifier: let kindIdentifier, - preciseIdentifierHash: let preciseIdentifierHash): - return symbol.preciseIdentifier?.stableHashString == preciseIdentifierHash - && symbol.kindIdentifier == kindIdentifier - } - } -} - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -public extension Collection where Element: DocCSymbolRepresentable { - /// Given a collection of colliding symbols, returns the disambiguation suffix required - /// for each symbol to disambiguate it from the others in the collection. - var requiredDisambiguationSuffixes: [(shouldAddIdHash: Bool, shouldAddKind: Bool)] { - guard let first else { - return [] - } - - guard count > 1 else { - // There are no path collisions - return Array(repeating: (shouldAddIdHash: false, shouldAddKind: false), count: count) - } - - if allSatisfy({ symbol in symbol.kindIdentifier == first.kindIdentifier }) { - // All collisions are the same symbol kind. - return Array(repeating: (shouldAddIdHash: true, shouldAddKind: false), count: count) - } else { - // Disambiguate by kind - return map { currentSymbol in - let kindCount = filter { $0.kindIdentifier == currentSymbol.kindIdentifier }.count - return ( - shouldAddIdHash: kindCount > 1, - shouldAddKind: kindCount == 1 - ) - } - } - } -} - -#if compiler(>=6) -// DocCSymbolRepresentable inherits from Equatable. If SymbolKit added Equatable conformance in the future, this could behave differently. -// It's reasonable to expect that symbols with the same unique ID would be equal but it's possible that SymbolKit's implementation would consider more symbol properties. -// -// In the long term we should try to phase out DocCSymbolRepresentable since it doesn't reflect how DocC resolves links or disambiguated symbols in links. -extension SymbolGraph.Symbol: @retroactive Equatable {} -extension UnifiedSymbolGraph.Symbol: @retroactive Equatable {} -#endif - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension SymbolGraph.Symbol: DocCSymbolRepresentable { - public var preciseIdentifier: String? { - self.identifier.precise - } - - public var title: String { - self.names.title - } - - public var kindIdentifier: String? { - "\(self.identifier.interfaceLanguage).\(self.kind.identifier.identifier)" - } - - public static func == (lhs: SymbolGraph.Symbol, rhs: SymbolGraph.Symbol) -> Bool { - lhs.identifier.precise == rhs.identifier.precise - } -} - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension UnifiedSymbolGraph.Symbol: DocCSymbolRepresentable { - public var preciseIdentifier: String? { - self.uniqueIdentifier - } - - public var title: String { - guard let selector = self.defaultSelector else { - fatalError(""" - Failed to find a supported default selector. \ - Language unsupported or corrupt symbol graph provided. - """ - ) - } - - return self.names[selector]!.title - } - - public var kindIdentifier: String? { - guard let selector = self.defaultSelector else { - fatalError(""" - Failed to find a supported default selector. \ - Language unsupported or corrupt symbol graph provided. - """ - ) - } - - return "\(selector.interfaceLanguage).\(self.kind[selector]!.identifier.identifier)" - } - - public static func == (lhs: UnifiedSymbolGraph.Symbol, rhs: UnifiedSymbolGraph.Symbol) -> Bool { - lhs.uniqueIdentifier == rhs.uniqueIdentifier - } -} diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift deleted file mode 100644 index d3834d1e1e..0000000000 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift +++ /dev/null @@ -1,75 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - 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 - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import Foundation - -/** - This class provides a simple way to transform a `FileSystemProvider` into a `RenderNodeProvider` to feed an index builder. - The data from the disk is fetched and processed in an efficient way to build a navigator index. - */ -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released.") -public class FileSystemRenderNodeProvider: RenderNodeProvider { - - /// The internal `FileSystemProvider` reference. - private let dataProvider: any FileSystemProvider - - /// The list of problems the provider encountered during the process. - private var problems = [Problem]() - - /// The enqueued file system nodes. - private var queue = [FSNode]() - - /** - Initialize an instance to provide `RenderNode` instances from a give `FileSystemProvider`. - */ - public init(fileSystemProvider: any FileSystemProvider) { - dataProvider = fileSystemProvider - - // Insert the first node in the queue - queue.append(fileSystemProvider.fileSystem) - } - - /// Returns a render node that can be processed by an index creator, for example. - public func getRenderNode() -> RenderNode? { - var renderNode: RenderNode? = nil - - while let next = queue.first, renderNode == nil { - switch next { - case .directory(let dir): - queue.append(contentsOf: dir.children) - case .file(let file): - // we need to process JSON files only - if file.url.pathExtension.lowercased() == "json" { - do { - let data = try Data(contentsOf: file.url) - renderNode = try RenderNode.decode(fromJSON: data) - } catch { - let diagnostic = Diagnostic(source: file.url, - severity: .warning, - range: nil, - identifier: "org.swift.docc", - summary: "Invalid file found while indexing content: \(error.localizedDescription)") - let problem = Problem(diagnostic: diagnostic, possibleSolutions: []) - problems.append(problem) - } - } - } - queue.removeFirst() - } - - return renderNode - } - - /// Get the problems that happened during the process. - /// - Returns: An array with the problems encountered during the filesystem read of render nodes. - public func getProblems() -> [Problem] { - return problems - } -} diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift index 63a427ff87..8a6e452f48 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.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 @@ -11,17 +11,6 @@ public import Foundation import Crypto -/// A protocol to provide data to be indexed. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released.") -public protocol RenderNodeProvider { - /// Get an instance of `RenderNode` to be processed by the index. - /// - Note: Returning `nil` will end the indexing process. - func getRenderNode() -> RenderNode? - - /// Returns an array of `Problem` indicating which problems the `Provider` encountered. - func getProblems() -> [Problem] -} - /** A `NavigatorIndex` contains all the necessary information to display the data inside a navigator. The data ranges from the tree to the necessary pieces of information to filter the content and perform actions in a fast way. @@ -477,14 +466,6 @@ extension NavigatorIndex { */ open class Builder { - /// The data provider. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - public var renderNodeProvider: (any RenderNodeProvider)? { - _renderNodeProvider as! (any RenderNodeProvider)? - } - // This property only exist to be able to assign `nil` to `renderNodeProvider` in the new initializer without causing a deprecation warning. - private let _renderNodeProvider: Any? - /// The documentation archive to build an index from. public let archiveURL: URL? @@ -579,20 +560,6 @@ extension NavigatorIndex { /// - usePageTitle: Configure the builder to use the "page title" instead of the "navigator title" as the title for each entry. public init(archiveURL: URL? = nil, outputURL: URL, bundleIdentifier: String, sortRootChildrenByName: Bool = false, groupByLanguage: Bool = false, writePathsOnDisk: Bool = true, usePageTitle: Bool = false) { self.archiveURL = archiveURL - self._renderNodeProvider = nil - self.outputURL = outputURL - self.bundleIdentifier = bundleIdentifier - self.sortRootChildrenByName = sortRootChildrenByName - self.groupByLanguage = groupByLanguage - self.writePathsOnDisk = writePathsOnDisk - self.usePageTitle = usePageTitle - } - - @available(*, deprecated, renamed: "init(archiveURL:outputURL:bundleIdentifier:sortRootChildrenByName:groupByLanguage:writePathsOnDisk:usePageTitle:)", message: "Use 'init(archiveURL:outputURL:bundleIdentifier:sortRootChildrenByName:groupByLanguage:writePathsOnDisk:usePageTitle:)' instead. This deprecated API will be removed after 6.2 is released") - @_disfavoredOverload - public init(renderNodeProvider: (any RenderNodeProvider)? = nil, outputURL: URL, bundleIdentifier: String, sortRootChildrenByName: Bool = false, groupByLanguage: Bool = false, writePathsOnDisk: Bool = true, usePageTitle: Bool = false) { - self._renderNodeProvider = renderNodeProvider - self.archiveURL = nil self.outputURL = outputURL self.bundleIdentifier = bundleIdentifier self.sortRootChildrenByName = sortRootChildrenByName @@ -1292,17 +1259,12 @@ extension NavigatorIndex { /// Build the index using the render nodes files in the provided documentation archive. /// - Returns: A list containing all the errors encountered during indexing. - /// - Precondition: Either ``archiveURL`` or ``renderNodeProvider`` is set. + /// - Precondition: ``archiveURL`` is set. public func build() -> [Problem] { - if let archiveURL { - return _build(archiveURL: archiveURL) - } else { - return (self as (any _DeprecatedRenderNodeProviderAccess))._legacyBuild() + guard let archiveURL else { + fatalError("Calling `build()` requires that `archiveURL` is set.") } - } - - // After 6.2 is released, move this into `build()`. - private func _build(archiveURL: URL) -> [Problem] { + setup() let dataDirectory = archiveURL.appendingPathComponent(NodeURLGenerator.Path.dataFolderName, isDirectory: true) @@ -1323,27 +1285,6 @@ extension NavigatorIndex { return problems } - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - fileprivate func _legacyBuild() -> [Problem] { - precondition(renderNodeProvider != nil, "Calling `build()` without an `archiveURL` or `renderNodeProvider` set is not permitted.") - - setup() - - while let renderNode = renderNodeProvider!.getRenderNode() { - do { - try index(renderNode: renderNode) - } catch { - problems.append(error.problem(source: renderNode.identifier.url, - severity: .warning, - summaryPrefix: "RenderNode indexing process failed")) - } - } - - finalize() - - return problems - } - func availabilityEntryIDs(for availabilityID: UInt64) -> [Int]? { return availabilityIDs[Int(availabilityID)] } @@ -1416,9 +1357,3 @@ enum PathHasher: String { } } } - -private protocol _DeprecatedRenderNodeProviderAccess { - // This private function accesses the deprecated RenderNodeProvider - func _legacyBuild() -> [Problem] -} -extension NavigatorIndex.Builder: _DeprecatedRenderNodeProviderAccess {} diff --git a/Sources/SwiftDocC/Infrastructure/Bundle Assets/DataAssetManager.swift b/Sources/SwiftDocC/Infrastructure/Bundle Assets/DataAssetManager.swift index ac932b1a36..c36f2542dc 100644 --- a/Sources/SwiftDocC/Infrastructure/Bundle Assets/DataAssetManager.swift +++ b/Sources/SwiftDocC/Infrastructure/Bundle Assets/DataAssetManager.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 @@ -346,10 +346,6 @@ fileprivate extension NSRegularExpression { public struct AssetReference: Hashable, Codable { /// The name of the asset. public var assetName: String - @available(*, deprecated, renamed: "bundleID", message: "Use 'bundleID' instead. This deprecated API will be removed after 6.2 is released") - public var bundleIdentifier: String { - bundleID.rawValue - } /// The identifier of the bundle the asset is apart of. public let bundleID: DocumentationBundle.Identifier @@ -359,11 +355,4 @@ public struct AssetReference: Hashable, Codable { self.assetName = assetName self.bundleID = bundleID } - @available(*, deprecated, renamed: "init(assetName:bundleID:)", message: "Use 'init(assetName:bundleID:)' instead. This deprecated API will be removed after 6.2 is released") - public init(assetName: String, bundleIdentifier: String) { - self.init( - assetName: assetName, - bundleID: .init(rawValue: bundleIdentifier) - ) - } } diff --git a/Sources/SwiftDocC/Infrastructure/Context/Deprecated/DocumentationContext+Deprecated.swift b/Sources/SwiftDocC/Infrastructure/Context/Deprecated/DocumentationContext+Deprecated.swift deleted file mode 100644 index 97685f8a19..0000000000 --- a/Sources/SwiftDocC/Infrastructure/Context/Deprecated/DocumentationContext+Deprecated.swift +++ /dev/null @@ -1,47 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -extension DocumentationContext { - - @available(*, deprecated, renamed: "configuration.externalMetadata", message: "Use 'configuration.externalMetadata' instead. This deprecated API will be removed after Swift 6.2 is released.") - public var externalMetadata: ExternalMetadata { - get { configuration.externalMetadata } - set { configuration.externalMetadata = newValue } - } - - @available(*, deprecated, renamed: "configuration.externalDocumentationConfiguration.sources", message: "Use 'configuration.externalDocumentationConfiguration.sources' instead. This deprecated API will be removed after Swift 6.2 is released.") - public var externalDocumentationSources: [BundleIdentifier: any ExternalDocumentationSource] { - get { - var result = [BundleIdentifier: any ExternalDocumentationSource]() - for (key, value) in configuration.externalDocumentationConfiguration.sources { - result[key.rawValue] = value - } - return result - } - set { - configuration.externalDocumentationConfiguration.sources.removeAll() - for (key, value) in newValue { - configuration.externalDocumentationConfiguration.sources[.init(rawValue: key)] = value - } - } - } - - @available(*, deprecated, renamed: "configuration.externalDocumentationConfiguration.globalSymbolResolver", message: "Use 'configuration.externalDocumentationConfiguration.globalSymbolResolver' instead. This deprecated API will be removed after Swift 6.2 is released.") - public var globalExternalSymbolResolver: (any GlobalExternalSymbolResolver)? { - get { configuration.externalDocumentationConfiguration.globalSymbolResolver } - set { configuration.externalDocumentationConfiguration.globalSymbolResolver = newValue } - } - - @available(*, deprecated, renamed: "configuration.experimentalCoverageConfiguration.shouldStoreManuallyCuratedReferences", message: "Use 'configuration.experimentalCoverageConfiguration.shouldStoreManuallyCuratedReferences' instead. This deprecated API will be removed after Swift 6.2 is released.") - public var shouldStoreManuallyCuratedReferences: Bool { - get { configuration.experimentalCoverageConfiguration.shouldStoreManuallyCuratedReferences } - set { configuration.experimentalCoverageConfiguration.shouldStoreManuallyCuratedReferences = newValue } - } -} diff --git a/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift b/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift index 830404dda6..f5e1ebd432 100644 --- a/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift +++ b/Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift @@ -16,8 +16,8 @@ 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 + @available(*, deprecated, message: "This deprecated API will be removed after 6.3 is released") + func _deprecated_consume(problems: [Problem]) throws /// Consumes a render node that was generated during a conversion. /// > Warning: This method might be called concurrently. @@ -62,7 +62,7 @@ public extension ConvertOutputConsumer { // Default implementation so that conforming types don't need to implement deprecated API. public extension ConvertOutputConsumer { - func consume(problems: [Problem]) throws {} + func _deprecated_consume(problems: [Problem]) throws {} } // A package-internal protocol that callers can cast to when they need to call `_consume(problems:)` for backwards compatibility (until `consume(problems:)` is removed). @@ -84,7 +84,7 @@ package struct _Deprecated: _DeprecatedConsumeP } // This needs to be deprecated to be able to call `consume(problems:)` without a deprecation warning. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") + @available(*, deprecated, message: "This deprecated API will be removed after 6.3 is released") package func _consume(problems: [Problem]) throws { var problems = problems @@ -94,7 +94,7 @@ package struct _Deprecated: _DeprecatedConsumeP severity: .warning, identifier: "org.swift.docc.DeprecatedDiagnosticsDigets", summary: """ - The 'diagnostics.json' digest file is deprecated and will be removed after 6.2 is released. \ + The 'diagnostics.json' digest file is deprecated and will be removed after 6.3 is released. \ Pass a `--diagnostics-file ` to specify a custom location where DocC will write a diagnostics JSON file with more information. """) ), @@ -102,7 +102,7 @@ package struct _Deprecated: _DeprecatedConsumeP ) } - try consumer.consume(problems: problems) + try consumer._deprecated_consume(problems: problems) } } diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationBundle.swift b/Sources/SwiftDocC/Infrastructure/DocumentationBundle.swift index 353559d890..11cc3ef426 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationBundle.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationBundle.swift @@ -57,25 +57,10 @@ public struct DocumentationBundle { info.displayName } - @available(*, deprecated, renamed: "id", message: "Use 'id' instead. This deprecated API will be removed after 6.2 is released") - public var identifier: String { - id.rawValue - } - /// The documentation bundle's stable and locally unique identifier. public var id: DocumentationBundle.Identifier { info.id } - - /** - The documentation bundle's version. - - It's not safe to make computations based on assumptions about the format of bundle's version. The version can be in any format. - */ - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - public var version: String? { - info.version - } /// Symbol graph JSON input files for the module that's represented by this unit of documentation. /// @@ -152,19 +137,9 @@ public struct DocumentationBundle { /// Default path to resolve symbol links. public private(set) var documentationRootReference: ResolvedTopicReference - @available(*, deprecated, renamed: "tutorialTableOfContentsContainer", message: "Use 'tutorialTableOfContentsContainer' instead. This deprecated API will be removed after 6.2 is released") - public var tutorialsRootReference: ResolvedTopicReference { - tutorialTableOfContentsContainer - } - /// Default path to resolve tutorial table-of-contents links. public var tutorialTableOfContentsContainer: ResolvedTopicReference - @available(*, deprecated, renamed: "tutorialsContainerReference", message: "Use 'tutorialsContainerReference' instead. This deprecated API will be removed after 6.2 is released") - public var technologyTutorialsRootReference: ResolvedTopicReference { - tutorialsContainerReference - } - /// Default path to resolve tutorial links. public var tutorialsContainerReference: ResolvedTopicReference diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 83a9aabba4..cd7fca83d7 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -12,56 +12,6 @@ public import Foundation import Markdown import SymbolKit -/// A type that provides information about documentation bundles and their content. -@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") -public protocol DocumentationContextDataProvider { - /// An object to notify when bundles are added or removed. - var delegate: (any DocumentationContextDataProviderDelegate)? { get set } - - /// The documentation bundles that this data provider provides. - var bundles: [BundleIdentifier: DocumentationBundle] { get } - - /// Returns the data for the specified `url` in the provided `bundle`. - /// - /// - Parameters: - /// - url: The URL of the file to read. - /// - bundle: The bundle that the file is a part of. - /// - /// - Throws: When the file cannot be found in the workspace. - func contentsOfURL(_ url: URL, in bundle: DocumentationBundle) throws -> Data -} - -/// An object that responds to changes in available documentation bundles for a specific provider. -@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") -public protocol DocumentationContextDataProviderDelegate: AnyObject { - - /// Called when the `dataProvider` has added a new documentation bundle to its list of `bundles`. - /// - /// - Parameters: - /// - dataProvider: The provider that added this bundle. - /// - bundle: The bundle that was added. - /// - /// - Note: This method is called after the `dataProvider` has been added the bundle to its `bundles` property. - func dataProvider(_ dataProvider: any DocumentationContextDataProvider, didAddBundle bundle: DocumentationBundle) throws - - /// Called when the `dataProvider` has removed a documentation bundle from its list of `bundles`. - /// - /// - Parameters: - /// - dataProvider: The provider that removed this bundle. - /// - bundle: The bundle that was removed. - /// - /// - Note: This method is called after the `dataProvider` has been removed the bundle from its `bundles` property. - func dataProvider(_ dataProvider: any DocumentationContextDataProvider, didRemoveBundle bundle: DocumentationBundle) throws -} - -/// Documentation bundles use a string value as a unique identifier. -/// -/// This value is typically a reverse host name, for example: `com..`. -/// -/// Documentation links may include the bundle identifier---as a host component of the URL---to reference content in a specific documentation bundle. -@available(*, deprecated, renamed: "DocumentationBundle.Identifier", message: "Use 'DocumentationBundle.Identifier' instead. This deprecated API will be removed after 6.2 is released") -public typealias BundleIdentifier = String - /// The documentation context manages the in-memory model for the built documentation. /// /// A ``DocumentationWorkspace`` discovers serialized documentation bundles from a variety of sources (files on disk, databases, or web services), provides them to the `DocumentationContext`, @@ -116,51 +66,15 @@ public class DocumentationContext { /// A class that resolves documentation links by orchestrating calls to other link resolver implementations. public var linkResolver: LinkResolver - - private enum _Provider { - @available(*, deprecated, message: "Use 'DataProvider' instead. This deprecated API will be removed after 6.2 is released") - case legacy(any DocumentationContextDataProvider) - case new(any DataProvider) - } - private var dataProvider: _Provider - /// The provider of documentation bundles for this context. - @available(*, deprecated, message: "Use 'DataProvider' instead. This deprecated API will be removed after 6.2 is released") - private var _legacyDataProvider: (any DocumentationContextDataProvider)! { - get { - switch dataProvider { - case .legacy(let legacyDataProvider): - legacyDataProvider - case .new: - nil - } - } - set { - dataProvider = .legacy(newValue) - } - } - - func contentsOfURL(_ url: URL, in bundle: DocumentationBundle) throws -> Data { - switch dataProvider { - case .legacy(let legacyDataProvider): - return try legacyDataProvider.contentsOfURL(url, in: bundle) - case .new(let dataProvider): - assert(self.bundle?.id == bundle.id, "New code shouldn't pass unknown bundle identifiers to 'DocumentationContext.bundle(identifier:)'.") - return try dataProvider.contents(of: url) - } - } + /// The data provider that the context can use to read the contents of files that belong to ``bundle``. + let dataProvider: any DataProvider /// The documentation bundle that is registered with the context. - var bundle: DocumentationBundle? + let bundle: DocumentationBundle /// A collection of configuration for this context. - public package(set) var configuration: Configuration { - get { _configuration } - @available(*, deprecated, message: "Pass a configuration at initialization. This property will become read-only after Swift 6.2 is released.") - set { _configuration = newValue } - } - // Having a deprecated setter above requires a computed property. - private var _configuration: Configuration + public let configuration: Configuration /// The graph of all the documentation content and their relationships to each other. /// @@ -173,11 +87,6 @@ public class DocumentationContext { /// The set of all manually curated references if `shouldStoreManuallyCuratedReferences` was true at the time of processing and has remained `true` since.. Nil if curation has not been processed yet. public private(set) var manuallyCuratedReferences: Set? - @available(*, deprecated, renamed: "tutorialTableOfContentsReferences", message: "Use 'tutorialTableOfContentsReferences' This deprecated API will be removed after 6.2 is released") - public var rootTechnologies: [ResolvedTopicReference] { - tutorialTableOfContentsReferences - } - /// The tutorial table-of-contents nodes in the topic graph. public var tutorialTableOfContentsReferences: [ResolvedTopicReference] { return topicGraph.nodes.values.compactMap { node in @@ -285,31 +194,6 @@ public class DocumentationContext { /// Mentions of symbols within articles. var articleSymbolMentions = ArticleSymbolMentions() - /// Initializes a documentation context with a given `dataProvider` and registers all the documentation bundles that it provides. - /// - /// - Parameters: - /// - dataProvider: The data provider to register bundles from. - /// - diagnosticEngine: The pre-configured engine that will collect problems encountered during compilation. - /// - configuration: A collection of configuration for the created context. - /// - Throws: If an error is encountered while registering a documentation bundle. - @available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") - public init( - dataProvider: any DocumentationContextDataProvider, - diagnosticEngine: DiagnosticEngine = .init(), - configuration: Configuration = .init() - ) throws { - self.dataProvider = .legacy(dataProvider) - self.diagnosticEngine = diagnosticEngine - self._configuration = configuration - self.linkResolver = LinkResolver(dataProvider: FileManager.default) - - _legacyDataProvider.delegate = self - - for bundle in dataProvider.bundles.values { - try register(bundle) - } - } - /// Initializes a documentation context with a given `bundle`. /// /// - Parameters: @@ -325,77 +209,26 @@ public class DocumentationContext { configuration: Configuration = .init() ) async throws { self.bundle = bundle - self.dataProvider = .new(dataProvider) + self.dataProvider = dataProvider self.diagnosticEngine = diagnosticEngine - self._configuration = configuration + self.configuration = configuration self.linkResolver = LinkResolver(dataProvider: dataProvider) ResolvedTopicReference.enableReferenceCaching(for: bundle.id) try register(bundle) } - - /// Respond to a new `bundle` being added to the `dataProvider` by registering it. - /// - /// - Parameters: - /// - dataProvider: The provider that added this bundle. - /// - bundle: The bundle that was added. - @available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") - public func dataProvider(_ dataProvider: any DocumentationContextDataProvider, didAddBundle bundle: DocumentationBundle) throws { - try benchmark(wrap: Benchmark.Duration(id: "bundle-registration")) { - // Enable reference caching for this documentation bundle. - ResolvedTopicReference.enableReferenceCaching(for: bundle.id) - - try self.register(bundle) - } - } - - /// Respond to a new `bundle` being removed from the `dataProvider` by unregistering it. - /// - /// - Parameters: - /// - dataProvider: The provider that removed this bundle. - /// - bundle: The bundle that was removed. - @available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") - public func dataProvider(_ dataProvider: any DocumentationContextDataProvider, didRemoveBundle bundle: DocumentationBundle) throws { - linkResolver.localResolver?.unregisterBundle(identifier: bundle.id) - - // Purge the reference cache for this bundle and disable reference caching for - // this bundle moving forward. - ResolvedTopicReference.purgePool(for: bundle.id) - - unregister(bundle) - } - - /// The documentation bundles that are currently registered with the context. - @available(*, deprecated, message: "Use 'bundle' instead. This deprecated API will be removed after 6.2 is released") - public var registeredBundles: some Collection { - _registeredBundles - } - - /// Returns the `DocumentationBundle` with the given `identifier` if it's registered with the context, otherwise `nil`. - @available(*, deprecated, message: "Use 'bundle' instead. This deprecated API will be removed after 6.2 is released") - public func bundle(identifier: String) -> DocumentationBundle? { - _bundle(identifier: identifier) - } // Remove these when removing `registeredBundles` and `bundle(identifier:)`. // These exist so that internal code that need to be compatible with legacy data providers can access the bundles without deprecation warnings. + @available(*, deprecated, renamed: "bundle", message: "REMOVE THIS") var _registeredBundles: [DocumentationBundle] { - switch dataProvider { - case .legacy(let legacyDataProvider): - Array(legacyDataProvider.bundles.values) - case .new: - bundle.map { [$0] } ?? [] - } + [bundle] } + @available(*, deprecated, renamed: "bundle", message: "REMOVE THIS") func _bundle(identifier: String) -> DocumentationBundle? { - switch dataProvider { - case .legacy(let legacyDataProvider): - return legacyDataProvider.bundles[identifier] - case .new: - assert(bundle?.id.rawValue == identifier, "New code shouldn't pass unknown bundle identifiers to 'DocumentationContext.bundle(identifier:)'.") - return bundle?.id.rawValue == identifier ? bundle : nil - } + assert(bundle.id.rawValue == identifier, "New code shouldn't pass unknown bundle identifiers to 'DocumentationContext.bundle(identifier:)'.") + return bundle.id.rawValue == identifier ? bundle : nil } /// Perform semantic analysis on a given `document` at a given `source` location and append any problems found to `problems`. @@ -900,7 +733,7 @@ public class DocumentationContext { guard decodeError.sync({ $0 == nil }) else { return } do { - let data = try contentsOfURL(url, in: bundle) + let data = try dataProvider.contents(of: url) let source = String(decoding: data, as: UTF8.self) let document = Document(parsing: source, source: url, options: [.parseBlockDirectives, .parseSymbolLinks]) @@ -1468,9 +1301,6 @@ public class DocumentationContext { private func shouldContinueRegistration() throws { try Task.checkCancellation() - guard isRegistrationEnabled.sync({ $0 }) else { - throw ContextError.registrationDisabled - } } /// Builds in-memory relationships between symbols based on the relationship information in a given symbol graph file. @@ -1847,11 +1677,6 @@ public class DocumentationContext { registeredAssets(withExtensions: DocumentationContext.supportedImageExtensions, forBundleID: bundleID) } - @available(*, deprecated, renamed: "registeredImageAssets(for:)", message: "registeredImageAssets(for:)' instead. This deprecated API will be removed after 6.2 is released") - public func registeredImageAssets(forBundleID bundleIdentifier: BundleIdentifier) -> [DataAsset] { - registeredImageAssets(for: DocumentationBundle.Identifier(rawValue: bundleIdentifier)) - } - /// Returns a list of all the video assets that registered for a given `bundleIdentifier`. /// /// - Parameter bundleID: The identifier of the bundle to return video assets for. @@ -1860,11 +1685,6 @@ public class DocumentationContext { registeredAssets(withExtensions: DocumentationContext.supportedVideoExtensions, forBundleID: bundleID) } - @available(*, deprecated, renamed: "registeredVideoAssets(for:)", message: "registeredImageAssets(for:)' instead. This deprecated API will be removed after 6.2 is released") - public func registeredVideoAssets(forBundleID bundleIdentifier: BundleIdentifier) -> [DataAsset] { - registeredVideoAssets(for: DocumentationBundle.Identifier(rawValue: bundleIdentifier)) - } - /// Returns a list of all the download assets that registered for a given `bundleIdentifier`. /// /// - Parameter bundleID: The identifier of the bundle to return download assets for. @@ -1873,11 +1693,6 @@ public class DocumentationContext { registeredAssets(inContexts: [DataAsset.Context.download], forBundleID: bundleID) } - @available(*, deprecated, renamed: "registeredDownloadsAssets(for:)", message: "registeredDownloadsAssets(for:)' instead. This deprecated API will be removed after 6.2 is released") - public func registeredDownloadsAssets(forBundleID bundleIdentifier: BundleIdentifier) -> [DataAsset] { - registeredDownloadsAssets(for: DocumentationBundle.Identifier(rawValue: bundleIdentifier)) - } - typealias Articles = [DocumentationContext.SemanticResult
] private typealias ArticlesTuple = (articles: Articles, rootPageArticles: Articles) @@ -1915,18 +1730,6 @@ public class DocumentationContext { } } - /// When `true` bundle registration will be cancelled asap. - private var isRegistrationEnabled = Synchronized(true) - - /// Enables or disables bundle registration. - /// - /// When given `false` the context will try to cancel as quick as possible - /// any ongoing bundle registrations. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - public func setRegistrationEnabled(_ value: Bool) { - isRegistrationEnabled.sync({ $0 = value }) - } - /// Adds articles that are not root pages to the documentation cache. /// /// This method adds all of the `articles` to the documentation cache and inserts a node representing @@ -2220,7 +2023,7 @@ public class DocumentationContext { discoveryGroup.async(queue: discoveryQueue) { [unowned self] in symbolGraphLoader = SymbolGraphLoader( bundle: bundle, - dataLoader: { try self.contentsOfURL($0, in: $1) }, + dataProvider: dataProvider, symbolGraphTransformer: configuration.convertServiceConfiguration.symbolGraphTransformer ) @@ -2654,14 +2457,6 @@ public class DocumentationContext { /// A closure type getting the information about a reference in a context and returns any possible problems with it. public typealias ReferenceCheck = (DocumentationContext, ResolvedTopicReference) -> [Problem] - /// Adds new checks to be run during the global topic analysis; after a bundle has been fully registered and its topic graph has been fully built. - /// - /// - Parameter newChecks: The new checks to add. - @available(*, deprecated, message: "Use 'TopicAnalysisConfiguration.additionalChecks' instead. This deprecated API will be removed after 6.2 is released") - public func addGlobalChecks(_ newChecks: [ReferenceCheck]) { - configuration.topicAnalysisConfiguration.additionalChecks.append(contentsOf: newChecks) - } - /// Crawls the hierarchy of the given list of nodes, adding relationships in the topic graph for all resolvable task group references. /// - Parameters: /// - references: A list of references to crawl. @@ -2871,15 +2666,13 @@ public class DocumentationContext { - Throws: ``ContextError/notFound(_:)` if a resource with the given was not found. */ public func resource(with identifier: ResourceReference, trait: DataTraitCollection = .init()) throws -> Data { - guard let bundle, - let assetManager = assetManagers[identifier.bundleID], - let asset = assetManager.allData(named: identifier.path) else { + guard let asset = assetManagers[identifier.bundleID]?.allData(named: identifier.path) else { throw ContextError.notFound(identifier.url) } let resource = asset.data(bestMatching: trait) - return try contentsOfURL(resource.url, in: bundle) + return try dataProvider.contents(of: resource.url) } /// Returns true if a resource with the given identifier exists in the registered bundle. @@ -3349,6 +3142,3 @@ extension DataAsset { } } } - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension DocumentationContext: DocumentationContextDataProviderDelegate {} diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift b/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift deleted file mode 100644 index 9b0cd3a86e..0000000000 --- a/Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift +++ /dev/null @@ -1,486 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - 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 - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -public import Foundation - -/// A converter from a documentation bundle to an output that can be consumed by a renderer. -/// -/// This protocol is primarily used for injecting mock documentation converters during testing. -/// -/// ## See Also -/// -/// - ``DocumentationConverter`` -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -public protocol DocumentationConverterProtocol { - /// Converts documentation, outputting products using the given output consumer. - /// - Parameter outputConsumer: The output consumer for content produced during conversion. - /// - Returns: The problems emitted during analysis of the documentation bundle and during conversion. - /// - Throws: Throws an error if the conversion process was not able to start at all, for example if the bundle could not be read. - /// Partial failures, such as failing to consume a single render node, are returned in the `conversionProblems` component - /// of the returned tuple. - mutating func convert( - outputConsumer: some ConvertOutputConsumer - ) throws -> (analysisProblems: [Problem], conversionProblems: [Problem]) -} - -/// A converter from a documentation bundle to an output that can be consumed by a renderer. -/// -/// A documentation converter analyzes a documentation bundle and converts it to products that can be used by a documentation -/// renderer to render documentation. The output format of the conversion is controlled by a ``ConvertOutputConsumer``, which -/// determines what to do with the conversion products, for example, write them to disk. -/// -/// You can also configure the documentation converter to emit extra metadata such as linkable entities and indexing records -/// information. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -public struct DocumentationConverter: DocumentationConverterProtocol { - let rootURL: URL? - let emitDigest: Bool - let documentationCoverageOptions: DocumentationCoverageOptions - let bundleDiscoveryOptions: BundleDiscoveryOptions - let diagnosticEngine: DiagnosticEngine - - private(set) var context: DocumentationContext - private let workspace: DocumentationWorkspace - private var currentDataProvider: (any DocumentationWorkspaceDataProvider)? - private var dataProvider: any DocumentationWorkspaceDataProvider - - /// An optional closure that sets up a context before the conversion begins. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - public var setupContext: ((inout DocumentationContext) -> Void)? - - /// Conversion batches should be big enough to keep all cores busy but small enough not to keep - /// around too many async blocks that update the conversion results. After running some tests it - /// seems that more than couple hundred of a batch size doesn't bring more performance CPU-wise - /// and it's a fair amount of async tasks to keep in memory before draining the results queue - /// after the batch is converted. - var batchNodeCount = 1 - - /// The external IDs of the symbols to convert. - /// - /// Use this property to indicate what symbol documentation nodes should be converted. When ``externalIDsToConvert`` - /// and ``documentationPathsToConvert`` are both set, the documentation nodes that are in either arrays will be - /// converted. - /// - /// If you want all the symbol render nodes to be returned as part of the conversion's response, set this property to `nil`. - /// For Swift, the external ID of the symbol is its USR. - var externalIDsToConvert: [String]? - - /// The paths of the documentation nodes to convert. - /// - /// Use this property to indicate what documentation nodes should be converted. When ``externalIDsToConvert`` - /// and ``documentationPathsToConvert`` are both set, the documentation nodes that are in either arrays will be - /// converted. - /// - /// If you want all the render nodes to be returned as part of the conversion's response, set this property to `nil`. - var documentPathsToConvert: [String]? - - /// Whether the documentation converter should include source file - /// location metadata in any render nodes representing symbols it creates. - /// - /// Before setting this value to `true` please confirm that your use case doesn't include - /// public distribution of any created render nodes as there are filesystem privacy and security - /// concerns with distributing this data. - var shouldEmitSymbolSourceFileURIs: Bool - - /// Whether the documentation converter should include access level information for symbols. - var shouldEmitSymbolAccessLevels: Bool - - /// The source repository where the documentation's sources are hosted. - var sourceRepository: SourceRepository? - - /// Whether the documentation converter should write documentation extension files containing markdown representations of DocC's automatic curation into the source documentation catalog. - var experimentalModifyCatalogWithGeneratedCuration: Bool - - /// The identifiers and access level requirements for symbols that have an expanded version of their documentation page if the requirements are met - var symbolIdentifiersWithExpandedDocumentation: [String: ConvertRequest.ExpandedDocumentationRequirements]? = nil - - /// `true` if the conversion is cancelled. - private var isCancelled: Synchronized? = nil - - private var processingDurationMetric: Benchmark.Duration? - - /// Creates a documentation converter given a documentation bundle's URL. - /// - /// - Parameters: - /// - documentationBundleURL: The root URL of the documentation bundle to convert. - /// - emitDigest: Whether the conversion should create metadata files, such as linkable entities information. - /// - documentationCoverageOptions: What level of documentation coverage output should be emitted. - /// - currentPlatforms: The current version and beta information for platforms that may be encountered while processing symbol graph files. - /// - workspace: A provided documentation workspace. Creates a new empty workspace if value is `nil`. - /// - context: A provided documentation context. - /// - dataProvider: A data provider to use when registering bundles. - /// - externalIDsToConvert: The external IDs of the documentation nodes to convert. - /// - documentPathsToConvert: The paths of the documentation nodes to convert. - /// - bundleDiscoveryOptions: Options to configure how the converter discovers documentation bundles. - /// - emitSymbolSourceFileURIs: Whether the documentation converter should include - /// source file location metadata in any render nodes representing symbols it creates. - /// - /// Before passing `true` please confirm that your use case doesn't include public - /// distribution of any created render nodes as there are filesystem privacy and security - /// concerns with distributing this data. - /// - emitSymbolAccessLevels: Whether the documentation converter should include access level information for symbols. - /// - sourceRepository: The source repository where the documentation's sources are hosted. - /// - isCancelled: A wrapped boolean value used for the caller to cancel converting the documentation. - /// that have an expanded version of their documentation page if the access level requirement is met. - /// - diagnosticEngine: The diagnostic engine that collects any problems encountered from converting the documentation. - /// - symbolIdentifiersWithExpandedDocumentation: Identifiers and access level requirements for symbols - /// - experimentalModifyCatalogWithGeneratedCuration: Whether the documentation converter should write documentation extension files containing markdown representations of DocC's automatic curation into the source documentation catalog. - public init( - documentationBundleURL: URL?, - emitDigest: Bool, - documentationCoverageOptions: DocumentationCoverageOptions, - currentPlatforms: [String : PlatformVersion]?, - workspace: DocumentationWorkspace, - context: DocumentationContext, - dataProvider: any DocumentationWorkspaceDataProvider, - externalIDsToConvert: [String]? = nil, - documentPathsToConvert: [String]? = nil, - bundleDiscoveryOptions: BundleDiscoveryOptions, - emitSymbolSourceFileURIs: Bool = false, - emitSymbolAccessLevels: Bool = false, - sourceRepository: SourceRepository? = nil, - isCancelled: Synchronized? = nil, - diagnosticEngine: DiagnosticEngine = .init(), - symbolIdentifiersWithExpandedDocumentation: [String: ConvertRequest.ExpandedDocumentationRequirements]? = nil, - experimentalModifyCatalogWithGeneratedCuration: Bool = false - ) { - self.rootURL = documentationBundleURL - self.emitDigest = emitDigest - self.documentationCoverageOptions = documentationCoverageOptions - self.workspace = workspace - self.context = context - self.dataProvider = dataProvider - self.externalIDsToConvert = externalIDsToConvert - self.documentPathsToConvert = documentPathsToConvert - self.bundleDiscoveryOptions = bundleDiscoveryOptions - self.shouldEmitSymbolSourceFileURIs = emitSymbolSourceFileURIs - self.shouldEmitSymbolAccessLevels = emitSymbolAccessLevels - self.sourceRepository = sourceRepository - self.isCancelled = isCancelled - self.diagnosticEngine = diagnosticEngine - self.symbolIdentifiersWithExpandedDocumentation = symbolIdentifiersWithExpandedDocumentation - self.experimentalModifyCatalogWithGeneratedCuration = experimentalModifyCatalogWithGeneratedCuration - } - - /// Returns the first bundle in the source directory, if any. - /// > Note: The result of this function is not cached, it reads the source directory and finds all bundles. - public func firstAvailableBundle() -> DocumentationBundle? { - return (try? dataProvider.bundles(options: bundleDiscoveryOptions)).map(sorted(bundles:))?.first - } - - /// Sorts a list of bundles by the bundle identifier. - private func sorted(bundles: [DocumentationBundle]) -> [DocumentationBundle] { - return bundles.sorted(by: \.identifier) - } - - mutating public func convert( - outputConsumer: some ConvertOutputConsumer - ) throws -> (analysisProblems: [Problem], conversionProblems: [Problem]) { - defer { - diagnosticEngine.flush() - } - - // Unregister the current file data provider and all its bundles - // when running repeated conversions. - if let dataProvider = self.currentDataProvider { - try workspace.unregisterProvider(dataProvider) - } - - let context = self.context - - // Start bundle registration - try workspace.registerProvider(dataProvider, options: bundleDiscoveryOptions) - self.currentDataProvider = dataProvider - - // If cancelled, return early before we emit diagnostics. - func isConversionCancelled() -> Bool { - Task.isCancelled || isCancelled?.sync({ $0 }) == true - } - guard !isConversionCancelled() else { return ([], []) } - - processingDurationMetric = benchmark(begin: Benchmark.Duration(id: "documentation-processing")) - - let bundles = try sorted(bundles: dataProvider.bundles(options: bundleDiscoveryOptions)) - guard !bundles.isEmpty else { - if let rootURL { - throw Error.doesNotContainBundle(url: rootURL) - } else { - try (_Deprecated(outputConsumer) as (any _DeprecatedConsumeProblemsAccess))._consume(problems: context.problems) - throw GeneratedDataProvider.Error.notEnoughDataToGenerateBundle(options: bundleDiscoveryOptions, underlyingError: nil) - } - } - - // For now, we only support one bundle. - let bundle = bundles.first! - - if experimentalModifyCatalogWithGeneratedCuration, let catalogURL = rootURL { - let writer = GeneratedCurationWriter(context: context, catalogURL: catalogURL, outputURL: catalogURL) - let curation = try writer.generateDefaultCurationContents() - for (url, updatedContent) in curation { - guard let data = updatedContent.data(using: .utf8) else { continue } - try? FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil) - try? data.write(to: url, options: .atomic) - } - } - - guard !context.problems.containsErrors else { - if emitDigest { - try (_Deprecated(outputConsumer) as (any _DeprecatedConsumeProblemsAccess))._consume(problems: context.problems) - } - return (analysisProblems: context.problems, conversionProblems: []) - } - - // Precompute the render context - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - - try outputConsumer.consume(renderReferenceStore: renderContext.store) - - // Copy images, sample files, and other static assets. - try outputConsumer.consume(assetsInBundle: bundle) - - let symbolIdentifiersMeetingRequirementsForExpandedDocumentation: [String]? = symbolIdentifiersWithExpandedDocumentation?.compactMap { (identifier, expandedDocsRequirement) -> String? in - guard let documentationNode = context.documentationCache[identifier] else { - return nil - } - - return documentationNode.meetsExpandedDocumentationRequirements(expandedDocsRequirement) ? identifier : nil - } - - let converter = DocumentationContextConverter( - bundle: bundle, - context: context, - renderContext: renderContext, - emitSymbolSourceFileURIs: shouldEmitSymbolSourceFileURIs, - emitSymbolAccessLevels: shouldEmitSymbolAccessLevels, - sourceRepository: sourceRepository, - symbolIdentifiersWithExpandedDocumentation: symbolIdentifiersMeetingRequirementsForExpandedDocumentation - ) - - var indexingRecords = [IndexingRecord]() - var linkSummaries = [LinkDestinationSummary]() - var assets = [RenderReferenceType : [any RenderReference]]() - - let references = context.knownPages - let resultsSyncQueue = DispatchQueue(label: "Convert Serial Queue", qos: .unspecified, attributes: []) - let resultsGroup = DispatchGroup() - - var coverageInfo = [CoverageDataEntry]() - // No need to generate this closure more than once. - let coverageFilterClosure = documentationCoverageOptions.generateFilterClosure() - - // Process render nodes in batches allowing us to release memory and sync after each batch - // Keep track of any problems in case emitDigest == true - var conversionProblems: [Problem] = references.concurrentPerform { identifier, results in - // If cancelled skip all concurrent conversion work in this block. - guard !isConversionCancelled() else { return } - - // Wrap JSON encoding in an autorelease pool to avoid retaining the autoreleased ObjC objects returned by `JSONSerialization` - autoreleasepool { - do { - let entity = try context.entity(with: identifier) - - guard shouldConvertEntity(entity: entity, identifier: identifier) else { - return - } - - guard let renderNode = converter.renderNode(for: entity) else { - // No render node was produced for this entity, so just skip it. - return - } - - try outputConsumer.consume(renderNode: renderNode) - - switch documentationCoverageOptions.level { - case .detailed, .brief: - let coverageEntry = try CoverageDataEntry( - documentationNode: entity, - renderNode: renderNode, - context: context - ) - if coverageFilterClosure(coverageEntry) { - resultsGroup.async(queue: resultsSyncQueue) { - coverageInfo.append(coverageEntry) - } - } - case .none: - break - } - - if emitDigest { - let nodeLinkSummaries = entity.externallyLinkableElementSummaries(context: context, renderNode: renderNode, includeTaskGroups: true) - let nodeIndexingRecords = try renderNode.indexingRecords(onPage: identifier) - - resultsGroup.async(queue: resultsSyncQueue) { - assets.merge(renderNode.assetReferences, uniquingKeysWith: +) - linkSummaries.append(contentsOf: nodeLinkSummaries) - indexingRecords.append(contentsOf: nodeIndexingRecords) - } - } else if FeatureFlags.current.isExperimentalLinkHierarchySerializationEnabled { - let nodeLinkSummaries = entity.externallyLinkableElementSummaries(context: context, renderNode: renderNode, includeTaskGroups: false) - - resultsGroup.async(queue: resultsSyncQueue) { - linkSummaries.append(contentsOf: nodeLinkSummaries) - } - } - } catch { - recordProblem(from: error, in: &results, withIdentifier: "render-node") - } - } - } - - // Wait for any concurrent updates to complete. - resultsGroup.wait() - - // If cancelled, return before producing outputs. - guard !isConversionCancelled() else { return ([], []) } - - // Write various metadata - if emitDigest { - do { - try outputConsumer.consume(linkableElementSummaries: linkSummaries) - try outputConsumer.consume(indexingRecords: indexingRecords) - try outputConsumer.consume(assets: assets) - } catch { - recordProblem(from: error, in: &conversionProblems, withIdentifier: "metadata") - } - } - - if FeatureFlags.current.isExperimentalLinkHierarchySerializationEnabled { - do { - let serializableLinkInformation = try context.linkResolver.localResolver.prepareForSerialization(bundleID: bundle.id) - try outputConsumer.consume(linkResolutionInformation: serializableLinkInformation) - - if !emitDigest { - try outputConsumer.consume(linkableElementSummaries: linkSummaries) - } - } catch { - recordProblem(from: error, in: &conversionProblems, withIdentifier: "link-resolver") - } - } - - if emitDigest { - do { - try (_Deprecated(outputConsumer) as (any _DeprecatedConsumeProblemsAccess))._consume(problems: context.problems + conversionProblems) - } catch { - recordProblem(from: error, in: &conversionProblems, withIdentifier: "problems") - } - } - - switch documentationCoverageOptions.level { - case .detailed, .brief: - do { - try outputConsumer.consume(documentationCoverageInfo: coverageInfo) - } catch { - recordProblem(from: error, in: &conversionProblems, withIdentifier: "coverage") - } - case .none: - break - } - - try outputConsumer.consume( - buildMetadata: BuildMetadata( - bundleDisplayName: bundle.displayName, - bundleIdentifier: bundle.identifier - ) - ) - - // Log the duration of the processing (after the bundle content finished registering). - benchmark(end: processingDurationMetric) - // Log the finalized topic graph checksum. - benchmark(add: Benchmark.TopicGraphHash(context: context)) - // Log the finalized list of topic anchor sections. - benchmark(add: Benchmark.TopicAnchorHash(context: context)) - // Log the finalized external topics checksum. - benchmark(add: Benchmark.ExternalTopicsHash(context: context)) - // Log the peak memory. - benchmark(add: Benchmark.PeakMemory()) - - return (analysisProblems: context.problems, conversionProblems: conversionProblems) - } - - /// Whether the given entity should be converted to a render node. - private func shouldConvertEntity( - entity: DocumentationNode, - identifier: ResolvedTopicReference - ) -> Bool { - let isDocumentPathToConvert: Bool - if let documentPathsToConvert { - isDocumentPathToConvert = documentPathsToConvert.contains(identifier.path) - } else { - isDocumentPathToConvert = true - } - - let isExternalIDToConvert: Bool - if let externalIDsToConvert { - isExternalIDToConvert = entity.symbol.map { - externalIDsToConvert.contains($0.identifier.precise) - } == true - } else { - isExternalIDToConvert = true - } - - // If the identifier of the entity is neither in `documentPathsToConvert` - // nor `externalIDsToConvert`, we don't convert it to a render node. - return isDocumentPathToConvert || isExternalIDToConvert - } - - /// Record a problem from the given error in the given problem array. - /// - /// Creates a ``Problem`` from the given `Error` and identifier, emits it to the - /// ``DocumentationConverter``'s ``DiagnosticEngine``, and appends it to the given - /// problem array. - /// - /// - Parameters: - /// - error: The error that describes the problem. - /// - problems: The array that the created problem should be appended to. - /// - identifier: A unique identifier the problem. - private func recordProblem( - from error: any Swift.Error, - in problems: inout [Problem], - withIdentifier identifier: String - ) { - let singleDiagnostic = Diagnostic( - source: nil, - severity: .error, - range: nil, - identifier: "org.swift.docc.documentation-converter.\(identifier)", - summary: error.localizedDescription - ) - let problem = Problem(diagnostic: singleDiagnostic, possibleSolutions: []) - - diagnosticEngine.emit(problem) - problems.append(problem) - } - - enum Error: DescribedError, Equatable { - case doesNotContainBundle(url: URL) - - var errorDescription: String { - switch self { - case .doesNotContainBundle(let url): - return """ - The directory at '\(url)' and its subdirectories do not contain at least one \ - valid documentation bundle. A documentation bundle is a directory ending in \ - `.docc`. - Pass `--allow-arbitrary-catalog-directories` flag to convert a directory \ - without a `.docc` extension. - """ - } - } - } -} - -extension DocumentationNode { - func meetsExpandedDocumentationRequirements(_ requirements: ConvertRequest.ExpandedDocumentationRequirements) -> Bool { - guard let symbol else { return false } - - return requirements.accessControlLevels.contains(symbol.accessLevel.rawValue) && (!symbol.names.title.starts(with: "_") || requirements.canBeUnderscored) - } -} diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift index fa3666edde..e4a737fa75 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.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 @@ -51,11 +51,6 @@ public import SymbolKit public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalExternalSymbolResolver { private let externalLinkResolvingClient: any ExternalLinkResolving - @available(*, deprecated, renamed: "id", message: "Use 'id' instead. This deprecated API will be removed after 6.2 is released") - public var bundleIdentifier: String { - bundleID.rawValue - } - /// The bundle identifier for the reference resolver in the other process. public let bundleID: DocumentationBundle.Identifier @@ -85,13 +80,6 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE self.bundleID = .init(rawValue: decodedBundleIdentifier) self.externalLinkResolvingClient = longRunningProcess } - - @available(*, deprecated, renamed: "init(bundleID:server:convertRequestIdentifier:)", message: "Use 'init(bundleID:server:convertRequestIdentifier:)' instead. This deprecated API will be removed after 6.2 is released") - public init(bundleIdentifier: String, server: DocumentationServer, convertRequestIdentifier: String?) throws { - self.bundleID = .init(rawValue: bundleIdentifier) - self.externalLinkResolvingClient = LongRunningService( - server: server, convertRequestIdentifier: convertRequestIdentifier) - } /// Creates a new reference resolver that interacts with a documentation service. /// diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift index 0ec34d9b4d..e7b44eb7d5 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -93,7 +93,8 @@ public class LinkResolver { // Check if this is a link to an external documentation source that should have previously been resolved in `DocumentationContext.preResolveExternalLinks(...)` if let bundleID = unresolvedReference.bundleID, - !context._registeredBundles.contains(where: { $0.id == bundleID || urlReadablePath($0.displayName) == bundleID.rawValue }) + context.bundle.id != bundleID, + urlReadablePath(context.bundle.displayName) != bundleID.rawValue { return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo("No external resolver registered for '\(bundleID)'.")) } @@ -171,9 +172,8 @@ private final class FallbackResolverBasedLinkResolver { // Check if a fallback reference resolver should resolve this let referenceBundleID = unresolvedReference.bundleID ?? parent.bundleID guard let fallbackResolver = context.configuration.convertServiceConfiguration.fallbackResolver, - // This uses an underscored internal variant of `registeredBundles` to avoid deprecation warnings and remain compatible with legacy data providers. - let knownBundleID = context._registeredBundles.first(where: { $0.id == referenceBundleID || urlReadablePath($0.displayName) == referenceBundleID.rawValue })?.id, - fallbackResolver.bundleID == knownBundleID + fallbackResolver.bundleID == context.bundle.id, + context.bundle.id == referenceBundleID || urlReadablePath(context.bundle.displayName) == referenceBundleID.rawValue else { return nil } @@ -191,8 +191,7 @@ private final class FallbackResolverBasedLinkResolver { ) allCandidateURLs.append(alreadyResolved.url) - // This uses an underscored internal variant of `bundle(identifier:)` to avoid deprecation warnings and remain compatible with legacy data providers. - let currentBundle = context._bundle(identifier: knownBundleID.rawValue)! + let currentBundle = context.bundle if !isCurrentlyResolvingSymbolLink { // First look up articles path allCandidateURLs.append(contentsOf: [ diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy.swift index be9144cc7e..d4891efab4 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy.swift @@ -718,23 +718,6 @@ extension PathHierarchy { } } -// MARK: Removing nodes - -extension PathHierarchy { - // When unregistering a documentation bundle from a context, entries for that bundle should no longer be findable. - // The below implementation marks nodes as "not findable" while leaving them in the hierarchy so that they can be - // traversed. - // This would be problematic if it happened repeatedly but in practice the path hierarchy will only be in this state - // after unregistering a data provider until a new data provider is registered. - - /// Removes a node from the path hierarchy so that it can no longer be found. - /// - Parameter id: The unique identifier for the node. - mutating func removeNodeWithID(_ id: ResolvedIdentifier) { - // Remove the node from the lookup and unset its identifier - lookup.removeValue(forKey: id)!.identifier = nil - } -} - // MARK: Disambiguation container extension PathHierarchy { diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift index 3c70f61124..4dc85a2968 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.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 @@ -24,19 +24,6 @@ final class PathHierarchyBasedLinkResolver { self.pathHierarchy = pathHierarchy } - /// Remove all matches from a given documentation bundle from the link resolver. - func unregisterBundle(identifier: DocumentationBundle.Identifier) { - var newMap = BidirectionalMap() - for (id, reference) in resolvedReferenceMap { - if reference.bundleID == identifier { - pathHierarchy.removeNodeWithID(id) - } else { - newMap[id] = reference - } - } - resolvedReferenceMap = newMap - } - /// Creates a path string---that can be used to find documentation in the path hierarchy---from an unresolved topic reference, private static func path(for unresolved: UnresolvedTopicReference) -> String { guard let fragment = unresolved.fragment else { diff --git a/Sources/SwiftDocC/Infrastructure/NodeURLGenerator.swift b/Sources/SwiftDocC/Infrastructure/NodeURLGenerator.swift index cac3ab219c..f58a4a32cc 100644 --- a/Sources/SwiftDocC/Infrastructure/NodeURLGenerator.swift +++ b/Sources/SwiftDocC/Infrastructure/NodeURLGenerator.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 @@ -55,8 +55,6 @@ public struct NodeURLGenerator { case documentationCuration(parentPath: String, articleName: String) case article(bundleName: String, articleName: String) case tutorialTableOfContents(name: String) - @available(*, deprecated, renamed: "tutorialTableOfContents(name:)", message: "Use 'tutorialTableOfContents(name:)' instead. This deprecated API will be removed after 6.2 is released") - case technology(technologyName: String) case tutorial(bundleName: String, tutorialName: String) /// A URL safe path under the given root path. @@ -94,8 +92,7 @@ public struct NodeURLGenerator { isDirectory: false ) .path - case .technology(let name), - .tutorialTableOfContents(let name): + case .tutorialTableOfContents(let name): // Format: "/tutorials/Name" return Self.tutorialsFolderURL .appendingPathComponent( diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift index 1975f58ea1..4418b6ad60 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift @@ -19,23 +19,22 @@ struct SymbolGraphLoader { private(set) var symbolGraphs: [URL: SymbolKit.SymbolGraph] = [:] private(set) var unifiedGraphs: [String: SymbolKit.UnifiedSymbolGraph] = [:] private(set) var graphLocations: [String: [SymbolKit.GraphCollector.GraphKind]] = [:] - // FIXME: After 6.2, when we no longer have `DocumentationContextDataProvider` we can simply this code to not use a closure to read data. - private var dataLoader: (URL, DocumentationBundle) throws -> Data - private var bundle: DocumentationBundle - private var symbolGraphTransformer: ((inout SymbolGraph) -> ())? = nil + private let dataProvider: any DataProvider + private let bundle: DocumentationBundle + private let symbolGraphTransformer: ((inout SymbolGraph) -> ())? /// Creates a new symbol graph loader /// - Parameters: /// - bundle: The documentation bundle from which to load symbol graphs. - /// - dataLoader: A closure that the loader uses to read symbol graph data. + /// - dataProvider: A provider that the loader uses to read symbol graph data. /// - symbolGraphTransformer: An optional closure that transforms the symbol graph after the loader decodes it. init( bundle: DocumentationBundle, - dataLoader: @escaping (URL, DocumentationBundle) throws -> Data, + dataProvider: any DataProvider, symbolGraphTransformer: ((inout SymbolGraph) -> ())? = nil ) { self.bundle = bundle - self.dataLoader = dataLoader + self.dataProvider = dataProvider self.symbolGraphTransformer = symbolGraphTransformer } @@ -61,13 +60,13 @@ struct SymbolGraphLoader { var loadedGraphs = [URL: (usesExtensionSymbolFormat: Bool?, graph: SymbolKit.SymbolGraph)]() var loadError: (any Error)? - let loadGraphAtURL: (URL) -> Void = { [dataLoader, bundle] symbolGraphURL in + let loadGraphAtURL: (URL) -> Void = { [dataProvider] symbolGraphURL in // Bail out in case a symbol graph has already errored guard loadingLock.sync({ loadError == nil }) else { return } do { // Load and decode a single symbol graph file - let data = try dataLoader(symbolGraphURL, bundle) + let data = try dataProvider.contents(of: symbolGraphURL) var symbolGraph: SymbolGraph diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift index d9b806b61a..28d4490c80 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.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 @@ -18,18 +18,9 @@ extension DocumentationBundle { /// The display name of the bundle. public var displayName: String - @available(*, deprecated, renamed: "id", message: "Use 'id' instead. This deprecated API will be removed after 6.2 is released") - public var identifier: String { - id.rawValue - } - /// The unique identifier of the bundle. public var id: DocumentationBundle.Identifier - /// The version of the bundle. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - public var version: String? - /// The default language identifier for code listings in the bundle. public var defaultCodeListingLanguage: String? @@ -109,23 +100,6 @@ extension DocumentationBundle { ) } - @available(*, deprecated, renamed: "init(displayName:id:defaultCodeListingLanguage:defaultAvailability:defaultModuleKind:)", message: "Use 'Info.init(displayName:id:defaultCodeListingLanguage:defaultAvailability:defaultModuleKind:)' instead. This deprecated API will be removed after 6.2 is released") - public init( - displayName: String, - identifier: String, - defaultCodeListingLanguage: String?, - defaultAvailability: DefaultAvailability?, - defaultModuleKind: String? - ) { - self.init( - displayName: displayName, - id: .init(rawValue: identifier), - defaultCodeListingLanguage: defaultCodeListingLanguage, - defaultAvailability: defaultAvailability, - defaultModuleKind: defaultModuleKind - ) - } - /// Creates documentation bundle information from the given Info.plist data, falling back to the values /// in the given bundle discovery options if necessary. init( @@ -330,26 +304,6 @@ extension BundleDiscoveryOptions { additionalSymbolGraphFiles: additionalSymbolGraphFiles ) } - - @available(*, deprecated, renamed: "init(fallbackDisplayName:fallbackIdentifier:fallbackDefaultCodeListingLanguage:fallbackDefaultModuleKind:fallbackDefaultAvailability:additionalSymbolGraphFiles:)", message: "Use 'init(fallbackDisplayName:fallbackIdentifier:fallbackDefaultCodeListingLanguage:fallbackDefaultModuleKind:fallbackDefaultAvailability:additionalSymbolGraphFiles:)' instead. This deprecated API will be removed after 6.2 is released") - public init( - fallbackDisplayName: String? = nil, - fallbackIdentifier: String? = nil, - fallbackVersion: String?, - fallbackDefaultCodeListingLanguage: String? = nil, - fallbackDefaultModuleKind: String? = nil, - fallbackDefaultAvailability: DefaultAvailability? = nil, - additionalSymbolGraphFiles: [URL] = [] - ) { - self.init( - fallbackDisplayName: fallbackDisplayName, - fallbackIdentifier: fallbackIdentifier, - fallbackDefaultCodeListingLanguage: fallbackDefaultCodeListingLanguage, - fallbackDefaultModuleKind: fallbackDefaultModuleKind, - fallbackDefaultAvailability: fallbackDefaultAvailability, - additionalSymbolGraphFiles:additionalSymbolGraphFiles - ) - } } private extension CodingUserInfoKey { @@ -358,23 +312,3 @@ private extension CodingUserInfoKey { /// A user info key to store derived display name in the decoder. static let derivedDisplayName = CodingUserInfoKey(rawValue: "derivedDisplayName")! } - -extension DocumentationBundle.Info { - @available(*, deprecated, renamed: "init(displayName:identifier:defaultCodeListingLanguage:defaultAvailability:defaultModuleKind:)", message: "Use 'init(displayName:identifier:defaultCodeListingLanguage:defaultAvailability:defaultModuleKind:)' instead. This deprecated API will be removed after 6.2 is released") - public init( - displayName: String, - identifier: String, - version: String?, - defaultCodeListingLanguage: String?, - defaultAvailability: DefaultAvailability?, - defaultModuleKind: String? - ) { - self.init( - displayName: displayName, - identifier: identifier, - defaultCodeListingLanguage: defaultCodeListingLanguage, - defaultAvailability: defaultAvailability, - defaultModuleKind: defaultModuleKind - ) - } -} diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspace.swift b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspace.swift deleted file mode 100644 index 874bbeb47e..0000000000 --- a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspace.swift +++ /dev/null @@ -1,148 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -public import Foundation - -/// The documentation workspace provides a unified interface for accessing serialized documentation bundles and their files, from a variety of sources. -/// -/// The ``DocumentationContext`` and the workspace that the context is operating in are connected in two ways: -/// - The workspace is the context's data provider. -/// - The context is the workspace's ``DocumentationContextDataProviderDelegate``. -/// -/// The first lets the workspace multiplex the bundles from any number of data providers (``DocumentationWorkspaceDataProvider``) into a single list of -/// ``DocumentationContextDataProvider/bundles`` and allows the context to access the contents of the various bundles without knowing any specifics -/// of its source (files on disk, a database, or a web services). -/// -/// The second lets the workspace notify the context when bundles are added or removed so that the context stays up to date, even after the context is created. -/// -/// ``` -/// ┌─────┐ -/// ┌────────────────────────────────│ IDE │─────────────────────────────┐ -/// ┌──────────┐ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ └─────┘ │ -/// │FileSystem│─▶ WorkspaceDataProvider ─┐ │ │ -/// └──────────┘ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ │ -/// │ │ │ -/// │ │ │ -/// ┌──────────┐ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌───────────┐ Read-only ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌─────────┐ -/// │WebService│─▶ WorkspaceDataProvider ─┼─▶│ Workspace │◀────interface───── ContextDataProvider ◀────get data────│ Context │ -/// └──────────┘ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ └───────────┘ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ └─────────┘ -/// │ │ ▲ -/// │ │ │ -/// ┌────────────────┐ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ │ -/// │MyCustomDatabase│─▶ WorkspaceDataProvider ─┘ │ Bundle or ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ Event push │ -/// └────────────────┘ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ └───────file ───────▶ ContextDataProviderDelegate ─────interface─────┘ -/// change └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ -/// ``` -/// -/// > Note: Each data provider is treated as a separate file system. A single documentation bundle may not span multiple data providers. -/// -/// ## Topics -/// -/// ### Data Providers -/// -/// - ``DocumentationWorkspaceDataProvider`` -/// - ``LocalFileSystemDataProvider`` -/// - ``PrebuiltLocalFileSystemDataProvider`` -/// -/// ## See Also -/// -/// - ``DocumentationContext`` -/// - ``DocumentationContextDataProvider`` -/// - ``DocumentationContextDataProviderDelegate`` -/// -@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") -public class DocumentationWorkspace: DocumentationContextDataProvider { - /// An error when requesting information from a workspace. - public enum WorkspaceError: DescribedError { - /// A bundle with the provided ID wasn't found in the workspace. - case unknownBundle(id: String) - /// A data provider with the provided ID wasn't found in the workspace. - case unknownProvider(id: String) - - /// A plain-text description of the error. - public var errorDescription: String { - switch self { - case .unknownBundle(let id): - return "The requested data could not be located because a containing bundle with id '\(id)' could not be found in the workspace." - case .unknownProvider(let id): - return "The requested data could not be located because a containing data provider with id '\(id)' could not be found in the workspace." - } - } - } - - /// Reads the data for a given file in a given documentation bundle. - /// - /// - Parameters: - /// - url: The URL of the file to read. - /// - bundle: The documentation bundle that the file belongs to. - /// - Throws: A ``WorkspaceError/unknownBundle(id:)`` error if the bundle doesn't exist in the workspace or - /// a ``WorkspaceError/unknownProvider(id:)`` error if the bundle's data provider doesn't exist in the workspace. - /// - Returns: The raw data for the given file. - public func contentsOfURL(_ url: URL, in bundle: DocumentationBundle) throws -> Data { - guard let providerID = bundleToProvider[bundle.identifier] else { - throw WorkspaceError.unknownBundle(id: bundle.identifier) - } - - guard let provider = providers[providerID] else { - throw WorkspaceError.unknownProvider(id: providerID) - } - - return try provider.contentsOfURL(url) - } - - /// A map of bundle identifiers to documentation bundles. - public var bundles: [String: DocumentationBundle] = [:] - /// A map of provider identifiers to data providers. - private var providers: [String: any DocumentationWorkspaceDataProvider] = [:] - /// A map of bundle identifiers to provider identifiers (in other words, a map from a bundle to the provider that vends the bundle). - private var bundleToProvider: [String: String] = [:] - /// The delegate to notify when documentation bundles are added or removed from this workspace. - public weak var delegate: (any DocumentationContextDataProviderDelegate)? - /// Creates a new, empty documentation workspace. - public init() {} - - /// Adds a new data provider to the workspace. - /// - /// Adding a data provider also adds the documentation bundles that it provides, and notifies the ``delegate`` of the added bundles. - /// - /// - Parameters: - /// - provider: The workspace data provider to add to the workspace. - /// - options: The options that the data provider uses to discover documentation bundles that it provides to the delegate. - public func registerProvider(_ provider: any DocumentationWorkspaceDataProvider, options: BundleDiscoveryOptions = .init()) throws { - // We must add the provider before adding the bundle so that the delegate - // may start making requests immediately. - providers[provider.identifier] = provider - - for bundle in try provider.bundles(options: options) { - bundles[bundle.identifier] = bundle - bundleToProvider[bundle.identifier] = provider.identifier - try delegate?.dataProvider(self, didAddBundle: bundle) - } - } - - /// Removes a given data provider from the workspace. - /// - /// Removing a data provider also removes all its provided documentation bundles and notifies the ``delegate`` of the removed bundles. - /// - /// - Parameters: - /// - provider: The workspace data provider to remove from the workspace. - /// - options: The options that the data provider uses to discover documentation bundles that it removes from the delegate. - public func unregisterProvider(_ provider: any DocumentationWorkspaceDataProvider, options: BundleDiscoveryOptions = .init()) throws { - for bundle in try provider.bundles(options: options) { - bundles[bundle.identifier] = nil - bundleToProvider[bundle.identifier] = nil - try delegate?.dataProvider(self, didRemoveBundle: bundle) - } - - // The provider must be removed after removing the bundle so that the delegate - // may continue making requests as part of removing the bundle. - providers[provider.identifier] = nil - } -} diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspaceDataProvider.swift b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspaceDataProvider.swift index dbf5c03772..e068262975 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspaceDataProvider.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationWorkspaceDataProvider.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-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 @@ -10,42 +10,6 @@ public import Foundation -/// A type that vends bundles and responds to requests for data. -@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") -public protocol DocumentationWorkspaceDataProvider { - /// A string that uniquely identifies this data provider. - /// - /// Unless your implementation needs a stable identifier to associate with an external system, it's reasonable to - /// use `UUID().uuidString` for the provider's identifier. - var identifier: String { get } - - /// Returns the data backing one of the files that this data provider provides. - /// - /// Your implementation can expect to only receive URLs that it provides. It's acceptable to assert if you receive - /// a URL that wasn't provided by your data provider, because this indicates a bug in the ``DocumentationWorkspace``. - /// - /// - Parameter url: The URL of a file to return the backing data for. - func contentsOfURL(_ url: URL) throws -> Data - - /// Returns the documentation bundles that your data provider provides. - /// - /// - Parameter options: Configuration that controls how the provider discovers documentation bundles. - /// - /// If your data provider also conforms to ``FileSystemProvider``, there is a default implementation of this method - /// that traverses the ``FileSystemProvider/fileSystem`` to find all documentation bundles in it. - func bundles(options: BundleDiscoveryOptions) throws -> [DocumentationBundle] -} - -@available(*, deprecated, message: "Pass the context its inputs at initialization instead. This deprecated API will be removed after 6.2 is released") -public extension DocumentationWorkspaceDataProvider { - /// Returns the documentation bundles that your data provider provides; discovered with the default options. - /// - /// If your data provider also conforms to ``FileSystemProvider``, there is a default implementation of this method - /// that traverses the ``FileSystemProvider/fileSystem`` to find all documentation bundles in it. - func bundles() throws -> [DocumentationBundle] { - return try bundles(options: BundleDiscoveryOptions()) - } -} /// Options to configure the discovery of documentation bundles public struct BundleDiscoveryOptions { diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/FileSystemProvider.swift b/Sources/SwiftDocC/Infrastructure/Workspace/FileSystemProvider.swift deleted file mode 100644 index 2fb4fa60f9..0000000000 --- a/Sources/SwiftDocC/Infrastructure/Workspace/FileSystemProvider.swift +++ /dev/null @@ -1,66 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -public import Foundation - -/// A type that vends a tree of virtual filesystem objects. -@available(*, deprecated, message: "Use 'FileManagerProtocol.recursiveFiles(startingPoint:)' instead. This deprecated API will be removed after 6.2 is released.") -public protocol FileSystemProvider { - /// The organization of the files that this provider provides. - var fileSystem: FSNode { get } -} - -/// An element in a virtual filesystem. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released.") -public enum FSNode { - /// A file in a filesystem. - case file(File) - /// A directory in a filesystem. - case directory(Directory) - - /// A file in a virtual file system - public struct File { - /// The URL to this file. - public var url: URL - - /// Creates a new virtual file with a given URL - /// - Parameter url: The URL to this file. - public init(url: URL) { - self.url = url - } - } - - /// A directory in a virtual file system. - public struct Directory { - /// The URL to this directory. - public var url: URL - /// The contents of this directory. - public var children: [FSNode] - - /// Creates a new virtual directory with a given URL and contents. - /// - Parameters: - /// - url: The URL to this directory. - /// - children: The contents of this directory. - public init(url: URL, children: [FSNode]) { - self.url = url - self.children = children - } - } - - /// The URL for the node in the filesystem. - public var url: URL { - switch self { - case .file(let file): - return file.url - case .directory(let directory): - return directory.url - } - } -} diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/GeneratedDataProvider.swift b/Sources/SwiftDocC/Infrastructure/Workspace/GeneratedDataProvider.swift deleted file mode 100644 index f345158b56..0000000000 --- a/Sources/SwiftDocC/Infrastructure/Workspace/GeneratedDataProvider.swift +++ /dev/null @@ -1,149 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2022 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 -*/ - -public import Foundation -import SymbolKit - -/// A type that provides documentation bundles that it discovers by traversing the local file system. -@available(*, deprecated, message: "Use 'DocumentationContext.InputProvider' instead. This deprecated API will be removed after 6.2 is released") -public class GeneratedDataProvider: DocumentationWorkspaceDataProvider { - public var identifier: String = UUID().uuidString - - public typealias SymbolGraphDataLoader = (URL) -> Data? - private let symbolGraphDataLoader: SymbolGraphDataLoader - private var generatedMarkdownFiles: [String: Data] = [:] - - /// Creates a new provider that generates documentation bundles from the ``BundleDiscoveryOptions`` it is passed in ``bundles(options:)``. - /// - /// - Parameters: - /// - symbolGraphDataLoader: A closure that loads the raw data for a symbol graph file at a given URL. - public init(symbolGraphDataLoader: @escaping SymbolGraphDataLoader) { - self.symbolGraphDataLoader = symbolGraphDataLoader - } - - public func bundles(options: BundleDiscoveryOptions) throws -> [DocumentationBundle] { - // Find all the unique module names from the symbol graph files and generate a top level module page for each of them. - var moduleNames = Set() - for url in options.additionalSymbolGraphFiles { - guard let data = symbolGraphDataLoader(url) else { - throw Error.unableToLoadSymbolGraphData(url: url) - } - let container = try JSONDecoder().decode(SymbolGraphModuleContainer.self, from: data) - moduleNames.insert(container.module.name) - } - let info: DocumentationBundle.Info - do { - let derivedDisplayName: String? - if moduleNames.count == 1, let moduleName = moduleNames.first { - derivedDisplayName = moduleName - } else { - derivedDisplayName = nil - } - info = try DocumentationBundle.Info( - bundleDiscoveryOptions: options, - derivedDisplayName: derivedDisplayName - ) - } catch { - throw Error.notEnoughDataToGenerateBundle(options: options, underlyingError: error) - } - - guard !options.additionalSymbolGraphFiles.isEmpty else { - return [] - } - - if moduleNames.count == 1, let moduleName = moduleNames.first, moduleName != info.displayName { - generatedMarkdownFiles[moduleName] = Data(""" - # ``\(moduleName)`` - - @Metadata { - @DisplayName("\(info.displayName)") - } - """.utf8) - } else { - for moduleName in moduleNames { - generatedMarkdownFiles[moduleName] = Data("# ``\(moduleName)``".utf8) - } - } - - let topLevelPages = generatedMarkdownFiles.keys.map { URL(string: $0 + ".md")! } - - return [ - DocumentationBundle( - info: info, - symbolGraphURLs: options.additionalSymbolGraphFiles, - markupURLs: topLevelPages, - miscResourceURLs: [] - ) - ] - } - - enum Error: DescribedError { - case unableToLoadSymbolGraphData(url: URL) - case notEnoughDataToGenerateBundle(options: BundleDiscoveryOptions, underlyingError: (any Swift.Error)?) - - var errorDescription: String { - switch self { - case .unableToLoadSymbolGraphData(let url): - return "Unable to load data for symbol graph file at \(url.path.singleQuoted)" - case .notEnoughDataToGenerateBundle(let options, let underlyingError): - var symbolGraphFileList = options.additionalSymbolGraphFiles.reduce("") { $0 + "\n\t" + $1.path } - if !symbolGraphFileList.isEmpty { - symbolGraphFileList += "\n" - } - - var errorMessage = """ - The information provided as command line arguments is not enough to generate a documentation bundle: - """ - - if let underlyingError { - errorMessage += """ - \((underlyingError as? (any DescribedError))?.errorDescription ?? underlyingError.localizedDescription) - - """ - } else { - errorMessage += """ - \(options.infoPlistFallbacks.sorted(by: { lhs, rhs in lhs.key < rhs.key }).map { "\($0.key) : '\($0.value)'" }.joined(separator: "\n")) - Additional symbol graph files: [\(symbolGraphFileList)] - - """ - } - - return errorMessage - } - } - } - - public func contentsOfURL(_ url: URL) throws -> Data { - if DocumentationBundleFileTypes.isMarkupFile(url), let content = generatedMarkdownFiles[url.deletingPathExtension().lastPathComponent] { - return content - } else if DocumentationBundleFileTypes.isSymbolGraphFile(url) { - guard let data = symbolGraphDataLoader(url) else { - throw Error.unableToLoadSymbolGraphData(url: url) - } - return data - } else { - preconditionFailure("Unexpected url '\(url)'.") - } - } -} - -/// A wrapper type that decodes only the module in the symbol graph. -private struct SymbolGraphModuleContainer: Decodable { - /// The decoded symbol graph module. - let module: SymbolGraph.Module - - typealias CodingKeys = SymbolGraph.CodingKeys - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.module = try container.decode(SymbolGraph.Module.self, forKey: .module) - } -} diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider+BundleDiscovery.swift b/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider+BundleDiscovery.swift deleted file mode 100644 index 9101f51ec1..0000000000 --- a/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider+BundleDiscovery.swift +++ /dev/null @@ -1,180 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import Foundation - -@available(*, deprecated, message: "Use 'DocumentationContext.InputProvider' instead. This deprecated API will be removed after 6.2 is released") -extension LocalFileSystemDataProvider: DocumentationWorkspaceDataProvider { - public func bundles(options: BundleDiscoveryOptions) throws -> [DocumentationBundle] { - var bundles = try bundlesInTree(fileSystem, options: options) - - guard case .directory(let rootDirectory) = fileSystem else { - preconditionFailure("Expected directory object at path '\(fileSystem.url.absoluteString)'.") - } - - // If no bundles were found in the root directory, assume that the directory itself is a bundle. - if bundles.isEmpty && self.allowArbitraryCatalogDirectories { - bundles.append(try createBundle(rootDirectory, rootDirectory.children, options: options)) - } - - return bundles - } - - /// Recursively traverses the file system, searching for documentation bundles. - /// - /// - Parameters: - /// - root: The directory in which to search for documentation bundles. - /// - options: Configuration that controls how the provider discovers documentation bundles. - /// - Throws: A ``WorkspaceError`` if one of the found documentation bundle directories is an invalid documentation bundle. - /// - Returns: A list of all the bundles that the provider discovered in the file system. - private func bundlesInTree(_ root: FSNode, options: BundleDiscoveryOptions) throws -> [DocumentationBundle] { - var bundles: [DocumentationBundle] = [] - - guard case .directory(let rootDirectory) = root else { - preconditionFailure("Expected directory object at path '\(root.url.absoluteString)'.") - } - - if DocumentationBundleFileTypes.isDocumentationCatalog(rootDirectory.url) { - bundles.append(try createBundle(rootDirectory, rootDirectory.children, options: options)) - } else { - // Recursively descend when the current root directory isn't a documentation bundle. - for child in rootDirectory.children { - if case .directory = child { - try bundles.append(contentsOf: bundlesInTree(child, options: options)) - } - } - } - - return bundles - } - - /// Creates a documentation bundle from the content in a given documentation bundle directory. - /// - Parameters: - /// - directory: The documentation bundle directory. - /// - bundleChildren: The top-level files and directories in the documentation bundle directory. - /// - options: Configuration that controls how the provider discovers documentation bundles. - /// - Throws: A ``WorkspaceError`` if the content is an invalid documentation bundle or - /// a ``DocumentationBundle/PropertyListError`` error if the bundle's Info.plist file is invalid. - /// - Returns: The new documentation bundle. - private func createBundle(_ directory: FSNode.Directory, _ bundleChildren: [FSNode], options: BundleDiscoveryOptions) throws -> DocumentationBundle { - let infoPlistData: Data? - if let infoPlistRef = findInfoPlist(bundleChildren) { - infoPlistData = try contentsOfURL(infoPlistRef.url) - } else { - infoPlistData = nil - } - let info = try DocumentationBundle.Info( - from: infoPlistData, - bundleDiscoveryOptions: options, - derivedDisplayName: directory.url.deletingPathExtension().lastPathComponent - ) - - let markupFiles = findMarkupFiles(bundleChildren, recursive: true).map { $0.url } - let miscResources = findNonMarkupFiles(bundleChildren, recursive: true).map { $0.url } - let symbolGraphFiles = findSymbolGraphFiles(bundleChildren, recursive: true).map { $0.url } + options.additionalSymbolGraphFiles - - let customHeader = findCustomHeader(bundleChildren)?.url - let customFooter = findCustomFooter(bundleChildren)?.url - let themeSettings = findThemeSettings(bundleChildren)?.url - - return DocumentationBundle( - info: info, - symbolGraphURLs: symbolGraphFiles, - markupURLs: markupFiles, - miscResourceURLs: miscResources, - customHeader: customHeader, - customFooter: customFooter, - themeSettings: themeSettings - ) - } - - /// Performs a shallow search for the first Info.plist file in the given list of files and directories. - /// - Parameter bundleChildren: The list of files and directories to check. - /// - Returns: The first Info.plist file, or `nil` if none of the files is an Info.plist file. - private func findInfoPlist(_ bundleChildren: [FSNode]) -> FSNode.File? { - return bundleChildren.firstFile { DocumentationBundleFileTypes.isInfoPlistFile($0.url) } - } - - /// Finds all the symbol-graph files in the given list of files and directories. - /// - Parameters: - /// - bundleChildren: The list of files and directories to check. - /// - recursive: If `true`, this function will recursively check the files of all directories in the array. If `false`, it will ignore all directories. - /// - Returns: A list of all the symbol-graph files. - private func findSymbolGraphFiles(_ bundleChildren: [FSNode], recursive: Bool) -> [FSNode.File] { - return bundleChildren.files(recursive: recursive) { DocumentationBundleFileTypes.isSymbolGraphFile($0.url) } - } - - /// Finds all the markup files in the given list of files and directories. - /// - Parameters: - /// - bundleChildren: The list of files and directories to check. - /// - recursive: If `true`, this function will recursively check the files of all directories in the array. If `false`, it will ignore all directories. - /// - Returns: A list of all the markup files. - private func findMarkupFiles(_ bundleChildren: [FSNode], recursive: Bool) -> [FSNode.File] { - return bundleChildren.files(recursive: recursive) { DocumentationBundleFileTypes.isMarkupFile($0.url) } - } - - /// Finds all the non-markup files in the given list of files and directories. - /// - Parameters: - /// - bundleChildren: The list of files and directories to check. - /// - recursive: If `true`, this function will recursively check the files of all directories in the array. If `false`, it will ignore all directories. - /// - Returns: A list of all the non-markup files. - private func findNonMarkupFiles(_ bundleChildren: [FSNode], recursive: Bool) -> [FSNode.File] { - bundleChildren.files(recursive: recursive) { !DocumentationBundleFileTypes.isMarkupFile($0.url) && !DocumentationBundleFileTypes.isSymbolGraphFile($0.url) } - } - - private func findCustomHeader(_ bundleChildren: [FSNode]) -> FSNode.File? { - return bundleChildren.firstFile { DocumentationBundleFileTypes.isCustomHeader($0.url) } - } - - private func findCustomFooter(_ bundleChildren: [FSNode]) -> FSNode.File? { - return bundleChildren.firstFile { DocumentationBundleFileTypes.isCustomFooter($0.url) } - } - - private func findThemeSettings(_ bundleChildren: [FSNode]) -> FSNode.File? { - return bundleChildren.firstFile { DocumentationBundleFileTypes.isThemeSettingsFile($0.url) } - } -} - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released.") -fileprivate extension [FSNode] { - /// Returns the first file that matches a given predicate. - /// - Parameter predicate: A closure that takes a file as its argument and returns a Boolean value indicating whether the file should be returned from this function. - /// - Throws: Any error that the predicate closure raises. - /// - Returns: The first file that matches the predicate. - func firstFile(where predicate: (FSNode.File) throws -> Bool) rethrows -> FSNode.File? { - for case .file(let file) in self where try predicate(file) { - return file - } - return nil - } - - /// Returns all the files that match s given predicate. - /// - Parameters: - /// - recursive: If `true`, this function will recursively check the files of all directories in the array. If `false`, it will ignore all directories in the array. - /// - predicate: A closure that takes a file as its argument and returns a Boolean value indicating whether the file should be included in the returned array. - /// - Throws: Any error that the predicate closure raises. - /// - Returns: The first file that matches the predicate. - func files(recursive: Bool, where predicate: (FSNode.File) throws -> Bool) rethrows -> [FSNode.File] { - var matches: [FSNode.File] = [] - for node in self { - switch node { - case .directory(let directory): - guard recursive else { break } - try matches.append(contentsOf: directory.children.files(recursive: true, where: predicate)) - case .file(let file) where try predicate(file): - matches.append(file) - case .file: - break - } - } - - return matches - } -} diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider.swift b/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider.swift deleted file mode 100644 index a24c75444c..0000000000 --- a/Sources/SwiftDocC/Infrastructure/Workspace/LocalFileSystemDataProvider.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -public import Foundation - -/// A type that provides documentation bundles that it discovers by traversing the local file system. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released.") -public struct LocalFileSystemDataProvider: FileSystemProvider { - public var identifier: String = UUID().uuidString - - /// The location that this provider searches for documentation bundles in. - public var rootURL: URL - - public var fileSystem: FSNode - - /// Whether to consider the root location as a documentation bundle if the data provider doesn't discover another bundle in the hierarchy from the root location. - public let allowArbitraryCatalogDirectories: Bool - - /// Creates a new provider that recursively traverses the content of the given root URL to discover documentation bundles. - /// - Parameters: - /// - rootURL: The location that this provider searches for documentation bundles in. - /// - allowArbitraryCatalogDirectories: Configures the data provider to consider the root location as a documentation bundle if it doesn't discover another bundle. - public init(rootURL: URL, allowArbitraryCatalogDirectories: Bool = false) throws { - self.rootURL = rootURL - self.allowArbitraryCatalogDirectories = allowArbitraryCatalogDirectories - fileSystem = try LocalFileSystemDataProvider.buildTree(root: rootURL) - } - - /// Builds a virtual file system hierarchy from the contents of a root URL in the local file system. - /// - Parameter root: The location from which to descend to build the virtual file system. - /// - Returns: A virtual file system that describe the file and directory structure within the given URL. - private static func buildTree(root: URL) throws -> FSNode { - var children: [FSNode] = [] - let childURLs = try FileManager.default.contentsOfDirectory(at: root, includingPropertiesForKeys: [URLResourceKey.isDirectoryKey], options: .skipsHiddenFiles) - - for url in childURLs { - if FileManager.default.directoryExists(atPath: url.path) { - children.append(try buildTree(root: url)) - } else { - children.append(FSNode.file(FSNode.File(url: url))) - } - } - return FSNode.directory(FSNode.Directory(url: root, children: children)) - } - - public func contentsOfURL(_ url: URL) throws -> Data { - precondition(url.isFileURL, "Unexpected non-file url '\(url)'.") - return try Data(contentsOf: url) - } -} diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/PrebuiltLocalFileSystemDataProvider.swift b/Sources/SwiftDocC/Infrastructure/Workspace/PrebuiltLocalFileSystemDataProvider.swift deleted file mode 100644 index 00c3231d35..0000000000 --- a/Sources/SwiftDocC/Infrastructure/Workspace/PrebuiltLocalFileSystemDataProvider.swift +++ /dev/null @@ -1,35 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -public import Foundation - -/// A data provider that provides existing in-memory documentation bundles with files on the local filesystem. -@available(*, deprecated, message: "Use 'DocumentationContext.InputProvider' instead. This deprecated API will be removed after 6.2 is released") -public struct PrebuiltLocalFileSystemDataProvider: DocumentationWorkspaceDataProvider { - public var identifier: String = UUID().uuidString - - private var _bundles: [DocumentationBundle] - public func bundles(options: BundleDiscoveryOptions) throws -> [DocumentationBundle] { - // Ignore the bundle discovery options, these bundles are already built. - return _bundles - } - - /// Creates a new provider to provide the given documentation bundles. - /// - Parameter bundles: The existing documentation bundles for this provider to provide. - public init(bundles: [DocumentationBundle]) { - _bundles = bundles - } - - public func contentsOfURL(_ url: URL) throws -> Data { - precondition(url.isFileURL, "Unexpected non-file url '\(url)'.") - return try Data(contentsOf: url) - } -} - diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 0a901bab56..8c6112dbb1 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.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 @@ -311,7 +311,8 @@ public extension DocumentationNode { renderNode: RenderNode, includeTaskGroups: Bool = true ) -> [LinkDestinationSummary] { - guard let bundle = context.bundle, bundle.id == reference.bundleID else { + let bundle = context.bundle + guard bundle.id == reference.bundleID else { // Don't return anything for external references that don't have a bundle in the context. return [] } @@ -330,7 +331,6 @@ public extension DocumentationNode { let taskGroups: [LinkDestinationSummary.TaskGroup]? if includeTaskGroups { switch kind { - case ._technologyOverview: fallthrough // This case is deprecated and will be removed after 6.2 is released. case .tutorial, .tutorialArticle, .tutorialTableOfContents, .chapter, .volume, .onPageLandmark: taskGroups = [.init(title: nil, identifiers: context.children(of: reference).map { $0.reference.absoluteString })] default: diff --git a/Sources/SwiftDocC/Model/BuildMetadata.swift b/Sources/SwiftDocC/Model/BuildMetadata.swift index abe6e57e0c..39a83aa468 100644 --- a/Sources/SwiftDocC/Model/BuildMetadata.swift +++ b/Sources/SwiftDocC/Model/BuildMetadata.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 @@ -23,11 +23,6 @@ public struct BuildMetadata: Codable { /// The display name of the documentation bundle that DocC built. public var bundleDisplayName: String - @available(*, deprecated, renamed: "bundleID", message: "Use 'bundleID' instead. This deprecated API will be removed after 6.2 is released") - public var bundleIdentifier: String { - bundleID.rawValue - } - /// The bundle identifier of the documentation bundle that DocC built. public let bundleID: DocumentationBundle.Identifier @@ -40,12 +35,4 @@ public struct BuildMetadata: Codable { self.bundleDisplayName = bundleDisplayName self.bundleID = bundleID } - - @available(*, deprecated, renamed: "init(bundleDisplayName:bundleID:)", message: "Use 'init(bundleDisplayName:bundleID:)' instead. This deprecated API will be removed after 6.2 is released") - public init(bundleDisplayName: String, bundleIdentifier: String) { - self.init( - bundleDisplayName: bundleDisplayName, - bundleID: .init(rawValue: bundleIdentifier) - ) - } } diff --git a/Sources/SwiftDocC/Model/Identifier.swift b/Sources/SwiftDocC/Model/Identifier.swift index d6e980784a..469af2f4ca 100644 --- a/Sources/SwiftDocC/Model/Identifier.swift +++ b/Sources/SwiftDocC/Model/Identifier.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2023 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 @@ -176,11 +176,6 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString /// The storage for the resolved topic reference's state. let _storage: Storage - @available(*, deprecated, renamed: "bundleID", message: "Use 'bundleID' instead. This deprecated API will be removed after 6.2 is released") - public var bundleIdentifier: String { - bundleID.rawValue - } - /// The identifier of the bundle that owns this documentation topic. public var bundleID: DocumentationBundle.Identifier { _storage.bundleID @@ -224,14 +219,6 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString sourceLanguages: sourceLanguages ) } - @available(*, deprecated, renamed: "init(id:path:fragment:sourceLanguage:)", message: "Use 'init(id:path:fragment:sourceLanguage:)' instead. This deprecated API will be removed after 6.2 is released") - public init(bundleIdentifier: String, path: String, fragment: String? = nil, sourceLanguage: SourceLanguage) { - self.init(bundleIdentifier: bundleIdentifier, path: path, fragment: fragment, sourceLanguages: [sourceLanguage]) - } - @available(*, deprecated, renamed: "init(id:path:fragment:sourceLanguages:)", message: "Use 'init(id:path:fragment:sourceLanguages:)' instead. This deprecated API will be removed after 6.2 is released") - public init(bundleIdentifier: String, path: String, fragment: String? = nil, sourceLanguages: Set) { - self.init(bundleID: .init(rawValue: bundleIdentifier), path: path, fragment: fragment, sourceLanguages: sourceLanguages) - } private init(bundleID: DocumentationBundle.Identifier, urlReadablePath: String, urlReadableFragment: String? = nil, sourceLanguages: Set) { precondition(!sourceLanguages.isEmpty, "ResolvedTopicReference.sourceLanguages cannot be empty") @@ -541,11 +528,6 @@ public struct UnresolvedTopicReference: Hashable, CustomStringConvertible { /// The URL as originally spelled. public let topicURL: ValidatedURL - @available(*, deprecated, renamed: "bundleID", message: "Use 'bundleID' instead. This deprecated API will be removed after 6.2 is released") - public var bundleIdentifier: String? { - bundleID?.rawValue - } - /// The bundle identifier, if one was provided in the host name component of the original URL. public var bundleID: DocumentationBundle.Identifier? { topicURL.components.host.map { .init(rawValue: $0) } @@ -607,11 +589,6 @@ public struct UnresolvedTopicReference: Hashable, CustomStringConvertible { /// A reference to an auxiliary resource such as an image. public struct ResourceReference: Hashable { - @available(*, deprecated, renamed: "bundleID", message: "Use 'bundleID' instead. This deprecated API will be removed after 6.2 is released") - public var bundleIdentifier: String { - bundleID.rawValue - } - /// The documentation bundle identifier for the bundle in which this resource resides. public let bundleID: DocumentationBundle.Identifier diff --git a/Sources/SwiftDocC/Model/Kind.swift b/Sources/SwiftDocC/Model/Kind.swift index c6d5abf621..f241c753d0 100644 --- a/Sources/SwiftDocC/Model/Kind.swift +++ b/Sources/SwiftDocC/Model/Kind.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2023 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 @@ -203,9 +203,6 @@ extension DocumentationNode.Kind { .extendedModule, .extendedStructure, .extendedClass, .extendedEnumeration, .extendedProtocol, .unknownExtendedType, // Other .keyword, .restAPI, .tag, .propertyList, .object - - // Deprecated, to be removed after 6.2 is released. - , _technologyOverview, ] /// Returns whether this symbol kind is a synthetic "Extended Symbol" symbol kind. @@ -218,12 +215,3 @@ extension DocumentationNode.Kind { } } } - -extension DocumentationNode.Kind { - @available(*, deprecated, renamed: "tutorialTableOfContents", message: "Use 'tutorialTableOfContents' This deprecated API will be removed after 6.2 is released") - public static var technology: Self { tutorialTableOfContents } - - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - public static var technologyOverview: Self { _technologyOverview } - static let _technologyOverview = DocumentationNode.Kind(name: "Technology (Overview)", id: "org.swift.docc.kind.technology.overview", isSymbol: false) -} diff --git a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift index 15d3a439ac..2ad9f43a8b 100644 --- a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift +++ b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift @@ -154,7 +154,6 @@ public class DocumentationContentRenderer { case .chapter: return .collectionGroup case .collection: return .collection case .collectionGroup: return .collectionGroup - case ._technologyOverview: fallthrough // This case is deprecated and will be removed after 6.2 is released. case .tutorialTableOfContents: return .overview case .landingPage: return .article case .module, .extendedModule: return .collection diff --git a/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift b/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift index bf8838c25f..73f85a43f4 100644 --- a/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift +++ b/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift @@ -27,17 +27,15 @@ struct LinkTitleResolver { /// - Parameter page: The page for which to resolve the title. /// - Returns: The variants of the link title for this page, or `nil` if the page doesn't exist in the context. func title(for page: DocumentationNode) -> DocumentationDataVariants? { - if let bundle = context.bundle, - let directive = page.markup.child(at: 0) as? BlockDirective { - + if let directive = page.markup.child(at: 0) as? BlockDirective { var problems = [Problem]() switch directive.name { case Tutorial.directiveName: - if let tutorial = Tutorial(from: directive, source: source, for: bundle, problems: &problems) { + if let tutorial = Tutorial(from: directive, source: source, for: context.bundle, problems: &problems) { return .init(defaultVariantValue: tutorial.intro.title) } case TutorialTableOfContents.directiveName: - if let overview = TutorialTableOfContents(from: directive, source: source, for: bundle, problems: &problems) { + if let overview = TutorialTableOfContents(from: directive, source: source, for: context.bundle, problems: &problems) { return .init(defaultVariantValue: overview.name) } default: break diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNode.swift b/Sources/SwiftDocC/Model/Rendering/RenderNode.swift index 973ff1f74e..520eb5e8a0 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNode.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNode.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 @@ -125,12 +125,6 @@ public struct RenderNode: VariantContainer { /// /// The key for each reference is the ``RenderReferenceIdentifier/identifier`` of the reference's ``RenderReference/identifier``. public var references: [String: any RenderReference] = [:] - - @available(*, deprecated, message: "Use 'hierarchyVariants' instead. This deprecated API will be removed after 6.2 is released") - public var hierarchy: RenderHierarchy? { - get { hierarchyVariants.defaultValue } - set { hierarchyVariants.defaultValue = newValue } - } /// Hierarchy information about the context in which this documentation node is placed. public var hierarchyVariants: VariantCollection = .init(defaultValue: nil) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index 15e9ca1753..1fd747b15a 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -1756,7 +1756,7 @@ public struct RenderNodeTranslator: SemanticVisitor { let downloadReference: DownloadReference do { let downloadURL = resolvedAssets.variants.first!.value - let downloadData = try context.contentsOfURL(downloadURL, in: bundle) + let downloadData = try context.dataProvider.contents(of: downloadURL) downloadReference = DownloadReference(identifier: mediaReference, renderURL: downloadURL, checksum: Checksum.sha512(of: downloadData)) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderReferenceStore.swift b/Sources/SwiftDocC/Model/Rendering/RenderReferenceStore.swift index b84ffa7616..d095303fc5 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderReferenceStore.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderReferenceStore.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 @@ -41,11 +41,6 @@ public struct RenderReferenceStore: Codable { public func content(forAssetNamed assetName: String, bundleID: DocumentationBundle.Identifier) -> DataAsset? { assets[AssetReference(assetName: assetName, bundleID: bundleID)] } - - @available(*, deprecated, renamed: "content(forAssetNamed:bundleID:)", message: "Use 'content(forAssetNamed:bundleID:)' instead. This deprecated API will be removed after 6.2 is released") - public func content(forAssetNamed assetName: String, bundleIdentifier: String) -> DataAsset? { - content(forAssetNamed: assetName, bundleID: .init(rawValue: bundleIdentifier)) - } } public extension RenderReferenceStore { diff --git a/Sources/SwiftDocC/Semantics/Article/MarkupConvertible.swift b/Sources/SwiftDocC/Semantics/Article/MarkupConvertible.swift index 5c3bdfbb76..2eace344cc 100644 --- a/Sources/SwiftDocC/Semantics/Article/MarkupConvertible.swift +++ b/Sources/SwiftDocC/Semantics/Article/MarkupConvertible.swift @@ -21,19 +21,4 @@ public protocol MarkupConvertible { /// - bundle: The documentation bundle that the source file belongs to. /// - problems: A mutable collection of problems to update with any problem encountered while initializing the element. init?(from markup: any Markup, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) - - @available(*, deprecated, renamed: "init(from:source:for:problems:)", message: "Use 'init(from:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - init?(from markup: any Markup, source: URL?, for bundle: DocumentationBundle, in context: DocumentationContext, problems: inout [Problem]) -} - -public extension MarkupConvertible { - // Default implementation to avoid source breaking changes. Remove this after 6.2 is released. - init?(from markup: any Markup, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) { - fatalError("Markup convertible type doesn't implement either 'init(from:source:for:problems:)' or 'init(from:source:for:in:problems:)'") - } - - // Default implementation to new types don't need to implement a deprecated initializer. Remove this after 6.2 is released. - init?(from markup: any Markup, source: URL?, for bundle: DocumentationBundle, in context: DocumentationContext, problems: inout [Problem]) { - self.init(from: markup, source: source, for: bundle, problems: &problems) - } } diff --git a/Sources/SwiftDocC/Semantics/DirectiveConvertable.swift b/Sources/SwiftDocC/Semantics/DirectiveConvertable.swift index b5407c0a93..fb2d4d6f9d 100644 --- a/Sources/SwiftDocC/Semantics/DirectiveConvertable.swift +++ b/Sources/SwiftDocC/Semantics/DirectiveConvertable.swift @@ -38,9 +38,6 @@ public protocol DirectiveConvertible { /// - problems: An inout array of ``Problem`` to be collected for later diagnostic reporting. init?(from directive: BlockDirective, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) - @available(*, deprecated, renamed: "init(from:source:for:problems:)", message: "Use 'init(from:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - init?(from directive: BlockDirective, source: URL?, for bundle: DocumentationBundle, in context: DocumentationContext, problems: inout [Problem]) - /// Returns a Boolean value indicating whether the `DirectiveConvertible` recognizes the given directive. /// /// - Parameter directive: The directive to check for conversion compatibility. @@ -54,15 +51,5 @@ public extension DirectiveConvertible { static func canConvertDirective(_ directive: BlockDirective) -> Bool { directiveName == directive.name } - - // Default implementation to avoid source breaking changes. Remove this after 6.2 is released. - init?(from directive: BlockDirective, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) { - fatalError("Directive named \(directive.name) doesn't implement either 'init(from:source:for:problems:)' or 'init(from:source:for:in:problems:)'") - } - - // Default implementation to new types don't need to implement a deprecated initializer. Remove this after 6.2 is released. - init?(from directive: BlockDirective, source: URL?, for bundle: DocumentationBundle, in context: DocumentationContext, problems: inout [Problem]) { - self.init(from: directive, source: source, for: bundle, problems: &problems) - } } diff --git a/Sources/SwiftDocC/Semantics/DirectiveInfrastructure/AutomaticDirectiveConvertible.swift b/Sources/SwiftDocC/Semantics/DirectiveInfrastructure/AutomaticDirectiveConvertible.swift index d6e172c990..bfd1ab53de 100644 --- a/Sources/SwiftDocC/Semantics/DirectiveInfrastructure/AutomaticDirectiveConvertible.swift +++ b/Sources/SwiftDocC/Semantics/DirectiveInfrastructure/AutomaticDirectiveConvertible.swift @@ -111,11 +111,6 @@ extension AutomaticDirectiveConvertible { ) } - @available(*, deprecated, renamed: "init(from:source:for:)", message: "Use 'init(from:source:for:)' instead. This deprecated API will be removed after 6.2 is released") - public init?(from directive: BlockDirective, source: URL? = nil, for bundle: DocumentationBundle, in _: DocumentationContext) { - self.init(from: directive, source: source, for: bundle) - } - public init?( from directive: BlockDirective, source: URL?, diff --git a/Sources/SwiftDocC/Semantics/ExternalLinks/ExternalReferenceWalker.swift b/Sources/SwiftDocC/Semantics/ExternalLinks/ExternalReferenceWalker.swift index 5e15a2d062..b1f2021f4a 100644 --- a/Sources/SwiftDocC/Semantics/ExternalLinks/ExternalReferenceWalker.swift +++ b/Sources/SwiftDocC/Semantics/ExternalLinks/ExternalReferenceWalker.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 @@ -110,11 +110,6 @@ struct ExternalReferenceWalker: SemanticVisitor { visitMarkupContainer(MarkupContainer(markup)) } - @available(*, deprecated) // This is a deprecated protocol requirement. Remove after 6.2 is released - mutating func visitTechnology(_ technology: TutorialTableOfContents) { - visitTutorialTableOfContents(technology) - } - mutating func visitTutorialTableOfContents(_ tutorialTableOfContents: TutorialTableOfContents) -> Void { visit(tutorialTableOfContents.intro) tutorialTableOfContents.volumes.forEach { visit($0) } diff --git a/Sources/SwiftDocC/Semantics/General Purpose Analyses/Extract.swift b/Sources/SwiftDocC/Semantics/General Purpose Analyses/Extract.swift index 9d063731ad..38b3611ea0 100644 --- a/Sources/SwiftDocC/Semantics/General Purpose Analyses/Extract.swift +++ b/Sources/SwiftDocC/Semantics/General Purpose Analyses/Extract.swift @@ -18,11 +18,6 @@ extension Semantic.Analyses { public struct ExtractAll { public init() {} - @available(*, deprecated, renamed: "analyze(_:children:source:for:problems:)", message: "Use 'analyze(_:children:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> ([Child], remainder: MarkupContainer) { - analyze(directive, children: children, source: source, for: bundle, problems: &problems) - } - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) -> ([Child], remainder: MarkupContainer) { return Semantic.Analyses.extractAll( childType: Child.self, @@ -66,11 +61,6 @@ extension Semantic.Analyses { } return (matches, remainder: MarkupContainer(remainder)) } - - @available(*, deprecated, renamed: "analyze(_:children:source:problems:)", message: "Use 'analyze(_:children:source:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for _: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> ([Child], remainder: MarkupContainer) { - analyze(directive, children: children, source: source, problems: &problems) - } } } diff --git a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasAtLeastOne.swift b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasAtLeastOne.swift index ba20a20496..7c8172f0d1 100644 --- a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasAtLeastOne.swift +++ b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasAtLeastOne.swift @@ -21,11 +21,6 @@ extension Semantic.Analyses { self.severityIfNotFound = severityIfNotFound } - @available(*, deprecated, renamed: "analyze(_:children:source:for:problems:)", message: "Use 'analyze(_:children:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> ([Child], remainder: MarkupContainer) { - analyze(directive, children: children, source: source, for: bundle, problems: &problems) - } - public func analyze( _ directive: BlockDirective, children: some Sequence, diff --git a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasAtMostOne.swift b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasAtMostOne.swift index 9c987c89dd..93765fd5d3 100644 --- a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasAtMostOne.swift +++ b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasAtMostOne.swift @@ -16,12 +16,6 @@ extension Semantic.Analyses { Checks to see if a parent directive has at most one child directive of a specified type. If so, return that child and the remainder. */ public struct HasAtMostOne { - - @available(*, deprecated, renamed: "analyze(_:children:source:for:problems:)", message: "Use 'analyze(_:children:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> (Child?, remainder: MarkupContainer) { - analyze(directive, children: children, source: source, for: bundle, problems: &problems) - } - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) -> (Child?, remainder: MarkupContainer) { return Semantic.Analyses.extractAtMostOne( childType: Child.self, diff --git a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasContent.swift b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasContent.swift index 8c0879ec21..4badc40152 100644 --- a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasContent.swift +++ b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasContent.swift @@ -36,10 +36,5 @@ extension Semantic.Analyses { problems.append(Problem(diagnostic: diagnostic, possibleSolutions: [])) return MarkupContainer() } - - @available(*, deprecated, renamed: "analyze(_:children:source:problems:)", message: "Use 'analyze(_:children:source:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for _: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> MarkupContainer { - analyze(directive, children: children, source: source, problems: &problems) - } } } diff --git a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasExactlyOne.swift b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasExactlyOne.swift index 1385323c14..aa05a6dd63 100644 --- a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasExactlyOne.swift +++ b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasExactlyOne.swift @@ -21,11 +21,6 @@ extension Semantic.Analyses { self.severityIfNotFound = severityIfNotFound } - @available(*, deprecated, renamed: "analyze(_:children:source:for:problems:)", message: "Use 'analyze(_:children:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> (Child?, remainder: MarkupContainer) { - analyze(directive, children: children, source: source, for: bundle, problems: &problems) - } - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) -> (Child?, remainder: MarkupContainer) { return Semantic.Analyses.extractExactlyOne( childType: Child.self, @@ -104,11 +99,6 @@ extension Semantic.Analyses { self.severityIfNotFound = severityIfNotFound } - @available(*, deprecated, renamed: "analyze(_:children:source:for:problems:)", message: "Use 'analyze(_:children:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> (Child1?, Child2?, remainder: MarkupContainer) { - analyze(directive, children: children, source: source, for: bundle, problems: &problems) - } - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) -> (Child1?, Child2?, remainder: MarkupContainer) { let (candidates, remainder) = children.categorize { child -> BlockDirective? in guard let childDirective = child as? BlockDirective else { @@ -159,11 +149,6 @@ extension Semantic.Analyses { self.severityIfNotFound = severityIfNotFound } - @available(*, deprecated, renamed: "analyze(_:children:source:for:problems:)", message: "Use 'analyze(_:children:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> ((any Media)?, remainder: MarkupContainer) { - analyze(directive, children: children, source: source, for: bundle, problems: &problems) - } - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) -> ((any Media)?, remainder: MarkupContainer) { let (foundImage, foundVideo, remainder) = HasExactlyOneOf(severityIfNotFound: severityIfNotFound).analyze(directive, children: children, source: source, for: bundle, problems: &problems) return (foundImage ?? foundVideo, remainder) @@ -177,11 +162,6 @@ extension Semantic.Analyses { self.severityIfNotFound = severityIfNotFound } - @available(*, deprecated, renamed: "analyze(_:children:source:for:problems:)", message: "Use 'analyze(_:children:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> ((any Media)?, remainder: MarkupContainer) { - analyze(directive, children: children, source: source, for: bundle, problems: &problems) - } - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, problems: inout [Problem]) -> ((any Media)?, remainder: MarkupContainer) { let (mediaDirectives, remainder) = children.categorize { child -> BlockDirective? in guard let childDirective = child as? BlockDirective else { @@ -279,11 +259,6 @@ extension Semantic.Analyses { return validElements } - @available(*, deprecated, renamed: "analyze(_:children:source:problems:)", message: "Use 'analyze(_:children:source:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for _: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> [ListElement]? { - analyze(directive, children: children, source: source, problems: &problems) - } - func firstChildElement(in markup: any Markup) -> ListElement? { return markup // ListItem .child(at: 0)? // Paragraph diff --git a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlyKnownArguments.swift b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlyKnownArguments.swift index 5ca45fae53..25da33e704 100644 --- a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlyKnownArguments.swift +++ b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlyKnownArguments.swift @@ -42,11 +42,6 @@ extension Semantic.Analyses { } return arguments } - - @available(*, deprecated, renamed: "analyze(_:children:source:problems:)", message: "Use 'analyze(_:children:source:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for _: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> [String: Markdown.DirectiveArgument] { - analyze(directive, children: children, source: source, problems: &problems) - } } } diff --git a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlyKnownDirectives.swift b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlyKnownDirectives.swift index d346015f53..ec54d1a2f3 100644 --- a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlyKnownDirectives.swift +++ b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlyKnownDirectives.swift @@ -75,11 +75,6 @@ extension Semantic.Analyses { } } } - - @available(*, deprecated, renamed: "analyze(_:children:source:problems:)", message: "Use 'analyze(_:children:source:problems:)' instead. This deprecated API will be removed after 6.2 is released") - public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for _: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) { - analyze(directive, children: children, source: source, problems: &problems) - } } } diff --git a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlySequentialHeadings.swift b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlySequentialHeadings.swift index addf05361a..b7515b4efa 100644 --- a/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlySequentialHeadings.swift +++ b/Sources/SwiftDocC/Semantics/General Purpose Analyses/HasOnlySequentialHeadings.swift @@ -35,11 +35,6 @@ extension Semantic.Analyses { self.startingFromLevel = startingFromLevel } - @available(*, deprecated, renamed: "analyze(_:children:source:for:problems:)", message: "Use 'analyze(_:children:source:for:problems:)' instead. This deprecated API will be removed after 6.2 is released") - @discardableResult public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, in _: DocumentationContext, problems: inout [Problem]) -> [Heading] { - analyze(directive, children: children, source: source, for: bundle, problems: &problems) - } - /// Returns all valid headings. @discardableResult public func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for _: DocumentationBundle, problems: inout [Problem]) -> [Heading] { var currentHeadingLevel = startingFromLevel diff --git a/Sources/SwiftDocC/Semantics/ReferenceResolver.swift b/Sources/SwiftDocC/Semantics/ReferenceResolver.swift index 9703463ad0..2bb44a90ec 100644 --- a/Sources/SwiftDocC/Semantics/ReferenceResolver.swift +++ b/Sources/SwiftDocC/Semantics/ReferenceResolver.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 @@ -252,11 +252,6 @@ struct ReferenceResolver: SemanticVisitor { return (visitMarkupContainer(MarkupContainer(markup)) as! MarkupContainer).elements.first! } - @available(*, deprecated) // This is a deprecated protocol requirement. Remove after 6.2 is released. - mutating func visitTechnology(_ technology: TutorialTableOfContents) -> Semantic { - visitTutorialTableOfContents(technology) - } - mutating func visitTutorialTableOfContents(_ tutorialTableOfContents: TutorialTableOfContents) -> Semantic { let newIntro = visit(tutorialTableOfContents.intro) as! Intro let newVolumes = tutorialTableOfContents.volumes.map { visit($0) } as! [Volume] diff --git a/Sources/SwiftDocC/Semantics/SemanticAnalysis.swift b/Sources/SwiftDocC/Semantics/SemanticAnalysis.swift deleted file mode 100644 index ac52cc8aae..0000000000 --- a/Sources/SwiftDocC/Semantics/SemanticAnalysis.swift +++ /dev/null @@ -1,76 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - 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 - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -public import Foundation -public import Markdown - -/** - A focused semantic analysis of a `BlockDirective`, recording problems and producing a result. - - A semantic analysis should check focus on the smallest meaningful aspect of the incoming `BlockDirective`. - This eases testing and helps prevent a tangle of dependencies and side effects. For every analysis, there - should be some number of tests for its robustness. - - For example, if an argument is required and is expected to be an integer, a semantic analysis - would check only that argument, attempt to convert it to an integer, and return it as the result. - - > Important: A ``SemanticAnalysis`` should not mutate outside state or directly depend on the results - of another analysis. This prevents runaway performance problems and strange bugs. - > It also makes it more amenable to parallelization should the need arise. - */ -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -public protocol SemanticAnalysis { - /** - The result of the analysis. - - > Note: This result may be `Void` as some analyses merely validate aspects of a `BlockDirective`. - */ - associatedtype Result - - /** - Perform the analysis on a directive, collect problems, and attempt to return a ``SemanticAnalysis/Result`` if required. - - - parameter directive: The `BlockDirective` that allegedly represents a ``Semantic`` object - - parameter children: The subset of `directive`'s children to analyze - - parameter source: A `URL` to the source file from which the `directive` came, if there was one. This is used for printing the location of a diagnostic. - - parameter bundle: The ``DocumentationBundle`` that owns the incoming `BlockDirective` - - parameter context: The ``DocumentationContext`` in which the bundle resides - - parameter problems: A container to append ``Problem``s encountered during the analysis - - returns: A result of the analysis if required, such as a validated parameter or subsection. - */ - func analyze(_ directive: BlockDirective, children: some Sequence, source: URL?, for bundle: DocumentationBundle, in context: DocumentationContext, problems: inout [Problem]) -> Result -} - -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.ExtractAll: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.ExtractAllMarkup: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasAtLeastOne: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasExactlyOne: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasExactlyOneOf: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasExactlyOneMedia: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasExactlyOneUnorderedList: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasExactlyOneImageOrVideoMedia: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasAtMostOne: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasContent: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasOnlyKnownArguments: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasOnlyKnownDirectives: SemanticAnalysis {} -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -extension Semantic.Analyses.HasOnlySequentialHeadings: SemanticAnalysis {} diff --git a/Sources/SwiftDocC/Semantics/Symbol/Symbol.swift b/Sources/SwiftDocC/Semantics/Symbol/Symbol.swift index 7991ebaf7e..a62aac5ba7 100644 --- a/Sources/SwiftDocC/Semantics/Symbol/Symbol.swift +++ b/Sources/SwiftDocC/Semantics/Symbol/Symbol.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 @@ -208,59 +208,24 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups /// Any dictionary keys of the symbol, if the symbol accepts keys. public var dictionaryKeysSection: DictionaryKeysSection? - @available(*, deprecated, renamed: "dictionaryKeysSection", message: "Use 'dictionaryKeysSection' instead. This deprecated API will be removed after 6.2 is released") - public var dictionaryKeysSectionVariants: DocumentationDataVariants { - get { .init(defaultVariantValue: dictionaryKeysSection) } - set { dictionaryKeysSection = newValue.firstValue } - } /// The symbol's possible values, if the symbol is a property list element with possible values. public var possibleValuesSection: PropertyListPossibleValuesSection? - @available(*, deprecated, renamed: "possibleValuesSection", message: "Use 'possibleValuesSection' instead. This deprecated API will be removed after 6.2 is released") - public var possibleValuesSectionVariants: DocumentationDataVariants { - get { .init(defaultVariantValue: possibleValuesSection) } - set { possibleValuesSection = newValue.firstValue } - } /// The HTTP endpoint of an HTTP request. public var httpEndpointSection: HTTPEndpointSection? - @available(*, deprecated, renamed: "httpEndpointSection", message: "Use 'httpEndpointSection' instead. This deprecated API will be removed after 6.2 is released") - public var httpEndpointSectionVariants: DocumentationDataVariants { - get { .init(defaultVariantValue: httpEndpointSection) } - set { httpEndpointSection = newValue.firstValue } - } - + /// The upload body of an HTTP request. public var httpBodySection: HTTPBodySection? - @available(*, deprecated, renamed: "httpBodySection", message: "Use 'httpBodySection' instead. This deprecated API will be removed after 6.2 is released") - public var httpBodySectionVariants: DocumentationDataVariants { - get { .init(defaultVariantValue: httpBodySection) } - set { httpBodySection = newValue.firstValue } - } - + /// The parameters of an HTTP request. public var httpParametersSection: HTTPParametersSection? - @available(*, deprecated, renamed: "httpParametersSection", message: "Use 'httpParametersSection' instead. This deprecated API will be removed after 6.2 is released") - public var httpParametersSectionVariants: DocumentationDataVariants { - get { .init(defaultVariantValue: httpParametersSection) } - set { httpParametersSection = newValue.firstValue } - } /// The responses of an HTTP request. public var httpResponsesSection: HTTPResponsesSection? - @available(*, deprecated, renamed: "httpResponsesSection", message: "Use 'httpResponsesSection' instead. This deprecated API will be removed after 6.2 is released") - public var httpResponsesSectionVariants: DocumentationDataVariants { - get { .init(defaultVariantValue: httpResponsesSection) } - set { httpResponsesSection = newValue.firstValue } - } /// Any redirect information of the symbol, if the symbol has been moved from another location. public var redirects: [Redirect]? - @available(*, deprecated, renamed: "redirects", message: "Use 'redirects' instead. This deprecated API will be removed after 6.2 is released") - public var redirectsVariants: DocumentationDataVariants<[Redirect]> { - get { .init(defaultVariantValue: redirects) } - set { redirects = newValue.firstValue } - } /// The symbol's abstract summary as a single paragraph, in each language variant the symbol is available in. public var abstractVariants: DocumentationDataVariants { diff --git a/Sources/SwiftDocC/Semantics/Technology/TutorialTableOfContents.swift b/Sources/SwiftDocC/Semantics/Technology/TutorialTableOfContents.swift index 1503e32f74..7e7098c0fb 100644 --- a/Sources/SwiftDocC/Semantics/Technology/TutorialTableOfContents.swift +++ b/Sources/SwiftDocC/Semantics/Technology/TutorialTableOfContents.swift @@ -120,6 +120,3 @@ public final class TutorialTableOfContents: Semantic, DirectiveConvertible, Abst return visitor.visitTutorialTableOfContents(self) } } - -@available(*, deprecated, renamed: "TutorialTableOfContents", message: "Use 'TutorialTableOfContents' instead. This deprecated API will be removed after 6.2 is released") -public typealias Technology = TutorialTableOfContents diff --git a/Sources/SwiftDocC/Semantics/TechnologyBound.swift b/Sources/SwiftDocC/Semantics/TechnologyBound.swift deleted file mode 100644 index c06ab6c3d9..0000000000 --- a/Sources/SwiftDocC/Semantics/TechnologyBound.swift +++ /dev/null @@ -1,16 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -/// An entity directly referring to the technology it belongs to. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -public protocol TechnologyBound { - /// The `name` of the ``TutorialTableOfContents`` this section refers to. - var technology: TopicReference { get } -} diff --git a/Sources/SwiftDocC/Semantics/Visitor/SemanticVisitor.swift b/Sources/SwiftDocC/Semantics/Visitor/SemanticVisitor.swift index b301551338..3426102ccc 100644 --- a/Sources/SwiftDocC/Semantics/Visitor/SemanticVisitor.swift +++ b/Sources/SwiftDocC/Semantics/Visitor/SemanticVisitor.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 @@ -86,9 +86,6 @@ public protocol SemanticVisitor { Visit a ``TutorialTableOfContents`` and return the result. */ mutating func visitTutorialTableOfContents(_ tutorialTableOfContents: TutorialTableOfContents) -> Result - - @available(*, deprecated, renamed: "visitTutorialTableOfContents(_:)", message: "Use 'visitTutorialTableOfContents(_:)' instead. This deprecated API will be removed after 6.2 is released") - mutating func visitTechnology(_ technology: TutorialTableOfContents) -> Result /** Visit an ``ImageMedia`` and return the result. @@ -151,11 +148,3 @@ extension SemanticVisitor { return semantic.accept(&self) } } - -@available(*, deprecated) // Remove this default implementation after 6.2 is released. -extension SemanticVisitor { - // We need to provide a default implementation to avoid the breaking change of a new protocol requirement. - mutating func visitTutorialTableOfContents(_ tutorialTableOfContents: TutorialTableOfContents) -> Result { - self.visitTechnology(tutorialTableOfContents) - } -} diff --git a/Sources/SwiftDocC/Semantics/Walker/SemanticWalker.swift b/Sources/SwiftDocC/Semantics/Walker/SemanticWalker.swift index 242476129a..105353b6d6 100644 --- a/Sources/SwiftDocC/Semantics/Walker/SemanticWalker.swift +++ b/Sources/SwiftDocC/Semantics/Walker/SemanticWalker.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 @@ -50,8 +50,6 @@ extension SemanticWalker { mutating func visitTile(_ tile: Tile) { descendIntoChildren(of: tile) } /// Visits a comment node. mutating func visitComment(_ comment: Comment) { descendIntoChildren(of: comment) } - @available(*, deprecated) // This is a deprecated protocol requirement. Remove after 6.2 is released. - mutating func visitTechnology(_ technology: TutorialTableOfContents) { descendIntoChildren(of: technology) } /// Visits a tutorials table-of-contents page. mutating func visitTutorialTableOfContents(_ tutorialTableOfContents: TutorialTableOfContents) { descendIntoChildren(of: tutorialTableOfContents) } /// Visits an image node. diff --git a/Sources/SwiftDocC/Semantics/Walker/Walkers/SemanticTreeDumper.swift b/Sources/SwiftDocC/Semantics/Walker/Walkers/SemanticTreeDumper.swift index bc379233c4..c34d9a541d 100644 --- a/Sources/SwiftDocC/Semantics/Walker/Walkers/SemanticTreeDumper.swift +++ b/Sources/SwiftDocC/Semantics/Walker/Walkers/SemanticTreeDumper.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 @@ -148,11 +148,6 @@ struct SemanticTreeDumper: SemanticWalker { dump(markupContainer, customDescription: description) } - @available(*, deprecated) // This is a deprecated protocol requirement. Remove after 6.2 is released - mutating func visitTechnology(_ technology: TutorialTableOfContents) { - visitTutorialTableOfContents(technology) - } - mutating func visitTutorialTableOfContents(_ tutorialTableOfContents: TutorialTableOfContents) -> () { dump(tutorialTableOfContents, customDescription: "name: '\(tutorialTableOfContents.name)'") } diff --git a/Sources/SwiftDocC/SwiftDocC.docc/Resources/Diagnostics.json b/Sources/SwiftDocC/SwiftDocC.docc/Resources/Diagnostics.json index 0c268b7b7b..bd53d8e809 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 deprecated DocC diagnostics.json digest file. This deprecated file will be removed after 6.2 is released.", + "description": "Specification of the deprecated DocC diagnostics.json digest file. This deprecated file will be removed after 6.3 is released.", "version": "0.1.0", "title": "Diagnostics" }, diff --git a/Sources/SwiftDocC/Utility/FeatureFlags.swift b/Sources/SwiftDocC/Utility/FeatureFlags.swift index b2ec4dbc5d..def7e642d1 100644 --- a/Sources/SwiftDocC/Utility/FeatureFlags.swift +++ b/Sources/SwiftDocC/Utility/FeatureFlags.swift @@ -26,30 +26,11 @@ public struct FeatureFlags: Codable { /// Whether support for automatically rendering links on symbol documentation to articles that mention that symbol is enabled. public var isMentionedInEnabled = true - @available(*, deprecated, renamed: "isMentionedInEnabled", message: "Use 'isMentionedInEnabled' instead. This deprecated API will be removed after 6.2 is released") - public var isExperimentalMentionedInEnabled: Bool { - get { isMentionedInEnabled } - set { isMentionedInEnabled = newValue } - } - /// Whether or not support for validating parameters and return value documentation is enabled. public var isParametersAndReturnsValidationEnabled = true /// Creates a set of feature flags with all default values. public init() {} - - /// Creates a set of feature flags with the given values. - /// - /// - Parameters: - /// - additionalFlags: Any additional flags to set. - /// - /// This field allows clients to set feature flags without adding new API. - @available(*, deprecated, renamed: "init()", message: "Use 'init()' instead. This deprecated API will be removed after 6.2 is released") - @_disfavoredOverload - public init( - additionalFlags: [String : Bool] = [:] - ) { - } /// Set feature flags that were loaded from a bundle's Info.plist. internal mutating func loadFlagsFromBundle(_ bundleFlags: DocumentationBundle.Info.BundleFeatureFlags) { diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift index 9d0370dda3..56e4925851 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift @@ -50,8 +50,8 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer, ExternalNodeConsumer { 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 { + @available(*, deprecated, message: "This deprecated API will be removed after 6.3 is released") + func _deprecated_consume(problems: [Problem]) throws { let diagnostics = problems.map { problem in Digest.Diagnostic(diagnostic: problem.diagnostic, rootURL: bundleRootFolder) } @@ -84,7 +84,6 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer, ExternalNodeConsumer { } } - // TODO: Supporting a single bundle for the moment. let bundleID = bundle.id assert(bundleID.rawValue == self.assetPrefixComponent, "Unexpectedly encoding assets for a bundle other than the one this output consumer was created for.") @@ -251,7 +250,7 @@ enum Digest { let downloads: [DownloadReference] } - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") + @available(*, deprecated, message: "This deprecated API will be removed after 6.3 is released") struct Diagnostic: Codable { struct Location: Codable { let line: Int @@ -270,7 +269,7 @@ enum Digest { } } -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") +@available(*, deprecated, message: "This deprecated API will be removed after 6.3 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/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift index 557b4f2a52..95b0d098c5 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-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 @@ -512,9 +512,10 @@ extension Docc { var enableMentionedIn = true // This flag only exist to allow developers to pass the previous '--enable-experimental-...' flag without errors. + // The last release to support this spelling was 6.2. @Flag(name: .customLong("enable-experimental-mentioned-in"), help: .hidden) - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - var enableExperimentalMentionedIn = false + @available(*, deprecated, message: "This flag is unused and only exist for backwards compatibility") + var _unusedExperimentalMentionedInFlagForBackwardsCompatibility = false @Flag( name: .customLong("parameters-and-returns-validation"), @@ -609,11 +610,6 @@ extension Docc { get { featureFlags.enableMentionedIn } set { featureFlags.enableMentionedIn = newValue } } - @available(*, deprecated, renamed: "enableMentionedIn", message: "Use 'enableMentionedIn' instead. This deprecated API will be removed after 6.2 is released") - public var enableExperimentalMentionedIn: Bool { - get { enableMentionedIn } - set { enableMentionedIn = newValue } - } /// A user-provided value that is true if the user enables experimental validation for parameters and return value documentation. public var enableParametersAndReturnsValidation: Bool { diff --git a/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift b/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift deleted file mode 100644 index cf021a05df..0000000000 --- a/Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift +++ /dev/null @@ -1,48 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - - -import XCTest -@testable import SwiftDocC - -// This test verifies the behavior of `DocumentationConverter` which is a deprecated type. -// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -class DocumentationConverterTests: XCTestCase { - /// An empty implementation of `ConvertOutputConsumer` that purposefully does nothing. - struct EmptyConvertOutputConsumer: ConvertOutputConsumer, ExternalNodeConsumer { - // Conformance to ConvertOutputConsumer - func consume(renderNode: RenderNode) throws { } - func consume(problems: [Problem]) throws { } - func consume(assetsInBundle bundle: DocumentationBundle) throws {} - func consume(linkableElementSummaries: [LinkDestinationSummary]) throws {} - func consume(indexingRecords: [IndexingRecord]) throws {} - func consume(assets: [RenderReferenceType: [any RenderReference]]) throws {} - func consume(benchmarks: Benchmark) throws {} - func consume(documentationCoverageInfo: [CoverageDataEntry]) throws {} - - // Conformance to ExternalNodeConsumer - func consume(externalRenderNode: SwiftDocC.ExternalRenderNode) throws { } - } - - func testThrowsErrorOnConvertingNoBundles() throws { - let rootURL = try createTemporaryDirectory() - - let dataProvider = try LocalFileSystemDataProvider(rootURL: rootURL) - let workspace = DocumentationWorkspace() - try workspace.registerProvider(dataProvider) - let context = try DocumentationContext(dataProvider: workspace) - var converter = DocumentationConverter(documentationBundleURL: rootURL, emitDigest: false, documentationCoverageOptions: .noCoverage, currentPlatforms: nil, workspace: workspace, context: context, dataProvider: dataProvider, bundleDiscoveryOptions: BundleDiscoveryOptions()) - XCTAssertThrowsError(try converter.convert(outputConsumer: EmptyConvertOutputConsumer())) { error in - let converterError = try? XCTUnwrap(error as? DocumentationConverter.Error) - XCTAssertEqual(converterError, DocumentationConverter.Error.doesNotContainBundle(url: rootURL)) - } - } -} diff --git a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift index 2d23726afb..f8875ee299 100644 --- a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift +++ b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift @@ -13,6 +13,7 @@ import SwiftDocC import SwiftDocCTestUtilities import XCTest +// THIS SHOULD BE REMOVED, RIGHT?! class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { func testNoDeprecationWarningWhenThereAreNoOtherWarnings() async throws { let catalog = Folder(name: "unit-test.docc", content: [ @@ -66,14 +67,14 @@ class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { 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.") + XCTAssertEqual(deprecationWarning.summary, "The 'diagnostics.json' digest file is deprecated and will be removed after 6.3 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, ExternalNodeConsumer { var problems: [Problem] = [] - func consume(problems: [Problem]) throws { + func _deprecated_consume(problems: [Problem]) throws { self.problems.append(contentsOf: problems) } diff --git a/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift b/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift index bd543071c2..323fe5a2c5 100644 --- a/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.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 @@ -26,97 +26,6 @@ class BundleDiscoveryTests: XCTestCase { return files } - // This tests registration of multiple catalogs which is deprecated - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - func testFirstBundle() throws { - let url = try createTemporaryDirectory() - // Create 3 minimal doc bundles - for i in 1 ... 3 { - let nestedBundle = Folder(name: "TestBundle\(i).docc", content: [ - InfoPlist(displayName: "Test Bundle \(i)", identifier: "com.example.bundle\(i)"), - TextFile(name: "Root.md", utf8Content: """ - # Test Bundle \(i) - @Metadata { - @TechnologyRoot - } - Abstract. - - Content. - """), - ]) - _ = try nestedBundle.write(inside: url) - } - - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let dataProvider = try LocalFileSystemDataProvider(rootURL: url) - try workspace.registerProvider(dataProvider) - - // Verify all bundles are loaded - XCTAssertEqual(context.registeredBundles.map { $0.identifier }.sorted(), - ["com.example.bundle1", "com.example.bundle2", "com.example.bundle3"] - ) - - // Verify the first one is bundle1 - let converter = DocumentationConverter(documentationBundleURL: url, emitDigest: false, documentationCoverageOptions: .noCoverage, currentPlatforms: nil, workspace: workspace, context: context, dataProvider: dataProvider, bundleDiscoveryOptions: .init()) - XCTAssertEqual(converter.firstAvailableBundle()?.identifier, "com.example.bundle1") - } - - // This test registration more than once data provider which is deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - func testLoadComplexWorkspace() throws { - let allFiles = try flatListOfFiles() - let workspace = Folder(name: "TestWorkspace", content: [ - CopyOfFolder(original: testBundleLocation), - Folder(name: "nested", content: [ - Folder(name: "irrelevant", content: [ - TextFile(name: "irrelevant.txt", utf8Content: "distraction"), - ]), - TextFile(name: "irrelevant.txt", utf8Content: "distraction"), - Folder(name: "TestBundle2.docc", content: [ - InfoPlist(displayName: "Test Bundle", identifier: "com.example.bundle2"), - Folder(name: "Subfolder", content: // All files flattened into one folder - allFiles.map { CopyOfFile(original: $0) } - ), - ]), - ]), - ]) - - let tempURL = try createTemporaryDirectory() - - let workspaceURL = try workspace.write(inside: tempURL) - - let dataProvider = try LocalFileSystemDataProvider(rootURL: workspaceURL) - - let bundles = (try dataProvider.bundles()).sorted { (bundle1, bundle2) -> Bool in - return bundle1.identifier < bundle2.identifier - } - - XCTAssertEqual(bundles.count, 2) - - guard bundles.count == 2 else { return } - - XCTAssertEqual(bundles[0].identifier, "com.example.bundle2") - XCTAssertEqual(bundles[1].identifier, "org.swift.docc.example") - - func checkBundle(_ bundle: DocumentationBundle) { - XCTAssertEqual(bundle.displayName, "Test Bundle") - XCTAssertEqual(bundle.symbolGraphURLs.count, 4) - XCTAssertTrue(bundle.symbolGraphURLs.map { $0.lastPathComponent }.contains("mykit-iOS.symbols.json")) - XCTAssertTrue(bundle.symbolGraphURLs.map { $0.lastPathComponent }.contains("MyKit@SideKit.symbols.json")) - XCTAssertTrue(bundle.symbolGraphURLs.map { $0.lastPathComponent }.contains("sidekit.symbols.json")) - XCTAssertTrue(bundle.symbolGraphURLs.map { $0.lastPathComponent }.contains("FillIntroduced.symbols.json")) - XCTAssertFalse(bundle.markupURLs.isEmpty) - XCTAssertTrue(bundle.miscResourceURLs.map { $0.lastPathComponent }.sorted().contains("intro.png")) - } - - for bundle in bundles { - checkBundle(bundle) - } - } - func testBundleFormat() throws { let allFiles = try flatListOfFiles() diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index 2d55fa1a10..11326bef88 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -36,52 +36,6 @@ extension CollectionDifference { } class DocumentationContextTests: XCTestCase { - // This test checks unregistration of workspace data providers which is deprecated - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - func testResolve() async throws { - let workspace = DocumentationWorkspace() - let context = try DocumentationContext(dataProvider: workspace) - let bundle = try await testBundle(named: "LegacyBundle_DoNotUseInNewTests") - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) - - // Test resolving - let unresolved = UnresolvedTopicReference(topicURL: ValidatedURL(parsingExact: "doc:/TestTutorial")!) - let parent = ResolvedTopicReference(bundleIdentifier: bundle.id.rawValue, path: "", sourceLanguage: .swift) - - guard case let .success(resolved) = context.resolve(.unresolved(unresolved), in: parent) else { - XCTFail("Couldn't resolve \(unresolved)") - return - } - - XCTAssertEqual(parent.bundleIdentifier, resolved.bundleIdentifier) - XCTAssertEqual("/tutorials/Test-Bundle/TestTutorial", resolved.path) - - // Test lowercasing of path - let unresolvedUppercase = UnresolvedTopicReference(topicURL: ValidatedURL(parsingExact: "doc:/TESTTUTORIAL")!) - guard case .failure = context.resolve(.unresolved(unresolvedUppercase), in: parent) else { - XCTFail("Did incorrectly resolve \(unresolvedUppercase)") - return - } - - // Test expected URLs - let expectedURL = URL(string: "doc://org.swift.docc.example/tutorials/Test-Bundle/TestTutorial") - XCTAssertEqual(expectedURL, resolved.url) - - guard context.documentURL(for: resolved) != nil else { - XCTFail("Couldn't resolve file URL for \(resolved)") - return - } - - try workspace.unregisterProvider(dataProvider) - - guard case .failure = context.resolve(.unresolved(unresolved), in: parent) else { - XCTFail("Unexpectedly resolved \(unresolved.topicURL) despite removing a data provider for it") - return - } - } - func testLoadEntity() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") @@ -651,35 +605,6 @@ class DocumentationContextTests: XCTestCase { XCTAssertEqual(downloadsAfter.first?.variants.values.first?.lastPathComponent, "intro.png") } - // This test registration more than once data provider which is deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - func testCreatesCorrectIdentifiers() throws { - let testBundleLocation = Bundle.module.url( - forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")! - let workspaceContent = Folder(name: "TestWorkspace", content: [ - CopyOfFolder(original: testBundleLocation), - - Folder(name: "TestBundle2.docc", content: [ - InfoPlist(displayName: "Test Bundle", identifier: "com.example.bundle2"), - CopyOfFolder(original: testBundleLocation, newName: "Subfolder", filter: { $0.lastPathComponent != "Info.plist" }), - ]) - ]) - - let tempURL = try createTemporaryDirectory() - - let workspaceURL = try workspaceContent.write(inside: tempURL) - let dataProvider = try LocalFileSystemDataProvider(rootURL: workspaceURL) - - let workspace = DocumentationWorkspace() - try workspace.registerProvider(dataProvider) - - let context = try DocumentationContext(dataProvider: workspace) - let identifiers = context.knownIdentifiers - let identifierSet = Set(identifiers) - XCTAssertEqual(identifiers.count, identifierSet.count, "Found duplicate identifiers.") - } - func testDetectsReferenceCollision() async throws { let (_, context) = try await testBundleAndContext(named: "TestBundleWithDupe") diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationWorkspaceTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationWorkspaceTests.swift deleted file mode 100644 index 81ea209019..0000000000 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationWorkspaceTests.swift +++ /dev/null @@ -1,206 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import XCTest -@testable import SwiftDocC - -// This test verifies the behavior of `DocumentationWorkspace` which is a deprecated type. -// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -class DocumentationWorkspaceTests: XCTestCase { - func testEmptyWorkspace() { - let workspace = DocumentationWorkspace() - let workspaceDelegate = SimpleWorkspaceDelegate() - workspace.delegate = workspaceDelegate - - XCTAssertEqual(workspace.bundles.count, 0) - - XCTAssertEqual(workspaceDelegate.record, []) - - checkTestWorkspaceContents(workspace: workspace, bundles: [SimpleDataProvider.bundle1, SimpleDataProvider.bundle2], filled: false) - } - - func testRegisterProvider() throws { - let provider = SimpleDataProvider(bundles: [SimpleDataProvider.bundle1, SimpleDataProvider.bundle2]) - let workspace = DocumentationWorkspace() - let workspaceDelegate = SimpleWorkspaceDelegate() - workspace.delegate = workspaceDelegate - - try workspace.registerProvider(provider) - - let events: [SimpleWorkspaceDelegate.Event] = provider._bundles.map { .add($0.identifier) } - - XCTAssertEqual(workspace.bundles.count, 2) - for bundlePair in workspace.bundles { - XCTAssertEqual(bundlePair.key, bundlePair.value.identifier) - } - - XCTAssertEqual(Set(workspace.bundles.map { $0.value.identifier }), Set(provider._bundles.map { $0.identifier })) - XCTAssertEqual(workspaceDelegate.record, events) - - checkTestWorkspaceContents(workspace: workspace, bundles: provider._bundles, filled: true) - } - - func testUnregisterProvider() throws { - let provider = SimpleDataProvider(bundles: [SimpleDataProvider.bundle1, SimpleDataProvider.bundle2]) - let workspace = DocumentationWorkspace() - let workspaceDelegate = SimpleWorkspaceDelegate() - workspace.delegate = workspaceDelegate - - try workspace.registerProvider(provider) - - var events: [SimpleWorkspaceDelegate.Event] = provider._bundles.map { .add($0.identifier) } - - XCTAssertEqual(workspace.bundles.count, 2) - for bundlePair in workspace.bundles { - XCTAssertEqual(bundlePair.key, bundlePair.value.identifier) - } - - XCTAssertEqual(Set(workspace.bundles.map { $0.value.identifier }), Set(provider._bundles.map { $0.identifier })) - XCTAssertEqual(workspaceDelegate.record, events) - - checkTestWorkspaceContents(workspace: workspace, bundles: provider._bundles, filled: true) - - try workspace.unregisterProvider(provider) - - events.append(contentsOf: provider._bundles.map { .remove($0.identifier) }) - - XCTAssertEqual(workspace.bundles.count, 0) - XCTAssertEqual(workspaceDelegate.record, events) - - checkTestWorkspaceContents(workspace: workspace, bundles: provider._bundles, filled: false) - } - - func testMultipleProviders() throws { - let provider1 = SimpleDataProvider(bundles: [SimpleDataProvider.bundle1, SimpleDataProvider.bundle2]) - let workspace = DocumentationWorkspace() - let workspaceDelegate = SimpleWorkspaceDelegate() - workspace.delegate = workspaceDelegate - - try workspace.registerProvider(provider1) - - var events: [SimpleWorkspaceDelegate.Event] = provider1._bundles.map { .add($0.identifier) } - - XCTAssertEqual(workspace.bundles.count, 2) - for bundlePair in workspace.bundles { - XCTAssertEqual(bundlePair.key, bundlePair.value.identifier) - } - - XCTAssertEqual(Set(workspace.bundles.map { $0.value.identifier }), Set(provider1._bundles.map { $0.identifier })) - XCTAssertEqual(workspaceDelegate.record, events) - - checkTestWorkspaceContents(workspace: workspace, bundles: provider1._bundles, filled: true) - - let provider2 = SimpleDataProvider(bundles: [SimpleDataProvider.bundle3, SimpleDataProvider.bundle4]) - try workspace.registerProvider(provider2) - - events.append(contentsOf: provider2._bundles.map { .add($0.identifier) }) - - XCTAssertEqual(workspace.bundles.count, 4) - for bundlePair in workspace.bundles { - XCTAssertEqual(bundlePair.key, bundlePair.value.identifier) - } - - XCTAssertEqual(Set(workspace.bundles.map { $0.value.identifier }), Set(provider1._bundles.map { $0.identifier } + provider2._bundles.map { $0.identifier })) - XCTAssertEqual(workspaceDelegate.record, events) - - checkTestWorkspaceContents(workspace: workspace, bundles: provider1._bundles + provider2._bundles, filled: true) - } - - func checkTestWorkspaceContents(workspace: DocumentationWorkspace, bundles: [DocumentationBundle], filled: Bool, line: UInt = #line) { - func check(file: URL, bundle: DocumentationBundle, line: UInt) { - if filled { - XCTAssertEqual(try workspace.contentsOfURL(file, in: bundle), SimpleDataProvider.files[file]!, line: line) - } else { - XCTAssertThrowsError(try workspace.contentsOfURL(file, in: bundle), line: line) - } - } - - for bundle in bundles { - check(file: SimpleDataProvider.testMarkupFile, bundle: bundle, line: line) - check(file: SimpleDataProvider.testResourceFile, bundle: bundle, line: line) - check(file: SimpleDataProvider.testSymbolGraphFile, bundle: bundle, line: line) - } - } - - struct SimpleDataProvider: DocumentationWorkspaceDataProvider { - let identifier: String = UUID().uuidString - - static let testMarkupFile = URL(fileURLWithPath: "/test.documentation/markup.md") - static let testResourceFile = URL(fileURLWithPath: "/test.documentation/resource.png") - static let testSymbolGraphFile = URL(fileURLWithPath: "/test.documentation/graph.json") - - static var files: [URL: Data] = [ - testMarkupFile: staticDataFromString("markup"), - testResourceFile: staticDataFromString("image"), - testSymbolGraphFile: staticDataFromString("symbols"), - ] - - private static func staticDataFromString(_ string: String) -> Data { - return string.data(using: .utf8)! - } - - static func bundle(_ suffix: String) -> DocumentationBundle { - return DocumentationBundle( - info: DocumentationBundle.Info( - displayName: "Test" + suffix, - id: DocumentationBundle.Identifier(rawValue: "com.example.test" + suffix) - ), - symbolGraphURLs: [testSymbolGraphFile], - markupURLs: [testMarkupFile], - miscResourceURLs: [testResourceFile] - ) - } - - static let bundle1 = bundle("1") - static let bundle2 = bundle("2") - static let bundle3 = bundle("3") - static let bundle4 = bundle("4") - - enum ProviderError: Error { - case missing - } - - func contentsOfURL(_ url: URL) throws -> Data { - guard let data = SimpleDataProvider.files[url] else { - throw ProviderError.missing - } - - return data - } - - var _bundles: [DocumentationBundle] = [] - - func bundles(options: BundleDiscoveryOptions) throws -> [DocumentationBundle] { - // Ignore the bundle discovery options. These test bundles are already built. - return _bundles - } - - init(bundles: [DocumentationBundle]) { - self._bundles = bundles - } - } - - class SimpleWorkspaceDelegate: DocumentationContextDataProviderDelegate { - enum Event: Equatable { - case add(String) - case remove(String) - } - var record: [Event] = [] - - func dataProvider(_ dataProvider: any DocumentationContextDataProvider, didAddBundle bundle: DocumentationBundle) throws { - record.append(.add(bundle.identifier)) - } - - func dataProvider(_ dataProvider: any DocumentationContextDataProvider, didRemoveBundle bundle: DocumentationBundle) throws { - record.append(.remove(bundle.identifier)) - } - } -} diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 2c55f82aa7..07cba25e1d 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -153,72 +153,6 @@ class ExternalReferenceResolverTests: XCTestCase { ) } - // This test verifies the behavior of a deprecated functionality (changing external documentation sources after registering the documentation) - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") - func testResolvesReferencesExternallyOnlyWhenFallbackResolversAreSet() async throws { - let workspace = DocumentationWorkspace() - let bundle = try await testBundle(named: "LegacyBundle_DoNotUseInNewTests") - let dataProvider = PrebuiltLocalFileSystemDataProvider(bundles: [bundle]) - try workspace.registerProvider(dataProvider) - let context = try DocumentationContext(dataProvider: workspace) - let bundleIdentifier = bundle.identifier - - let unresolved = UnresolvedTopicReference(topicURL: ValidatedURL(parsingExact: "doc://\(bundleIdentifier)/ArticleThatDoesNotExistInLocally")!) - let parent = ResolvedTopicReference(bundleIdentifier: bundle.id.rawValue, path: "", sourceLanguage: .swift) - - do { - context.configuration.externalDocumentationConfiguration.sources = [:] - context.configuration.convertServiceConfiguration.fallbackResolver = nil - - if case .success = context.resolve(.unresolved(unresolved), in: parent) { - XCTFail("The reference was unexpectedly resolved.") - } - } - - do { - class TestFallbackResolver: ConvertServiceFallbackResolver { - init(bundleID: DocumentationBundle.Identifier) { - resolver.bundleID = bundleID - } - var bundleID: DocumentationBundle.Identifier { - resolver.bundleID - } - private var resolver = TestExternalReferenceResolver() - func resolve(_ reference: SwiftDocC.TopicReference) -> TopicReferenceResolutionResult { - TestExternalReferenceResolver().resolve(reference) - } - func entityIfPreviouslyResolved(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity? { - nil - } - func resolve(assetNamed assetName: String) -> DataAsset? { - nil - } - } - - context.configuration.externalDocumentationConfiguration.sources = [:] - context.configuration.convertServiceConfiguration.fallbackResolver = TestFallbackResolver(bundleID: "org.swift.docc.example") - - guard case let .success(resolved) = context.resolve(.unresolved(unresolved), in: parent) else { - XCTFail("The reference was unexpectedly unresolved.") - return - } - - XCTAssertEqual("com.external.testbundle", resolved.bundleIdentifier) - XCTAssertEqual("/externally/resolved/path", resolved.path) - - let expectedURL = URL(string: "doc://com.external.testbundle/externally/resolved/path") - XCTAssertEqual(expectedURL, resolved.url) - - try workspace.unregisterProvider(dataProvider) - context.configuration.externalDocumentationConfiguration.sources = [:] - guard case .failure = context.resolve(.unresolved(unresolved), in: parent) else { - XCTFail("Unexpectedly resolved \(unresolved.topicURL) despite removing a data provider for it") - return - } - } - } - func testLoadEntityForExternalReference() async throws { let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : TestExternalReferenceResolver()]) let identifier = ResolvedTopicReference(bundleID: "com.external.testbundle", path: "/externally/resolved/path", sourceLanguage: .swift) diff --git a/Tests/SwiftDocCTests/Infrastructure/GeneratedDataProvider.swift b/Tests/SwiftDocCTests/Infrastructure/GeneratedDataProvider.swift deleted file mode 100644 index 56360a8793..0000000000 --- a/Tests/SwiftDocCTests/Infrastructure/GeneratedDataProvider.swift +++ /dev/null @@ -1,154 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import XCTest -@testable import SwiftDocC -import SymbolKit - -// This test verifies the behavior of `GeneratedDataProvider` which is a deprecated type. -// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. -@available(*, deprecated, message: "Use 'DocumentationContext.InputProvider' instead. This deprecated API will be removed after 6.2 is released") -class GeneratedDataProviderTests: XCTestCase { - - func testGeneratingBundles() throws { - let firstSymbolGraph = SymbolGraph( - metadata: .init( - formatVersion: .init(major: 0, minor: 0, patch: 1), - generator: "unit-test" - ), - module: .init( - name: "FirstModuleName", - platform: .init() - ), - symbols: [], - relationships: [] - ) - var secondSymbolGraph = firstSymbolGraph - secondSymbolGraph.module.name = "SecondModuleName" - - let thirdSymbolGraph = firstSymbolGraph // Another symbol graph with the same module name - - let dataProvider = GeneratedDataProvider( - symbolGraphDataLoader: { - switch $0.lastPathComponent { - case "first.symbols.json": - return try? JSONEncoder().encode(firstSymbolGraph) - case "second.symbols.json": - return try? JSONEncoder().encode(secondSymbolGraph) - case "third.symbols.json": - return try? JSONEncoder().encode(thirdSymbolGraph) - default: - return nil - } - } - ) - - let options = BundleDiscoveryOptions( - infoPlistFallbacks: [ - "CFBundleDisplayName": "Custom Display Name", - "CFBundleIdentifier": "com.test.example", - ], - additionalSymbolGraphFiles: [ - URL(fileURLWithPath: "first.symbols.json"), - URL(fileURLWithPath: "second.symbols.json"), - URL(fileURLWithPath: "third.symbols.json"), - ] - ) - let bundles = try dataProvider.bundles(options: options) - XCTAssertEqual(bundles.count, 1) - let bundle = try XCTUnwrap(bundles.first) - - XCTAssertEqual(bundle.displayName, "Custom Display Name") - XCTAssertEqual(bundle.symbolGraphURLs.map { $0.lastPathComponent }.sorted(), [ - "first.symbols.json", - "second.symbols.json", - "third.symbols.json", - - ]) - XCTAssertEqual(bundle.markupURLs.map { $0.path }.sorted(), [ - "FirstModuleName.md", - "SecondModuleName.md", - // No third file since that symbol graph has the same module name as the first - ]) - - XCTAssertEqual( - try String(data: dataProvider.contentsOfURL(URL(fileURLWithPath: "FirstModuleName.md")), encoding: .utf8), - "# ``FirstModuleName``" - ) - XCTAssertEqual( - try String(data: dataProvider.contentsOfURL(URL(fileURLWithPath: "SecondModuleName.md")), encoding: .utf8), - "# ``SecondModuleName``" - ) - } - - func testGeneratingSingleModuleBundle() throws { - let firstSymbolGraph = SymbolGraph( - metadata: .init( - formatVersion: .init(major: 0, minor: 0, patch: 1), - generator: "unit-test" - ), - module: .init( - name: "FirstModuleName", - platform: .init() - ), - symbols: [], - relationships: [] - ) - - let secondSymbolGraph = firstSymbolGraph // Another symbol graph with the same module name - - let dataProvider = GeneratedDataProvider( - symbolGraphDataLoader: { - switch $0.lastPathComponent { - case "first.symbols.json": - return try? JSONEncoder().encode(firstSymbolGraph) - case "second.symbols.json": - return try? JSONEncoder().encode(secondSymbolGraph) - default: - return nil - } - } - ) - - let options = BundleDiscoveryOptions( - infoPlistFallbacks: [ - "CFBundleDisplayName": "Custom Display Name", - "CFBundleIdentifier": "com.test.example", - ], - additionalSymbolGraphFiles: [ - URL(fileURLWithPath: "first.symbols.json"), - URL(fileURLWithPath: "second.symbols.json"), - ] - ) - let bundles = try dataProvider.bundles(options: options) - XCTAssertEqual(bundles.count, 1) - let bundle = try XCTUnwrap(bundles.first) - - XCTAssertEqual(bundle.displayName, "Custom Display Name") - XCTAssertEqual(bundle.symbolGraphURLs.map { $0.lastPathComponent }.sorted(), [ - "first.symbols.json", - "second.symbols.json", - ]) - XCTAssertEqual(bundle.markupURLs.map { $0.path }.sorted(), [ - "FirstModuleName.md", - // No second file since that symbol graph has the same module name as the first - ]) - - XCTAssertEqual( - try String(data: dataProvider.contentsOfURL(URL(fileURLWithPath: "FirstModuleName.md")), encoding: .utf8), """ - # ``FirstModuleName`` - - @Metadata { - @DisplayName("Custom Display Name") - } - """ - ) - } -} diff --git a/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift b/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift index 5778863dda..2d6edd7e57 100644 --- a/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.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 @@ -14,9 +14,6 @@ import SwiftDocCTestUtilities class DocumentationInputsProviderTests: XCTestCase { - // After 6.2 we can update this test to verify that the input provider discovers the same inputs regardless of FileManagerProtocol - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated, message: "This test uses `LocalFileSystemDataProvider` as a `DocumentationWorkspaceDataProvider` which is deprecated and will be removed after 6.2 is released") func testDiscoversSameFilesAsPreviousImplementation() throws { let folderHierarchy = Folder(name: "one", content: [ Folder(name: "two", content: [ @@ -68,38 +65,26 @@ class DocumentationInputsProviderTests: XCTestCase { Folder(name: "OutsideSearchScope.docc", content: []), ]) + // Prepare the real on-disk file system let tempDirectory = try createTempFolder(content: [folderHierarchy]) - let realProvider = DocumentationContext.InputsProvider(fileManager: FileManager.default) - let testFileSystem = try TestFileSystem(folders: [folderHierarchy]) - let testProvider = DocumentationContext.InputsProvider(fileManager: testFileSystem) - - let options = BundleDiscoveryOptions(fallbackIdentifier: "com.example.test", additionalSymbolGraphFiles: [ - tempDirectory.appendingPathComponent("/path/to/SomethingAdditional.symbols.json") - ]) + // Prepare the test file system + let testFileSystem = try TestFileSystem(folders: []) + try testFileSystem.addFolder(folderHierarchy, basePath: tempDirectory) - let foundPrevImplBundle = try XCTUnwrap(LocalFileSystemDataProvider(rootURL: tempDirectory.appendingPathComponent("/one/two")).bundles(options: options).first) - let (foundRealBundle, _) = try XCTUnwrap(realProvider.inputsAndDataProvider(startingPoint: tempDirectory.appendingPathComponent("/one/two"), options: options)) - - let (foundTestBundle, _) = try XCTUnwrap(testProvider.inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/one/two"), options: .init( - infoPlistFallbacks: options.infoPlistFallbacks, - // The test file system has a default base URL and needs different URLs for the symbol graph files - additionalSymbolGraphFiles: [ - URL(fileURLWithPath: "/path/to/SomethingAdditional.symbols.json") + for fileManager in [FileManager.default as FileManagerProtocol, testFileSystem as FileManagerProtocol] { + let inputsProvider = DocumentationContext.InputsProvider(fileManager: fileManager) + let options = BundleDiscoveryOptions(fallbackIdentifier: "com.example.test", additionalSymbolGraphFiles: [ + tempDirectory.appendingPathComponent("/path/to/SomethingAdditional.symbols.json") ]) - )) - - for (bundle, relativeBase) in [ - (foundPrevImplBundle, tempDirectory.appendingPathComponent("/one/two/three")), - (foundRealBundle, tempDirectory.appendingPathComponent("/one/two/three")), - (foundTestBundle, URL(fileURLWithPath: "/one/two/three")), - ] { + let (bundle, _) = try XCTUnwrap(inputsProvider.inputsAndDataProvider(startingPoint: tempDirectory.appendingPathComponent("/one/two"), options: options)) + func relativePathString(_ url: URL) -> String { - url.relative(to: relativeBase)!.path + url.relative(to: tempDirectory.appendingPathComponent("/one/two/three"))!.path } XCTAssertEqual(bundle.displayName, "CustomDisplayName") - XCTAssertEqual(bundle.identifier, "com.example.test") + XCTAssertEqual(bundle.id, "com.example.test") XCTAssertEqual(bundle.markupURLs.map(relativePathString).sorted(), [ "Found.docc/CCC.md", "Found.docc/Inner/DDD.md", diff --git a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/AbsoluteSymbolLinkTests.swift b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/AbsoluteSymbolLinkTests.swift deleted file mode 100644 index e6386f511f..0000000000 --- a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/AbsoluteSymbolLinkTests.swift +++ /dev/null @@ -1,870 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import Foundation -import XCTest -@testable import SwiftDocC - -// This test uses ``AbsoluteSymbolLink`` which is deprecated. -// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -class AbsoluteSymbolLinkTests: XCTestCase { - func testCreationOfValidLinks() throws { - let validLinks = [ - "doc://org.swift.ShapeKit/documentation/ShapeKit", - "doc://org.swift.ShapeKit/documentation/ShapeKit/ParentType/Test-swift.class/", - "doc://org.swift.ShapeKit/documentation/ShapeKit/ParentType/Test-swift.class/testFunc()-k2k9d", - ] - - let expectedLinkDescriptions = [ - """ - { - bundleID: 'org.swift.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'ShapeKit', suffix: (none)), - representsModule: true, - basePathComponents: [] - } - """, - """ - { - bundleID: 'org.swift.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'ParentType', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'Test', suffix: (kind: 'swift.class'))] - } - """, - """ - { - bundleID: 'org.swift.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'ParentType', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'Test', suffix: (kind: 'swift.class')), (name: 'testFunc()', suffix: (idHash: 'k2k9d'))] - } - """, - ] - - let absoluteSymbolLinks = validLinks.compactMap(AbsoluteSymbolLink.init(string:)) - - XCTAssertEqual(absoluteSymbolLinks.count, expectedLinkDescriptions.count) - - for (absoluteSymbolLink, expectedDescription) in zip(absoluteSymbolLinks, expectedLinkDescriptions) { - XCTAssertEqual(absoluteSymbolLink.description, expectedDescription) - } - } - - func testCreationOfInvalidLinkWithBadScheme() { - XCTAssertNil( - AbsoluteSymbolLink(string: "dc://org.swift.ShapeKit/documentation/ShapeKit") - ) - - XCTAssertNil( - AbsoluteSymbolLink(string: "http://org.swift.ShapeKit/documentation/ShapeKit") - ) - - XCTAssertNil( - AbsoluteSymbolLink(string: "https://org.swift.ShapeKit/documentation/ShapeKit") - ) - } - - func testCreationOfInvalidLinkWithoutDocumentationPath() { - XCTAssertNil( - AbsoluteSymbolLink(string: "doc://org.swift.ShapeKit/tutorials/ShapeKit") - ) - - XCTAssertNil( - AbsoluteSymbolLink(string: "doc://org.swift.ShapeKit/ShapeKit") - ) - } - - func testCreationOfInvalidLinkWithNoBundleID() { - XCTAssertNil( - AbsoluteSymbolLink(string: "doc:///documentation/ShapeKit") - ) - XCTAssertNil( - AbsoluteSymbolLink(string: "doc:/documentation/ShapeKit") - ) - } - - func testCreationOfInvalidLinkWithBadSuffix() { - XCTAssertNil( - // Empty suffix - AbsoluteSymbolLink(string: "doc://org.swift.ShapeKit/ShapeKit/ParentType/Test-swift.class/testFunc()-") - ) - - XCTAssertNil( - // Empty suffix - AbsoluteSymbolLink(string: "doc://org.swift.ShapeKit/ShapeKit/ParentType/Test-/testFunc()") - ) - - XCTAssertNil( - // Empty suffix - AbsoluteSymbolLink(string: "doc://org.swift.ShapeKit/ShapeKit/ParentType-/Test/testFunc()") - ) - - XCTAssertNil( - // Empty suffix - AbsoluteSymbolLink(string: "doc://org.swift.ShapeKit/ShapeKit-/ParentType/Test/testFunc()") - ) - - XCTAssertNil( - // Invalid type - AbsoluteSymbolLink(string: "doc://org.swift.ShapeKit/ShapeKit/ParentType/Test-swift.class/testFunc()-swift.funny-1s4Rt") - ) - - XCTAssertNil( - // Invalid type - AbsoluteSymbolLink(string: "doc://org.swift.ShapeKit/ShapeKit/ParentType/Test-swift.clss-5f7h9/testFunc()") - ) - } - - func testCreationOfValidLinksFromRenderNode() throws { - let symbolJSON = try String(contentsOf: Bundle.module.url( - forResource: "symbol-with-automatic-see-also-section", withExtension: "json", - subdirectory: "Converter Fixtures")!) - - let renderNode = try RenderNodeTransformer(renderNodeData: symbolJSON.data(using: .utf8)!) - - let references = Array(renderNode.renderNode.references.keys).sorted() - - let absoluteSymbolLinks = references.map(AbsoluteSymbolLink.init(string:)) - let absoluteSymbolLinkDescriptions = absoluteSymbolLinks.map(\.?.description) - - let expectedDescriptions: [String?] = [ - // doc://org.swift.docc.example/documentation/MyKit - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyKit', suffix: (none)), - representsModule: true, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/MyKit-Basics: (This is an article link) - nil, - // doc://org.swift.docc.example/documentation/MyKit/MyClass: - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyClass', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/MyClass/init(_:)-3743d: - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'init(_:)', suffix: (idHash: '3743d'))] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/MyClass/init(_:)-98u07: - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'init(_:)', suffix: (idHash: '98u07'))] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/MyClass/myFunction(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'myFunction()', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/YourClass: - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'YourClass', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/Reference-From-Automatic-SeeAlso-Section-Only: - nil, - // doc://org.swift.docc.example/documentation/Reference-In-Automatic-SeeAlso-And-Fragments: - nil, - // doc://org.swift.docc.example/tutorials/TechnologyX/Tutorial: (Tutorials link): - nil, - // doc://org.swift.docc.example/tutorials/TechnologyX/Tutorial2: - nil, - // doc://org.swift.docc.example/tutorials/TechnologyX/Tutorial4: - nil, - ] - - for (index, expectedDescription) in expectedDescriptions.enumerated() { - XCTAssertEqual( - absoluteSymbolLinkDescriptions[index], - expectedDescription, - """ - Failed to correctly construct link from '\(references[index])' - """ - ) - } - } - - func testCompileSymbolGraphAndValidateLinks() async throws { - let (_, _, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let expectedDescriptions = [ - // doc://org.swift.docc.example/documentation/FillIntroduced: - """ - { - bundleID: 'org.swift.docc.example', - module: 'FillIntroduced', - topLevelSymbol: (name: 'FillIntroduced', suffix: (none)), - representsModule: true, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/FillIntroduced/iOSMacOSOnly(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'FillIntroduced', - topLevelSymbol: (name: 'iOSMacOSOnly()', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/FillIntroduced/iOSOnlyDeprecated(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'FillIntroduced', - topLevelSymbol: (name: 'iOSOnlyDeprecated()', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/FillIntroduced/iOSOnlyIntroduced(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'FillIntroduced', - topLevelSymbol: (name: 'iOSOnlyIntroduced()', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/FillIntroduced/macCatalystOnlyDeprecated(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'FillIntroduced', - topLevelSymbol: (name: 'macCatalystOnlyDeprecated()', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/FillIntroduced/macCatalystOnlyIntroduced(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'FillIntroduced', - topLevelSymbol: (name: 'macCatalystOnlyIntroduced()', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/FillIntroduced/macOSOnlyDeprecated(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'FillIntroduced', - topLevelSymbol: (name: 'macOSOnlyDeprecated()', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/FillIntroduced/macOSOnlyIntroduced(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'FillIntroduced', - topLevelSymbol: (name: 'macOSOnlyIntroduced()', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/MyKit: - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyKit', suffix: (none)), - representsModule: true, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/MyClass: - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyClass', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/MyClass/init()-33vaw: - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'init()', suffix: (idHash: '33vaw'))] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/MyClass/init()-3743d: - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'init()', suffix: (idHash: '3743d'))] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/MyClass/myFunction(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'myFunction()', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/MyProtocol: - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'MyProtocol', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/MyKit/globalFunction(_:considering:): - """ - { - bundleID: 'org.swift.docc.example', - module: 'MyKit', - topLevelSymbol: (name: 'globalFunction(_:considering:)', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/SideKit: - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideKit', suffix: (none)), - representsModule: true, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideClass: - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideClass', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideClass/Element: - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'Element', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideClass/Element/inherited(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'Element', suffix: (none)), (name: 'inherited()', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideClass/Value(_:): - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'Value(_:)', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideClass/init(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'init()', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideClass/myFunction(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'myFunction()', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideClass/path: - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'path', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideClass/url: - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'url', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideProtocol: - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideProtocol', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideProtocol/func(): - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideProtocol', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'func()', suffix: (none))] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/SideProtocol/func()-2dxqn: - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'SideProtocol', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'func()', suffix: (idHash: '2dxqn'))] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/UncuratedClass: - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'UncuratedClass', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://org.swift.docc.example/documentation/SideKit/UncuratedClass/angle: - """ - { - bundleID: 'org.swift.docc.example', - module: 'SideKit', - topLevelSymbol: (name: 'UncuratedClass', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'angle', suffix: (none))] - } - """, - ] - XCTAssertEqual(expectedDescriptions.count, context.documentationCache.symbolReferences.count) - - let validatedSymbolLinkDescriptions = context.documentationCache.symbolReferences - .map(\.url.absoluteString) - .sorted() - .compactMap(AbsoluteSymbolLink.init(string:)) - .map(\.description) - - XCTAssertEqual(validatedSymbolLinkDescriptions.count, context.documentationCache.symbolReferences.count) - for (symbolLinkDescription, expectedDescription) in zip(validatedSymbolLinkDescriptions, expectedDescriptions) { - XCTAssertEqual(symbolLinkDescription, expectedDescription) - } - } - - func testCompileOverloadedSymbolGraphAndValidateLinks() async throws { - let (_, _, context) = try await testBundleAndContext(named: "OverloadedSymbols") - - let expectedDescriptions = [ - // doc://com.shapes.ShapeKit/documentation/ShapeKit: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'ShapeKit', suffix: (none)), - representsModule: true, - basePathComponents: [] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedByCaseStruct: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedByCaseStruct', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedByCaseStruct/ThirdTestMemberName-5vyx9: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedByCaseStruct', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'ThirdTestMemberName', suffix: (idHash: '5vyx9'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedByCaseStruct/thirdTestMemberNamE-4irjn: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedByCaseStruct', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'thirdTestMemberNamE', suffix: (idHash: '4irjn'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedByCaseStruct/thirdTestMemberName-8x5kx: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedByCaseStruct', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'thirdTestMemberName', suffix: (idHash: '8x5kx'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedByCaseStruct/thirdtestMemberName-u0gl: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedByCaseStruct', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'thirdtestMemberName', suffix: (idHash: 'u0gl'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedEnum', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-14g8s: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedEnum', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'firstTestMemberName(_:)', suffix: (idHash: '14g8s'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-14ife: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedEnum', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'firstTestMemberName(_:)', suffix: (idHash: '14ife'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-14ob0: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedEnum', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'firstTestMemberName(_:)', suffix: (idHash: '14ob0'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-4ja8m: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedEnum', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'firstTestMemberName(_:)', suffix: (idHash: '4ja8m'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-88rbf: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedEnum', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'firstTestMemberName(_:)', suffix: (idHash: '88rbf'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-swift.enum.case: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedEnum', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'firstTestMemberName(_:)', suffix: (kind: 'swift.enum.case'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedParentStruct-1jr3p: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedParentStruct', suffix: (idHash: '1jr3p')), - representsModule: false, - basePathComponents: [] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedParentStruct-1jr3p/fifthTestMember: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedParentStruct', suffix: (idHash: '1jr3p')), - representsModule: false, - basePathComponents: [(name: 'fifthTestMember', suffix: (none))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedProtocol', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)-1h173: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedProtocol', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'fourthTestMemberName(test:)', suffix: (idHash: '1h173'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)-8iuz7: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedProtocol', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'fourthTestMemberName(test:)', suffix: (idHash: '8iuz7'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)-91hxs: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedProtocol', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'fourthTestMemberName(test:)', suffix: (idHash: '91hxs'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)-961zx: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedProtocol', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'fourthTestMemberName(test:)', suffix: (idHash: '961zx'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedStruct: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedStruct', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedStruct/secondTestMemberName-swift.property: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedStruct', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'secondTestMemberName', suffix: (kind: 'swift.property'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedStruct/secondTestMemberName-swift.type.property: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'OverloadedStruct', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'secondTestMemberName', suffix: (kind: 'swift.type.property'))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'RegularParent', suffix: (none)), - representsModule: false, - basePathComponents: [] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/FourthMember: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'RegularParent', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'FourthMember', suffix: (none))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/firstMember: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'RegularParent', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'firstMember', suffix: (none))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/secondMember(first:second:): - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'RegularParent', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'secondMember(first:second:)', suffix: (none))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/thirdMember: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'RegularParent', suffix: (none)), - representsModule: false, - basePathComponents: [(name: 'thirdMember', suffix: (none))] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/overloadedparentstruct-6a7lx: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'overloadedparentstruct', suffix: (idHash: '6a7lx')), - representsModule: false, - basePathComponents: [] - } - """, - // doc://com.shapes.ShapeKit/documentation/ShapeKit/overloadedparentstruct-6a7lx/fifthTestMember: - """ - { - bundleID: 'com.shapes.ShapeKit', - module: 'ShapeKit', - topLevelSymbol: (name: 'overloadedparentstruct', suffix: (idHash: '6a7lx')), - representsModule: false, - basePathComponents: [(name: 'fifthTestMember', suffix: (none))] - } - """, - ] - - XCTAssertEqual(expectedDescriptions.count, context.documentationCache.count) - - let validatedSymbolLinkDescriptions = context.documentationCache.allReferences - .map(\.url.absoluteString) - .sorted() - .compactMap(AbsoluteSymbolLink.init(string:)) - .map(\.description) - - XCTAssertEqual(validatedSymbolLinkDescriptions.count, context.documentationCache.count) - for (symbolLinkDescription, expectedDescription) in zip(validatedSymbolLinkDescriptions, expectedDescriptions) { - XCTAssertEqual(symbolLinkDescription, expectedDescription) - } - } - - func testLinkComponentStringConversion() async throws { - let (_, _, context) = try await testBundleAndContext(named: "OverloadedSymbols") - - let bundlePathComponents = context.documentationCache.allReferences - .flatMap(\.pathComponents) - - - bundlePathComponents.forEach { component in - let symbolLinkComponent = AbsoluteSymbolLink.LinkComponent(string: component) - // Assert that round-trip conversion doesn't change the string representation - // of the component - XCTAssertEqual(symbolLinkComponent?.asLinkComponentString, component) - } - } -} diff --git a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/DocCSymbolRepresentableTests.swift b/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/DocCSymbolRepresentableTests.swift deleted file mode 100644 index 614cf9c378..0000000000 --- a/Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/DocCSymbolRepresentableTests.swift +++ /dev/null @@ -1,205 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import Foundation - -import Foundation -import XCTest -import SymbolKit -@testable import SwiftDocC - -// This test uses ``DocCSymbolRepresentable`` and ``AbsoluteSymbolLink`` which are deprecated. -// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. -@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released") -class DocCSymbolRepresentableTests: XCTestCase { - func testDisambiguatedByType() async throws { - try await performOverloadSymbolDisambiguationTest( - correctLink: """ - doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedStruct/secondTestMemberName-swift.property - """, - incorrectLinks: [ - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedStruct/secondTestMemberName-swift.method", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedStruct/secondTestMemberName-swift.enum.case", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedStruct/secondTestMemberName", - ], - symbolTitle: "secondTestMemberName", - expectedNumberOfAmbiguousSymbols: 2 - ) - } - - func testOverloadedByCaseInsensitivity() async throws { - try await performOverloadSymbolDisambiguationTest( - correctLink: """ - doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedByCaseStruct/ThirdTestMemberName-5vyx9 - """, - incorrectLinks: [ - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedByCaseStruct/ThirdTestMemberName", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedByCaseStruct/ThirdTestMemberName-swift.enum.case", - ], - symbolTitle: "thirdtestmembername", - expectedNumberOfAmbiguousSymbols: 4 - ) - } - - func testProtocolMemberWithUSRHash() async throws { - try await performOverloadSymbolDisambiguationTest( - correctLink: """ - doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)-961zx - """, - incorrectLinks: [ - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthtestmembername(test:)-961zx", - ], - symbolTitle: "fourthTestMemberName(test:)", - expectedNumberOfAmbiguousSymbols: 4 - ) - } - - func testFunctionWithKindIdentifierAndUSRHash() async throws { - try await performOverloadSymbolDisambiguationTest( - correctLink: """ - doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-14g8s - """, - incorrectLinks: [ - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-swift.method-14g8s", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)-swift.method", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedEnum/firstTestMemberName(_:)", - ], - symbolTitle: "firstTestMemberName(_:)", - expectedNumberOfAmbiguousSymbols: 6 - ) - } - - func testSymbolWithNoDisambiguation() async throws { - try await performOverloadSymbolDisambiguationTest( - correctLink: """ - doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/firstMember - """, - incorrectLinks: [ - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/firstMember-961zx", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/firstMember-swift.property", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/firstMember-swift.property-961zx", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/firstmember", - ], - symbolTitle: "firstMember", - expectedNumberOfAmbiguousSymbols: 1 - ) - } - - func testAmbiguousProtocolMember() async throws { - try await performOverloadSymbolDisambiguationTest( - correctLink: """ - doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/firstMember - """, - incorrectLinks: [ - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/firstMember-961zx", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/firstMember-swift.property", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/firstMember-swift.property-961zx", - "doc://com.shapes.ShapeKit/documentation/ShapeKit/RegularParent/firstmember", - ], - symbolTitle: "firstMember", - expectedNumberOfAmbiguousSymbols: 1 - ) - } - - func performOverloadSymbolDisambiguationTest( - correctLink: String, - incorrectLinks: [String], - symbolTitle: String, - expectedNumberOfAmbiguousSymbols: Int - ) async throws { - // Build a bundle with an unusual number of overloaded symbols - let (_, _, context) = try await testBundleAndContext(named: "OverloadedSymbols") - - // Collect the overloaded symbols nodes from the built bundle - let ambiguousSymbols = context.documentationCache - .compactMap { $0.value.symbol } - .filter { $0.names.title.lowercased() == symbolTitle.lowercased() } - XCTAssertEqual(ambiguousSymbols.count, expectedNumberOfAmbiguousSymbols) - - // Find the documentation node based on what we expect the correct link to be - let correctReferenceToSelect = try XCTUnwrap( - context.documentationCache.allReferences.first(where: { $0.absoluteString == correctLink }) - ) - let correctSymbolToSelect = try XCTUnwrap( - context.documentationCache[correctReferenceToSelect]?.symbol - ) - - // First confirm the first link does resolve as expected - do { - // Build an absolute symbol link - let absoluteSymbolLinkLastPathComponent = try XCTUnwrap( - AbsoluteSymbolLink(string: correctLink)?.basePathComponents.last - ) - - // Pass it all of the ambiguous symbols to disambiguate between - let selectedSymbols = absoluteSymbolLinkLastPathComponent.disambiguateBetweenOverloadedSymbols( - ambiguousSymbols - ) - - // Assert that it selects a single symbol - XCTAssertEqual(selectedSymbols.count, 1) - - // Assert that the correct symbol is selected - let selectedSymbol = try XCTUnwrap(selectedSymbols.first) - XCTAssertEqual(correctSymbolToSelect, selectedSymbol) - } - - // Now we'll try a couple of imprecise links and verify they don't resolve - try incorrectLinks.forEach { incorrectLink in - let absoluteSymbolLinkLastPathComponent = try XCTUnwrap( - AbsoluteSymbolLink(string: incorrectLink)?.basePathComponents.last - ) - - // Pass it all of the ambiguous symbols to disambiguate between - let selectedSymbols = absoluteSymbolLinkLastPathComponent.disambiguateBetweenOverloadedSymbols( - ambiguousSymbols - ) - - // We expect it to return an empty array since the given - // absolute symbol link isn't correct - XCTAssertTrue(selectedSymbols.isEmpty) - } - } - - func testLinkComponentInitialization() async throws { - let (_, _, context) = try await testBundleAndContext(named: "OverloadedSymbols") - - var count = 0 - for (reference, documentationNode) in context.documentationCache { - guard let symbolLink = AbsoluteSymbolLink(string: reference.absoluteString) else { - continue - } - - // The `asLinkComponent` property of DocCSymbolRepresentable doesn't have the context - // to know what type disambiguation information it should use, so it always includes - // all the available disambiguation information. Because of this, - // we want to restrict to symbols that require both. - guard case .kindAndPreciseIdentifier = symbolLink.basePathComponents.last?.disambiguationSuffix else { - continue - } - - // Create a link component from the symbol information - let linkComponent = try XCTUnwrap(documentationNode.symbol?.asLinkComponent) - - // Confirm that link component we created is the same on the compiler - // created in a full documentation build. - XCTAssertEqual( - linkComponent.asLinkComponentString, - documentationNode.reference.lastPathComponent - ) - - count += 1 - } - - // It's not necessary to disambiguate with both kind and usr. - XCTAssertEqual(count, 0) - } -} diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift index 93f2cd2435..797ac40e49 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift @@ -191,7 +191,7 @@ class SymbolDisambiguationTests: XCTestCase { func testMixedLanguageFramework() async throws { let (bundle, context) = try await testBundleAndContext(named: "MixedLanguageFramework") - var loader = SymbolGraphLoader(bundle: bundle, dataLoader: { try context.contentsOfURL($0, in: $1) }) + var loader = SymbolGraphLoader(bundle: bundle, dataProvider: context.dataProvider) try loader.loadAll() let references = context.linkResolver.localResolver.referencesForSymbols(in: loader.unifiedGraphs, bundle: bundle, context: context).mapValues(\.path) diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift index 668dc47c58..39ad7e8482 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift @@ -1801,9 +1801,7 @@ class SymbolGraphLoaderTests: XCTestCase { return SymbolGraphLoader( bundle: bundle, - dataLoader: { url, _ in - try FileManager.default.contents(of: url) - }, + dataProvider: FileManager.default, symbolGraphTransformer: configureSymbolGraph ) } diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift index 1e51718c0c..85fc99fa13 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift @@ -609,7 +609,7 @@ class ConvertActionTests: XCTestCase { 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.", + summary: "The 'diagnostics.json' digest file is deprecated and will be removed after 6.3 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: [] ), @@ -672,7 +672,7 @@ class ConvertActionTests: XCTestCase { 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.", + summary: "The 'diagnostics.json' digest file is deprecated and will be removed after 6.3 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: [] ), @@ -764,7 +764,7 @@ class ConvertActionTests: XCTestCase { 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.", + summary: "The 'diagnostics.json' digest file is deprecated and will be removed after 6.3 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: [] ), From 1c54e106f5c9a92a69225492b1a89c934677ae58 Mon Sep 17 00:00:00 2001 From: Andrea Fernandez Buitrago <15234535+anferbui@users.noreply.github.com> Date: Mon, 15 Sep 2025 10:40:28 +0100 Subject: [PATCH 31/90] Support beta status in navigator (#1249) * Propagate beta attribute for external entities to the navigator The `isBeta` attribute is part of `TopicRenderReference` [1], and is already computed based on the platforms [2][3] (though the platform information itself is dropped). This changes propagates the property to `ExternalRenderNode` and `ExternalRenderNodeMetadataRepresentation` so that we can ultimately store it as part of the navigator `RenderIndex`. [4] Fixes rdar://155521394. [1]: https://github.com/swiftlang/swift-docc/blob/f968935b770b0011d7aa28f59eda22a8407282b7/Sources/SwiftDocC/Model/Rendering/References/TopicRenderReference.swift#L82-L85 [2]: https://github.com/swiftlang/swift-docc/blob/f968935b770b0011d7aa28f59eda22a8407282b7/Sources/SwiftDocC/Infrastructure/External%20Data/OutOfProcessReferenceResolver.swift#L158 [3]: https://github.com/swiftlang/swift-docc/blob/f968935b770b0011d7aa28f59eda22a8407282b7/Sources/SwiftDocC/Infrastructure/External%20Data/OutOfProcessReferenceResolver.swift#L592-L598 [4]: https://github.com/swiftlang/swift-docc/blob/f968935b770b0011d7aa28f59eda22a8407282b7/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift#L251 * Add beta attribute to NavigatorIndexableRenderMetadataRepresentation Adds a computed property to `NavigatorIndexableRenderMetadataRepresentation` which derives whether the navigator item `isBeta` or not. This uses the same logic used in other places in the codebase [1]. Fixes rdar://155521394. [1]: https://github.com/swiftlang/swift-docc/blob/f968935b770b0011d7aa28f59eda22a8407282b7/Sources/SwiftDocC/Infrastructure/External%20Data/OutOfProcessReferenceResolver.swift#L592-L598 * Capture whether an item `isBeta` in the navigator item Propagates the `isBeta` property to the `NavigatorItem` type from `NavigatorIndexableRenderMetadataRepresentation`. When we index a new node, whether the item is beta or not will now be captured as part of the navigator This is preparatory work before this property is propagated to the `RenderIndex` [1]. Fixes rdar://155521394. [1]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift#L257-L259 * Fix serialisation of isBeta and isExternal properties The isBeta and isExternal properties weren't being serialised as part of `NavigatorItem`. These properties are used to initialise to `RenderIndex.Node` during the conversion to `index.json` [1] and must be preserved when navigator indexes are written to disk [2] so that when they are read [3], we don't drop beta and external information. This serialisation happens during the `finalize` step [4] and the deserialisation can be invoked via `NavigatorIndex.readNavigatorIndex` [5]. Otherwise, the values can be lost on serialisation roundtrip. [1]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift#L329 [2]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Indexing/Navigator/NavigatorTree.swift#L195 [3]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Indexing/Navigator/NavigatorTree.swift#L266 [4]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift#L1193 [5]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift#L157 * Propagates `isBeta` property to RenderIndex All that was left was that on initialisation of a `RenderIndex.Node`, we propagate the `isBeta` value from the `NavigatorItem`. With this change, beta information is now available in the navigator, encoded as `"beta": true` in the resulting `index.json` file. Fixes rdar://155521394. * Check that we don't cause an out of bounds crash when accessing the new values. Added an assertion that checks that the cursor plus the lenght of the memory to be decoded is less or equal than the length of the entire memory for the raw value that has been passed, avoiding a runtime crash. Also both if statements were combined into a signgle one since, as pointed out in [1], `isBeta` and `isExternal` have to either both exists or not. --- [1] https://github.com/swiftlang/swift-docc/pull/1249#discussion_r2259784235 * Update license year in the header of the modified files. --------- Co-authored-by: Sofia Rodriguez Morales --- .../Indexing/Navigator/NavigatorIndex.swift | 3 +- .../Indexing/Navigator/NavigatorItem.swift | 43 +++++- .../Navigator/RenderNode+NavigatorIndex.swift | 13 +- .../RenderIndexJSON/RenderIndex.swift | 11 +- .../LinkResolver+NavigatorIndex.swift | 11 +- .../Indexing/ExternalRenderNodeTests.swift | 76 +++++++++- .../Indexing/NavigatorIndexTests.swift | 115 +++++++++++++++ ...avigatorIndexableRenderMetadataTests.swift | 134 ++++++++++++++++++ .../TestExternalReferenceResolvers.swift | 4 +- 9 files changed, 389 insertions(+), 21 deletions(-) create mode 100644 Tests/SwiftDocCTests/Indexing/NavigatorIndexableRenderMetadataTests.swift diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift index 8a6e452f48..4024bf3f93 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift @@ -771,7 +771,8 @@ extension NavigatorIndex { platformMask: platformID, availabilityID: UInt64(availabilityID), icon: renderNode.icon, - isExternal: external + isExternal: external, + isBeta: renderNode.metadata.isBeta ) navigationItem.path = identifierPath diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift index 32192682f2..d3d1d3a30b 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.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 @@ -49,6 +49,11 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString var icon: RenderReferenceIdentifier? = nil + /// A value that indicates whether this item is built for a beta platform. + /// + /// This value is `false` if the referenced item is not a symbol. + var isBeta: Bool = false + /// Whether the item has originated from an external reference. /// /// Used for determining whether stray navigation items should remain part of the final navigator. @@ -66,7 +71,7 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString - path: The path to load the content. - icon: A reference to a custom image for this navigator item. */ - init(pageType: UInt8, languageID: UInt8, title: String, platformMask: UInt64, availabilityID: UInt64, path: String, icon: RenderReferenceIdentifier? = nil, isExternal: Bool = false) { + init(pageType: UInt8, languageID: UInt8, title: String, platformMask: UInt64, availabilityID: UInt64, path: String, icon: RenderReferenceIdentifier? = nil, isExternal: Bool = false, isBeta: Bool = false) { self.pageType = pageType self.languageID = languageID self.title = title @@ -75,6 +80,7 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString self.path = path self.icon = icon self.isExternal = isExternal + self.isBeta = isBeta } /** @@ -87,8 +93,10 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString - platformMask: The mask indicating for which platform the page is available. - availabilityID: The identifier of the availability information of the page. - icon: A reference to a custom image for this navigator item. + - isExternal: A flag indicating whether the navigator item belongs to an external documentation archive. + - isBeta: A flag indicating whether the navigator item is in beta. */ - public init(pageType: UInt8, languageID: UInt8, title: String, platformMask: UInt64, availabilityID: UInt64, icon: RenderReferenceIdentifier? = nil, isExternal: Bool = false) { + public init(pageType: UInt8, languageID: UInt8, title: String, platformMask: UInt64, availabilityID: UInt64, icon: RenderReferenceIdentifier? = nil, isExternal: Bool = false, isBeta: Bool = false) { self.pageType = pageType self.languageID = languageID self.title = title @@ -96,6 +104,7 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString self.availabilityID = availabilityID self.icon = icon self.isExternal = isExternal + self.isBeta = isBeta } // MARK: - Serialization and Deserialization @@ -137,8 +146,27 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString let pathData = data[cursor...stride + // To ensure backwards compatibility, handle both when `isBeta` and `isExternal` has been encoded and when it hasn't + if cursor < data.count { + // Encoded `isBeta` + assert(cursor + length <= data.count, "The serialized data is malformed: `isBeta` value should not extend past the end of the data") + let betaValue: UInt8 = unpackedValueFromData(data[cursor.. = [ .framework, .class, .structure, .enumeration, .protocol, .typeAlias, .associatedType, .extension ] diff --git a/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift b/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift index 5140e35fd3..7b228244bb 100644 --- a/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift +++ b/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.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 @@ -92,6 +92,7 @@ public struct RenderIndex: Codable, Equatable { pageType: .framework, isDeprecated: false, isExternal: false, + isBeta: false, children: nodes, icon: nil ) @@ -245,6 +246,7 @@ extension RenderIndex { pageType: NavigatorIndex.PageType?, isDeprecated: Bool, isExternal: Bool, + isBeta: Bool, children: [Node], icon: RenderReferenceIdentifier? ) { @@ -253,10 +255,8 @@ extension RenderIndex { self.isDeprecated = isDeprecated self.isExternal = isExternal - - // Currently Swift-DocC doesn't support marking a node as beta in the navigation index - // so we default to `false` here. - self.isBeta = false + self.isBeta = isBeta + self.icon = icon guard let pageType else { @@ -327,6 +327,7 @@ extension RenderIndex.Node { pageType: NavigatorIndex.PageType(rawValue: node.item.pageType), isDeprecated: isDeprecated, isExternal: node.item.isExternal, + isBeta: node.item.isBeta, children: node.children.map { RenderIndex.Node.fromNavigatorTreeNode($0, in: navigatorIndex, with: builder) }, diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift index a0875fbde1..c1163f62ed 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift @@ -77,6 +77,13 @@ package struct ExternalRenderNode { RenderNode.Variant(traits: [.interfaceLanguage($0.id)], paths: [externalEntity.topicRenderReference.url]) } } + + /// A value that indicates whether this symbol is built for a beta platform + /// + /// This value is `false` if the referenced page is not a symbol. + var isBeta: Bool { + externalEntity.topicRenderReference.isBeta + } } /// A language specific representation of an external render node value for building a navigator index. @@ -110,7 +117,8 @@ struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { externalID: renderNode.externalIdentifier.identifier, role: renderNode.role, symbolKind: renderNode.symbolKind?.identifier, - images: renderNode.images + images: renderNode.images, + isBeta: renderNode.isBeta ) } } @@ -123,6 +131,7 @@ struct ExternalRenderNodeMetadataRepresentation: NavigatorIndexableRenderMetadat var role: String? var symbolKind: String? var images: [TopicImage] + var isBeta: Bool // Values that we have insufficient information to derive. // These are needed to conform to the navigator indexable metadata protocol. diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index 0f220d928b..a8afa4aff1 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -21,7 +21,8 @@ class ExternalRenderNodeTests: XCTestCase { referencePath: "/path/to/external/swiftArticle", title: "SwiftArticle", kind: .article, - language: .swift + language: .swift, + platforms: [.init(name: "iOS", introduced: nil, isBeta: false)] ) ) externalResolver.entitiesToReturn["/path/to/external/objCArticle"] = .success( @@ -29,7 +30,8 @@ class ExternalRenderNodeTests: XCTestCase { referencePath: "/path/to/external/objCArticle", title: "ObjCArticle", kind: .article, - language: .objectiveC + language: .objectiveC, + platforms: [.init(name: "macOS", introduced: nil, isBeta: true)] ) ) externalResolver.entitiesToReturn["/path/to/external/swiftSymbol"] = .success( @@ -37,7 +39,8 @@ class ExternalRenderNodeTests: XCTestCase { referencePath: "/path/to/external/swiftSymbol", title: "SwiftSymbol", kind: .class, - language: .swift + language: .swift, + platforms: [.init(name: "iOS", introduced: nil, isBeta: true)] ) ) externalResolver.entitiesToReturn["/path/to/external/objCSymbol"] = .success( @@ -45,7 +48,8 @@ class ExternalRenderNodeTests: XCTestCase { referencePath: "/path/to/external/objCSymbol", title: "ObjCSymbol", kind: .function, - language: .objectiveC + language: .objectiveC, + platforms: [.init(name: "macOS", introduced: nil, isBeta: false)] ) ) return externalResolver @@ -89,24 +93,28 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(externalRenderNodes[0].symbolKind, nil) XCTAssertEqual(externalRenderNodes[0].role, "article") XCTAssertEqual(externalRenderNodes[0].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCArticle") - + XCTAssertTrue(externalRenderNodes[0].isBeta) + XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCSymbol") XCTAssertEqual(externalRenderNodes[1].kind, .symbol) XCTAssertEqual(externalRenderNodes[1].symbolKind, nil) XCTAssertEqual(externalRenderNodes[1].role, "symbol") XCTAssertEqual(externalRenderNodes[1].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCSymbol") + XCTAssertFalse(externalRenderNodes[1].isBeta) XCTAssertEqual(externalRenderNodes[2].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftArticle") XCTAssertEqual(externalRenderNodes[2].kind, .article) XCTAssertEqual(externalRenderNodes[2].symbolKind, nil) XCTAssertEqual(externalRenderNodes[2].role, "article") XCTAssertEqual(externalRenderNodes[2].externalIdentifier.identifier, "doc://com.test.external/path/to/external/swiftArticle") + XCTAssertFalse(externalRenderNodes[2].isBeta) XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftSymbol") XCTAssertEqual(externalRenderNodes[3].kind, .symbol) XCTAssertEqual(externalRenderNodes[3].symbolKind, nil) XCTAssertEqual(externalRenderNodes[3].role, "symbol") XCTAssertEqual(externalRenderNodes[3].externalIdentifier.identifier, "doc://com.test.external/path/to/external/swiftSymbol") + XCTAssertTrue(externalRenderNodes[3].isBeta) } func testExternalRenderNodeVariantRepresentation() throws { @@ -146,14 +154,16 @@ class ExternalRenderNodeTests: XCTestCase { ) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) - + XCTAssertFalse(swiftNavigatorExternalRenderNode.metadata.isBeta) + let objcNavigatorExternalRenderNode = try XCTUnwrap( NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) ) XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) + XCTAssertFalse(objcNavigatorExternalRenderNode.metadata.isBeta) } - + func testNavigatorWithExternalNodes() async throws { let externalResolver = generateExternalResolver() let (_, bundle, context) = try await testBundleAndContext( @@ -208,6 +218,10 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) + XCTAssert(swiftExternalNodes.first { $0.title == "SwiftArticle" }?.isBeta == false) + XCTAssert(swiftExternalNodes.first { $0.title == "SwiftSymbol" }?.isBeta == true) + XCTAssert(occExternalNodes.first { $0.title == "ObjCArticle" }?.isBeta == true) + XCTAssert(occExternalNodes.first { $0.title == "ObjCSymbol" }?.isBeta == false) } func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() async throws { @@ -269,4 +283,52 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) } + + func testExternalRenderNodeVariantRepresentationWhenIsBeta() throws { + let renderReferenceIdentifier = RenderReferenceIdentifier(forExternalLink: "doc://com.test.external/path/to/external/symbol") + + // Variants for the title + let swiftTitle = "Swift Symbol" + let occTitle = "Occ Symbol" + + // Variants for the navigator title + let navigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "symbol", kind: .identifier)] + let occNavigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "occ_symbol", kind: .identifier)] + + // Variants for the fragments + let fragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] + let occFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] + + let externalEntity = LinkResolver.ExternalEntity( + topicRenderReference: .init( + identifier: renderReferenceIdentifier, + titleVariants: .init(defaultValue: swiftTitle, objectiveCValue: occTitle), + abstractVariants: .init(defaultValue: []), + url: "/example/path/to/external/symbol", + kind: .symbol, + fragmentsVariants: .init(defaultValue: fragments, objectiveCValue: occFragments), + navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle), + isBeta: true + ), + renderReferenceDependencies: .init(), + sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")]) + let externalRenderNode = ExternalRenderNode( + externalEntity: externalEntity, + bundleIdentifier: "com.test.external" + ) + + let swiftNavigatorExternalRenderNode = try XCTUnwrap( + NavigatorExternalRenderNode(renderNode: externalRenderNode) + ) + XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) + XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) + XCTAssertTrue(swiftNavigatorExternalRenderNode.metadata.isBeta) + + let objcNavigatorExternalRenderNode = try XCTUnwrap( + NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) + ) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) + XCTAssertTrue(objcNavigatorExternalRenderNode.metadata.isBeta) + } } diff --git a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift index 9c64514d31..71ea0f8da4 100644 --- a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift @@ -138,6 +138,66 @@ Root XCTAssertEqual(item, fromData) } + func testNavigatorEquality() { + // Test for equal + var item1 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024, isExternal: true, isBeta: true) + var item2 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024, isExternal: true, isBeta: true) + XCTAssertEqual(item1, item2) + + // Tests for not equal + item1 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024, isBeta: true) + item2 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024, isBeta: false) + XCTAssertNotEqual(item1, item2) + + item1 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024, isExternal: true) + item2 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024, isExternal: false) + XCTAssertNotEqual(item1, item2) + + item1 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024) + item2 = NavigatorItem(pageType: 2, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024) + XCTAssertNotEqual(item1, item2) + + item1 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024) + item2 = NavigatorItem(pageType: 1, languageID: 5, title: "My Title", platformMask: 256, availabilityID: 1024) + XCTAssertNotEqual(item1, item2) + + item1 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024) + item2 = NavigatorItem(pageType: 1, languageID: 4, title: "My Other Title", platformMask: 256, availabilityID: 1024) + XCTAssertNotEqual(item1, item2) + + item1 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024) + item2 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 257, availabilityID: 1024) + XCTAssertNotEqual(item1, item2) + + item1 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024) + item2 = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1025) + XCTAssertNotEqual(item1, item2) + } + + func testNavigatorItemRawDumpWithExtraProperties() { + let item = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024, isExternal: true, isBeta: true) + let data = item.rawValue + let fromData = NavigatorItem(rawValue: data) + XCTAssertEqual(item, fromData) + } + + func testNavigatorItemRawDumpBackwardCompatibility() { + let item = NavigatorItem(pageType: 1, languageID: 4, title: "My Title", platformMask: 256, availabilityID: 1024) + var data = Data() + data.append(packedDataFromValue(item.pageType)) + data.append(packedDataFromValue(item.languageID)) + data.append(packedDataFromValue(item.platformMask)) + data.append(packedDataFromValue(item.availabilityID)) + data.append(packedDataFromValue(UInt64(item.title.utf8.count))) + data.append(packedDataFromValue(UInt64(item.path.utf8.count))) + data.append(Data(item.title.utf8)) + data.append(Data(item.path.utf8)) + // Note: NOT adding isBeta and isExternal flags to simulate when they were not supported + + let fromData = NavigatorItem(rawValue: data) + XCTAssertEqual(item, fromData) + } + func testObjCLanguage() { let root = generateLargeTree() var objcFiltered: Node? @@ -1977,6 +2037,61 @@ Root ) } + func testNavigatorIndexCapturesBetaStatus() async throws { + // Set up configuration with beta platforms + let platformMetadata = [ + "macOS": PlatformVersion(VersionTriplet(1, 0, 0), beta: true), + "watchOS": PlatformVersion(VersionTriplet(2, 0, 0), beta: true), + "tvOS": PlatformVersion(VersionTriplet(3, 0, 0), beta: true), + "iOS": PlatformVersion(VersionTriplet(4, 0, 0), beta: true), + "Mac Catalyst": PlatformVersion(VersionTriplet(4, 0, 0), beta: true), + "iPadOS": PlatformVersion(VersionTriplet(4, 0, 0), beta: true), + ] + var configuration = DocumentationContext.Configuration() + configuration.externalMetadata.currentPlatforms = platformMetadata + + let (_, bundle, context) = try await testBundleAndContext(named: "AvailabilityBetaBundle", configuration: configuration) + let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let targetURL = try createTemporaryDirectory() + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true) + builder.setup() + for identifier in context.knownPages { + let entity = try context.entity(with: identifier) + let renderNode = try XCTUnwrap(converter.renderNode(for: entity)) + try builder.index(renderNode: renderNode) + } + builder.finalize() + let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) + + // Find nodes that should have beta status + let swiftNodes = renderIndex.interfaceLanguages["swift"] ?? [] + let betaNodes = findNodesWithBetaStatus(in: swiftNodes, isBeta: true) + let nonBetaNodes = findNodesWithBetaStatus(in: swiftNodes, isBeta: false) + + // Verify that beta status was captured in the render index + XCTAssertEqual(betaNodes.map(\.title), ["MyClass"]) + XCTAssert(betaNodes.allSatisfy(\.isBeta)) // Sanity check + XCTAssertEqual(nonBetaNodes.map(\.title).sorted(), ["Classes", "MyOtherClass", "MyThirdClass"]) + XCTAssert(nonBetaNodes.allSatisfy { $0.isBeta == false }) // Sanity check + } + + private func findNodesWithBetaStatus(in nodes: [RenderIndex.Node], isBeta: Bool) -> [RenderIndex.Node] { + var betaNodes: [RenderIndex.Node] = [] + + for node in nodes { + if node.isBeta == isBeta { + betaNodes.append(node) + } + + if let children = node.children { + betaNodes.append(contentsOf: findNodesWithBetaStatus(in: children, isBeta: isBeta)) + } + } + + return betaNodes + } + func generatedNavigatorIndex(for testBundleName: String, bundleIdentifier: String) async throws -> NavigatorIndex { let (bundle, context) = try await testBundleAndContext(named: testBundleName) let renderContext = RenderContext(documentationContext: context, bundle: bundle) diff --git a/Tests/SwiftDocCTests/Indexing/NavigatorIndexableRenderMetadataTests.swift b/Tests/SwiftDocCTests/Indexing/NavigatorIndexableRenderMetadataTests.swift new file mode 100644 index 0000000000..3a997e259a --- /dev/null +++ b/Tests/SwiftDocCTests/Indexing/NavigatorIndexableRenderMetadataTests.swift @@ -0,0 +1,134 @@ +/* + 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 XCTest +@testable import SwiftDocC + +class NavigatorIndexableRenderMetadataTests: XCTestCase { + + // MARK: - Test Helper Methods + + /// Creates a test platform with the specified beta status + private func createPlatform(name: String, isBeta: Bool) -> AvailabilityRenderItem { + return AvailabilityRenderItem(name: name, introduced: "1.0", isBeta: isBeta) + } + + /// Creates a RenderMetadata instance with the specified platforms + private func createRenderMetadata(platforms: [AvailabilityRenderItem]?) -> RenderMetadata { + var metadata = RenderMetadata() + metadata.platforms = platforms + return metadata + } + + /// Creates a RenderMetadataVariantView with the specified platforms + private func createRenderMetadataVariantView(platforms: [AvailabilityRenderItem]?) -> RenderMetadataVariantView { + let metadata = createRenderMetadata(platforms: platforms) + return RenderMetadataVariantView(wrapped: metadata, traits: []) + } + + // MARK: - RenderMetadataVariantView Tests + + func testRenderMetadataVariantViewIsBeta() { + var metadataView = createRenderMetadataVariantView(platforms: nil) + XCTAssertFalse(metadataView.isBeta, "isBeta should be false when no platforms are defined") + + metadataView = createRenderMetadataVariantView(platforms: []) + XCTAssertFalse(metadataView.isBeta, "isBeta should be false when platforms array is empty") + + metadataView = createRenderMetadataVariantView(platforms: [ + createPlatform(name: "iOS", isBeta: false) + ]) + XCTAssertFalse(metadataView.isBeta, "isBeta should be false when single platform is non-beta") + + metadataView = createRenderMetadataVariantView(platforms: [ + createPlatform(name: "iOS", isBeta: false), + createPlatform(name: "macOS", isBeta: false), + createPlatform(name: "watchOS", isBeta: false) + ]) + XCTAssertFalse(metadataView.isBeta, "isBeta should be false when multiple platforms are non-beta") + + var platform1 = AvailabilityRenderItem(name: "iOS", introduced: "1.0", isBeta: false) + platform1.isBeta = nil + var platform2 = AvailabilityRenderItem(name: "macOS", introduced: "1.0", isBeta: false) + platform2.isBeta = nil + + metadataView = createRenderMetadataVariantView(platforms: [platform1, platform2]) + XCTAssertFalse(metadataView.isBeta, "isBeta should be false when platforms have nil beta status") + + metadataView = createRenderMetadataVariantView(platforms: [ + createPlatform(name: "iOS", isBeta: true), + createPlatform(name: "macOS", isBeta: false), + createPlatform(name: "watchOS", isBeta: true) + ]) + XCTAssertFalse(metadataView.isBeta, "isBeta should be false when some platforms are beta and some are non-beta") + + metadataView = createRenderMetadataVariantView(platforms: [ + createPlatform(name: "iOS", isBeta: true) + ]) + XCTAssertTrue(metadataView.isBeta, "isBeta should be true when single platform is beta") + + metadataView = createRenderMetadataVariantView(platforms: [ + createPlatform(name: "iOS", isBeta: true), + createPlatform(name: "macOS", isBeta: true), + createPlatform(name: "watchOS", isBeta: true) + ]) + XCTAssertTrue(metadataView.isBeta, "isBeta should be true when multiple platforms are beta") + } + + // MARK: - RenderMetadata Tests + + func testRenderMetadataIsBeta() { + var metadata = createRenderMetadata(platforms: nil) + XCTAssertFalse(metadata.isBeta, "isBeta should be false when no platforms are defined") + + metadata = createRenderMetadata(platforms: []) + XCTAssertFalse(metadata.isBeta, "isBeta should be false when platforms array is empty") + + metadata = createRenderMetadata(platforms: [ + createPlatform(name: "macOS", isBeta: false) + ]) + XCTAssertFalse(metadata.isBeta, "isBeta should be false when single platform is non-beta") + + metadata = createRenderMetadata(platforms: [ + createPlatform(name: "iOS", isBeta: false), + createPlatform(name: "macOS", isBeta: false), + createPlatform(name: "tvOS", isBeta: false) + ]) + XCTAssertFalse(metadata.isBeta, "isBeta should be false when all platforms are non-beta") + + var platform1 = AvailabilityRenderItem(name: "iOS", introduced: "1.0", isBeta: false) + platform1.isBeta = nil + var platform2 = AvailabilityRenderItem(name: "macOS", introduced: "1.0", isBeta: false) + platform2.isBeta = nil + + metadata = createRenderMetadata(platforms: [platform1, platform2]) + XCTAssertFalse(metadata.isBeta, "isBeta should be false when platforms have nil beta status") + + metadata = createRenderMetadata(platforms: [ + createPlatform(name: "iOS", isBeta: false), + createPlatform(name: "macOS", isBeta: true), + createPlatform(name: "tvOS", isBeta: false) + ]) + XCTAssertFalse(metadata.isBeta, "isBeta should be false when some platforms are beta and some are non-beta") + + metadata = createRenderMetadata(platforms: [ + createPlatform(name: "macOS", isBeta: true) + ]) + XCTAssertTrue(metadata.isBeta, "isBeta should be true when single platform is beta") + + metadata = createRenderMetadata(platforms: [ + createPlatform(name: "iOS", isBeta: true), + createPlatform(name: "macOS", isBeta: true), + createPlatform(name: "tvOS", isBeta: true) + ]) + XCTAssertTrue(metadata.isBeta, "isBeta should be true when all platforms are beta") + } +} diff --git a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift index 129d866338..3b135aa23c 100644 --- a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift +++ b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -29,6 +29,7 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { var language = SourceLanguage.swift var declarationFragments: SymbolGraph.Symbol.DeclarationFragments? = nil var topicImages: [(TopicImage, alt: String)]? = nil + var platforms: [AvailabilityRenderItem]? = nil } // When more tests use this we may find that there's a better way to describe this (for example by separating @@ -105,6 +106,7 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { fragments: entityInfo.declarationFragments?.declarationFragments.map { fragment in return DeclarationRenderSection.Token(fragment: fragment, identifier: nil) }, + isBeta: entityInfo.platforms?.allSatisfy({$0.isBeta == true}) ?? false, images: entityInfo.topicImages?.map(\.0) ?? [] ), renderReferenceDependencies: dependencies, From 3268e73aa5c445958e2cbb57b76eb69596784c00 Mon Sep 17 00:00:00 2001 From: Andrea Fernandez Buitrago <15234535+anferbui@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:52:03 +0100 Subject: [PATCH 32/90] Populate symbol kind in external render nodes (#1251) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add mapping from documentation kind to symbol kind There is a mapping from `SymbolGraph.Symbol.KindIdentifier` to `DocumentationNode.Kind`, but not the other way around; this commit adds one. For documentation kinds which both map to the same symbol kind, this has been expressed as: - both documentation kinds are converted to the same symbol kind in `symbolKind(for:)`. - a specific documentation kind is chosen as the default mapping in `kind(forKind:)` For example, for `DocumentationNode.Kind. typeConstant` [1]: - both `symbolKind(for: .typeConstant)` and `symbolKind(for: .typeProperty)` return `.typeProperty` - `kind(forKind: .typeProperty)` returns `.typeProperty` This function will be used to map and external entity's documentation kind to a symbol kind, so that that information can be populated as part of the navigator. ## Verification: This has been verified by testing the round trip conversion of all symbol kinds, and all documentation kinds which are symbols. [1]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Model/Kind.swift#L151 * Capture symbol kind as part of external entity Adds a new property to `LinkResolver.ExternalEntity` which stores the symbol kind of the external entity. This is derived at the level of `OutOfProcessReferenceResolver.ResolvedInformation`, which has access to the documentation kind [1] and already uses it to derive the render node kind and role, but then discards it. This commit adds logic to `OutOfProcessReferenceResolver.ResolvedInformation` to derive the symbol kind from the documentation kind, then capture that in `LinkResolver.ExternalEntity. We need access to the documentation kind in order to resolve the symbol kind as part of computing the navigator title. [2] [1]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Infrastructure/External%20Data/OutOfProcessReferenceResolver.swift#L564-L565 [2]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Indexing/Navigator/RenderNode%2BNavigatorIndex.swift#L163 * Propagate symbol kind to navigator for external nodes Propagates the symbol kind value from `LinkResolver.ExternalEntity` to the `ExternalRenderNode` used for generating the navigation index for external render nodes. This completes adding symbol kind support for external entities in the navigator. Also updates how the symbol kind is propagated to the navigator metadata, by using `.renderingIdentifier` over `.identifier`, to match the behaviour of local nodes [1]. Fixes rdar://156487928. [1]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift#L1300 --- .../OutOfProcessReferenceResolver.swift | 7 ++- .../ExternalPathHierarchyResolver.swift | 5 +- .../LinkResolver+NavigatorIndex.swift | 7 +-- .../Link Resolution/LinkResolver.swift | 19 +++++-- .../SwiftDocC/Model/DocumentationNode.swift | 50 +++++++++++++++++++ .../Benchmark/ExternalTopicsHashTests.swift | 3 +- .../Indexing/ExternalRenderNodeTests.swift | 11 ++-- .../ExternalReferenceResolverTests.swift | 6 ++- .../TestExternalReferenceResolvers.swift | 3 +- .../Model/DocumentationNodeTests.swift | 35 +++++++++++++ .../Model/SemaToRenderNodeTests.swift | 6 ++- .../OutOfProcessReferenceResolverTests.swift | 6 ++- 12 files changed, 138 insertions(+), 20 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift index e4a737fa75..1988f8b074 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift @@ -182,7 +182,12 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE imageReferences: (resolvedInformation.references ?? []).compactMap { $0 as? ImageReference } ) - return LinkResolver.ExternalEntity(topicRenderReference: renderReference, renderReferenceDependencies: dependencies, sourceLanguages: resolvedInformation.availableLanguages) + return LinkResolver.ExternalEntity( + topicRenderReference: renderReference, + renderReferenceDependencies: dependencies, + sourceLanguages: resolvedInformation.availableLanguages, + symbolKind: DocumentationNode.symbolKind(for: resolvedInformation.kind) + ) } // MARK: Implementation diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift index 0d145a597d..6e6fdbb3ab 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -109,7 +109,8 @@ final class ExternalPathHierarchyResolver { return .init( topicRenderReference: resolvedInformation.topicRenderReference(), renderReferenceDependencies: dependencies, - sourceLanguages: resolvedInformation.availableLanguages + sourceLanguages: resolvedInformation.availableLanguages, + symbolKind: DocumentationNode.symbolKind(for: resolvedInformation.kind) ) } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift index c1163f62ed..8f0966fc79 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift @@ -39,9 +39,10 @@ package struct ExternalRenderNode { } /// The symbol kind of this documentation node. + /// + /// This value is `nil` if the referenced page is not a symbol. var symbolKind: SymbolGraph.Symbol.KindIdentifier? { - // Symbol kind information is not available for external entities - return nil + externalEntity.symbolKind } /// The additional "role" assigned to the symbol, if any @@ -116,7 +117,7 @@ struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { navigatorTitle: renderNode.navigatorTitleVariants.value(for: traits), externalID: renderNode.externalIdentifier.identifier, role: renderNode.role, - symbolKind: renderNode.symbolKind?.identifier, + symbolKind: renderNode.symbolKind?.renderingIdentifier, images: renderNode.images, isBeta: renderNode.isBeta ) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift index e7b44eb7d5..20291f286d 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift @@ -9,7 +9,7 @@ */ import Foundation -import SymbolKit +public import SymbolKit /// A class that resolves documentation links by orchestrating calls to other link resolver implementations. public class LinkResolver { @@ -48,11 +48,18 @@ public class LinkResolver { /// - topicRenderReference: The render reference for this external topic. /// - renderReferenceDependencies: Any dependencies for the render reference. /// - sourceLanguages: The different source languages for which this page is available. + /// - symbolKind: The kind of symbol that's being referenced. @_spi(ExternalLinks) - public init(topicRenderReference: TopicRenderReference, renderReferenceDependencies: RenderReferenceDependencies, sourceLanguages: Set) { + public init( + topicRenderReference: TopicRenderReference, + renderReferenceDependencies: RenderReferenceDependencies, + sourceLanguages: Set, + symbolKind: SymbolGraph.Symbol.KindIdentifier? = nil + ) { self.topicRenderReference = topicRenderReference self.renderReferenceDependencies = renderReferenceDependencies self.sourceLanguages = sourceLanguages + self.symbolKind = symbolKind } /// The render reference for this external topic. @@ -63,7 +70,13 @@ public class LinkResolver { var renderReferenceDependencies: RenderReferenceDependencies /// The different source languages for which this page is available. var sourceLanguages: Set - + /// The kind of symbol that's being referenced. + /// + /// This value is `nil` if the entity does not reference a symbol. + /// + /// For example, the navigator requires specific knowledge about what type of external symbol is being linked to. + var symbolKind: SymbolGraph.Symbol.KindIdentifier? + /// Creates a pre-render new topic content value to be added to a render context's reference store. func topicContent() -> RenderReferenceStore.TopicContent { return .init( diff --git a/Sources/SwiftDocC/Model/DocumentationNode.swift b/Sources/SwiftDocC/Model/DocumentationNode.swift index 3aca3e3e26..803ddb7761 100644 --- a/Sources/SwiftDocC/Model/DocumentationNode.swift +++ b/Sources/SwiftDocC/Model/DocumentationNode.swift @@ -674,6 +674,7 @@ public struct DocumentationNode { case .union: return .union case .`var`: return .globalVariable case .module: return .module + case .extension: return .extension case .extendedModule: return .extendedModule case .extendedStructure: return .extendedStructure case .extendedClass: return .extendedClass @@ -684,6 +685,55 @@ public struct DocumentationNode { } } + /// Returns a symbol kind for the given documentation node. + /// - Parameter symbol: A documentation node kind. + /// - Returns: A symbol graph symbol. + static func symbolKind(for kind: Kind) -> SymbolGraph.Symbol.KindIdentifier? { + switch kind { + case .associatedType: return .`associatedtype` + case .class: return .`class` + case .deinitializer: return .`deinit` + case .dictionary, .object: return .dictionary + case .dictionaryKey: return .dictionaryKey + case .enumeration: return .`enum` + case .enumerationCase: return .`case` + case .function: return .`func` + case .httpRequest: return .httpRequest + case .httpParameter: return .httpParameter + case .httpBody: return .httpBody + case .httpResponse: return .httpResponse + case .operator: return .`operator` + case .initializer: return .`init` + case .instanceVariable: return .ivar + case .macro: return .macro + case .instanceMethod: return .`method` + case .namespace: return .namespace + case .instanceProperty: return .`property` + case .protocol: return .`protocol` + case .snippet: return .snippet + case .structure: return .`struct` + case .instanceSubscript: return .`subscript` + case .typeMethod: return .`typeMethod` + case .typeProperty, .typeConstant: return .`typeProperty` + case .typeSubscript: return .`typeSubscript` + case .typeAlias, .typeDef: return .`typealias` + case .union: return .union + case .globalVariable, .localVariable: return .`var` + case .module: return .module + case .extension: return .extension + case .extendedModule: return .extendedModule + case .extendedStructure: return .extendedStructure + case .extendedClass: return .extendedClass + case .extendedEnumeration: return .extendedEnumeration + case .extendedProtocol: return .extendedProtocol + case .unknownExtendedType: return .unknownExtendedType + default: + // For non-symbol kinds (like .article, .tutorial, etc.), + // return nil since these don't have corresponding SymbolGraph.Symbol.KindIdentifier values + return nil + } + } + /// Initializes a documentation node to represent a symbol from a symbol graph. /// /// - Parameters: diff --git a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift index 447ed860a7..4e87138288 100644 --- a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift +++ b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift @@ -31,7 +31,8 @@ class ExternalTopicsGraphHashTests: XCTestCase { estimatedTime: nil ), renderReferenceDependencies: .init(), - sourceLanguages: [.swift] + sourceLanguages: [.swift], + symbolKind: .class ) return (reference, entity) } diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index a8afa4aff1..4ca7dc631e 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -97,7 +97,7 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCSymbol") XCTAssertEqual(externalRenderNodes[1].kind, .symbol) - XCTAssertEqual(externalRenderNodes[1].symbolKind, nil) + XCTAssertEqual(externalRenderNodes[1].symbolKind, .func) XCTAssertEqual(externalRenderNodes[1].role, "symbol") XCTAssertEqual(externalRenderNodes[1].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCSymbol") XCTAssertFalse(externalRenderNodes[1].isBeta) @@ -111,7 +111,7 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftSymbol") XCTAssertEqual(externalRenderNodes[3].kind, .symbol) - XCTAssertEqual(externalRenderNodes[3].symbolKind, nil) + XCTAssertEqual(externalRenderNodes[3].symbolKind, .class) XCTAssertEqual(externalRenderNodes[3].role, "symbol") XCTAssertEqual(externalRenderNodes[3].externalIdentifier.identifier, "doc://com.test.external/path/to/external/swiftSymbol") XCTAssertTrue(externalRenderNodes[3].isBeta) @@ -143,7 +143,8 @@ class ExternalRenderNodeTests: XCTestCase { navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle) ), renderReferenceDependencies: .init(), - sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")]) + sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")], + symbolKind: .func) let externalRenderNode = ExternalRenderNode( externalEntity: externalEntity, bundleIdentifier: "com.test.external" @@ -222,6 +223,8 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssert(swiftExternalNodes.first { $0.title == "SwiftSymbol" }?.isBeta == true) XCTAssert(occExternalNodes.first { $0.title == "ObjCArticle" }?.isBeta == true) XCTAssert(occExternalNodes.first { $0.title == "ObjCSymbol" }?.isBeta == false) + XCTAssertEqual(swiftExternalNodes.map(\.type), ["article", "class"]) + XCTAssertEqual(occExternalNodes.map(\.type), ["article", "func"]) } func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() async throws { @@ -282,6 +285,8 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCSymbol"]) XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) + XCTAssertEqual(swiftExternalNodes.map(\.type), ["article"]) + XCTAssertEqual(occExternalNodes.map(\.type), ["func"]) } func testExternalRenderNodeVariantRepresentationWhenIsBeta() throws { diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 07cba25e1d..13b47f6743 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -52,7 +52,8 @@ class ExternalReferenceResolverTests: XCTestCase { } ), renderReferenceDependencies: RenderReferenceDependencies(), - sourceLanguages: [resolvedEntityLanguage] + sourceLanguages: [resolvedEntityLanguage], + symbolKind: nil ) } } @@ -641,7 +642,8 @@ class ExternalReferenceResolverTests: XCTestCase { estimatedTime: nil ), renderReferenceDependencies: RenderReferenceDependencies(), - sourceLanguages: [.swift] + sourceLanguages: [.swift], + symbolKind: .property ) } } diff --git a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift index 3b135aa23c..c875b07f60 100644 --- a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift +++ b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift @@ -110,7 +110,8 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { images: entityInfo.topicImages?.map(\.0) ?? [] ), renderReferenceDependencies: dependencies, - sourceLanguages: [entityInfo.language] + sourceLanguages: [entityInfo.language], + symbolKind: DocumentationNode.symbolKind(for: entityInfo.kind) ) } } diff --git a/Tests/SwiftDocCTests/Model/DocumentationNodeTests.swift b/Tests/SwiftDocCTests/Model/DocumentationNodeTests.swift index 9b371d23ed..5f17246fd5 100644 --- a/Tests/SwiftDocCTests/Model/DocumentationNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/DocumentationNodeTests.swift @@ -11,6 +11,7 @@ import Foundation import Markdown @testable import SwiftDocC +import SymbolKit import XCTest class DocumentationNodeTests: XCTestCase { @@ -41,4 +42,38 @@ class DocumentationNodeTests: XCTestCase { XCTAssertEqual(anchorSection.reference, node.reference.withFragment(expectedTitle)) } } + + func testDocumentationKindToSymbolKindMapping() throws { + // Testing all symbol kinds map to a documentation kind + for symbolKind in SymbolGraph.Symbol.KindIdentifier.allCases { + let documentationKind = DocumentationNode.kind(forKind: symbolKind) + guard documentationKind != .unknown else { + continue + } + + let roundtrippedSymbolKind = DocumentationNode.symbolKind(for: documentationKind) + XCTAssertEqual(symbolKind, roundtrippedSymbolKind) + } + + // Testing that documentation kinds correctly map to a symbol kind + // Sometimes there are multiple mappings from DocumentationKind -> SymbolKind, exclude those here and test them separately + let documentationKinds = DocumentationNode.Kind.allKnownValues + .filter({ ![.localVariable, .typeDef, .typeConstant, .`keyword`, .tag, .object].contains($0) }) + for documentationKind in documentationKinds { + let symbolKind = DocumentationNode.symbolKind(for: documentationKind) + if documentationKind.isSymbol { + let symbolKind = try XCTUnwrap(DocumentationNode.symbolKind(for: documentationKind), "Expected a symbol kind equivalent for \(documentationKind)") + let rountrippedDocumentationKind = DocumentationNode.kind(forKind: symbolKind) + XCTAssertEqual(documentationKind, rountrippedDocumentationKind) + } else { + XCTAssertNil(symbolKind) + } + } + + // Test the exception documentation kinds + XCTAssertEqual(DocumentationNode.symbolKind(for: .localVariable), .var) + XCTAssertEqual(DocumentationNode.symbolKind(for: .typeDef), .typealias) + XCTAssertEqual(DocumentationNode.symbolKind(for: .typeConstant), .typeProperty) + XCTAssertEqual(DocumentationNode.symbolKind(for: .object), .dictionary) + } } diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 49d48a2458..355ae1c327 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -1188,7 +1188,8 @@ class SemaToRenderNodeTests: XCTestCase { estimatedTime: nil ), renderReferenceDependencies: .init(), - sourceLanguages: [.objectiveC] + sourceLanguages: [.objectiveC], + symbolKind: .class ) return (reference, entity) } @@ -1218,7 +1219,8 @@ class SemaToRenderNodeTests: XCTestCase { estimatedTime: nil ), renderReferenceDependencies: .init(), - sourceLanguages: [.swift] + sourceLanguages: [.swift], + symbolKind: nil ) } } diff --git a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift b/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift index d96a0216f9..84b59d722c 100644 --- a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.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 @@ -53,7 +53,7 @@ class OutOfProcessReferenceResolverTests: XCTestCase { func assertResolvesTopicLink(makeResolver: (OutOfProcessReferenceResolver.ResolvedInformation) throws -> OutOfProcessReferenceResolver) throws { let testMetadata = OutOfProcessReferenceResolver.ResolvedInformation( - kind: .init(name: "Kind Name", id: "com.test.kind.id", isSymbol: true), + kind: .function, url: URL(string: "doc://com.test.bundle/something")!, title: "Resolved Title", abstract: "Resolved abstract for this topic.", @@ -133,6 +133,8 @@ class OutOfProcessReferenceResolverTests: XCTestCase { } else { XCTFail("Unexpected fragments variant patch") } + + XCTAssertEqual(entity.symbolKind, .func) } func testResolvingTopicLinkProcess() throws { From 083a3115d86e7a6c680adb6aa8974af095710dab Mon Sep 17 00:00:00 2001 From: Prajwal Nadig Date: Wed, 17 Sep 2025 18:29:12 +0100 Subject: [PATCH 33/90] Ignore all build dirs in `bin/check-source` (#1291) The `bin/check-source` script performs checks to ensure that headers are correctly updated in the modified files in a commit. This script ignored certain build directories since they contain generated Swift files. Some build directories were missing in this list (particularly the build directory emitted by `make-test-bundle`). The script has been modified to indiscrimanately ignore all `.build` directories. --- bin/check-source | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/check-source b/bin/check-source index 61780116e2..7ab7fa1de5 100755 --- a/bin/check-source +++ b/bin/check-source @@ -134,8 +134,7 @@ EOF ( cd "$here/.." find . \ - \( \! -path './.build/*' -a \ - \! -path './bin/benchmark/.build/*' -a \ + \( \! -path '*/.build/*' -a \ \! -name '.' -a \ \( "${matching_files[@]}" \) -a \ \( \! \( "${exceptions[@]}" \) \) \) | while read line; do From 3c67f9a796a41577f8ec886ea247bee423204a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 22 Sep 2025 15:38:01 +0200 Subject: [PATCH 34/90] Avoid expecting too many near miss suggestions (#1293) --- Tests/SwiftDocCTests/Utility/NearMissTests.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Tests/SwiftDocCTests/Utility/NearMissTests.swift b/Tests/SwiftDocCTests/Utility/NearMissTests.swift index 3f39321cd8..0c9ba39ea4 100644 --- a/Tests/SwiftDocCTests/Utility/NearMissTests.swift +++ b/Tests/SwiftDocCTests/Utility/NearMissTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -109,7 +109,6 @@ class NearMissTests: XCTestCase { "init(help:)", "init(exclusivity:help:)", "init(wrappedValue:exclusivity:help:)", - "init(wrappedValue:help:)", // Infrequently Used APIs "init(from:)", "wrappedValue", @@ -122,13 +121,12 @@ class NearMissTests: XCTestCase { checkBestMatches(for: flagSubpaths, against: "wrappedValue", expectedMatches: [ // These need to be in the best matches in this order. "wrappedValue", - "init(wrappedValue:help:)", - "init(wrappedValue:name:help:)", - "init(wrappedValue:exclusivity:help:)", ], acceptedMatches: [ // These don't need to be in the best matches but it's acceptable if they are. // // Most of the string doesn't match but it contains the full 'wrappedValue'. + "init(wrappedValue:name:help:)", + "init(wrappedValue:exclusivity:help:)", "init(wrappedValue:name:inversion:exclusivity:help:)", ]) } From 1d1ebbe3ebec95c3e531cbf22c4b7c73235b82ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Wed, 24 Sep 2025 09:41:51 +0200 Subject: [PATCH 35/90] Update the Swift tools version to 6.0 (#1268) --- Package.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 2381b5dfeb..f954d00903 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version:6.0 /* This source file is part of the Swift.org open source project @@ -15,6 +15,8 @@ import class Foundation.ProcessInfo let swiftSettings: [SwiftSetting] = [ .unsafeFlags(["-Xfrontend", "-warn-long-expression-type-checking=1000"], .when(configuration: .debug)), + .swiftLanguageMode(.v5), + .enableUpcomingFeature("ConciseMagicFile"), // SE-0274: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0274-magic-file.md .enableUpcomingFeature("ExistentialAny"), // SE-0335: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0335-existential-any.md .enableUpcomingFeature("InternalImportsByDefault"), // SE-0409: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0409-access-level-on-imports.md @@ -23,8 +25,8 @@ let swiftSettings: [SwiftSetting] = [ let package = Package( name: "SwiftDocC", platforms: [ - .macOS(.v12), - .iOS(.v15) + .macOS(.v13), + .iOS(.v16) ], products: [ .library( From 9413c599817abc9dd4f8feeed57a998b19a05a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Wed, 24 Sep 2025 14:19:22 +0200 Subject: [PATCH 36/90] Add a new version of the communication protocol between DocC and external link resolver executables (#1292) * Move old request and response to a separate file * Introduce new request and response versions * Allow external link executables to more information based on declared capabilities rdar://159610082 #802 * Support transient references in external content * Stop emitting no longer necessary warning about external references rdar://76656957 rdar://77906558 rdar://86334416 rdar://92254059 rdar://120424365 #341 * Remove trailing comma in parameters for Swift 5.9 compatibility * Address code review feedback * Add missing public initializers to new response diagnostic info type --- .../AbstractContainsFormattedTextOnly.swift | 3 +- .../Infrastructure/DocumentationContext.swift | 9 +- ...ocessReferenceResolver+Communication.swift | 198 ++++ ...enceResolver+DeprecatedCommunication.swift | 346 +++++++ .../OutOfProcessReferenceResolver.swift | 942 ++++++++---------- .../ExternalPathHierarchyResolver.swift | 31 +- .../LinkResolver+NavigatorIndex.swift | 46 +- .../Link Resolution/LinkResolver.swift | 88 +- .../LinkTargets/LinkDestinationSummary.swift | 168 +++- .../DocumentationContentRenderer.swift | 10 +- .../Model/Rendering/RenderContext.swift | 21 +- .../Rendering/RenderNodeTranslator.swift | 2 +- .../SwiftDocC/StaticAnalysis.md | 3 +- .../Benchmark/ExternalTopicsHashTests.swift | 18 +- ...stractContainsFormattedTextOnlyTests.swift | 5 +- .../ConvertService/ConvertServiceTests.swift | 33 +- .../DocumentationServer+DefaultTests.swift | 5 +- .../Indexing/ExternalRenderNodeTests.swift | 269 ++--- .../ExternalPathHierarchyResolverTests.swift | 2 +- .../ExternalReferenceResolverTests.swift | 47 +- .../TestExternalReferenceResolvers.swift | 43 +- .../LinkDestinationSummaryTests.swift | 75 +- .../Model/SemaToRenderNodeTests.swift | 42 +- ...utOfProcessReferenceResolverV1Tests.swift} | 93 +- ...OutOfProcessReferenceResolverV2Tests.swift | 680 +++++++++++++ bin/test-data-external-resolver | 204 ++-- 26 files changed, 2361 insertions(+), 1022 deletions(-) create mode 100644 Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift create mode 100644 Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift rename Tests/{SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift => SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift} (90%) create mode 100644 Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift diff --git a/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift b/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift index 05f30ea575..a2ed042d6c 100644 --- a/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift +++ b/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.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 @@ -14,6 +14,7 @@ public import Markdown /** A document's abstract may only contain formatted text. Images and links are not allowed. */ +@available(*, deprecated, message: "This check is no longer applicable. This deprecated API will be removed after 6.3 is released") public struct AbstractContainsFormattedTextOnly: Checker { public var problems: [Problem] = [Problem]() private var sourceFile: URL? diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index cd7fca83d7..654185a776 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -265,7 +265,6 @@ public class DocumentationContext { /// - source: The location of the document. private func check(_ document: Document, at source: URL) { var checker = CompositeChecker([ - AbstractContainsFormattedTextOnly(sourceFile: source).any(), DuplicateTopicsSections(sourceFile: source).any(), InvalidAdditionalTitle(sourceFile: source).any(), MissingAbstract(sourceFile: source).any(), @@ -2739,7 +2738,7 @@ public class DocumentationContext { knownEntityValue( reference: reference, valueInLocalEntity: \.availableSourceLanguages, - valueInExternalEntity: \.sourceLanguages + valueInExternalEntity: \.availableLanguages ) } @@ -2747,9 +2746,9 @@ public class DocumentationContext { func isSymbol(reference: ResolvedTopicReference) -> Bool { knownEntityValue( reference: reference, - valueInLocalEntity: { node in node.kind.isSymbol }, - valueInExternalEntity: { entity in entity.topicRenderReference.kind == .symbol } - ) + valueInLocalEntity: \.kind, + valueInExternalEntity: \.kind + ).isSymbol } // MARK: - Relationship queries diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift new file mode 100644 index 0000000000..b60dce179a --- /dev/null +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift @@ -0,0 +1,198 @@ +/* + 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 +*/ + +extension OutOfProcessReferenceResolver { + // MARK: Capabilities + + /// A set of optional capabilities that either DocC or your external link resolver declares that it supports. + /// + /// ## Supported messages + /// + /// If your external link resolver declares none of the optional capabilities, then DocC will only send it the following messages: + /// - ``RequestV2/link(_:)`` + /// - ``RequestV2/symbol(_:)`` + public struct Capabilities: OptionSet, Codable { + public let rawValue: Int + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + rawValue = try container.decode(Int.self) + } + } + + // MARK: Request & Response + + /// Request messages that DocC sends to the external link resolver. + /// + /// ## Topics + /// ### Base requests + /// + /// Your external link resolver always needs to handle the following requests regardless of its declared capabilities: + /// + /// - ``link(_:)`` + /// - ``symbol(_:)`` + public enum RequestV2: Codable { + /// A request to resolve a link + /// + /// Your external resolver + case link(String) + /// A request to resolve a symbol based on its precise identifier. + case symbol(String) + + // This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled, + // which is not available to Swift Packages without unsafe flags (rdar://78773361). + // This can be removed once that is available and applied to Swift-DocC (rdar://89033233). + @available(*, deprecated, message: """ + This enum is non-frozen and may be expanded in the future; add a `default` case, and do nothing in it, instead of matching this one. + Your external link resolver won't be passed new messages that it hasn't declared the corresponding capability for. + """) + case _nonFrozenEnum_useDefaultCase + + private enum CodingKeys: CodingKey { + case link, symbol // Default requests keys + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .link(let link): try container.encode(link, forKey: .link) + case .symbol(let id): try container.encode(id, forKey: .symbol) + + case ._nonFrozenEnum_useDefaultCase: + fatalError("Never use '_nonFrozenEnum_useDefaultCase' as a real case.") + } + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self = switch container.allKeys.first { + case .link?: .link( try container.decode(String.self, forKey: .link)) + case .symbol?: .symbol(try container.decode(String.self, forKey: .symbol)) + case nil: throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest + } + } + } + + /// A response message from the external link resolver. + /// + /// If your external resolver sends a response that's associated with a capability that DocC hasn't declared support for, then DocC will fail to handle the response. + public enum ResponseV2: Codable { + /// The initial identifier and capabilities message. + /// + /// Your external link resolver should send this message, exactly once, after it has launched to signal that its ready to receive requests. + /// + /// The capabilities that your external link resolver declares in this message determines which optional request messages that DocC will send. + /// If your resolver doesn't declare _any_ capabilities it only needs to handle the 3 default requests. See . + case identifierAndCapabilities(DocumentationBundle.Identifier, Capabilities) + /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. + case failure(DiagnosticInformation) + /// A response with the resolved information about the requested topic or symbol. + case resolved(LinkDestinationSummary) + + // This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled, + // which is not available to Swift Packages without unsafe flags (rdar://78773361). + // This can be removed once that is available and applied to Swift-DocC (rdar://89033233). + @available(*, deprecated, message: """ + This enum is non-frozen and may be expanded in the future; add a `default` case, and do nothing in it, instead of matching this one. + Your external link resolver won't be passed new messages that it hasn't declared the corresponding capability for. + """) + case _nonFrozenEnum_useDefaultCase + + private enum CodingKeys: String, CodingKey { + // Default response keys + case identifier, capabilities + case failure + case resolved + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self = switch container.allKeys.first { + case .identifier?, .capabilities?: + .identifierAndCapabilities( + try container.decode(DocumentationBundle.Identifier.self, forKey: .identifier), + try container.decode(Capabilities.self, forKey: .capabilities) + ) + case .failure?: + .failure(try container.decode(DiagnosticInformation.self, forKey: .failure)) + case .resolved?: + .resolved(try container.decode(LinkDestinationSummary.self, forKey: .resolved)) + case nil: + throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .identifierAndCapabilities(let identifier, let capabilities): + try container.encode(identifier, forKey: .identifier) + try container.encode(capabilities, forKey: .capabilities) + + case .failure(errorMessage: let diagnosticInfo): + try container.encode(diagnosticInfo, forKey: .failure) + + case .resolved(let summary): + try container.encode(summary, forKey: .resolved) + + case ._nonFrozenEnum_useDefaultCase: + fatalError("Never use '_nonFrozenEnum_useDefaultCase' for anything.") + } + } + } +} + +extension OutOfProcessReferenceResolver.ResponseV2 { + /// Information about why the external resolver failed to resolve the `link(_:)`, or `symbol(_:)` request. + public struct DiagnosticInformation: Codable { + /// A brief user-facing summary of the issue that caused the external resolver to fail. + public var summary: String + + /// A list of possible suggested solutions that can address the failure. + public var solutions: [Solution]? + + /// Creates a new value with information about why the external resolver failed to resolve the `link(_:)`, or `symbol(_:)` request. + /// - Parameters: + /// - summary: A brief user-facing summary of the issue that caused the external resolver to fail. + /// - solutions: Possible possible suggested solutions that can address the failure. + public init( + summary: String, + solutions: [Solution]? + ) { + self.summary = summary + self.solutions = solutions + } + + /// A possible solution to an external resolver issue. + public struct Solution: Codable { + /// A brief user-facing description of what the solution is. + public var summary: String + /// A full replacement of the link. + public var replacement: String? + + /// Creates a new solution to an external resolver issue + /// - Parameters: + /// - summary: A brief user-facing description of what the solution is. + /// - replacement: A full replacement of the link. + public init(summary: String, replacement: String?) { + self.summary = summary + self.replacement = replacement + } + } + } +} diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift new file mode 100644 index 0000000000..e95a3a01ff --- /dev/null +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift @@ -0,0 +1,346 @@ +/* + 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 +*/ + +public import Foundation +public import SymbolKit + +extension OutOfProcessReferenceResolver { + + // MARK: Request & Response + + /// An outdated version of a request message to send to the external link resolver. + /// + /// This can either be a request to resolve a topic URL or to resolve a symbol based on its precise identifier. + /// + /// @DeprecationSummary { + /// This version of the communication protocol is no longer recommended. Update to ``RequestV2`` and ``ResponseV2`` instead. + /// + /// The new version of the communication protocol both has a mechanism for expanding functionality in the future (through common ``Capabilities`` between DocC and the external resolver) and supports richer responses for both successful and and failed requests. + /// } + @available(*, deprecated, message: "This version of the communication protocol is no longer recommended. Update to `RequestV2` and `ResponseV2` instead.") + public typealias Request = _DeprecatedRequestV1 + + // Note this type isn't formally deprecated to avoid warnings in the ConvertService, which still _implicitly_ require this version of requests and responses. + public enum _DeprecatedRequestV1: Codable, CustomStringConvertible { + /// A request to resolve a topic URL + case topic(URL) + /// A request to resolve a symbol based on its precise identifier. + case symbol(String) + /// A request to resolve an asset. + case asset(AssetReference) + + private enum CodingKeys: CodingKey { + case topic + case symbol + case asset + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .topic(let url): + try container.encode(url, forKey: .topic) + case .symbol(let identifier): + try container.encode(identifier, forKey: .symbol) + case .asset(let assetReference): + try container.encode(assetReference, forKey: .asset) + } + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + switch container.allKeys.first { + case .topic?: + self = .topic(try container.decode(URL.self, forKey: .topic)) + case .symbol?: + self = .symbol(try container.decode(String.self, forKey: .symbol)) + case .asset?: + self = .asset(try container.decode(AssetReference.self, forKey: .asset)) + case nil: + throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest + } + } + + /// A plain text representation of the request message. + public var description: String { + switch self { + case .topic(let url): + return "topic: \(url.absoluteString.singleQuoted)" + case .symbol(let identifier): + return "symbol: \(identifier.singleQuoted)" + case .asset(let asset): + return "asset with name: \(asset.assetName), bundle identifier: \(asset.bundleID)" + } + } + } + + /// An outdated version of a response message from the external link resolver. + /// + /// @DeprecationSummary { + /// This version of the communication protocol is no longer recommended. Update to ``RequestV2`` and ``ResponseV2`` instead. + /// + /// The new version of the communication protocol both has a mechanism for expanding functionality in the future (through common ``Capabilities`` between DocC and the external resolver) and supports richer responses for both successful and and failed requests. + /// } + @available(*, deprecated, message: "This version of the communication protocol is no longer recommended. Update to `RequestV2` and `ResponseV2` instead.") + public typealias Response = _DeprecatedResponseV1 + + @available(*, deprecated, message: "This version of the communication protocol is no longer recommended. Update to `RequestV2` and `ResponseV2` instead.") + public enum _DeprecatedResponseV1: Codable { + /// A bundle identifier response. + /// + /// This message should only be sent once, after the external link resolver has launched. + case bundleIdentifier(String) + /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. + case errorMessage(String) + /// A response with the resolved information about the requested topic or symbol. + case resolvedInformation(ResolvedInformation) + /// A response with information about the resolved asset. + case asset(DataAsset) + + enum CodingKeys: String, CodingKey { + case bundleIdentifier + case errorMessage + case resolvedInformation + case asset + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + switch container.allKeys.first { + case .bundleIdentifier?: + self = .bundleIdentifier(try container.decode(String.self, forKey: .bundleIdentifier)) + case .errorMessage?: + self = .errorMessage(try container.decode(String.self, forKey: .errorMessage)) + case .resolvedInformation?: + self = .resolvedInformation(try container.decode(ResolvedInformation.self, forKey: .resolvedInformation)) + case .asset?: + self = .asset(try container.decode(DataAsset.self, forKey: .asset)) + case nil: + throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .bundleIdentifier(let bundleIdentifier): + try container.encode(bundleIdentifier, forKey: .bundleIdentifier) + case .errorMessage(let errorMessage): + try container.encode(errorMessage, forKey: .errorMessage) + case .resolvedInformation(let resolvedInformation): + try container.encode(resolvedInformation, forKey: .resolvedInformation) + case .asset(let assetReference): + try container.encode(assetReference, forKey: .asset) + } + } + } + + // MARK: Resolved Information + + /// A type used to transfer information about a resolved reference in the outdated and no longer recommended version of the external resolver communication protocol. + @available(*, deprecated, message: "This type is only used in the outdated, and no longer recommended, version of the out-of-process external resolver communication protocol.") + public struct ResolvedInformation: Codable { + /// Information about the resolved kind. + public let kind: DocumentationNode.Kind + /// Information about the resolved URL. + public let url: URL + /// Information about the resolved title. + public let title: String // DocumentationNode.Name + /// Information about the resolved abstract. + public let abstract: String // Markup + /// Information about the resolved language. + public let language: SourceLanguage + /// Information about the languages where the resolved node is available. + public let availableLanguages: Set + /// Information about the platforms and their versions where the resolved node is available, if any. + public let platforms: [PlatformAvailability]? + /// Information about the resolved declaration fragments, if any. + public let declarationFragments: DeclarationFragments? + + // We use the real types here because they're Codable and don't have public member-wise initializers. + + /// Platform availability for a resolved symbol reference. + public typealias PlatformAvailability = AvailabilityRenderItem + + /// The declaration fragments for a resolved symbol reference. + public typealias DeclarationFragments = SymbolGraph.Symbol.DeclarationFragments + + /// The platform names, derived from the platform availability. + public var platformNames: Set? { + return platforms.map { platforms in Set(platforms.compactMap { $0.name }) } + } + + /// Images that are used to represent the summarized element. + public var topicImages: [TopicImage]? + + /// References used in the content of the summarized element. + public var references: [any RenderReference]? + + /// The variants of content (kind, url, title, abstract, language, declaration) for this resolver information. + public var variants: [Variant]? + + /// A value that indicates whether this symbol is under development and likely to change. + var isBeta: Bool { + guard let platforms, !platforms.isEmpty else { + return false + } + + return platforms.allSatisfy { $0.isBeta == true } + } + + /// Creates a new resolved information value with all its values. + /// + /// - Parameters: + /// - kind: The resolved kind. + /// - url: The resolved URL. + /// - title: The resolved title + /// - abstract: The resolved (plain text) abstract. + /// - language: The resolved language. + /// - availableLanguages: The languages where the resolved node is available. + /// - platforms: The platforms and their versions where the resolved node is available, if any. + /// - declarationFragments: The resolved declaration fragments, if any. + /// - topicImages: Images that are used to represent the summarized element. + /// - references: References used in the content of the summarized element. + /// - variants: The variants of content for this resolver information. + public init( + kind: DocumentationNode.Kind, + url: URL, + title: String, + abstract: String, + language: SourceLanguage, + availableLanguages: Set, + platforms: [PlatformAvailability]? = nil, + declarationFragments: DeclarationFragments? = nil, + topicImages: [TopicImage]? = nil, + references: [any RenderReference]? = nil, + variants: [Variant]? = nil + ) { + self.kind = kind + self.url = url + self.title = title + self.abstract = abstract + self.language = language + self.availableLanguages = availableLanguages + self.platforms = platforms + self.declarationFragments = declarationFragments + self.topicImages = topicImages + self.references = references + self.variants = variants + } + + /// A variant of content for the resolved information. + /// + /// - Note: All properties except for ``traits`` are optional. If a property is `nil` it means that the value is the same as the resolved information's value. + public struct Variant: Codable { + /// The traits of the variant. + public let traits: [RenderNode.Variant.Trait] + + /// A wrapper for variant values that can either be specified, meaning the variant has a custom value, or not, meaning the variant has the same value as the resolved information. + /// + /// This alias is used to make the property declarations more explicit while at the same time offering the convenient syntax of optionals. + public typealias VariantValue = Optional + + /// The kind of the variant or `nil` if the kind is the same as the resolved information. + public let kind: VariantValue + /// The url of the variant or `nil` if the url is the same as the resolved information. + public let url: VariantValue + /// The title of the variant or `nil` if the title is the same as the resolved information. + public let title: VariantValue + /// The abstract of the variant or `nil` if the abstract is the same as the resolved information. + public let abstract: VariantValue + /// The language of the variant or `nil` if the language is the same as the resolved information. + public let language: VariantValue + /// The declaration fragments of the variant or `nil` if the declaration is the same as the resolved information. + /// + /// If the resolver information has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. + public let declarationFragments: VariantValue + + /// Creates a new resolved information variant with the values that are different from the resolved information values. + /// + /// - Parameters: + /// - traits: The traits of the variant. + /// - kind: The resolved kind. + /// - url: The resolved URL. + /// - title: The resolved title + /// - abstract: The resolved (plain text) abstract. + /// - language: The resolved language. + /// - declarationFragments: The resolved declaration fragments, if any. + public init( + traits: [RenderNode.Variant.Trait], + kind: VariantValue = nil, + url: VariantValue = nil, + title: VariantValue = nil, + abstract: VariantValue = nil, + language: VariantValue = nil, + declarationFragments: VariantValue = nil + ) { + self.traits = traits + self.kind = kind + self.url = url + self.title = title + self.abstract = abstract + self.language = language + self.declarationFragments = declarationFragments + } + } + } +} + +@available(*, deprecated, message: "This type is only used in the outdates, and no longer recommended, version of the out-of-process external resolver communication protocol.") +extension OutOfProcessReferenceResolver.ResolvedInformation { + enum CodingKeys: CodingKey { + case kind + case url + case title + case abstract + case language + case availableLanguages + case platforms + case declarationFragments + case topicImages + case references + case variants + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) + url = try container.decode(URL.self, forKey: .url) + title = try container.decode(String.self, forKey: .title) + abstract = try container.decode(String.self, forKey: .abstract) + language = try container.decode(SourceLanguage.self, forKey: .language) + availableLanguages = try container.decode(Set.self, forKey: .availableLanguages) + platforms = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.PlatformAvailability].self, forKey: .platforms) + declarationFragments = try container.decodeIfPresent(OutOfProcessReferenceResolver.ResolvedInformation.DeclarationFragments.self, forKey: .declarationFragments) + topicImages = try container.decodeIfPresent([TopicImage].self, forKey: .topicImages) + references = try container.decodeIfPresent([CodableRenderReference].self, forKey: .references).map { decodedReferences in + decodedReferences.map(\.reference) + } + variants = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.Variant].self, forKey: .variants) + + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.kind, forKey: .kind) + try container.encode(self.url, forKey: .url) + try container.encode(self.title, forKey: .title) + try container.encode(self.abstract, forKey: .abstract) + try container.encode(self.language, forKey: .language) + try container.encode(self.availableLanguages, forKey: .availableLanguages) + try container.encodeIfPresent(self.platforms, forKey: .platforms) + try container.encodeIfPresent(self.declarationFragments, forKey: .declarationFragments) + try container.encodeIfPresent(self.topicImages, forKey: .topicImages) + try container.encodeIfPresent(references?.map { CodableRenderReference($0) }, forKey: .references) + try container.encodeIfPresent(self.variants, forKey: .variants) + } +} diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift index 1988f8b074..bb7b74eedb 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift @@ -9,54 +9,78 @@ */ public import Foundation -import Markdown -public import SymbolKit +private import Markdown /// A reference resolver that launches and interactively communicates with another process or service to resolve links. /// /// If your external reference resolver or an external symbol resolver is implemented in another executable, you can use this object /// to communicate between DocC and the `docc` executable. /// -/// The launched executable is expected to follow the flow outlined below, sending ``OutOfProcessReferenceResolver/Request`` -/// and ``OutOfProcessReferenceResolver/Response`` values back and forth: +/// ## Launching and responding to requests /// -/// │ -/// 1 ▼ -/// ┌──────────────────┐ -/// │ Output bundle ID │ -/// └──────────────────┘ -/// │ -/// 2 ▼ -/// ┌──────────────────┐ -/// │ Wait for input │◀───┐ -/// └──────────────────┘ │ -/// │ │ -/// 3 ▼ │ repeat -/// ┌──────────────────┐ │ -/// │ Output resolved │ │ -/// │ information │────┘ -/// └──────────────────┘ +/// When creating an out-of-process resolver using ``init(processLocation:errorOutputHandler:)`` to communicate with another executable; +/// DocC launches your link resolver executable and declares _its_ own ``Capabilities`` as a raw value passed via the `--capabilities` option. +/// Your link resolver executable is expected to respond with a ``ResponseV2/identifierAndCapabilities(_:_:)`` message that declares: +/// - The documentation bundle identifier that the executable can to resolve links for. +/// - The capabilities that the resolver supports. /// -/// When resolving against a server, the server is expected to be able to handle messages of type "resolve-reference" with a -/// ``OutOfProcessReferenceResolver/Request`` payload and respond with messages of type "resolved-reference-response" -/// with a ``OutOfProcessReferenceResolver/Response`` payload. +/// After this "handshake" your link resolver executable is expected to wait for ``RequestV2`` messages from DocC and respond with exactly one ``ResponseV2`` per message. +/// A visual representation of this flow of execution can be seen in the diagram below: +/// +/// DocC link resolver executable +/// ┌─┐ ╎ +/// │ ├─────────── Launch ──────────▶┴┐ +/// │ │ --capabilities │ │ +/// │ │ │ │ +/// │ ◀───────── Handshake ─────────┤ │ +/// │ │ { "identifier" : ... , │ │ +/// │ │ "capabilities" : ... } │ │ +/// ┏ loop ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +/// ┃ │ │ │ │ ┃ +/// ┃ │ ├────────── Request ──────────▶ │ ┃ +/// ┃ │ │ { "link" : ... } OR │ │ ┃ +/// ┃ │ │ { "symbol" : ... } │ │ ┃ +/// ┃ │ │ │ │ ┃ +/// ┃ │ ◀────────── Response ─────────┤ │ ┃ +/// ┃ │ │ { "resolved" : ... } OR │ │ ┃ +/// ┃ │ │ { "failure" : ... } │ │ ┃ +/// ┃ │ │ │ │ ┃ +/// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +/// │ │ └─┘ +/// │ │ ╎ +/// +/// ## Interacting with a Convert Service +/// +/// When creating an out-of-process resolver using ``init(bundleID:server:convertRequestIdentifier:)`` to communicate with another process using a ``ConvertService``; +/// DocC sends that service `"resolve-reference"` messages with a``OutOfProcessReferenceResolver/Request`` payload and expects a `"resolved-reference-response"` responses with a ``OutOfProcessReferenceResolver/Response`` payload. +/// +/// Because the ``ConvertService`` messages are _implicitly_ tied to these outdated—and no longer recommended—request and response types, the richness of its responses is limited. +/// +/// - Note: when interacting with a ``ConvertService`` your service also needs to handle "asset" requests (``OutOfProcessReferenceResolver/Request/asset(_:)`` and responses that (``OutOfProcessReferenceResolver/Response/asset(_:)``) that link resolver executables don't need to handle. +/// +/// ## Topics +/// +/// - ``RequestV2`` +/// - ``ResponseV2`` /// /// ## See Also -/// - ``ExternalDocumentationSource`` -/// - ``GlobalExternalSymbolResolver`` /// - ``DocumentationContext/externalDocumentationSources`` /// - ``DocumentationContext/globalExternalSymbolResolver`` -/// - ``Request`` -/// - ``Response`` public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalExternalSymbolResolver { - private let externalLinkResolvingClient: any ExternalLinkResolving + private var implementation: any _Implementation /// The bundle identifier for the reference resolver in the other process. - public let bundleID: DocumentationBundle.Identifier + public var bundleID: DocumentationBundle.Identifier { + implementation.bundleID + } + + // This variable is used below for the `ConvertServiceFallbackResolver` conformance. + private var assetCache: [AssetReference: DataAsset] = [:] /// Creates a new reference resolver that interacts with another executable. /// /// Initializing the resolver will also launch the other executable. The other executable will remain running for the lifetime of this object. + /// This and the rest of the communication between DocC and the link resolver executable is described in /// /// - Parameters: /// - processLocation: The location of the other executable. @@ -73,12 +97,12 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE let longRunningProcess = try LongRunningProcess(location: processLocation, errorOutputHandler: errorOutputHandler) - guard case let .bundleIdentifier(decodedBundleIdentifier) = try longRunningProcess.sendAndWait(request: nil as Request?) as Response else { + guard let handshake: InitialHandshakeMessage = try? longRunningProcess.readInitialHandshakeMessage() else { throw Error.invalidBundleIdentifierOutputFromExecutable(processLocation) } - self.bundleID = .init(rawValue: decodedBundleIdentifier) - self.externalLinkResolvingClient = longRunningProcess + // This private type and protocol exist to silence deprecation warnings + self.implementation = (_ImplementationProvider() as (any _ImplementationProviding)).makeImplementation(for: handshake, longRunningProcess: longRunningProcess) } /// Creates a new reference resolver that interacts with a documentation service. @@ -90,179 +114,367 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE /// - server: The server to send link resolution requests to. /// - convertRequestIdentifier: The identifier that the resolver will use for convert requests that it sends to the server. public init(bundleID: DocumentationBundle.Identifier, server: DocumentationServer, convertRequestIdentifier: String?) throws { - self.bundleID = bundleID - self.externalLinkResolvingClient = LongRunningService( - server: server, convertRequestIdentifier: convertRequestIdentifier) + self.implementation = (_ImplementationProvider() as any _ImplementationProviding).makeImplementation( + for: .init(identifier: bundleID, capabilities: nil /* always use the V1 implementation */), + longRunningProcess: LongRunningService(server: server, convertRequestIdentifier: convertRequestIdentifier) + ) } - // MARK: External Reference Resolver - - public func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult { - switch reference { - case .resolved(let resolved): - return resolved + fileprivate struct InitialHandshakeMessage: Decodable { + var identifier: DocumentationBundle.Identifier + var capabilities: Capabilities? // The old V1 handshake didn't include this but the V2 requires it. + + init(identifier: DocumentationBundle.Identifier, capabilities: OutOfProcessReferenceResolver.Capabilities?) { + self.identifier = identifier + self.capabilities = capabilities + } + + private enum CodingKeys: CodingKey { + case bundleIdentifier // Legacy V1 handshake + case identifier, capabilities // V2 handshake + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) - case let .unresolved(unresolvedReference): - guard unresolvedReference.bundleID == bundleID else { - fatalError(""" - Attempted to resolve a local reference externally: \(unresolvedReference.description.singleQuoted). - DocC should never pass a reference to an external resolver unless it matches that resolver's bundle identifier. - """) - } - do { - guard let unresolvedTopicURL = unresolvedReference.topicURL.components.url else { - // Return the unresolved reference if the underlying URL is not valid - return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo("URL \(unresolvedReference.topicURL.absoluteString.singleQuoted) is not valid.")) - } - let resolvedInformation = try resolveInformationForTopicURL(unresolvedTopicURL) - return .success( resolvedReference(for: resolvedInformation) ) - } catch let error { - return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo(error)) + guard container.contains(.identifier) || container.contains(.bundleIdentifier) else { + throw DecodingError.keyNotFound(CodingKeys.identifier, .init(codingPath: decoder.codingPath, debugDescription: """ + Initial handshake message includes neither a '\(CodingKeys.identifier.stringValue)' key nor a '\(CodingKeys.bundleIdentifier.stringValue)' key. + """)) } + + self.identifier = try container.decodeIfPresent(DocumentationBundle.Identifier.self, forKey: .identifier) + ?? container.decode(DocumentationBundle.Identifier.self, forKey: .bundleIdentifier) + + self.capabilities = try container.decodeIfPresent(Capabilities.self, forKey: .capabilities) } } + // MARK: External Reference Resolver + + public func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult { + implementation.resolve(reference) + } + @_spi(ExternalLinks) // LinkResolver.ExternalEntity isn't stable API yet public func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - guard let resolvedInformation = referenceCache[reference.url] else { - fatalError("A topic reference that has already been resolved should always exist in the cache.") - } - return makeEntity(with: resolvedInformation, reference: reference.absoluteString) + implementation.entity(with: reference) } @_spi(ExternalLinks) // LinkResolver.ExternalEntity isn't stable API yet public func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { - guard let resolvedInformation = try? resolveInformationForSymbolIdentifier(preciseIdentifier) else { return nil } - - let reference = ResolvedTopicReference( - bundleID: "com.externally.resolved.symbol", - path: "/\(preciseIdentifier)", - sourceLanguages: sourceLanguages(for: resolvedInformation) - ) - let entity = makeEntity(with: resolvedInformation, reference: reference.absoluteString) - return (reference, entity) + implementation.symbolReferenceAndEntity(withPreciseIdentifier: preciseIdentifier) } +} + +// MARK: Implementations + +private protocol _Implementation: ExternalDocumentationSource, GlobalExternalSymbolResolver { + var bundleID: DocumentationBundle.Identifier { get } + var longRunningProcess: any ExternalLinkResolving { get } - private func makeEntity(with resolvedInformation: ResolvedInformation, reference: String) -> LinkResolver.ExternalEntity { - let (kind, role) = DocumentationContentRenderer.renderKindAndRole(resolvedInformation.kind, semantic: nil) - - var renderReference = TopicRenderReference( - identifier: .init(reference), - title: resolvedInformation.title, - // The resolved information only stores the plain text abstract https://github.com/swiftlang/swift-docc/issues/802 - abstract: [.text(resolvedInformation.abstract)], - url: resolvedInformation.url.path, - kind: kind, - role: role, - fragments: resolvedInformation.declarationFragments?.declarationFragments.map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) }, - isBeta: resolvedInformation.isBeta, - isDeprecated: (resolvedInformation.platforms ?? []).contains(where: { $0.deprecated != nil }), - images: resolvedInformation.topicImages ?? [] - ) - for variant in resolvedInformation.variants ?? [] { - if let title = variant.title { - renderReference.titleVariants.variants.append( - .init(traits: variant.traits, patch: [.replace(value: title)]) - ) - } - if let abstract = variant.abstract { - renderReference.abstractVariants.variants.append( - .init(traits: variant.traits, patch: [.replace(value: [.text(abstract)])]) - ) + // + func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult +} + +private extension _Implementation { + // Avoid some common boilerplate between implementations. + func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult { + switch reference { + case .resolved(let resolved): + return resolved + + case let .unresolved(unresolvedReference): + guard unresolvedReference.bundleID == bundleID else { + fatalError(""" + Attempted to resolve a local reference externally: \(unresolvedReference.description.singleQuoted). + DocC should never pass a reference to an external resolver unless it matches that resolver's bundle identifier. + """) + } + do { + // This is where each implementation differs + return try resolve(unresolvedReference: unresolvedReference) + } catch let error { + return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo(error)) + } + } + } +} + +// This private protocol allows the out-of-process resolver to create ImplementationV1 without deprecation warnings +private protocol _ImplementationProviding { + func makeImplementation(for handshake: OutOfProcessReferenceResolver.InitialHandshakeMessage, longRunningProcess: any ExternalLinkResolving) -> any _Implementation +} + +private extension OutOfProcessReferenceResolver { + // A concrete type with a deprecated implementation that can be cast to `_ImplementationProviding` to avoid deprecation warnings. + struct _ImplementationProvider: _ImplementationProviding { + @available(*, deprecated) // The V1 implementation is built around several now-deprecated types. This deprecation silences those depreciation warnings. + func makeImplementation(for handshake: OutOfProcessReferenceResolver.InitialHandshakeMessage, longRunningProcess: any ExternalLinkResolving) -> any _Implementation { + if let capabilities = handshake.capabilities { + return ImplementationV2(longRunningProcess: longRunningProcess, bundleID: handshake.identifier, executableCapabilities: capabilities) + } else { + return ImplementationV1(longRunningProcess: longRunningProcess, bundleID: handshake.identifier) } - if let declarationFragments = variant.declarationFragments { - renderReference.fragmentsVariants.variants.append( - .init(traits: variant.traits, patch: [.replace(value: declarationFragments?.declarationFragments.map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) })]) - ) + } + } +} + +// MARK: Version 1 (deprecated) + +extension OutOfProcessReferenceResolver { + /// The original—no longer recommended—version of the out-of-process resolver implementation. + /// + /// This implementation uses ``Request`` and ``Response`` which aren't extensible and have restrictions on the details of the response payloads. + @available(*, deprecated) // The V1 implementation is built around several now-deprecated types. This deprecation silences those depreciation warnings. + private final class ImplementationV1: _Implementation { + let bundleID: DocumentationBundle.Identifier + let longRunningProcess: any ExternalLinkResolving + + init(longRunningProcess: any ExternalLinkResolving, bundleID: DocumentationBundle.Identifier) { + self.longRunningProcess = longRunningProcess + self.bundleID = bundleID + } + + // This is fileprivate so that the ConvertService conformance below can access it. + fileprivate private(set) var referenceCache: [URL: ResolvedInformation] = [:] + private var symbolCache: [String: ResolvedInformation] = [:] + + func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult { + guard let unresolvedTopicURL = unresolvedReference.topicURL.components.url else { + // Return the unresolved reference if the underlying URL is not valid + return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo("URL \(unresolvedReference.topicURL.absoluteString.singleQuoted) is not valid.")) } + let resolvedInformation = try resolveInformationForTopicURL(unresolvedTopicURL) + return .success( resolvedReference(for: resolvedInformation) ) } - let dependencies = RenderReferenceDependencies( - topicReferences: [], - linkReferences: (resolvedInformation.references ?? []).compactMap { $0 as? LinkReference }, - imageReferences: (resolvedInformation.references ?? []).compactMap { $0 as? ImageReference } - ) - return LinkResolver.ExternalEntity( - topicRenderReference: renderReference, - renderReferenceDependencies: dependencies, - sourceLanguages: resolvedInformation.availableLanguages, - symbolKind: DocumentationNode.symbolKind(for: resolvedInformation.kind) - ) - } - - // MARK: Implementation - - private var referenceCache: [URL: ResolvedInformation] = [:] - private var symbolCache: [String: ResolvedInformation] = [:] - private var assetCache: [AssetReference: DataAsset] = [:] - - /// Makes a call to the other process to resolve information about a page based on its URL. - func resolveInformationForTopicURL(_ topicURL: URL) throws -> ResolvedInformation { - if let cachedInformation = referenceCache[topicURL] { - return cachedInformation + func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { + guard let resolvedInformation = referenceCache[reference.url] else { + fatalError("A topic reference that has already been resolved should always exist in the cache.") + } + return makeEntity(with: resolvedInformation, reference: reference.absoluteString) } - let response: Response = try externalLinkResolvingClient.sendAndWait(request: Request.topic(topicURL)) + func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { + guard let resolvedInformation = try? resolveInformationForSymbolIdentifier(preciseIdentifier) else { return nil } + + let reference = ResolvedTopicReference( + bundleID: "com.externally.resolved.symbol", + path: "/\(preciseIdentifier)", + sourceLanguages: sourceLanguages(for: resolvedInformation) + ) + let entity = makeEntity(with: resolvedInformation, reference: reference.absoluteString) + return (reference, entity) + } - switch response { - case .bundleIdentifier: - throw Error.executableSentBundleIdentifierAgain + /// Makes a call to the other process to resolve information about a page based on its URL. + private func resolveInformationForTopicURL(_ topicURL: URL) throws -> ResolvedInformation { + if let cachedInformation = referenceCache[topicURL] { + return cachedInformation + } + + let response: Response = try longRunningProcess.sendAndWait(request: Request.topic(topicURL)) - case .errorMessage(let errorMessage): - throw Error.forwardedErrorFromClient(errorMessage: errorMessage) + switch response { + case .bundleIdentifier: + throw Error.executableSentBundleIdentifierAgain + + case .errorMessage(let errorMessage): + throw Error.forwardedErrorFromClient(errorMessage: errorMessage) + + case .resolvedInformation(let resolvedInformation): + // Cache the information for the resolved reference, that's what's will be used when returning the entity later. + let resolvedReference = resolvedReference(for: resolvedInformation) + referenceCache[resolvedReference.url] = resolvedInformation + return resolvedInformation + + default: + throw Error.unexpectedResponse(response: response, requestDescription: "topic URL") + } + } + + /// Makes a call to the other process to resolve information about a symbol based on its precise identifier. + private func resolveInformationForSymbolIdentifier(_ preciseIdentifier: String) throws -> ResolvedInformation { + if let cachedInformation = symbolCache[preciseIdentifier] { + return cachedInformation + } - case .resolvedInformation(let resolvedInformation): - // Cache the information for the resolved reference, that's what's will be used when returning the entity later. - let resolvedReference = resolvedReference(for: resolvedInformation) - referenceCache[resolvedReference.url] = resolvedInformation - return resolvedInformation + let response: Response = try longRunningProcess.sendAndWait(request: Request.symbol(preciseIdentifier)) - default: - throw Error.unexpectedResponse(response: response, requestDescription: "topic URL") + switch response { + case .bundleIdentifier: + throw Error.executableSentBundleIdentifierAgain + + case .errorMessage(let errorMessage): + throw Error.forwardedErrorFromClient(errorMessage: errorMessage) + + case .resolvedInformation(let resolvedInformation): + symbolCache[preciseIdentifier] = resolvedInformation + return resolvedInformation + + default: + throw Error.unexpectedResponse(response: response, requestDescription: "symbol ID") + } + } + + private func resolvedReference(for resolvedInformation: ResolvedInformation) -> ResolvedTopicReference { + return ResolvedTopicReference( + bundleID: bundleID, + path: resolvedInformation.url.path, + fragment: resolvedInformation.url.fragment, + sourceLanguages: sourceLanguages(for: resolvedInformation) + ) + } + + private func sourceLanguages(for resolvedInformation: ResolvedInformation) -> Set { + // It is expected that the available languages contains the main language + return resolvedInformation.availableLanguages.union(CollectionOfOne(resolvedInformation.language)) + } + + private func makeEntity(with resolvedInformation: ResolvedInformation, reference: String) -> LinkResolver.ExternalEntity { + return LinkResolver.ExternalEntity( + kind: resolvedInformation.kind, + language: resolvedInformation.language, + relativePresentationURL: resolvedInformation.url.withoutHostAndPortAndScheme(), + referenceURL: URL(string: reference)!, + title: resolvedInformation.title, + // The resolved information only stores the plain text abstract and can't be changed. Use the version 2 communication protocol to support rich abstracts. + abstract: [.text(resolvedInformation.abstract)], + availableLanguages: resolvedInformation.availableLanguages, + platforms: resolvedInformation.platforms, + taskGroups: nil, + usr: nil, + declarationFragments: resolvedInformation.declarationFragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, + redirects: nil, + topicImages: resolvedInformation.topicImages, + references: resolvedInformation.references, + variants: (resolvedInformation.variants ?? []).map { variant in + .init( + traits: variant.traits, + kind: variant.kind, + language: variant.language, + relativePresentationURL: variant.url?.withoutHostAndPortAndScheme(), + title: variant.title, + abstract: variant.abstract.map { [.text($0)] }, + taskGroups: nil, + usr: nil, + declarationFragments: variant.declarationFragments.map { fragments in + fragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) } + } + ) + } + ) } } - - /// Makes a call to the other process to resolve information about a symbol based on its precise identifier. - private func resolveInformationForSymbolIdentifier(_ preciseIdentifier: String) throws -> ResolvedInformation { - if let cachedInformation = symbolCache[preciseIdentifier] { - return cachedInformation +} + +// MARK: Version 2 + +extension OutOfProcessReferenceResolver { + private final class ImplementationV2: _Implementation { + let longRunningProcess: any ExternalLinkResolving + let bundleID: DocumentationBundle.Identifier + let executableCapabilities: Capabilities + + init( + longRunningProcess: any ExternalLinkResolving, + bundleID: DocumentationBundle.Identifier, + executableCapabilities: Capabilities + ) { + self.longRunningProcess = longRunningProcess + self.bundleID = bundleID + self.executableCapabilities = executableCapabilities + } + + private var linkCache: [String /* either a USR or an absolute UnresolvedTopicReference */: LinkDestinationSummary] = [:] + + func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult { + let linkString = unresolvedReference.topicURL.absoluteString + if let cachedSummary = linkCache[linkString] { + return .success( makeReference(for: cachedSummary) ) + } + + let response: ResponseV2 = try longRunningProcess.sendAndWait(request: RequestV2.link(linkString)) + + switch response { + case .identifierAndCapabilities: + throw Error.executableSentBundleIdentifierAgain + + case .failure(let diagnosticMessage): + let solutions: [Solution] = (diagnosticMessage.solutions ?? []).map { + Solution(summary: $0.summary, replacements: $0.replacement.map { replacement in + [Replacement( + // The replacement ranges are relative to the link itself. + // To replace the entire link, we create a range from 0 to the original length, both offset by -4 (the "doc:" length) + range: SourceLocation(line: 0, column: -4, source: nil) ..< SourceLocation(line: 0, column: linkString.utf8.count - 4, source: nil), + replacement: replacement + )] + } ?? []) + } + return .failure( + unresolvedReference, + TopicReferenceResolutionErrorInfo(diagnosticMessage.summary, solutions: solutions) + ) + + case .resolved(let linkSummary): + // Cache the information for the original authored link + linkCache[linkString] = linkSummary + // Cache the information for the resolved reference. That's what's will be used when returning the entity later. + let reference = makeReference(for: linkSummary) + linkCache[reference.absoluteString] = linkSummary + if let usr = linkSummary.usr { + // If the page is a symbol, cache its information for the USR as well. + linkCache[usr] = linkSummary + } + return .success(reference) + + default: + throw Error.unexpectedResponse(response: response, requestDescription: "topic link") + } } - let response: Response = try externalLinkResolvingClient.sendAndWait(request: Request.symbol(preciseIdentifier)) + func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { + guard let linkSummary = linkCache[reference.url.standardized.absoluteString] else { + fatalError("A topic reference that has already been resolved should always exist in the cache.") + } + return linkSummary + } - switch response { - case .bundleIdentifier: - throw Error.executableSentBundleIdentifierAgain + func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { + if let cachedSummary = linkCache[preciseIdentifier] { + return (makeReference(for: cachedSummary), cachedSummary) + } + + guard case ResponseV2.resolved(let linkSummary)? = try? longRunningProcess.sendAndWait(request: RequestV2.symbol(preciseIdentifier)) else { + return nil + } - case .errorMessage(let errorMessage): - throw Error.forwardedErrorFromClient(errorMessage: errorMessage) + // Cache the information for the USR + linkCache[preciseIdentifier] = linkSummary - case .resolvedInformation(let resolvedInformation): - symbolCache[preciseIdentifier] = resolvedInformation - return resolvedInformation + // Cache the information for the resolved reference. + let reference = makeReference(for: linkSummary) + linkCache[reference.absoluteString] = linkSummary - default: - throw Error.unexpectedResponse(response: response, requestDescription: "symbol ID") + return (reference, linkSummary) + } + + private func makeReference(for linkSummary: LinkDestinationSummary) -> ResolvedTopicReference { + ResolvedTopicReference( + bundleID: linkSummary.referenceURL.host.map { .init(rawValue: $0) } ?? "unknown", + path: linkSummary.referenceURL.path, + fragment: linkSummary.referenceURL.fragment, + sourceLanguages: linkSummary.availableLanguages + ) } - } - - private func resolvedReference(for resolvedInformation: ResolvedInformation) -> ResolvedTopicReference { - return ResolvedTopicReference( - bundleID: bundleID, - path: resolvedInformation.url.path, - fragment: resolvedInformation.url.fragment, - sourceLanguages: sourceLanguages(for: resolvedInformation) - ) - } - - private func sourceLanguages(for resolvedInformation: ResolvedInformation) -> Set { - // It is expected that the available languages contains the main language - return resolvedInformation.availableLanguages.union(CollectionOfOne(resolvedInformation.language)) } } +// MARK: Cross process communication + private protocol ExternalLinkResolving { - func sendAndWait(request: Request?) throws -> Response + func sendAndWait(request: Request) throws -> Response } private class LongRunningService: ExternalLinkResolving { @@ -273,7 +485,7 @@ private class LongRunningService: ExternalLinkResolving { server: server, convertRequestIdentifier: convertRequestIdentifier) } - func sendAndWait(request: Request?) throws -> Response { + func sendAndWait(request: Request) throws -> Response { let responseData = try client.sendAndWait(request) return try JSONDecoder().decode(Response.self, from: responseData) } @@ -290,6 +502,7 @@ private class LongRunningProcess: ExternalLinkResolving { init(location: URL, errorOutputHandler: @escaping (String) -> Void) throws { let process = Process() process.executableURL = location + process.arguments = ["--capabilities", "\(OutOfProcessReferenceResolver.Capabilities().rawValue)"] process.standardInput = input process.standardOutput = output @@ -301,7 +514,7 @@ private class LongRunningProcess: ExternalLinkResolving { errorReadSource.setEventHandler { [errorOutput] in let data = errorOutput.fileHandleForReading.availableData let errorMessage = String(data: data, encoding: .utf8) - ?? "<\(ByteCountFormatter.string(fromByteCount: Int64(data.count), countStyle: .memory)) of non-utf8 data>" + ?? "<\(ByteCountFormatter.string(fromByteCount: Int64(data.count), countStyle: .memory)) of non-utf8 data>" errorOutputHandler(errorMessage) } @@ -319,16 +532,25 @@ private class LongRunningProcess: ExternalLinkResolving { private let output = Pipe() private let errorOutput = Pipe() private let errorReadSource: any DispatchSourceRead - - func sendAndWait(request: Request?) throws -> Response { - if let request { - guard let requestString = String(data: try JSONEncoder().encode(request), encoding: .utf8)?.appending("\n"), - let requestData = requestString.data(using: .utf8) - else { - throw OutOfProcessReferenceResolver.Error.unableToEncodeRequestToClient(requestDescription: request.description) - } - input.fileHandleForWriting.write(requestData) + + func readInitialHandshakeMessage() throws -> Response { + return try _readResponse() + } + + func sendAndWait(request: Request) throws -> Response { + // Send + guard let requestString = String(data: try JSONEncoder().encode(request), encoding: .utf8)?.appending("\n"), + let requestData = requestString.data(using: .utf8) + else { + throw OutOfProcessReferenceResolver.Error.unableToEncodeRequestToClient(requestDescription: "\(request)") } + input.fileHandleForWriting.write(requestData) + + // Receive + return try _readResponse() + } + + private func _readResponse() throws -> Response { var response = output.fileHandleForReading.availableData guard !response.isEmpty else { throw OutOfProcessReferenceResolver.Error.processDidExit(code: Int(process.terminationStatus)) @@ -341,8 +563,8 @@ private class LongRunningProcess: ExternalLinkResolving { // To avoid blocking forever we check if the response can be decoded after each chunk of data. return try JSONDecoder().decode(Response.self, from: response) } catch { - if case DecodingError.dataCorrupted = error, // If the data wasn't valid JSON, read more data and try to decode it again. - response.count.isMultiple(of: Int(PIPE_BUF)) // To reduce the risk of deadlocking, check that bytes so far is a multiple of the pipe buffer size. + if case DecodingError.dataCorrupted = error, // If the data wasn't valid JSON, read more data and try to decode it again. + response.count.isMultiple(of: Int(PIPE_BUF)) // To reduce the risk of deadlocking, check that bytes so far is a multiple of the pipe buffer size. { let moreResponseData = output.fileHandleForReading.availableData guard !moreResponseData.isEmpty else { @@ -351,7 +573,7 @@ private class LongRunningProcess: ExternalLinkResolving { response += moreResponseData continue } - + // Other errors are re-thrown as wrapped errors. throw OutOfProcessReferenceResolver.Error.unableToDecodeResponseFromClient(response, error) } @@ -371,6 +593,8 @@ private class LongRunningProcess: ExternalLinkResolving { #endif } +// MARK: Error + extension OutOfProcessReferenceResolver { /// Errors that may occur when communicating with an external reference resolver. enum Error: Swift.Error, DescribedError { @@ -400,7 +624,7 @@ extension OutOfProcessReferenceResolver { /// The request type was not known (neither 'topic' nor 'symbol'). case unknownTypeOfRequest /// Received an unknown type of response to sent request. - case unexpectedResponse(response: Response, requestDescription: String) + case unexpectedResponse(response: Any, requestDescription: String) /// A plain text representation of the error message. var errorDescription: String { @@ -435,360 +659,46 @@ extension OutOfProcessReferenceResolver { } } -extension OutOfProcessReferenceResolver { - - // MARK: Request & Response - - /// A request message to send to the external link resolver. - /// - /// This can either be a request to resolve a topic URL or to resolve a symbol based on its precise identifier. - public enum Request: Codable, CustomStringConvertible { - /// A request to resolve a topic URL - case topic(URL) - /// A request to resolve a symbol based on its precise identifier. - case symbol(String) - /// A request to resolve an asset. - case asset(AssetReference) - - private enum CodingKeys: CodingKey { - case topic - case symbol - case asset - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .topic(let url): - try container.encode(url, forKey: .topic) - case .symbol(let identifier): - try container.encode(identifier, forKey: .symbol) - case .asset(let assetReference): - try container.encode(assetReference, forKey: .asset) - } - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - switch container.allKeys.first { - case .topic?: - self = .topic(try container.decode(URL.self, forKey: .topic)) - case .symbol?: - self = .symbol(try container.decode(String.self, forKey: .symbol)) - case .asset?: - self = .asset(try container.decode(AssetReference.self, forKey: .asset)) - case nil: - throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest - } - } - - /// A plain text representation of the request message. - public var description: String { - switch self { - case .topic(let url): - return "topic: \(url.absoluteString.singleQuoted)" - case .symbol(let identifier): - return "symbol: \(identifier.singleQuoted)" - case .asset(let asset): - return "asset with name: \(asset.assetName), bundle identifier: \(asset.bundleID)" - } - } - } - - /// A response message from the external link resolver. - public enum Response: Codable { - /// A bundle identifier response. - /// - /// This message should only be sent once, after the external link resolver has launched. - case bundleIdentifier(String) - /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. - case errorMessage(String) - /// A response with the resolved information about the requested topic or symbol. - case resolvedInformation(ResolvedInformation) - /// A response with information about the resolved asset. - case asset(DataAsset) - - enum CodingKeys: String, CodingKey { - case bundleIdentifier - case errorMessage - case resolvedInformation - case asset - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - switch container.allKeys.first { - case .bundleIdentifier?: - self = .bundleIdentifier(try container.decode(String.self, forKey: .bundleIdentifier)) - case .errorMessage?: - self = .errorMessage(try container.decode(String.self, forKey: .errorMessage)) - case .resolvedInformation?: - self = .resolvedInformation(try container.decode(ResolvedInformation.self, forKey: .resolvedInformation)) - case .asset?: - self = .asset(try container.decode(DataAsset.self, forKey: .asset)) - case nil: - throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient - } - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .bundleIdentifier(let bundleIdentifier): - try container.encode(bundleIdentifier, forKey: .bundleIdentifier) - case .errorMessage(let errorMessage): - try container.encode(errorMessage, forKey: .errorMessage) - case .resolvedInformation(let resolvedInformation): - try container.encode(resolvedInformation, forKey: .resolvedInformation) - case .asset(let assetReference): - try container.encode(assetReference, forKey: .asset) - } - } - } - - // MARK: Resolved Information - - /// A type used to transfer information about a resolved reference to DocC from from a reference resolver in another executable. - public struct ResolvedInformation: Codable { - // This type is duplicating the information from LinkDestinationSummary with some minor differences. - // Changes generally need to be made in both places. It would be good to replace this with LinkDestinationSummary. - // FIXME: https://github.com/swiftlang/swift-docc/issues/802 - - /// Information about the resolved kind. - public let kind: DocumentationNode.Kind - /// Information about the resolved URL. - public let url: URL - /// Information about the resolved title. - public let title: String // DocumentationNode.Name - /// Information about the resolved abstract. - public let abstract: String // Markup - /// Information about the resolved language. - public let language: SourceLanguage - /// Information about the languages where the resolved node is available. - public let availableLanguages: Set - /// Information about the platforms and their versions where the resolved node is available, if any. - public let platforms: [PlatformAvailability]? - /// Information about the resolved declaration fragments, if any. - public let declarationFragments: DeclarationFragments? - - // We use the real types here because they're Codable and don't have public member-wise initializers. - - /// Platform availability for a resolved symbol reference. - public typealias PlatformAvailability = AvailabilityRenderItem - - /// The declaration fragments for a resolved symbol reference. - public typealias DeclarationFragments = SymbolGraph.Symbol.DeclarationFragments - - /// The platform names, derived from the platform availability. - public var platformNames: Set? { - return platforms.map { platforms in Set(platforms.compactMap { $0.name }) } - } - - /// Images that are used to represent the summarized element. - public var topicImages: [TopicImage]? - - /// References used in the content of the summarized element. - public var references: [any RenderReference]? - - /// The variants of content (kind, url, title, abstract, language, declaration) for this resolver information. - public var variants: [Variant]? - - /// A value that indicates whether this symbol is under development and likely to change. - var isBeta: Bool { - guard let platforms, !platforms.isEmpty else { - return false - } - - return platforms.allSatisfy { $0.isBeta == true } - } - - /// Creates a new resolved information value with all its values. - /// - /// - Parameters: - /// - kind: The resolved kind. - /// - url: The resolved URL. - /// - title: The resolved title - /// - abstract: The resolved (plain text) abstract. - /// - language: The resolved language. - /// - availableLanguages: The languages where the resolved node is available. - /// - platforms: The platforms and their versions where the resolved node is available, if any. - /// - declarationFragments: The resolved declaration fragments, if any. - /// - topicImages: Images that are used to represent the summarized element. - /// - references: References used in the content of the summarized element. - /// - variants: The variants of content for this resolver information. - public init( - kind: DocumentationNode.Kind, - url: URL, - title: String, - abstract: String, - language: SourceLanguage, - availableLanguages: Set, - platforms: [PlatformAvailability]? = nil, - declarationFragments: DeclarationFragments? = nil, - topicImages: [TopicImage]? = nil, - references: [any RenderReference]? = nil, - variants: [Variant]? = nil - ) { - self.kind = kind - self.url = url - self.title = title - self.abstract = abstract - self.language = language - self.availableLanguages = availableLanguages - self.platforms = platforms - self.declarationFragments = declarationFragments - self.topicImages = topicImages - self.references = references - self.variants = variants - } - - /// A variant of content for the resolved information. - /// - /// - Note: All properties except for ``traits`` are optional. If a property is `nil` it means that the value is the same as the resolved information's value. - public struct Variant: Codable { - /// The traits of the variant. - public let traits: [RenderNode.Variant.Trait] - - /// A wrapper for variant values that can either be specified, meaning the variant has a custom value, or not, meaning the variant has the same value as the resolved information. - /// - /// This alias is used to make the property declarations more explicit while at the same time offering the convenient syntax of optionals. - public typealias VariantValue = Optional - - /// The kind of the variant or `nil` if the kind is the same as the resolved information. - public let kind: VariantValue - /// The url of the variant or `nil` if the url is the same as the resolved information. - public let url: VariantValue - /// The title of the variant or `nil` if the title is the same as the resolved information. - public let title: VariantValue - /// The abstract of the variant or `nil` if the abstract is the same as the resolved information. - public let abstract: VariantValue - /// The language of the variant or `nil` if the language is the same as the resolved information. - public let language: VariantValue - /// The declaration fragments of the variant or `nil` if the declaration is the same as the resolved information. - /// - /// If the resolver information has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. - public let declarationFragments: VariantValue - - /// Creates a new resolved information variant with the values that are different from the resolved information values. - /// - /// - Parameters: - /// - traits: The traits of the variant. - /// - kind: The resolved kind. - /// - url: The resolved URL. - /// - title: The resolved title - /// - abstract: The resolved (plain text) abstract. - /// - language: The resolved language. - /// - declarationFragments: The resolved declaration fragments, if any. - public init( - traits: [RenderNode.Variant.Trait], - kind: VariantValue = nil, - url: VariantValue = nil, - title: VariantValue = nil, - abstract: VariantValue = nil, - language: VariantValue = nil, - declarationFragments: VariantValue = nil - ) { - self.traits = traits - self.kind = kind - self.url = url - self.title = title - self.abstract = abstract - self.language = language - self.declarationFragments = declarationFragments - } - } - } -} - -extension OutOfProcessReferenceResolver.ResolvedInformation { - enum CodingKeys: CodingKey { - case kind - case url - case title - case abstract - case language - case availableLanguages - case platforms - case declarationFragments - case topicImages - case references - case variants - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) - url = try container.decode(URL.self, forKey: .url) - title = try container.decode(String.self, forKey: .title) - abstract = try container.decode(String.self, forKey: .abstract) - language = try container.decode(SourceLanguage.self, forKey: .language) - availableLanguages = try container.decode(Set.self, forKey: .availableLanguages) - platforms = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.PlatformAvailability].self, forKey: .platforms) - declarationFragments = try container.decodeIfPresent(OutOfProcessReferenceResolver.ResolvedInformation.DeclarationFragments.self, forKey: .declarationFragments) - topicImages = try container.decodeIfPresent([TopicImage].self, forKey: .topicImages) - references = try container.decodeIfPresent([CodableRenderReference].self, forKey: .references).map { decodedReferences in - decodedReferences.map(\.reference) - } - variants = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.Variant].self, forKey: .variants) - - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(self.kind, forKey: .kind) - try container.encode(self.url, forKey: .url) - try container.encode(self.title, forKey: .title) - try container.encode(self.abstract, forKey: .abstract) - try container.encode(self.language, forKey: .language) - try container.encode(self.availableLanguages, forKey: .availableLanguages) - try container.encodeIfPresent(self.platforms, forKey: .platforms) - try container.encodeIfPresent(self.declarationFragments, forKey: .declarationFragments) - try container.encodeIfPresent(self.topicImages, forKey: .topicImages) - try container.encodeIfPresent(references?.map { CodableRenderReference($0) }, forKey: .references) - try container.encodeIfPresent(self.variants, forKey: .variants) - } -} +// MARK: Convert Service extension OutOfProcessReferenceResolver: ConvertServiceFallbackResolver { @_spi(ExternalLinks) + @available(*, deprecated, message: "The ConvertService is implicitly reliant on the deprecated `Request` and `Response` types.") public func entityIfPreviouslyResolved(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity? { - guard referenceCache.keys.contains(reference.url) else { return nil } + guard let implementation = implementation as? ImplementationV1 else { + assertionFailure("ConvertServiceFallbackResolver expects V1 requests and responses") + return nil + } + + guard implementation.referenceCache.keys.contains(reference.url) else { return nil } var entity = entity(with: reference) // The entity response doesn't include the assets that it references. // Before returning the entity, make sure that its references assets are included among the image dependencies. - for image in entity.topicRenderReference.images { + var references = entity.references ?? [] + + for image in entity.topicImages ?? [] { if let asset = resolve(assetNamed: image.identifier.identifier) { - entity.renderReferenceDependencies.imageReferences.append(ImageReference(identifier: image.identifier, imageAsset: asset)) + references.append(ImageReference(identifier: image.identifier, imageAsset: asset)) } } + if !references.isEmpty { + entity.references = references + } + return entity } + @available(*, deprecated, message: "The ConvertService is implicitly reliant on the deprecated `Request` and `Response` types.") func resolve(assetNamed assetName: String) -> DataAsset? { - return try? resolveInformationForAsset(named: assetName) - } - - func resolveInformationForAsset(named assetName: String) throws -> DataAsset { let assetReference = AssetReference(assetName: assetName, bundleID: bundleID) if let asset = assetCache[assetReference] { return asset } - let response = try externalLinkResolvingClient.sendAndWait( - request: Request.asset(AssetReference(assetName: assetName, bundleID: bundleID)) - ) as Response - - switch response { - case .asset(let asset): - assetCache[assetReference] = asset - return asset - case .errorMessage(let errorMessage): - throw Error.forwardedErrorFromClient(errorMessage: errorMessage) - default: - throw Error.unexpectedResponse(response: response, requestDescription: "asset") + guard case .asset(let asset)? = try? implementation.longRunningProcess.sendAndWait(request: Request.asset(assetReference)) as Response else { + return nil } + return asset } } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift index 6e6fdbb3ab..4c6fac3500 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift @@ -87,31 +87,10 @@ final class ExternalPathHierarchyResolver { /// /// - Precondition: The `reference` was previously resolved by this resolver. func entity(_ reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - guard let resolvedInformation = content[reference] else { + guard let alreadyResolvedSummary = content[reference] else { fatalError("The resolver should only be asked for entities that it resolved.") } - - let topicReferences: [ResolvedTopicReference] = (resolvedInformation.references ?? []).compactMap { - guard let renderReference = $0 as? TopicRenderReference, - let url = URL(string: renderReference.identifier.identifier), - let bundleID = url.host - else { - return nil - } - return ResolvedTopicReference(bundleID: .init(rawValue: bundleID), path: url.path, fragment: url.fragment, sourceLanguage: .swift) - } - let dependencies = RenderReferenceDependencies( - topicReferences: topicReferences, - linkReferences: (resolvedInformation.references ?? []).compactMap { $0 as? LinkReference }, - imageReferences: (resolvedInformation.references ?? []).compactMap { $0 as? ImageReference } - ) - - return .init( - topicRenderReference: resolvedInformation.topicRenderReference(), - renderReferenceDependencies: dependencies, - sourceLanguages: resolvedInformation.availableLanguages, - symbolKind: DocumentationNode.symbolKind(for: resolvedInformation.kind) - ) + return alreadyResolvedSummary } // MARK: Deserialization @@ -182,9 +161,9 @@ private extension Sequence { // MARK: ExternalEntity -private extension LinkDestinationSummary { +extension LinkDestinationSummary { /// A value that indicates whether this symbol is under development and likely to change. - var isBeta: Bool { + private var isBeta: Bool { guard let platforms, !platforms.isEmpty else { return false } @@ -193,7 +172,7 @@ private extension LinkDestinationSummary { } /// Create a topic render render reference for this link summary and its content variants. - func topicRenderReference() -> TopicRenderReference { + func makeTopicRenderReference() -> TopicRenderReference { let (kind, role) = DocumentationContentRenderer.renderKindAndRole(kind, semantic: nil) var titleVariants = VariantCollection(defaultValue: title) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift index 8f0966fc79..8fa9575bd5 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift @@ -13,69 +13,76 @@ import SymbolKit /// A rendering-friendly representation of a external node. package struct ExternalRenderNode { - /// Underlying external entity backing this external node. - private var externalEntity: LinkResolver.ExternalEntity - + private var entity: LinkResolver.ExternalEntity + private var topicRenderReference: TopicRenderReference + /// The bundle identifier for this external node. private var bundleIdentifier: DocumentationBundle.Identifier + // This type is designed to misrepresent external content as local content to fit in with the navigator. + // This spreads the issue to more code rather than fixing it, which adds technical debt and can be fragile. + // + // At the time of writing this comment, this type and the issues it comes with has spread to 6 files (+ 3 test files). + // Luckily, none of that code is public API so we can modify or even remove it without compatibility restrictions. init(externalEntity: LinkResolver.ExternalEntity, bundleIdentifier: DocumentationBundle.Identifier) { - self.externalEntity = externalEntity + self.entity = externalEntity self.bundleIdentifier = bundleIdentifier + self.topicRenderReference = externalEntity.makeTopicRenderReference() } /// The identifier of the external render node. package var identifier: ResolvedTopicReference { ResolvedTopicReference( bundleID: bundleIdentifier, - path: externalEntity.topicRenderReference.url, - sourceLanguages: externalEntity.sourceLanguages + path: entity.referenceURL.path, + fragment: entity.referenceURL.fragment, + sourceLanguages: entity.availableLanguages ) } /// The kind of this documentation node. var kind: RenderNode.Kind { - externalEntity.topicRenderReference.kind + topicRenderReference.kind } /// The symbol kind of this documentation node. /// /// This value is `nil` if the referenced page is not a symbol. var symbolKind: SymbolGraph.Symbol.KindIdentifier? { - externalEntity.symbolKind + DocumentationNode.symbolKind(for: entity.kind) } /// The additional "role" assigned to the symbol, if any /// /// This value is `nil` if the referenced page is not a symbol. var role: String? { - externalEntity.topicRenderReference.role + topicRenderReference.role } /// The variants of the title. var titleVariants: VariantCollection { - externalEntity.topicRenderReference.titleVariants + topicRenderReference.titleVariants } /// The variants of the abbreviated declaration of the symbol to display in navigation. var navigatorTitleVariants: VariantCollection<[DeclarationRenderSection.Token]?> { - externalEntity.topicRenderReference.navigatorTitleVariants + topicRenderReference.navigatorTitleVariants } /// Author provided images that represent this page. var images: [TopicImage] { - externalEntity.topicRenderReference.images + entity.topicImages ?? [] } /// The identifier of the external reference. var externalIdentifier: RenderReferenceIdentifier { - externalEntity.topicRenderReference.identifier + topicRenderReference.identifier } /// List of variants of the same external node for various languages. var variants: [RenderNode.Variant]? { - externalEntity.sourceLanguages.map { - RenderNode.Variant(traits: [.interfaceLanguage($0.id)], paths: [externalEntity.topicRenderReference.url]) + entity.availableLanguages.map { + RenderNode.Variant(traits: [.interfaceLanguage($0.id)], paths: [topicRenderReference.url]) } } @@ -83,13 +90,16 @@ package struct ExternalRenderNode { /// /// This value is `false` if the referenced page is not a symbol. var isBeta: Bool { - externalEntity.topicRenderReference.isBeta + topicRenderReference.isBeta } } /// A language specific representation of an external render node value for building a navigator index. struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { - var identifier: ResolvedTopicReference + private var _identifier: ResolvedTopicReference + var identifier: ResolvedTopicReference { + _identifier + } var kind: RenderNode.Kind var metadata: ExternalRenderNodeMetadataRepresentation @@ -109,7 +119,7 @@ struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { } let traits = trait.map { [$0] } ?? [] - self.identifier = renderNode.identifier.withSourceLanguages(Set(arrayLiteral: traitLanguage)) + self._identifier = renderNode.identifier.withSourceLanguages([traitLanguage]) self.kind = renderNode.kind self.metadata = ExternalRenderNodeMetadataRepresentation( diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift index 20291f286d..27cd2d2a1a 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift @@ -9,7 +9,6 @@ */ import Foundation -public import SymbolKit /// A class that resolves documentation links by orchestrating calls to other link resolver implementations. public class LinkResolver { @@ -42,53 +41,7 @@ public class LinkResolver { /// The minimal information about an external entity necessary to render links to it on another page. @_spi(ExternalLinks) // This isn't stable API yet. - public struct ExternalEntity { - /// Creates a new external entity. - /// - Parameters: - /// - topicRenderReference: The render reference for this external topic. - /// - renderReferenceDependencies: Any dependencies for the render reference. - /// - sourceLanguages: The different source languages for which this page is available. - /// - symbolKind: The kind of symbol that's being referenced. - @_spi(ExternalLinks) - public init( - topicRenderReference: TopicRenderReference, - renderReferenceDependencies: RenderReferenceDependencies, - sourceLanguages: Set, - symbolKind: SymbolGraph.Symbol.KindIdentifier? = nil - ) { - self.topicRenderReference = topicRenderReference - self.renderReferenceDependencies = renderReferenceDependencies - self.sourceLanguages = sourceLanguages - self.symbolKind = symbolKind - } - - /// The render reference for this external topic. - var topicRenderReference: TopicRenderReference - /// Any dependencies for the render reference. - /// - /// For example, if the external content contains links or images, those are included here. - var renderReferenceDependencies: RenderReferenceDependencies - /// The different source languages for which this page is available. - var sourceLanguages: Set - /// The kind of symbol that's being referenced. - /// - /// This value is `nil` if the entity does not reference a symbol. - /// - /// For example, the navigator requires specific knowledge about what type of external symbol is being linked to. - var symbolKind: SymbolGraph.Symbol.KindIdentifier? - - /// Creates a pre-render new topic content value to be added to a render context's reference store. - func topicContent() -> RenderReferenceStore.TopicContent { - return .init( - renderReference: topicRenderReference, - canonicalPath: nil, - taskGroups: nil, - source: nil, - isDocumentationExtensionContent: false, - renderReferenceDependencies: renderReferenceDependencies - ) - } - } + public typealias ExternalEntity = LinkDestinationSummary // Currently we use the same format as DocC outputs for its own pages. That may change depending on what information we need here. /// Attempts to resolve an unresolved reference. /// @@ -268,3 +221,42 @@ private final class FallbackResolverBasedLinkResolver { return nil } } + +extension LinkResolver.ExternalEntity { + /// Creates a pre-render new topic content value to be added to a render context's reference store. + func makeTopicContent() -> RenderReferenceStore.TopicContent { + .init( + renderReference: makeTopicRenderReference(), + canonicalPath: nil, + taskGroups: nil, + source: nil, + isDocumentationExtensionContent: false, + renderReferenceDependencies: makeRenderDependencies() + ) + } + + func makeRenderDependencies() -> RenderReferenceDependencies { + guard let references else { return .init() } + + return .init( + topicReferences: references.compactMap { ($0 as? TopicRenderReference)?.topicReference(languages: availableLanguages) }, + linkReferences: references.compactMap { $0 as? LinkReference }, + imageReferences: references.compactMap { $0 as? ImageReference } + ) + } +} + +private extension TopicRenderReference { + func topicReference(languages: Set) -> ResolvedTopicReference? { + guard let url = URL(string: identifier.identifier), let rawBundleID = url.host else { + return nil + } + return ResolvedTopicReference( + bundleID: .init(rawValue: rawBundleID), + path: url.path, + fragment: url.fragment, + // TopicRenderReference doesn't have language information. Also, the reference's languages _doesn't_ specify the languages of the linked entity. + sourceLanguages: languages + ) + } +} diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 8c6112dbb1..0a53b58c3d 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -201,7 +201,8 @@ public struct LinkDestinationSummary: Codable, Equatable { /// Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. /// /// If the summarized element has an image but the variant doesn't, this property will be `Optional.some(nil)`. - public let topicImages: VariantValue<[TopicImage]?> + @available(*, deprecated, message: "`TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") + public let topicImages: VariantValue<[TopicImage]?> = nil /// Creates a new summary variant with the values that are different from the main summarized values. /// @@ -215,7 +216,6 @@ public struct LinkDestinationSummary: Codable, Equatable { /// - taskGroups: The taskGroups of the variant or `nil` if the taskGroups is the same as the summarized element. /// - usr: The precise symbol identifier of the variant or `nil` if the precise symbol identifier is the same as the summarized element. /// - declarationFragments: The declaration of the variant or `nil` if the declaration is the same as the summarized element. - /// - topicImages: Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. public init( traits: [RenderNode.Variant.Trait], kind: VariantValue = nil, @@ -225,8 +225,7 @@ public struct LinkDestinationSummary: Codable, Equatable { abstract: VariantValue = nil, taskGroups: VariantValue<[LinkDestinationSummary.TaskGroup]?> = nil, usr: VariantValue = nil, - declarationFragments: VariantValue = nil, - topicImages: VariantValue<[TopicImage]?> = nil + declarationFragments: VariantValue = nil ) { self.traits = traits self.kind = kind @@ -237,7 +236,32 @@ public struct LinkDestinationSummary: Codable, Equatable { self.taskGroups = taskGroups self.usr = usr self.declarationFragments = declarationFragments - self.topicImages = topicImages + } + + @available(*, deprecated, renamed: "init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:declarationFragments:)", message: "Use `init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:declarationFragments:)` instead. `TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") + public init( + traits: [RenderNode.Variant.Trait], + kind: VariantValue = nil, + language: VariantValue = nil, + relativePresentationURL: VariantValue = nil, + title: VariantValue = nil, + abstract: VariantValue = nil, + taskGroups: VariantValue<[LinkDestinationSummary.TaskGroup]?> = nil, + usr: VariantValue = nil, + declarationFragments: VariantValue = nil, + topicImages: VariantValue<[TopicImage]?> = nil + ) { + self.init( + traits: traits, + kind: kind, + language: language, + relativePresentationURL: relativePresentationURL, + title: title, + abstract: abstract, + taskGroups: taskGroups, + usr: usr, + declarationFragments: declarationFragments + ) } } @@ -469,8 +493,7 @@ extension LinkDestinationSummary { abstract: nilIfEqual(main: abstract, variant: abstractVariant), taskGroups: nilIfEqual(main: taskGroups, variant: taskGroupVariants[variantTraits]), usr: nil, // The symbol variant uses the same USR - declarationFragments: nilIfEqual(main: declaration, variant: declarationVariant), - topicImages: nil // The symbol variant doesn't currently have their own images + declarationFragments: nilIfEqual(main: declaration, variant: declarationVariant) ) } @@ -578,13 +601,28 @@ extension LinkDestinationSummary { public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(kind.id, forKey: .kind) + if DocumentationNode.Kind.allKnownValues.contains(kind) { + try container.encode(kind.id, forKey: .kind) + } else { + try container.encode(kind, forKey: .kind) + } try container.encode(relativePresentationURL, forKey: .relativePresentationURL) try container.encode(referenceURL, forKey: .referenceURL) try container.encode(title, forKey: .title) try container.encodeIfPresent(abstract, forKey: .abstract) - try container.encode(language.id, forKey: .language) - try container.encode(availableLanguages.map { $0.id }, forKey: .availableLanguages) + if SourceLanguage.knownLanguages.contains(language) { + try container.encode(language.id, forKey: .language) + } else { + try container.encode(language, forKey: .language) + } + var languagesContainer = container.nestedUnkeyedContainer(forKey: .availableLanguages) + for language in availableLanguages.sorted() { + if SourceLanguage.knownLanguages.contains(language) { + try languagesContainer.encode(language.id) + } else { + try languagesContainer.encode(language) + } + } try container.encodeIfPresent(platforms, forKey: .platforms) try container.encodeIfPresent(taskGroups, forKey: .taskGroups) try container.encodeIfPresent(usr, forKey: .usr) @@ -600,28 +638,47 @@ extension LinkDestinationSummary { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let kindID = try container.decode(String.self, forKey: .kind) - guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { - throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") + // Kind can either be a known identifier or a full structure + do { + let kindID = try container.decode(String.self, forKey: .kind) + guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { + throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") + } + kind = foundKind + } catch { + kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) } - kind = foundKind relativePresentationURL = try container.decode(URL.self, forKey: .relativePresentationURL) referenceURL = try container.decode(URL.self, forKey: .referenceURL) title = try container.decode(String.self, forKey: .title) abstract = try container.decodeIfPresent(Abstract.self, forKey: .abstract) - let languageID = try container.decode(String.self, forKey: .language) - guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") - } - language = foundLanguage - - let availableLanguageIDs = try container.decode([String].self, forKey: .availableLanguages) - availableLanguages = try Set(availableLanguageIDs.map { languageID in + // Language can either be an identifier of a known language or a full structure + do { + let languageID = try container.decode(String.self, forKey: .language) guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .availableLanguages, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + } + language = foundLanguage + } catch DecodingError.typeMismatch { + language = try container.decode(SourceLanguage.self, forKey: .language) + } + + // The set of languages can be a mix of identifiers and full structure + var languagesContainer = try container.nestedUnkeyedContainer(forKey: .availableLanguages) + var decodedLanguages = Set() + while !languagesContainer.isAtEnd { + do { + let languageID = try languagesContainer.decode(String.self) + guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { + throw DecodingError.dataCorruptedError(forKey: .availableLanguages, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + } + decodedLanguages.insert( foundLanguage ) + } catch DecodingError.typeMismatch { + decodedLanguages.insert( try languagesContainer.decode(SourceLanguage.self) ) } - return foundLanguage - }) + } + availableLanguages = decodedLanguages + platforms = try container.decodeIfPresent([AvailabilityRenderItem].self, forKey: .platforms) taskGroups = try container.decodeIfPresent([TaskGroup].self, forKey: .taskGroups) usr = try container.decodeIfPresent(String.self, forKey: .usr) @@ -638,7 +695,7 @@ extension LinkDestinationSummary { extension LinkDestinationSummary.Variant { enum CodingKeys: String, CodingKey { - case traits, kind, title, abstract, language, usr, taskGroups, topicImages + case traits, kind, title, abstract, language, usr, taskGroups case relativePresentationURL = "path" case declarationFragments = "fragments" } @@ -646,44 +703,58 @@ extension LinkDestinationSummary.Variant { public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(traits, forKey: .traits) - try container.encodeIfPresent(kind?.id, forKey: .kind) + if let kind { + if DocumentationNode.Kind.allKnownValues.contains(kind) { + try container.encode(kind.id, forKey: .kind) + } else { + try container.encode(kind, forKey: .kind) + } + } try container.encodeIfPresent(relativePresentationURL, forKey: .relativePresentationURL) try container.encodeIfPresent(title, forKey: .title) try container.encodeIfPresent(abstract, forKey: .abstract) - try container.encodeIfPresent(language?.id, forKey: .language) + if let language { + if SourceLanguage.knownLanguages.contains(language) { + try container.encode(language.id, forKey: .language) + } else { + try container.encode(language, forKey: .language) + } + } try container.encodeIfPresent(usr, forKey: .usr) try container.encodeIfPresent(declarationFragments, forKey: .declarationFragments) try container.encodeIfPresent(taskGroups, forKey: .taskGroups) - try container.encodeIfPresent(topicImages, forKey: .topicImages) } public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - - let traits = try container.decode([RenderNode.Variant.Trait].self, forKey: .traits) - for case .interfaceLanguage(let languageID) in traits { - guard SourceLanguage.knownLanguages.contains(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .traits, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") - } - } - self.traits = traits - - let kindID = try container.decodeIfPresent(String.self, forKey: .kind) - if let kindID { - guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { - throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") + traits = try container.decode([RenderNode.Variant.Trait].self, forKey: .traits) + + if container.contains(.kind) { + // The kind can either be a known identifier or a full structure + do { + let kindID = try container.decode(String.self, forKey: .kind) + guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { + throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") + } + kind = foundKind + } catch { + kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) } - kind = foundKind } else { kind = nil } - let languageID = try container.decodeIfPresent(String.self, forKey: .language) - if let languageID { - guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + if container.contains(.language) { + // Language can either be an identifier of a known language or a full structure + do { + let languageID = try container.decode(String.self, forKey: .language) + guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { + throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + } + language = foundLanguage + } catch DecodingError.typeMismatch { + language = try container.decode(SourceLanguage.self, forKey: .language) } - language = foundLanguage } else { language = nil } @@ -693,7 +764,6 @@ extension LinkDestinationSummary.Variant { usr = try container.decodeIfPresent(String?.self, forKey: .usr) declarationFragments = try container.decodeIfPresent(LinkDestinationSummary.DeclarationFragments?.self, forKey: .declarationFragments) taskGroups = try container.decodeIfPresent([LinkDestinationSummary.TaskGroup]?.self, forKey: .taskGroups) - topicImages = try container.decodeIfPresent([TopicImage]?.self, forKey: .topicImages) } } diff --git a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift index 2ad9f43a8b..465a58aa8a 100644 --- a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift +++ b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift @@ -309,11 +309,13 @@ public class DocumentationContentRenderer { // try resolving that way as a fallback after looking up `documentationCache`. titleVariants = .init(defaultVariantValue: topicGraphOnlyNode.title) } else if let external = documentationContext.externalCache[reference] { - dependencies.topicReferences.append(contentsOf: external.renderReferenceDependencies.topicReferences) - dependencies.linkReferences.append(contentsOf: external.renderReferenceDependencies.linkReferences) - dependencies.imageReferences.append(contentsOf: external.renderReferenceDependencies.imageReferences) + let renderDependencies = external.makeRenderDependencies() - return external.topicRenderReference + dependencies.topicReferences.append(contentsOf: renderDependencies.topicReferences) + dependencies.linkReferences.append(contentsOf: renderDependencies.linkReferences) + dependencies.imageReferences.append(contentsOf: renderDependencies.imageReferences) + + return external.makeTopicRenderReference() } else { titleVariants = .init(defaultVariantValue: reference.absoluteString) } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift b/Sources/SwiftDocC/Model/Rendering/RenderContext.swift index dade49b7a0..26a299c100 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContext.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 @@ -90,7 +90,24 @@ public struct RenderContext { // Add all the external content to the topic store for (reference, entity) in documentationContext.externalCache { - topics[reference] = entity.topicContent() + topics[reference] = entity.makeTopicContent() + + // Also include transitive dependencies in the store, so that the external entity can reference them. + for case let dependency as TopicRenderReference in (entity.references ?? []) { + guard let url = URL(string: dependency.identifier.identifier), let rawBundleID = url.host else { + // This dependency doesn't have a valid topic reference, skip adding it to the render context. + continue + } + + let dependencyReference = ResolvedTopicReference( + bundleID: .init(rawValue: rawBundleID), + path: url.path, + fragment: url.fragment, + // TopicRenderReference doesn't have language information. Also, the reference's languages _doesn't_ specify the languages of the linked entity. + sourceLanguages: reference.sourceLanguages + ) + topics[dependencyReference] = .init(renderReference: dependency, canonicalPath: nil, taskGroups: nil, source: nil, isDocumentationExtensionContent: false) + } } self.store = RenderReferenceStore(topics: topics, assets: assets) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index 1fd747b15a..aad67d6b85 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -1478,7 +1478,7 @@ public struct RenderNodeTranslator: SemanticVisitor { } } else if let entity = context.externalCache[resolved] { collectedTopicReferences.append(resolved) - destinationsMap[destination] = entity.topicRenderReference.title + destinationsMap[destination] = entity.title } else { fatalError("A successfully resolved reference should have either local or external content.") } diff --git a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md index 5155029943..a2330698d4 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md +++ b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md @@ -12,7 +12,6 @@ Run static analysis checks on markup files. ### Predefined Checks -- ``AbstractContainsFormattedTextOnly`` - ``DuplicateTopicsSections`` - ``InvalidAdditionalTitle`` - ``MissingAbstract`` @@ -20,4 +19,4 @@ Run static analysis checks on markup files. - ``NonOverviewHeadingChecker`` - ``SeeAlsoInTopicsHeadingChecker`` - + diff --git a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift index 4e87138288..ee391de385 100644 --- a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift +++ b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift @@ -22,17 +22,13 @@ class ExternalTopicsGraphHashTests: XCTestCase { func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { let reference = ResolvedTopicReference(bundleID: "com.test.symbols", path: "/\(preciseIdentifier)", sourceLanguage: SourceLanguage.swift) let entity = LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(preciseIdentifier), - title: preciseIdentifier, - abstract: [], - url: "/" + preciseIdentifier, - kind: .symbol, - estimatedTime: nil - ), - renderReferenceDependencies: .init(), - sourceLanguages: [.swift], - symbolKind: .class + kind: .class, + language: .swift, + relativePresentationURL: URL(string: "/\(preciseIdentifier)")!, + referenceURL: reference.url, + title: preciseIdentifier, + availableLanguages: [.swift], + variants: [] ) return (reference, entity) } diff --git a/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift index 6add5371de..958f31c3db 100644 --- a/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift +++ b/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.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 @@ -12,6 +12,9 @@ import XCTest @testable import SwiftDocC import Markdown +// This tests `AbstractContainsFormattedTextOnly` which are deprecated. +// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. +@available(*, deprecated) class AbstractContainsFormattedTextOnlyTests: XCTestCase { var checker = AbstractContainsFormattedTextOnly(sourceFile: nil) diff --git a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift index 2550a52345..a46afb677e 100644 --- a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift @@ -532,6 +532,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertPageWithLinkResolvingAndKnownPathComponents() throws { let symbolGraphFile = Bundle.module.url( forResource: "mykit-one-symbol", @@ -827,7 +830,9 @@ class ConvertServiceTests: XCTestCase { ) } } - + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertTutorialWithCode() throws { let tutorialContent = """ @Tutorial(time: 99) { @@ -998,6 +1003,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertArticleWithImageReferencesAndDetailedGridLinks() throws { let articleData = try XCTUnwrap(""" # First article @@ -1718,6 +1726,9 @@ class ConvertServiceTests: XCTestCase { #endif } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertPageWithLinkResolving() throws { let symbolGraphFile = Bundle.module.url( forResource: "mykit-one-symbol", @@ -2007,6 +2018,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertTopLevelSymbolWithLinkResolving() throws { let symbolGraphFile = Bundle.module.url( forResource: "one-symbol-top-level", @@ -2114,6 +2128,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testOrderOfLinkResolutionRequestsForDocLink() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( @@ -2152,6 +2169,9 @@ class ConvertServiceTests: XCTestCase { XCTAssertEqual(expectedLinkResolutionRequests, receivedLinkResolutionRequests) } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testOrderOfLinkResolutionRequestsForDeeplyNestedSymbol() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( @@ -2191,6 +2211,9 @@ class ConvertServiceTests: XCTestCase { XCTAssertEqual(expectedLinkResolutionRequests, receivedLinkResolutionRequests) } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testOrderOfLinkResolutionRequestsForSymbolLink() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( @@ -2226,7 +2249,10 @@ class ConvertServiceTests: XCTestCase { XCTAssertEqual(expectedLinkResolutionRequests, receivedLinkResolutionRequests) } - func linkResolutionRequestsForConvertRequest(_ request: ConvertRequest) throws -> [String] { + // This test helper uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) + private func linkResolutionRequestsForConvertRequest(_ request: ConvertRequest) throws -> [String] { var receivedLinkResolutionRequests = [String]() let mockLinkResolvingService = LinkResolvingService { message in do { @@ -2314,6 +2340,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testDoesNotResolveLinksUnlessBundleIDMatches() throws { let tempURL = try createTempFolder(content: [ Folder(name: "unit-test.docc", content: [ diff --git a/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift b/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift index 6f4ddc2c61..9d2fcf89b1 100644 --- a/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.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 @@ -56,6 +56,9 @@ class DocumentationServer_DefaultTests: XCTestCase { wait(for: [expectation], timeout: 1.0) } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testQueriesLinkResolutionServer() throws { let symbolGraphFile = Bundle.module.url( forResource: "mykit-one-symbol", diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index 4ca7dc631e..d0602e48ca 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -11,6 +11,7 @@ import Foundation import XCTest @_spi(ExternalLinks) @testable import SwiftDocC +import SwiftDocCTestUtilities class ExternalRenderNodeTests: XCTestCase { private func generateExternalResolver() -> TestMultiResultExternalReferenceResolver { @@ -56,7 +57,6 @@ class ExternalRenderNodeTests: XCTestCase { } func testExternalRenderNode() async throws { - let externalResolver = generateExternalResolver() let (_, bundle, context) = try await testBundleAndContext( copying: "MixedLanguageFramework", @@ -79,37 +79,33 @@ class ExternalRenderNodeTests: XCTestCase { try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) } - var externalRenderNodes = [ExternalRenderNode]() - for externalLink in context.externalCache { - externalRenderNodes.append( - ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) - ) - } - externalRenderNodes.sort(by: \.titleVariants.defaultValue) + let externalRenderNodes = context.externalCache.valuesByReference.values.map { + ExternalRenderNode(externalEntity: $0, bundleIdentifier: bundle.id) + }.sorted(by: \.titleVariants.defaultValue) XCTAssertEqual(externalRenderNodes.count, 4) - XCTAssertEqual(externalRenderNodes[0].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCArticle") + XCTAssertEqual(externalRenderNodes[0].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/objCArticle") XCTAssertEqual(externalRenderNodes[0].kind, .article) XCTAssertEqual(externalRenderNodes[0].symbolKind, nil) XCTAssertEqual(externalRenderNodes[0].role, "article") XCTAssertEqual(externalRenderNodes[0].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCArticle") XCTAssertTrue(externalRenderNodes[0].isBeta) - XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCSymbol") + XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/objCSymbol") XCTAssertEqual(externalRenderNodes[1].kind, .symbol) XCTAssertEqual(externalRenderNodes[1].symbolKind, .func) XCTAssertEqual(externalRenderNodes[1].role, "symbol") XCTAssertEqual(externalRenderNodes[1].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCSymbol") XCTAssertFalse(externalRenderNodes[1].isBeta) - XCTAssertEqual(externalRenderNodes[2].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftArticle") + XCTAssertEqual(externalRenderNodes[2].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/swiftArticle") XCTAssertEqual(externalRenderNodes[2].kind, .article) XCTAssertEqual(externalRenderNodes[2].symbolKind, nil) XCTAssertEqual(externalRenderNodes[2].role, "article") XCTAssertEqual(externalRenderNodes[2].externalIdentifier.identifier, "doc://com.test.external/path/to/external/swiftArticle") XCTAssertFalse(externalRenderNodes[2].isBeta) - XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftSymbol") + XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/swiftSymbol") XCTAssertEqual(externalRenderNodes[3].kind, .symbol) XCTAssertEqual(externalRenderNodes[3].symbolKind, .class) XCTAssertEqual(externalRenderNodes[3].role, "symbol") @@ -118,33 +114,34 @@ class ExternalRenderNodeTests: XCTestCase { } func testExternalRenderNodeVariantRepresentation() throws { - let renderReferenceIdentifier = RenderReferenceIdentifier(forExternalLink: "doc://com.test.external/path/to/external/symbol") + let reference = ResolvedTopicReference(bundleID: "com.test.external", path: "/path/to/external/symbol", sourceLanguages: [.swift, .objectiveC]) // Variants for the title let swiftTitle = "Swift Symbol" - let occTitle = "Occ Symbol" - - // Variants for the navigator title - let navigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "symbol", kind: .identifier)] - let occNavigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "occ_symbol", kind: .identifier)] + let objcTitle = "Objective-C Symbol" // Variants for the fragments - let fragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] - let occFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] + let swiftFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] + let objcFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] let externalEntity = LinkResolver.ExternalEntity( - topicRenderReference: .init( - identifier: renderReferenceIdentifier, - titleVariants: .init(defaultValue: swiftTitle, objectiveCValue: occTitle), - abstractVariants: .init(defaultValue: []), - url: "/example/path/to/external/symbol", - kind: .symbol, - fragmentsVariants: .init(defaultValue: fragments, objectiveCValue: occFragments), - navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle) - ), - renderReferenceDependencies: .init(), - sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")], - symbolKind: .func) + kind: .function, + language: .swift, + relativePresentationURL: URL(string: "/example/path/to/external/symbol")!, + referenceURL: reference.url, + title: swiftTitle, + availableLanguages: [.swift, .objectiveC], + usr: "some-unique-symbol-id", + declarationFragments: swiftFragments, + variants: [ + .init( + traits: [.interfaceLanguage(SourceLanguage.objectiveC.id)], + language: .objectiveC, + title: objcTitle, + declarationFragments: objcFragments + ) + ] + ) let externalRenderNode = ExternalRenderNode( externalEntity: externalEntity, bundleIdentifier: "com.test.external" @@ -154,39 +151,52 @@ class ExternalRenderNodeTests: XCTestCase { NavigatorExternalRenderNode(renderNode: externalRenderNode) ) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) - XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) XCTAssertFalse(swiftNavigatorExternalRenderNode.metadata.isBeta) let objcNavigatorExternalRenderNode = try XCTUnwrap( - NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) + NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage(SourceLanguage.objectiveC.id)) ) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, objcTitle) XCTAssertFalse(objcNavigatorExternalRenderNode.metadata.isBeta) } func testNavigatorWithExternalNodes() async throws { - let externalResolver = generateExternalResolver() - let (_, bundle, context) = try await testBundleAndContext( - copying: "MixedLanguageFramework", - externalResolvers: [externalResolver.bundleID: externalResolver] - ) { url in - let mixedLanguageFrameworkExtension = """ - # ``MixedLanguageFramework`` - - This symbol has a Swift and Objective-C variant. + let catalog = Folder(name: "ModuleName.docc", content: [ + Folder(name: "swift", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .swift, kind: .class, pathComponents: ["SomeClass"]) + ])) + ]), + Folder(name: "clang", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .objectiveC, kind: .class, pathComponents: ["TLASomeClass"]) + ])) + ]), + + InfoPlist(identifier: "some.custom.identifier"), + + TextFile(name: "ModuleName.md", utf8Content: """ + # ``ModuleName`` + + Curate a few external language-specific symbols and articles - ## Topics + ## Topics - ### External Reference + ### External Reference - - - - - - - - - """ - try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) - } + - + - + - + - + """), + ]) + + var configuration = DocumentationContext.Configuration() + let externalResolver = generateExternalResolver() + configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") + let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() @@ -205,53 +215,63 @@ class ExternalRenderNodeTests: XCTestCase { let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) // Verify that there are no uncurated external links at the top level - let swiftTopLevelExternalNodes = renderIndex.interfaceLanguages["swift"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - let occTopLevelExternalNodes = renderIndex.interfaceLanguages["occ"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - XCTAssertEqual(swiftTopLevelExternalNodes.count, 0) - XCTAssertEqual(occTopLevelExternalNodes.count, 0) + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.count(where: \.isExternal), 0) + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.count(where: \.isExternal), 0) // Verify that the curated external links are part of the index. - let swiftExternalNodes = renderIndex.interfaceLanguages["swift"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - let occExternalNodes = renderIndex.interfaceLanguages["occ"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let swiftExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) + let objcExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) XCTAssertEqual(swiftExternalNodes.count, 2) - XCTAssertEqual(occExternalNodes.count, 2) + XCTAssertEqual(objcExternalNodes.count, 2) XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle", "SwiftSymbol"]) - XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) - XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) - XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) - XCTAssert(swiftExternalNodes.first { $0.title == "SwiftArticle" }?.isBeta == false) - XCTAssert(swiftExternalNodes.first { $0.title == "SwiftSymbol" }?.isBeta == true) - XCTAssert(occExternalNodes.first { $0.title == "ObjCArticle" }?.isBeta == true) - XCTAssert(occExternalNodes.first { $0.title == "ObjCSymbol" }?.isBeta == false) + XCTAssertEqual(objcExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) + XCTAssert(swiftExternalNodes.first?.isBeta == false) + XCTAssert(swiftExternalNodes.last?.isBeta == true) + XCTAssert(objcExternalNodes.first?.isBeta == true) + XCTAssert(objcExternalNodes.last?.isBeta == false) XCTAssertEqual(swiftExternalNodes.map(\.type), ["article", "class"]) - XCTAssertEqual(occExternalNodes.map(\.type), ["article", "func"]) + XCTAssertEqual(objcExternalNodes.map(\.type), ["article", "func"]) } func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() async throws { + let catalog = Folder(name: "ModuleName.docc", content: [ + Folder(name: "swift", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .swift, kind: .class, pathComponents: ["SomeClass"]) + ])) + ]), + Folder(name: "clang", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .objectiveC, kind: .class, pathComponents: ["TLASomeClass"]) + ])) + ]), + + InfoPlist(identifier: "some.custom.identifier"), + + TextFile(name: "ModuleName.md", utf8Content: """ + # ``ModuleName`` + + Curate and link to a few external language-specific symbols and articles + + It also has an external reference which is not curated in the Topics section: + + + + ## Topics + + ### External Reference + + - + - + """), + ]) + + var configuration = DocumentationContext.Configuration() let externalResolver = generateExternalResolver() + configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") - let (_, bundle, context) = try await testBundleAndContext( - copying: "MixedLanguageFramework", - externalResolvers: [externalResolver.bundleID: externalResolver] - ) { url in - let mixedLanguageFrameworkExtension = """ - # ``MixedLanguageFramework`` - - This symbol has a Swift and Objective-C variant. - - It also has an external reference which is not curated in the Topics section: - - - - ## Topics - - ### External Reference - - - - - - """ - try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) - } let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() @@ -268,55 +288,52 @@ class ExternalRenderNodeTests: XCTestCase { } builder.finalize() let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) - // Verify that there are no uncurated external links at the top level - let swiftTopLevelExternalNodes = renderIndex.interfaceLanguages["swift"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - let occTopLevelExternalNodes = renderIndex.interfaceLanguages["occ"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - XCTAssertEqual(swiftTopLevelExternalNodes.count, 0) - XCTAssertEqual(occTopLevelExternalNodes.count, 0) + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.count(where: \.isExternal), 0) + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.count(where: \.isExternal), 0) // Verify that the curated external links are part of the index. - let swiftExternalNodes = renderIndex.interfaceLanguages["swift"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - let occExternalNodes = renderIndex.interfaceLanguages["occ"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let swiftExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) + let objcExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) XCTAssertEqual(swiftExternalNodes.count, 1) - XCTAssertEqual(occExternalNodes.count, 1) + XCTAssertEqual(objcExternalNodes.count, 1) XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle"]) - XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCSymbol"]) - XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) - XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) + XCTAssertEqual(objcExternalNodes.map(\.title), ["ObjCSymbol"]) XCTAssertEqual(swiftExternalNodes.map(\.type), ["article"]) - XCTAssertEqual(occExternalNodes.map(\.type), ["func"]) + XCTAssertEqual(objcExternalNodes.map(\.type), ["func"]) } func testExternalRenderNodeVariantRepresentationWhenIsBeta() throws { - let renderReferenceIdentifier = RenderReferenceIdentifier(forExternalLink: "doc://com.test.external/path/to/external/symbol") + let reference = ResolvedTopicReference(bundleID: "com.test.external", path: "/path/to/external/symbol", sourceLanguages: [.swift, .objectiveC]) // Variants for the title let swiftTitle = "Swift Symbol" - let occTitle = "Occ Symbol" - - // Variants for the navigator title - let navigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "symbol", kind: .identifier)] - let occNavigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "occ_symbol", kind: .identifier)] + let objcTitle = "Objective-C Symbol" // Variants for the fragments - let fragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] - let occFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] + let swiftFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] + let objcFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] let externalEntity = LinkResolver.ExternalEntity( - topicRenderReference: .init( - identifier: renderReferenceIdentifier, - titleVariants: .init(defaultValue: swiftTitle, objectiveCValue: occTitle), - abstractVariants: .init(defaultValue: []), - url: "/example/path/to/external/symbol", - kind: .symbol, - fragmentsVariants: .init(defaultValue: fragments, objectiveCValue: occFragments), - navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle), - isBeta: true - ), - renderReferenceDependencies: .init(), - sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")]) + kind: .function, + language: .swift, + relativePresentationURL: URL(string: "/example/path/to/external/symbol")!, + referenceURL: reference.url, + title: swiftTitle, + availableLanguages: [.swift, .objectiveC], + platforms: [.init(name: "Platform name", introduced: "1.2.3", isBeta: true)], + usr: "some-unique-symbol-id", + declarationFragments: swiftFragments, + variants: [ + .init( + traits: [.interfaceLanguage(SourceLanguage.objectiveC.id)], + language: .objectiveC, + title: objcTitle, + declarationFragments: objcFragments + ) + ] + ) let externalRenderNode = ExternalRenderNode( externalEntity: externalEntity, bundleIdentifier: "com.test.external" @@ -326,14 +343,12 @@ class ExternalRenderNodeTests: XCTestCase { NavigatorExternalRenderNode(renderNode: externalRenderNode) ) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) - XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) XCTAssertTrue(swiftNavigatorExternalRenderNode.metadata.isBeta) let objcNavigatorExternalRenderNode = try XCTUnwrap( - NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) + NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage(SourceLanguage.objectiveC.id)) ) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, objcTitle) XCTAssertTrue(objcNavigatorExternalRenderNode.metadata.isBeta) } } diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift index 7af1fa06eb..9a62ddf51e 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift @@ -950,7 +950,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { switch result { case .success(let resolved): let entity = externalResolver.entity(resolved) - XCTAssertEqual(entity.topicRenderReference.isBeta, isBeta, file: file, line: line) + XCTAssertEqual(entity.makeTopicRenderReference().isBeta, isBeta, file: file, line: line) case .failure(_, let errorInfo): XCTFail("Unexpectedly failed to resolve \(label) link: \(errorInfo.message) \(errorInfo.solutions.map(\.summary).joined(separator: ", "))", file: file, line: line) } diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 13b47f6743..6e2526397e 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -38,22 +38,15 @@ class ExternalReferenceResolverTests: XCTestCase { fatalError("It is a programming mistake to retrieve an entity for a reference that the external resolver didn't resolve.") } - let (kind, role) = DocumentationContentRenderer.renderKindAndRole(resolvedEntityKind, semantic: nil) return LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: resolvedEntityTitle, - abstract: [.text("Externally Resolved Markup Content")], - url: "/example" + reference.path + (reference.fragment.map { "#\($0)" } ?? ""), - kind: kind, - role: role, - fragments: resolvedEntityDeclarationFragments?.declarationFragments.map { fragment in - return DeclarationRenderSection.Token(fragment: fragment, identifier: nil) - } - ), - renderReferenceDependencies: RenderReferenceDependencies(), - sourceLanguages: [resolvedEntityLanguage], - symbolKind: nil + kind: resolvedEntityKind, + language: resolvedEntityLanguage, + relativePresentationURL: URL(string: "/example" + reference.path + (reference.fragment.map { "#\($0)" } ?? ""))!, + referenceURL: reference.url, + title: resolvedEntityTitle, + availableLanguages: [resolvedEntityLanguage], + declarationFragments: resolvedEntityDeclarationFragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, + variants: [] ) } } @@ -445,7 +438,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(firstExternalRenderReference.identifier.identifier, "doc://com.test.external/path/to/external-page-with-topic-image-1") XCTAssertEqual(firstExternalRenderReference.title, "First external page with topic image") - XCTAssertEqual(firstExternalRenderReference.url, "/example/path/to/external-page-with-topic-image-1") + XCTAssertEqual(firstExternalRenderReference.url, "/path/to/external-page-with-topic-image-1") XCTAssertEqual(firstExternalRenderReference.kind, .article) XCTAssertEqual(firstExternalRenderReference.images, [ @@ -457,7 +450,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(secondExternalRenderReference.identifier.identifier, "doc://com.test.external/path/to/external-page-with-topic-image-2") XCTAssertEqual(secondExternalRenderReference.title, "Second external page with topic image") - XCTAssertEqual(secondExternalRenderReference.url, "/example/path/to/external-page-with-topic-image-2") + XCTAssertEqual(secondExternalRenderReference.url, "/path/to/external-page-with-topic-image-2") XCTAssertEqual(secondExternalRenderReference.kind, .article) XCTAssertEqual(secondExternalRenderReference.images, [ @@ -631,19 +624,15 @@ class ExternalReferenceResolverTests: XCTestCase { func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { referencesCreatingEntityFor.insert(reference) - // Return an empty node + // Return an "empty" node return .init( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: "Resolved", - abstract: [], - url: reference.absoluteString, - kind: .symbol, - estimatedTime: nil - ), - renderReferenceDependencies: RenderReferenceDependencies(), - sourceLanguages: [.swift], - symbolKind: .property + kind: .instanceProperty, + language: .swift, + relativePresentationURL: reference.url.withoutHostAndPortAndScheme(), + referenceURL: reference.url, + title: "Resolved", + availableLanguages: [.swift], + variants: [] ) } } diff --git a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift index c875b07f60..7ea89aa72c 100644 --- a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift +++ b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift @@ -53,7 +53,7 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { let entity = entityInfo(path: path) return .success( - ResolvedTopicReference(bundleID: bundleID, path: entity.referencePath,fragment: entity.fragment,sourceLanguage: entity.language) + ResolvedTopicReference(bundleID: bundleID, path: entity.referencePath, fragment: entity.fragment, sourceLanguage: entity.language) ) } } @@ -84,34 +84,19 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { } private func makeNode(for entityInfo: EntityInfo, reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - let (kind, role) = DocumentationContentRenderer.renderKindAndRole(entityInfo.kind, semantic: nil) - - let dependencies: RenderReferenceDependencies - if let topicImages = entityInfo.topicImages { - dependencies = .init(imageReferences: topicImages.map { topicImage, altText in - return ImageReference(identifier: topicImage.identifier, altText: altText, imageAsset: assetsToReturn[topicImage.identifier.identifier] ?? .init()) - }) - } else { - dependencies = .init() - } - - return LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: entityInfo.title, - abstract: [.text(entityInfo.abstract.format())], - url: "/example" + reference.path, - kind: kind, - role: role, - fragments: entityInfo.declarationFragments?.declarationFragments.map { fragment in - return DeclarationRenderSection.Token(fragment: fragment, identifier: nil) - }, - isBeta: entityInfo.platforms?.allSatisfy({$0.isBeta == true}) ?? false, - images: entityInfo.topicImages?.map(\.0) ?? [] - ), - renderReferenceDependencies: dependencies, - sourceLanguages: [entityInfo.language], - symbolKind: DocumentationNode.symbolKind(for: entityInfo.kind) + LinkResolver.ExternalEntity( + kind: entityInfo.kind, + language: entityInfo.language, + relativePresentationURL: reference.url.withoutHostAndPortAndScheme(), + referenceURL: reference.url, + title: entityInfo.title, + availableLanguages: [entityInfo.language], + platforms: entityInfo.platforms, + topicImages: entityInfo.topicImages?.map(\.0), + references: entityInfo.topicImages?.map { topicImage, altText in + ImageReference(identifier: topicImage.identifier, altText: altText, imageAsset: assetsToReturn[topicImage.identifier.identifier] ?? .init()) + }, + variants: [] ) } } diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 8d1752b75b..a51ef8b102 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -13,12 +13,10 @@ import SymbolKit @testable import SwiftDocC import SwiftDocCTestUtilities -class ExternalLinkableTests: XCTestCase { +class LinkDestinationSummaryTests: XCTestCase { - // Write example documentation bundle with a minimal Tutorials page - let catalogHierarchy = Folder(name: "unit-test.docc", content: [ - Folder(name: "Symbols", content: []), - Folder(name: "Resources", content: [ + func testSummaryOfTutorialPage() async throws { + let catalogHierarchy = Folder(name: "unit-test.docc", content: [ TextFile(name: "TechnologyX.tutorial", utf8Content: """ @Tutorials(name: "TechnologyX") { @Intro(title: "Technology X") { @@ -89,11 +87,9 @@ class ExternalLinkableTests: XCTestCase { } } """), - ]), - InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), - ]) - - func testSummaryOfTutorialPage() async throws { + InfoPlist(displayName: "TestBundle", identifier: "com.test.example") + ]) + let (bundle, context) = try await loadBundle(catalog: catalogHierarchy) let converter = DocumentationNodeConverter(bundle: bundle, context: context) @@ -486,7 +482,6 @@ class ExternalLinkableTests: XCTestCase { XCTAssertEqual(variant.usr, nil) XCTAssertEqual(variant.kind, nil) XCTAssertEqual(variant.taskGroups, nil) - XCTAssertEqual(variant.topicImages, nil) let encoded = try JSONEncoder().encode(summary) let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded) @@ -577,7 +572,6 @@ class ExternalLinkableTests: XCTestCase { ) ] ) - XCTAssertEqual(variant.topicImages, nil) let encoded = try JSONEncoder().encode(summary) let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded) @@ -585,6 +579,63 @@ class ExternalLinkableTests: XCTestCase { } } + func testDecodingUnknownKindAndLanguage() throws { + let json = """ + { + "kind" : { + "id" : "kind-id", + "name" : "Kind name", + "isSymbol" : false + }, + "language" : { + "id" : "language-id", + "name" : "Language name", + "idAliases" : [ + "language-alias-id" + ], + "linkDisambiguationID" : "language-id" + }, + "availableLanguages" : [ + "swift", + "data", + { + "id" : "language-id", + "idAliases" : [ + "language-alias-id" + ], + "linkDisambiguationID" : "language-id", + "name" : "Language name" + }, + { + "id" : "language-id-2", + "linkDisambiguationID" : "language-id-2", + "name" : "Other language name" + }, + "occ" + ], + "title" : "Something", + "path" : "/documentation/something", + "referenceURL" : "/documentation/something" + } + """ + + let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: Data(json.utf8)) + try assertRoundTripCoding(decoded) + + XCTAssertEqual(decoded.kind, DocumentationNode.Kind(name: "Kind name", id: "kind-id", isSymbol: false)) + XCTAssertEqual(decoded.language, SourceLanguage(name: "Language name", id: "language-id", idAliases: ["language-alias-id"])) + XCTAssertEqual(decoded.availableLanguages, [ + // Known languages + .swift, + .objectiveC, + .data, + + // Custom languages + SourceLanguage(name: "Language name", id: "language-id", idAliases: ["language-alias-id"]), + SourceLanguage(name: "Other language name", id: "language-id-2"), + ]) + } + func testDecodingLegacyData() throws { let legacyData = """ { diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 355ae1c327..2a16c9db87 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -1178,18 +1178,13 @@ class SemaToRenderNodeTests: XCTestCase { let reference = ResolvedTopicReference(bundleID: "com.test.external.symbols", path: "/\(preciseIdentifier)", sourceLanguage: .objectiveC) let entity = LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: "SymbolName ( \(preciseIdentifier) )", - abstract: [], - url: "/documentation/FrameworkName/path/to/symbol/\(preciseIdentifier)", - kind: .symbol, - role: "ExternalResolvedSymbolRoleHeading", - estimatedTime: nil - ), - renderReferenceDependencies: .init(), - sourceLanguages: [.objectiveC], - symbolKind: .class + kind: .class, + language: .objectiveC, + relativePresentationURL: URL(string: "/documentation/FrameworkName/path/to/symbol/\(preciseIdentifier)")!, + referenceURL: reference.url, + title: "SymbolName ( \(preciseIdentifier) )", + availableLanguages: [.objectiveC], + variants: [] ) return (reference, entity) } @@ -1207,20 +1202,15 @@ class SemaToRenderNodeTests: XCTestCase { } func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - let (kind, role) = DocumentationContentRenderer.renderKindAndRole(.collection, semantic: nil) - return LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: "Title for \(reference.url.path)", - abstract: [.text("Abstract for \(reference.url.path)")], - url: reference.url.path, - kind: kind, - role: role, - estimatedTime: nil - ), - renderReferenceDependencies: .init(), - sourceLanguages: [.swift], - symbolKind: nil + LinkResolver.ExternalEntity( + kind: .collection, + language: .swift, + relativePresentationURL: reference.url.withoutHostAndPortAndScheme(), + referenceURL: reference.url, + title: "Title for \(reference.url.path)", + abstract: [.text("Abstract for \(reference.url.path)")], + availableLanguages: [.swift], + variants: [] ) } } diff --git a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift similarity index 90% rename from Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift rename to Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift index 84b59d722c..a4640f8b2f 100644 --- a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift @@ -12,10 +12,12 @@ import XCTest import Foundation import SymbolKit @_spi(ExternalLinks) @testable import SwiftDocC -@testable import SwiftDocCUtilities import SwiftDocCTestUtilities -class OutOfProcessReferenceResolverTests: XCTestCase { +// This tests the deprecated V1 implementation of `OutOfProcessReferenceResolver`. +// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. +@available(*, deprecated) +class OutOfProcessReferenceResolverV1Tests: XCTestCase { func testInitializationProcess() throws { #if os(macOS) @@ -51,7 +53,7 @@ class OutOfProcessReferenceResolverTests: XCTestCase { #endif } - func assertResolvesTopicLink(makeResolver: (OutOfProcessReferenceResolver.ResolvedInformation) throws -> OutOfProcessReferenceResolver) throws { + private func assertResolvesTopicLink(makeResolver: (OutOfProcessReferenceResolver.ResolvedInformation) throws -> OutOfProcessReferenceResolver) throws { let testMetadata = OutOfProcessReferenceResolver.ResolvedInformation( kind: .function, url: URL(string: "doc://com.test.bundle/something")!, @@ -100,33 +102,34 @@ class OutOfProcessReferenceResolverTests: XCTestCase { // Resolve the symbol let entity = resolver.entity(with: resolvedReference) + let topicRenderReference = entity.makeTopicRenderReference() - XCTAssertEqual(entity.topicRenderReference.url, testMetadata.url.withoutHostAndPortAndScheme().absoluteString) + XCTAssertEqual(topicRenderReference.url, testMetadata.url.withoutHostAndPortAndScheme().absoluteString) - XCTAssertEqual(entity.topicRenderReference.kind.rawValue, "symbol") - XCTAssertEqual(entity.topicRenderReference.role, "symbol") + XCTAssertEqual(topicRenderReference.kind.rawValue, "symbol") + XCTAssertEqual(topicRenderReference.role, "symbol") - XCTAssertEqual(entity.topicRenderReference.title, "Resolved Title") - XCTAssertEqual(entity.topicRenderReference.abstract, [.text("Resolved abstract for this topic.")]) + XCTAssertEqual(topicRenderReference.title, "Resolved Title") + XCTAssertEqual(topicRenderReference.abstract, [.text("Resolved abstract for this topic.")]) - XCTAssertFalse(entity.topicRenderReference.isBeta) + XCTAssertFalse(topicRenderReference.isBeta) - XCTAssertEqual(entity.sourceLanguages.count, 3) + XCTAssertEqual(entity.availableLanguages.count, 3) - let availableSourceLanguages = entity.sourceLanguages.sorted() + let availableSourceLanguages = entity.availableLanguages.sorted() let expectedLanguages = testMetadata.availableLanguages.sorted() XCTAssertEqual(availableSourceLanguages[0], expectedLanguages[0]) XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) - XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] - XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") - XCTAssertEqual(entity.topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) + XCTAssertEqual(topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) - let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) + let fragmentVariant = try XCTUnwrap(topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) if case .replace(let variantFragment) = fragmentVariant.patch.first { XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) @@ -134,7 +137,7 @@ class OutOfProcessReferenceResolverTests: XCTestCase { XCTFail("Unexpected fragments variant patch") } - XCTAssertEqual(entity.symbolKind, .func) + XCTAssertEqual(entity.kind, .function) } func testResolvingTopicLinkProcess() throws { @@ -273,30 +276,31 @@ class OutOfProcessReferenceResolverTests: XCTestCase { // Resolve the symbol let (_, entity) = try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "abc123"), "Unexpectedly failed to resolve symbol") + let topicRenderReference = entity.makeTopicRenderReference() - XCTAssertEqual(entity.topicRenderReference.url, testMetadata.url.absoluteString) + XCTAssertEqual(topicRenderReference.url, testMetadata.url.absoluteString) - XCTAssertEqual(entity.topicRenderReference.kind.rawValue, "symbol") - XCTAssertEqual(entity.topicRenderReference.role, "symbol") + XCTAssertEqual(topicRenderReference.kind.rawValue, "symbol") + XCTAssertEqual(topicRenderReference.role, "symbol") - XCTAssertEqual(entity.topicRenderReference.title, "Resolved Title") + XCTAssertEqual(topicRenderReference.title, "Resolved Title") - XCTAssertEqual(entity.sourceLanguages.count, 3) + XCTAssertEqual(entity.availableLanguages.count, 3) - let availableSourceLanguages = entity.sourceLanguages.sorted() + let availableSourceLanguages = entity.availableLanguages.sorted() let expectedLanguages = testMetadata.availableLanguages.sorted() XCTAssertEqual(availableSourceLanguages[0], expectedLanguages[0]) XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) - XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] - XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") - XCTAssertEqual(entity.topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) + XCTAssertEqual(topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) - let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) + let fragmentVariant = try XCTUnwrap(topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) if case .replace(let variantFragment) = fragmentVariant.patch.first { XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) @@ -304,19 +308,19 @@ class OutOfProcessReferenceResolverTests: XCTestCase { XCTFail("Unexpected fragments variant patch") } - XCTAssertNil(entity.topicRenderReference.conformance) - XCTAssertNil(entity.topicRenderReference.estimatedTime) - XCTAssertNil(entity.topicRenderReference.defaultImplementationCount) - XCTAssertFalse(entity.topicRenderReference.isBeta) - XCTAssertFalse(entity.topicRenderReference.isDeprecated) - XCTAssertNil(entity.topicRenderReference.propertyListKeyNames) - XCTAssertNil(entity.topicRenderReference.tags) - - XCTAssertEqual(entity.topicRenderReference.images.count, 1) - let topicImage = try XCTUnwrap(entity.topicRenderReference.images.first) + XCTAssertNil(topicRenderReference.conformance) + XCTAssertNil(topicRenderReference.estimatedTime) + XCTAssertNil(topicRenderReference.defaultImplementationCount) + XCTAssertFalse(topicRenderReference.isBeta) + XCTAssertFalse(topicRenderReference.isDeprecated) + XCTAssertNil(topicRenderReference.propertyListKeyNames) + XCTAssertNil(topicRenderReference.tags) + + XCTAssertEqual(topicRenderReference.images.count, 1) + let topicImage = try XCTUnwrap(topicRenderReference.images.first) XCTAssertEqual(topicImage.type, .card) - let image = try XCTUnwrap(entity.renderReferenceDependencies.imageReferences.first(where: { $0.identifier == topicImage.identifier })) + let image = try XCTUnwrap(entity.makeRenderDependencies().imageReferences.first(where: { $0.identifier == topicImage.identifier })) XCTAssertEqual(image.identifier, RenderReferenceIdentifier("external-card")) XCTAssertEqual(image.altText, "External card alt text") @@ -678,11 +682,10 @@ class OutOfProcessReferenceResolverTests: XCTestCase { let resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) XCTAssertEqual(resolver.bundleID, "com.test.bundle") - XCTAssertThrowsError(try resolver.resolveInformationForTopicURL(URL(string: "doc://com.test.bundle/something")!)) { - guard case OutOfProcessReferenceResolver.Error.executableSentBundleIdentifierAgain = $0 else { - XCTFail("Encountered an unexpected type of error.") - return - } + if case .failure(_, let errorInfo) = resolver.resolve(.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(parsingAuthoredLink: "doc://com.test.bundle/something")!))) { + XCTAssertEqual(errorInfo.message, "Executable sent bundle identifier message again, after it was already received.") + } else { + XCTFail("Unexpectedly resolved the link from an identifier and capabilities response") } #endif } @@ -734,13 +737,13 @@ class OutOfProcessReferenceResolverTests: XCTestCase { // Resolve the symbol let topicLinkEntity = resolver.entity(with: resolvedReference) - - XCTAssertEqual(topicLinkEntity.topicRenderReference.isBeta, isBeta, file: file, line: line) + + XCTAssertEqual(topicLinkEntity.makeTopicRenderReference().isBeta, isBeta, file: file, line: line) // Resolve the symbol let (_, symbolEntity) = try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "abc123"), "Unexpectedly failed to resolve symbol") - XCTAssertEqual(symbolEntity.topicRenderReference.isBeta, isBeta, file: file, line: line) + XCTAssertEqual(symbolEntity.makeTopicRenderReference().isBeta, isBeta, file: file, line: line) } diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift new file mode 100644 index 0000000000..dcd51bf89e --- /dev/null +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift @@ -0,0 +1,680 @@ +/* + 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 XCTest +import Foundation +import SymbolKit +@_spi(ExternalLinks) @testable import SwiftDocC +import SwiftDocCTestUtilities + +#if os(macOS) +class OutOfProcessReferenceResolverV2Tests: XCTestCase { + + func testInitializationProcess() throws { + let temporaryFolder = try createTemporaryDirectory() + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + // When the executable file doesn't exist + XCTAssertFalse(FileManager.default.fileExists(atPath: executableLocation.path)) + XCTAssertThrowsError(try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }), + "There should be a validation error if the executable file doesn't exist") + + // When the file isn't executable + try "".write(to: executableLocation, atomically: true, encoding: .utf8) + XCTAssertFalse(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + XCTAssertThrowsError(try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }), + "There should be a validation error if the file isn't executable") + + // When the file isn't executable + try """ + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + let resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { errorMessage in + XCTFail("No error output is expected for this test executable. Got:\n\(errorMessage)") + }) + XCTAssertEqual(resolver.bundleID, "com.test.bundle") + } + + private func makeTestSummary() -> (summary: LinkDestinationSummary, imageReference: RenderReferenceIdentifier, imageURLs: (light: URL, dark: URL)) { + let linkedReference = RenderReferenceIdentifier("doc://com.test.bundle/something-else") + let linkedImage = RenderReferenceIdentifier("some-image-identifier") + let linkedVariantReference = RenderReferenceIdentifier("doc://com.test.bundle/something-else-2") + + func cardImages(name: String) -> (light: URL, dark: URL) { + ( URL(string: "https://example.com/path/to/\(name)@2x.png")!, + URL(string: "https://example.com/path/to/\(name)~dark@2x.png")! ) + } + + let imageURLs = cardImages(name: "some-image") + + let summary = LinkDestinationSummary( + kind: .structure, + language: .swift, // This is Swift to account for what is considered a symbol's "first" variant value (rdar://86580516), + relativePresentationURL: URL(string: "/path/so/something")!, + referenceURL: URL(string: "doc://com.test.bundle/something")!, + title: "Resolved Title", + abstract: [ + .text("Resolved abstract with "), + .emphasis(inlineContent: [.text("formatted")]), + .text(" "), + .strong(inlineContent: [.text("formatted")]), + .text(" and a link: "), + .reference(identifier: linkedReference, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) + ], + availableLanguages: [ + .swift, + .init(name: "Language Name 2", id: "com.test.another-language.id"), + .objectiveC, + ], + platforms: [ + .init(name: "firstOS", introduced: "1.2.3", isBeta: false), + .init(name: "secondOS", introduced: "4.5.6", isBeta: false), + ], + usr: "some-unique-symbol-id", + declarationFragments: .init([ + .init(text: "struct", kind: .keyword, preciseIdentifier: nil), + .init(text: " ", kind: .text, preciseIdentifier: nil), + .init(text: "declaration fragment", kind: .identifier, preciseIdentifier: nil), + ]), + topicImages: [ + .init(pageImagePurpose: .card, identifier: linkedImage) + ], + references: [ + TopicRenderReference(identifier: linkedReference, title: "Something Else", abstract: [.text("Some other page")], url: "/path/to/something-else", kind: .symbol), + TopicRenderReference(identifier: linkedVariantReference, title: "Another Page", abstract: [.text("Yet another page")], url: "/path/to/something-else-2", kind: .article), + + ImageReference( + identifier: linkedImage, + altText: "External card alt text", + imageAsset: DataAsset( + variants: [ + DataTraitCollection(userInterfaceStyle: .light, displayScale: .double): imageURLs.light, + DataTraitCollection(userInterfaceStyle: .dark, displayScale: .double): imageURLs.dark, + ], + metadata: [ + imageURLs.light : DataAsset.Metadata(svgID: nil), + imageURLs.dark : DataAsset.Metadata(svgID: nil), + ], + context: .display + ) + ), + ], + variants: [ + .init( + traits: [.interfaceLanguage("com.test.another-language.id")], + kind: .init(name: "Variant Kind Name", id: "com.test.kind2.id", isSymbol: true), + language: .init(name: "Language Name 2", id: "com.test.another-language.id"), + title: "Resolved Variant Title", + abstract: [ + .text("Resolved variant abstract with "), + .emphasis(inlineContent: [.text("formatted")]), + .text(" "), + .strong(inlineContent: [.text("formatted")]), + .text(" and a link: "), + .reference(identifier: linkedVariantReference, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) + ], + declarationFragments: .init([ + .init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil) + ]) + ) + ] + ) + + return (summary, linkedImage, imageURLs) + } + + func testResolvingLinkAndSymbol() throws { + enum RequestKind { + case link, symbol + + func perform(resolver: OutOfProcessReferenceResolver, file: StaticString = #filePath, line: UInt = #line) throws -> LinkResolver.ExternalEntity? { + switch self { + case .link: + let unresolved = TopicReference.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(parsingExact: "doc://com.test.bundle/something")!)) + let reference: ResolvedTopicReference + switch resolver.resolve(unresolved) { + case .success(let resolved): + reference = resolved + case .failure(_, let errorInfo): + XCTFail("Unexpectedly failed to resolve reference with error: \(errorInfo.message)", file: file, line: line) + return nil + } + + // Resolve the symbol + return resolver.entity(with: reference) + + case .symbol: + return try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "")?.1, file: file, line: line) + } + } + } + + for requestKind in [RequestKind.link, .symbol] { + let (testSummary, linkedImage, imageURLs) = makeTestSummary() + + let resolver: OutOfProcessReferenceResolver + do { + let temporaryFolder = try createTemporaryDirectory() + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + + let encodedLinkSummary = try String(data: JSONEncoder().encode(testSummary), encoding: .utf8)! + + try """ + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '{"resolved":\(encodedLinkSummary)}' # Respond with the test link summary (above) + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + XCTAssertEqual(resolver.bundleID, "com.test.bundle") + } + + let entity = try XCTUnwrap(requestKind.perform(resolver: resolver)) + let topicRenderReference = entity.makeTopicRenderReference() + + XCTAssertEqual(topicRenderReference.url, testSummary.relativePresentationURL.absoluteString) + + XCTAssertEqual(topicRenderReference.kind.rawValue, "symbol") + XCTAssertEqual(topicRenderReference.role, "symbol") + + XCTAssertEqual(topicRenderReference.title, "Resolved Title") + XCTAssertEqual(topicRenderReference.abstract, [ + .text("Resolved abstract with "), + .emphasis(inlineContent: [.text("formatted")]), + .text(" "), + .strong(inlineContent: [.text("formatted")]), + .text(" and a link: "), + .reference(identifier: .init("doc://com.test.bundle/something-else"), isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) + ]) + + XCTAssertFalse(topicRenderReference.isBeta) + + XCTAssertEqual(entity.availableLanguages.count, 3) + + let availableSourceLanguages = entity.availableLanguages.sorted() + let expectedLanguages = testSummary.availableLanguages.sorted() + + XCTAssertEqual(availableSourceLanguages[0], expectedLanguages[0]) + XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) + XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) + + XCTAssertEqual(topicRenderReference.fragments, [ + .init(text: "struct", kind: .keyword, preciseIdentifier: nil), + .init(text: " ", kind: .text, preciseIdentifier: nil), + .init(text: "declaration fragment", kind: .identifier, preciseIdentifier: nil), + ]) + + let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] + XCTAssertEqual(topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(topicRenderReference.abstractVariants.value(for: variantTraits), [ + .text("Resolved variant abstract with "), + .emphasis(inlineContent: [.text("formatted")]), + .text(" "), + .strong(inlineContent: [.text("formatted")]), + .text(" and a link: "), + .reference(identifier: .init("doc://com.test.bundle/something-else-2"), isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) + ]) + + let fragmentVariant = try XCTUnwrap(topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) + XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) + if case .replace(let variantFragment) = fragmentVariant.patch.first { + XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) + } else { + XCTFail("Unexpected fragments variant patch") + } + + XCTAssertNil(topicRenderReference.conformance) + XCTAssertNil(topicRenderReference.estimatedTime) + XCTAssertNil(topicRenderReference.defaultImplementationCount) + XCTAssertFalse(topicRenderReference.isBeta) + XCTAssertFalse(topicRenderReference.isDeprecated) + XCTAssertNil(topicRenderReference.propertyListKeyNames) + XCTAssertNil(topicRenderReference.tags) + + XCTAssertEqual(topicRenderReference.images.count, 1) + let topicImage = try XCTUnwrap(topicRenderReference.images.first) + XCTAssertEqual(topicImage.type, .card) + + let image = try XCTUnwrap(entity.makeRenderDependencies().imageReferences.first(where: { $0.identifier == topicImage.identifier })) + + XCTAssertEqual(image.identifier, linkedImage) + XCTAssertEqual(image.altText, "External card alt text") + + XCTAssertEqual(image.asset, DataAsset( + variants: [ + DataTraitCollection(userInterfaceStyle: .light, displayScale: .double): imageURLs.light, + DataTraitCollection(userInterfaceStyle: .dark, displayScale: .double): imageURLs.dark, + ], + metadata: [ + imageURLs.light: DataAsset.Metadata(svgID: nil), + imageURLs.dark: DataAsset.Metadata(svgID: nil), + ], + context: .display + )) + } + } + + func testForwardsErrorOutputProcess() throws { + let temporaryFolder = try createTemporaryDirectory() + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + try """ + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + echo "Some error output" 1>&2 # Write to stderr + read # Wait for docc to send a request + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + let didReadErrorOutputExpectation = expectation(description: "Did read forwarded error output.") + + let resolver = try? OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { + errorMessage in + XCTAssertEqual(errorMessage, "Some error output\n") + didReadErrorOutputExpectation.fulfill() + }) + XCTAssertEqual(resolver?.bundleID, "com.test.bundle") + + wait(for: [didReadErrorOutputExpectation], timeout: 20.0) + } + + func testLinksAndImagesInExternalAbstractAreIncludedInTheRenderedPageReferenecs() async throws { + let externalBundleID: DocumentationBundle.Identifier = "com.example.test" + + let imageRef = RenderReferenceIdentifier("some-external-card-image-identifier") + let linkRef = RenderReferenceIdentifier("doc://\(externalBundleID)/path/to/other-page") + + let imageURL = URL(string: "https://example.com/path/to/some-image.png")! + + let originalLinkedImage = ImageReference( + identifier: imageRef, + imageAsset: DataAsset( + variants: [.init(displayScale: .standard): imageURL], + metadata: [imageURL: .init()], + context: .display + ) + ) + + let originalLinkedTopic = TopicRenderReference( + identifier: linkRef, + title: "Resolved title of link inside abstract", + abstract: [ + .text("This transient content is not displayed anywhere"), + ], + url: "/path/to/other-page", + kind: .article + ) + + let externalSummary = LinkDestinationSummary( + kind: .article, + language: .swift, + relativePresentationURL: URL(string: "/path/to/something")!, + referenceURL: URL(string: "doc://\(externalBundleID)/path/to/something")!, + title: "Resolved title", + abstract: [ + .text("External abstract with an image "), + .image(identifier: imageRef, metadata: nil), + .text(" and link "), + .reference(identifier: linkRef, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil), + .text("."), + ], + availableLanguages: [.swift], + platforms: nil, + taskGroups: nil, + usr: nil, + declarationFragments: nil, + redirects: nil, + topicImages: nil, + references: [originalLinkedImage, originalLinkedTopic], + variants: [] + ) + + let resolver: OutOfProcessReferenceResolver + do { + let temporaryFolder = try createTemporaryDirectory() + let encodedResponse = try String(decoding: JSONEncoder().encode(OutOfProcessReferenceResolver.ResponseV2.resolved(externalSummary)), as: UTF8.self) + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + try """ + #!/bin/bash + echo '{"identifier":"\(externalBundleID)","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '\(encodedResponse)' # Respond with the resolved link summary + read # Wait for docc to send another request + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + } + + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Something.md", utf8Content: """ + # My root page + + This page curates an an external page (so that its abstract and transient references are displayed on the page) + + ## Topics + + ### An external link + + - + """) + ]) + let inputDirectory = Folder(name: "path", content: [Folder(name: "to", content: [catalog])]) + + var configuration = DocumentationContext.Configuration() + configuration.externalDocumentationConfiguration.sources = [ + externalBundleID: resolver + ] + let (_, context) = try await loadBundle(catalog: inputDirectory, configuration: configuration) + XCTAssertEqual(context.problems.map(\.diagnostic.summary), [], "Encountered unexpected problems") + + let reference = try XCTUnwrap(context.soleRootModuleReference, "This example catalog only has a root page") + + let converter = DocumentationContextConverter( + bundle: context.bundle, + context: context, + renderContext: RenderContext( + documentationContext: context, + bundle: context.bundle + ) + ) + let renderNode = try XCTUnwrap(converter.renderNode(for: context.entity(with: reference))) + + // Verify that the topic section exist and has the external link + XCTAssertEqual(renderNode.topicSections.flatMap { [$0.title ?? ""] + $0.identifiers }, [ + "An external link", + "doc://\(externalBundleID)/path/to/something", // Resolved links use their canonical references + ]) + + // Verify that the externally resolved page's references are included on the page + XCTAssertEqual(Set(renderNode.references.keys), [ + "doc://com.example.test/path/to/something", // The external page that the root links to + + "some-external-card-image-identifier", // The image in that page's abstract + "doc://com.example.test/path/to/other-page", // The link in that page's abstract + ], "The external page and its two references should be included on this page") + + XCTAssertEqual(renderNode.references[imageRef.identifier] as? ImageReference, originalLinkedImage) + XCTAssertEqual(renderNode.references[linkRef.identifier] as? TopicRenderReference, originalLinkedTopic) + } + + func testExternalLinkFailureResultInDiagnosticWithSolutions() async throws { + let externalBundleID: DocumentationBundle.Identifier = "com.example.test" + + let resolver: OutOfProcessReferenceResolver + do { + let temporaryFolder = try createTemporaryDirectory() + + let diagnosticInfo = OutOfProcessReferenceResolver.ResponseV2.DiagnosticInformation( + summary: "Some external link issue summary", + solutions: [ + .init(summary: "Some external solution", replacement: "some-replacement") + ] + ) + let encodedDiagnostic = try String(decoding: JSONEncoder().encode(diagnosticInfo), as: UTF8.self) + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + try """ + #!/bin/bash + echo '{"identifier":"\(externalBundleID)","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '{"failure":\(encodedDiagnostic)}' # Respond with an error message + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + } + + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Something.md", utf8Content: """ + # My root page + + This page contains an external link that will fail to resolve: + """) + ]) + let inputDirectory = Folder(name: "path", content: [Folder(name: "to", content: [catalog])]) + + var configuration = DocumentationContext.Configuration() + configuration.externalDocumentationConfiguration.sources = [ + externalBundleID: resolver + ] + let (_, context) = try await loadBundle(catalog: inputDirectory, configuration: configuration) + + XCTAssertEqual(context.problems.map(\.diagnostic.summary), [ + "Some external link issue summary", + ]) + + let problem = try XCTUnwrap(context.problems.sorted(by: \.diagnostic.identifier).first) + + XCTAssertEqual(problem.diagnostic.summary, "Some external link issue summary") + XCTAssertEqual(problem.diagnostic.range?.lowerBound, .init(line: 3, column: 69, source: URL(fileURLWithPath: "/path/to/unit-test.docc/Something.md"))) + XCTAssertEqual(problem.diagnostic.range?.upperBound, .init(line: 3, column: 97, source: URL(fileURLWithPath: "/path/to/unit-test.docc/Something.md"))) + + XCTAssertEqual(problem.possibleSolutions.count, 1) + let solution = try XCTUnwrap(problem.possibleSolutions.first) + XCTAssertEqual(solution.summary, "Some external solution") + XCTAssertEqual(solution.replacements.count, 1) + XCTAssertEqual(solution.replacements.first?.range.lowerBound, .init(line: 3, column: 65, source: nil)) + XCTAssertEqual(solution.replacements.first?.range.upperBound, .init(line: 3, column: 97, source: nil)) + + // Verify the warning presentation + let diagnosticOutput = LogHandle.LogStorage() + let fileSystem = try TestFileSystem(folders: [inputDirectory]) + let diagnosticFormatter = DiagnosticConsoleWriter(LogHandle.memory(diagnosticOutput), formattingOptions: [], highlight: true, dataProvider: fileSystem) + diagnosticFormatter.receive(context.diagnosticEngine.problems) + try diagnosticFormatter.flush() + + let warning = "\u{001B}[1;33m" + let highlight = "\u{001B}[1;32m" + let suggestion = "\u{001B}[1;39m" + let clear = "\u{001B}[0;0m" + XCTAssertEqual(diagnosticOutput.text, """ + \(warning)warning: Some external link issue summary\(clear) + --> /path/to/unit-test.docc/Something.md:3:69-3:97 + 1 | # My root page + 2 | + 3 + This page contains an external link that will fail to resolve: + | ╰─\(suggestion)suggestion: Some external solution\(clear) + + """) + + // Verify the suggestion replacement + let source = try XCTUnwrap(problem.diagnostic.source) + let original = String(decoding: try fileSystem.contents(of: source), as: UTF8.self) + + XCTAssertEqual(try solution.applyTo(original), """ + # My root page + + This page contains an external link that will fail to resolve: + """) + } + + func testEncodingAndDecodingRequests() throws { + do { + let request = OutOfProcessReferenceResolver.RequestV2.link("doc://com.example/path/to/something") + + let data = try JSONEncoder().encode(request) + if case .link(let link) = try JSONDecoder().decode(OutOfProcessReferenceResolver.RequestV2.self, from: data) { + XCTAssertEqual(link, "doc://com.example/path/to/something") + } else { + XCTFail("Decoded the wrong type of request") + } + } + + do { + let request = OutOfProcessReferenceResolver.RequestV2.symbol("some-unique-symbol-id") + + let data = try JSONEncoder().encode(request) + if case .symbol(let usr) = try JSONDecoder().decode(OutOfProcessReferenceResolver.RequestV2.self, from: data) { + XCTAssertEqual(usr, "some-unique-symbol-id") + } else { + XCTFail("Decoded the wrong type of request") + } + } + } + + func testEncodingAndDecodingResponses() throws { + // Identifier and capabilities + do { + let request = OutOfProcessReferenceResolver.ResponseV2.identifierAndCapabilities("com.example.test", []) + + let data = try JSONEncoder().encode(request) + if case .identifierAndCapabilities(let identifier, let capabilities) = try JSONDecoder().decode(OutOfProcessReferenceResolver.ResponseV2.self, from: data) { + XCTAssertEqual(identifier.rawValue, "com.example.test") + XCTAssertEqual(capabilities.rawValue, 0) + } else { + XCTFail("Decoded the wrong type of message") + } + } + + // Failures + do { + let originalInfo = OutOfProcessReferenceResolver.ResponseV2.DiagnosticInformation( + summary: "Some summary", + solutions: [ + .init(summary: "Some solution", replacement: "some-replacement") + ] + ) + + let request = OutOfProcessReferenceResolver.ResponseV2.failure(originalInfo) + let data = try JSONEncoder().encode(request) + if case .failure(let info) = try JSONDecoder().decode(OutOfProcessReferenceResolver.ResponseV2.self, from: data) { + XCTAssertEqual(info.summary, originalInfo.summary) + XCTAssertEqual(info.solutions?.count, originalInfo.solutions?.count) + for (solution, originalSolution) in zip(info.solutions ?? [], originalInfo.solutions ?? []) { + XCTAssertEqual(solution.summary, originalSolution.summary) + XCTAssertEqual(solution.replacement, originalSolution.replacement) + } + } else { + XCTFail("Decoded the wrong type of message") + } + } + + // Resolved link information + do { + let originalSummary = makeTestSummary().summary + let message = OutOfProcessReferenceResolver.ResponseV2.resolved(originalSummary) + + let data = try JSONEncoder().encode(message) + if case .resolved(let summary) = try JSONDecoder().decode(OutOfProcessReferenceResolver.ResponseV2.self, from: data) { + XCTAssertEqual(summary, originalSummary) + } else { + XCTFail("Decoded the wrong type of message") + return + } + } + } + + func testErrorWhenReceivingBundleIdentifierTwice() throws { + let temporaryFolder = try createTemporaryDirectory() + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + try """ + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this identifier & capabilities again + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + let resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + XCTAssertEqual(resolver.bundleID, "com.test.bundle") + + if case .failure(_, let errorInfo) = resolver.resolve(.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(parsingAuthoredLink: "doc://com.test.bundle/something")!))) { + XCTAssertEqual(errorInfo.message, "Executable sent bundle identifier message again, after it was already received.") + } else { + XCTFail("Unexpectedly resolved the link from an identifier and capabilities response") + } + } + + func testResolvingSymbolBetaStatusProcess() throws { + func betaStatus(forSymbolWithPlatforms platforms: [LinkDestinationSummary.PlatformAvailability], file: StaticString = #filePath, line: UInt = #line) throws -> Bool { + let summary = LinkDestinationSummary( + kind: .class, + language: .swift, + relativePresentationURL: URL(string: "/documentation/ModuleName/Something")!, + referenceURL: URL(string: "/documentation/ModuleName/Something")!, + title: "Something", + availableLanguages: [.swift, .objectiveC], + platforms: platforms, + variants: [] + ) + + let resolver: OutOfProcessReferenceResolver + do { + let temporaryFolder = try createTemporaryDirectory() + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + + let encodedLinkSummary = try String(data: JSONEncoder().encode(summary), encoding: .utf8)! + + try """ + #!/bin/bash + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '{"resolved":\(encodedLinkSummary)}' # Respond with the test link summary (above) + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + XCTAssertEqual(resolver.bundleID, "com.test.bundle", file: file, line: line) + } + + let (_, symbolEntity) = try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "abc123"), "Unexpectedly failed to resolve symbol") + return symbolEntity.makeTopicRenderReference().isBeta + } + + // All platforms are in beta + XCTAssertEqual(true, try betaStatus(forSymbolWithPlatforms: [ + .init(name: "fooOS", introduced: "1.2.3", isBeta: true), + .init(name: "barOS", introduced: "1.2.3", isBeta: true), + .init(name: "bazOS", introduced: "1.2.3", isBeta: true), + ])) + + // One platform is stable, the other two are in beta + XCTAssertEqual(false, try betaStatus(forSymbolWithPlatforms: [ + .init(name: "fooOS", introduced: "1.2.3", isBeta: false), + .init(name: "barOS", introduced: "1.2.3", isBeta: true), + .init(name: "bazOS", introduced: "1.2.3", isBeta: true), + ])) + + // No platforms explicitly supported + XCTAssertEqual(false, try betaStatus(forSymbolWithPlatforms: [])) + } +} +#endif diff --git a/bin/test-data-external-resolver b/bin/test-data-external-resolver index 23d9f9d13d..d38e2db513 100755 --- a/bin/test-data-external-resolver +++ b/bin/test-data-external-resolver @@ -2,7 +2,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 @@ -18,108 +18,177 @@ # absolute documentation links with the "com.test.bundle" identifier. For example: RESPONSE='{ - "resolvedInformation" : { - "abstract" : "Resolved abstract.", - "availableLanguages" : [ + "resolved" : { + "abstract" : [ { - "id" : "swift", - "name" : "Language Name", - "idAliases" : [], - "linkDisambiguationID": "swift" + "text" : "Resolved ", + "type" : "text" }, { - "id" : "occ", - "name" : "Variant Language Name", - "idAliases" : [ - "objective-c", - "c" + "inlineContent" : [ + { + "text" : "formatted", + "type" : "text" + } ], - "linkDisambiguationID" : "c" + "type" : "strong" + }, + { + "text" : " abstract with ", + "type" : "text" + }, + { + "identifier" : "doc://com.test.bundle/path/to/other-page", + "isActive" : true, + "type" : "reference" + }, + { + "text" : ".", + "type" : "text" } ], - "declarationFragments" : [ + "availableLanguages" : [ + "swift", + "data", + { + "id" : "language-id", + "idAliases" : [ + "language-alias-id" + ], + "linkDisambiguationID" : "language-id", + "name" : "Language name" + }, + { + "id" : "language-id-2", + "linkDisambiguationID" : "language-id-2", + "name" : "Other language name" + }, + "occ" + ], + "fragments" : [ + { + "kind" : "keyword", + "text" : "resolved" + }, { "kind" : "text", - "spelling" : "declaration fragment" + "text" : " " + }, + { + "kind" : "identifier", + "text" : "fragment" } ], "kind" : { - "id" : "com.test.kind.id", + "id" : "kind-id", "isSymbol" : true, - "name" : "Kind Name" + "name" : "Kind name" }, "language" : { - "id" : "swift", - "name" : "Language Name", - "idAliases" : [], - "linkDisambiguationID": "swift" - + "id" : "language-id", + "idAliases" : [ + "language-alias-id" + ], + "linkDisambiguationID" : "language-id", + "name" : "Language name" }, + "path" : "/documentation/something", "platforms" : [ { "beta" : false, - "introducedAt" : "1.0.0", - "name" : "Platform Name" + "introducedAt" : "1.2.3", + "name" : "Platform name" } ], - "topicImages": [ + "referenceURL" : "doc://com.test.bundle/documentation/something", + "references" : [ { - "type": "card", - "identifier": "some-external-card-image-identifier" - } - ], - "references": [ + "abstract" : [ + { + "text" : "The abstract of another page that is linked to", + "type" : "text" + } + ], + "identifier" : "doc://com.test.bundle/path/to/other-page", + "kind" : "article", + "title" : "Linked from abstract", + "type" : "topic", + "url" : "/path/to/other-page" + }, { - "type": "image", - "identifier": "some-external-card-image-identifier", - "variants": [ + "alt" : "Resolved image alt text", + "identifier" : "some-external-card-image-identifier", + "type" : "image", + "variants" : [ { - "url": "http:\/\/example.com\/some-image-1x.jpg", - "traits": [ + "traits" : [ "1x" - ] - }, - { - "url": "http:\/\/example.com\/some-image-1x-dark.jpg", - "traits": [ - "1x", "dark" - ] + ], + "url" : "http://example.com/some-image.jpg" }, { - "url": "http:\/\/example.com\/some-image-2x.jpg", - "traits": [ - "2x" - ] + "traits" : [ + "2x", + "dark" + ], + "url" : "http://example.com/some-image@2x~dark.jpg" } ] } ], - "title" : "Resolved Title", - "url" : "doc:\/\/com.test.bundle\/resolved/path\/", + "title" : "Resolved title", + "topicImages" : [ + { + "identifier" : "some-external-card-image-identifier", + "type" : "card" + } + ], + "usr" : "resolved-unique-symbol-id", "variants" : [ { - "abstract" : "Resolved variant abstract for this topic.", - "declarationFragments" : [ + "abstract" : [ + { + "text" : "Resolved abstract", + "type" : "text" + }, + { + "code" : "variant", + "type" : "codeVoice" + }, + { + "text" : "Resolved abstract", + "type" : "text" + } + ], + "fragments" : [ + { + "kind" : "keyword", + "text" : "resolved" + }, + { + "kind" : "text", + "text" : " " + }, + { + "kind" : "identifier", + "text" : "variant" + }, { "kind" : "text", - "spelling" : "variant declaration fragment" + "text" : ": " + }, + { + "kind" : "typeIdentifier", + "text" : "fragment" } ], "kind" : { - "id" : "com.test.other-kind.id", + "id" : "variant-kind-id", "isSymbol" : true, - "name" : "Variant Kind Name" - }, - "language" : { - "id" : "occ", - "name" : "Variant Language Name", - "idAliases" : [ - "objective-c", - "c" - ], - "linkDisambiguationID" : "c" + "name" : "Variant kind name" }, - "title" : "Resolved Variant Title", + "language" : "occ", + "title" : "Resolved variant title", "traits" : [ { "interfaceLanguage" : "occ" @@ -130,8 +199,11 @@ RESPONSE='{ } }' -# Write this resolver's bundle identifier -echo '{"bundleIdentifier":"com.test.bundle"}' +# Write this resolver's identifier and capabilities +echo '{ + "identifier": "com.test.bundle", + "capabilities": 0 +}' # Forever, wait for DocC to send a request and respond the resolved information while true From 257c62d1ef11efc023667fda89c0cd4419a8afa3 Mon Sep 17 00:00:00 2001 From: Kavon Farvardin Date: Wed, 24 Sep 2025 23:06:20 -0700 Subject: [PATCH 37/90] Revert "Add a new version of the communication protocol between DocC and external link resolver executables (#1292)" (#1299) This reverts commit 9413c599817abc9dd4f8feeed57a998b19a05a6f. --- .../AbstractContainsFormattedTextOnly.swift | 3 +- .../Infrastructure/DocumentationContext.swift | 9 +- ...ocessReferenceResolver+Communication.swift | 198 ---- ...enceResolver+DeprecatedCommunication.swift | 346 ------- .../OutOfProcessReferenceResolver.swift | 942 ++++++++++-------- .../ExternalPathHierarchyResolver.swift | 31 +- .../LinkResolver+NavigatorIndex.swift | 46 +- .../Link Resolution/LinkResolver.swift | 88 +- .../LinkTargets/LinkDestinationSummary.swift | 168 +--- .../DocumentationContentRenderer.swift | 10 +- .../Model/Rendering/RenderContext.swift | 21 +- .../Rendering/RenderNodeTranslator.swift | 2 +- .../SwiftDocC/StaticAnalysis.md | 3 +- .../Benchmark/ExternalTopicsHashTests.swift | 18 +- ...stractContainsFormattedTextOnlyTests.swift | 5 +- .../ConvertService/ConvertServiceTests.swift | 33 +- .../DocumentationServer+DefaultTests.swift | 5 +- .../Indexing/ExternalRenderNodeTests.swift | 269 +++-- .../ExternalPathHierarchyResolverTests.swift | 2 +- .../ExternalReferenceResolverTests.swift | 47 +- .../TestExternalReferenceResolvers.swift | 43 +- .../LinkDestinationSummaryTests.swift | 75 +- .../Model/SemaToRenderNodeTests.swift | 42 +- ...OutOfProcessReferenceResolverV2Tests.swift | 680 ------------- .../OutOfProcessReferenceResolverTests.swift} | 93 +- bin/test-data-external-resolver | 204 ++-- 26 files changed, 1022 insertions(+), 2361 deletions(-) delete mode 100644 Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift delete mode 100644 Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift delete mode 100644 Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift rename Tests/{SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift => SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift} (90%) diff --git a/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift b/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift index a2ed042d6c..05f30ea575 100644 --- a/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift +++ b/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -14,7 +14,6 @@ public import Markdown /** A document's abstract may only contain formatted text. Images and links are not allowed. */ -@available(*, deprecated, message: "This check is no longer applicable. This deprecated API will be removed after 6.3 is released") public struct AbstractContainsFormattedTextOnly: Checker { public var problems: [Problem] = [Problem]() private var sourceFile: URL? diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 654185a776..cd7fca83d7 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -265,6 +265,7 @@ public class DocumentationContext { /// - source: The location of the document. private func check(_ document: Document, at source: URL) { var checker = CompositeChecker([ + AbstractContainsFormattedTextOnly(sourceFile: source).any(), DuplicateTopicsSections(sourceFile: source).any(), InvalidAdditionalTitle(sourceFile: source).any(), MissingAbstract(sourceFile: source).any(), @@ -2738,7 +2739,7 @@ public class DocumentationContext { knownEntityValue( reference: reference, valueInLocalEntity: \.availableSourceLanguages, - valueInExternalEntity: \.availableLanguages + valueInExternalEntity: \.sourceLanguages ) } @@ -2746,9 +2747,9 @@ public class DocumentationContext { func isSymbol(reference: ResolvedTopicReference) -> Bool { knownEntityValue( reference: reference, - valueInLocalEntity: \.kind, - valueInExternalEntity: \.kind - ).isSymbol + valueInLocalEntity: { node in node.kind.isSymbol }, + valueInExternalEntity: { entity in entity.topicRenderReference.kind == .symbol } + ) } // MARK: - Relationship queries diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift deleted file mode 100644 index b60dce179a..0000000000 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift +++ /dev/null @@ -1,198 +0,0 @@ -/* - 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 -*/ - -extension OutOfProcessReferenceResolver { - // MARK: Capabilities - - /// A set of optional capabilities that either DocC or your external link resolver declares that it supports. - /// - /// ## Supported messages - /// - /// If your external link resolver declares none of the optional capabilities, then DocC will only send it the following messages: - /// - ``RequestV2/link(_:)`` - /// - ``RequestV2/symbol(_:)`` - public struct Capabilities: OptionSet, Codable { - public let rawValue: Int - public init(rawValue: Int) { - self.rawValue = rawValue - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(rawValue) - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - rawValue = try container.decode(Int.self) - } - } - - // MARK: Request & Response - - /// Request messages that DocC sends to the external link resolver. - /// - /// ## Topics - /// ### Base requests - /// - /// Your external link resolver always needs to handle the following requests regardless of its declared capabilities: - /// - /// - ``link(_:)`` - /// - ``symbol(_:)`` - public enum RequestV2: Codable { - /// A request to resolve a link - /// - /// Your external resolver - case link(String) - /// A request to resolve a symbol based on its precise identifier. - case symbol(String) - - // This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled, - // which is not available to Swift Packages without unsafe flags (rdar://78773361). - // This can be removed once that is available and applied to Swift-DocC (rdar://89033233). - @available(*, deprecated, message: """ - This enum is non-frozen and may be expanded in the future; add a `default` case, and do nothing in it, instead of matching this one. - Your external link resolver won't be passed new messages that it hasn't declared the corresponding capability for. - """) - case _nonFrozenEnum_useDefaultCase - - private enum CodingKeys: CodingKey { - case link, symbol // Default requests keys - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .link(let link): try container.encode(link, forKey: .link) - case .symbol(let id): try container.encode(id, forKey: .symbol) - - case ._nonFrozenEnum_useDefaultCase: - fatalError("Never use '_nonFrozenEnum_useDefaultCase' as a real case.") - } - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self = switch container.allKeys.first { - case .link?: .link( try container.decode(String.self, forKey: .link)) - case .symbol?: .symbol(try container.decode(String.self, forKey: .symbol)) - case nil: throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest - } - } - } - - /// A response message from the external link resolver. - /// - /// If your external resolver sends a response that's associated with a capability that DocC hasn't declared support for, then DocC will fail to handle the response. - public enum ResponseV2: Codable { - /// The initial identifier and capabilities message. - /// - /// Your external link resolver should send this message, exactly once, after it has launched to signal that its ready to receive requests. - /// - /// The capabilities that your external link resolver declares in this message determines which optional request messages that DocC will send. - /// If your resolver doesn't declare _any_ capabilities it only needs to handle the 3 default requests. See . - case identifierAndCapabilities(DocumentationBundle.Identifier, Capabilities) - /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. - case failure(DiagnosticInformation) - /// A response with the resolved information about the requested topic or symbol. - case resolved(LinkDestinationSummary) - - // This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled, - // which is not available to Swift Packages without unsafe flags (rdar://78773361). - // This can be removed once that is available and applied to Swift-DocC (rdar://89033233). - @available(*, deprecated, message: """ - This enum is non-frozen and may be expanded in the future; add a `default` case, and do nothing in it, instead of matching this one. - Your external link resolver won't be passed new messages that it hasn't declared the corresponding capability for. - """) - case _nonFrozenEnum_useDefaultCase - - private enum CodingKeys: String, CodingKey { - // Default response keys - case identifier, capabilities - case failure - case resolved - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self = switch container.allKeys.first { - case .identifier?, .capabilities?: - .identifierAndCapabilities( - try container.decode(DocumentationBundle.Identifier.self, forKey: .identifier), - try container.decode(Capabilities.self, forKey: .capabilities) - ) - case .failure?: - .failure(try container.decode(DiagnosticInformation.self, forKey: .failure)) - case .resolved?: - .resolved(try container.decode(LinkDestinationSummary.self, forKey: .resolved)) - case nil: - throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient - } - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .identifierAndCapabilities(let identifier, let capabilities): - try container.encode(identifier, forKey: .identifier) - try container.encode(capabilities, forKey: .capabilities) - - case .failure(errorMessage: let diagnosticInfo): - try container.encode(diagnosticInfo, forKey: .failure) - - case .resolved(let summary): - try container.encode(summary, forKey: .resolved) - - case ._nonFrozenEnum_useDefaultCase: - fatalError("Never use '_nonFrozenEnum_useDefaultCase' for anything.") - } - } - } -} - -extension OutOfProcessReferenceResolver.ResponseV2 { - /// Information about why the external resolver failed to resolve the `link(_:)`, or `symbol(_:)` request. - public struct DiagnosticInformation: Codable { - /// A brief user-facing summary of the issue that caused the external resolver to fail. - public var summary: String - - /// A list of possible suggested solutions that can address the failure. - public var solutions: [Solution]? - - /// Creates a new value with information about why the external resolver failed to resolve the `link(_:)`, or `symbol(_:)` request. - /// - Parameters: - /// - summary: A brief user-facing summary of the issue that caused the external resolver to fail. - /// - solutions: Possible possible suggested solutions that can address the failure. - public init( - summary: String, - solutions: [Solution]? - ) { - self.summary = summary - self.solutions = solutions - } - - /// A possible solution to an external resolver issue. - public struct Solution: Codable { - /// A brief user-facing description of what the solution is. - public var summary: String - /// A full replacement of the link. - public var replacement: String? - - /// Creates a new solution to an external resolver issue - /// - Parameters: - /// - summary: A brief user-facing description of what the solution is. - /// - replacement: A full replacement of the link. - public init(summary: String, replacement: String?) { - self.summary = summary - self.replacement = replacement - } - } - } -} diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift deleted file mode 100644 index e95a3a01ff..0000000000 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift +++ /dev/null @@ -1,346 +0,0 @@ -/* - 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 -*/ - -public import Foundation -public import SymbolKit - -extension OutOfProcessReferenceResolver { - - // MARK: Request & Response - - /// An outdated version of a request message to send to the external link resolver. - /// - /// This can either be a request to resolve a topic URL or to resolve a symbol based on its precise identifier. - /// - /// @DeprecationSummary { - /// This version of the communication protocol is no longer recommended. Update to ``RequestV2`` and ``ResponseV2`` instead. - /// - /// The new version of the communication protocol both has a mechanism for expanding functionality in the future (through common ``Capabilities`` between DocC and the external resolver) and supports richer responses for both successful and and failed requests. - /// } - @available(*, deprecated, message: "This version of the communication protocol is no longer recommended. Update to `RequestV2` and `ResponseV2` instead.") - public typealias Request = _DeprecatedRequestV1 - - // Note this type isn't formally deprecated to avoid warnings in the ConvertService, which still _implicitly_ require this version of requests and responses. - public enum _DeprecatedRequestV1: Codable, CustomStringConvertible { - /// A request to resolve a topic URL - case topic(URL) - /// A request to resolve a symbol based on its precise identifier. - case symbol(String) - /// A request to resolve an asset. - case asset(AssetReference) - - private enum CodingKeys: CodingKey { - case topic - case symbol - case asset - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .topic(let url): - try container.encode(url, forKey: .topic) - case .symbol(let identifier): - try container.encode(identifier, forKey: .symbol) - case .asset(let assetReference): - try container.encode(assetReference, forKey: .asset) - } - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - switch container.allKeys.first { - case .topic?: - self = .topic(try container.decode(URL.self, forKey: .topic)) - case .symbol?: - self = .symbol(try container.decode(String.self, forKey: .symbol)) - case .asset?: - self = .asset(try container.decode(AssetReference.self, forKey: .asset)) - case nil: - throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest - } - } - - /// A plain text representation of the request message. - public var description: String { - switch self { - case .topic(let url): - return "topic: \(url.absoluteString.singleQuoted)" - case .symbol(let identifier): - return "symbol: \(identifier.singleQuoted)" - case .asset(let asset): - return "asset with name: \(asset.assetName), bundle identifier: \(asset.bundleID)" - } - } - } - - /// An outdated version of a response message from the external link resolver. - /// - /// @DeprecationSummary { - /// This version of the communication protocol is no longer recommended. Update to ``RequestV2`` and ``ResponseV2`` instead. - /// - /// The new version of the communication protocol both has a mechanism for expanding functionality in the future (through common ``Capabilities`` between DocC and the external resolver) and supports richer responses for both successful and and failed requests. - /// } - @available(*, deprecated, message: "This version of the communication protocol is no longer recommended. Update to `RequestV2` and `ResponseV2` instead.") - public typealias Response = _DeprecatedResponseV1 - - @available(*, deprecated, message: "This version of the communication protocol is no longer recommended. Update to `RequestV2` and `ResponseV2` instead.") - public enum _DeprecatedResponseV1: Codable { - /// A bundle identifier response. - /// - /// This message should only be sent once, after the external link resolver has launched. - case bundleIdentifier(String) - /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. - case errorMessage(String) - /// A response with the resolved information about the requested topic or symbol. - case resolvedInformation(ResolvedInformation) - /// A response with information about the resolved asset. - case asset(DataAsset) - - enum CodingKeys: String, CodingKey { - case bundleIdentifier - case errorMessage - case resolvedInformation - case asset - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - switch container.allKeys.first { - case .bundleIdentifier?: - self = .bundleIdentifier(try container.decode(String.self, forKey: .bundleIdentifier)) - case .errorMessage?: - self = .errorMessage(try container.decode(String.self, forKey: .errorMessage)) - case .resolvedInformation?: - self = .resolvedInformation(try container.decode(ResolvedInformation.self, forKey: .resolvedInformation)) - case .asset?: - self = .asset(try container.decode(DataAsset.self, forKey: .asset)) - case nil: - throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient - } - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .bundleIdentifier(let bundleIdentifier): - try container.encode(bundleIdentifier, forKey: .bundleIdentifier) - case .errorMessage(let errorMessage): - try container.encode(errorMessage, forKey: .errorMessage) - case .resolvedInformation(let resolvedInformation): - try container.encode(resolvedInformation, forKey: .resolvedInformation) - case .asset(let assetReference): - try container.encode(assetReference, forKey: .asset) - } - } - } - - // MARK: Resolved Information - - /// A type used to transfer information about a resolved reference in the outdated and no longer recommended version of the external resolver communication protocol. - @available(*, deprecated, message: "This type is only used in the outdated, and no longer recommended, version of the out-of-process external resolver communication protocol.") - public struct ResolvedInformation: Codable { - /// Information about the resolved kind. - public let kind: DocumentationNode.Kind - /// Information about the resolved URL. - public let url: URL - /// Information about the resolved title. - public let title: String // DocumentationNode.Name - /// Information about the resolved abstract. - public let abstract: String // Markup - /// Information about the resolved language. - public let language: SourceLanguage - /// Information about the languages where the resolved node is available. - public let availableLanguages: Set - /// Information about the platforms and their versions where the resolved node is available, if any. - public let platforms: [PlatformAvailability]? - /// Information about the resolved declaration fragments, if any. - public let declarationFragments: DeclarationFragments? - - // We use the real types here because they're Codable and don't have public member-wise initializers. - - /// Platform availability for a resolved symbol reference. - public typealias PlatformAvailability = AvailabilityRenderItem - - /// The declaration fragments for a resolved symbol reference. - public typealias DeclarationFragments = SymbolGraph.Symbol.DeclarationFragments - - /// The platform names, derived from the platform availability. - public var platformNames: Set? { - return platforms.map { platforms in Set(platforms.compactMap { $0.name }) } - } - - /// Images that are used to represent the summarized element. - public var topicImages: [TopicImage]? - - /// References used in the content of the summarized element. - public var references: [any RenderReference]? - - /// The variants of content (kind, url, title, abstract, language, declaration) for this resolver information. - public var variants: [Variant]? - - /// A value that indicates whether this symbol is under development and likely to change. - var isBeta: Bool { - guard let platforms, !platforms.isEmpty else { - return false - } - - return platforms.allSatisfy { $0.isBeta == true } - } - - /// Creates a new resolved information value with all its values. - /// - /// - Parameters: - /// - kind: The resolved kind. - /// - url: The resolved URL. - /// - title: The resolved title - /// - abstract: The resolved (plain text) abstract. - /// - language: The resolved language. - /// - availableLanguages: The languages where the resolved node is available. - /// - platforms: The platforms and their versions where the resolved node is available, if any. - /// - declarationFragments: The resolved declaration fragments, if any. - /// - topicImages: Images that are used to represent the summarized element. - /// - references: References used in the content of the summarized element. - /// - variants: The variants of content for this resolver information. - public init( - kind: DocumentationNode.Kind, - url: URL, - title: String, - abstract: String, - language: SourceLanguage, - availableLanguages: Set, - platforms: [PlatformAvailability]? = nil, - declarationFragments: DeclarationFragments? = nil, - topicImages: [TopicImage]? = nil, - references: [any RenderReference]? = nil, - variants: [Variant]? = nil - ) { - self.kind = kind - self.url = url - self.title = title - self.abstract = abstract - self.language = language - self.availableLanguages = availableLanguages - self.platforms = platforms - self.declarationFragments = declarationFragments - self.topicImages = topicImages - self.references = references - self.variants = variants - } - - /// A variant of content for the resolved information. - /// - /// - Note: All properties except for ``traits`` are optional. If a property is `nil` it means that the value is the same as the resolved information's value. - public struct Variant: Codable { - /// The traits of the variant. - public let traits: [RenderNode.Variant.Trait] - - /// A wrapper for variant values that can either be specified, meaning the variant has a custom value, or not, meaning the variant has the same value as the resolved information. - /// - /// This alias is used to make the property declarations more explicit while at the same time offering the convenient syntax of optionals. - public typealias VariantValue = Optional - - /// The kind of the variant or `nil` if the kind is the same as the resolved information. - public let kind: VariantValue - /// The url of the variant or `nil` if the url is the same as the resolved information. - public let url: VariantValue - /// The title of the variant or `nil` if the title is the same as the resolved information. - public let title: VariantValue - /// The abstract of the variant or `nil` if the abstract is the same as the resolved information. - public let abstract: VariantValue - /// The language of the variant or `nil` if the language is the same as the resolved information. - public let language: VariantValue - /// The declaration fragments of the variant or `nil` if the declaration is the same as the resolved information. - /// - /// If the resolver information has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. - public let declarationFragments: VariantValue - - /// Creates a new resolved information variant with the values that are different from the resolved information values. - /// - /// - Parameters: - /// - traits: The traits of the variant. - /// - kind: The resolved kind. - /// - url: The resolved URL. - /// - title: The resolved title - /// - abstract: The resolved (plain text) abstract. - /// - language: The resolved language. - /// - declarationFragments: The resolved declaration fragments, if any. - public init( - traits: [RenderNode.Variant.Trait], - kind: VariantValue = nil, - url: VariantValue = nil, - title: VariantValue = nil, - abstract: VariantValue = nil, - language: VariantValue = nil, - declarationFragments: VariantValue = nil - ) { - self.traits = traits - self.kind = kind - self.url = url - self.title = title - self.abstract = abstract - self.language = language - self.declarationFragments = declarationFragments - } - } - } -} - -@available(*, deprecated, message: "This type is only used in the outdates, and no longer recommended, version of the out-of-process external resolver communication protocol.") -extension OutOfProcessReferenceResolver.ResolvedInformation { - enum CodingKeys: CodingKey { - case kind - case url - case title - case abstract - case language - case availableLanguages - case platforms - case declarationFragments - case topicImages - case references - case variants - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) - url = try container.decode(URL.self, forKey: .url) - title = try container.decode(String.self, forKey: .title) - abstract = try container.decode(String.self, forKey: .abstract) - language = try container.decode(SourceLanguage.self, forKey: .language) - availableLanguages = try container.decode(Set.self, forKey: .availableLanguages) - platforms = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.PlatformAvailability].self, forKey: .platforms) - declarationFragments = try container.decodeIfPresent(OutOfProcessReferenceResolver.ResolvedInformation.DeclarationFragments.self, forKey: .declarationFragments) - topicImages = try container.decodeIfPresent([TopicImage].self, forKey: .topicImages) - references = try container.decodeIfPresent([CodableRenderReference].self, forKey: .references).map { decodedReferences in - decodedReferences.map(\.reference) - } - variants = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.Variant].self, forKey: .variants) - - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(self.kind, forKey: .kind) - try container.encode(self.url, forKey: .url) - try container.encode(self.title, forKey: .title) - try container.encode(self.abstract, forKey: .abstract) - try container.encode(self.language, forKey: .language) - try container.encode(self.availableLanguages, forKey: .availableLanguages) - try container.encodeIfPresent(self.platforms, forKey: .platforms) - try container.encodeIfPresent(self.declarationFragments, forKey: .declarationFragments) - try container.encodeIfPresent(self.topicImages, forKey: .topicImages) - try container.encodeIfPresent(references?.map { CodableRenderReference($0) }, forKey: .references) - try container.encodeIfPresent(self.variants, forKey: .variants) - } -} diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift index bb7b74eedb..1988f8b074 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift @@ -9,78 +9,54 @@ */ public import Foundation -private import Markdown +import Markdown +public import SymbolKit /// A reference resolver that launches and interactively communicates with another process or service to resolve links. /// /// If your external reference resolver or an external symbol resolver is implemented in another executable, you can use this object /// to communicate between DocC and the `docc` executable. /// -/// ## Launching and responding to requests +/// The launched executable is expected to follow the flow outlined below, sending ``OutOfProcessReferenceResolver/Request`` +/// and ``OutOfProcessReferenceResolver/Response`` values back and forth: /// -/// When creating an out-of-process resolver using ``init(processLocation:errorOutputHandler:)`` to communicate with another executable; -/// DocC launches your link resolver executable and declares _its_ own ``Capabilities`` as a raw value passed via the `--capabilities` option. -/// Your link resolver executable is expected to respond with a ``ResponseV2/identifierAndCapabilities(_:_:)`` message that declares: -/// - The documentation bundle identifier that the executable can to resolve links for. -/// - The capabilities that the resolver supports. +/// │ +/// 1 ▼ +/// ┌──────────────────┐ +/// │ Output bundle ID │ +/// └──────────────────┘ +/// │ +/// 2 ▼ +/// ┌──────────────────┐ +/// │ Wait for input │◀───┐ +/// └──────────────────┘ │ +/// │ │ +/// 3 ▼ │ repeat +/// ┌──────────────────┐ │ +/// │ Output resolved │ │ +/// │ information │────┘ +/// └──────────────────┘ /// -/// After this "handshake" your link resolver executable is expected to wait for ``RequestV2`` messages from DocC and respond with exactly one ``ResponseV2`` per message. -/// A visual representation of this flow of execution can be seen in the diagram below: -/// -/// DocC link resolver executable -/// ┌─┐ ╎ -/// │ ├─────────── Launch ──────────▶┴┐ -/// │ │ --capabilities │ │ -/// │ │ │ │ -/// │ ◀───────── Handshake ─────────┤ │ -/// │ │ { "identifier" : ... , │ │ -/// │ │ "capabilities" : ... } │ │ -/// ┏ loop ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -/// ┃ │ │ │ │ ┃ -/// ┃ │ ├────────── Request ──────────▶ │ ┃ -/// ┃ │ │ { "link" : ... } OR │ │ ┃ -/// ┃ │ │ { "symbol" : ... } │ │ ┃ -/// ┃ │ │ │ │ ┃ -/// ┃ │ ◀────────── Response ─────────┤ │ ┃ -/// ┃ │ │ { "resolved" : ... } OR │ │ ┃ -/// ┃ │ │ { "failure" : ... } │ │ ┃ -/// ┃ │ │ │ │ ┃ -/// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -/// │ │ └─┘ -/// │ │ ╎ -/// -/// ## Interacting with a Convert Service -/// -/// When creating an out-of-process resolver using ``init(bundleID:server:convertRequestIdentifier:)`` to communicate with another process using a ``ConvertService``; -/// DocC sends that service `"resolve-reference"` messages with a``OutOfProcessReferenceResolver/Request`` payload and expects a `"resolved-reference-response"` responses with a ``OutOfProcessReferenceResolver/Response`` payload. -/// -/// Because the ``ConvertService`` messages are _implicitly_ tied to these outdated—and no longer recommended—request and response types, the richness of its responses is limited. -/// -/// - Note: when interacting with a ``ConvertService`` your service also needs to handle "asset" requests (``OutOfProcessReferenceResolver/Request/asset(_:)`` and responses that (``OutOfProcessReferenceResolver/Response/asset(_:)``) that link resolver executables don't need to handle. -/// -/// ## Topics -/// -/// - ``RequestV2`` -/// - ``ResponseV2`` +/// When resolving against a server, the server is expected to be able to handle messages of type "resolve-reference" with a +/// ``OutOfProcessReferenceResolver/Request`` payload and respond with messages of type "resolved-reference-response" +/// with a ``OutOfProcessReferenceResolver/Response`` payload. /// /// ## See Also +/// - ``ExternalDocumentationSource`` +/// - ``GlobalExternalSymbolResolver`` /// - ``DocumentationContext/externalDocumentationSources`` /// - ``DocumentationContext/globalExternalSymbolResolver`` +/// - ``Request`` +/// - ``Response`` public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalExternalSymbolResolver { - private var implementation: any _Implementation + private let externalLinkResolvingClient: any ExternalLinkResolving /// The bundle identifier for the reference resolver in the other process. - public var bundleID: DocumentationBundle.Identifier { - implementation.bundleID - } - - // This variable is used below for the `ConvertServiceFallbackResolver` conformance. - private var assetCache: [AssetReference: DataAsset] = [:] + public let bundleID: DocumentationBundle.Identifier /// Creates a new reference resolver that interacts with another executable. /// /// Initializing the resolver will also launch the other executable. The other executable will remain running for the lifetime of this object. - /// This and the rest of the communication between DocC and the link resolver executable is described in /// /// - Parameters: /// - processLocation: The location of the other executable. @@ -97,12 +73,12 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE let longRunningProcess = try LongRunningProcess(location: processLocation, errorOutputHandler: errorOutputHandler) - guard let handshake: InitialHandshakeMessage = try? longRunningProcess.readInitialHandshakeMessage() else { + guard case let .bundleIdentifier(decodedBundleIdentifier) = try longRunningProcess.sendAndWait(request: nil as Request?) as Response else { throw Error.invalidBundleIdentifierOutputFromExecutable(processLocation) } - // This private type and protocol exist to silence deprecation warnings - self.implementation = (_ImplementationProvider() as (any _ImplementationProviding)).makeImplementation(for: handshake, longRunningProcess: longRunningProcess) + self.bundleID = .init(rawValue: decodedBundleIdentifier) + self.externalLinkResolvingClient = longRunningProcess } /// Creates a new reference resolver that interacts with a documentation service. @@ -114,367 +90,179 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE /// - server: The server to send link resolution requests to. /// - convertRequestIdentifier: The identifier that the resolver will use for convert requests that it sends to the server. public init(bundleID: DocumentationBundle.Identifier, server: DocumentationServer, convertRequestIdentifier: String?) throws { - self.implementation = (_ImplementationProvider() as any _ImplementationProviding).makeImplementation( - for: .init(identifier: bundleID, capabilities: nil /* always use the V1 implementation */), - longRunningProcess: LongRunningService(server: server, convertRequestIdentifier: convertRequestIdentifier) - ) - } - - fileprivate struct InitialHandshakeMessage: Decodable { - var identifier: DocumentationBundle.Identifier - var capabilities: Capabilities? // The old V1 handshake didn't include this but the V2 requires it. - - init(identifier: DocumentationBundle.Identifier, capabilities: OutOfProcessReferenceResolver.Capabilities?) { - self.identifier = identifier - self.capabilities = capabilities - } - - private enum CodingKeys: CodingKey { - case bundleIdentifier // Legacy V1 handshake - case identifier, capabilities // V2 handshake - } - - init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - guard container.contains(.identifier) || container.contains(.bundleIdentifier) else { - throw DecodingError.keyNotFound(CodingKeys.identifier, .init(codingPath: decoder.codingPath, debugDescription: """ - Initial handshake message includes neither a '\(CodingKeys.identifier.stringValue)' key nor a '\(CodingKeys.bundleIdentifier.stringValue)' key. - """)) - } - - self.identifier = try container.decodeIfPresent(DocumentationBundle.Identifier.self, forKey: .identifier) - ?? container.decode(DocumentationBundle.Identifier.self, forKey: .bundleIdentifier) - - self.capabilities = try container.decodeIfPresent(Capabilities.self, forKey: .capabilities) - } + self.bundleID = bundleID + self.externalLinkResolvingClient = LongRunningService( + server: server, convertRequestIdentifier: convertRequestIdentifier) } // MARK: External Reference Resolver public func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult { - implementation.resolve(reference) + switch reference { + case .resolved(let resolved): + return resolved + + case let .unresolved(unresolvedReference): + guard unresolvedReference.bundleID == bundleID else { + fatalError(""" + Attempted to resolve a local reference externally: \(unresolvedReference.description.singleQuoted). + DocC should never pass a reference to an external resolver unless it matches that resolver's bundle identifier. + """) + } + do { + guard let unresolvedTopicURL = unresolvedReference.topicURL.components.url else { + // Return the unresolved reference if the underlying URL is not valid + return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo("URL \(unresolvedReference.topicURL.absoluteString.singleQuoted) is not valid.")) + } + let resolvedInformation = try resolveInformationForTopicURL(unresolvedTopicURL) + return .success( resolvedReference(for: resolvedInformation) ) + } catch let error { + return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo(error)) + } + } } @_spi(ExternalLinks) // LinkResolver.ExternalEntity isn't stable API yet public func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - implementation.entity(with: reference) + guard let resolvedInformation = referenceCache[reference.url] else { + fatalError("A topic reference that has already been resolved should always exist in the cache.") + } + return makeEntity(with: resolvedInformation, reference: reference.absoluteString) } @_spi(ExternalLinks) // LinkResolver.ExternalEntity isn't stable API yet public func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { - implementation.symbolReferenceAndEntity(withPreciseIdentifier: preciseIdentifier) + guard let resolvedInformation = try? resolveInformationForSymbolIdentifier(preciseIdentifier) else { return nil } + + let reference = ResolvedTopicReference( + bundleID: "com.externally.resolved.symbol", + path: "/\(preciseIdentifier)", + sourceLanguages: sourceLanguages(for: resolvedInformation) + ) + let entity = makeEntity(with: resolvedInformation, reference: reference.absoluteString) + return (reference, entity) } -} - -// MARK: Implementations - -private protocol _Implementation: ExternalDocumentationSource, GlobalExternalSymbolResolver { - var bundleID: DocumentationBundle.Identifier { get } - var longRunningProcess: any ExternalLinkResolving { get } - // - func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult -} - -private extension _Implementation { - // Avoid some common boilerplate between implementations. - func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult { - switch reference { - case .resolved(let resolved): - return resolved - - case let .unresolved(unresolvedReference): - guard unresolvedReference.bundleID == bundleID else { - fatalError(""" - Attempted to resolve a local reference externally: \(unresolvedReference.description.singleQuoted). - DocC should never pass a reference to an external resolver unless it matches that resolver's bundle identifier. - """) - } - do { - // This is where each implementation differs - return try resolve(unresolvedReference: unresolvedReference) - } catch let error { - return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo(error)) - } - } - } -} - -// This private protocol allows the out-of-process resolver to create ImplementationV1 without deprecation warnings -private protocol _ImplementationProviding { - func makeImplementation(for handshake: OutOfProcessReferenceResolver.InitialHandshakeMessage, longRunningProcess: any ExternalLinkResolving) -> any _Implementation -} - -private extension OutOfProcessReferenceResolver { - // A concrete type with a deprecated implementation that can be cast to `_ImplementationProviding` to avoid deprecation warnings. - struct _ImplementationProvider: _ImplementationProviding { - @available(*, deprecated) // The V1 implementation is built around several now-deprecated types. This deprecation silences those depreciation warnings. - func makeImplementation(for handshake: OutOfProcessReferenceResolver.InitialHandshakeMessage, longRunningProcess: any ExternalLinkResolving) -> any _Implementation { - if let capabilities = handshake.capabilities { - return ImplementationV2(longRunningProcess: longRunningProcess, bundleID: handshake.identifier, executableCapabilities: capabilities) - } else { - return ImplementationV1(longRunningProcess: longRunningProcess, bundleID: handshake.identifier) + private func makeEntity(with resolvedInformation: ResolvedInformation, reference: String) -> LinkResolver.ExternalEntity { + let (kind, role) = DocumentationContentRenderer.renderKindAndRole(resolvedInformation.kind, semantic: nil) + + var renderReference = TopicRenderReference( + identifier: .init(reference), + title: resolvedInformation.title, + // The resolved information only stores the plain text abstract https://github.com/swiftlang/swift-docc/issues/802 + abstract: [.text(resolvedInformation.abstract)], + url: resolvedInformation.url.path, + kind: kind, + role: role, + fragments: resolvedInformation.declarationFragments?.declarationFragments.map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) }, + isBeta: resolvedInformation.isBeta, + isDeprecated: (resolvedInformation.platforms ?? []).contains(where: { $0.deprecated != nil }), + images: resolvedInformation.topicImages ?? [] + ) + for variant in resolvedInformation.variants ?? [] { + if let title = variant.title { + renderReference.titleVariants.variants.append( + .init(traits: variant.traits, patch: [.replace(value: title)]) + ) } - } - } -} - -// MARK: Version 1 (deprecated) - -extension OutOfProcessReferenceResolver { - /// The original—no longer recommended—version of the out-of-process resolver implementation. - /// - /// This implementation uses ``Request`` and ``Response`` which aren't extensible and have restrictions on the details of the response payloads. - @available(*, deprecated) // The V1 implementation is built around several now-deprecated types. This deprecation silences those depreciation warnings. - private final class ImplementationV1: _Implementation { - let bundleID: DocumentationBundle.Identifier - let longRunningProcess: any ExternalLinkResolving - - init(longRunningProcess: any ExternalLinkResolving, bundleID: DocumentationBundle.Identifier) { - self.longRunningProcess = longRunningProcess - self.bundleID = bundleID - } - - // This is fileprivate so that the ConvertService conformance below can access it. - fileprivate private(set) var referenceCache: [URL: ResolvedInformation] = [:] - private var symbolCache: [String: ResolvedInformation] = [:] - - func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult { - guard let unresolvedTopicURL = unresolvedReference.topicURL.components.url else { - // Return the unresolved reference if the underlying URL is not valid - return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo("URL \(unresolvedReference.topicURL.absoluteString.singleQuoted) is not valid.")) + if let abstract = variant.abstract { + renderReference.abstractVariants.variants.append( + .init(traits: variant.traits, patch: [.replace(value: [.text(abstract)])]) + ) } - let resolvedInformation = try resolveInformationForTopicURL(unresolvedTopicURL) - return .success( resolvedReference(for: resolvedInformation) ) - } - - func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - guard let resolvedInformation = referenceCache[reference.url] else { - fatalError("A topic reference that has already been resolved should always exist in the cache.") + if let declarationFragments = variant.declarationFragments { + renderReference.fragmentsVariants.variants.append( + .init(traits: variant.traits, patch: [.replace(value: declarationFragments?.declarationFragments.map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) })]) + ) } - return makeEntity(with: resolvedInformation, reference: reference.absoluteString) } + let dependencies = RenderReferenceDependencies( + topicReferences: [], + linkReferences: (resolvedInformation.references ?? []).compactMap { $0 as? LinkReference }, + imageReferences: (resolvedInformation.references ?? []).compactMap { $0 as? ImageReference } + ) - func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { - guard let resolvedInformation = try? resolveInformationForSymbolIdentifier(preciseIdentifier) else { return nil } - - let reference = ResolvedTopicReference( - bundleID: "com.externally.resolved.symbol", - path: "/\(preciseIdentifier)", - sourceLanguages: sourceLanguages(for: resolvedInformation) - ) - let entity = makeEntity(with: resolvedInformation, reference: reference.absoluteString) - return (reference, entity) + return LinkResolver.ExternalEntity( + topicRenderReference: renderReference, + renderReferenceDependencies: dependencies, + sourceLanguages: resolvedInformation.availableLanguages, + symbolKind: DocumentationNode.symbolKind(for: resolvedInformation.kind) + ) + } + + // MARK: Implementation + + private var referenceCache: [URL: ResolvedInformation] = [:] + private var symbolCache: [String: ResolvedInformation] = [:] + private var assetCache: [AssetReference: DataAsset] = [:] + + /// Makes a call to the other process to resolve information about a page based on its URL. + func resolveInformationForTopicURL(_ topicURL: URL) throws -> ResolvedInformation { + if let cachedInformation = referenceCache[topicURL] { + return cachedInformation } - /// Makes a call to the other process to resolve information about a page based on its URL. - private func resolveInformationForTopicURL(_ topicURL: URL) throws -> ResolvedInformation { - if let cachedInformation = referenceCache[topicURL] { - return cachedInformation - } - - let response: Response = try longRunningProcess.sendAndWait(request: Request.topic(topicURL)) - - switch response { - case .bundleIdentifier: - throw Error.executableSentBundleIdentifierAgain - - case .errorMessage(let errorMessage): - throw Error.forwardedErrorFromClient(errorMessage: errorMessage) - - case .resolvedInformation(let resolvedInformation): - // Cache the information for the resolved reference, that's what's will be used when returning the entity later. - let resolvedReference = resolvedReference(for: resolvedInformation) - referenceCache[resolvedReference.url] = resolvedInformation - return resolvedInformation - - default: - throw Error.unexpectedResponse(response: response, requestDescription: "topic URL") - } - } + let response: Response = try externalLinkResolvingClient.sendAndWait(request: Request.topic(topicURL)) - /// Makes a call to the other process to resolve information about a symbol based on its precise identifier. - private func resolveInformationForSymbolIdentifier(_ preciseIdentifier: String) throws -> ResolvedInformation { - if let cachedInformation = symbolCache[preciseIdentifier] { - return cachedInformation - } + switch response { + case .bundleIdentifier: + throw Error.executableSentBundleIdentifierAgain - let response: Response = try longRunningProcess.sendAndWait(request: Request.symbol(preciseIdentifier)) + case .errorMessage(let errorMessage): + throw Error.forwardedErrorFromClient(errorMessage: errorMessage) - switch response { - case .bundleIdentifier: - throw Error.executableSentBundleIdentifierAgain - - case .errorMessage(let errorMessage): - throw Error.forwardedErrorFromClient(errorMessage: errorMessage) - - case .resolvedInformation(let resolvedInformation): - symbolCache[preciseIdentifier] = resolvedInformation - return resolvedInformation - - default: - throw Error.unexpectedResponse(response: response, requestDescription: "symbol ID") - } - } - - private func resolvedReference(for resolvedInformation: ResolvedInformation) -> ResolvedTopicReference { - return ResolvedTopicReference( - bundleID: bundleID, - path: resolvedInformation.url.path, - fragment: resolvedInformation.url.fragment, - sourceLanguages: sourceLanguages(for: resolvedInformation) - ) - } - - private func sourceLanguages(for resolvedInformation: ResolvedInformation) -> Set { - // It is expected that the available languages contains the main language - return resolvedInformation.availableLanguages.union(CollectionOfOne(resolvedInformation.language)) - } - - private func makeEntity(with resolvedInformation: ResolvedInformation, reference: String) -> LinkResolver.ExternalEntity { - return LinkResolver.ExternalEntity( - kind: resolvedInformation.kind, - language: resolvedInformation.language, - relativePresentationURL: resolvedInformation.url.withoutHostAndPortAndScheme(), - referenceURL: URL(string: reference)!, - title: resolvedInformation.title, - // The resolved information only stores the plain text abstract and can't be changed. Use the version 2 communication protocol to support rich abstracts. - abstract: [.text(resolvedInformation.abstract)], - availableLanguages: resolvedInformation.availableLanguages, - platforms: resolvedInformation.platforms, - taskGroups: nil, - usr: nil, - declarationFragments: resolvedInformation.declarationFragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, - redirects: nil, - topicImages: resolvedInformation.topicImages, - references: resolvedInformation.references, - variants: (resolvedInformation.variants ?? []).map { variant in - .init( - traits: variant.traits, - kind: variant.kind, - language: variant.language, - relativePresentationURL: variant.url?.withoutHostAndPortAndScheme(), - title: variant.title, - abstract: variant.abstract.map { [.text($0)] }, - taskGroups: nil, - usr: nil, - declarationFragments: variant.declarationFragments.map { fragments in - fragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) } - } - ) - } - ) + case .resolvedInformation(let resolvedInformation): + // Cache the information for the resolved reference, that's what's will be used when returning the entity later. + let resolvedReference = resolvedReference(for: resolvedInformation) + referenceCache[resolvedReference.url] = resolvedInformation + return resolvedInformation + + default: + throw Error.unexpectedResponse(response: response, requestDescription: "topic URL") } } -} - -// MARK: Version 2 - -extension OutOfProcessReferenceResolver { - private final class ImplementationV2: _Implementation { - let longRunningProcess: any ExternalLinkResolving - let bundleID: DocumentationBundle.Identifier - let executableCapabilities: Capabilities - - init( - longRunningProcess: any ExternalLinkResolving, - bundleID: DocumentationBundle.Identifier, - executableCapabilities: Capabilities - ) { - self.longRunningProcess = longRunningProcess - self.bundleID = bundleID - self.executableCapabilities = executableCapabilities - } - - private var linkCache: [String /* either a USR or an absolute UnresolvedTopicReference */: LinkDestinationSummary] = [:] - - func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult { - let linkString = unresolvedReference.topicURL.absoluteString - if let cachedSummary = linkCache[linkString] { - return .success( makeReference(for: cachedSummary) ) - } - - let response: ResponseV2 = try longRunningProcess.sendAndWait(request: RequestV2.link(linkString)) - - switch response { - case .identifierAndCapabilities: - throw Error.executableSentBundleIdentifierAgain - - case .failure(let diagnosticMessage): - let solutions: [Solution] = (diagnosticMessage.solutions ?? []).map { - Solution(summary: $0.summary, replacements: $0.replacement.map { replacement in - [Replacement( - // The replacement ranges are relative to the link itself. - // To replace the entire link, we create a range from 0 to the original length, both offset by -4 (the "doc:" length) - range: SourceLocation(line: 0, column: -4, source: nil) ..< SourceLocation(line: 0, column: linkString.utf8.count - 4, source: nil), - replacement: replacement - )] - } ?? []) - } - return .failure( - unresolvedReference, - TopicReferenceResolutionErrorInfo(diagnosticMessage.summary, solutions: solutions) - ) - - case .resolved(let linkSummary): - // Cache the information for the original authored link - linkCache[linkString] = linkSummary - // Cache the information for the resolved reference. That's what's will be used when returning the entity later. - let reference = makeReference(for: linkSummary) - linkCache[reference.absoluteString] = linkSummary - if let usr = linkSummary.usr { - // If the page is a symbol, cache its information for the USR as well. - linkCache[usr] = linkSummary - } - return .success(reference) - - default: - throw Error.unexpectedResponse(response: response, requestDescription: "topic link") - } + + /// Makes a call to the other process to resolve information about a symbol based on its precise identifier. + private func resolveInformationForSymbolIdentifier(_ preciseIdentifier: String) throws -> ResolvedInformation { + if let cachedInformation = symbolCache[preciseIdentifier] { + return cachedInformation } - func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - guard let linkSummary = linkCache[reference.url.standardized.absoluteString] else { - fatalError("A topic reference that has already been resolved should always exist in the cache.") - } - return linkSummary - } + let response: Response = try externalLinkResolvingClient.sendAndWait(request: Request.symbol(preciseIdentifier)) - func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { - if let cachedSummary = linkCache[preciseIdentifier] { - return (makeReference(for: cachedSummary), cachedSummary) - } - - guard case ResponseV2.resolved(let linkSummary)? = try? longRunningProcess.sendAndWait(request: RequestV2.symbol(preciseIdentifier)) else { - return nil - } + switch response { + case .bundleIdentifier: + throw Error.executableSentBundleIdentifierAgain - // Cache the information for the USR - linkCache[preciseIdentifier] = linkSummary + case .errorMessage(let errorMessage): + throw Error.forwardedErrorFromClient(errorMessage: errorMessage) - // Cache the information for the resolved reference. - let reference = makeReference(for: linkSummary) - linkCache[reference.absoluteString] = linkSummary + case .resolvedInformation(let resolvedInformation): + symbolCache[preciseIdentifier] = resolvedInformation + return resolvedInformation - return (reference, linkSummary) - } - - private func makeReference(for linkSummary: LinkDestinationSummary) -> ResolvedTopicReference { - ResolvedTopicReference( - bundleID: linkSummary.referenceURL.host.map { .init(rawValue: $0) } ?? "unknown", - path: linkSummary.referenceURL.path, - fragment: linkSummary.referenceURL.fragment, - sourceLanguages: linkSummary.availableLanguages - ) + default: + throw Error.unexpectedResponse(response: response, requestDescription: "symbol ID") } } + + private func resolvedReference(for resolvedInformation: ResolvedInformation) -> ResolvedTopicReference { + return ResolvedTopicReference( + bundleID: bundleID, + path: resolvedInformation.url.path, + fragment: resolvedInformation.url.fragment, + sourceLanguages: sourceLanguages(for: resolvedInformation) + ) + } + + private func sourceLanguages(for resolvedInformation: ResolvedInformation) -> Set { + // It is expected that the available languages contains the main language + return resolvedInformation.availableLanguages.union(CollectionOfOne(resolvedInformation.language)) + } } -// MARK: Cross process communication - private protocol ExternalLinkResolving { - func sendAndWait(request: Request) throws -> Response + func sendAndWait(request: Request?) throws -> Response } private class LongRunningService: ExternalLinkResolving { @@ -485,7 +273,7 @@ private class LongRunningService: ExternalLinkResolving { server: server, convertRequestIdentifier: convertRequestIdentifier) } - func sendAndWait(request: Request) throws -> Response { + func sendAndWait(request: Request?) throws -> Response { let responseData = try client.sendAndWait(request) return try JSONDecoder().decode(Response.self, from: responseData) } @@ -502,7 +290,6 @@ private class LongRunningProcess: ExternalLinkResolving { init(location: URL, errorOutputHandler: @escaping (String) -> Void) throws { let process = Process() process.executableURL = location - process.arguments = ["--capabilities", "\(OutOfProcessReferenceResolver.Capabilities().rawValue)"] process.standardInput = input process.standardOutput = output @@ -514,7 +301,7 @@ private class LongRunningProcess: ExternalLinkResolving { errorReadSource.setEventHandler { [errorOutput] in let data = errorOutput.fileHandleForReading.availableData let errorMessage = String(data: data, encoding: .utf8) - ?? "<\(ByteCountFormatter.string(fromByteCount: Int64(data.count), countStyle: .memory)) of non-utf8 data>" + ?? "<\(ByteCountFormatter.string(fromByteCount: Int64(data.count), countStyle: .memory)) of non-utf8 data>" errorOutputHandler(errorMessage) } @@ -532,25 +319,16 @@ private class LongRunningProcess: ExternalLinkResolving { private let output = Pipe() private let errorOutput = Pipe() private let errorReadSource: any DispatchSourceRead - - func readInitialHandshakeMessage() throws -> Response { - return try _readResponse() - } - - func sendAndWait(request: Request) throws -> Response { - // Send - guard let requestString = String(data: try JSONEncoder().encode(request), encoding: .utf8)?.appending("\n"), - let requestData = requestString.data(using: .utf8) - else { - throw OutOfProcessReferenceResolver.Error.unableToEncodeRequestToClient(requestDescription: "\(request)") - } - input.fileHandleForWriting.write(requestData) - // Receive - return try _readResponse() - } - - private func _readResponse() throws -> Response { + func sendAndWait(request: Request?) throws -> Response { + if let request { + guard let requestString = String(data: try JSONEncoder().encode(request), encoding: .utf8)?.appending("\n"), + let requestData = requestString.data(using: .utf8) + else { + throw OutOfProcessReferenceResolver.Error.unableToEncodeRequestToClient(requestDescription: request.description) + } + input.fileHandleForWriting.write(requestData) + } var response = output.fileHandleForReading.availableData guard !response.isEmpty else { throw OutOfProcessReferenceResolver.Error.processDidExit(code: Int(process.terminationStatus)) @@ -563,8 +341,8 @@ private class LongRunningProcess: ExternalLinkResolving { // To avoid blocking forever we check if the response can be decoded after each chunk of data. return try JSONDecoder().decode(Response.self, from: response) } catch { - if case DecodingError.dataCorrupted = error, // If the data wasn't valid JSON, read more data and try to decode it again. - response.count.isMultiple(of: Int(PIPE_BUF)) // To reduce the risk of deadlocking, check that bytes so far is a multiple of the pipe buffer size. + if case DecodingError.dataCorrupted = error, // If the data wasn't valid JSON, read more data and try to decode it again. + response.count.isMultiple(of: Int(PIPE_BUF)) // To reduce the risk of deadlocking, check that bytes so far is a multiple of the pipe buffer size. { let moreResponseData = output.fileHandleForReading.availableData guard !moreResponseData.isEmpty else { @@ -573,7 +351,7 @@ private class LongRunningProcess: ExternalLinkResolving { response += moreResponseData continue } - + // Other errors are re-thrown as wrapped errors. throw OutOfProcessReferenceResolver.Error.unableToDecodeResponseFromClient(response, error) } @@ -593,8 +371,6 @@ private class LongRunningProcess: ExternalLinkResolving { #endif } -// MARK: Error - extension OutOfProcessReferenceResolver { /// Errors that may occur when communicating with an external reference resolver. enum Error: Swift.Error, DescribedError { @@ -624,7 +400,7 @@ extension OutOfProcessReferenceResolver { /// The request type was not known (neither 'topic' nor 'symbol'). case unknownTypeOfRequest /// Received an unknown type of response to sent request. - case unexpectedResponse(response: Any, requestDescription: String) + case unexpectedResponse(response: Response, requestDescription: String) /// A plain text representation of the error message. var errorDescription: String { @@ -659,46 +435,360 @@ extension OutOfProcessReferenceResolver { } } -// MARK: Convert Service +extension OutOfProcessReferenceResolver { + + // MARK: Request & Response + + /// A request message to send to the external link resolver. + /// + /// This can either be a request to resolve a topic URL or to resolve a symbol based on its precise identifier. + public enum Request: Codable, CustomStringConvertible { + /// A request to resolve a topic URL + case topic(URL) + /// A request to resolve a symbol based on its precise identifier. + case symbol(String) + /// A request to resolve an asset. + case asset(AssetReference) + + private enum CodingKeys: CodingKey { + case topic + case symbol + case asset + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .topic(let url): + try container.encode(url, forKey: .topic) + case .symbol(let identifier): + try container.encode(identifier, forKey: .symbol) + case .asset(let assetReference): + try container.encode(assetReference, forKey: .asset) + } + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + switch container.allKeys.first { + case .topic?: + self = .topic(try container.decode(URL.self, forKey: .topic)) + case .symbol?: + self = .symbol(try container.decode(String.self, forKey: .symbol)) + case .asset?: + self = .asset(try container.decode(AssetReference.self, forKey: .asset)) + case nil: + throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest + } + } + + /// A plain text representation of the request message. + public var description: String { + switch self { + case .topic(let url): + return "topic: \(url.absoluteString.singleQuoted)" + case .symbol(let identifier): + return "symbol: \(identifier.singleQuoted)" + case .asset(let asset): + return "asset with name: \(asset.assetName), bundle identifier: \(asset.bundleID)" + } + } + } + + /// A response message from the external link resolver. + public enum Response: Codable { + /// A bundle identifier response. + /// + /// This message should only be sent once, after the external link resolver has launched. + case bundleIdentifier(String) + /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. + case errorMessage(String) + /// A response with the resolved information about the requested topic or symbol. + case resolvedInformation(ResolvedInformation) + /// A response with information about the resolved asset. + case asset(DataAsset) + + enum CodingKeys: String, CodingKey { + case bundleIdentifier + case errorMessage + case resolvedInformation + case asset + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + switch container.allKeys.first { + case .bundleIdentifier?: + self = .bundleIdentifier(try container.decode(String.self, forKey: .bundleIdentifier)) + case .errorMessage?: + self = .errorMessage(try container.decode(String.self, forKey: .errorMessage)) + case .resolvedInformation?: + self = .resolvedInformation(try container.decode(ResolvedInformation.self, forKey: .resolvedInformation)) + case .asset?: + self = .asset(try container.decode(DataAsset.self, forKey: .asset)) + case nil: + throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .bundleIdentifier(let bundleIdentifier): + try container.encode(bundleIdentifier, forKey: .bundleIdentifier) + case .errorMessage(let errorMessage): + try container.encode(errorMessage, forKey: .errorMessage) + case .resolvedInformation(let resolvedInformation): + try container.encode(resolvedInformation, forKey: .resolvedInformation) + case .asset(let assetReference): + try container.encode(assetReference, forKey: .asset) + } + } + } + + // MARK: Resolved Information + + /// A type used to transfer information about a resolved reference to DocC from from a reference resolver in another executable. + public struct ResolvedInformation: Codable { + // This type is duplicating the information from LinkDestinationSummary with some minor differences. + // Changes generally need to be made in both places. It would be good to replace this with LinkDestinationSummary. + // FIXME: https://github.com/swiftlang/swift-docc/issues/802 + + /// Information about the resolved kind. + public let kind: DocumentationNode.Kind + /// Information about the resolved URL. + public let url: URL + /// Information about the resolved title. + public let title: String // DocumentationNode.Name + /// Information about the resolved abstract. + public let abstract: String // Markup + /// Information about the resolved language. + public let language: SourceLanguage + /// Information about the languages where the resolved node is available. + public let availableLanguages: Set + /// Information about the platforms and their versions where the resolved node is available, if any. + public let platforms: [PlatformAvailability]? + /// Information about the resolved declaration fragments, if any. + public let declarationFragments: DeclarationFragments? + + // We use the real types here because they're Codable and don't have public member-wise initializers. + + /// Platform availability for a resolved symbol reference. + public typealias PlatformAvailability = AvailabilityRenderItem + + /// The declaration fragments for a resolved symbol reference. + public typealias DeclarationFragments = SymbolGraph.Symbol.DeclarationFragments + + /// The platform names, derived from the platform availability. + public var platformNames: Set? { + return platforms.map { platforms in Set(platforms.compactMap { $0.name }) } + } + + /// Images that are used to represent the summarized element. + public var topicImages: [TopicImage]? + + /// References used in the content of the summarized element. + public var references: [any RenderReference]? + + /// The variants of content (kind, url, title, abstract, language, declaration) for this resolver information. + public var variants: [Variant]? + + /// A value that indicates whether this symbol is under development and likely to change. + var isBeta: Bool { + guard let platforms, !platforms.isEmpty else { + return false + } + + return platforms.allSatisfy { $0.isBeta == true } + } + + /// Creates a new resolved information value with all its values. + /// + /// - Parameters: + /// - kind: The resolved kind. + /// - url: The resolved URL. + /// - title: The resolved title + /// - abstract: The resolved (plain text) abstract. + /// - language: The resolved language. + /// - availableLanguages: The languages where the resolved node is available. + /// - platforms: The platforms and their versions where the resolved node is available, if any. + /// - declarationFragments: The resolved declaration fragments, if any. + /// - topicImages: Images that are used to represent the summarized element. + /// - references: References used in the content of the summarized element. + /// - variants: The variants of content for this resolver information. + public init( + kind: DocumentationNode.Kind, + url: URL, + title: String, + abstract: String, + language: SourceLanguage, + availableLanguages: Set, + platforms: [PlatformAvailability]? = nil, + declarationFragments: DeclarationFragments? = nil, + topicImages: [TopicImage]? = nil, + references: [any RenderReference]? = nil, + variants: [Variant]? = nil + ) { + self.kind = kind + self.url = url + self.title = title + self.abstract = abstract + self.language = language + self.availableLanguages = availableLanguages + self.platforms = platforms + self.declarationFragments = declarationFragments + self.topicImages = topicImages + self.references = references + self.variants = variants + } + + /// A variant of content for the resolved information. + /// + /// - Note: All properties except for ``traits`` are optional. If a property is `nil` it means that the value is the same as the resolved information's value. + public struct Variant: Codable { + /// The traits of the variant. + public let traits: [RenderNode.Variant.Trait] + + /// A wrapper for variant values that can either be specified, meaning the variant has a custom value, or not, meaning the variant has the same value as the resolved information. + /// + /// This alias is used to make the property declarations more explicit while at the same time offering the convenient syntax of optionals. + public typealias VariantValue = Optional + + /// The kind of the variant or `nil` if the kind is the same as the resolved information. + public let kind: VariantValue + /// The url of the variant or `nil` if the url is the same as the resolved information. + public let url: VariantValue + /// The title of the variant or `nil` if the title is the same as the resolved information. + public let title: VariantValue + /// The abstract of the variant or `nil` if the abstract is the same as the resolved information. + public let abstract: VariantValue + /// The language of the variant or `nil` if the language is the same as the resolved information. + public let language: VariantValue + /// The declaration fragments of the variant or `nil` if the declaration is the same as the resolved information. + /// + /// If the resolver information has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. + public let declarationFragments: VariantValue + + /// Creates a new resolved information variant with the values that are different from the resolved information values. + /// + /// - Parameters: + /// - traits: The traits of the variant. + /// - kind: The resolved kind. + /// - url: The resolved URL. + /// - title: The resolved title + /// - abstract: The resolved (plain text) abstract. + /// - language: The resolved language. + /// - declarationFragments: The resolved declaration fragments, if any. + public init( + traits: [RenderNode.Variant.Trait], + kind: VariantValue = nil, + url: VariantValue = nil, + title: VariantValue = nil, + abstract: VariantValue = nil, + language: VariantValue = nil, + declarationFragments: VariantValue = nil + ) { + self.traits = traits + self.kind = kind + self.url = url + self.title = title + self.abstract = abstract + self.language = language + self.declarationFragments = declarationFragments + } + } + } +} + +extension OutOfProcessReferenceResolver.ResolvedInformation { + enum CodingKeys: CodingKey { + case kind + case url + case title + case abstract + case language + case availableLanguages + case platforms + case declarationFragments + case topicImages + case references + case variants + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) + url = try container.decode(URL.self, forKey: .url) + title = try container.decode(String.self, forKey: .title) + abstract = try container.decode(String.self, forKey: .abstract) + language = try container.decode(SourceLanguage.self, forKey: .language) + availableLanguages = try container.decode(Set.self, forKey: .availableLanguages) + platforms = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.PlatformAvailability].self, forKey: .platforms) + declarationFragments = try container.decodeIfPresent(OutOfProcessReferenceResolver.ResolvedInformation.DeclarationFragments.self, forKey: .declarationFragments) + topicImages = try container.decodeIfPresent([TopicImage].self, forKey: .topicImages) + references = try container.decodeIfPresent([CodableRenderReference].self, forKey: .references).map { decodedReferences in + decodedReferences.map(\.reference) + } + variants = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.Variant].self, forKey: .variants) + + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.kind, forKey: .kind) + try container.encode(self.url, forKey: .url) + try container.encode(self.title, forKey: .title) + try container.encode(self.abstract, forKey: .abstract) + try container.encode(self.language, forKey: .language) + try container.encode(self.availableLanguages, forKey: .availableLanguages) + try container.encodeIfPresent(self.platforms, forKey: .platforms) + try container.encodeIfPresent(self.declarationFragments, forKey: .declarationFragments) + try container.encodeIfPresent(self.topicImages, forKey: .topicImages) + try container.encodeIfPresent(references?.map { CodableRenderReference($0) }, forKey: .references) + try container.encodeIfPresent(self.variants, forKey: .variants) + } +} extension OutOfProcessReferenceResolver: ConvertServiceFallbackResolver { @_spi(ExternalLinks) - @available(*, deprecated, message: "The ConvertService is implicitly reliant on the deprecated `Request` and `Response` types.") public func entityIfPreviouslyResolved(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity? { - guard let implementation = implementation as? ImplementationV1 else { - assertionFailure("ConvertServiceFallbackResolver expects V1 requests and responses") - return nil - } - - guard implementation.referenceCache.keys.contains(reference.url) else { return nil } + guard referenceCache.keys.contains(reference.url) else { return nil } var entity = entity(with: reference) // The entity response doesn't include the assets that it references. // Before returning the entity, make sure that its references assets are included among the image dependencies. - var references = entity.references ?? [] - - for image in entity.topicImages ?? [] { + for image in entity.topicRenderReference.images { if let asset = resolve(assetNamed: image.identifier.identifier) { - references.append(ImageReference(identifier: image.identifier, imageAsset: asset)) + entity.renderReferenceDependencies.imageReferences.append(ImageReference(identifier: image.identifier, imageAsset: asset)) } } - if !references.isEmpty { - entity.references = references - } - return entity } - @available(*, deprecated, message: "The ConvertService is implicitly reliant on the deprecated `Request` and `Response` types.") func resolve(assetNamed assetName: String) -> DataAsset? { + return try? resolveInformationForAsset(named: assetName) + } + + func resolveInformationForAsset(named assetName: String) throws -> DataAsset { let assetReference = AssetReference(assetName: assetName, bundleID: bundleID) if let asset = assetCache[assetReference] { return asset } - guard case .asset(let asset)? = try? implementation.longRunningProcess.sendAndWait(request: Request.asset(assetReference)) as Response else { - return nil + let response = try externalLinkResolvingClient.sendAndWait( + request: Request.asset(AssetReference(assetName: assetName, bundleID: bundleID)) + ) as Response + + switch response { + case .asset(let asset): + assetCache[assetReference] = asset + return asset + case .errorMessage(let errorMessage): + throw Error.forwardedErrorFromClient(errorMessage: errorMessage) + default: + throw Error.unexpectedResponse(response: response, requestDescription: "asset") } - return asset } } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift index 4c6fac3500..6e6fdbb3ab 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift @@ -87,10 +87,31 @@ final class ExternalPathHierarchyResolver { /// /// - Precondition: The `reference` was previously resolved by this resolver. func entity(_ reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - guard let alreadyResolvedSummary = content[reference] else { + guard let resolvedInformation = content[reference] else { fatalError("The resolver should only be asked for entities that it resolved.") } - return alreadyResolvedSummary + + let topicReferences: [ResolvedTopicReference] = (resolvedInformation.references ?? []).compactMap { + guard let renderReference = $0 as? TopicRenderReference, + let url = URL(string: renderReference.identifier.identifier), + let bundleID = url.host + else { + return nil + } + return ResolvedTopicReference(bundleID: .init(rawValue: bundleID), path: url.path, fragment: url.fragment, sourceLanguage: .swift) + } + let dependencies = RenderReferenceDependencies( + topicReferences: topicReferences, + linkReferences: (resolvedInformation.references ?? []).compactMap { $0 as? LinkReference }, + imageReferences: (resolvedInformation.references ?? []).compactMap { $0 as? ImageReference } + ) + + return .init( + topicRenderReference: resolvedInformation.topicRenderReference(), + renderReferenceDependencies: dependencies, + sourceLanguages: resolvedInformation.availableLanguages, + symbolKind: DocumentationNode.symbolKind(for: resolvedInformation.kind) + ) } // MARK: Deserialization @@ -161,9 +182,9 @@ private extension Sequence { // MARK: ExternalEntity -extension LinkDestinationSummary { +private extension LinkDestinationSummary { /// A value that indicates whether this symbol is under development and likely to change. - private var isBeta: Bool { + var isBeta: Bool { guard let platforms, !platforms.isEmpty else { return false } @@ -172,7 +193,7 @@ extension LinkDestinationSummary { } /// Create a topic render render reference for this link summary and its content variants. - func makeTopicRenderReference() -> TopicRenderReference { + func topicRenderReference() -> TopicRenderReference { let (kind, role) = DocumentationContentRenderer.renderKindAndRole(kind, semantic: nil) var titleVariants = VariantCollection(defaultValue: title) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift index 8fa9575bd5..8f0966fc79 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift @@ -13,76 +13,69 @@ import SymbolKit /// A rendering-friendly representation of a external node. package struct ExternalRenderNode { - private var entity: LinkResolver.ExternalEntity - private var topicRenderReference: TopicRenderReference - + /// Underlying external entity backing this external node. + private var externalEntity: LinkResolver.ExternalEntity + /// The bundle identifier for this external node. private var bundleIdentifier: DocumentationBundle.Identifier - // This type is designed to misrepresent external content as local content to fit in with the navigator. - // This spreads the issue to more code rather than fixing it, which adds technical debt and can be fragile. - // - // At the time of writing this comment, this type and the issues it comes with has spread to 6 files (+ 3 test files). - // Luckily, none of that code is public API so we can modify or even remove it without compatibility restrictions. init(externalEntity: LinkResolver.ExternalEntity, bundleIdentifier: DocumentationBundle.Identifier) { - self.entity = externalEntity + self.externalEntity = externalEntity self.bundleIdentifier = bundleIdentifier - self.topicRenderReference = externalEntity.makeTopicRenderReference() } /// The identifier of the external render node. package var identifier: ResolvedTopicReference { ResolvedTopicReference( bundleID: bundleIdentifier, - path: entity.referenceURL.path, - fragment: entity.referenceURL.fragment, - sourceLanguages: entity.availableLanguages + path: externalEntity.topicRenderReference.url, + sourceLanguages: externalEntity.sourceLanguages ) } /// The kind of this documentation node. var kind: RenderNode.Kind { - topicRenderReference.kind + externalEntity.topicRenderReference.kind } /// The symbol kind of this documentation node. /// /// This value is `nil` if the referenced page is not a symbol. var symbolKind: SymbolGraph.Symbol.KindIdentifier? { - DocumentationNode.symbolKind(for: entity.kind) + externalEntity.symbolKind } /// The additional "role" assigned to the symbol, if any /// /// This value is `nil` if the referenced page is not a symbol. var role: String? { - topicRenderReference.role + externalEntity.topicRenderReference.role } /// The variants of the title. var titleVariants: VariantCollection { - topicRenderReference.titleVariants + externalEntity.topicRenderReference.titleVariants } /// The variants of the abbreviated declaration of the symbol to display in navigation. var navigatorTitleVariants: VariantCollection<[DeclarationRenderSection.Token]?> { - topicRenderReference.navigatorTitleVariants + externalEntity.topicRenderReference.navigatorTitleVariants } /// Author provided images that represent this page. var images: [TopicImage] { - entity.topicImages ?? [] + externalEntity.topicRenderReference.images } /// The identifier of the external reference. var externalIdentifier: RenderReferenceIdentifier { - topicRenderReference.identifier + externalEntity.topicRenderReference.identifier } /// List of variants of the same external node for various languages. var variants: [RenderNode.Variant]? { - entity.availableLanguages.map { - RenderNode.Variant(traits: [.interfaceLanguage($0.id)], paths: [topicRenderReference.url]) + externalEntity.sourceLanguages.map { + RenderNode.Variant(traits: [.interfaceLanguage($0.id)], paths: [externalEntity.topicRenderReference.url]) } } @@ -90,16 +83,13 @@ package struct ExternalRenderNode { /// /// This value is `false` if the referenced page is not a symbol. var isBeta: Bool { - topicRenderReference.isBeta + externalEntity.topicRenderReference.isBeta } } /// A language specific representation of an external render node value for building a navigator index. struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { - private var _identifier: ResolvedTopicReference - var identifier: ResolvedTopicReference { - _identifier - } + var identifier: ResolvedTopicReference var kind: RenderNode.Kind var metadata: ExternalRenderNodeMetadataRepresentation @@ -119,7 +109,7 @@ struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { } let traits = trait.map { [$0] } ?? [] - self._identifier = renderNode.identifier.withSourceLanguages([traitLanguage]) + self.identifier = renderNode.identifier.withSourceLanguages(Set(arrayLiteral: traitLanguage)) self.kind = renderNode.kind self.metadata = ExternalRenderNodeMetadataRepresentation( diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift index 27cd2d2a1a..20291f286d 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift @@ -9,6 +9,7 @@ */ import Foundation +public import SymbolKit /// A class that resolves documentation links by orchestrating calls to other link resolver implementations. public class LinkResolver { @@ -41,7 +42,53 @@ public class LinkResolver { /// The minimal information about an external entity necessary to render links to it on another page. @_spi(ExternalLinks) // This isn't stable API yet. - public typealias ExternalEntity = LinkDestinationSummary // Currently we use the same format as DocC outputs for its own pages. That may change depending on what information we need here. + public struct ExternalEntity { + /// Creates a new external entity. + /// - Parameters: + /// - topicRenderReference: The render reference for this external topic. + /// - renderReferenceDependencies: Any dependencies for the render reference. + /// - sourceLanguages: The different source languages for which this page is available. + /// - symbolKind: The kind of symbol that's being referenced. + @_spi(ExternalLinks) + public init( + topicRenderReference: TopicRenderReference, + renderReferenceDependencies: RenderReferenceDependencies, + sourceLanguages: Set, + symbolKind: SymbolGraph.Symbol.KindIdentifier? = nil + ) { + self.topicRenderReference = topicRenderReference + self.renderReferenceDependencies = renderReferenceDependencies + self.sourceLanguages = sourceLanguages + self.symbolKind = symbolKind + } + + /// The render reference for this external topic. + var topicRenderReference: TopicRenderReference + /// Any dependencies for the render reference. + /// + /// For example, if the external content contains links or images, those are included here. + var renderReferenceDependencies: RenderReferenceDependencies + /// The different source languages for which this page is available. + var sourceLanguages: Set + /// The kind of symbol that's being referenced. + /// + /// This value is `nil` if the entity does not reference a symbol. + /// + /// For example, the navigator requires specific knowledge about what type of external symbol is being linked to. + var symbolKind: SymbolGraph.Symbol.KindIdentifier? + + /// Creates a pre-render new topic content value to be added to a render context's reference store. + func topicContent() -> RenderReferenceStore.TopicContent { + return .init( + renderReference: topicRenderReference, + canonicalPath: nil, + taskGroups: nil, + source: nil, + isDocumentationExtensionContent: false, + renderReferenceDependencies: renderReferenceDependencies + ) + } + } /// Attempts to resolve an unresolved reference. /// @@ -221,42 +268,3 @@ private final class FallbackResolverBasedLinkResolver { return nil } } - -extension LinkResolver.ExternalEntity { - /// Creates a pre-render new topic content value to be added to a render context's reference store. - func makeTopicContent() -> RenderReferenceStore.TopicContent { - .init( - renderReference: makeTopicRenderReference(), - canonicalPath: nil, - taskGroups: nil, - source: nil, - isDocumentationExtensionContent: false, - renderReferenceDependencies: makeRenderDependencies() - ) - } - - func makeRenderDependencies() -> RenderReferenceDependencies { - guard let references else { return .init() } - - return .init( - topicReferences: references.compactMap { ($0 as? TopicRenderReference)?.topicReference(languages: availableLanguages) }, - linkReferences: references.compactMap { $0 as? LinkReference }, - imageReferences: references.compactMap { $0 as? ImageReference } - ) - } -} - -private extension TopicRenderReference { - func topicReference(languages: Set) -> ResolvedTopicReference? { - guard let url = URL(string: identifier.identifier), let rawBundleID = url.host else { - return nil - } - return ResolvedTopicReference( - bundleID: .init(rawValue: rawBundleID), - path: url.path, - fragment: url.fragment, - // TopicRenderReference doesn't have language information. Also, the reference's languages _doesn't_ specify the languages of the linked entity. - sourceLanguages: languages - ) - } -} diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 0a53b58c3d..8c6112dbb1 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -201,8 +201,7 @@ public struct LinkDestinationSummary: Codable, Equatable { /// Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. /// /// If the summarized element has an image but the variant doesn't, this property will be `Optional.some(nil)`. - @available(*, deprecated, message: "`TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") - public let topicImages: VariantValue<[TopicImage]?> = nil + public let topicImages: VariantValue<[TopicImage]?> /// Creates a new summary variant with the values that are different from the main summarized values. /// @@ -216,6 +215,7 @@ public struct LinkDestinationSummary: Codable, Equatable { /// - taskGroups: The taskGroups of the variant or `nil` if the taskGroups is the same as the summarized element. /// - usr: The precise symbol identifier of the variant or `nil` if the precise symbol identifier is the same as the summarized element. /// - declarationFragments: The declaration of the variant or `nil` if the declaration is the same as the summarized element. + /// - topicImages: Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. public init( traits: [RenderNode.Variant.Trait], kind: VariantValue = nil, @@ -225,7 +225,8 @@ public struct LinkDestinationSummary: Codable, Equatable { abstract: VariantValue = nil, taskGroups: VariantValue<[LinkDestinationSummary.TaskGroup]?> = nil, usr: VariantValue = nil, - declarationFragments: VariantValue = nil + declarationFragments: VariantValue = nil, + topicImages: VariantValue<[TopicImage]?> = nil ) { self.traits = traits self.kind = kind @@ -236,32 +237,7 @@ public struct LinkDestinationSummary: Codable, Equatable { self.taskGroups = taskGroups self.usr = usr self.declarationFragments = declarationFragments - } - - @available(*, deprecated, renamed: "init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:declarationFragments:)", message: "Use `init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:declarationFragments:)` instead. `TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") - public init( - traits: [RenderNode.Variant.Trait], - kind: VariantValue = nil, - language: VariantValue = nil, - relativePresentationURL: VariantValue = nil, - title: VariantValue = nil, - abstract: VariantValue = nil, - taskGroups: VariantValue<[LinkDestinationSummary.TaskGroup]?> = nil, - usr: VariantValue = nil, - declarationFragments: VariantValue = nil, - topicImages: VariantValue<[TopicImage]?> = nil - ) { - self.init( - traits: traits, - kind: kind, - language: language, - relativePresentationURL: relativePresentationURL, - title: title, - abstract: abstract, - taskGroups: taskGroups, - usr: usr, - declarationFragments: declarationFragments - ) + self.topicImages = topicImages } } @@ -493,7 +469,8 @@ extension LinkDestinationSummary { abstract: nilIfEqual(main: abstract, variant: abstractVariant), taskGroups: nilIfEqual(main: taskGroups, variant: taskGroupVariants[variantTraits]), usr: nil, // The symbol variant uses the same USR - declarationFragments: nilIfEqual(main: declaration, variant: declarationVariant) + declarationFragments: nilIfEqual(main: declaration, variant: declarationVariant), + topicImages: nil // The symbol variant doesn't currently have their own images ) } @@ -601,28 +578,13 @@ extension LinkDestinationSummary { public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - if DocumentationNode.Kind.allKnownValues.contains(kind) { - try container.encode(kind.id, forKey: .kind) - } else { - try container.encode(kind, forKey: .kind) - } + try container.encode(kind.id, forKey: .kind) try container.encode(relativePresentationURL, forKey: .relativePresentationURL) try container.encode(referenceURL, forKey: .referenceURL) try container.encode(title, forKey: .title) try container.encodeIfPresent(abstract, forKey: .abstract) - if SourceLanguage.knownLanguages.contains(language) { - try container.encode(language.id, forKey: .language) - } else { - try container.encode(language, forKey: .language) - } - var languagesContainer = container.nestedUnkeyedContainer(forKey: .availableLanguages) - for language in availableLanguages.sorted() { - if SourceLanguage.knownLanguages.contains(language) { - try languagesContainer.encode(language.id) - } else { - try languagesContainer.encode(language) - } - } + try container.encode(language.id, forKey: .language) + try container.encode(availableLanguages.map { $0.id }, forKey: .availableLanguages) try container.encodeIfPresent(platforms, forKey: .platforms) try container.encodeIfPresent(taskGroups, forKey: .taskGroups) try container.encodeIfPresent(usr, forKey: .usr) @@ -638,47 +600,28 @@ extension LinkDestinationSummary { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - // Kind can either be a known identifier or a full structure - do { - let kindID = try container.decode(String.self, forKey: .kind) - guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { - throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") - } - kind = foundKind - } catch { - kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) + let kindID = try container.decode(String.self, forKey: .kind) + guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { + throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") } + kind = foundKind relativePresentationURL = try container.decode(URL.self, forKey: .relativePresentationURL) referenceURL = try container.decode(URL.self, forKey: .referenceURL) title = try container.decode(String.self, forKey: .title) abstract = try container.decodeIfPresent(Abstract.self, forKey: .abstract) - // Language can either be an identifier of a known language or a full structure - do { - let languageID = try container.decode(String.self, forKey: .language) - guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") - } - language = foundLanguage - } catch DecodingError.typeMismatch { - language = try container.decode(SourceLanguage.self, forKey: .language) - } - - // The set of languages can be a mix of identifiers and full structure - var languagesContainer = try container.nestedUnkeyedContainer(forKey: .availableLanguages) - var decodedLanguages = Set() - while !languagesContainer.isAtEnd { - do { - let languageID = try languagesContainer.decode(String.self) - guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .availableLanguages, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") - } - decodedLanguages.insert( foundLanguage ) - } catch DecodingError.typeMismatch { - decodedLanguages.insert( try languagesContainer.decode(SourceLanguage.self) ) - } + let languageID = try container.decode(String.self, forKey: .language) + guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { + throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") } - availableLanguages = decodedLanguages + language = foundLanguage + let availableLanguageIDs = try container.decode([String].self, forKey: .availableLanguages) + availableLanguages = try Set(availableLanguageIDs.map { languageID in + guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { + throw DecodingError.dataCorruptedError(forKey: .availableLanguages, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + } + return foundLanguage + }) platforms = try container.decodeIfPresent([AvailabilityRenderItem].self, forKey: .platforms) taskGroups = try container.decodeIfPresent([TaskGroup].self, forKey: .taskGroups) usr = try container.decodeIfPresent(String.self, forKey: .usr) @@ -695,7 +638,7 @@ extension LinkDestinationSummary { extension LinkDestinationSummary.Variant { enum CodingKeys: String, CodingKey { - case traits, kind, title, abstract, language, usr, taskGroups + case traits, kind, title, abstract, language, usr, taskGroups, topicImages case relativePresentationURL = "path" case declarationFragments = "fragments" } @@ -703,58 +646,44 @@ extension LinkDestinationSummary.Variant { public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(traits, forKey: .traits) - if let kind { - if DocumentationNode.Kind.allKnownValues.contains(kind) { - try container.encode(kind.id, forKey: .kind) - } else { - try container.encode(kind, forKey: .kind) - } - } + try container.encodeIfPresent(kind?.id, forKey: .kind) try container.encodeIfPresent(relativePresentationURL, forKey: .relativePresentationURL) try container.encodeIfPresent(title, forKey: .title) try container.encodeIfPresent(abstract, forKey: .abstract) - if let language { - if SourceLanguage.knownLanguages.contains(language) { - try container.encode(language.id, forKey: .language) - } else { - try container.encode(language, forKey: .language) - } - } + try container.encodeIfPresent(language?.id, forKey: .language) try container.encodeIfPresent(usr, forKey: .usr) try container.encodeIfPresent(declarationFragments, forKey: .declarationFragments) try container.encodeIfPresent(taskGroups, forKey: .taskGroups) + try container.encodeIfPresent(topicImages, forKey: .topicImages) } public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - traits = try container.decode([RenderNode.Variant.Trait].self, forKey: .traits) - - if container.contains(.kind) { - // The kind can either be a known identifier or a full structure - do { - let kindID = try container.decode(String.self, forKey: .kind) - guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { - throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") - } - kind = foundKind - } catch { - kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) + + let traits = try container.decode([RenderNode.Variant.Trait].self, forKey: .traits) + for case .interfaceLanguage(let languageID) in traits { + guard SourceLanguage.knownLanguages.contains(where: { $0.id == languageID }) else { + throw DecodingError.dataCorruptedError(forKey: .traits, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + } + } + self.traits = traits + + let kindID = try container.decodeIfPresent(String.self, forKey: .kind) + if let kindID { + guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { + throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") } + kind = foundKind } else { kind = nil } - if container.contains(.language) { - // Language can either be an identifier of a known language or a full structure - do { - let languageID = try container.decode(String.self, forKey: .language) - guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") - } - language = foundLanguage - } catch DecodingError.typeMismatch { - language = try container.decode(SourceLanguage.self, forKey: .language) + let languageID = try container.decodeIfPresent(String.self, forKey: .language) + if let languageID { + guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { + throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") } + language = foundLanguage } else { language = nil } @@ -764,6 +693,7 @@ extension LinkDestinationSummary.Variant { usr = try container.decodeIfPresent(String?.self, forKey: .usr) declarationFragments = try container.decodeIfPresent(LinkDestinationSummary.DeclarationFragments?.self, forKey: .declarationFragments) taskGroups = try container.decodeIfPresent([LinkDestinationSummary.TaskGroup]?.self, forKey: .taskGroups) + topicImages = try container.decodeIfPresent([TopicImage]?.self, forKey: .topicImages) } } diff --git a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift index 465a58aa8a..2ad9f43a8b 100644 --- a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift +++ b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift @@ -309,13 +309,11 @@ public class DocumentationContentRenderer { // try resolving that way as a fallback after looking up `documentationCache`. titleVariants = .init(defaultVariantValue: topicGraphOnlyNode.title) } else if let external = documentationContext.externalCache[reference] { - let renderDependencies = external.makeRenderDependencies() + dependencies.topicReferences.append(contentsOf: external.renderReferenceDependencies.topicReferences) + dependencies.linkReferences.append(contentsOf: external.renderReferenceDependencies.linkReferences) + dependencies.imageReferences.append(contentsOf: external.renderReferenceDependencies.imageReferences) - dependencies.topicReferences.append(contentsOf: renderDependencies.topicReferences) - dependencies.linkReferences.append(contentsOf: renderDependencies.linkReferences) - dependencies.imageReferences.append(contentsOf: renderDependencies.imageReferences) - - return external.makeTopicRenderReference() + return external.topicRenderReference } else { titleVariants = .init(defaultVariantValue: reference.absoluteString) } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift b/Sources/SwiftDocC/Model/Rendering/RenderContext.swift index 26a299c100..dade49b7a0 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContext.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -90,24 +90,7 @@ public struct RenderContext { // Add all the external content to the topic store for (reference, entity) in documentationContext.externalCache { - topics[reference] = entity.makeTopicContent() - - // Also include transitive dependencies in the store, so that the external entity can reference them. - for case let dependency as TopicRenderReference in (entity.references ?? []) { - guard let url = URL(string: dependency.identifier.identifier), let rawBundleID = url.host else { - // This dependency doesn't have a valid topic reference, skip adding it to the render context. - continue - } - - let dependencyReference = ResolvedTopicReference( - bundleID: .init(rawValue: rawBundleID), - path: url.path, - fragment: url.fragment, - // TopicRenderReference doesn't have language information. Also, the reference's languages _doesn't_ specify the languages of the linked entity. - sourceLanguages: reference.sourceLanguages - ) - topics[dependencyReference] = .init(renderReference: dependency, canonicalPath: nil, taskGroups: nil, source: nil, isDocumentationExtensionContent: false) - } + topics[reference] = entity.topicContent() } self.store = RenderReferenceStore(topics: topics, assets: assets) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index aad67d6b85..1fd747b15a 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -1478,7 +1478,7 @@ public struct RenderNodeTranslator: SemanticVisitor { } } else if let entity = context.externalCache[resolved] { collectedTopicReferences.append(resolved) - destinationsMap[destination] = entity.title + destinationsMap[destination] = entity.topicRenderReference.title } else { fatalError("A successfully resolved reference should have either local or external content.") } diff --git a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md index a2330698d4..5155029943 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md +++ b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md @@ -12,6 +12,7 @@ Run static analysis checks on markup files. ### Predefined Checks +- ``AbstractContainsFormattedTextOnly`` - ``DuplicateTopicsSections`` - ``InvalidAdditionalTitle`` - ``MissingAbstract`` @@ -19,4 +20,4 @@ Run static analysis checks on markup files. - ``NonOverviewHeadingChecker`` - ``SeeAlsoInTopicsHeadingChecker`` - + diff --git a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift index ee391de385..4e87138288 100644 --- a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift +++ b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift @@ -22,13 +22,17 @@ class ExternalTopicsGraphHashTests: XCTestCase { func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { let reference = ResolvedTopicReference(bundleID: "com.test.symbols", path: "/\(preciseIdentifier)", sourceLanguage: SourceLanguage.swift) let entity = LinkResolver.ExternalEntity( - kind: .class, - language: .swift, - relativePresentationURL: URL(string: "/\(preciseIdentifier)")!, - referenceURL: reference.url, - title: preciseIdentifier, - availableLanguages: [.swift], - variants: [] + topicRenderReference: TopicRenderReference( + identifier: .init(preciseIdentifier), + title: preciseIdentifier, + abstract: [], + url: "/" + preciseIdentifier, + kind: .symbol, + estimatedTime: nil + ), + renderReferenceDependencies: .init(), + sourceLanguages: [.swift], + symbolKind: .class ) return (reference, entity) } diff --git a/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift index 958f31c3db..6add5371de 100644 --- a/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift +++ b/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -12,9 +12,6 @@ import XCTest @testable import SwiftDocC import Markdown -// This tests `AbstractContainsFormattedTextOnly` which are deprecated. -// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. -@available(*, deprecated) class AbstractContainsFormattedTextOnlyTests: XCTestCase { var checker = AbstractContainsFormattedTextOnly(sourceFile: nil) diff --git a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift index a46afb677e..2550a52345 100644 --- a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift @@ -532,9 +532,6 @@ class ConvertServiceTests: XCTestCase { } } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testConvertPageWithLinkResolvingAndKnownPathComponents() throws { let symbolGraphFile = Bundle.module.url( forResource: "mykit-one-symbol", @@ -830,9 +827,7 @@ class ConvertServiceTests: XCTestCase { ) } } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) + func testConvertTutorialWithCode() throws { let tutorialContent = """ @Tutorial(time: 99) { @@ -1003,9 +998,6 @@ class ConvertServiceTests: XCTestCase { } } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testConvertArticleWithImageReferencesAndDetailedGridLinks() throws { let articleData = try XCTUnwrap(""" # First article @@ -1726,9 +1718,6 @@ class ConvertServiceTests: XCTestCase { #endif } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testConvertPageWithLinkResolving() throws { let symbolGraphFile = Bundle.module.url( forResource: "mykit-one-symbol", @@ -2018,9 +2007,6 @@ class ConvertServiceTests: XCTestCase { } } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testConvertTopLevelSymbolWithLinkResolving() throws { let symbolGraphFile = Bundle.module.url( forResource: "one-symbol-top-level", @@ -2128,9 +2114,6 @@ class ConvertServiceTests: XCTestCase { } } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testOrderOfLinkResolutionRequestsForDocLink() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( @@ -2169,9 +2152,6 @@ class ConvertServiceTests: XCTestCase { XCTAssertEqual(expectedLinkResolutionRequests, receivedLinkResolutionRequests) } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testOrderOfLinkResolutionRequestsForDeeplyNestedSymbol() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( @@ -2211,9 +2191,6 @@ class ConvertServiceTests: XCTestCase { XCTAssertEqual(expectedLinkResolutionRequests, receivedLinkResolutionRequests) } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testOrderOfLinkResolutionRequestsForSymbolLink() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( @@ -2249,10 +2226,7 @@ class ConvertServiceTests: XCTestCase { XCTAssertEqual(expectedLinkResolutionRequests, receivedLinkResolutionRequests) } - // This test helper uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) - private func linkResolutionRequestsForConvertRequest(_ request: ConvertRequest) throws -> [String] { + func linkResolutionRequestsForConvertRequest(_ request: ConvertRequest) throws -> [String] { var receivedLinkResolutionRequests = [String]() let mockLinkResolvingService = LinkResolvingService { message in do { @@ -2340,9 +2314,6 @@ class ConvertServiceTests: XCTestCase { } } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testDoesNotResolveLinksUnlessBundleIDMatches() throws { let tempURL = try createTempFolder(content: [ Folder(name: "unit-test.docc", content: [ diff --git a/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift b/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift index 9d2fcf89b1..6f4ddc2c61 100644 --- a/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -56,9 +56,6 @@ class DocumentationServer_DefaultTests: XCTestCase { wait(for: [expectation], timeout: 1.0) } - // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. - // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. - @available(*, deprecated) func testQueriesLinkResolutionServer() throws { let symbolGraphFile = Bundle.module.url( forResource: "mykit-one-symbol", diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index d0602e48ca..4ca7dc631e 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -11,7 +11,6 @@ import Foundation import XCTest @_spi(ExternalLinks) @testable import SwiftDocC -import SwiftDocCTestUtilities class ExternalRenderNodeTests: XCTestCase { private func generateExternalResolver() -> TestMultiResultExternalReferenceResolver { @@ -57,6 +56,7 @@ class ExternalRenderNodeTests: XCTestCase { } func testExternalRenderNode() async throws { + let externalResolver = generateExternalResolver() let (_, bundle, context) = try await testBundleAndContext( copying: "MixedLanguageFramework", @@ -79,33 +79,37 @@ class ExternalRenderNodeTests: XCTestCase { try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) } - let externalRenderNodes = context.externalCache.valuesByReference.values.map { - ExternalRenderNode(externalEntity: $0, bundleIdentifier: bundle.id) - }.sorted(by: \.titleVariants.defaultValue) + var externalRenderNodes = [ExternalRenderNode]() + for externalLink in context.externalCache { + externalRenderNodes.append( + ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + ) + } + externalRenderNodes.sort(by: \.titleVariants.defaultValue) XCTAssertEqual(externalRenderNodes.count, 4) - XCTAssertEqual(externalRenderNodes[0].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/objCArticle") + XCTAssertEqual(externalRenderNodes[0].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCArticle") XCTAssertEqual(externalRenderNodes[0].kind, .article) XCTAssertEqual(externalRenderNodes[0].symbolKind, nil) XCTAssertEqual(externalRenderNodes[0].role, "article") XCTAssertEqual(externalRenderNodes[0].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCArticle") XCTAssertTrue(externalRenderNodes[0].isBeta) - XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/objCSymbol") + XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCSymbol") XCTAssertEqual(externalRenderNodes[1].kind, .symbol) XCTAssertEqual(externalRenderNodes[1].symbolKind, .func) XCTAssertEqual(externalRenderNodes[1].role, "symbol") XCTAssertEqual(externalRenderNodes[1].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCSymbol") XCTAssertFalse(externalRenderNodes[1].isBeta) - XCTAssertEqual(externalRenderNodes[2].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/swiftArticle") + XCTAssertEqual(externalRenderNodes[2].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftArticle") XCTAssertEqual(externalRenderNodes[2].kind, .article) XCTAssertEqual(externalRenderNodes[2].symbolKind, nil) XCTAssertEqual(externalRenderNodes[2].role, "article") XCTAssertEqual(externalRenderNodes[2].externalIdentifier.identifier, "doc://com.test.external/path/to/external/swiftArticle") XCTAssertFalse(externalRenderNodes[2].isBeta) - XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/swiftSymbol") + XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftSymbol") XCTAssertEqual(externalRenderNodes[3].kind, .symbol) XCTAssertEqual(externalRenderNodes[3].symbolKind, .class) XCTAssertEqual(externalRenderNodes[3].role, "symbol") @@ -114,34 +118,33 @@ class ExternalRenderNodeTests: XCTestCase { } func testExternalRenderNodeVariantRepresentation() throws { - let reference = ResolvedTopicReference(bundleID: "com.test.external", path: "/path/to/external/symbol", sourceLanguages: [.swift, .objectiveC]) + let renderReferenceIdentifier = RenderReferenceIdentifier(forExternalLink: "doc://com.test.external/path/to/external/symbol") // Variants for the title let swiftTitle = "Swift Symbol" - let objcTitle = "Objective-C Symbol" + let occTitle = "Occ Symbol" + + // Variants for the navigator title + let navigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "symbol", kind: .identifier)] + let occNavigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "occ_symbol", kind: .identifier)] // Variants for the fragments - let swiftFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] - let objcFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] + let fragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] + let occFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] let externalEntity = LinkResolver.ExternalEntity( - kind: .function, - language: .swift, - relativePresentationURL: URL(string: "/example/path/to/external/symbol")!, - referenceURL: reference.url, - title: swiftTitle, - availableLanguages: [.swift, .objectiveC], - usr: "some-unique-symbol-id", - declarationFragments: swiftFragments, - variants: [ - .init( - traits: [.interfaceLanguage(SourceLanguage.objectiveC.id)], - language: .objectiveC, - title: objcTitle, - declarationFragments: objcFragments - ) - ] - ) + topicRenderReference: .init( + identifier: renderReferenceIdentifier, + titleVariants: .init(defaultValue: swiftTitle, objectiveCValue: occTitle), + abstractVariants: .init(defaultValue: []), + url: "/example/path/to/external/symbol", + kind: .symbol, + fragmentsVariants: .init(defaultValue: fragments, objectiveCValue: occFragments), + navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle) + ), + renderReferenceDependencies: .init(), + sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")], + symbolKind: .func) let externalRenderNode = ExternalRenderNode( externalEntity: externalEntity, bundleIdentifier: "com.test.external" @@ -151,52 +154,39 @@ class ExternalRenderNodeTests: XCTestCase { NavigatorExternalRenderNode(renderNode: externalRenderNode) ) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) + XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) XCTAssertFalse(swiftNavigatorExternalRenderNode.metadata.isBeta) let objcNavigatorExternalRenderNode = try XCTUnwrap( - NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage(SourceLanguage.objectiveC.id)) + NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) ) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, objcTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) XCTAssertFalse(objcNavigatorExternalRenderNode.metadata.isBeta) } func testNavigatorWithExternalNodes() async throws { - let catalog = Folder(name: "ModuleName.docc", content: [ - Folder(name: "swift", content: [ - JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ - makeSymbol(id: "some-symbol-id", language: .swift, kind: .class, pathComponents: ["SomeClass"]) - ])) - ]), - Folder(name: "clang", content: [ - JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ - makeSymbol(id: "some-symbol-id", language: .objectiveC, kind: .class, pathComponents: ["TLASomeClass"]) - ])) - ]), - - InfoPlist(identifier: "some.custom.identifier"), - - TextFile(name: "ModuleName.md", utf8Content: """ - # ``ModuleName`` - - Curate a few external language-specific symbols and articles + let externalResolver = generateExternalResolver() + let (_, bundle, context) = try await testBundleAndContext( + copying: "MixedLanguageFramework", + externalResolvers: [externalResolver.bundleID: externalResolver] + ) { url in + let mixedLanguageFrameworkExtension = """ + # ``MixedLanguageFramework`` + + This symbol has a Swift and Objective-C variant. - ## Topics + ## Topics - ### External Reference + ### External Reference - - - - - - - - - """), - ]) - - var configuration = DocumentationContext.Configuration() - let externalResolver = generateExternalResolver() - configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver - let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) - XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") - + - + - + - + - + """ + try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) + } let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() @@ -215,63 +205,53 @@ class ExternalRenderNodeTests: XCTestCase { let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) // Verify that there are no uncurated external links at the top level - XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.count(where: \.isExternal), 0) - XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.count(where: \.isExternal), 0) + let swiftTopLevelExternalNodes = renderIndex.interfaceLanguages["swift"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let occTopLevelExternalNodes = renderIndex.interfaceLanguages["occ"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + XCTAssertEqual(swiftTopLevelExternalNodes.count, 0) + XCTAssertEqual(occTopLevelExternalNodes.count, 0) // Verify that the curated external links are part of the index. - let swiftExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) - let objcExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) + let swiftExternalNodes = renderIndex.interfaceLanguages["swift"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let occExternalNodes = renderIndex.interfaceLanguages["occ"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] XCTAssertEqual(swiftExternalNodes.count, 2) - XCTAssertEqual(objcExternalNodes.count, 2) + XCTAssertEqual(occExternalNodes.count, 2) XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle", "SwiftSymbol"]) - XCTAssertEqual(objcExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) - XCTAssert(swiftExternalNodes.first?.isBeta == false) - XCTAssert(swiftExternalNodes.last?.isBeta == true) - XCTAssert(objcExternalNodes.first?.isBeta == true) - XCTAssert(objcExternalNodes.last?.isBeta == false) + XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) + XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) + XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) + XCTAssert(swiftExternalNodes.first { $0.title == "SwiftArticle" }?.isBeta == false) + XCTAssert(swiftExternalNodes.first { $0.title == "SwiftSymbol" }?.isBeta == true) + XCTAssert(occExternalNodes.first { $0.title == "ObjCArticle" }?.isBeta == true) + XCTAssert(occExternalNodes.first { $0.title == "ObjCSymbol" }?.isBeta == false) XCTAssertEqual(swiftExternalNodes.map(\.type), ["article", "class"]) - XCTAssertEqual(objcExternalNodes.map(\.type), ["article", "func"]) + XCTAssertEqual(occExternalNodes.map(\.type), ["article", "func"]) } func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() async throws { - let catalog = Folder(name: "ModuleName.docc", content: [ - Folder(name: "swift", content: [ - JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ - makeSymbol(id: "some-symbol-id", language: .swift, kind: .class, pathComponents: ["SomeClass"]) - ])) - ]), - Folder(name: "clang", content: [ - JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ - makeSymbol(id: "some-symbol-id", language: .objectiveC, kind: .class, pathComponents: ["TLASomeClass"]) - ])) - ]), - - InfoPlist(identifier: "some.custom.identifier"), - - TextFile(name: "ModuleName.md", utf8Content: """ - # ``ModuleName`` - - Curate and link to a few external language-specific symbols and articles - - It also has an external reference which is not curated in the Topics section: - - - - ## Topics - - ### External Reference - - - - - - """), - ]) - - var configuration = DocumentationContext.Configuration() let externalResolver = generateExternalResolver() - configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver - let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) - XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") + let (_, bundle, context) = try await testBundleAndContext( + copying: "MixedLanguageFramework", + externalResolvers: [externalResolver.bundleID: externalResolver] + ) { url in + let mixedLanguageFrameworkExtension = """ + # ``MixedLanguageFramework`` + + This symbol has a Swift and Objective-C variant. + + It also has an external reference which is not curated in the Topics section: + + + + ## Topics + + ### External Reference + + - + - + """ + try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) + } let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() @@ -288,52 +268,55 @@ class ExternalRenderNodeTests: XCTestCase { } builder.finalize() let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) + // Verify that there are no uncurated external links at the top level - XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.count(where: \.isExternal), 0) - XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.count(where: \.isExternal), 0) + let swiftTopLevelExternalNodes = renderIndex.interfaceLanguages["swift"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let occTopLevelExternalNodes = renderIndex.interfaceLanguages["occ"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + XCTAssertEqual(swiftTopLevelExternalNodes.count, 0) + XCTAssertEqual(occTopLevelExternalNodes.count, 0) // Verify that the curated external links are part of the index. - let swiftExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) - let objcExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) + let swiftExternalNodes = renderIndex.interfaceLanguages["swift"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let occExternalNodes = renderIndex.interfaceLanguages["occ"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] XCTAssertEqual(swiftExternalNodes.count, 1) - XCTAssertEqual(objcExternalNodes.count, 1) + XCTAssertEqual(occExternalNodes.count, 1) XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle"]) - XCTAssertEqual(objcExternalNodes.map(\.title), ["ObjCSymbol"]) + XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCSymbol"]) + XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) + XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) XCTAssertEqual(swiftExternalNodes.map(\.type), ["article"]) - XCTAssertEqual(objcExternalNodes.map(\.type), ["func"]) + XCTAssertEqual(occExternalNodes.map(\.type), ["func"]) } func testExternalRenderNodeVariantRepresentationWhenIsBeta() throws { - let reference = ResolvedTopicReference(bundleID: "com.test.external", path: "/path/to/external/symbol", sourceLanguages: [.swift, .objectiveC]) + let renderReferenceIdentifier = RenderReferenceIdentifier(forExternalLink: "doc://com.test.external/path/to/external/symbol") // Variants for the title let swiftTitle = "Swift Symbol" - let objcTitle = "Objective-C Symbol" + let occTitle = "Occ Symbol" + + // Variants for the navigator title + let navigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "symbol", kind: .identifier)] + let occNavigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "occ_symbol", kind: .identifier)] // Variants for the fragments - let swiftFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] - let objcFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] + let fragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] + let occFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] let externalEntity = LinkResolver.ExternalEntity( - kind: .function, - language: .swift, - relativePresentationURL: URL(string: "/example/path/to/external/symbol")!, - referenceURL: reference.url, - title: swiftTitle, - availableLanguages: [.swift, .objectiveC], - platforms: [.init(name: "Platform name", introduced: "1.2.3", isBeta: true)], - usr: "some-unique-symbol-id", - declarationFragments: swiftFragments, - variants: [ - .init( - traits: [.interfaceLanguage(SourceLanguage.objectiveC.id)], - language: .objectiveC, - title: objcTitle, - declarationFragments: objcFragments - ) - ] - ) + topicRenderReference: .init( + identifier: renderReferenceIdentifier, + titleVariants: .init(defaultValue: swiftTitle, objectiveCValue: occTitle), + abstractVariants: .init(defaultValue: []), + url: "/example/path/to/external/symbol", + kind: .symbol, + fragmentsVariants: .init(defaultValue: fragments, objectiveCValue: occFragments), + navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle), + isBeta: true + ), + renderReferenceDependencies: .init(), + sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")]) let externalRenderNode = ExternalRenderNode( externalEntity: externalEntity, bundleIdentifier: "com.test.external" @@ -343,12 +326,14 @@ class ExternalRenderNodeTests: XCTestCase { NavigatorExternalRenderNode(renderNode: externalRenderNode) ) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) + XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) XCTAssertTrue(swiftNavigatorExternalRenderNode.metadata.isBeta) let objcNavigatorExternalRenderNode = try XCTUnwrap( - NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage(SourceLanguage.objectiveC.id)) + NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) ) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, objcTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) XCTAssertTrue(objcNavigatorExternalRenderNode.metadata.isBeta) } } diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift index 9a62ddf51e..7af1fa06eb 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift @@ -950,7 +950,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { switch result { case .success(let resolved): let entity = externalResolver.entity(resolved) - XCTAssertEqual(entity.makeTopicRenderReference().isBeta, isBeta, file: file, line: line) + XCTAssertEqual(entity.topicRenderReference.isBeta, isBeta, file: file, line: line) case .failure(_, let errorInfo): XCTFail("Unexpectedly failed to resolve \(label) link: \(errorInfo.message) \(errorInfo.solutions.map(\.summary).joined(separator: ", "))", file: file, line: line) } diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 6e2526397e..13b47f6743 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -38,15 +38,22 @@ class ExternalReferenceResolverTests: XCTestCase { fatalError("It is a programming mistake to retrieve an entity for a reference that the external resolver didn't resolve.") } + let (kind, role) = DocumentationContentRenderer.renderKindAndRole(resolvedEntityKind, semantic: nil) return LinkResolver.ExternalEntity( - kind: resolvedEntityKind, - language: resolvedEntityLanguage, - relativePresentationURL: URL(string: "/example" + reference.path + (reference.fragment.map { "#\($0)" } ?? ""))!, - referenceURL: reference.url, - title: resolvedEntityTitle, - availableLanguages: [resolvedEntityLanguage], - declarationFragments: resolvedEntityDeclarationFragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, - variants: [] + topicRenderReference: TopicRenderReference( + identifier: .init(reference.absoluteString), + title: resolvedEntityTitle, + abstract: [.text("Externally Resolved Markup Content")], + url: "/example" + reference.path + (reference.fragment.map { "#\($0)" } ?? ""), + kind: kind, + role: role, + fragments: resolvedEntityDeclarationFragments?.declarationFragments.map { fragment in + return DeclarationRenderSection.Token(fragment: fragment, identifier: nil) + } + ), + renderReferenceDependencies: RenderReferenceDependencies(), + sourceLanguages: [resolvedEntityLanguage], + symbolKind: nil ) } } @@ -438,7 +445,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(firstExternalRenderReference.identifier.identifier, "doc://com.test.external/path/to/external-page-with-topic-image-1") XCTAssertEqual(firstExternalRenderReference.title, "First external page with topic image") - XCTAssertEqual(firstExternalRenderReference.url, "/path/to/external-page-with-topic-image-1") + XCTAssertEqual(firstExternalRenderReference.url, "/example/path/to/external-page-with-topic-image-1") XCTAssertEqual(firstExternalRenderReference.kind, .article) XCTAssertEqual(firstExternalRenderReference.images, [ @@ -450,7 +457,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(secondExternalRenderReference.identifier.identifier, "doc://com.test.external/path/to/external-page-with-topic-image-2") XCTAssertEqual(secondExternalRenderReference.title, "Second external page with topic image") - XCTAssertEqual(secondExternalRenderReference.url, "/path/to/external-page-with-topic-image-2") + XCTAssertEqual(secondExternalRenderReference.url, "/example/path/to/external-page-with-topic-image-2") XCTAssertEqual(secondExternalRenderReference.kind, .article) XCTAssertEqual(secondExternalRenderReference.images, [ @@ -624,15 +631,19 @@ class ExternalReferenceResolverTests: XCTestCase { func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { referencesCreatingEntityFor.insert(reference) - // Return an "empty" node + // Return an empty node return .init( - kind: .instanceProperty, - language: .swift, - relativePresentationURL: reference.url.withoutHostAndPortAndScheme(), - referenceURL: reference.url, - title: "Resolved", - availableLanguages: [.swift], - variants: [] + topicRenderReference: TopicRenderReference( + identifier: .init(reference.absoluteString), + title: "Resolved", + abstract: [], + url: reference.absoluteString, + kind: .symbol, + estimatedTime: nil + ), + renderReferenceDependencies: RenderReferenceDependencies(), + sourceLanguages: [.swift], + symbolKind: .property ) } } diff --git a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift index 7ea89aa72c..c875b07f60 100644 --- a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift +++ b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift @@ -53,7 +53,7 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { let entity = entityInfo(path: path) return .success( - ResolvedTopicReference(bundleID: bundleID, path: entity.referencePath, fragment: entity.fragment, sourceLanguage: entity.language) + ResolvedTopicReference(bundleID: bundleID, path: entity.referencePath,fragment: entity.fragment,sourceLanguage: entity.language) ) } } @@ -84,19 +84,34 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { } private func makeNode(for entityInfo: EntityInfo, reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - LinkResolver.ExternalEntity( - kind: entityInfo.kind, - language: entityInfo.language, - relativePresentationURL: reference.url.withoutHostAndPortAndScheme(), - referenceURL: reference.url, - title: entityInfo.title, - availableLanguages: [entityInfo.language], - platforms: entityInfo.platforms, - topicImages: entityInfo.topicImages?.map(\.0), - references: entityInfo.topicImages?.map { topicImage, altText in - ImageReference(identifier: topicImage.identifier, altText: altText, imageAsset: assetsToReturn[topicImage.identifier.identifier] ?? .init()) - }, - variants: [] + let (kind, role) = DocumentationContentRenderer.renderKindAndRole(entityInfo.kind, semantic: nil) + + let dependencies: RenderReferenceDependencies + if let topicImages = entityInfo.topicImages { + dependencies = .init(imageReferences: topicImages.map { topicImage, altText in + return ImageReference(identifier: topicImage.identifier, altText: altText, imageAsset: assetsToReturn[topicImage.identifier.identifier] ?? .init()) + }) + } else { + dependencies = .init() + } + + return LinkResolver.ExternalEntity( + topicRenderReference: TopicRenderReference( + identifier: .init(reference.absoluteString), + title: entityInfo.title, + abstract: [.text(entityInfo.abstract.format())], + url: "/example" + reference.path, + kind: kind, + role: role, + fragments: entityInfo.declarationFragments?.declarationFragments.map { fragment in + return DeclarationRenderSection.Token(fragment: fragment, identifier: nil) + }, + isBeta: entityInfo.platforms?.allSatisfy({$0.isBeta == true}) ?? false, + images: entityInfo.topicImages?.map(\.0) ?? [] + ), + renderReferenceDependencies: dependencies, + sourceLanguages: [entityInfo.language], + symbolKind: DocumentationNode.symbolKind(for: entityInfo.kind) ) } } diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index a51ef8b102..8d1752b75b 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -13,10 +13,12 @@ import SymbolKit @testable import SwiftDocC import SwiftDocCTestUtilities -class LinkDestinationSummaryTests: XCTestCase { +class ExternalLinkableTests: XCTestCase { - func testSummaryOfTutorialPage() async throws { - let catalogHierarchy = Folder(name: "unit-test.docc", content: [ + // Write example documentation bundle with a minimal Tutorials page + let catalogHierarchy = Folder(name: "unit-test.docc", content: [ + Folder(name: "Symbols", content: []), + Folder(name: "Resources", content: [ TextFile(name: "TechnologyX.tutorial", utf8Content: """ @Tutorials(name: "TechnologyX") { @Intro(title: "Technology X") { @@ -87,9 +89,11 @@ class LinkDestinationSummaryTests: XCTestCase { } } """), - InfoPlist(displayName: "TestBundle", identifier: "com.test.example") - ]) - + ]), + InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), + ]) + + func testSummaryOfTutorialPage() async throws { let (bundle, context) = try await loadBundle(catalog: catalogHierarchy) let converter = DocumentationNodeConverter(bundle: bundle, context: context) @@ -482,6 +486,7 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(variant.usr, nil) XCTAssertEqual(variant.kind, nil) XCTAssertEqual(variant.taskGroups, nil) + XCTAssertEqual(variant.topicImages, nil) let encoded = try JSONEncoder().encode(summary) let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded) @@ -572,6 +577,7 @@ class LinkDestinationSummaryTests: XCTestCase { ) ] ) + XCTAssertEqual(variant.topicImages, nil) let encoded = try JSONEncoder().encode(summary) let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded) @@ -579,63 +585,6 @@ class LinkDestinationSummaryTests: XCTestCase { } } - func testDecodingUnknownKindAndLanguage() throws { - let json = """ - { - "kind" : { - "id" : "kind-id", - "name" : "Kind name", - "isSymbol" : false - }, - "language" : { - "id" : "language-id", - "name" : "Language name", - "idAliases" : [ - "language-alias-id" - ], - "linkDisambiguationID" : "language-id" - }, - "availableLanguages" : [ - "swift", - "data", - { - "id" : "language-id", - "idAliases" : [ - "language-alias-id" - ], - "linkDisambiguationID" : "language-id", - "name" : "Language name" - }, - { - "id" : "language-id-2", - "linkDisambiguationID" : "language-id-2", - "name" : "Other language name" - }, - "occ" - ], - "title" : "Something", - "path" : "/documentation/something", - "referenceURL" : "/documentation/something" - } - """ - - let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: Data(json.utf8)) - try assertRoundTripCoding(decoded) - - XCTAssertEqual(decoded.kind, DocumentationNode.Kind(name: "Kind name", id: "kind-id", isSymbol: false)) - XCTAssertEqual(decoded.language, SourceLanguage(name: "Language name", id: "language-id", idAliases: ["language-alias-id"])) - XCTAssertEqual(decoded.availableLanguages, [ - // Known languages - .swift, - .objectiveC, - .data, - - // Custom languages - SourceLanguage(name: "Language name", id: "language-id", idAliases: ["language-alias-id"]), - SourceLanguage(name: "Other language name", id: "language-id-2"), - ]) - } - func testDecodingLegacyData() throws { let legacyData = """ { diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 2a16c9db87..355ae1c327 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -1178,13 +1178,18 @@ class SemaToRenderNodeTests: XCTestCase { let reference = ResolvedTopicReference(bundleID: "com.test.external.symbols", path: "/\(preciseIdentifier)", sourceLanguage: .objectiveC) let entity = LinkResolver.ExternalEntity( - kind: .class, - language: .objectiveC, - relativePresentationURL: URL(string: "/documentation/FrameworkName/path/to/symbol/\(preciseIdentifier)")!, - referenceURL: reference.url, - title: "SymbolName ( \(preciseIdentifier) )", - availableLanguages: [.objectiveC], - variants: [] + topicRenderReference: TopicRenderReference( + identifier: .init(reference.absoluteString), + title: "SymbolName ( \(preciseIdentifier) )", + abstract: [], + url: "/documentation/FrameworkName/path/to/symbol/\(preciseIdentifier)", + kind: .symbol, + role: "ExternalResolvedSymbolRoleHeading", + estimatedTime: nil + ), + renderReferenceDependencies: .init(), + sourceLanguages: [.objectiveC], + symbolKind: .class ) return (reference, entity) } @@ -1202,15 +1207,20 @@ class SemaToRenderNodeTests: XCTestCase { } func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - LinkResolver.ExternalEntity( - kind: .collection, - language: .swift, - relativePresentationURL: reference.url.withoutHostAndPortAndScheme(), - referenceURL: reference.url, - title: "Title for \(reference.url.path)", - abstract: [.text("Abstract for \(reference.url.path)")], - availableLanguages: [.swift], - variants: [] + let (kind, role) = DocumentationContentRenderer.renderKindAndRole(.collection, semantic: nil) + return LinkResolver.ExternalEntity( + topicRenderReference: TopicRenderReference( + identifier: .init(reference.absoluteString), + title: "Title for \(reference.url.path)", + abstract: [.text("Abstract for \(reference.url.path)")], + url: reference.url.path, + kind: kind, + role: role, + estimatedTime: nil + ), + renderReferenceDependencies: .init(), + sourceLanguages: [.swift], + symbolKind: nil ) } } diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift deleted file mode 100644 index dcd51bf89e..0000000000 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift +++ /dev/null @@ -1,680 +0,0 @@ -/* - 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 XCTest -import Foundation -import SymbolKit -@_spi(ExternalLinks) @testable import SwiftDocC -import SwiftDocCTestUtilities - -#if os(macOS) -class OutOfProcessReferenceResolverV2Tests: XCTestCase { - - func testInitializationProcess() throws { - let temporaryFolder = try createTemporaryDirectory() - - let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") - // When the executable file doesn't exist - XCTAssertFalse(FileManager.default.fileExists(atPath: executableLocation.path)) - XCTAssertThrowsError(try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }), - "There should be a validation error if the executable file doesn't exist") - - // When the file isn't executable - try "".write(to: executableLocation, atomically: true, encoding: .utf8) - XCTAssertFalse(FileManager.default.isExecutableFile(atPath: executableLocation.path)) - XCTAssertThrowsError(try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }), - "There should be a validation error if the file isn't executable") - - // When the file isn't executable - try """ - #!/bin/bash - echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities - read # Wait for docc to send a request - """.write(to: executableLocation, atomically: true, encoding: .utf8) - - // `0o0700` is `-rwx------` (read, write, & execute only for owner) - try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) - XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) - - let resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { errorMessage in - XCTFail("No error output is expected for this test executable. Got:\n\(errorMessage)") - }) - XCTAssertEqual(resolver.bundleID, "com.test.bundle") - } - - private func makeTestSummary() -> (summary: LinkDestinationSummary, imageReference: RenderReferenceIdentifier, imageURLs: (light: URL, dark: URL)) { - let linkedReference = RenderReferenceIdentifier("doc://com.test.bundle/something-else") - let linkedImage = RenderReferenceIdentifier("some-image-identifier") - let linkedVariantReference = RenderReferenceIdentifier("doc://com.test.bundle/something-else-2") - - func cardImages(name: String) -> (light: URL, dark: URL) { - ( URL(string: "https://example.com/path/to/\(name)@2x.png")!, - URL(string: "https://example.com/path/to/\(name)~dark@2x.png")! ) - } - - let imageURLs = cardImages(name: "some-image") - - let summary = LinkDestinationSummary( - kind: .structure, - language: .swift, // This is Swift to account for what is considered a symbol's "first" variant value (rdar://86580516), - relativePresentationURL: URL(string: "/path/so/something")!, - referenceURL: URL(string: "doc://com.test.bundle/something")!, - title: "Resolved Title", - abstract: [ - .text("Resolved abstract with "), - .emphasis(inlineContent: [.text("formatted")]), - .text(" "), - .strong(inlineContent: [.text("formatted")]), - .text(" and a link: "), - .reference(identifier: linkedReference, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) - ], - availableLanguages: [ - .swift, - .init(name: "Language Name 2", id: "com.test.another-language.id"), - .objectiveC, - ], - platforms: [ - .init(name: "firstOS", introduced: "1.2.3", isBeta: false), - .init(name: "secondOS", introduced: "4.5.6", isBeta: false), - ], - usr: "some-unique-symbol-id", - declarationFragments: .init([ - .init(text: "struct", kind: .keyword, preciseIdentifier: nil), - .init(text: " ", kind: .text, preciseIdentifier: nil), - .init(text: "declaration fragment", kind: .identifier, preciseIdentifier: nil), - ]), - topicImages: [ - .init(pageImagePurpose: .card, identifier: linkedImage) - ], - references: [ - TopicRenderReference(identifier: linkedReference, title: "Something Else", abstract: [.text("Some other page")], url: "/path/to/something-else", kind: .symbol), - TopicRenderReference(identifier: linkedVariantReference, title: "Another Page", abstract: [.text("Yet another page")], url: "/path/to/something-else-2", kind: .article), - - ImageReference( - identifier: linkedImage, - altText: "External card alt text", - imageAsset: DataAsset( - variants: [ - DataTraitCollection(userInterfaceStyle: .light, displayScale: .double): imageURLs.light, - DataTraitCollection(userInterfaceStyle: .dark, displayScale: .double): imageURLs.dark, - ], - metadata: [ - imageURLs.light : DataAsset.Metadata(svgID: nil), - imageURLs.dark : DataAsset.Metadata(svgID: nil), - ], - context: .display - ) - ), - ], - variants: [ - .init( - traits: [.interfaceLanguage("com.test.another-language.id")], - kind: .init(name: "Variant Kind Name", id: "com.test.kind2.id", isSymbol: true), - language: .init(name: "Language Name 2", id: "com.test.another-language.id"), - title: "Resolved Variant Title", - abstract: [ - .text("Resolved variant abstract with "), - .emphasis(inlineContent: [.text("formatted")]), - .text(" "), - .strong(inlineContent: [.text("formatted")]), - .text(" and a link: "), - .reference(identifier: linkedVariantReference, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) - ], - declarationFragments: .init([ - .init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil) - ]) - ) - ] - ) - - return (summary, linkedImage, imageURLs) - } - - func testResolvingLinkAndSymbol() throws { - enum RequestKind { - case link, symbol - - func perform(resolver: OutOfProcessReferenceResolver, file: StaticString = #filePath, line: UInt = #line) throws -> LinkResolver.ExternalEntity? { - switch self { - case .link: - let unresolved = TopicReference.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(parsingExact: "doc://com.test.bundle/something")!)) - let reference: ResolvedTopicReference - switch resolver.resolve(unresolved) { - case .success(let resolved): - reference = resolved - case .failure(_, let errorInfo): - XCTFail("Unexpectedly failed to resolve reference with error: \(errorInfo.message)", file: file, line: line) - return nil - } - - // Resolve the symbol - return resolver.entity(with: reference) - - case .symbol: - return try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "")?.1, file: file, line: line) - } - } - } - - for requestKind in [RequestKind.link, .symbol] { - let (testSummary, linkedImage, imageURLs) = makeTestSummary() - - let resolver: OutOfProcessReferenceResolver - do { - let temporaryFolder = try createTemporaryDirectory() - let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") - - let encodedLinkSummary = try String(data: JSONEncoder().encode(testSummary), encoding: .utf8)! - - try """ - #!/bin/bash - echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities - read # Wait for docc to send a request - echo '{"resolved":\(encodedLinkSummary)}' # Respond with the test link summary (above) - """.write(to: executableLocation, atomically: true, encoding: .utf8) - - // `0o0700` is `-rwx------` (read, write, & execute only for owner) - try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) - XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) - - resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) - XCTAssertEqual(resolver.bundleID, "com.test.bundle") - } - - let entity = try XCTUnwrap(requestKind.perform(resolver: resolver)) - let topicRenderReference = entity.makeTopicRenderReference() - - XCTAssertEqual(topicRenderReference.url, testSummary.relativePresentationURL.absoluteString) - - XCTAssertEqual(topicRenderReference.kind.rawValue, "symbol") - XCTAssertEqual(topicRenderReference.role, "symbol") - - XCTAssertEqual(topicRenderReference.title, "Resolved Title") - XCTAssertEqual(topicRenderReference.abstract, [ - .text("Resolved abstract with "), - .emphasis(inlineContent: [.text("formatted")]), - .text(" "), - .strong(inlineContent: [.text("formatted")]), - .text(" and a link: "), - .reference(identifier: .init("doc://com.test.bundle/something-else"), isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) - ]) - - XCTAssertFalse(topicRenderReference.isBeta) - - XCTAssertEqual(entity.availableLanguages.count, 3) - - let availableSourceLanguages = entity.availableLanguages.sorted() - let expectedLanguages = testSummary.availableLanguages.sorted() - - XCTAssertEqual(availableSourceLanguages[0], expectedLanguages[0]) - XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) - XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) - - XCTAssertEqual(topicRenderReference.fragments, [ - .init(text: "struct", kind: .keyword, preciseIdentifier: nil), - .init(text: " ", kind: .text, preciseIdentifier: nil), - .init(text: "declaration fragment", kind: .identifier, preciseIdentifier: nil), - ]) - - let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] - XCTAssertEqual(topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") - XCTAssertEqual(topicRenderReference.abstractVariants.value(for: variantTraits), [ - .text("Resolved variant abstract with "), - .emphasis(inlineContent: [.text("formatted")]), - .text(" "), - .strong(inlineContent: [.text("formatted")]), - .text(" and a link: "), - .reference(identifier: .init("doc://com.test.bundle/something-else-2"), isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) - ]) - - let fragmentVariant = try XCTUnwrap(topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) - XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) - if case .replace(let variantFragment) = fragmentVariant.patch.first { - XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) - } else { - XCTFail("Unexpected fragments variant patch") - } - - XCTAssertNil(topicRenderReference.conformance) - XCTAssertNil(topicRenderReference.estimatedTime) - XCTAssertNil(topicRenderReference.defaultImplementationCount) - XCTAssertFalse(topicRenderReference.isBeta) - XCTAssertFalse(topicRenderReference.isDeprecated) - XCTAssertNil(topicRenderReference.propertyListKeyNames) - XCTAssertNil(topicRenderReference.tags) - - XCTAssertEqual(topicRenderReference.images.count, 1) - let topicImage = try XCTUnwrap(topicRenderReference.images.first) - XCTAssertEqual(topicImage.type, .card) - - let image = try XCTUnwrap(entity.makeRenderDependencies().imageReferences.first(where: { $0.identifier == topicImage.identifier })) - - XCTAssertEqual(image.identifier, linkedImage) - XCTAssertEqual(image.altText, "External card alt text") - - XCTAssertEqual(image.asset, DataAsset( - variants: [ - DataTraitCollection(userInterfaceStyle: .light, displayScale: .double): imageURLs.light, - DataTraitCollection(userInterfaceStyle: .dark, displayScale: .double): imageURLs.dark, - ], - metadata: [ - imageURLs.light: DataAsset.Metadata(svgID: nil), - imageURLs.dark: DataAsset.Metadata(svgID: nil), - ], - context: .display - )) - } - } - - func testForwardsErrorOutputProcess() throws { - let temporaryFolder = try createTemporaryDirectory() - - let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") - try """ - #!/bin/bash - echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities - echo "Some error output" 1>&2 # Write to stderr - read # Wait for docc to send a request - """.write(to: executableLocation, atomically: true, encoding: .utf8) - - // `0o0700` is `-rwx------` (read, write, & execute only for owner) - try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) - XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) - - let didReadErrorOutputExpectation = expectation(description: "Did read forwarded error output.") - - let resolver = try? OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { - errorMessage in - XCTAssertEqual(errorMessage, "Some error output\n") - didReadErrorOutputExpectation.fulfill() - }) - XCTAssertEqual(resolver?.bundleID, "com.test.bundle") - - wait(for: [didReadErrorOutputExpectation], timeout: 20.0) - } - - func testLinksAndImagesInExternalAbstractAreIncludedInTheRenderedPageReferenecs() async throws { - let externalBundleID: DocumentationBundle.Identifier = "com.example.test" - - let imageRef = RenderReferenceIdentifier("some-external-card-image-identifier") - let linkRef = RenderReferenceIdentifier("doc://\(externalBundleID)/path/to/other-page") - - let imageURL = URL(string: "https://example.com/path/to/some-image.png")! - - let originalLinkedImage = ImageReference( - identifier: imageRef, - imageAsset: DataAsset( - variants: [.init(displayScale: .standard): imageURL], - metadata: [imageURL: .init()], - context: .display - ) - ) - - let originalLinkedTopic = TopicRenderReference( - identifier: linkRef, - title: "Resolved title of link inside abstract", - abstract: [ - .text("This transient content is not displayed anywhere"), - ], - url: "/path/to/other-page", - kind: .article - ) - - let externalSummary = LinkDestinationSummary( - kind: .article, - language: .swift, - relativePresentationURL: URL(string: "/path/to/something")!, - referenceURL: URL(string: "doc://\(externalBundleID)/path/to/something")!, - title: "Resolved title", - abstract: [ - .text("External abstract with an image "), - .image(identifier: imageRef, metadata: nil), - .text(" and link "), - .reference(identifier: linkRef, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil), - .text("."), - ], - availableLanguages: [.swift], - platforms: nil, - taskGroups: nil, - usr: nil, - declarationFragments: nil, - redirects: nil, - topicImages: nil, - references: [originalLinkedImage, originalLinkedTopic], - variants: [] - ) - - let resolver: OutOfProcessReferenceResolver - do { - let temporaryFolder = try createTemporaryDirectory() - let encodedResponse = try String(decoding: JSONEncoder().encode(OutOfProcessReferenceResolver.ResponseV2.resolved(externalSummary)), as: UTF8.self) - - let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") - try """ - #!/bin/bash - echo '{"identifier":"\(externalBundleID)","capabilities": 0}' # Write this resolver's identifier & capabilities - read # Wait for docc to send a request - echo '\(encodedResponse)' # Respond with the resolved link summary - read # Wait for docc to send another request - """.write(to: executableLocation, atomically: true, encoding: .utf8) - - // `0o0700` is `-rwx------` (read, write, & execute only for owner) - try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) - XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) - - resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) - } - - let catalog = Folder(name: "unit-test.docc", content: [ - TextFile(name: "Something.md", utf8Content: """ - # My root page - - This page curates an an external page (so that its abstract and transient references are displayed on the page) - - ## Topics - - ### An external link - - - - """) - ]) - let inputDirectory = Folder(name: "path", content: [Folder(name: "to", content: [catalog])]) - - var configuration = DocumentationContext.Configuration() - configuration.externalDocumentationConfiguration.sources = [ - externalBundleID: resolver - ] - let (_, context) = try await loadBundle(catalog: inputDirectory, configuration: configuration) - XCTAssertEqual(context.problems.map(\.diagnostic.summary), [], "Encountered unexpected problems") - - let reference = try XCTUnwrap(context.soleRootModuleReference, "This example catalog only has a root page") - - let converter = DocumentationContextConverter( - bundle: context.bundle, - context: context, - renderContext: RenderContext( - documentationContext: context, - bundle: context.bundle - ) - ) - let renderNode = try XCTUnwrap(converter.renderNode(for: context.entity(with: reference))) - - // Verify that the topic section exist and has the external link - XCTAssertEqual(renderNode.topicSections.flatMap { [$0.title ?? ""] + $0.identifiers }, [ - "An external link", - "doc://\(externalBundleID)/path/to/something", // Resolved links use their canonical references - ]) - - // Verify that the externally resolved page's references are included on the page - XCTAssertEqual(Set(renderNode.references.keys), [ - "doc://com.example.test/path/to/something", // The external page that the root links to - - "some-external-card-image-identifier", // The image in that page's abstract - "doc://com.example.test/path/to/other-page", // The link in that page's abstract - ], "The external page and its two references should be included on this page") - - XCTAssertEqual(renderNode.references[imageRef.identifier] as? ImageReference, originalLinkedImage) - XCTAssertEqual(renderNode.references[linkRef.identifier] as? TopicRenderReference, originalLinkedTopic) - } - - func testExternalLinkFailureResultInDiagnosticWithSolutions() async throws { - let externalBundleID: DocumentationBundle.Identifier = "com.example.test" - - let resolver: OutOfProcessReferenceResolver - do { - let temporaryFolder = try createTemporaryDirectory() - - let diagnosticInfo = OutOfProcessReferenceResolver.ResponseV2.DiagnosticInformation( - summary: "Some external link issue summary", - solutions: [ - .init(summary: "Some external solution", replacement: "some-replacement") - ] - ) - let encodedDiagnostic = try String(decoding: JSONEncoder().encode(diagnosticInfo), as: UTF8.self) - - let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") - try """ - #!/bin/bash - echo '{"identifier":"\(externalBundleID)","capabilities": 0}' # Write this resolver's identifier & capabilities - read # Wait for docc to send a request - echo '{"failure":\(encodedDiagnostic)}' # Respond with an error message - """.write(to: executableLocation, atomically: true, encoding: .utf8) - - // `0o0700` is `-rwx------` (read, write, & execute only for owner) - try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) - XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) - - resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) - } - - let catalog = Folder(name: "unit-test.docc", content: [ - TextFile(name: "Something.md", utf8Content: """ - # My root page - - This page contains an external link that will fail to resolve: - """) - ]) - let inputDirectory = Folder(name: "path", content: [Folder(name: "to", content: [catalog])]) - - var configuration = DocumentationContext.Configuration() - configuration.externalDocumentationConfiguration.sources = [ - externalBundleID: resolver - ] - let (_, context) = try await loadBundle(catalog: inputDirectory, configuration: configuration) - - XCTAssertEqual(context.problems.map(\.diagnostic.summary), [ - "Some external link issue summary", - ]) - - let problem = try XCTUnwrap(context.problems.sorted(by: \.diagnostic.identifier).first) - - XCTAssertEqual(problem.diagnostic.summary, "Some external link issue summary") - XCTAssertEqual(problem.diagnostic.range?.lowerBound, .init(line: 3, column: 69, source: URL(fileURLWithPath: "/path/to/unit-test.docc/Something.md"))) - XCTAssertEqual(problem.diagnostic.range?.upperBound, .init(line: 3, column: 97, source: URL(fileURLWithPath: "/path/to/unit-test.docc/Something.md"))) - - XCTAssertEqual(problem.possibleSolutions.count, 1) - let solution = try XCTUnwrap(problem.possibleSolutions.first) - XCTAssertEqual(solution.summary, "Some external solution") - XCTAssertEqual(solution.replacements.count, 1) - XCTAssertEqual(solution.replacements.first?.range.lowerBound, .init(line: 3, column: 65, source: nil)) - XCTAssertEqual(solution.replacements.first?.range.upperBound, .init(line: 3, column: 97, source: nil)) - - // Verify the warning presentation - let diagnosticOutput = LogHandle.LogStorage() - let fileSystem = try TestFileSystem(folders: [inputDirectory]) - let diagnosticFormatter = DiagnosticConsoleWriter(LogHandle.memory(diagnosticOutput), formattingOptions: [], highlight: true, dataProvider: fileSystem) - diagnosticFormatter.receive(context.diagnosticEngine.problems) - try diagnosticFormatter.flush() - - let warning = "\u{001B}[1;33m" - let highlight = "\u{001B}[1;32m" - let suggestion = "\u{001B}[1;39m" - let clear = "\u{001B}[0;0m" - XCTAssertEqual(diagnosticOutput.text, """ - \(warning)warning: Some external link issue summary\(clear) - --> /path/to/unit-test.docc/Something.md:3:69-3:97 - 1 | # My root page - 2 | - 3 + This page contains an external link that will fail to resolve: - | ╰─\(suggestion)suggestion: Some external solution\(clear) - - """) - - // Verify the suggestion replacement - let source = try XCTUnwrap(problem.diagnostic.source) - let original = String(decoding: try fileSystem.contents(of: source), as: UTF8.self) - - XCTAssertEqual(try solution.applyTo(original), """ - # My root page - - This page contains an external link that will fail to resolve: - """) - } - - func testEncodingAndDecodingRequests() throws { - do { - let request = OutOfProcessReferenceResolver.RequestV2.link("doc://com.example/path/to/something") - - let data = try JSONEncoder().encode(request) - if case .link(let link) = try JSONDecoder().decode(OutOfProcessReferenceResolver.RequestV2.self, from: data) { - XCTAssertEqual(link, "doc://com.example/path/to/something") - } else { - XCTFail("Decoded the wrong type of request") - } - } - - do { - let request = OutOfProcessReferenceResolver.RequestV2.symbol("some-unique-symbol-id") - - let data = try JSONEncoder().encode(request) - if case .symbol(let usr) = try JSONDecoder().decode(OutOfProcessReferenceResolver.RequestV2.self, from: data) { - XCTAssertEqual(usr, "some-unique-symbol-id") - } else { - XCTFail("Decoded the wrong type of request") - } - } - } - - func testEncodingAndDecodingResponses() throws { - // Identifier and capabilities - do { - let request = OutOfProcessReferenceResolver.ResponseV2.identifierAndCapabilities("com.example.test", []) - - let data = try JSONEncoder().encode(request) - if case .identifierAndCapabilities(let identifier, let capabilities) = try JSONDecoder().decode(OutOfProcessReferenceResolver.ResponseV2.self, from: data) { - XCTAssertEqual(identifier.rawValue, "com.example.test") - XCTAssertEqual(capabilities.rawValue, 0) - } else { - XCTFail("Decoded the wrong type of message") - } - } - - // Failures - do { - let originalInfo = OutOfProcessReferenceResolver.ResponseV2.DiagnosticInformation( - summary: "Some summary", - solutions: [ - .init(summary: "Some solution", replacement: "some-replacement") - ] - ) - - let request = OutOfProcessReferenceResolver.ResponseV2.failure(originalInfo) - let data = try JSONEncoder().encode(request) - if case .failure(let info) = try JSONDecoder().decode(OutOfProcessReferenceResolver.ResponseV2.self, from: data) { - XCTAssertEqual(info.summary, originalInfo.summary) - XCTAssertEqual(info.solutions?.count, originalInfo.solutions?.count) - for (solution, originalSolution) in zip(info.solutions ?? [], originalInfo.solutions ?? []) { - XCTAssertEqual(solution.summary, originalSolution.summary) - XCTAssertEqual(solution.replacement, originalSolution.replacement) - } - } else { - XCTFail("Decoded the wrong type of message") - } - } - - // Resolved link information - do { - let originalSummary = makeTestSummary().summary - let message = OutOfProcessReferenceResolver.ResponseV2.resolved(originalSummary) - - let data = try JSONEncoder().encode(message) - if case .resolved(let summary) = try JSONDecoder().decode(OutOfProcessReferenceResolver.ResponseV2.self, from: data) { - XCTAssertEqual(summary, originalSummary) - } else { - XCTFail("Decoded the wrong type of message") - return - } - } - } - - func testErrorWhenReceivingBundleIdentifierTwice() throws { - let temporaryFolder = try createTemporaryDirectory() - - let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") - try """ - #!/bin/bash - echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities - read # Wait for docc to send a request - echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this identifier & capabilities again - """.write(to: executableLocation, atomically: true, encoding: .utf8) - - // `0o0700` is `-rwx------` (read, write, & execute only for owner) - try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) - XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) - - let resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) - XCTAssertEqual(resolver.bundleID, "com.test.bundle") - - if case .failure(_, let errorInfo) = resolver.resolve(.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(parsingAuthoredLink: "doc://com.test.bundle/something")!))) { - XCTAssertEqual(errorInfo.message, "Executable sent bundle identifier message again, after it was already received.") - } else { - XCTFail("Unexpectedly resolved the link from an identifier and capabilities response") - } - } - - func testResolvingSymbolBetaStatusProcess() throws { - func betaStatus(forSymbolWithPlatforms platforms: [LinkDestinationSummary.PlatformAvailability], file: StaticString = #filePath, line: UInt = #line) throws -> Bool { - let summary = LinkDestinationSummary( - kind: .class, - language: .swift, - relativePresentationURL: URL(string: "/documentation/ModuleName/Something")!, - referenceURL: URL(string: "/documentation/ModuleName/Something")!, - title: "Something", - availableLanguages: [.swift, .objectiveC], - platforms: platforms, - variants: [] - ) - - let resolver: OutOfProcessReferenceResolver - do { - let temporaryFolder = try createTemporaryDirectory() - let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") - - let encodedLinkSummary = try String(data: JSONEncoder().encode(summary), encoding: .utf8)! - - try """ - #!/bin/bash - #!/bin/bash - echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities - read # Wait for docc to send a request - echo '{"resolved":\(encodedLinkSummary)}' # Respond with the test link summary (above) - """.write(to: executableLocation, atomically: true, encoding: .utf8) - - // `0o0700` is `-rwx------` (read, write, & execute only for owner) - try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) - XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) - - resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) - XCTAssertEqual(resolver.bundleID, "com.test.bundle", file: file, line: line) - } - - let (_, symbolEntity) = try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "abc123"), "Unexpectedly failed to resolve symbol") - return symbolEntity.makeTopicRenderReference().isBeta - } - - // All platforms are in beta - XCTAssertEqual(true, try betaStatus(forSymbolWithPlatforms: [ - .init(name: "fooOS", introduced: "1.2.3", isBeta: true), - .init(name: "barOS", introduced: "1.2.3", isBeta: true), - .init(name: "bazOS", introduced: "1.2.3", isBeta: true), - ])) - - // One platform is stable, the other two are in beta - XCTAssertEqual(false, try betaStatus(forSymbolWithPlatforms: [ - .init(name: "fooOS", introduced: "1.2.3", isBeta: false), - .init(name: "barOS", introduced: "1.2.3", isBeta: true), - .init(name: "bazOS", introduced: "1.2.3", isBeta: true), - ])) - - // No platforms explicitly supported - XCTAssertEqual(false, try betaStatus(forSymbolWithPlatforms: [])) - } -} -#endif diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift b/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift similarity index 90% rename from Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift rename to Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift index a4640f8b2f..84b59d722c 100644 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift +++ b/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift @@ -12,12 +12,10 @@ import XCTest import Foundation import SymbolKit @_spi(ExternalLinks) @testable import SwiftDocC +@testable import SwiftDocCUtilities import SwiftDocCTestUtilities -// This tests the deprecated V1 implementation of `OutOfProcessReferenceResolver`. -// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. -@available(*, deprecated) -class OutOfProcessReferenceResolverV1Tests: XCTestCase { +class OutOfProcessReferenceResolverTests: XCTestCase { func testInitializationProcess() throws { #if os(macOS) @@ -53,7 +51,7 @@ class OutOfProcessReferenceResolverV1Tests: XCTestCase { #endif } - private func assertResolvesTopicLink(makeResolver: (OutOfProcessReferenceResolver.ResolvedInformation) throws -> OutOfProcessReferenceResolver) throws { + func assertResolvesTopicLink(makeResolver: (OutOfProcessReferenceResolver.ResolvedInformation) throws -> OutOfProcessReferenceResolver) throws { let testMetadata = OutOfProcessReferenceResolver.ResolvedInformation( kind: .function, url: URL(string: "doc://com.test.bundle/something")!, @@ -102,34 +100,33 @@ class OutOfProcessReferenceResolverV1Tests: XCTestCase { // Resolve the symbol let entity = resolver.entity(with: resolvedReference) - let topicRenderReference = entity.makeTopicRenderReference() - XCTAssertEqual(topicRenderReference.url, testMetadata.url.withoutHostAndPortAndScheme().absoluteString) + XCTAssertEqual(entity.topicRenderReference.url, testMetadata.url.withoutHostAndPortAndScheme().absoluteString) - XCTAssertEqual(topicRenderReference.kind.rawValue, "symbol") - XCTAssertEqual(topicRenderReference.role, "symbol") + XCTAssertEqual(entity.topicRenderReference.kind.rawValue, "symbol") + XCTAssertEqual(entity.topicRenderReference.role, "symbol") - XCTAssertEqual(topicRenderReference.title, "Resolved Title") - XCTAssertEqual(topicRenderReference.abstract, [.text("Resolved abstract for this topic.")]) + XCTAssertEqual(entity.topicRenderReference.title, "Resolved Title") + XCTAssertEqual(entity.topicRenderReference.abstract, [.text("Resolved abstract for this topic.")]) - XCTAssertFalse(topicRenderReference.isBeta) + XCTAssertFalse(entity.topicRenderReference.isBeta) - XCTAssertEqual(entity.availableLanguages.count, 3) + XCTAssertEqual(entity.sourceLanguages.count, 3) - let availableSourceLanguages = entity.availableLanguages.sorted() + let availableSourceLanguages = entity.sourceLanguages.sorted() let expectedLanguages = testMetadata.availableLanguages.sorted() XCTAssertEqual(availableSourceLanguages[0], expectedLanguages[0]) XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) - XCTAssertEqual(topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] - XCTAssertEqual(topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") - XCTAssertEqual(topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) + XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(entity.topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) - let fragmentVariant = try XCTUnwrap(topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) + let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) if case .replace(let variantFragment) = fragmentVariant.patch.first { XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) @@ -137,7 +134,7 @@ class OutOfProcessReferenceResolverV1Tests: XCTestCase { XCTFail("Unexpected fragments variant patch") } - XCTAssertEqual(entity.kind, .function) + XCTAssertEqual(entity.symbolKind, .func) } func testResolvingTopicLinkProcess() throws { @@ -276,31 +273,30 @@ class OutOfProcessReferenceResolverV1Tests: XCTestCase { // Resolve the symbol let (_, entity) = try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "abc123"), "Unexpectedly failed to resolve symbol") - let topicRenderReference = entity.makeTopicRenderReference() - XCTAssertEqual(topicRenderReference.url, testMetadata.url.absoluteString) + XCTAssertEqual(entity.topicRenderReference.url, testMetadata.url.absoluteString) - XCTAssertEqual(topicRenderReference.kind.rawValue, "symbol") - XCTAssertEqual(topicRenderReference.role, "symbol") + XCTAssertEqual(entity.topicRenderReference.kind.rawValue, "symbol") + XCTAssertEqual(entity.topicRenderReference.role, "symbol") - XCTAssertEqual(topicRenderReference.title, "Resolved Title") + XCTAssertEqual(entity.topicRenderReference.title, "Resolved Title") - XCTAssertEqual(entity.availableLanguages.count, 3) + XCTAssertEqual(entity.sourceLanguages.count, 3) - let availableSourceLanguages = entity.availableLanguages.sorted() + let availableSourceLanguages = entity.sourceLanguages.sorted() let expectedLanguages = testMetadata.availableLanguages.sorted() XCTAssertEqual(availableSourceLanguages[0], expectedLanguages[0]) XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) - XCTAssertEqual(topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] - XCTAssertEqual(topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") - XCTAssertEqual(topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) + XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(entity.topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) - let fragmentVariant = try XCTUnwrap(topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) + let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) if case .replace(let variantFragment) = fragmentVariant.patch.first { XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) @@ -308,19 +304,19 @@ class OutOfProcessReferenceResolverV1Tests: XCTestCase { XCTFail("Unexpected fragments variant patch") } - XCTAssertNil(topicRenderReference.conformance) - XCTAssertNil(topicRenderReference.estimatedTime) - XCTAssertNil(topicRenderReference.defaultImplementationCount) - XCTAssertFalse(topicRenderReference.isBeta) - XCTAssertFalse(topicRenderReference.isDeprecated) - XCTAssertNil(topicRenderReference.propertyListKeyNames) - XCTAssertNil(topicRenderReference.tags) - - XCTAssertEqual(topicRenderReference.images.count, 1) - let topicImage = try XCTUnwrap(topicRenderReference.images.first) + XCTAssertNil(entity.topicRenderReference.conformance) + XCTAssertNil(entity.topicRenderReference.estimatedTime) + XCTAssertNil(entity.topicRenderReference.defaultImplementationCount) + XCTAssertFalse(entity.topicRenderReference.isBeta) + XCTAssertFalse(entity.topicRenderReference.isDeprecated) + XCTAssertNil(entity.topicRenderReference.propertyListKeyNames) + XCTAssertNil(entity.topicRenderReference.tags) + + XCTAssertEqual(entity.topicRenderReference.images.count, 1) + let topicImage = try XCTUnwrap(entity.topicRenderReference.images.first) XCTAssertEqual(topicImage.type, .card) - let image = try XCTUnwrap(entity.makeRenderDependencies().imageReferences.first(where: { $0.identifier == topicImage.identifier })) + let image = try XCTUnwrap(entity.renderReferenceDependencies.imageReferences.first(where: { $0.identifier == topicImage.identifier })) XCTAssertEqual(image.identifier, RenderReferenceIdentifier("external-card")) XCTAssertEqual(image.altText, "External card alt text") @@ -682,10 +678,11 @@ class OutOfProcessReferenceResolverV1Tests: XCTestCase { let resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) XCTAssertEqual(resolver.bundleID, "com.test.bundle") - if case .failure(_, let errorInfo) = resolver.resolve(.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(parsingAuthoredLink: "doc://com.test.bundle/something")!))) { - XCTAssertEqual(errorInfo.message, "Executable sent bundle identifier message again, after it was already received.") - } else { - XCTFail("Unexpectedly resolved the link from an identifier and capabilities response") + XCTAssertThrowsError(try resolver.resolveInformationForTopicURL(URL(string: "doc://com.test.bundle/something")!)) { + guard case OutOfProcessReferenceResolver.Error.executableSentBundleIdentifierAgain = $0 else { + XCTFail("Encountered an unexpected type of error.") + return + } } #endif } @@ -737,13 +734,13 @@ class OutOfProcessReferenceResolverV1Tests: XCTestCase { // Resolve the symbol let topicLinkEntity = resolver.entity(with: resolvedReference) - - XCTAssertEqual(topicLinkEntity.makeTopicRenderReference().isBeta, isBeta, file: file, line: line) + + XCTAssertEqual(topicLinkEntity.topicRenderReference.isBeta, isBeta, file: file, line: line) // Resolve the symbol let (_, symbolEntity) = try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "abc123"), "Unexpectedly failed to resolve symbol") - XCTAssertEqual(symbolEntity.makeTopicRenderReference().isBeta, isBeta, file: file, line: line) + XCTAssertEqual(symbolEntity.topicRenderReference.isBeta, isBeta, file: file, line: line) } diff --git a/bin/test-data-external-resolver b/bin/test-data-external-resolver index d38e2db513..23d9f9d13d 100755 --- a/bin/test-data-external-resolver +++ b/bin/test-data-external-resolver @@ -2,7 +2,7 @@ # # This source file is part of the Swift.org open source project # -# Copyright (c) 2021-2025 Apple Inc. and the Swift project authors +# Copyright (c) 2021 Apple Inc. and the Swift project authors # Licensed under Apache License v2.0 with Runtime Library Exception # # See https://swift.org/LICENSE.txt for license information @@ -18,177 +18,108 @@ # absolute documentation links with the "com.test.bundle" identifier. For example: RESPONSE='{ - "resolved" : { - "abstract" : [ - { - "text" : "Resolved ", - "type" : "text" - }, - { - "inlineContent" : [ - { - "text" : "formatted", - "type" : "text" - } - ], - "type" : "strong" - }, - { - "text" : " abstract with ", - "type" : "text" - }, + "resolvedInformation" : { + "abstract" : "Resolved abstract.", + "availableLanguages" : [ { - "identifier" : "doc://com.test.bundle/path/to/other-page", - "isActive" : true, - "type" : "reference" + "id" : "swift", + "name" : "Language Name", + "idAliases" : [], + "linkDisambiguationID": "swift" }, { - "text" : ".", - "type" : "text" - } - ], - "availableLanguages" : [ - "swift", - "data", - { - "id" : "language-id", + "id" : "occ", + "name" : "Variant Language Name", "idAliases" : [ - "language-alias-id" + "objective-c", + "c" ], - "linkDisambiguationID" : "language-id", - "name" : "Language name" - }, - { - "id" : "language-id-2", - "linkDisambiguationID" : "language-id-2", - "name" : "Other language name" - }, - "occ" + "linkDisambiguationID" : "c" + } ], - "fragments" : [ - { - "kind" : "keyword", - "text" : "resolved" - }, + "declarationFragments" : [ { "kind" : "text", - "text" : " " - }, - { - "kind" : "identifier", - "text" : "fragment" + "spelling" : "declaration fragment" } ], "kind" : { - "id" : "kind-id", + "id" : "com.test.kind.id", "isSymbol" : true, - "name" : "Kind name" + "name" : "Kind Name" }, "language" : { - "id" : "language-id", - "idAliases" : [ - "language-alias-id" - ], - "linkDisambiguationID" : "language-id", - "name" : "Language name" + "id" : "swift", + "name" : "Language Name", + "idAliases" : [], + "linkDisambiguationID": "swift" + }, - "path" : "/documentation/something", "platforms" : [ { "beta" : false, - "introducedAt" : "1.2.3", - "name" : "Platform name" + "introducedAt" : "1.0.0", + "name" : "Platform Name" } ], - "referenceURL" : "doc://com.test.bundle/documentation/something", - "references" : [ + "topicImages": [ { - "abstract" : [ - { - "text" : "The abstract of another page that is linked to", - "type" : "text" - } - ], - "identifier" : "doc://com.test.bundle/path/to/other-page", - "kind" : "article", - "title" : "Linked from abstract", - "type" : "topic", - "url" : "/path/to/other-page" - }, + "type": "card", + "identifier": "some-external-card-image-identifier" + } + ], + "references": [ { - "alt" : "Resolved image alt text", - "identifier" : "some-external-card-image-identifier", - "type" : "image", - "variants" : [ + "type": "image", + "identifier": "some-external-card-image-identifier", + "variants": [ { - "traits" : [ + "url": "http:\/\/example.com\/some-image-1x.jpg", + "traits": [ "1x" - ], - "url" : "http://example.com/some-image.jpg" + ] + }, + { + "url": "http:\/\/example.com\/some-image-1x-dark.jpg", + "traits": [ + "1x", "dark" + ] }, { - "traits" : [ - "2x", - "dark" - ], - "url" : "http://example.com/some-image@2x~dark.jpg" + "url": "http:\/\/example.com\/some-image-2x.jpg", + "traits": [ + "2x" + ] } ] } ], - "title" : "Resolved title", - "topicImages" : [ - { - "identifier" : "some-external-card-image-identifier", - "type" : "card" - } - ], - "usr" : "resolved-unique-symbol-id", + "title" : "Resolved Title", + "url" : "doc:\/\/com.test.bundle\/resolved/path\/", "variants" : [ { - "abstract" : [ - { - "text" : "Resolved abstract", - "type" : "text" - }, - { - "code" : "variant", - "type" : "codeVoice" - }, - { - "text" : "Resolved abstract", - "type" : "text" - } - ], - "fragments" : [ - { - "kind" : "keyword", - "text" : "resolved" - }, - { - "kind" : "text", - "text" : " " - }, - { - "kind" : "identifier", - "text" : "variant" - }, + "abstract" : "Resolved variant abstract for this topic.", + "declarationFragments" : [ { "kind" : "text", - "text" : ": " - }, - { - "kind" : "typeIdentifier", - "text" : "fragment" + "spelling" : "variant declaration fragment" } ], "kind" : { - "id" : "variant-kind-id", + "id" : "com.test.other-kind.id", "isSymbol" : true, - "name" : "Variant kind name" + "name" : "Variant Kind Name" + }, + "language" : { + "id" : "occ", + "name" : "Variant Language Name", + "idAliases" : [ + "objective-c", + "c" + ], + "linkDisambiguationID" : "c" }, - "language" : "occ", - "title" : "Resolved variant title", + "title" : "Resolved Variant Title", "traits" : [ { "interfaceLanguage" : "occ" @@ -199,11 +130,8 @@ RESPONSE='{ } }' -# Write this resolver's identifier and capabilities -echo '{ - "identifier": "com.test.bundle", - "capabilities": 0 -}' +# Write this resolver's bundle identifier +echo '{"bundleIdentifier":"com.test.bundle"}' # Forever, wait for DocC to send a request and respond the resolved information while true From 163fd035c0b0223fa38de9a5cad0f091193ab20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 26 Sep 2025 15:21:31 +0200 Subject: [PATCH 38/90] Revert #1299 (#1300) * Revert "Revert "Add a new version of the communication protocol between DocC and external link resolver executables (#1292)" (#1299)" 257c62d1ef11efc023667fda89c0cd4419a8afa3 * Update missed Windows API in #if conditional --- .../AbstractContainsFormattedTextOnly.swift | 3 +- .../Infrastructure/DocumentationContext.swift | 9 +- ...ocessReferenceResolver+Communication.swift | 198 ++++ ...enceResolver+DeprecatedCommunication.swift | 346 +++++++ .../OutOfProcessReferenceResolver.swift | 948 ++++++++---------- .../ExternalPathHierarchyResolver.swift | 31 +- .../LinkResolver+NavigatorIndex.swift | 46 +- .../Link Resolution/LinkResolver.swift | 88 +- .../LinkTargets/LinkDestinationSummary.swift | 168 +++- .../DocumentationContentRenderer.swift | 10 +- .../Model/Rendering/RenderContext.swift | 21 +- .../Rendering/RenderNodeTranslator.swift | 2 +- .../SwiftDocC/StaticAnalysis.md | 3 +- .../Benchmark/ExternalTopicsHashTests.swift | 18 +- ...stractContainsFormattedTextOnlyTests.swift | 5 +- .../ConvertService/ConvertServiceTests.swift | 33 +- .../DocumentationServer+DefaultTests.swift | 5 +- .../Indexing/ExternalRenderNodeTests.swift | 269 ++--- .../ExternalPathHierarchyResolverTests.swift | 2 +- .../ExternalReferenceResolverTests.swift | 47 +- .../TestExternalReferenceResolvers.swift | 43 +- .../LinkDestinationSummaryTests.swift | 75 +- .../Model/SemaToRenderNodeTests.swift | 42 +- ...utOfProcessReferenceResolverV1Tests.swift} | 93 +- ...OutOfProcessReferenceResolverV2Tests.swift | 680 +++++++++++++ bin/test-data-external-resolver | 204 ++-- 26 files changed, 2366 insertions(+), 1023 deletions(-) create mode 100644 Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift create mode 100644 Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift rename Tests/{SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift => SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift} (90%) create mode 100644 Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift diff --git a/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift b/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift index 05f30ea575..a2ed042d6c 100644 --- a/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift +++ b/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.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 @@ -14,6 +14,7 @@ public import Markdown /** A document's abstract may only contain formatted text. Images and links are not allowed. */ +@available(*, deprecated, message: "This check is no longer applicable. This deprecated API will be removed after 6.3 is released") public struct AbstractContainsFormattedTextOnly: Checker { public var problems: [Problem] = [Problem]() private var sourceFile: URL? diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index cd7fca83d7..654185a776 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -265,7 +265,6 @@ public class DocumentationContext { /// - source: The location of the document. private func check(_ document: Document, at source: URL) { var checker = CompositeChecker([ - AbstractContainsFormattedTextOnly(sourceFile: source).any(), DuplicateTopicsSections(sourceFile: source).any(), InvalidAdditionalTitle(sourceFile: source).any(), MissingAbstract(sourceFile: source).any(), @@ -2739,7 +2738,7 @@ public class DocumentationContext { knownEntityValue( reference: reference, valueInLocalEntity: \.availableSourceLanguages, - valueInExternalEntity: \.sourceLanguages + valueInExternalEntity: \.availableLanguages ) } @@ -2747,9 +2746,9 @@ public class DocumentationContext { func isSymbol(reference: ResolvedTopicReference) -> Bool { knownEntityValue( reference: reference, - valueInLocalEntity: { node in node.kind.isSymbol }, - valueInExternalEntity: { entity in entity.topicRenderReference.kind == .symbol } - ) + valueInLocalEntity: \.kind, + valueInExternalEntity: \.kind + ).isSymbol } // MARK: - Relationship queries diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift new file mode 100644 index 0000000000..b60dce179a --- /dev/null +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift @@ -0,0 +1,198 @@ +/* + 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 +*/ + +extension OutOfProcessReferenceResolver { + // MARK: Capabilities + + /// A set of optional capabilities that either DocC or your external link resolver declares that it supports. + /// + /// ## Supported messages + /// + /// If your external link resolver declares none of the optional capabilities, then DocC will only send it the following messages: + /// - ``RequestV2/link(_:)`` + /// - ``RequestV2/symbol(_:)`` + public struct Capabilities: OptionSet, Codable { + public let rawValue: Int + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + rawValue = try container.decode(Int.self) + } + } + + // MARK: Request & Response + + /// Request messages that DocC sends to the external link resolver. + /// + /// ## Topics + /// ### Base requests + /// + /// Your external link resolver always needs to handle the following requests regardless of its declared capabilities: + /// + /// - ``link(_:)`` + /// - ``symbol(_:)`` + public enum RequestV2: Codable { + /// A request to resolve a link + /// + /// Your external resolver + case link(String) + /// A request to resolve a symbol based on its precise identifier. + case symbol(String) + + // This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled, + // which is not available to Swift Packages without unsafe flags (rdar://78773361). + // This can be removed once that is available and applied to Swift-DocC (rdar://89033233). + @available(*, deprecated, message: """ + This enum is non-frozen and may be expanded in the future; add a `default` case, and do nothing in it, instead of matching this one. + Your external link resolver won't be passed new messages that it hasn't declared the corresponding capability for. + """) + case _nonFrozenEnum_useDefaultCase + + private enum CodingKeys: CodingKey { + case link, symbol // Default requests keys + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .link(let link): try container.encode(link, forKey: .link) + case .symbol(let id): try container.encode(id, forKey: .symbol) + + case ._nonFrozenEnum_useDefaultCase: + fatalError("Never use '_nonFrozenEnum_useDefaultCase' as a real case.") + } + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self = switch container.allKeys.first { + case .link?: .link( try container.decode(String.self, forKey: .link)) + case .symbol?: .symbol(try container.decode(String.self, forKey: .symbol)) + case nil: throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest + } + } + } + + /// A response message from the external link resolver. + /// + /// If your external resolver sends a response that's associated with a capability that DocC hasn't declared support for, then DocC will fail to handle the response. + public enum ResponseV2: Codable { + /// The initial identifier and capabilities message. + /// + /// Your external link resolver should send this message, exactly once, after it has launched to signal that its ready to receive requests. + /// + /// The capabilities that your external link resolver declares in this message determines which optional request messages that DocC will send. + /// If your resolver doesn't declare _any_ capabilities it only needs to handle the 3 default requests. See . + case identifierAndCapabilities(DocumentationBundle.Identifier, Capabilities) + /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. + case failure(DiagnosticInformation) + /// A response with the resolved information about the requested topic or symbol. + case resolved(LinkDestinationSummary) + + // This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled, + // which is not available to Swift Packages without unsafe flags (rdar://78773361). + // This can be removed once that is available and applied to Swift-DocC (rdar://89033233). + @available(*, deprecated, message: """ + This enum is non-frozen and may be expanded in the future; add a `default` case, and do nothing in it, instead of matching this one. + Your external link resolver won't be passed new messages that it hasn't declared the corresponding capability for. + """) + case _nonFrozenEnum_useDefaultCase + + private enum CodingKeys: String, CodingKey { + // Default response keys + case identifier, capabilities + case failure + case resolved + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self = switch container.allKeys.first { + case .identifier?, .capabilities?: + .identifierAndCapabilities( + try container.decode(DocumentationBundle.Identifier.self, forKey: .identifier), + try container.decode(Capabilities.self, forKey: .capabilities) + ) + case .failure?: + .failure(try container.decode(DiagnosticInformation.self, forKey: .failure)) + case .resolved?: + .resolved(try container.decode(LinkDestinationSummary.self, forKey: .resolved)) + case nil: + throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .identifierAndCapabilities(let identifier, let capabilities): + try container.encode(identifier, forKey: .identifier) + try container.encode(capabilities, forKey: .capabilities) + + case .failure(errorMessage: let diagnosticInfo): + try container.encode(diagnosticInfo, forKey: .failure) + + case .resolved(let summary): + try container.encode(summary, forKey: .resolved) + + case ._nonFrozenEnum_useDefaultCase: + fatalError("Never use '_nonFrozenEnum_useDefaultCase' for anything.") + } + } + } +} + +extension OutOfProcessReferenceResolver.ResponseV2 { + /// Information about why the external resolver failed to resolve the `link(_:)`, or `symbol(_:)` request. + public struct DiagnosticInformation: Codable { + /// A brief user-facing summary of the issue that caused the external resolver to fail. + public var summary: String + + /// A list of possible suggested solutions that can address the failure. + public var solutions: [Solution]? + + /// Creates a new value with information about why the external resolver failed to resolve the `link(_:)`, or `symbol(_:)` request. + /// - Parameters: + /// - summary: A brief user-facing summary of the issue that caused the external resolver to fail. + /// - solutions: Possible possible suggested solutions that can address the failure. + public init( + summary: String, + solutions: [Solution]? + ) { + self.summary = summary + self.solutions = solutions + } + + /// A possible solution to an external resolver issue. + public struct Solution: Codable { + /// A brief user-facing description of what the solution is. + public var summary: String + /// A full replacement of the link. + public var replacement: String? + + /// Creates a new solution to an external resolver issue + /// - Parameters: + /// - summary: A brief user-facing description of what the solution is. + /// - replacement: A full replacement of the link. + public init(summary: String, replacement: String?) { + self.summary = summary + self.replacement = replacement + } + } + } +} diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift new file mode 100644 index 0000000000..e95a3a01ff --- /dev/null +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift @@ -0,0 +1,346 @@ +/* + 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 +*/ + +public import Foundation +public import SymbolKit + +extension OutOfProcessReferenceResolver { + + // MARK: Request & Response + + /// An outdated version of a request message to send to the external link resolver. + /// + /// This can either be a request to resolve a topic URL or to resolve a symbol based on its precise identifier. + /// + /// @DeprecationSummary { + /// This version of the communication protocol is no longer recommended. Update to ``RequestV2`` and ``ResponseV2`` instead. + /// + /// The new version of the communication protocol both has a mechanism for expanding functionality in the future (through common ``Capabilities`` between DocC and the external resolver) and supports richer responses for both successful and and failed requests. + /// } + @available(*, deprecated, message: "This version of the communication protocol is no longer recommended. Update to `RequestV2` and `ResponseV2` instead.") + public typealias Request = _DeprecatedRequestV1 + + // Note this type isn't formally deprecated to avoid warnings in the ConvertService, which still _implicitly_ require this version of requests and responses. + public enum _DeprecatedRequestV1: Codable, CustomStringConvertible { + /// A request to resolve a topic URL + case topic(URL) + /// A request to resolve a symbol based on its precise identifier. + case symbol(String) + /// A request to resolve an asset. + case asset(AssetReference) + + private enum CodingKeys: CodingKey { + case topic + case symbol + case asset + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .topic(let url): + try container.encode(url, forKey: .topic) + case .symbol(let identifier): + try container.encode(identifier, forKey: .symbol) + case .asset(let assetReference): + try container.encode(assetReference, forKey: .asset) + } + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + switch container.allKeys.first { + case .topic?: + self = .topic(try container.decode(URL.self, forKey: .topic)) + case .symbol?: + self = .symbol(try container.decode(String.self, forKey: .symbol)) + case .asset?: + self = .asset(try container.decode(AssetReference.self, forKey: .asset)) + case nil: + throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest + } + } + + /// A plain text representation of the request message. + public var description: String { + switch self { + case .topic(let url): + return "topic: \(url.absoluteString.singleQuoted)" + case .symbol(let identifier): + return "symbol: \(identifier.singleQuoted)" + case .asset(let asset): + return "asset with name: \(asset.assetName), bundle identifier: \(asset.bundleID)" + } + } + } + + /// An outdated version of a response message from the external link resolver. + /// + /// @DeprecationSummary { + /// This version of the communication protocol is no longer recommended. Update to ``RequestV2`` and ``ResponseV2`` instead. + /// + /// The new version of the communication protocol both has a mechanism for expanding functionality in the future (through common ``Capabilities`` between DocC and the external resolver) and supports richer responses for both successful and and failed requests. + /// } + @available(*, deprecated, message: "This version of the communication protocol is no longer recommended. Update to `RequestV2` and `ResponseV2` instead.") + public typealias Response = _DeprecatedResponseV1 + + @available(*, deprecated, message: "This version of the communication protocol is no longer recommended. Update to `RequestV2` and `ResponseV2` instead.") + public enum _DeprecatedResponseV1: Codable { + /// A bundle identifier response. + /// + /// This message should only be sent once, after the external link resolver has launched. + case bundleIdentifier(String) + /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. + case errorMessage(String) + /// A response with the resolved information about the requested topic or symbol. + case resolvedInformation(ResolvedInformation) + /// A response with information about the resolved asset. + case asset(DataAsset) + + enum CodingKeys: String, CodingKey { + case bundleIdentifier + case errorMessage + case resolvedInformation + case asset + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + switch container.allKeys.first { + case .bundleIdentifier?: + self = .bundleIdentifier(try container.decode(String.self, forKey: .bundleIdentifier)) + case .errorMessage?: + self = .errorMessage(try container.decode(String.self, forKey: .errorMessage)) + case .resolvedInformation?: + self = .resolvedInformation(try container.decode(ResolvedInformation.self, forKey: .resolvedInformation)) + case .asset?: + self = .asset(try container.decode(DataAsset.self, forKey: .asset)) + case nil: + throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .bundleIdentifier(let bundleIdentifier): + try container.encode(bundleIdentifier, forKey: .bundleIdentifier) + case .errorMessage(let errorMessage): + try container.encode(errorMessage, forKey: .errorMessage) + case .resolvedInformation(let resolvedInformation): + try container.encode(resolvedInformation, forKey: .resolvedInformation) + case .asset(let assetReference): + try container.encode(assetReference, forKey: .asset) + } + } + } + + // MARK: Resolved Information + + /// A type used to transfer information about a resolved reference in the outdated and no longer recommended version of the external resolver communication protocol. + @available(*, deprecated, message: "This type is only used in the outdated, and no longer recommended, version of the out-of-process external resolver communication protocol.") + public struct ResolvedInformation: Codable { + /// Information about the resolved kind. + public let kind: DocumentationNode.Kind + /// Information about the resolved URL. + public let url: URL + /// Information about the resolved title. + public let title: String // DocumentationNode.Name + /// Information about the resolved abstract. + public let abstract: String // Markup + /// Information about the resolved language. + public let language: SourceLanguage + /// Information about the languages where the resolved node is available. + public let availableLanguages: Set + /// Information about the platforms and their versions where the resolved node is available, if any. + public let platforms: [PlatformAvailability]? + /// Information about the resolved declaration fragments, if any. + public let declarationFragments: DeclarationFragments? + + // We use the real types here because they're Codable and don't have public member-wise initializers. + + /// Platform availability for a resolved symbol reference. + public typealias PlatformAvailability = AvailabilityRenderItem + + /// The declaration fragments for a resolved symbol reference. + public typealias DeclarationFragments = SymbolGraph.Symbol.DeclarationFragments + + /// The platform names, derived from the platform availability. + public var platformNames: Set? { + return platforms.map { platforms in Set(platforms.compactMap { $0.name }) } + } + + /// Images that are used to represent the summarized element. + public var topicImages: [TopicImage]? + + /// References used in the content of the summarized element. + public var references: [any RenderReference]? + + /// The variants of content (kind, url, title, abstract, language, declaration) for this resolver information. + public var variants: [Variant]? + + /// A value that indicates whether this symbol is under development and likely to change. + var isBeta: Bool { + guard let platforms, !platforms.isEmpty else { + return false + } + + return platforms.allSatisfy { $0.isBeta == true } + } + + /// Creates a new resolved information value with all its values. + /// + /// - Parameters: + /// - kind: The resolved kind. + /// - url: The resolved URL. + /// - title: The resolved title + /// - abstract: The resolved (plain text) abstract. + /// - language: The resolved language. + /// - availableLanguages: The languages where the resolved node is available. + /// - platforms: The platforms and their versions where the resolved node is available, if any. + /// - declarationFragments: The resolved declaration fragments, if any. + /// - topicImages: Images that are used to represent the summarized element. + /// - references: References used in the content of the summarized element. + /// - variants: The variants of content for this resolver information. + public init( + kind: DocumentationNode.Kind, + url: URL, + title: String, + abstract: String, + language: SourceLanguage, + availableLanguages: Set, + platforms: [PlatformAvailability]? = nil, + declarationFragments: DeclarationFragments? = nil, + topicImages: [TopicImage]? = nil, + references: [any RenderReference]? = nil, + variants: [Variant]? = nil + ) { + self.kind = kind + self.url = url + self.title = title + self.abstract = abstract + self.language = language + self.availableLanguages = availableLanguages + self.platforms = platforms + self.declarationFragments = declarationFragments + self.topicImages = topicImages + self.references = references + self.variants = variants + } + + /// A variant of content for the resolved information. + /// + /// - Note: All properties except for ``traits`` are optional. If a property is `nil` it means that the value is the same as the resolved information's value. + public struct Variant: Codable { + /// The traits of the variant. + public let traits: [RenderNode.Variant.Trait] + + /// A wrapper for variant values that can either be specified, meaning the variant has a custom value, or not, meaning the variant has the same value as the resolved information. + /// + /// This alias is used to make the property declarations more explicit while at the same time offering the convenient syntax of optionals. + public typealias VariantValue = Optional + + /// The kind of the variant or `nil` if the kind is the same as the resolved information. + public let kind: VariantValue + /// The url of the variant or `nil` if the url is the same as the resolved information. + public let url: VariantValue + /// The title of the variant or `nil` if the title is the same as the resolved information. + public let title: VariantValue + /// The abstract of the variant or `nil` if the abstract is the same as the resolved information. + public let abstract: VariantValue + /// The language of the variant or `nil` if the language is the same as the resolved information. + public let language: VariantValue + /// The declaration fragments of the variant or `nil` if the declaration is the same as the resolved information. + /// + /// If the resolver information has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. + public let declarationFragments: VariantValue + + /// Creates a new resolved information variant with the values that are different from the resolved information values. + /// + /// - Parameters: + /// - traits: The traits of the variant. + /// - kind: The resolved kind. + /// - url: The resolved URL. + /// - title: The resolved title + /// - abstract: The resolved (plain text) abstract. + /// - language: The resolved language. + /// - declarationFragments: The resolved declaration fragments, if any. + public init( + traits: [RenderNode.Variant.Trait], + kind: VariantValue = nil, + url: VariantValue = nil, + title: VariantValue = nil, + abstract: VariantValue = nil, + language: VariantValue = nil, + declarationFragments: VariantValue = nil + ) { + self.traits = traits + self.kind = kind + self.url = url + self.title = title + self.abstract = abstract + self.language = language + self.declarationFragments = declarationFragments + } + } + } +} + +@available(*, deprecated, message: "This type is only used in the outdates, and no longer recommended, version of the out-of-process external resolver communication protocol.") +extension OutOfProcessReferenceResolver.ResolvedInformation { + enum CodingKeys: CodingKey { + case kind + case url + case title + case abstract + case language + case availableLanguages + case platforms + case declarationFragments + case topicImages + case references + case variants + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) + url = try container.decode(URL.self, forKey: .url) + title = try container.decode(String.self, forKey: .title) + abstract = try container.decode(String.self, forKey: .abstract) + language = try container.decode(SourceLanguage.self, forKey: .language) + availableLanguages = try container.decode(Set.self, forKey: .availableLanguages) + platforms = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.PlatformAvailability].self, forKey: .platforms) + declarationFragments = try container.decodeIfPresent(OutOfProcessReferenceResolver.ResolvedInformation.DeclarationFragments.self, forKey: .declarationFragments) + topicImages = try container.decodeIfPresent([TopicImage].self, forKey: .topicImages) + references = try container.decodeIfPresent([CodableRenderReference].self, forKey: .references).map { decodedReferences in + decodedReferences.map(\.reference) + } + variants = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.Variant].self, forKey: .variants) + + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.kind, forKey: .kind) + try container.encode(self.url, forKey: .url) + try container.encode(self.title, forKey: .title) + try container.encode(self.abstract, forKey: .abstract) + try container.encode(self.language, forKey: .language) + try container.encode(self.availableLanguages, forKey: .availableLanguages) + try container.encodeIfPresent(self.platforms, forKey: .platforms) + try container.encodeIfPresent(self.declarationFragments, forKey: .declarationFragments) + try container.encodeIfPresent(self.topicImages, forKey: .topicImages) + try container.encodeIfPresent(references?.map { CodableRenderReference($0) }, forKey: .references) + try container.encodeIfPresent(self.variants, forKey: .variants) + } +} diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift index 1988f8b074..d062ed0820 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift @@ -9,54 +9,78 @@ */ public import Foundation -import Markdown -public import SymbolKit +private import Markdown /// A reference resolver that launches and interactively communicates with another process or service to resolve links. /// /// If your external reference resolver or an external symbol resolver is implemented in another executable, you can use this object /// to communicate between DocC and the `docc` executable. /// -/// The launched executable is expected to follow the flow outlined below, sending ``OutOfProcessReferenceResolver/Request`` -/// and ``OutOfProcessReferenceResolver/Response`` values back and forth: +/// ## Launching and responding to requests /// -/// │ -/// 1 ▼ -/// ┌──────────────────┐ -/// │ Output bundle ID │ -/// └──────────────────┘ -/// │ -/// 2 ▼ -/// ┌──────────────────┐ -/// │ Wait for input │◀───┐ -/// └──────────────────┘ │ -/// │ │ -/// 3 ▼ │ repeat -/// ┌──────────────────┐ │ -/// │ Output resolved │ │ -/// │ information │────┘ -/// └──────────────────┘ +/// When creating an out-of-process resolver using ``init(processLocation:errorOutputHandler:)`` to communicate with another executable; +/// DocC launches your link resolver executable and declares _its_ own ``Capabilities`` as a raw value passed via the `--capabilities` option. +/// Your link resolver executable is expected to respond with a ``ResponseV2/identifierAndCapabilities(_:_:)`` message that declares: +/// - The documentation bundle identifier that the executable can to resolve links for. +/// - The capabilities that the resolver supports. /// -/// When resolving against a server, the server is expected to be able to handle messages of type "resolve-reference" with a -/// ``OutOfProcessReferenceResolver/Request`` payload and respond with messages of type "resolved-reference-response" -/// with a ``OutOfProcessReferenceResolver/Response`` payload. +/// After this "handshake" your link resolver executable is expected to wait for ``RequestV2`` messages from DocC and respond with exactly one ``ResponseV2`` per message. +/// A visual representation of this flow of execution can be seen in the diagram below: +/// +/// DocC link resolver executable +/// ┌─┐ ╎ +/// │ ├─────────── Launch ──────────▶┴┐ +/// │ │ --capabilities │ │ +/// │ │ │ │ +/// │ ◀───────── Handshake ─────────┤ │ +/// │ │ { "identifier" : ... , │ │ +/// │ │ "capabilities" : ... } │ │ +/// ┏ loop ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +/// ┃ │ │ │ │ ┃ +/// ┃ │ ├────────── Request ──────────▶ │ ┃ +/// ┃ │ │ { "link" : ... } OR │ │ ┃ +/// ┃ │ │ { "symbol" : ... } │ │ ┃ +/// ┃ │ │ │ │ ┃ +/// ┃ │ ◀────────── Response ─────────┤ │ ┃ +/// ┃ │ │ { "resolved" : ... } OR │ │ ┃ +/// ┃ │ │ { "failure" : ... } │ │ ┃ +/// ┃ │ │ │ │ ┃ +/// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +/// │ │ └─┘ +/// │ │ ╎ +/// +/// ## Interacting with a Convert Service +/// +/// When creating an out-of-process resolver using ``init(bundleID:server:convertRequestIdentifier:)`` to communicate with another process using a ``ConvertService``; +/// DocC sends that service `"resolve-reference"` messages with a``OutOfProcessReferenceResolver/Request`` payload and expects a `"resolved-reference-response"` responses with a ``OutOfProcessReferenceResolver/Response`` payload. +/// +/// Because the ``ConvertService`` messages are _implicitly_ tied to these outdated—and no longer recommended—request and response types, the richness of its responses is limited. +/// +/// - Note: when interacting with a ``ConvertService`` your service also needs to handle "asset" requests (``OutOfProcessReferenceResolver/Request/asset(_:)`` and responses that (``OutOfProcessReferenceResolver/Response/asset(_:)``) that link resolver executables don't need to handle. +/// +/// ## Topics +/// +/// - ``RequestV2`` +/// - ``ResponseV2`` /// /// ## See Also -/// - ``ExternalDocumentationSource`` -/// - ``GlobalExternalSymbolResolver`` /// - ``DocumentationContext/externalDocumentationSources`` /// - ``DocumentationContext/globalExternalSymbolResolver`` -/// - ``Request`` -/// - ``Response`` public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalExternalSymbolResolver { - private let externalLinkResolvingClient: any ExternalLinkResolving + private var implementation: any _Implementation /// The bundle identifier for the reference resolver in the other process. - public let bundleID: DocumentationBundle.Identifier + public var bundleID: DocumentationBundle.Identifier { + implementation.bundleID + } + + // This variable is used below for the `ConvertServiceFallbackResolver` conformance. + private var assetCache: [AssetReference: DataAsset] = [:] /// Creates a new reference resolver that interacts with another executable. /// /// Initializing the resolver will also launch the other executable. The other executable will remain running for the lifetime of this object. + /// This and the rest of the communication between DocC and the link resolver executable is described in /// /// - Parameters: /// - processLocation: The location of the other executable. @@ -73,12 +97,12 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE let longRunningProcess = try LongRunningProcess(location: processLocation, errorOutputHandler: errorOutputHandler) - guard case let .bundleIdentifier(decodedBundleIdentifier) = try longRunningProcess.sendAndWait(request: nil as Request?) as Response else { + guard let handshake: InitialHandshakeMessage = try? longRunningProcess.readInitialHandshakeMessage() else { throw Error.invalidBundleIdentifierOutputFromExecutable(processLocation) } - self.bundleID = .init(rawValue: decodedBundleIdentifier) - self.externalLinkResolvingClient = longRunningProcess + // This private type and protocol exist to silence deprecation warnings + self.implementation = (_ImplementationProvider() as (any _ImplementationProviding)).makeImplementation(for: handshake, longRunningProcess: longRunningProcess) } /// Creates a new reference resolver that interacts with a documentation service. @@ -90,179 +114,367 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE /// - server: The server to send link resolution requests to. /// - convertRequestIdentifier: The identifier that the resolver will use for convert requests that it sends to the server. public init(bundleID: DocumentationBundle.Identifier, server: DocumentationServer, convertRequestIdentifier: String?) throws { - self.bundleID = bundleID - self.externalLinkResolvingClient = LongRunningService( - server: server, convertRequestIdentifier: convertRequestIdentifier) + self.implementation = (_ImplementationProvider() as any _ImplementationProviding).makeImplementation( + for: .init(identifier: bundleID, capabilities: nil /* always use the V1 implementation */), + longRunningProcess: LongRunningService(server: server, convertRequestIdentifier: convertRequestIdentifier) + ) } - // MARK: External Reference Resolver - - public func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult { - switch reference { - case .resolved(let resolved): - return resolved + fileprivate struct InitialHandshakeMessage: Decodable { + var identifier: DocumentationBundle.Identifier + var capabilities: Capabilities? // The old V1 handshake didn't include this but the V2 requires it. + + init(identifier: DocumentationBundle.Identifier, capabilities: OutOfProcessReferenceResolver.Capabilities?) { + self.identifier = identifier + self.capabilities = capabilities + } + + private enum CodingKeys: CodingKey { + case bundleIdentifier // Legacy V1 handshake + case identifier, capabilities // V2 handshake + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) - case let .unresolved(unresolvedReference): - guard unresolvedReference.bundleID == bundleID else { - fatalError(""" - Attempted to resolve a local reference externally: \(unresolvedReference.description.singleQuoted). - DocC should never pass a reference to an external resolver unless it matches that resolver's bundle identifier. - """) - } - do { - guard let unresolvedTopicURL = unresolvedReference.topicURL.components.url else { - // Return the unresolved reference if the underlying URL is not valid - return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo("URL \(unresolvedReference.topicURL.absoluteString.singleQuoted) is not valid.")) - } - let resolvedInformation = try resolveInformationForTopicURL(unresolvedTopicURL) - return .success( resolvedReference(for: resolvedInformation) ) - } catch let error { - return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo(error)) + guard container.contains(.identifier) || container.contains(.bundleIdentifier) else { + throw DecodingError.keyNotFound(CodingKeys.identifier, .init(codingPath: decoder.codingPath, debugDescription: """ + Initial handshake message includes neither a '\(CodingKeys.identifier.stringValue)' key nor a '\(CodingKeys.bundleIdentifier.stringValue)' key. + """)) } + + self.identifier = try container.decodeIfPresent(DocumentationBundle.Identifier.self, forKey: .identifier) + ?? container.decode(DocumentationBundle.Identifier.self, forKey: .bundleIdentifier) + + self.capabilities = try container.decodeIfPresent(Capabilities.self, forKey: .capabilities) } } + // MARK: External Reference Resolver + + public func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult { + implementation.resolve(reference) + } + @_spi(ExternalLinks) // LinkResolver.ExternalEntity isn't stable API yet public func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - guard let resolvedInformation = referenceCache[reference.url] else { - fatalError("A topic reference that has already been resolved should always exist in the cache.") - } - return makeEntity(with: resolvedInformation, reference: reference.absoluteString) + implementation.entity(with: reference) } @_spi(ExternalLinks) // LinkResolver.ExternalEntity isn't stable API yet public func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { - guard let resolvedInformation = try? resolveInformationForSymbolIdentifier(preciseIdentifier) else { return nil } - - let reference = ResolvedTopicReference( - bundleID: "com.externally.resolved.symbol", - path: "/\(preciseIdentifier)", - sourceLanguages: sourceLanguages(for: resolvedInformation) - ) - let entity = makeEntity(with: resolvedInformation, reference: reference.absoluteString) - return (reference, entity) + implementation.symbolReferenceAndEntity(withPreciseIdentifier: preciseIdentifier) } +} + +// MARK: Implementations + +private protocol _Implementation: ExternalDocumentationSource, GlobalExternalSymbolResolver { + var bundleID: DocumentationBundle.Identifier { get } + var longRunningProcess: any ExternalLinkResolving { get } - private func makeEntity(with resolvedInformation: ResolvedInformation, reference: String) -> LinkResolver.ExternalEntity { - let (kind, role) = DocumentationContentRenderer.renderKindAndRole(resolvedInformation.kind, semantic: nil) - - var renderReference = TopicRenderReference( - identifier: .init(reference), - title: resolvedInformation.title, - // The resolved information only stores the plain text abstract https://github.com/swiftlang/swift-docc/issues/802 - abstract: [.text(resolvedInformation.abstract)], - url: resolvedInformation.url.path, - kind: kind, - role: role, - fragments: resolvedInformation.declarationFragments?.declarationFragments.map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) }, - isBeta: resolvedInformation.isBeta, - isDeprecated: (resolvedInformation.platforms ?? []).contains(where: { $0.deprecated != nil }), - images: resolvedInformation.topicImages ?? [] - ) - for variant in resolvedInformation.variants ?? [] { - if let title = variant.title { - renderReference.titleVariants.variants.append( - .init(traits: variant.traits, patch: [.replace(value: title)]) - ) - } - if let abstract = variant.abstract { - renderReference.abstractVariants.variants.append( - .init(traits: variant.traits, patch: [.replace(value: [.text(abstract)])]) - ) + // + func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult +} + +private extension _Implementation { + // Avoid some common boilerplate between implementations. + func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult { + switch reference { + case .resolved(let resolved): + return resolved + + case let .unresolved(unresolvedReference): + guard unresolvedReference.bundleID == bundleID else { + fatalError(""" + Attempted to resolve a local reference externally: \(unresolvedReference.description.singleQuoted). + DocC should never pass a reference to an external resolver unless it matches that resolver's bundle identifier. + """) + } + do { + // This is where each implementation differs + return try resolve(unresolvedReference: unresolvedReference) + } catch let error { + return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo(error)) + } + } + } +} + +// This private protocol allows the out-of-process resolver to create ImplementationV1 without deprecation warnings +private protocol _ImplementationProviding { + func makeImplementation(for handshake: OutOfProcessReferenceResolver.InitialHandshakeMessage, longRunningProcess: any ExternalLinkResolving) -> any _Implementation +} + +private extension OutOfProcessReferenceResolver { + // A concrete type with a deprecated implementation that can be cast to `_ImplementationProviding` to avoid deprecation warnings. + struct _ImplementationProvider: _ImplementationProviding { + @available(*, deprecated) // The V1 implementation is built around several now-deprecated types. This deprecation silences those depreciation warnings. + func makeImplementation(for handshake: OutOfProcessReferenceResolver.InitialHandshakeMessage, longRunningProcess: any ExternalLinkResolving) -> any _Implementation { + if let capabilities = handshake.capabilities { + return ImplementationV2(longRunningProcess: longRunningProcess, bundleID: handshake.identifier, executableCapabilities: capabilities) + } else { + return ImplementationV1(longRunningProcess: longRunningProcess, bundleID: handshake.identifier) } - if let declarationFragments = variant.declarationFragments { - renderReference.fragmentsVariants.variants.append( - .init(traits: variant.traits, patch: [.replace(value: declarationFragments?.declarationFragments.map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) })]) - ) + } + } +} + +// MARK: Version 1 (deprecated) + +extension OutOfProcessReferenceResolver { + /// The original—no longer recommended—version of the out-of-process resolver implementation. + /// + /// This implementation uses ``Request`` and ``Response`` which aren't extensible and have restrictions on the details of the response payloads. + @available(*, deprecated) // The V1 implementation is built around several now-deprecated types. This deprecation silences those depreciation warnings. + private final class ImplementationV1: _Implementation { + let bundleID: DocumentationBundle.Identifier + let longRunningProcess: any ExternalLinkResolving + + init(longRunningProcess: any ExternalLinkResolving, bundleID: DocumentationBundle.Identifier) { + self.longRunningProcess = longRunningProcess + self.bundleID = bundleID + } + + // This is fileprivate so that the ConvertService conformance below can access it. + fileprivate private(set) var referenceCache: [URL: ResolvedInformation] = [:] + private var symbolCache: [String: ResolvedInformation] = [:] + + func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult { + guard let unresolvedTopicURL = unresolvedReference.topicURL.components.url else { + // Return the unresolved reference if the underlying URL is not valid + return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo("URL \(unresolvedReference.topicURL.absoluteString.singleQuoted) is not valid.")) } + let resolvedInformation = try resolveInformationForTopicURL(unresolvedTopicURL) + return .success( resolvedReference(for: resolvedInformation) ) } - let dependencies = RenderReferenceDependencies( - topicReferences: [], - linkReferences: (resolvedInformation.references ?? []).compactMap { $0 as? LinkReference }, - imageReferences: (resolvedInformation.references ?? []).compactMap { $0 as? ImageReference } - ) - return LinkResolver.ExternalEntity( - topicRenderReference: renderReference, - renderReferenceDependencies: dependencies, - sourceLanguages: resolvedInformation.availableLanguages, - symbolKind: DocumentationNode.symbolKind(for: resolvedInformation.kind) - ) - } - - // MARK: Implementation - - private var referenceCache: [URL: ResolvedInformation] = [:] - private var symbolCache: [String: ResolvedInformation] = [:] - private var assetCache: [AssetReference: DataAsset] = [:] - - /// Makes a call to the other process to resolve information about a page based on its URL. - func resolveInformationForTopicURL(_ topicURL: URL) throws -> ResolvedInformation { - if let cachedInformation = referenceCache[topicURL] { - return cachedInformation + func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { + guard let resolvedInformation = referenceCache[reference.url] else { + fatalError("A topic reference that has already been resolved should always exist in the cache.") + } + return makeEntity(with: resolvedInformation, reference: reference.absoluteString) } - let response: Response = try externalLinkResolvingClient.sendAndWait(request: Request.topic(topicURL)) + func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { + guard let resolvedInformation = try? resolveInformationForSymbolIdentifier(preciseIdentifier) else { return nil } + + let reference = ResolvedTopicReference( + bundleID: "com.externally.resolved.symbol", + path: "/\(preciseIdentifier)", + sourceLanguages: sourceLanguages(for: resolvedInformation) + ) + let entity = makeEntity(with: resolvedInformation, reference: reference.absoluteString) + return (reference, entity) + } - switch response { - case .bundleIdentifier: - throw Error.executableSentBundleIdentifierAgain + /// Makes a call to the other process to resolve information about a page based on its URL. + private func resolveInformationForTopicURL(_ topicURL: URL) throws -> ResolvedInformation { + if let cachedInformation = referenceCache[topicURL] { + return cachedInformation + } + + let response: Response = try longRunningProcess.sendAndWait(request: Request.topic(topicURL)) - case .errorMessage(let errorMessage): - throw Error.forwardedErrorFromClient(errorMessage: errorMessage) + switch response { + case .bundleIdentifier: + throw Error.executableSentBundleIdentifierAgain + + case .errorMessage(let errorMessage): + throw Error.forwardedErrorFromClient(errorMessage: errorMessage) + + case .resolvedInformation(let resolvedInformation): + // Cache the information for the resolved reference, that's what's will be used when returning the entity later. + let resolvedReference = resolvedReference(for: resolvedInformation) + referenceCache[resolvedReference.url] = resolvedInformation + return resolvedInformation + + default: + throw Error.unexpectedResponse(response: response, requestDescription: "topic URL") + } + } + + /// Makes a call to the other process to resolve information about a symbol based on its precise identifier. + private func resolveInformationForSymbolIdentifier(_ preciseIdentifier: String) throws -> ResolvedInformation { + if let cachedInformation = symbolCache[preciseIdentifier] { + return cachedInformation + } - case .resolvedInformation(let resolvedInformation): - // Cache the information for the resolved reference, that's what's will be used when returning the entity later. - let resolvedReference = resolvedReference(for: resolvedInformation) - referenceCache[resolvedReference.url] = resolvedInformation - return resolvedInformation + let response: Response = try longRunningProcess.sendAndWait(request: Request.symbol(preciseIdentifier)) - default: - throw Error.unexpectedResponse(response: response, requestDescription: "topic URL") + switch response { + case .bundleIdentifier: + throw Error.executableSentBundleIdentifierAgain + + case .errorMessage(let errorMessage): + throw Error.forwardedErrorFromClient(errorMessage: errorMessage) + + case .resolvedInformation(let resolvedInformation): + symbolCache[preciseIdentifier] = resolvedInformation + return resolvedInformation + + default: + throw Error.unexpectedResponse(response: response, requestDescription: "symbol ID") + } + } + + private func resolvedReference(for resolvedInformation: ResolvedInformation) -> ResolvedTopicReference { + return ResolvedTopicReference( + bundleID: bundleID, + path: resolvedInformation.url.path, + fragment: resolvedInformation.url.fragment, + sourceLanguages: sourceLanguages(for: resolvedInformation) + ) + } + + private func sourceLanguages(for resolvedInformation: ResolvedInformation) -> Set { + // It is expected that the available languages contains the main language + return resolvedInformation.availableLanguages.union(CollectionOfOne(resolvedInformation.language)) + } + + private func makeEntity(with resolvedInformation: ResolvedInformation, reference: String) -> LinkResolver.ExternalEntity { + return LinkResolver.ExternalEntity( + kind: resolvedInformation.kind, + language: resolvedInformation.language, + relativePresentationURL: resolvedInformation.url.withoutHostAndPortAndScheme(), + referenceURL: URL(string: reference)!, + title: resolvedInformation.title, + // The resolved information only stores the plain text abstract and can't be changed. Use the version 2 communication protocol to support rich abstracts. + abstract: [.text(resolvedInformation.abstract)], + availableLanguages: resolvedInformation.availableLanguages, + platforms: resolvedInformation.platforms, + taskGroups: nil, + usr: nil, + declarationFragments: resolvedInformation.declarationFragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, + redirects: nil, + topicImages: resolvedInformation.topicImages, + references: resolvedInformation.references, + variants: (resolvedInformation.variants ?? []).map { variant in + .init( + traits: variant.traits, + kind: variant.kind, + language: variant.language, + relativePresentationURL: variant.url?.withoutHostAndPortAndScheme(), + title: variant.title, + abstract: variant.abstract.map { [.text($0)] }, + taskGroups: nil, + usr: nil, + declarationFragments: variant.declarationFragments.map { fragments in + fragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) } + } + ) + } + ) } } - - /// Makes a call to the other process to resolve information about a symbol based on its precise identifier. - private func resolveInformationForSymbolIdentifier(_ preciseIdentifier: String) throws -> ResolvedInformation { - if let cachedInformation = symbolCache[preciseIdentifier] { - return cachedInformation +} + +// MARK: Version 2 + +extension OutOfProcessReferenceResolver { + private final class ImplementationV2: _Implementation { + let longRunningProcess: any ExternalLinkResolving + let bundleID: DocumentationBundle.Identifier + let executableCapabilities: Capabilities + + init( + longRunningProcess: any ExternalLinkResolving, + bundleID: DocumentationBundle.Identifier, + executableCapabilities: Capabilities + ) { + self.longRunningProcess = longRunningProcess + self.bundleID = bundleID + self.executableCapabilities = executableCapabilities } - let response: Response = try externalLinkResolvingClient.sendAndWait(request: Request.symbol(preciseIdentifier)) + private var linkCache: [String /* either a USR or an absolute UnresolvedTopicReference */: LinkDestinationSummary] = [:] - switch response { - case .bundleIdentifier: - throw Error.executableSentBundleIdentifierAgain + func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult { + let linkString = unresolvedReference.topicURL.absoluteString + if let cachedSummary = linkCache[linkString] { + return .success( makeReference(for: cachedSummary) ) + } - case .errorMessage(let errorMessage): - throw Error.forwardedErrorFromClient(errorMessage: errorMessage) + let response: ResponseV2 = try longRunningProcess.sendAndWait(request: RequestV2.link(linkString)) - case .resolvedInformation(let resolvedInformation): - symbolCache[preciseIdentifier] = resolvedInformation - return resolvedInformation + switch response { + case .identifierAndCapabilities: + throw Error.executableSentBundleIdentifierAgain + + case .failure(let diagnosticMessage): + let solutions: [Solution] = (diagnosticMessage.solutions ?? []).map { + Solution(summary: $0.summary, replacements: $0.replacement.map { replacement in + [Replacement( + // The replacement ranges are relative to the link itself. + // To replace the entire link, we create a range from 0 to the original length, both offset by -4 (the "doc:" length) + range: SourceLocation(line: 0, column: -4, source: nil) ..< SourceLocation(line: 0, column: linkString.utf8.count - 4, source: nil), + replacement: replacement + )] + } ?? []) + } + return .failure( + unresolvedReference, + TopicReferenceResolutionErrorInfo(diagnosticMessage.summary, solutions: solutions) + ) + + case .resolved(let linkSummary): + // Cache the information for the original authored link + linkCache[linkString] = linkSummary + // Cache the information for the resolved reference. That's what's will be used when returning the entity later. + let reference = makeReference(for: linkSummary) + linkCache[reference.absoluteString] = linkSummary + if let usr = linkSummary.usr { + // If the page is a symbol, cache its information for the USR as well. + linkCache[usr] = linkSummary + } + return .success(reference) + + default: + throw Error.unexpectedResponse(response: response, requestDescription: "topic link") + } + } + + func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { + guard let linkSummary = linkCache[reference.url.standardized.absoluteString] else { + fatalError("A topic reference that has already been resolved should always exist in the cache.") + } + return linkSummary + } + + func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { + if let cachedSummary = linkCache[preciseIdentifier] { + return (makeReference(for: cachedSummary), cachedSummary) + } - default: - throw Error.unexpectedResponse(response: response, requestDescription: "symbol ID") + guard case ResponseV2.resolved(let linkSummary)? = try? longRunningProcess.sendAndWait(request: RequestV2.symbol(preciseIdentifier)) else { + return nil + } + + // Cache the information for the USR + linkCache[preciseIdentifier] = linkSummary + + // Cache the information for the resolved reference. + let reference = makeReference(for: linkSummary) + linkCache[reference.absoluteString] = linkSummary + + return (reference, linkSummary) + } + + private func makeReference(for linkSummary: LinkDestinationSummary) -> ResolvedTopicReference { + ResolvedTopicReference( + bundleID: linkSummary.referenceURL.host.map { .init(rawValue: $0) } ?? "unknown", + path: linkSummary.referenceURL.path, + fragment: linkSummary.referenceURL.fragment, + sourceLanguages: linkSummary.availableLanguages + ) } - } - - private func resolvedReference(for resolvedInformation: ResolvedInformation) -> ResolvedTopicReference { - return ResolvedTopicReference( - bundleID: bundleID, - path: resolvedInformation.url.path, - fragment: resolvedInformation.url.fragment, - sourceLanguages: sourceLanguages(for: resolvedInformation) - ) - } - - private func sourceLanguages(for resolvedInformation: ResolvedInformation) -> Set { - // It is expected that the available languages contains the main language - return resolvedInformation.availableLanguages.union(CollectionOfOne(resolvedInformation.language)) } } +// MARK: Cross process communication + private protocol ExternalLinkResolving { - func sendAndWait(request: Request?) throws -> Response + func sendAndWait(request: Request) throws -> Response } private class LongRunningService: ExternalLinkResolving { @@ -273,7 +485,7 @@ private class LongRunningService: ExternalLinkResolving { server: server, convertRequestIdentifier: convertRequestIdentifier) } - func sendAndWait(request: Request?) throws -> Response { + func sendAndWait(request: Request) throws -> Response { let responseData = try client.sendAndWait(request) return try JSONDecoder().decode(Response.self, from: responseData) } @@ -290,6 +502,7 @@ private class LongRunningProcess: ExternalLinkResolving { init(location: URL, errorOutputHandler: @escaping (String) -> Void) throws { let process = Process() process.executableURL = location + process.arguments = ["--capabilities", "\(OutOfProcessReferenceResolver.Capabilities().rawValue)"] process.standardInput = input process.standardOutput = output @@ -301,7 +514,7 @@ private class LongRunningProcess: ExternalLinkResolving { errorReadSource.setEventHandler { [errorOutput] in let data = errorOutput.fileHandleForReading.availableData let errorMessage = String(data: data, encoding: .utf8) - ?? "<\(ByteCountFormatter.string(fromByteCount: Int64(data.count), countStyle: .memory)) of non-utf8 data>" + ?? "<\(ByteCountFormatter.string(fromByteCount: Int64(data.count), countStyle: .memory)) of non-utf8 data>" errorOutputHandler(errorMessage) } @@ -319,16 +532,25 @@ private class LongRunningProcess: ExternalLinkResolving { private let output = Pipe() private let errorOutput = Pipe() private let errorReadSource: any DispatchSourceRead - - func sendAndWait(request: Request?) throws -> Response { - if let request { - guard let requestString = String(data: try JSONEncoder().encode(request), encoding: .utf8)?.appending("\n"), - let requestData = requestString.data(using: .utf8) - else { - throw OutOfProcessReferenceResolver.Error.unableToEncodeRequestToClient(requestDescription: request.description) - } - input.fileHandleForWriting.write(requestData) + + func readInitialHandshakeMessage() throws -> Response { + return try _readResponse() + } + + func sendAndWait(request: Request) throws -> Response { + // Send + guard let requestString = String(data: try JSONEncoder().encode(request), encoding: .utf8)?.appending("\n"), + let requestData = requestString.data(using: .utf8) + else { + throw OutOfProcessReferenceResolver.Error.unableToEncodeRequestToClient(requestDescription: "\(request)") } + input.fileHandleForWriting.write(requestData) + + // Receive + return try _readResponse() + } + + private func _readResponse() throws -> Response { var response = output.fileHandleForReading.availableData guard !response.isEmpty else { throw OutOfProcessReferenceResolver.Error.processDidExit(code: Int(process.terminationStatus)) @@ -341,8 +563,8 @@ private class LongRunningProcess: ExternalLinkResolving { // To avoid blocking forever we check if the response can be decoded after each chunk of data. return try JSONDecoder().decode(Response.self, from: response) } catch { - if case DecodingError.dataCorrupted = error, // If the data wasn't valid JSON, read more data and try to decode it again. - response.count.isMultiple(of: Int(PIPE_BUF)) // To reduce the risk of deadlocking, check that bytes so far is a multiple of the pipe buffer size. + if case DecodingError.dataCorrupted = error, // If the data wasn't valid JSON, read more data and try to decode it again. + response.count.isMultiple(of: Int(PIPE_BUF)) // To reduce the risk of deadlocking, check that bytes so far is a multiple of the pipe buffer size. { let moreResponseData = output.fileHandleForReading.availableData guard !moreResponseData.isEmpty else { @@ -351,7 +573,7 @@ private class LongRunningProcess: ExternalLinkResolving { response += moreResponseData continue } - + // Other errors are re-thrown as wrapped errors. throw OutOfProcessReferenceResolver.Error.unableToDecodeResponseFromClient(response, error) } @@ -364,13 +586,19 @@ private class LongRunningProcess: ExternalLinkResolving { fatalError("Cannot initialize an out of process resolver outside of macOS or Linux platforms.") } - func sendAndWait(request: Request?) throws -> Response { + func readInitialHandshakeMessage() throws -> Response { + fatalError("Cannot call sendAndWait in non macOS/Linux platform.") + } + + func sendAndWait(request: Request) throws -> Response { fatalError("Cannot call sendAndWait in non macOS/Linux platform.") } #endif } +// MARK: Error + extension OutOfProcessReferenceResolver { /// Errors that may occur when communicating with an external reference resolver. enum Error: Swift.Error, DescribedError { @@ -400,7 +628,7 @@ extension OutOfProcessReferenceResolver { /// The request type was not known (neither 'topic' nor 'symbol'). case unknownTypeOfRequest /// Received an unknown type of response to sent request. - case unexpectedResponse(response: Response, requestDescription: String) + case unexpectedResponse(response: Any, requestDescription: String) /// A plain text representation of the error message. var errorDescription: String { @@ -435,360 +663,46 @@ extension OutOfProcessReferenceResolver { } } -extension OutOfProcessReferenceResolver { - - // MARK: Request & Response - - /// A request message to send to the external link resolver. - /// - /// This can either be a request to resolve a topic URL or to resolve a symbol based on its precise identifier. - public enum Request: Codable, CustomStringConvertible { - /// A request to resolve a topic URL - case topic(URL) - /// A request to resolve a symbol based on its precise identifier. - case symbol(String) - /// A request to resolve an asset. - case asset(AssetReference) - - private enum CodingKeys: CodingKey { - case topic - case symbol - case asset - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .topic(let url): - try container.encode(url, forKey: .topic) - case .symbol(let identifier): - try container.encode(identifier, forKey: .symbol) - case .asset(let assetReference): - try container.encode(assetReference, forKey: .asset) - } - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - switch container.allKeys.first { - case .topic?: - self = .topic(try container.decode(URL.self, forKey: .topic)) - case .symbol?: - self = .symbol(try container.decode(String.self, forKey: .symbol)) - case .asset?: - self = .asset(try container.decode(AssetReference.self, forKey: .asset)) - case nil: - throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest - } - } - - /// A plain text representation of the request message. - public var description: String { - switch self { - case .topic(let url): - return "topic: \(url.absoluteString.singleQuoted)" - case .symbol(let identifier): - return "symbol: \(identifier.singleQuoted)" - case .asset(let asset): - return "asset with name: \(asset.assetName), bundle identifier: \(asset.bundleID)" - } - } - } - - /// A response message from the external link resolver. - public enum Response: Codable { - /// A bundle identifier response. - /// - /// This message should only be sent once, after the external link resolver has launched. - case bundleIdentifier(String) - /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. - case errorMessage(String) - /// A response with the resolved information about the requested topic or symbol. - case resolvedInformation(ResolvedInformation) - /// A response with information about the resolved asset. - case asset(DataAsset) - - enum CodingKeys: String, CodingKey { - case bundleIdentifier - case errorMessage - case resolvedInformation - case asset - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - switch container.allKeys.first { - case .bundleIdentifier?: - self = .bundleIdentifier(try container.decode(String.self, forKey: .bundleIdentifier)) - case .errorMessage?: - self = .errorMessage(try container.decode(String.self, forKey: .errorMessage)) - case .resolvedInformation?: - self = .resolvedInformation(try container.decode(ResolvedInformation.self, forKey: .resolvedInformation)) - case .asset?: - self = .asset(try container.decode(DataAsset.self, forKey: .asset)) - case nil: - throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient - } - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .bundleIdentifier(let bundleIdentifier): - try container.encode(bundleIdentifier, forKey: .bundleIdentifier) - case .errorMessage(let errorMessage): - try container.encode(errorMessage, forKey: .errorMessage) - case .resolvedInformation(let resolvedInformation): - try container.encode(resolvedInformation, forKey: .resolvedInformation) - case .asset(let assetReference): - try container.encode(assetReference, forKey: .asset) - } - } - } - - // MARK: Resolved Information - - /// A type used to transfer information about a resolved reference to DocC from from a reference resolver in another executable. - public struct ResolvedInformation: Codable { - // This type is duplicating the information from LinkDestinationSummary with some minor differences. - // Changes generally need to be made in both places. It would be good to replace this with LinkDestinationSummary. - // FIXME: https://github.com/swiftlang/swift-docc/issues/802 - - /// Information about the resolved kind. - public let kind: DocumentationNode.Kind - /// Information about the resolved URL. - public let url: URL - /// Information about the resolved title. - public let title: String // DocumentationNode.Name - /// Information about the resolved abstract. - public let abstract: String // Markup - /// Information about the resolved language. - public let language: SourceLanguage - /// Information about the languages where the resolved node is available. - public let availableLanguages: Set - /// Information about the platforms and their versions where the resolved node is available, if any. - public let platforms: [PlatformAvailability]? - /// Information about the resolved declaration fragments, if any. - public let declarationFragments: DeclarationFragments? - - // We use the real types here because they're Codable and don't have public member-wise initializers. - - /// Platform availability for a resolved symbol reference. - public typealias PlatformAvailability = AvailabilityRenderItem - - /// The declaration fragments for a resolved symbol reference. - public typealias DeclarationFragments = SymbolGraph.Symbol.DeclarationFragments - - /// The platform names, derived from the platform availability. - public var platformNames: Set? { - return platforms.map { platforms in Set(platforms.compactMap { $0.name }) } - } - - /// Images that are used to represent the summarized element. - public var topicImages: [TopicImage]? - - /// References used in the content of the summarized element. - public var references: [any RenderReference]? - - /// The variants of content (kind, url, title, abstract, language, declaration) for this resolver information. - public var variants: [Variant]? - - /// A value that indicates whether this symbol is under development and likely to change. - var isBeta: Bool { - guard let platforms, !platforms.isEmpty else { - return false - } - - return platforms.allSatisfy { $0.isBeta == true } - } - - /// Creates a new resolved information value with all its values. - /// - /// - Parameters: - /// - kind: The resolved kind. - /// - url: The resolved URL. - /// - title: The resolved title - /// - abstract: The resolved (plain text) abstract. - /// - language: The resolved language. - /// - availableLanguages: The languages where the resolved node is available. - /// - platforms: The platforms and their versions where the resolved node is available, if any. - /// - declarationFragments: The resolved declaration fragments, if any. - /// - topicImages: Images that are used to represent the summarized element. - /// - references: References used in the content of the summarized element. - /// - variants: The variants of content for this resolver information. - public init( - kind: DocumentationNode.Kind, - url: URL, - title: String, - abstract: String, - language: SourceLanguage, - availableLanguages: Set, - platforms: [PlatformAvailability]? = nil, - declarationFragments: DeclarationFragments? = nil, - topicImages: [TopicImage]? = nil, - references: [any RenderReference]? = nil, - variants: [Variant]? = nil - ) { - self.kind = kind - self.url = url - self.title = title - self.abstract = abstract - self.language = language - self.availableLanguages = availableLanguages - self.platforms = platforms - self.declarationFragments = declarationFragments - self.topicImages = topicImages - self.references = references - self.variants = variants - } - - /// A variant of content for the resolved information. - /// - /// - Note: All properties except for ``traits`` are optional. If a property is `nil` it means that the value is the same as the resolved information's value. - public struct Variant: Codable { - /// The traits of the variant. - public let traits: [RenderNode.Variant.Trait] - - /// A wrapper for variant values that can either be specified, meaning the variant has a custom value, or not, meaning the variant has the same value as the resolved information. - /// - /// This alias is used to make the property declarations more explicit while at the same time offering the convenient syntax of optionals. - public typealias VariantValue = Optional - - /// The kind of the variant or `nil` if the kind is the same as the resolved information. - public let kind: VariantValue - /// The url of the variant or `nil` if the url is the same as the resolved information. - public let url: VariantValue - /// The title of the variant or `nil` if the title is the same as the resolved information. - public let title: VariantValue - /// The abstract of the variant or `nil` if the abstract is the same as the resolved information. - public let abstract: VariantValue - /// The language of the variant or `nil` if the language is the same as the resolved information. - public let language: VariantValue - /// The declaration fragments of the variant or `nil` if the declaration is the same as the resolved information. - /// - /// If the resolver information has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. - public let declarationFragments: VariantValue - - /// Creates a new resolved information variant with the values that are different from the resolved information values. - /// - /// - Parameters: - /// - traits: The traits of the variant. - /// - kind: The resolved kind. - /// - url: The resolved URL. - /// - title: The resolved title - /// - abstract: The resolved (plain text) abstract. - /// - language: The resolved language. - /// - declarationFragments: The resolved declaration fragments, if any. - public init( - traits: [RenderNode.Variant.Trait], - kind: VariantValue = nil, - url: VariantValue = nil, - title: VariantValue = nil, - abstract: VariantValue = nil, - language: VariantValue = nil, - declarationFragments: VariantValue = nil - ) { - self.traits = traits - self.kind = kind - self.url = url - self.title = title - self.abstract = abstract - self.language = language - self.declarationFragments = declarationFragments - } - } - } -} - -extension OutOfProcessReferenceResolver.ResolvedInformation { - enum CodingKeys: CodingKey { - case kind - case url - case title - case abstract - case language - case availableLanguages - case platforms - case declarationFragments - case topicImages - case references - case variants - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) - url = try container.decode(URL.self, forKey: .url) - title = try container.decode(String.self, forKey: .title) - abstract = try container.decode(String.self, forKey: .abstract) - language = try container.decode(SourceLanguage.self, forKey: .language) - availableLanguages = try container.decode(Set.self, forKey: .availableLanguages) - platforms = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.PlatformAvailability].self, forKey: .platforms) - declarationFragments = try container.decodeIfPresent(OutOfProcessReferenceResolver.ResolvedInformation.DeclarationFragments.self, forKey: .declarationFragments) - topicImages = try container.decodeIfPresent([TopicImage].self, forKey: .topicImages) - references = try container.decodeIfPresent([CodableRenderReference].self, forKey: .references).map { decodedReferences in - decodedReferences.map(\.reference) - } - variants = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.Variant].self, forKey: .variants) - - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(self.kind, forKey: .kind) - try container.encode(self.url, forKey: .url) - try container.encode(self.title, forKey: .title) - try container.encode(self.abstract, forKey: .abstract) - try container.encode(self.language, forKey: .language) - try container.encode(self.availableLanguages, forKey: .availableLanguages) - try container.encodeIfPresent(self.platforms, forKey: .platforms) - try container.encodeIfPresent(self.declarationFragments, forKey: .declarationFragments) - try container.encodeIfPresent(self.topicImages, forKey: .topicImages) - try container.encodeIfPresent(references?.map { CodableRenderReference($0) }, forKey: .references) - try container.encodeIfPresent(self.variants, forKey: .variants) - } -} +// MARK: Convert Service extension OutOfProcessReferenceResolver: ConvertServiceFallbackResolver { @_spi(ExternalLinks) + @available(*, deprecated, message: "The ConvertService is implicitly reliant on the deprecated `Request` and `Response` types.") public func entityIfPreviouslyResolved(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity? { - guard referenceCache.keys.contains(reference.url) else { return nil } + guard let implementation = implementation as? ImplementationV1 else { + assertionFailure("ConvertServiceFallbackResolver expects V1 requests and responses") + return nil + } + + guard implementation.referenceCache.keys.contains(reference.url) else { return nil } var entity = entity(with: reference) // The entity response doesn't include the assets that it references. // Before returning the entity, make sure that its references assets are included among the image dependencies. - for image in entity.topicRenderReference.images { + var references = entity.references ?? [] + + for image in entity.topicImages ?? [] { if let asset = resolve(assetNamed: image.identifier.identifier) { - entity.renderReferenceDependencies.imageReferences.append(ImageReference(identifier: image.identifier, imageAsset: asset)) + references.append(ImageReference(identifier: image.identifier, imageAsset: asset)) } } + if !references.isEmpty { + entity.references = references + } + return entity } + @available(*, deprecated, message: "The ConvertService is implicitly reliant on the deprecated `Request` and `Response` types.") func resolve(assetNamed assetName: String) -> DataAsset? { - return try? resolveInformationForAsset(named: assetName) - } - - func resolveInformationForAsset(named assetName: String) throws -> DataAsset { let assetReference = AssetReference(assetName: assetName, bundleID: bundleID) if let asset = assetCache[assetReference] { return asset } - let response = try externalLinkResolvingClient.sendAndWait( - request: Request.asset(AssetReference(assetName: assetName, bundleID: bundleID)) - ) as Response - - switch response { - case .asset(let asset): - assetCache[assetReference] = asset - return asset - case .errorMessage(let errorMessage): - throw Error.forwardedErrorFromClient(errorMessage: errorMessage) - default: - throw Error.unexpectedResponse(response: response, requestDescription: "asset") + guard case .asset(let asset)? = try? implementation.longRunningProcess.sendAndWait(request: Request.asset(assetReference)) as Response else { + return nil } + return asset } } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift index 6e6fdbb3ab..4c6fac3500 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift @@ -87,31 +87,10 @@ final class ExternalPathHierarchyResolver { /// /// - Precondition: The `reference` was previously resolved by this resolver. func entity(_ reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - guard let resolvedInformation = content[reference] else { + guard let alreadyResolvedSummary = content[reference] else { fatalError("The resolver should only be asked for entities that it resolved.") } - - let topicReferences: [ResolvedTopicReference] = (resolvedInformation.references ?? []).compactMap { - guard let renderReference = $0 as? TopicRenderReference, - let url = URL(string: renderReference.identifier.identifier), - let bundleID = url.host - else { - return nil - } - return ResolvedTopicReference(bundleID: .init(rawValue: bundleID), path: url.path, fragment: url.fragment, sourceLanguage: .swift) - } - let dependencies = RenderReferenceDependencies( - topicReferences: topicReferences, - linkReferences: (resolvedInformation.references ?? []).compactMap { $0 as? LinkReference }, - imageReferences: (resolvedInformation.references ?? []).compactMap { $0 as? ImageReference } - ) - - return .init( - topicRenderReference: resolvedInformation.topicRenderReference(), - renderReferenceDependencies: dependencies, - sourceLanguages: resolvedInformation.availableLanguages, - symbolKind: DocumentationNode.symbolKind(for: resolvedInformation.kind) - ) + return alreadyResolvedSummary } // MARK: Deserialization @@ -182,9 +161,9 @@ private extension Sequence { // MARK: ExternalEntity -private extension LinkDestinationSummary { +extension LinkDestinationSummary { /// A value that indicates whether this symbol is under development and likely to change. - var isBeta: Bool { + private var isBeta: Bool { guard let platforms, !platforms.isEmpty else { return false } @@ -193,7 +172,7 @@ private extension LinkDestinationSummary { } /// Create a topic render render reference for this link summary and its content variants. - func topicRenderReference() -> TopicRenderReference { + func makeTopicRenderReference() -> TopicRenderReference { let (kind, role) = DocumentationContentRenderer.renderKindAndRole(kind, semantic: nil) var titleVariants = VariantCollection(defaultValue: title) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift index 8f0966fc79..8fa9575bd5 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift @@ -13,69 +13,76 @@ import SymbolKit /// A rendering-friendly representation of a external node. package struct ExternalRenderNode { - /// Underlying external entity backing this external node. - private var externalEntity: LinkResolver.ExternalEntity - + private var entity: LinkResolver.ExternalEntity + private var topicRenderReference: TopicRenderReference + /// The bundle identifier for this external node. private var bundleIdentifier: DocumentationBundle.Identifier + // This type is designed to misrepresent external content as local content to fit in with the navigator. + // This spreads the issue to more code rather than fixing it, which adds technical debt and can be fragile. + // + // At the time of writing this comment, this type and the issues it comes with has spread to 6 files (+ 3 test files). + // Luckily, none of that code is public API so we can modify or even remove it without compatibility restrictions. init(externalEntity: LinkResolver.ExternalEntity, bundleIdentifier: DocumentationBundle.Identifier) { - self.externalEntity = externalEntity + self.entity = externalEntity self.bundleIdentifier = bundleIdentifier + self.topicRenderReference = externalEntity.makeTopicRenderReference() } /// The identifier of the external render node. package var identifier: ResolvedTopicReference { ResolvedTopicReference( bundleID: bundleIdentifier, - path: externalEntity.topicRenderReference.url, - sourceLanguages: externalEntity.sourceLanguages + path: entity.referenceURL.path, + fragment: entity.referenceURL.fragment, + sourceLanguages: entity.availableLanguages ) } /// The kind of this documentation node. var kind: RenderNode.Kind { - externalEntity.topicRenderReference.kind + topicRenderReference.kind } /// The symbol kind of this documentation node. /// /// This value is `nil` if the referenced page is not a symbol. var symbolKind: SymbolGraph.Symbol.KindIdentifier? { - externalEntity.symbolKind + DocumentationNode.symbolKind(for: entity.kind) } /// The additional "role" assigned to the symbol, if any /// /// This value is `nil` if the referenced page is not a symbol. var role: String? { - externalEntity.topicRenderReference.role + topicRenderReference.role } /// The variants of the title. var titleVariants: VariantCollection { - externalEntity.topicRenderReference.titleVariants + topicRenderReference.titleVariants } /// The variants of the abbreviated declaration of the symbol to display in navigation. var navigatorTitleVariants: VariantCollection<[DeclarationRenderSection.Token]?> { - externalEntity.topicRenderReference.navigatorTitleVariants + topicRenderReference.navigatorTitleVariants } /// Author provided images that represent this page. var images: [TopicImage] { - externalEntity.topicRenderReference.images + entity.topicImages ?? [] } /// The identifier of the external reference. var externalIdentifier: RenderReferenceIdentifier { - externalEntity.topicRenderReference.identifier + topicRenderReference.identifier } /// List of variants of the same external node for various languages. var variants: [RenderNode.Variant]? { - externalEntity.sourceLanguages.map { - RenderNode.Variant(traits: [.interfaceLanguage($0.id)], paths: [externalEntity.topicRenderReference.url]) + entity.availableLanguages.map { + RenderNode.Variant(traits: [.interfaceLanguage($0.id)], paths: [topicRenderReference.url]) } } @@ -83,13 +90,16 @@ package struct ExternalRenderNode { /// /// This value is `false` if the referenced page is not a symbol. var isBeta: Bool { - externalEntity.topicRenderReference.isBeta + topicRenderReference.isBeta } } /// A language specific representation of an external render node value for building a navigator index. struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { - var identifier: ResolvedTopicReference + private var _identifier: ResolvedTopicReference + var identifier: ResolvedTopicReference { + _identifier + } var kind: RenderNode.Kind var metadata: ExternalRenderNodeMetadataRepresentation @@ -109,7 +119,7 @@ struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { } let traits = trait.map { [$0] } ?? [] - self.identifier = renderNode.identifier.withSourceLanguages(Set(arrayLiteral: traitLanguage)) + self._identifier = renderNode.identifier.withSourceLanguages([traitLanguage]) self.kind = renderNode.kind self.metadata = ExternalRenderNodeMetadataRepresentation( diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift index 20291f286d..27cd2d2a1a 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift @@ -9,7 +9,6 @@ */ import Foundation -public import SymbolKit /// A class that resolves documentation links by orchestrating calls to other link resolver implementations. public class LinkResolver { @@ -42,53 +41,7 @@ public class LinkResolver { /// The minimal information about an external entity necessary to render links to it on another page. @_spi(ExternalLinks) // This isn't stable API yet. - public struct ExternalEntity { - /// Creates a new external entity. - /// - Parameters: - /// - topicRenderReference: The render reference for this external topic. - /// - renderReferenceDependencies: Any dependencies for the render reference. - /// - sourceLanguages: The different source languages for which this page is available. - /// - symbolKind: The kind of symbol that's being referenced. - @_spi(ExternalLinks) - public init( - topicRenderReference: TopicRenderReference, - renderReferenceDependencies: RenderReferenceDependencies, - sourceLanguages: Set, - symbolKind: SymbolGraph.Symbol.KindIdentifier? = nil - ) { - self.topicRenderReference = topicRenderReference - self.renderReferenceDependencies = renderReferenceDependencies - self.sourceLanguages = sourceLanguages - self.symbolKind = symbolKind - } - - /// The render reference for this external topic. - var topicRenderReference: TopicRenderReference - /// Any dependencies for the render reference. - /// - /// For example, if the external content contains links or images, those are included here. - var renderReferenceDependencies: RenderReferenceDependencies - /// The different source languages for which this page is available. - var sourceLanguages: Set - /// The kind of symbol that's being referenced. - /// - /// This value is `nil` if the entity does not reference a symbol. - /// - /// For example, the navigator requires specific knowledge about what type of external symbol is being linked to. - var symbolKind: SymbolGraph.Symbol.KindIdentifier? - - /// Creates a pre-render new topic content value to be added to a render context's reference store. - func topicContent() -> RenderReferenceStore.TopicContent { - return .init( - renderReference: topicRenderReference, - canonicalPath: nil, - taskGroups: nil, - source: nil, - isDocumentationExtensionContent: false, - renderReferenceDependencies: renderReferenceDependencies - ) - } - } + public typealias ExternalEntity = LinkDestinationSummary // Currently we use the same format as DocC outputs for its own pages. That may change depending on what information we need here. /// Attempts to resolve an unresolved reference. /// @@ -268,3 +221,42 @@ private final class FallbackResolverBasedLinkResolver { return nil } } + +extension LinkResolver.ExternalEntity { + /// Creates a pre-render new topic content value to be added to a render context's reference store. + func makeTopicContent() -> RenderReferenceStore.TopicContent { + .init( + renderReference: makeTopicRenderReference(), + canonicalPath: nil, + taskGroups: nil, + source: nil, + isDocumentationExtensionContent: false, + renderReferenceDependencies: makeRenderDependencies() + ) + } + + func makeRenderDependencies() -> RenderReferenceDependencies { + guard let references else { return .init() } + + return .init( + topicReferences: references.compactMap { ($0 as? TopicRenderReference)?.topicReference(languages: availableLanguages) }, + linkReferences: references.compactMap { $0 as? LinkReference }, + imageReferences: references.compactMap { $0 as? ImageReference } + ) + } +} + +private extension TopicRenderReference { + func topicReference(languages: Set) -> ResolvedTopicReference? { + guard let url = URL(string: identifier.identifier), let rawBundleID = url.host else { + return nil + } + return ResolvedTopicReference( + bundleID: .init(rawValue: rawBundleID), + path: url.path, + fragment: url.fragment, + // TopicRenderReference doesn't have language information. Also, the reference's languages _doesn't_ specify the languages of the linked entity. + sourceLanguages: languages + ) + } +} diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 8c6112dbb1..0a53b58c3d 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -201,7 +201,8 @@ public struct LinkDestinationSummary: Codable, Equatable { /// Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. /// /// If the summarized element has an image but the variant doesn't, this property will be `Optional.some(nil)`. - public let topicImages: VariantValue<[TopicImage]?> + @available(*, deprecated, message: "`TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") + public let topicImages: VariantValue<[TopicImage]?> = nil /// Creates a new summary variant with the values that are different from the main summarized values. /// @@ -215,7 +216,6 @@ public struct LinkDestinationSummary: Codable, Equatable { /// - taskGroups: The taskGroups of the variant or `nil` if the taskGroups is the same as the summarized element. /// - usr: The precise symbol identifier of the variant or `nil` if the precise symbol identifier is the same as the summarized element. /// - declarationFragments: The declaration of the variant or `nil` if the declaration is the same as the summarized element. - /// - topicImages: Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. public init( traits: [RenderNode.Variant.Trait], kind: VariantValue = nil, @@ -225,8 +225,7 @@ public struct LinkDestinationSummary: Codable, Equatable { abstract: VariantValue = nil, taskGroups: VariantValue<[LinkDestinationSummary.TaskGroup]?> = nil, usr: VariantValue = nil, - declarationFragments: VariantValue = nil, - topicImages: VariantValue<[TopicImage]?> = nil + declarationFragments: VariantValue = nil ) { self.traits = traits self.kind = kind @@ -237,7 +236,32 @@ public struct LinkDestinationSummary: Codable, Equatable { self.taskGroups = taskGroups self.usr = usr self.declarationFragments = declarationFragments - self.topicImages = topicImages + } + + @available(*, deprecated, renamed: "init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:declarationFragments:)", message: "Use `init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:declarationFragments:)` instead. `TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") + public init( + traits: [RenderNode.Variant.Trait], + kind: VariantValue = nil, + language: VariantValue = nil, + relativePresentationURL: VariantValue = nil, + title: VariantValue = nil, + abstract: VariantValue = nil, + taskGroups: VariantValue<[LinkDestinationSummary.TaskGroup]?> = nil, + usr: VariantValue = nil, + declarationFragments: VariantValue = nil, + topicImages: VariantValue<[TopicImage]?> = nil + ) { + self.init( + traits: traits, + kind: kind, + language: language, + relativePresentationURL: relativePresentationURL, + title: title, + abstract: abstract, + taskGroups: taskGroups, + usr: usr, + declarationFragments: declarationFragments + ) } } @@ -469,8 +493,7 @@ extension LinkDestinationSummary { abstract: nilIfEqual(main: abstract, variant: abstractVariant), taskGroups: nilIfEqual(main: taskGroups, variant: taskGroupVariants[variantTraits]), usr: nil, // The symbol variant uses the same USR - declarationFragments: nilIfEqual(main: declaration, variant: declarationVariant), - topicImages: nil // The symbol variant doesn't currently have their own images + declarationFragments: nilIfEqual(main: declaration, variant: declarationVariant) ) } @@ -578,13 +601,28 @@ extension LinkDestinationSummary { public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(kind.id, forKey: .kind) + if DocumentationNode.Kind.allKnownValues.contains(kind) { + try container.encode(kind.id, forKey: .kind) + } else { + try container.encode(kind, forKey: .kind) + } try container.encode(relativePresentationURL, forKey: .relativePresentationURL) try container.encode(referenceURL, forKey: .referenceURL) try container.encode(title, forKey: .title) try container.encodeIfPresent(abstract, forKey: .abstract) - try container.encode(language.id, forKey: .language) - try container.encode(availableLanguages.map { $0.id }, forKey: .availableLanguages) + if SourceLanguage.knownLanguages.contains(language) { + try container.encode(language.id, forKey: .language) + } else { + try container.encode(language, forKey: .language) + } + var languagesContainer = container.nestedUnkeyedContainer(forKey: .availableLanguages) + for language in availableLanguages.sorted() { + if SourceLanguage.knownLanguages.contains(language) { + try languagesContainer.encode(language.id) + } else { + try languagesContainer.encode(language) + } + } try container.encodeIfPresent(platforms, forKey: .platforms) try container.encodeIfPresent(taskGroups, forKey: .taskGroups) try container.encodeIfPresent(usr, forKey: .usr) @@ -600,28 +638,47 @@ extension LinkDestinationSummary { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let kindID = try container.decode(String.self, forKey: .kind) - guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { - throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") + // Kind can either be a known identifier or a full structure + do { + let kindID = try container.decode(String.self, forKey: .kind) + guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { + throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") + } + kind = foundKind + } catch { + kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) } - kind = foundKind relativePresentationURL = try container.decode(URL.self, forKey: .relativePresentationURL) referenceURL = try container.decode(URL.self, forKey: .referenceURL) title = try container.decode(String.self, forKey: .title) abstract = try container.decodeIfPresent(Abstract.self, forKey: .abstract) - let languageID = try container.decode(String.self, forKey: .language) - guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") - } - language = foundLanguage - - let availableLanguageIDs = try container.decode([String].self, forKey: .availableLanguages) - availableLanguages = try Set(availableLanguageIDs.map { languageID in + // Language can either be an identifier of a known language or a full structure + do { + let languageID = try container.decode(String.self, forKey: .language) guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .availableLanguages, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + } + language = foundLanguage + } catch DecodingError.typeMismatch { + language = try container.decode(SourceLanguage.self, forKey: .language) + } + + // The set of languages can be a mix of identifiers and full structure + var languagesContainer = try container.nestedUnkeyedContainer(forKey: .availableLanguages) + var decodedLanguages = Set() + while !languagesContainer.isAtEnd { + do { + let languageID = try languagesContainer.decode(String.self) + guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { + throw DecodingError.dataCorruptedError(forKey: .availableLanguages, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + } + decodedLanguages.insert( foundLanguage ) + } catch DecodingError.typeMismatch { + decodedLanguages.insert( try languagesContainer.decode(SourceLanguage.self) ) } - return foundLanguage - }) + } + availableLanguages = decodedLanguages + platforms = try container.decodeIfPresent([AvailabilityRenderItem].self, forKey: .platforms) taskGroups = try container.decodeIfPresent([TaskGroup].self, forKey: .taskGroups) usr = try container.decodeIfPresent(String.self, forKey: .usr) @@ -638,7 +695,7 @@ extension LinkDestinationSummary { extension LinkDestinationSummary.Variant { enum CodingKeys: String, CodingKey { - case traits, kind, title, abstract, language, usr, taskGroups, topicImages + case traits, kind, title, abstract, language, usr, taskGroups case relativePresentationURL = "path" case declarationFragments = "fragments" } @@ -646,44 +703,58 @@ extension LinkDestinationSummary.Variant { public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(traits, forKey: .traits) - try container.encodeIfPresent(kind?.id, forKey: .kind) + if let kind { + if DocumentationNode.Kind.allKnownValues.contains(kind) { + try container.encode(kind.id, forKey: .kind) + } else { + try container.encode(kind, forKey: .kind) + } + } try container.encodeIfPresent(relativePresentationURL, forKey: .relativePresentationURL) try container.encodeIfPresent(title, forKey: .title) try container.encodeIfPresent(abstract, forKey: .abstract) - try container.encodeIfPresent(language?.id, forKey: .language) + if let language { + if SourceLanguage.knownLanguages.contains(language) { + try container.encode(language.id, forKey: .language) + } else { + try container.encode(language, forKey: .language) + } + } try container.encodeIfPresent(usr, forKey: .usr) try container.encodeIfPresent(declarationFragments, forKey: .declarationFragments) try container.encodeIfPresent(taskGroups, forKey: .taskGroups) - try container.encodeIfPresent(topicImages, forKey: .topicImages) } public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - - let traits = try container.decode([RenderNode.Variant.Trait].self, forKey: .traits) - for case .interfaceLanguage(let languageID) in traits { - guard SourceLanguage.knownLanguages.contains(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .traits, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") - } - } - self.traits = traits - - let kindID = try container.decodeIfPresent(String.self, forKey: .kind) - if let kindID { - guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { - throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") + traits = try container.decode([RenderNode.Variant.Trait].self, forKey: .traits) + + if container.contains(.kind) { + // The kind can either be a known identifier or a full structure + do { + let kindID = try container.decode(String.self, forKey: .kind) + guard let foundKind = DocumentationNode.Kind.allKnownValues.first(where: { $0.id == kindID }) else { + throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown DocumentationNode.Kind identifier: '\(kindID)'.") + } + kind = foundKind + } catch { + kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) } - kind = foundKind } else { kind = nil } - let languageID = try container.decodeIfPresent(String.self, forKey: .language) - if let languageID { - guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { - throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + if container.contains(.language) { + // Language can either be an identifier of a known language or a full structure + do { + let languageID = try container.decode(String.self, forKey: .language) + guard let foundLanguage = SourceLanguage.knownLanguages.first(where: { $0.id == languageID }) else { + throw DecodingError.dataCorruptedError(forKey: .language, in: container, debugDescription: "Unknown SourceLanguage identifier: '\(languageID)'.") + } + language = foundLanguage + } catch DecodingError.typeMismatch { + language = try container.decode(SourceLanguage.self, forKey: .language) } - language = foundLanguage } else { language = nil } @@ -693,7 +764,6 @@ extension LinkDestinationSummary.Variant { usr = try container.decodeIfPresent(String?.self, forKey: .usr) declarationFragments = try container.decodeIfPresent(LinkDestinationSummary.DeclarationFragments?.self, forKey: .declarationFragments) taskGroups = try container.decodeIfPresent([LinkDestinationSummary.TaskGroup]?.self, forKey: .taskGroups) - topicImages = try container.decodeIfPresent([TopicImage]?.self, forKey: .topicImages) } } diff --git a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift index 2ad9f43a8b..465a58aa8a 100644 --- a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift +++ b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift @@ -309,11 +309,13 @@ public class DocumentationContentRenderer { // try resolving that way as a fallback after looking up `documentationCache`. titleVariants = .init(defaultVariantValue: topicGraphOnlyNode.title) } else if let external = documentationContext.externalCache[reference] { - dependencies.topicReferences.append(contentsOf: external.renderReferenceDependencies.topicReferences) - dependencies.linkReferences.append(contentsOf: external.renderReferenceDependencies.linkReferences) - dependencies.imageReferences.append(contentsOf: external.renderReferenceDependencies.imageReferences) + let renderDependencies = external.makeRenderDependencies() - return external.topicRenderReference + dependencies.topicReferences.append(contentsOf: renderDependencies.topicReferences) + dependencies.linkReferences.append(contentsOf: renderDependencies.linkReferences) + dependencies.imageReferences.append(contentsOf: renderDependencies.imageReferences) + + return external.makeTopicRenderReference() } else { titleVariants = .init(defaultVariantValue: reference.absoluteString) } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift b/Sources/SwiftDocC/Model/Rendering/RenderContext.swift index dade49b7a0..26a299c100 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContext.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 @@ -90,7 +90,24 @@ public struct RenderContext { // Add all the external content to the topic store for (reference, entity) in documentationContext.externalCache { - topics[reference] = entity.topicContent() + topics[reference] = entity.makeTopicContent() + + // Also include transitive dependencies in the store, so that the external entity can reference them. + for case let dependency as TopicRenderReference in (entity.references ?? []) { + guard let url = URL(string: dependency.identifier.identifier), let rawBundleID = url.host else { + // This dependency doesn't have a valid topic reference, skip adding it to the render context. + continue + } + + let dependencyReference = ResolvedTopicReference( + bundleID: .init(rawValue: rawBundleID), + path: url.path, + fragment: url.fragment, + // TopicRenderReference doesn't have language information. Also, the reference's languages _doesn't_ specify the languages of the linked entity. + sourceLanguages: reference.sourceLanguages + ) + topics[dependencyReference] = .init(renderReference: dependency, canonicalPath: nil, taskGroups: nil, source: nil, isDocumentationExtensionContent: false) + } } self.store = RenderReferenceStore(topics: topics, assets: assets) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index 1fd747b15a..aad67d6b85 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -1478,7 +1478,7 @@ public struct RenderNodeTranslator: SemanticVisitor { } } else if let entity = context.externalCache[resolved] { collectedTopicReferences.append(resolved) - destinationsMap[destination] = entity.topicRenderReference.title + destinationsMap[destination] = entity.title } else { fatalError("A successfully resolved reference should have either local or external content.") } diff --git a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md index 5155029943..a2330698d4 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md +++ b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/StaticAnalysis.md @@ -12,7 +12,6 @@ Run static analysis checks on markup files. ### Predefined Checks -- ``AbstractContainsFormattedTextOnly`` - ``DuplicateTopicsSections`` - ``InvalidAdditionalTitle`` - ``MissingAbstract`` @@ -20,4 +19,4 @@ Run static analysis checks on markup files. - ``NonOverviewHeadingChecker`` - ``SeeAlsoInTopicsHeadingChecker`` - + diff --git a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift index 4e87138288..ee391de385 100644 --- a/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift +++ b/Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift @@ -22,17 +22,13 @@ class ExternalTopicsGraphHashTests: XCTestCase { func symbolReferenceAndEntity(withPreciseIdentifier preciseIdentifier: String) -> (ResolvedTopicReference, LinkResolver.ExternalEntity)? { let reference = ResolvedTopicReference(bundleID: "com.test.symbols", path: "/\(preciseIdentifier)", sourceLanguage: SourceLanguage.swift) let entity = LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(preciseIdentifier), - title: preciseIdentifier, - abstract: [], - url: "/" + preciseIdentifier, - kind: .symbol, - estimatedTime: nil - ), - renderReferenceDependencies: .init(), - sourceLanguages: [.swift], - symbolKind: .class + kind: .class, + language: .swift, + relativePresentationURL: URL(string: "/\(preciseIdentifier)")!, + referenceURL: reference.url, + title: preciseIdentifier, + availableLanguages: [.swift], + variants: [] ) return (reference, entity) } diff --git a/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift index 6add5371de..958f31c3db 100644 --- a/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.swift +++ b/Tests/SwiftDocCTests/Checker/Checkers/AbstractContainsFormattedTextOnlyTests.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 @@ -12,6 +12,9 @@ import XCTest @testable import SwiftDocC import Markdown +// This tests `AbstractContainsFormattedTextOnly` which are deprecated. +// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. +@available(*, deprecated) class AbstractContainsFormattedTextOnlyTests: XCTestCase { var checker = AbstractContainsFormattedTextOnly(sourceFile: nil) diff --git a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift index 2550a52345..a46afb677e 100644 --- a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift @@ -532,6 +532,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertPageWithLinkResolvingAndKnownPathComponents() throws { let symbolGraphFile = Bundle.module.url( forResource: "mykit-one-symbol", @@ -827,7 +830,9 @@ class ConvertServiceTests: XCTestCase { ) } } - + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertTutorialWithCode() throws { let tutorialContent = """ @Tutorial(time: 99) { @@ -998,6 +1003,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertArticleWithImageReferencesAndDetailedGridLinks() throws { let articleData = try XCTUnwrap(""" # First article @@ -1718,6 +1726,9 @@ class ConvertServiceTests: XCTestCase { #endif } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertPageWithLinkResolving() throws { let symbolGraphFile = Bundle.module.url( forResource: "mykit-one-symbol", @@ -2007,6 +2018,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testConvertTopLevelSymbolWithLinkResolving() throws { let symbolGraphFile = Bundle.module.url( forResource: "one-symbol-top-level", @@ -2114,6 +2128,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testOrderOfLinkResolutionRequestsForDocLink() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( @@ -2152,6 +2169,9 @@ class ConvertServiceTests: XCTestCase { XCTAssertEqual(expectedLinkResolutionRequests, receivedLinkResolutionRequests) } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testOrderOfLinkResolutionRequestsForDeeplyNestedSymbol() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( @@ -2191,6 +2211,9 @@ class ConvertServiceTests: XCTestCase { XCTAssertEqual(expectedLinkResolutionRequests, receivedLinkResolutionRequests) } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testOrderOfLinkResolutionRequestsForSymbolLink() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( @@ -2226,7 +2249,10 @@ class ConvertServiceTests: XCTestCase { XCTAssertEqual(expectedLinkResolutionRequests, receivedLinkResolutionRequests) } - func linkResolutionRequestsForConvertRequest(_ request: ConvertRequest) throws -> [String] { + // This test helper uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) + private func linkResolutionRequestsForConvertRequest(_ request: ConvertRequest) throws -> [String] { var receivedLinkResolutionRequests = [String]() let mockLinkResolvingService = LinkResolvingService { message in do { @@ -2314,6 +2340,9 @@ class ConvertServiceTests: XCTestCase { } } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testDoesNotResolveLinksUnlessBundleIDMatches() throws { let tempURL = try createTempFolder(content: [ Folder(name: "unit-test.docc", content: [ diff --git a/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift b/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift index 6f4ddc2c61..9d2fcf89b1 100644 --- a/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/DocumentationServer+DefaultTests.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 @@ -56,6 +56,9 @@ class DocumentationServer_DefaultTests: XCTestCase { wait(for: [expectation], timeout: 1.0) } + // This test uses `OutOfProcessReferenceResolver/Request` and `OutOfProcessReferenceResolver.Response` which are deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) func testQueriesLinkResolutionServer() throws { let symbolGraphFile = Bundle.module.url( forResource: "mykit-one-symbol", diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index 4ca7dc631e..d0602e48ca 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -11,6 +11,7 @@ import Foundation import XCTest @_spi(ExternalLinks) @testable import SwiftDocC +import SwiftDocCTestUtilities class ExternalRenderNodeTests: XCTestCase { private func generateExternalResolver() -> TestMultiResultExternalReferenceResolver { @@ -56,7 +57,6 @@ class ExternalRenderNodeTests: XCTestCase { } func testExternalRenderNode() async throws { - let externalResolver = generateExternalResolver() let (_, bundle, context) = try await testBundleAndContext( copying: "MixedLanguageFramework", @@ -79,37 +79,33 @@ class ExternalRenderNodeTests: XCTestCase { try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) } - var externalRenderNodes = [ExternalRenderNode]() - for externalLink in context.externalCache { - externalRenderNodes.append( - ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) - ) - } - externalRenderNodes.sort(by: \.titleVariants.defaultValue) + let externalRenderNodes = context.externalCache.valuesByReference.values.map { + ExternalRenderNode(externalEntity: $0, bundleIdentifier: bundle.id) + }.sorted(by: \.titleVariants.defaultValue) XCTAssertEqual(externalRenderNodes.count, 4) - XCTAssertEqual(externalRenderNodes[0].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCArticle") + XCTAssertEqual(externalRenderNodes[0].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/objCArticle") XCTAssertEqual(externalRenderNodes[0].kind, .article) XCTAssertEqual(externalRenderNodes[0].symbolKind, nil) XCTAssertEqual(externalRenderNodes[0].role, "article") XCTAssertEqual(externalRenderNodes[0].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCArticle") XCTAssertTrue(externalRenderNodes[0].isBeta) - XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCSymbol") + XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/objCSymbol") XCTAssertEqual(externalRenderNodes[1].kind, .symbol) XCTAssertEqual(externalRenderNodes[1].symbolKind, .func) XCTAssertEqual(externalRenderNodes[1].role, "symbol") XCTAssertEqual(externalRenderNodes[1].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCSymbol") XCTAssertFalse(externalRenderNodes[1].isBeta) - XCTAssertEqual(externalRenderNodes[2].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftArticle") + XCTAssertEqual(externalRenderNodes[2].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/swiftArticle") XCTAssertEqual(externalRenderNodes[2].kind, .article) XCTAssertEqual(externalRenderNodes[2].symbolKind, nil) XCTAssertEqual(externalRenderNodes[2].role, "article") XCTAssertEqual(externalRenderNodes[2].externalIdentifier.identifier, "doc://com.test.external/path/to/external/swiftArticle") XCTAssertFalse(externalRenderNodes[2].isBeta) - XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftSymbol") + XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/path/to/external/swiftSymbol") XCTAssertEqual(externalRenderNodes[3].kind, .symbol) XCTAssertEqual(externalRenderNodes[3].symbolKind, .class) XCTAssertEqual(externalRenderNodes[3].role, "symbol") @@ -118,33 +114,34 @@ class ExternalRenderNodeTests: XCTestCase { } func testExternalRenderNodeVariantRepresentation() throws { - let renderReferenceIdentifier = RenderReferenceIdentifier(forExternalLink: "doc://com.test.external/path/to/external/symbol") + let reference = ResolvedTopicReference(bundleID: "com.test.external", path: "/path/to/external/symbol", sourceLanguages: [.swift, .objectiveC]) // Variants for the title let swiftTitle = "Swift Symbol" - let occTitle = "Occ Symbol" - - // Variants for the navigator title - let navigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "symbol", kind: .identifier)] - let occNavigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "occ_symbol", kind: .identifier)] + let objcTitle = "Objective-C Symbol" // Variants for the fragments - let fragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] - let occFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] + let swiftFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] + let objcFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] let externalEntity = LinkResolver.ExternalEntity( - topicRenderReference: .init( - identifier: renderReferenceIdentifier, - titleVariants: .init(defaultValue: swiftTitle, objectiveCValue: occTitle), - abstractVariants: .init(defaultValue: []), - url: "/example/path/to/external/symbol", - kind: .symbol, - fragmentsVariants: .init(defaultValue: fragments, objectiveCValue: occFragments), - navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle) - ), - renderReferenceDependencies: .init(), - sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")], - symbolKind: .func) + kind: .function, + language: .swift, + relativePresentationURL: URL(string: "/example/path/to/external/symbol")!, + referenceURL: reference.url, + title: swiftTitle, + availableLanguages: [.swift, .objectiveC], + usr: "some-unique-symbol-id", + declarationFragments: swiftFragments, + variants: [ + .init( + traits: [.interfaceLanguage(SourceLanguage.objectiveC.id)], + language: .objectiveC, + title: objcTitle, + declarationFragments: objcFragments + ) + ] + ) let externalRenderNode = ExternalRenderNode( externalEntity: externalEntity, bundleIdentifier: "com.test.external" @@ -154,39 +151,52 @@ class ExternalRenderNodeTests: XCTestCase { NavigatorExternalRenderNode(renderNode: externalRenderNode) ) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) - XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) XCTAssertFalse(swiftNavigatorExternalRenderNode.metadata.isBeta) let objcNavigatorExternalRenderNode = try XCTUnwrap( - NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) + NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage(SourceLanguage.objectiveC.id)) ) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, objcTitle) XCTAssertFalse(objcNavigatorExternalRenderNode.metadata.isBeta) } func testNavigatorWithExternalNodes() async throws { - let externalResolver = generateExternalResolver() - let (_, bundle, context) = try await testBundleAndContext( - copying: "MixedLanguageFramework", - externalResolvers: [externalResolver.bundleID: externalResolver] - ) { url in - let mixedLanguageFrameworkExtension = """ - # ``MixedLanguageFramework`` - - This symbol has a Swift and Objective-C variant. + let catalog = Folder(name: "ModuleName.docc", content: [ + Folder(name: "swift", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .swift, kind: .class, pathComponents: ["SomeClass"]) + ])) + ]), + Folder(name: "clang", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .objectiveC, kind: .class, pathComponents: ["TLASomeClass"]) + ])) + ]), + + InfoPlist(identifier: "some.custom.identifier"), + + TextFile(name: "ModuleName.md", utf8Content: """ + # ``ModuleName`` + + Curate a few external language-specific symbols and articles - ## Topics + ## Topics - ### External Reference + ### External Reference - - - - - - - - - """ - try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) - } + - + - + - + - + """), + ]) + + var configuration = DocumentationContext.Configuration() + let externalResolver = generateExternalResolver() + configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") + let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() @@ -205,53 +215,63 @@ class ExternalRenderNodeTests: XCTestCase { let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) // Verify that there are no uncurated external links at the top level - let swiftTopLevelExternalNodes = renderIndex.interfaceLanguages["swift"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - let occTopLevelExternalNodes = renderIndex.interfaceLanguages["occ"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - XCTAssertEqual(swiftTopLevelExternalNodes.count, 0) - XCTAssertEqual(occTopLevelExternalNodes.count, 0) + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.count(where: \.isExternal), 0) + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.count(where: \.isExternal), 0) // Verify that the curated external links are part of the index. - let swiftExternalNodes = renderIndex.interfaceLanguages["swift"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - let occExternalNodes = renderIndex.interfaceLanguages["occ"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let swiftExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) + let objcExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) XCTAssertEqual(swiftExternalNodes.count, 2) - XCTAssertEqual(occExternalNodes.count, 2) + XCTAssertEqual(objcExternalNodes.count, 2) XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle", "SwiftSymbol"]) - XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) - XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) - XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) - XCTAssert(swiftExternalNodes.first { $0.title == "SwiftArticle" }?.isBeta == false) - XCTAssert(swiftExternalNodes.first { $0.title == "SwiftSymbol" }?.isBeta == true) - XCTAssert(occExternalNodes.first { $0.title == "ObjCArticle" }?.isBeta == true) - XCTAssert(occExternalNodes.first { $0.title == "ObjCSymbol" }?.isBeta == false) + XCTAssertEqual(objcExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) + XCTAssert(swiftExternalNodes.first?.isBeta == false) + XCTAssert(swiftExternalNodes.last?.isBeta == true) + XCTAssert(objcExternalNodes.first?.isBeta == true) + XCTAssert(objcExternalNodes.last?.isBeta == false) XCTAssertEqual(swiftExternalNodes.map(\.type), ["article", "class"]) - XCTAssertEqual(occExternalNodes.map(\.type), ["article", "func"]) + XCTAssertEqual(objcExternalNodes.map(\.type), ["article", "func"]) } func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() async throws { + let catalog = Folder(name: "ModuleName.docc", content: [ + Folder(name: "swift", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .swift, kind: .class, pathComponents: ["SomeClass"]) + ])) + ]), + Folder(name: "clang", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .objectiveC, kind: .class, pathComponents: ["TLASomeClass"]) + ])) + ]), + + InfoPlist(identifier: "some.custom.identifier"), + + TextFile(name: "ModuleName.md", utf8Content: """ + # ``ModuleName`` + + Curate and link to a few external language-specific symbols and articles + + It also has an external reference which is not curated in the Topics section: + + + + ## Topics + + ### External Reference + + - + - + """), + ]) + + var configuration = DocumentationContext.Configuration() let externalResolver = generateExternalResolver() + configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") - let (_, bundle, context) = try await testBundleAndContext( - copying: "MixedLanguageFramework", - externalResolvers: [externalResolver.bundleID: externalResolver] - ) { url in - let mixedLanguageFrameworkExtension = """ - # ``MixedLanguageFramework`` - - This symbol has a Swift and Objective-C variant. - - It also has an external reference which is not curated in the Topics section: - - - - ## Topics - - ### External Reference - - - - - - """ - try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) - } let renderContext = RenderContext(documentationContext: context, bundle: bundle) let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() @@ -268,55 +288,52 @@ class ExternalRenderNodeTests: XCTestCase { } builder.finalize() let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) - // Verify that there are no uncurated external links at the top level - let swiftTopLevelExternalNodes = renderIndex.interfaceLanguages["swift"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - let occTopLevelExternalNodes = renderIndex.interfaceLanguages["occ"]?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - XCTAssertEqual(swiftTopLevelExternalNodes.count, 0) - XCTAssertEqual(occTopLevelExternalNodes.count, 0) + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.count(where: \.isExternal), 0) + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.count(where: \.isExternal), 0) // Verify that the curated external links are part of the index. - let swiftExternalNodes = renderIndex.interfaceLanguages["swift"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] - let occExternalNodes = renderIndex.interfaceLanguages["occ"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] + let swiftExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) + let objcExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) XCTAssertEqual(swiftExternalNodes.count, 1) - XCTAssertEqual(occExternalNodes.count, 1) + XCTAssertEqual(objcExternalNodes.count, 1) XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle"]) - XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCSymbol"]) - XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) - XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) + XCTAssertEqual(objcExternalNodes.map(\.title), ["ObjCSymbol"]) XCTAssertEqual(swiftExternalNodes.map(\.type), ["article"]) - XCTAssertEqual(occExternalNodes.map(\.type), ["func"]) + XCTAssertEqual(objcExternalNodes.map(\.type), ["func"]) } func testExternalRenderNodeVariantRepresentationWhenIsBeta() throws { - let renderReferenceIdentifier = RenderReferenceIdentifier(forExternalLink: "doc://com.test.external/path/to/external/symbol") + let reference = ResolvedTopicReference(bundleID: "com.test.external", path: "/path/to/external/symbol", sourceLanguages: [.swift, .objectiveC]) // Variants for the title let swiftTitle = "Swift Symbol" - let occTitle = "Occ Symbol" - - // Variants for the navigator title - let navigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "symbol", kind: .identifier)] - let occNavigatorTitle: [DeclarationRenderSection.Token] = [.init(text: "occ_symbol", kind: .identifier)] + let objcTitle = "Objective-C Symbol" // Variants for the fragments - let fragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] - let occFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] + let swiftFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "symbol", kind: .identifier)] + let objcFragments: [DeclarationRenderSection.Token] = [.init(text: "func", kind: .keyword), .init(text: "occ_symbol", kind: .identifier)] let externalEntity = LinkResolver.ExternalEntity( - topicRenderReference: .init( - identifier: renderReferenceIdentifier, - titleVariants: .init(defaultValue: swiftTitle, objectiveCValue: occTitle), - abstractVariants: .init(defaultValue: []), - url: "/example/path/to/external/symbol", - kind: .symbol, - fragmentsVariants: .init(defaultValue: fragments, objectiveCValue: occFragments), - navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle), - isBeta: true - ), - renderReferenceDependencies: .init(), - sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")]) + kind: .function, + language: .swift, + relativePresentationURL: URL(string: "/example/path/to/external/symbol")!, + referenceURL: reference.url, + title: swiftTitle, + availableLanguages: [.swift, .objectiveC], + platforms: [.init(name: "Platform name", introduced: "1.2.3", isBeta: true)], + usr: "some-unique-symbol-id", + declarationFragments: swiftFragments, + variants: [ + .init( + traits: [.interfaceLanguage(SourceLanguage.objectiveC.id)], + language: .objectiveC, + title: objcTitle, + declarationFragments: objcFragments + ) + ] + ) let externalRenderNode = ExternalRenderNode( externalEntity: externalEntity, bundleIdentifier: "com.test.external" @@ -326,14 +343,12 @@ class ExternalRenderNodeTests: XCTestCase { NavigatorExternalRenderNode(renderNode: externalRenderNode) ) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) - XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) XCTAssertTrue(swiftNavigatorExternalRenderNode.metadata.isBeta) let objcNavigatorExternalRenderNode = try XCTUnwrap( - NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) + NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage(SourceLanguage.objectiveC.id)) ) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) - XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, objcTitle) XCTAssertTrue(objcNavigatorExternalRenderNode.metadata.isBeta) } } diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift index 7af1fa06eb..9a62ddf51e 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift @@ -950,7 +950,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { switch result { case .success(let resolved): let entity = externalResolver.entity(resolved) - XCTAssertEqual(entity.topicRenderReference.isBeta, isBeta, file: file, line: line) + XCTAssertEqual(entity.makeTopicRenderReference().isBeta, isBeta, file: file, line: line) case .failure(_, let errorInfo): XCTFail("Unexpectedly failed to resolve \(label) link: \(errorInfo.message) \(errorInfo.solutions.map(\.summary).joined(separator: ", "))", file: file, line: line) } diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 13b47f6743..6e2526397e 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -38,22 +38,15 @@ class ExternalReferenceResolverTests: XCTestCase { fatalError("It is a programming mistake to retrieve an entity for a reference that the external resolver didn't resolve.") } - let (kind, role) = DocumentationContentRenderer.renderKindAndRole(resolvedEntityKind, semantic: nil) return LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: resolvedEntityTitle, - abstract: [.text("Externally Resolved Markup Content")], - url: "/example" + reference.path + (reference.fragment.map { "#\($0)" } ?? ""), - kind: kind, - role: role, - fragments: resolvedEntityDeclarationFragments?.declarationFragments.map { fragment in - return DeclarationRenderSection.Token(fragment: fragment, identifier: nil) - } - ), - renderReferenceDependencies: RenderReferenceDependencies(), - sourceLanguages: [resolvedEntityLanguage], - symbolKind: nil + kind: resolvedEntityKind, + language: resolvedEntityLanguage, + relativePresentationURL: URL(string: "/example" + reference.path + (reference.fragment.map { "#\($0)" } ?? ""))!, + referenceURL: reference.url, + title: resolvedEntityTitle, + availableLanguages: [resolvedEntityLanguage], + declarationFragments: resolvedEntityDeclarationFragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, + variants: [] ) } } @@ -445,7 +438,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(firstExternalRenderReference.identifier.identifier, "doc://com.test.external/path/to/external-page-with-topic-image-1") XCTAssertEqual(firstExternalRenderReference.title, "First external page with topic image") - XCTAssertEqual(firstExternalRenderReference.url, "/example/path/to/external-page-with-topic-image-1") + XCTAssertEqual(firstExternalRenderReference.url, "/path/to/external-page-with-topic-image-1") XCTAssertEqual(firstExternalRenderReference.kind, .article) XCTAssertEqual(firstExternalRenderReference.images, [ @@ -457,7 +450,7 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(secondExternalRenderReference.identifier.identifier, "doc://com.test.external/path/to/external-page-with-topic-image-2") XCTAssertEqual(secondExternalRenderReference.title, "Second external page with topic image") - XCTAssertEqual(secondExternalRenderReference.url, "/example/path/to/external-page-with-topic-image-2") + XCTAssertEqual(secondExternalRenderReference.url, "/path/to/external-page-with-topic-image-2") XCTAssertEqual(secondExternalRenderReference.kind, .article) XCTAssertEqual(secondExternalRenderReference.images, [ @@ -631,19 +624,15 @@ class ExternalReferenceResolverTests: XCTestCase { func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { referencesCreatingEntityFor.insert(reference) - // Return an empty node + // Return an "empty" node return .init( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: "Resolved", - abstract: [], - url: reference.absoluteString, - kind: .symbol, - estimatedTime: nil - ), - renderReferenceDependencies: RenderReferenceDependencies(), - sourceLanguages: [.swift], - symbolKind: .property + kind: .instanceProperty, + language: .swift, + relativePresentationURL: reference.url.withoutHostAndPortAndScheme(), + referenceURL: reference.url, + title: "Resolved", + availableLanguages: [.swift], + variants: [] ) } } diff --git a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift index c875b07f60..7ea89aa72c 100644 --- a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift +++ b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift @@ -53,7 +53,7 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { let entity = entityInfo(path: path) return .success( - ResolvedTopicReference(bundleID: bundleID, path: entity.referencePath,fragment: entity.fragment,sourceLanguage: entity.language) + ResolvedTopicReference(bundleID: bundleID, path: entity.referencePath, fragment: entity.fragment, sourceLanguage: entity.language) ) } } @@ -84,34 +84,19 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { } private func makeNode(for entityInfo: EntityInfo, reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - let (kind, role) = DocumentationContentRenderer.renderKindAndRole(entityInfo.kind, semantic: nil) - - let dependencies: RenderReferenceDependencies - if let topicImages = entityInfo.topicImages { - dependencies = .init(imageReferences: topicImages.map { topicImage, altText in - return ImageReference(identifier: topicImage.identifier, altText: altText, imageAsset: assetsToReturn[topicImage.identifier.identifier] ?? .init()) - }) - } else { - dependencies = .init() - } - - return LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: entityInfo.title, - abstract: [.text(entityInfo.abstract.format())], - url: "/example" + reference.path, - kind: kind, - role: role, - fragments: entityInfo.declarationFragments?.declarationFragments.map { fragment in - return DeclarationRenderSection.Token(fragment: fragment, identifier: nil) - }, - isBeta: entityInfo.platforms?.allSatisfy({$0.isBeta == true}) ?? false, - images: entityInfo.topicImages?.map(\.0) ?? [] - ), - renderReferenceDependencies: dependencies, - sourceLanguages: [entityInfo.language], - symbolKind: DocumentationNode.symbolKind(for: entityInfo.kind) + LinkResolver.ExternalEntity( + kind: entityInfo.kind, + language: entityInfo.language, + relativePresentationURL: reference.url.withoutHostAndPortAndScheme(), + referenceURL: reference.url, + title: entityInfo.title, + availableLanguages: [entityInfo.language], + platforms: entityInfo.platforms, + topicImages: entityInfo.topicImages?.map(\.0), + references: entityInfo.topicImages?.map { topicImage, altText in + ImageReference(identifier: topicImage.identifier, altText: altText, imageAsset: assetsToReturn[topicImage.identifier.identifier] ?? .init()) + }, + variants: [] ) } } diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 8d1752b75b..a51ef8b102 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -13,12 +13,10 @@ import SymbolKit @testable import SwiftDocC import SwiftDocCTestUtilities -class ExternalLinkableTests: XCTestCase { +class LinkDestinationSummaryTests: XCTestCase { - // Write example documentation bundle with a minimal Tutorials page - let catalogHierarchy = Folder(name: "unit-test.docc", content: [ - Folder(name: "Symbols", content: []), - Folder(name: "Resources", content: [ + func testSummaryOfTutorialPage() async throws { + let catalogHierarchy = Folder(name: "unit-test.docc", content: [ TextFile(name: "TechnologyX.tutorial", utf8Content: """ @Tutorials(name: "TechnologyX") { @Intro(title: "Technology X") { @@ -89,11 +87,9 @@ class ExternalLinkableTests: XCTestCase { } } """), - ]), - InfoPlist(displayName: "TestBundle", identifier: "com.test.example"), - ]) - - func testSummaryOfTutorialPage() async throws { + InfoPlist(displayName: "TestBundle", identifier: "com.test.example") + ]) + let (bundle, context) = try await loadBundle(catalog: catalogHierarchy) let converter = DocumentationNodeConverter(bundle: bundle, context: context) @@ -486,7 +482,6 @@ class ExternalLinkableTests: XCTestCase { XCTAssertEqual(variant.usr, nil) XCTAssertEqual(variant.kind, nil) XCTAssertEqual(variant.taskGroups, nil) - XCTAssertEqual(variant.topicImages, nil) let encoded = try JSONEncoder().encode(summary) let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded) @@ -577,7 +572,6 @@ class ExternalLinkableTests: XCTestCase { ) ] ) - XCTAssertEqual(variant.topicImages, nil) let encoded = try JSONEncoder().encode(summary) let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded) @@ -585,6 +579,63 @@ class ExternalLinkableTests: XCTestCase { } } + func testDecodingUnknownKindAndLanguage() throws { + let json = """ + { + "kind" : { + "id" : "kind-id", + "name" : "Kind name", + "isSymbol" : false + }, + "language" : { + "id" : "language-id", + "name" : "Language name", + "idAliases" : [ + "language-alias-id" + ], + "linkDisambiguationID" : "language-id" + }, + "availableLanguages" : [ + "swift", + "data", + { + "id" : "language-id", + "idAliases" : [ + "language-alias-id" + ], + "linkDisambiguationID" : "language-id", + "name" : "Language name" + }, + { + "id" : "language-id-2", + "linkDisambiguationID" : "language-id-2", + "name" : "Other language name" + }, + "occ" + ], + "title" : "Something", + "path" : "/documentation/something", + "referenceURL" : "/documentation/something" + } + """ + + let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: Data(json.utf8)) + try assertRoundTripCoding(decoded) + + XCTAssertEqual(decoded.kind, DocumentationNode.Kind(name: "Kind name", id: "kind-id", isSymbol: false)) + XCTAssertEqual(decoded.language, SourceLanguage(name: "Language name", id: "language-id", idAliases: ["language-alias-id"])) + XCTAssertEqual(decoded.availableLanguages, [ + // Known languages + .swift, + .objectiveC, + .data, + + // Custom languages + SourceLanguage(name: "Language name", id: "language-id", idAliases: ["language-alias-id"]), + SourceLanguage(name: "Other language name", id: "language-id-2"), + ]) + } + func testDecodingLegacyData() throws { let legacyData = """ { diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 355ae1c327..2a16c9db87 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -1178,18 +1178,13 @@ class SemaToRenderNodeTests: XCTestCase { let reference = ResolvedTopicReference(bundleID: "com.test.external.symbols", path: "/\(preciseIdentifier)", sourceLanguage: .objectiveC) let entity = LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: "SymbolName ( \(preciseIdentifier) )", - abstract: [], - url: "/documentation/FrameworkName/path/to/symbol/\(preciseIdentifier)", - kind: .symbol, - role: "ExternalResolvedSymbolRoleHeading", - estimatedTime: nil - ), - renderReferenceDependencies: .init(), - sourceLanguages: [.objectiveC], - symbolKind: .class + kind: .class, + language: .objectiveC, + relativePresentationURL: URL(string: "/documentation/FrameworkName/path/to/symbol/\(preciseIdentifier)")!, + referenceURL: reference.url, + title: "SymbolName ( \(preciseIdentifier) )", + availableLanguages: [.objectiveC], + variants: [] ) return (reference, entity) } @@ -1207,20 +1202,15 @@ class SemaToRenderNodeTests: XCTestCase { } func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { - let (kind, role) = DocumentationContentRenderer.renderKindAndRole(.collection, semantic: nil) - return LinkResolver.ExternalEntity( - topicRenderReference: TopicRenderReference( - identifier: .init(reference.absoluteString), - title: "Title for \(reference.url.path)", - abstract: [.text("Abstract for \(reference.url.path)")], - url: reference.url.path, - kind: kind, - role: role, - estimatedTime: nil - ), - renderReferenceDependencies: .init(), - sourceLanguages: [.swift], - symbolKind: nil + LinkResolver.ExternalEntity( + kind: .collection, + language: .swift, + relativePresentationURL: reference.url.withoutHostAndPortAndScheme(), + referenceURL: reference.url, + title: "Title for \(reference.url.path)", + abstract: [.text("Abstract for \(reference.url.path)")], + availableLanguages: [.swift], + variants: [] ) } } diff --git a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift similarity index 90% rename from Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift rename to Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift index 84b59d722c..a4640f8b2f 100644 --- a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift @@ -12,10 +12,12 @@ import XCTest import Foundation import SymbolKit @_spi(ExternalLinks) @testable import SwiftDocC -@testable import SwiftDocCUtilities import SwiftDocCTestUtilities -class OutOfProcessReferenceResolverTests: XCTestCase { +// This tests the deprecated V1 implementation of `OutOfProcessReferenceResolver`. +// Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. +@available(*, deprecated) +class OutOfProcessReferenceResolverV1Tests: XCTestCase { func testInitializationProcess() throws { #if os(macOS) @@ -51,7 +53,7 @@ class OutOfProcessReferenceResolverTests: XCTestCase { #endif } - func assertResolvesTopicLink(makeResolver: (OutOfProcessReferenceResolver.ResolvedInformation) throws -> OutOfProcessReferenceResolver) throws { + private func assertResolvesTopicLink(makeResolver: (OutOfProcessReferenceResolver.ResolvedInformation) throws -> OutOfProcessReferenceResolver) throws { let testMetadata = OutOfProcessReferenceResolver.ResolvedInformation( kind: .function, url: URL(string: "doc://com.test.bundle/something")!, @@ -100,33 +102,34 @@ class OutOfProcessReferenceResolverTests: XCTestCase { // Resolve the symbol let entity = resolver.entity(with: resolvedReference) + let topicRenderReference = entity.makeTopicRenderReference() - XCTAssertEqual(entity.topicRenderReference.url, testMetadata.url.withoutHostAndPortAndScheme().absoluteString) + XCTAssertEqual(topicRenderReference.url, testMetadata.url.withoutHostAndPortAndScheme().absoluteString) - XCTAssertEqual(entity.topicRenderReference.kind.rawValue, "symbol") - XCTAssertEqual(entity.topicRenderReference.role, "symbol") + XCTAssertEqual(topicRenderReference.kind.rawValue, "symbol") + XCTAssertEqual(topicRenderReference.role, "symbol") - XCTAssertEqual(entity.topicRenderReference.title, "Resolved Title") - XCTAssertEqual(entity.topicRenderReference.abstract, [.text("Resolved abstract for this topic.")]) + XCTAssertEqual(topicRenderReference.title, "Resolved Title") + XCTAssertEqual(topicRenderReference.abstract, [.text("Resolved abstract for this topic.")]) - XCTAssertFalse(entity.topicRenderReference.isBeta) + XCTAssertFalse(topicRenderReference.isBeta) - XCTAssertEqual(entity.sourceLanguages.count, 3) + XCTAssertEqual(entity.availableLanguages.count, 3) - let availableSourceLanguages = entity.sourceLanguages.sorted() + let availableSourceLanguages = entity.availableLanguages.sorted() let expectedLanguages = testMetadata.availableLanguages.sorted() XCTAssertEqual(availableSourceLanguages[0], expectedLanguages[0]) XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) - XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] - XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") - XCTAssertEqual(entity.topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) + XCTAssertEqual(topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) - let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) + let fragmentVariant = try XCTUnwrap(topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) if case .replace(let variantFragment) = fragmentVariant.patch.first { XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) @@ -134,7 +137,7 @@ class OutOfProcessReferenceResolverTests: XCTestCase { XCTFail("Unexpected fragments variant patch") } - XCTAssertEqual(entity.symbolKind, .func) + XCTAssertEqual(entity.kind, .function) } func testResolvingTopicLinkProcess() throws { @@ -273,30 +276,31 @@ class OutOfProcessReferenceResolverTests: XCTestCase { // Resolve the symbol let (_, entity) = try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "abc123"), "Unexpectedly failed to resolve symbol") + let topicRenderReference = entity.makeTopicRenderReference() - XCTAssertEqual(entity.topicRenderReference.url, testMetadata.url.absoluteString) + XCTAssertEqual(topicRenderReference.url, testMetadata.url.absoluteString) - XCTAssertEqual(entity.topicRenderReference.kind.rawValue, "symbol") - XCTAssertEqual(entity.topicRenderReference.role, "symbol") + XCTAssertEqual(topicRenderReference.kind.rawValue, "symbol") + XCTAssertEqual(topicRenderReference.role, "symbol") - XCTAssertEqual(entity.topicRenderReference.title, "Resolved Title") + XCTAssertEqual(topicRenderReference.title, "Resolved Title") - XCTAssertEqual(entity.sourceLanguages.count, 3) + XCTAssertEqual(entity.availableLanguages.count, 3) - let availableSourceLanguages = entity.sourceLanguages.sorted() + let availableSourceLanguages = entity.availableLanguages.sorted() let expectedLanguages = testMetadata.availableLanguages.sorted() XCTAssertEqual(availableSourceLanguages[0], expectedLanguages[0]) XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) - XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] - XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") - XCTAssertEqual(entity.topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) + XCTAssertEqual(topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) - let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) + let fragmentVariant = try XCTUnwrap(topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) if case .replace(let variantFragment) = fragmentVariant.patch.first { XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) @@ -304,19 +308,19 @@ class OutOfProcessReferenceResolverTests: XCTestCase { XCTFail("Unexpected fragments variant patch") } - XCTAssertNil(entity.topicRenderReference.conformance) - XCTAssertNil(entity.topicRenderReference.estimatedTime) - XCTAssertNil(entity.topicRenderReference.defaultImplementationCount) - XCTAssertFalse(entity.topicRenderReference.isBeta) - XCTAssertFalse(entity.topicRenderReference.isDeprecated) - XCTAssertNil(entity.topicRenderReference.propertyListKeyNames) - XCTAssertNil(entity.topicRenderReference.tags) - - XCTAssertEqual(entity.topicRenderReference.images.count, 1) - let topicImage = try XCTUnwrap(entity.topicRenderReference.images.first) + XCTAssertNil(topicRenderReference.conformance) + XCTAssertNil(topicRenderReference.estimatedTime) + XCTAssertNil(topicRenderReference.defaultImplementationCount) + XCTAssertFalse(topicRenderReference.isBeta) + XCTAssertFalse(topicRenderReference.isDeprecated) + XCTAssertNil(topicRenderReference.propertyListKeyNames) + XCTAssertNil(topicRenderReference.tags) + + XCTAssertEqual(topicRenderReference.images.count, 1) + let topicImage = try XCTUnwrap(topicRenderReference.images.first) XCTAssertEqual(topicImage.type, .card) - let image = try XCTUnwrap(entity.renderReferenceDependencies.imageReferences.first(where: { $0.identifier == topicImage.identifier })) + let image = try XCTUnwrap(entity.makeRenderDependencies().imageReferences.first(where: { $0.identifier == topicImage.identifier })) XCTAssertEqual(image.identifier, RenderReferenceIdentifier("external-card")) XCTAssertEqual(image.altText, "External card alt text") @@ -678,11 +682,10 @@ class OutOfProcessReferenceResolverTests: XCTestCase { let resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) XCTAssertEqual(resolver.bundleID, "com.test.bundle") - XCTAssertThrowsError(try resolver.resolveInformationForTopicURL(URL(string: "doc://com.test.bundle/something")!)) { - guard case OutOfProcessReferenceResolver.Error.executableSentBundleIdentifierAgain = $0 else { - XCTFail("Encountered an unexpected type of error.") - return - } + if case .failure(_, let errorInfo) = resolver.resolve(.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(parsingAuthoredLink: "doc://com.test.bundle/something")!))) { + XCTAssertEqual(errorInfo.message, "Executable sent bundle identifier message again, after it was already received.") + } else { + XCTFail("Unexpectedly resolved the link from an identifier and capabilities response") } #endif } @@ -734,13 +737,13 @@ class OutOfProcessReferenceResolverTests: XCTestCase { // Resolve the symbol let topicLinkEntity = resolver.entity(with: resolvedReference) - - XCTAssertEqual(topicLinkEntity.topicRenderReference.isBeta, isBeta, file: file, line: line) + + XCTAssertEqual(topicLinkEntity.makeTopicRenderReference().isBeta, isBeta, file: file, line: line) // Resolve the symbol let (_, symbolEntity) = try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "abc123"), "Unexpectedly failed to resolve symbol") - XCTAssertEqual(symbolEntity.topicRenderReference.isBeta, isBeta, file: file, line: line) + XCTAssertEqual(symbolEntity.makeTopicRenderReference().isBeta, isBeta, file: file, line: line) } diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift new file mode 100644 index 0000000000..dcd51bf89e --- /dev/null +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift @@ -0,0 +1,680 @@ +/* + 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 XCTest +import Foundation +import SymbolKit +@_spi(ExternalLinks) @testable import SwiftDocC +import SwiftDocCTestUtilities + +#if os(macOS) +class OutOfProcessReferenceResolverV2Tests: XCTestCase { + + func testInitializationProcess() throws { + let temporaryFolder = try createTemporaryDirectory() + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + // When the executable file doesn't exist + XCTAssertFalse(FileManager.default.fileExists(atPath: executableLocation.path)) + XCTAssertThrowsError(try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }), + "There should be a validation error if the executable file doesn't exist") + + // When the file isn't executable + try "".write(to: executableLocation, atomically: true, encoding: .utf8) + XCTAssertFalse(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + XCTAssertThrowsError(try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }), + "There should be a validation error if the file isn't executable") + + // When the file isn't executable + try """ + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + let resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { errorMessage in + XCTFail("No error output is expected for this test executable. Got:\n\(errorMessage)") + }) + XCTAssertEqual(resolver.bundleID, "com.test.bundle") + } + + private func makeTestSummary() -> (summary: LinkDestinationSummary, imageReference: RenderReferenceIdentifier, imageURLs: (light: URL, dark: URL)) { + let linkedReference = RenderReferenceIdentifier("doc://com.test.bundle/something-else") + let linkedImage = RenderReferenceIdentifier("some-image-identifier") + let linkedVariantReference = RenderReferenceIdentifier("doc://com.test.bundle/something-else-2") + + func cardImages(name: String) -> (light: URL, dark: URL) { + ( URL(string: "https://example.com/path/to/\(name)@2x.png")!, + URL(string: "https://example.com/path/to/\(name)~dark@2x.png")! ) + } + + let imageURLs = cardImages(name: "some-image") + + let summary = LinkDestinationSummary( + kind: .structure, + language: .swift, // This is Swift to account for what is considered a symbol's "first" variant value (rdar://86580516), + relativePresentationURL: URL(string: "/path/so/something")!, + referenceURL: URL(string: "doc://com.test.bundle/something")!, + title: "Resolved Title", + abstract: [ + .text("Resolved abstract with "), + .emphasis(inlineContent: [.text("formatted")]), + .text(" "), + .strong(inlineContent: [.text("formatted")]), + .text(" and a link: "), + .reference(identifier: linkedReference, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) + ], + availableLanguages: [ + .swift, + .init(name: "Language Name 2", id: "com.test.another-language.id"), + .objectiveC, + ], + platforms: [ + .init(name: "firstOS", introduced: "1.2.3", isBeta: false), + .init(name: "secondOS", introduced: "4.5.6", isBeta: false), + ], + usr: "some-unique-symbol-id", + declarationFragments: .init([ + .init(text: "struct", kind: .keyword, preciseIdentifier: nil), + .init(text: " ", kind: .text, preciseIdentifier: nil), + .init(text: "declaration fragment", kind: .identifier, preciseIdentifier: nil), + ]), + topicImages: [ + .init(pageImagePurpose: .card, identifier: linkedImage) + ], + references: [ + TopicRenderReference(identifier: linkedReference, title: "Something Else", abstract: [.text("Some other page")], url: "/path/to/something-else", kind: .symbol), + TopicRenderReference(identifier: linkedVariantReference, title: "Another Page", abstract: [.text("Yet another page")], url: "/path/to/something-else-2", kind: .article), + + ImageReference( + identifier: linkedImage, + altText: "External card alt text", + imageAsset: DataAsset( + variants: [ + DataTraitCollection(userInterfaceStyle: .light, displayScale: .double): imageURLs.light, + DataTraitCollection(userInterfaceStyle: .dark, displayScale: .double): imageURLs.dark, + ], + metadata: [ + imageURLs.light : DataAsset.Metadata(svgID: nil), + imageURLs.dark : DataAsset.Metadata(svgID: nil), + ], + context: .display + ) + ), + ], + variants: [ + .init( + traits: [.interfaceLanguage("com.test.another-language.id")], + kind: .init(name: "Variant Kind Name", id: "com.test.kind2.id", isSymbol: true), + language: .init(name: "Language Name 2", id: "com.test.another-language.id"), + title: "Resolved Variant Title", + abstract: [ + .text("Resolved variant abstract with "), + .emphasis(inlineContent: [.text("formatted")]), + .text(" "), + .strong(inlineContent: [.text("formatted")]), + .text(" and a link: "), + .reference(identifier: linkedVariantReference, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) + ], + declarationFragments: .init([ + .init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil) + ]) + ) + ] + ) + + return (summary, linkedImage, imageURLs) + } + + func testResolvingLinkAndSymbol() throws { + enum RequestKind { + case link, symbol + + func perform(resolver: OutOfProcessReferenceResolver, file: StaticString = #filePath, line: UInt = #line) throws -> LinkResolver.ExternalEntity? { + switch self { + case .link: + let unresolved = TopicReference.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(parsingExact: "doc://com.test.bundle/something")!)) + let reference: ResolvedTopicReference + switch resolver.resolve(unresolved) { + case .success(let resolved): + reference = resolved + case .failure(_, let errorInfo): + XCTFail("Unexpectedly failed to resolve reference with error: \(errorInfo.message)", file: file, line: line) + return nil + } + + // Resolve the symbol + return resolver.entity(with: reference) + + case .symbol: + return try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "")?.1, file: file, line: line) + } + } + } + + for requestKind in [RequestKind.link, .symbol] { + let (testSummary, linkedImage, imageURLs) = makeTestSummary() + + let resolver: OutOfProcessReferenceResolver + do { + let temporaryFolder = try createTemporaryDirectory() + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + + let encodedLinkSummary = try String(data: JSONEncoder().encode(testSummary), encoding: .utf8)! + + try """ + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '{"resolved":\(encodedLinkSummary)}' # Respond with the test link summary (above) + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + XCTAssertEqual(resolver.bundleID, "com.test.bundle") + } + + let entity = try XCTUnwrap(requestKind.perform(resolver: resolver)) + let topicRenderReference = entity.makeTopicRenderReference() + + XCTAssertEqual(topicRenderReference.url, testSummary.relativePresentationURL.absoluteString) + + XCTAssertEqual(topicRenderReference.kind.rawValue, "symbol") + XCTAssertEqual(topicRenderReference.role, "symbol") + + XCTAssertEqual(topicRenderReference.title, "Resolved Title") + XCTAssertEqual(topicRenderReference.abstract, [ + .text("Resolved abstract with "), + .emphasis(inlineContent: [.text("formatted")]), + .text(" "), + .strong(inlineContent: [.text("formatted")]), + .text(" and a link: "), + .reference(identifier: .init("doc://com.test.bundle/something-else"), isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) + ]) + + XCTAssertFalse(topicRenderReference.isBeta) + + XCTAssertEqual(entity.availableLanguages.count, 3) + + let availableSourceLanguages = entity.availableLanguages.sorted() + let expectedLanguages = testSummary.availableLanguages.sorted() + + XCTAssertEqual(availableSourceLanguages[0], expectedLanguages[0]) + XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) + XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) + + XCTAssertEqual(topicRenderReference.fragments, [ + .init(text: "struct", kind: .keyword, preciseIdentifier: nil), + .init(text: " ", kind: .text, preciseIdentifier: nil), + .init(text: "declaration fragment", kind: .identifier, preciseIdentifier: nil), + ]) + + let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] + XCTAssertEqual(topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(topicRenderReference.abstractVariants.value(for: variantTraits), [ + .text("Resolved variant abstract with "), + .emphasis(inlineContent: [.text("formatted")]), + .text(" "), + .strong(inlineContent: [.text("formatted")]), + .text(" and a link: "), + .reference(identifier: .init("doc://com.test.bundle/something-else-2"), isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) + ]) + + let fragmentVariant = try XCTUnwrap(topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) + XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) + if case .replace(let variantFragment) = fragmentVariant.patch.first { + XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) + } else { + XCTFail("Unexpected fragments variant patch") + } + + XCTAssertNil(topicRenderReference.conformance) + XCTAssertNil(topicRenderReference.estimatedTime) + XCTAssertNil(topicRenderReference.defaultImplementationCount) + XCTAssertFalse(topicRenderReference.isBeta) + XCTAssertFalse(topicRenderReference.isDeprecated) + XCTAssertNil(topicRenderReference.propertyListKeyNames) + XCTAssertNil(topicRenderReference.tags) + + XCTAssertEqual(topicRenderReference.images.count, 1) + let topicImage = try XCTUnwrap(topicRenderReference.images.first) + XCTAssertEqual(topicImage.type, .card) + + let image = try XCTUnwrap(entity.makeRenderDependencies().imageReferences.first(where: { $0.identifier == topicImage.identifier })) + + XCTAssertEqual(image.identifier, linkedImage) + XCTAssertEqual(image.altText, "External card alt text") + + XCTAssertEqual(image.asset, DataAsset( + variants: [ + DataTraitCollection(userInterfaceStyle: .light, displayScale: .double): imageURLs.light, + DataTraitCollection(userInterfaceStyle: .dark, displayScale: .double): imageURLs.dark, + ], + metadata: [ + imageURLs.light: DataAsset.Metadata(svgID: nil), + imageURLs.dark: DataAsset.Metadata(svgID: nil), + ], + context: .display + )) + } + } + + func testForwardsErrorOutputProcess() throws { + let temporaryFolder = try createTemporaryDirectory() + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + try """ + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + echo "Some error output" 1>&2 # Write to stderr + read # Wait for docc to send a request + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + let didReadErrorOutputExpectation = expectation(description: "Did read forwarded error output.") + + let resolver = try? OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { + errorMessage in + XCTAssertEqual(errorMessage, "Some error output\n") + didReadErrorOutputExpectation.fulfill() + }) + XCTAssertEqual(resolver?.bundleID, "com.test.bundle") + + wait(for: [didReadErrorOutputExpectation], timeout: 20.0) + } + + func testLinksAndImagesInExternalAbstractAreIncludedInTheRenderedPageReferenecs() async throws { + let externalBundleID: DocumentationBundle.Identifier = "com.example.test" + + let imageRef = RenderReferenceIdentifier("some-external-card-image-identifier") + let linkRef = RenderReferenceIdentifier("doc://\(externalBundleID)/path/to/other-page") + + let imageURL = URL(string: "https://example.com/path/to/some-image.png")! + + let originalLinkedImage = ImageReference( + identifier: imageRef, + imageAsset: DataAsset( + variants: [.init(displayScale: .standard): imageURL], + metadata: [imageURL: .init()], + context: .display + ) + ) + + let originalLinkedTopic = TopicRenderReference( + identifier: linkRef, + title: "Resolved title of link inside abstract", + abstract: [ + .text("This transient content is not displayed anywhere"), + ], + url: "/path/to/other-page", + kind: .article + ) + + let externalSummary = LinkDestinationSummary( + kind: .article, + language: .swift, + relativePresentationURL: URL(string: "/path/to/something")!, + referenceURL: URL(string: "doc://\(externalBundleID)/path/to/something")!, + title: "Resolved title", + abstract: [ + .text("External abstract with an image "), + .image(identifier: imageRef, metadata: nil), + .text(" and link "), + .reference(identifier: linkRef, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil), + .text("."), + ], + availableLanguages: [.swift], + platforms: nil, + taskGroups: nil, + usr: nil, + declarationFragments: nil, + redirects: nil, + topicImages: nil, + references: [originalLinkedImage, originalLinkedTopic], + variants: [] + ) + + let resolver: OutOfProcessReferenceResolver + do { + let temporaryFolder = try createTemporaryDirectory() + let encodedResponse = try String(decoding: JSONEncoder().encode(OutOfProcessReferenceResolver.ResponseV2.resolved(externalSummary)), as: UTF8.self) + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + try """ + #!/bin/bash + echo '{"identifier":"\(externalBundleID)","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '\(encodedResponse)' # Respond with the resolved link summary + read # Wait for docc to send another request + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + } + + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Something.md", utf8Content: """ + # My root page + + This page curates an an external page (so that its abstract and transient references are displayed on the page) + + ## Topics + + ### An external link + + - + """) + ]) + let inputDirectory = Folder(name: "path", content: [Folder(name: "to", content: [catalog])]) + + var configuration = DocumentationContext.Configuration() + configuration.externalDocumentationConfiguration.sources = [ + externalBundleID: resolver + ] + let (_, context) = try await loadBundle(catalog: inputDirectory, configuration: configuration) + XCTAssertEqual(context.problems.map(\.diagnostic.summary), [], "Encountered unexpected problems") + + let reference = try XCTUnwrap(context.soleRootModuleReference, "This example catalog only has a root page") + + let converter = DocumentationContextConverter( + bundle: context.bundle, + context: context, + renderContext: RenderContext( + documentationContext: context, + bundle: context.bundle + ) + ) + let renderNode = try XCTUnwrap(converter.renderNode(for: context.entity(with: reference))) + + // Verify that the topic section exist and has the external link + XCTAssertEqual(renderNode.topicSections.flatMap { [$0.title ?? ""] + $0.identifiers }, [ + "An external link", + "doc://\(externalBundleID)/path/to/something", // Resolved links use their canonical references + ]) + + // Verify that the externally resolved page's references are included on the page + XCTAssertEqual(Set(renderNode.references.keys), [ + "doc://com.example.test/path/to/something", // The external page that the root links to + + "some-external-card-image-identifier", // The image in that page's abstract + "doc://com.example.test/path/to/other-page", // The link in that page's abstract + ], "The external page and its two references should be included on this page") + + XCTAssertEqual(renderNode.references[imageRef.identifier] as? ImageReference, originalLinkedImage) + XCTAssertEqual(renderNode.references[linkRef.identifier] as? TopicRenderReference, originalLinkedTopic) + } + + func testExternalLinkFailureResultInDiagnosticWithSolutions() async throws { + let externalBundleID: DocumentationBundle.Identifier = "com.example.test" + + let resolver: OutOfProcessReferenceResolver + do { + let temporaryFolder = try createTemporaryDirectory() + + let diagnosticInfo = OutOfProcessReferenceResolver.ResponseV2.DiagnosticInformation( + summary: "Some external link issue summary", + solutions: [ + .init(summary: "Some external solution", replacement: "some-replacement") + ] + ) + let encodedDiagnostic = try String(decoding: JSONEncoder().encode(diagnosticInfo), as: UTF8.self) + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + try """ + #!/bin/bash + echo '{"identifier":"\(externalBundleID)","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '{"failure":\(encodedDiagnostic)}' # Respond with an error message + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + } + + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Something.md", utf8Content: """ + # My root page + + This page contains an external link that will fail to resolve: + """) + ]) + let inputDirectory = Folder(name: "path", content: [Folder(name: "to", content: [catalog])]) + + var configuration = DocumentationContext.Configuration() + configuration.externalDocumentationConfiguration.sources = [ + externalBundleID: resolver + ] + let (_, context) = try await loadBundle(catalog: inputDirectory, configuration: configuration) + + XCTAssertEqual(context.problems.map(\.diagnostic.summary), [ + "Some external link issue summary", + ]) + + let problem = try XCTUnwrap(context.problems.sorted(by: \.diagnostic.identifier).first) + + XCTAssertEqual(problem.diagnostic.summary, "Some external link issue summary") + XCTAssertEqual(problem.diagnostic.range?.lowerBound, .init(line: 3, column: 69, source: URL(fileURLWithPath: "/path/to/unit-test.docc/Something.md"))) + XCTAssertEqual(problem.diagnostic.range?.upperBound, .init(line: 3, column: 97, source: URL(fileURLWithPath: "/path/to/unit-test.docc/Something.md"))) + + XCTAssertEqual(problem.possibleSolutions.count, 1) + let solution = try XCTUnwrap(problem.possibleSolutions.first) + XCTAssertEqual(solution.summary, "Some external solution") + XCTAssertEqual(solution.replacements.count, 1) + XCTAssertEqual(solution.replacements.first?.range.lowerBound, .init(line: 3, column: 65, source: nil)) + XCTAssertEqual(solution.replacements.first?.range.upperBound, .init(line: 3, column: 97, source: nil)) + + // Verify the warning presentation + let diagnosticOutput = LogHandle.LogStorage() + let fileSystem = try TestFileSystem(folders: [inputDirectory]) + let diagnosticFormatter = DiagnosticConsoleWriter(LogHandle.memory(diagnosticOutput), formattingOptions: [], highlight: true, dataProvider: fileSystem) + diagnosticFormatter.receive(context.diagnosticEngine.problems) + try diagnosticFormatter.flush() + + let warning = "\u{001B}[1;33m" + let highlight = "\u{001B}[1;32m" + let suggestion = "\u{001B}[1;39m" + let clear = "\u{001B}[0;0m" + XCTAssertEqual(diagnosticOutput.text, """ + \(warning)warning: Some external link issue summary\(clear) + --> /path/to/unit-test.docc/Something.md:3:69-3:97 + 1 | # My root page + 2 | + 3 + This page contains an external link that will fail to resolve: + | ╰─\(suggestion)suggestion: Some external solution\(clear) + + """) + + // Verify the suggestion replacement + let source = try XCTUnwrap(problem.diagnostic.source) + let original = String(decoding: try fileSystem.contents(of: source), as: UTF8.self) + + XCTAssertEqual(try solution.applyTo(original), """ + # My root page + + This page contains an external link that will fail to resolve: + """) + } + + func testEncodingAndDecodingRequests() throws { + do { + let request = OutOfProcessReferenceResolver.RequestV2.link("doc://com.example/path/to/something") + + let data = try JSONEncoder().encode(request) + if case .link(let link) = try JSONDecoder().decode(OutOfProcessReferenceResolver.RequestV2.self, from: data) { + XCTAssertEqual(link, "doc://com.example/path/to/something") + } else { + XCTFail("Decoded the wrong type of request") + } + } + + do { + let request = OutOfProcessReferenceResolver.RequestV2.symbol("some-unique-symbol-id") + + let data = try JSONEncoder().encode(request) + if case .symbol(let usr) = try JSONDecoder().decode(OutOfProcessReferenceResolver.RequestV2.self, from: data) { + XCTAssertEqual(usr, "some-unique-symbol-id") + } else { + XCTFail("Decoded the wrong type of request") + } + } + } + + func testEncodingAndDecodingResponses() throws { + // Identifier and capabilities + do { + let request = OutOfProcessReferenceResolver.ResponseV2.identifierAndCapabilities("com.example.test", []) + + let data = try JSONEncoder().encode(request) + if case .identifierAndCapabilities(let identifier, let capabilities) = try JSONDecoder().decode(OutOfProcessReferenceResolver.ResponseV2.self, from: data) { + XCTAssertEqual(identifier.rawValue, "com.example.test") + XCTAssertEqual(capabilities.rawValue, 0) + } else { + XCTFail("Decoded the wrong type of message") + } + } + + // Failures + do { + let originalInfo = OutOfProcessReferenceResolver.ResponseV2.DiagnosticInformation( + summary: "Some summary", + solutions: [ + .init(summary: "Some solution", replacement: "some-replacement") + ] + ) + + let request = OutOfProcessReferenceResolver.ResponseV2.failure(originalInfo) + let data = try JSONEncoder().encode(request) + if case .failure(let info) = try JSONDecoder().decode(OutOfProcessReferenceResolver.ResponseV2.self, from: data) { + XCTAssertEqual(info.summary, originalInfo.summary) + XCTAssertEqual(info.solutions?.count, originalInfo.solutions?.count) + for (solution, originalSolution) in zip(info.solutions ?? [], originalInfo.solutions ?? []) { + XCTAssertEqual(solution.summary, originalSolution.summary) + XCTAssertEqual(solution.replacement, originalSolution.replacement) + } + } else { + XCTFail("Decoded the wrong type of message") + } + } + + // Resolved link information + do { + let originalSummary = makeTestSummary().summary + let message = OutOfProcessReferenceResolver.ResponseV2.resolved(originalSummary) + + let data = try JSONEncoder().encode(message) + if case .resolved(let summary) = try JSONDecoder().decode(OutOfProcessReferenceResolver.ResponseV2.self, from: data) { + XCTAssertEqual(summary, originalSummary) + } else { + XCTFail("Decoded the wrong type of message") + return + } + } + } + + func testErrorWhenReceivingBundleIdentifierTwice() throws { + let temporaryFolder = try createTemporaryDirectory() + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + try """ + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this identifier & capabilities again + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + let resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + XCTAssertEqual(resolver.bundleID, "com.test.bundle") + + if case .failure(_, let errorInfo) = resolver.resolve(.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(parsingAuthoredLink: "doc://com.test.bundle/something")!))) { + XCTAssertEqual(errorInfo.message, "Executable sent bundle identifier message again, after it was already received.") + } else { + XCTFail("Unexpectedly resolved the link from an identifier and capabilities response") + } + } + + func testResolvingSymbolBetaStatusProcess() throws { + func betaStatus(forSymbolWithPlatforms platforms: [LinkDestinationSummary.PlatformAvailability], file: StaticString = #filePath, line: UInt = #line) throws -> Bool { + let summary = LinkDestinationSummary( + kind: .class, + language: .swift, + relativePresentationURL: URL(string: "/documentation/ModuleName/Something")!, + referenceURL: URL(string: "/documentation/ModuleName/Something")!, + title: "Something", + availableLanguages: [.swift, .objectiveC], + platforms: platforms, + variants: [] + ) + + let resolver: OutOfProcessReferenceResolver + do { + let temporaryFolder = try createTemporaryDirectory() + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + + let encodedLinkSummary = try String(data: JSONEncoder().encode(summary), encoding: .utf8)! + + try """ + #!/bin/bash + #!/bin/bash + echo '{"identifier":"com.test.bundle","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo '{"resolved":\(encodedLinkSummary)}' # Respond with the test link summary (above) + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + XCTAssertEqual(resolver.bundleID, "com.test.bundle", file: file, line: line) + } + + let (_, symbolEntity) = try XCTUnwrap(resolver.symbolReferenceAndEntity(withPreciseIdentifier: "abc123"), "Unexpectedly failed to resolve symbol") + return symbolEntity.makeTopicRenderReference().isBeta + } + + // All platforms are in beta + XCTAssertEqual(true, try betaStatus(forSymbolWithPlatforms: [ + .init(name: "fooOS", introduced: "1.2.3", isBeta: true), + .init(name: "barOS", introduced: "1.2.3", isBeta: true), + .init(name: "bazOS", introduced: "1.2.3", isBeta: true), + ])) + + // One platform is stable, the other two are in beta + XCTAssertEqual(false, try betaStatus(forSymbolWithPlatforms: [ + .init(name: "fooOS", introduced: "1.2.3", isBeta: false), + .init(name: "barOS", introduced: "1.2.3", isBeta: true), + .init(name: "bazOS", introduced: "1.2.3", isBeta: true), + ])) + + // No platforms explicitly supported + XCTAssertEqual(false, try betaStatus(forSymbolWithPlatforms: [])) + } +} +#endif diff --git a/bin/test-data-external-resolver b/bin/test-data-external-resolver index 23d9f9d13d..d38e2db513 100755 --- a/bin/test-data-external-resolver +++ b/bin/test-data-external-resolver @@ -2,7 +2,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 @@ -18,108 +18,177 @@ # absolute documentation links with the "com.test.bundle" identifier. For example: RESPONSE='{ - "resolvedInformation" : { - "abstract" : "Resolved abstract.", - "availableLanguages" : [ + "resolved" : { + "abstract" : [ { - "id" : "swift", - "name" : "Language Name", - "idAliases" : [], - "linkDisambiguationID": "swift" + "text" : "Resolved ", + "type" : "text" }, { - "id" : "occ", - "name" : "Variant Language Name", - "idAliases" : [ - "objective-c", - "c" + "inlineContent" : [ + { + "text" : "formatted", + "type" : "text" + } ], - "linkDisambiguationID" : "c" + "type" : "strong" + }, + { + "text" : " abstract with ", + "type" : "text" + }, + { + "identifier" : "doc://com.test.bundle/path/to/other-page", + "isActive" : true, + "type" : "reference" + }, + { + "text" : ".", + "type" : "text" } ], - "declarationFragments" : [ + "availableLanguages" : [ + "swift", + "data", + { + "id" : "language-id", + "idAliases" : [ + "language-alias-id" + ], + "linkDisambiguationID" : "language-id", + "name" : "Language name" + }, + { + "id" : "language-id-2", + "linkDisambiguationID" : "language-id-2", + "name" : "Other language name" + }, + "occ" + ], + "fragments" : [ + { + "kind" : "keyword", + "text" : "resolved" + }, { "kind" : "text", - "spelling" : "declaration fragment" + "text" : " " + }, + { + "kind" : "identifier", + "text" : "fragment" } ], "kind" : { - "id" : "com.test.kind.id", + "id" : "kind-id", "isSymbol" : true, - "name" : "Kind Name" + "name" : "Kind name" }, "language" : { - "id" : "swift", - "name" : "Language Name", - "idAliases" : [], - "linkDisambiguationID": "swift" - + "id" : "language-id", + "idAliases" : [ + "language-alias-id" + ], + "linkDisambiguationID" : "language-id", + "name" : "Language name" }, + "path" : "/documentation/something", "platforms" : [ { "beta" : false, - "introducedAt" : "1.0.0", - "name" : "Platform Name" + "introducedAt" : "1.2.3", + "name" : "Platform name" } ], - "topicImages": [ + "referenceURL" : "doc://com.test.bundle/documentation/something", + "references" : [ { - "type": "card", - "identifier": "some-external-card-image-identifier" - } - ], - "references": [ + "abstract" : [ + { + "text" : "The abstract of another page that is linked to", + "type" : "text" + } + ], + "identifier" : "doc://com.test.bundle/path/to/other-page", + "kind" : "article", + "title" : "Linked from abstract", + "type" : "topic", + "url" : "/path/to/other-page" + }, { - "type": "image", - "identifier": "some-external-card-image-identifier", - "variants": [ + "alt" : "Resolved image alt text", + "identifier" : "some-external-card-image-identifier", + "type" : "image", + "variants" : [ { - "url": "http:\/\/example.com\/some-image-1x.jpg", - "traits": [ + "traits" : [ "1x" - ] - }, - { - "url": "http:\/\/example.com\/some-image-1x-dark.jpg", - "traits": [ - "1x", "dark" - ] + ], + "url" : "http://example.com/some-image.jpg" }, { - "url": "http:\/\/example.com\/some-image-2x.jpg", - "traits": [ - "2x" - ] + "traits" : [ + "2x", + "dark" + ], + "url" : "http://example.com/some-image@2x~dark.jpg" } ] } ], - "title" : "Resolved Title", - "url" : "doc:\/\/com.test.bundle\/resolved/path\/", + "title" : "Resolved title", + "topicImages" : [ + { + "identifier" : "some-external-card-image-identifier", + "type" : "card" + } + ], + "usr" : "resolved-unique-symbol-id", "variants" : [ { - "abstract" : "Resolved variant abstract for this topic.", - "declarationFragments" : [ + "abstract" : [ + { + "text" : "Resolved abstract", + "type" : "text" + }, + { + "code" : "variant", + "type" : "codeVoice" + }, + { + "text" : "Resolved abstract", + "type" : "text" + } + ], + "fragments" : [ + { + "kind" : "keyword", + "text" : "resolved" + }, + { + "kind" : "text", + "text" : " " + }, + { + "kind" : "identifier", + "text" : "variant" + }, { "kind" : "text", - "spelling" : "variant declaration fragment" + "text" : ": " + }, + { + "kind" : "typeIdentifier", + "text" : "fragment" } ], "kind" : { - "id" : "com.test.other-kind.id", + "id" : "variant-kind-id", "isSymbol" : true, - "name" : "Variant Kind Name" - }, - "language" : { - "id" : "occ", - "name" : "Variant Language Name", - "idAliases" : [ - "objective-c", - "c" - ], - "linkDisambiguationID" : "c" + "name" : "Variant kind name" }, - "title" : "Resolved Variant Title", + "language" : "occ", + "title" : "Resolved variant title", "traits" : [ { "interfaceLanguage" : "occ" @@ -130,8 +199,11 @@ RESPONSE='{ } }' -# Write this resolver's bundle identifier -echo '{"bundleIdentifier":"com.test.bundle"}' +# Write this resolver's identifier and capabilities +echo '{ + "identifier": "com.test.bundle", + "capabilities": 0 +}' # Forever, wait for DocC to send a request and respond the resolved information while true From f0a3e95ce59ae8fd24faeb2af30dbc00a1d4ab4d Mon Sep 17 00:00:00 2001 From: Andrea Fernandez Buitrago <15234535+anferbui@users.noreply.github.com> Date: Fri, 26 Sep 2025 14:34:56 +0100 Subject: [PATCH 39/90] Populate abbreviated declaration fragments in external render nodes (#1255) * Clarify doc comment for `OutOfProcessReferenceResolver.declarationFragments` These declaration fragments are directly used to populate `TopicRenderReference.fragmentsVariants` [1], which is described as "The abbreviated declaration of the symbol to display in links" [2]. This change clarifies that the declaration fragments are expected to be the abbreviated declaration fragments. [1]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Infrastructure/External%20Data/OutOfProcessReferenceResolver.swift#L169 [2]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Model/Rendering/References/TopicRenderReference.swift#L50-L53 * Add full (symbol) name to LinkDestinationSummary The only use-case for storing the full declaration fragments in `LinkDestinationSummary` rather than the shortened declaration fragments intended for display, is to derive the full name of the symbol in diagnostics [1] [2]. We have a number of tests [3] which verify that the diagnostic emitted when a symbol is referenced locally is the same as when the symbol is referenced externally. However, we want to stop storing the full declaration fragments in favour of the shortened ones because: - the full fragments are never displayed when referenced externally, as they are too long - the full fragments take up additionally storage space, and encoding/decoding time, to only be used to derive the full name of the symbol This commit updates the logic such that, we pre-derive the full name of the symbol from its declaration fragments, and then store that as part of `LinkDestinationSummary`, so that in a subsequent commit we can stop storing the full declaration fragments and store only the shortened ones instead. The diagnostic logic has also been updated to use the new field rather than the full declaration fragments. [1]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/Infrastructure/Link%20Resolution/ExternalPathHierarchyResolver.swift#L61-L63 [2]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/Infrastructure/Link%20Resolution/PathHierarchy%2BError.swift#L115-L117 [3]: https://github.com/swiftlang/swift-docc/blob/9413c599817abc9dd4f8feeed57a998b19a05a6f/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift#L907-L918 * Use the abbreviated declaration fragments in LinkDestinationSummary LinkDestinationSummary contains a summary of an element that you can link to from outside the documentation bundle. [1] This information is meant to be used by a server to provide information to an out-of-process resolver to resolve links to external entities, so that the partner implementation of `LinkDestinationSummary` is `OutOfProcessReferenceResolver.ResolvedInformation` [2]. However, currently `OutOfProcessReferenceResolver.ResolvedInformation. declarationFragments` is expecting the abbreviated declaration fragments, but we are storing the full fragments instead. [3] Instead, we should be storing the abbreviated declaration fragments, which are stored as the `subHeading` of the symbol. [4] This subheading is further processed during the render node transformation phase [5], and stored as `renderNode.metadata.fragmentsVariants`. This commit modifies `LinkDestinationSummary` such that its declaration fragments are the abbreviated declaration fragments from `renderNode.metadata.fragmentsVariants` rather than the full declaration fragments. The final result will be that declaration fragments for external links will behave the same as local links when referencing them in the Topics section. They will both now use the abbreviated declaration fragments. [1]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift#L66 [2]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Infrastructure/External%20Data/OutOfProcessReferenceResolver.swift#L558-L562 [3]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift#L445 [4]: https://github.com/swiftlang/swift-docc-symbolkit/blob/ebe89c7da4cf03ded04cd708f3399087c6f2dad7/Sources/SymbolKit/SymbolGraph/Symbol/Names.swift#L28-L31 * Populate declaration fragments in navigator metadata for external links Now that the declaration fragments will be the abbreviated declaration fragments from `LinkDestinationSummary`, we can propagate those to the navigator metadata for them to be used to inform the title of the navigator item [1]. Fixes rdar://152652751. [1]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Indexing/Navigator/RenderNode%2BNavigatorIndex.swift#L140 * Add navigator title to LinkDestinationSummary The navigator title, which contains abbreviated fragments of a symbol's declaration for display in navigation [1], was missing from LinkDestinationSummary. The navigator title is used here [2], but the information wasn't being populated anywhere [3]. This commit captures the navigator title information from the render metadata, and stores it as part of `LinkDestinationSummary`. While the original information comes from the symbol graph [4], the `DocumentationContentRenderer` has some special logic to sanitise the declaration fragments, particularly for Swift [5]. In order to ensure that we have parity between internal and external navigator titles, used the render metadata rather than the original information from the symbol graph. This mirrors what we're already doing with the shortened declaration fragments, which also are processed with the same logic. Finally, `LinkDestinationSummary.makeTopicRenderReference()` has been updated to populate the navigator title variants, such that after this commit navigator titles will be correctly populated for external links in the navigator. Fixes rdar://156488184. [1]: https://github.com/swiftlang/swift-docc/blob/257c62d1ef11efc023667fda89c0cd4419a8afa3/Sources/SwiftDocC/Model/Rendering/References/TopicRenderReference.swift#L60-L63 [2]: https://github.com/swiftlang/swift-docc/blob/257c62d1ef11efc023667fda89c0cd4419a8afa3/Sources/SwiftDocC/Indexing/Navigator/RenderNode%2BNavigatorIndex.swift#L153 [3]: https://github.com/swiftlang/swift-docc/blob/257c62d1ef11efc023667fda89c0cd4419a8afa3/Sources/SwiftDocC/Infrastructure/Link%20Resolution/ExternalPathHierarchyResolver.swift#L225 [4]: https://github.com/swiftlang/swift-docc-symbolkit/blob/3fc0c6880f712ce38137d0752f24b568fce1522e/Sources/SymbolKit/SymbolGraph/Symbol/Names.swift#L23-L26 [5]: https://github.com/swiftlang/swift-docc/blob/257c62d1ef11efc023667fda89c0cd4419a8afa3/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift#L87-L105 * Add navigator title tests for external links in the navigator Now that the navigator title is supported in external references, we can add some tests which verify this logic is working as expected. Added some tests which verify that the navigator title is used as expected for both Swift and Objective C symbols. * Rename `fullName` to `plainTextDeclaration` in LinkDestinationSummary `plainTextDeclaration` is clearer and makes it harder to confuse it with `title`. It is also closer to how the value is derived. Also updates the JSON schema in `LinkableEntities.json` to add the new `plainTextDeclaration` property to the `LinkDestinationSummary` and `LinkDestinationSummaryVariant` schemas. * Rename to `subheadingDeclarationFragments` for clarity `declarationFragments` was not descriptive enough about the contents of the property, so renamed to `subheadingDeclarationFragments` and deprecated `declarationFragments` for backwards compatibility. The JSON encoding key is kept the same (`fragments`) for backwards compatibility. * Rename `navigatorTitle` to `navigatorDeclarationFragments` Renamed the property in `LinkDestinationSummary` which stores the declaration fragments for the navigator to `navigatorDeclarationFragments`, to follow the same naming convention as `subheadingDeclarationFragments` which fulfils a very similar purpose. The documentation comments mirror each other, with one explaining that it's intended for use in topic groups, and the other that it's intended for use in navigators. * Rename helper function for readability Clarifies the function name so that it clearly states that only top level nodes are returned, as well as improving the grammar at the call site. * Fix minor issue in comment --- ...enceResolver+DeprecatedCommunication.swift | 8 +- .../ExternalPathHierarchyResolver.swift | 24 ++- .../LinkResolver+NavigatorIndex.swift | 15 +- .../LinkTargets/LinkDestinationSummary.swift | 187 +++++++++++++++--- .../Resources/LinkableEntities.json | 20 ++ .../Indexing/ExternalRenderNodeTests.swift | 175 ++++++++++++++-- .../Indexing/RenderIndexTests.swift | 2 +- .../ExternalReferenceResolverTests.swift | 2 +- .../TestExternalReferenceResolvers.swift | 3 + .../LinkDestinationSummaryTests.swift | 91 ++++++--- .../SemaToRenderNodeMultiLanguageTests.swift | 2 +- ...OutOfProcessReferenceResolverV2Tests.swift | 10 +- .../clang/MixedLanguageFramework.symbols.json | 57 ++---- .../ConvertActionTests.swift | 2 +- 14 files changed, 448 insertions(+), 150 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift index e95a3a01ff..153a9aa72d 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift @@ -162,6 +162,8 @@ extension OutOfProcessReferenceResolver { /// Information about the platforms and their versions where the resolved node is available, if any. public let platforms: [PlatformAvailability]? /// Information about the resolved declaration fragments, if any. + /// + /// This is expected to be an abbreviated declaration for the symbol. public let declarationFragments: DeclarationFragments? // We use the real types here because they're Codable and don't have public member-wise initializers. @@ -205,7 +207,7 @@ extension OutOfProcessReferenceResolver { /// - language: The resolved language. /// - availableLanguages: The languages where the resolved node is available. /// - platforms: The platforms and their versions where the resolved node is available, if any. - /// - declarationFragments: The resolved declaration fragments, if any. + /// - declarationFragments: The resolved declaration fragments, if any. This is expected to be an abbreviated declaration for the symbol. /// - topicImages: Images that are used to represent the summarized element. /// - references: References used in the content of the summarized element. /// - variants: The variants of content for this resolver information. @@ -259,6 +261,8 @@ extension OutOfProcessReferenceResolver { public let language: VariantValue /// The declaration fragments of the variant or `nil` if the declaration is the same as the resolved information. /// + /// This is expected to be an abbreviated declaration for the symbol. + /// /// If the resolver information has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. public let declarationFragments: VariantValue @@ -271,7 +275,7 @@ extension OutOfProcessReferenceResolver { /// - title: The resolved title /// - abstract: The resolved (plain text) abstract. /// - language: The resolved language. - /// - declarationFragments: The resolved declaration fragments, if any. + /// - declarationFragments: The resolved declaration fragments, if any. This is expected to be an abbreviated declaration for the symbol. public init( traits: [RenderNode.Variant.Trait], kind: VariantValue = nil, diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift index 4c6fac3500..55bb272813 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift @@ -58,13 +58,13 @@ final class ExternalPathHierarchyResolver { return collidingNode.name } if let symbolID = collidingNode.symbol?.identifier { - if symbolID.interfaceLanguage == summary.language.id, let fragments = summary.declarationFragments { - return fragments.plainTextDeclaration() + if symbolID.interfaceLanguage == summary.language.id, let plainTextDeclaration = summary.plainTextDeclaration { + return plainTextDeclaration } if let variant = summary.variants.first(where: { $0.traits.contains(.interfaceLanguage(symbolID.interfaceLanguage)) }), - let fragments = variant.declarationFragments ?? summary.declarationFragments + let plainTextDeclaration = variant.plainTextDeclaration ?? summary.plainTextDeclaration { - return fragments.plainTextDeclaration() + return plainTextDeclaration } } return summary.title @@ -153,12 +153,6 @@ final class ExternalPathHierarchyResolver { } } -private extension Sequence { - func plainTextDeclaration() -> String { - return self.map(\.text).joined().split(whereSeparator: { $0.isWhitespace || $0.isNewline }).joined(separator: " ") - } -} - // MARK: ExternalEntity extension LinkDestinationSummary { @@ -177,7 +171,8 @@ extension LinkDestinationSummary { var titleVariants = VariantCollection(defaultValue: title) var abstractVariants = VariantCollection(defaultValue: abstract ?? []) - var fragmentVariants = VariantCollection(defaultValue: declarationFragments) + var fragmentVariants = VariantCollection(defaultValue: subheadingDeclarationFragments) + var navigatorTitleVariants = VariantCollection(defaultValue: navigatorDeclarationFragments) for variant in variants { let traits = variant.traits @@ -187,9 +182,12 @@ extension LinkDestinationSummary { if let abstract = variant.abstract { abstractVariants.variants.append(.init(traits: traits, patch: [.replace(value: abstract ?? [])])) } - if let fragment = variant.declarationFragments { + if let fragment = variant.subheadingDeclarationFragments { fragmentVariants.variants.append(.init(traits: traits, patch: [.replace(value: fragment)])) } + if let navigatorTitle = variant.navigatorDeclarationFragments { + navigatorTitleVariants.variants.append(.init(traits: traits, patch: [.replace(value: navigatorTitle)])) + } } return TopicRenderReference( @@ -201,7 +199,7 @@ extension LinkDestinationSummary { required: false, role: role, fragmentsVariants: fragmentVariants, - navigatorTitleVariants: .init(defaultValue: nil), + navigatorTitleVariants: navigatorTitleVariants, estimatedTime: nil, conformance: nil, isBeta: isBeta, diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift index 8fa9575bd5..2bb01f5abe 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift @@ -69,6 +69,13 @@ package struct ExternalRenderNode { topicRenderReference.navigatorTitleVariants } + /// The variants of the abbreviated declaration of the symbol to display in links and fall-back to in navigation. + /// + /// This value is `nil` if the referenced page is not a symbol. + var fragmentsVariants: VariantCollection<[DeclarationRenderSection.Token]?> { + topicRenderReference.fragmentsVariants + } + /// Author provided images that represent this page. var images: [TopicImage] { entity.topicImages ?? [] @@ -129,7 +136,8 @@ struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { role: renderNode.role, symbolKind: renderNode.symbolKind?.renderingIdentifier, images: renderNode.images, - isBeta: renderNode.isBeta + isBeta: renderNode.isBeta, + fragments: renderNode.fragmentsVariants.value(for: traits) ) } } @@ -143,19 +151,16 @@ struct ExternalRenderNodeMetadataRepresentation: NavigatorIndexableRenderMetadat var symbolKind: String? var images: [TopicImage] var isBeta: Bool + var fragments: [DeclarationRenderSection.Token]? // Values that we have insufficient information to derive. // These are needed to conform to the navigator indexable metadata protocol. // - // The fragments that we get as part of the external link are the full declaration fragments. - // These are too verbose for the navigator, so instead of using them, we rely on the title, navigator title and symbol kind instead. - // // The role heading is used to identify Property Lists. // The value being missing is used for computing the final navigator title. // // The platforms are used for generating the availability index, // but doesn't affect how the node is rendered in the sidebar. - var fragments: [DeclarationRenderSection.Token]? = nil var roleHeading: String? = nil var platforms: [AvailabilityRenderItem]? = nil } diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 0a53b58c3d..a092359f28 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -135,11 +135,32 @@ public struct LinkDestinationSummary: Codable, Equatable { /// The unique, precise identifier for this symbol that you use to reference it across different systems, or `nil` if the summarized element isn't a symbol. public let usr: String? + /// The plain text declaration of this symbol, derived from its full declaration fragments, or `nil` if the summarized element isn't a symbol. + public let plainTextDeclaration: String? + /// The rendered fragments of a symbol's declaration. public typealias DeclarationFragments = [DeclarationRenderSection.Token] - /// The fragments for this symbol's declaration, or `nil` if the summarized element isn't a symbol. - public let declarationFragments: DeclarationFragments? + /// The simplified "subheading" declaration fragments for this symbol, or `nil` if the summarized element isn't a symbol. + /// + /// These subheading fragments are suitable to use to refer to a symbol that's linked to in a topic group. + /// + /// - Note: The subheading fragments do not represent the symbol's full declaration. + /// Different overloads may have indistinguishable subheading fragments. + public let subheadingDeclarationFragments: DeclarationFragments? + @available(*, deprecated, renamed: "subheadingDeclarationFragments", message: "Use 'subheadingDeclarationFragments' instead. This deprecated API will be removed after 6.3 is released.") + public var declarationFragments: DeclarationFragments? { + subheadingDeclarationFragments + } + + /// The simplified "navigator" declaration fragments for this symbol, or `nil` if the summarized element isn't a symbol. + /// + /// These navigator fragments are suitable to use to refer to a symbol that's linked to in a navigator. + /// + /// - Note: The navigator title does not represent the symbol's full declaration. + /// Different overloads may have indistinguishable navigator fragments. + public let navigatorDeclarationFragments: DeclarationFragments? + /// Any previous URLs for this element. /// /// A web server can use this list of URLs to redirect to the current URL. @@ -193,11 +214,30 @@ public struct LinkDestinationSummary: Codable, Equatable { /// If the summarized element has a precise symbol identifier but the variant doesn't, this property will be `Optional.some(nil)`. public let usr: VariantValue - /// The declaration of the variant or `nil` if the declaration is the same as the summarized element. + /// The plain text declaration of this symbol, derived from its full declaration fragments, or `nil` if the precise symbol identifier is the same as the summarized element. + /// + /// If the summarized element has a plain text declaration but the variant doesn't, this property will be `Optional.some(nil)`. + public let plainTextDeclaration: VariantValue + + /// The simplified "subheading" declaration fragments for this symbol, or `nil` if the declaration is the same as the summarized element. + /// + /// These subheading fragments are suitable to use to refer to a symbol that's linked to in a topic group. /// /// If the summarized element has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. - public let declarationFragments: VariantValue + public let subheadingDeclarationFragments: VariantValue + @available(*, deprecated, renamed: "subheadingDeclarationFragments", message: "Use 'subheadingDeclarationFragments' instead. This deprecated API will be removed after 6.3 is released.") + public var declarationFragments: VariantValue { + subheadingDeclarationFragments + } + + /// The simplified "navigator" declaration fragments for this symbol, or `nil` if the navigator title is the same as the summarized element. + /// + /// These navigator fragments are suitable to use to refer to a symbol that's linked to in a navigator. + /// + /// If the summarized element has a navigator title but the variant doesn't, this property will be `Optional.some(nil)`. + public let navigatorDeclarationFragments: VariantValue + /// Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. /// /// If the summarized element has an image but the variant doesn't, this property will be `Optional.some(nil)`. @@ -215,7 +255,9 @@ public struct LinkDestinationSummary: Codable, Equatable { /// - abstract: The abstract of the variant or `nil` if the abstract is the same as the summarized element. /// - taskGroups: The taskGroups of the variant or `nil` if the taskGroups is the same as the summarized element. /// - usr: The precise symbol identifier of the variant or `nil` if the precise symbol identifier is the same as the summarized element. - /// - declarationFragments: The declaration of the variant or `nil` if the declaration is the same as the summarized element. + /// - plainTextDeclaration: The plain text declaration of this symbol, derived from its full declaration fragments, or `nil` if the precise symbol identifier is the same as the summarized element. + /// - subheadingDeclarationFragments: The simplified "subheading" declaration fragments for this symbol, to display in topic groups, or `nil` if the declaration is the same as the summarized element. + /// - navigatorDeclarationFragments: The simplified "navigator" declaration fragments for this symbol, to display in navigation, or `nil` if the declaration is the same as the summarized element. public init( traits: [RenderNode.Variant.Trait], kind: VariantValue = nil, @@ -225,7 +267,9 @@ public struct LinkDestinationSummary: Codable, Equatable { abstract: VariantValue = nil, taskGroups: VariantValue<[LinkDestinationSummary.TaskGroup]?> = nil, usr: VariantValue = nil, - declarationFragments: VariantValue = nil + plainTextDeclaration: VariantValue = nil, + subheadingDeclarationFragments: VariantValue = nil, + navigatorDeclarationFragments: VariantValue = nil ) { self.traits = traits self.kind = kind @@ -235,10 +279,12 @@ public struct LinkDestinationSummary: Codable, Equatable { self.abstract = abstract self.taskGroups = taskGroups self.usr = usr - self.declarationFragments = declarationFragments + self.plainTextDeclaration = plainTextDeclaration + self.subheadingDeclarationFragments = subheadingDeclarationFragments + self.navigatorDeclarationFragments = navigatorDeclarationFragments } - @available(*, deprecated, renamed: "init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:declarationFragments:)", message: "Use `init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:declarationFragments:)` instead. `TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") + @available(*, deprecated, renamed: "init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:)", message: "Use `init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:)` instead. `TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") public init( traits: [RenderNode.Variant.Trait], kind: VariantValue = nil, @@ -248,7 +294,9 @@ public struct LinkDestinationSummary: Codable, Equatable { abstract: VariantValue = nil, taskGroups: VariantValue<[LinkDestinationSummary.TaskGroup]?> = nil, usr: VariantValue = nil, + plainTextDeclaration: VariantValue = nil, declarationFragments: VariantValue = nil, + navigatorDeclarationFragments: VariantValue = nil, topicImages: VariantValue<[TopicImage]?> = nil ) { self.init( @@ -260,7 +308,9 @@ public struct LinkDestinationSummary: Codable, Equatable { abstract: abstract, taskGroups: taskGroups, usr: usr, - declarationFragments: declarationFragments + plainTextDeclaration: plainTextDeclaration, + subheadingDeclarationFragments: declarationFragments, + navigatorDeclarationFragments: navigatorDeclarationFragments ) } } @@ -281,7 +331,9 @@ public struct LinkDestinationSummary: Codable, Equatable { /// - platforms: Information about the platforms for which the summarized element is available. /// - taskGroups: The reference URLs of the summarized element's children, grouped by their task groups. /// - usr: The unique, precise identifier for this symbol that you use to reference it across different systems, or `nil` if the summarized element isn't a symbol. - /// - declarationFragments: The fragments for this symbol's declaration, or `nil` if the summarized element isn't a symbol. + /// - plainTextDeclaration: The plain text declaration of this symbol, derived from its full declaration fragments, or `nil` if the summarized element isn't a symbol. + /// - subheadingDeclarationFragments: The simplified "subheading" fragments for this symbol, to display in topic groups, or `nil` if the summarized element isn't a symbol. + /// - navigatorDeclarationFragments: The simplified "subheading" declaration fragments for this symbol, to display in navigation, or `nil` if the summarized element isn't a symbol. /// - redirects: Any previous URLs for this element, or `nil` if this element has no previous URLs. /// - topicImages: Images that are used to represent the summarized element, or `nil` if this element has no topic images. /// - references: References used in the content of the summarized element, or `nil` if this element has no references to other content. @@ -296,7 +348,9 @@ public struct LinkDestinationSummary: Codable, Equatable { platforms: [LinkDestinationSummary.PlatformAvailability]? = nil, taskGroups: [LinkDestinationSummary.TaskGroup]? = nil, usr: String? = nil, - declarationFragments: LinkDestinationSummary.DeclarationFragments? = nil, + plainTextDeclaration: String? = nil, + subheadingDeclarationFragments: LinkDestinationSummary.DeclarationFragments? = nil, + navigatorDeclarationFragments: LinkDestinationSummary.DeclarationFragments? = nil, redirects: [URL]? = nil, topicImages: [TopicImage]? = nil, references: [any RenderReference]? = nil, @@ -312,12 +366,54 @@ public struct LinkDestinationSummary: Codable, Equatable { self.platforms = platforms self.taskGroups = taskGroups self.usr = usr - self.declarationFragments = declarationFragments + self.plainTextDeclaration = plainTextDeclaration + self.subheadingDeclarationFragments = subheadingDeclarationFragments + self.navigatorDeclarationFragments = navigatorDeclarationFragments self.redirects = redirects self.topicImages = topicImages self.references = references self.variants = variants } + + @available(*, deprecated, renamed: "init(kind:language:relativePresentationURL:referenceURL:title:abstract:availableLanguages:platforms:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:redirects:topicImages:references:variants:)", message: "Use `init(kind:language:relativePresentationURL:referenceURL:title:abstract:availableLanguages:platforms:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:redirects:topicImages:references:variants:)` instead. This property will be removed after 6.3 is released") + public init( + kind: DocumentationNode.Kind, + language: SourceLanguage, + relativePresentationURL: URL, + referenceURL: URL, title: String, + abstract: LinkDestinationSummary.Abstract? = nil, + availableLanguages: Set, + platforms: [LinkDestinationSummary.PlatformAvailability]? = nil, + taskGroups: [LinkDestinationSummary.TaskGroup]? = nil, + usr: String? = nil, + plainTextDeclaration: String? = nil, + declarationFragments: LinkDestinationSummary.DeclarationFragments?, + navigatorDeclarationFragments: LinkDestinationSummary.DeclarationFragments? = nil, + redirects: [URL]? = nil, + topicImages: [TopicImage]? = nil, + references: [any RenderReference]? = nil, + variants: [LinkDestinationSummary.Variant] + ) { + self.init( + kind: kind, + language: language, + relativePresentationURL: relativePresentationURL, + referenceURL: referenceURL, + title: title, + abstract: abstract, + availableLanguages: availableLanguages, + platforms: platforms, + taskGroups: taskGroups, + usr: usr, + plainTextDeclaration: plainTextDeclaration, + subheadingDeclarationFragments: declarationFragments, + navigatorDeclarationFragments: navigatorDeclarationFragments, + redirects: redirects, + topicImages: topicImages, + references: references, + variants: variants + ) + } } // MARK: - Accessing the externally linkable elements @@ -442,7 +538,7 @@ extension LinkDestinationSummary { platforms: platforms, taskGroups: taskGroups, usr: nil, - declarationFragments: nil, + subheadingDeclarationFragments: nil, redirects: redirects, topicImages: topicImages.nilIfEmpty, references: references.nilIfEmpty, @@ -466,24 +562,37 @@ extension LinkDestinationSummary { let abstract = renderSymbolAbstract(symbol.abstractVariants[summaryTrait] ?? symbol.abstract) let usr = symbol.externalIDVariants[summaryTrait] ?? symbol.externalID - let declaration = (symbol.declarationVariants[summaryTrait] ?? symbol.declaration).renderDeclarationTokens() + let plainTextDeclaration = symbol.plainTextDeclaration(for: summaryTrait) let language = documentationNode.sourceLanguage - + // If no abbreviated declaration fragments are available, use the full declaration fragments instead. + // In this case, they are assumed to be the same. + let subheadingDeclarationFragments = renderNode.metadata.fragmentsVariants.value(for: language) ?? (symbol.declarationVariants[summaryTrait] ?? symbol.declaration).renderDeclarationTokens() + let navigatorDeclarationFragments = renderNode.metadata.navigatorTitleVariants.value(for: language) + let variants: [Variant] = documentationNode.availableVariantTraits.compactMap { trait in // Skip the variant for the summarized elements source language. guard let interfaceLanguage = trait.interfaceLanguage, interfaceLanguage != documentationNode.sourceLanguage.id else { return nil } - let declarationVariant = symbol.declarationVariants[trait]?.renderDeclarationTokens() - let abstractVariant: Variant.VariantValue = symbol.abstractVariants[trait].map { renderSymbolAbstract($0) } func nilIfEqual(main: Value, variant: Value?) -> Value? { return main == variant ? nil : variant } + let plainTextDeclarationVariant = symbol.plainTextDeclaration(for: trait) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage(interfaceLanguage)] + + // Use the abbreviated declaration fragments instead of the full declaration fragments. + // These have been derived from the symbol's subheading declaration fragments as part of rendering. + // We only want an abbreviated version of the declaration in the link summary (for display in Topic sections, the navigator, etc.). + // Otherwise, the declaration would be too verbose. + // + // However if no abbreviated declaration fragments are available, use the full declaration fragments instead. + // In this case, they are assumed to be the same. + let subheadingDeclarationFragmentsVariant = renderNode.metadata.fragmentsVariants.value(for: variantTraits) ?? symbol.declarationVariants[trait]?.renderDeclarationTokens() + let navigatorDeclarationFragmentsVariant = renderNode.metadata.navigatorTitleVariants.value(for: variantTraits) return Variant( traits: variantTraits, kind: nilIfEqual(main: kind, variant: symbol.kindVariants[trait].map { DocumentationNode.kind(forKind: $0.identifier) }), @@ -493,7 +602,9 @@ extension LinkDestinationSummary { abstract: nilIfEqual(main: abstract, variant: abstractVariant), taskGroups: nilIfEqual(main: taskGroups, variant: taskGroupVariants[variantTraits]), usr: nil, // The symbol variant uses the same USR - declarationFragments: nilIfEqual(main: declaration, variant: declarationVariant) + plainTextDeclaration: nilIfEqual(main: plainTextDeclaration, variant: plainTextDeclarationVariant), + subheadingDeclarationFragments: nilIfEqual(main: subheadingDeclarationFragments, variant: subheadingDeclarationFragmentsVariant), + navigatorDeclarationFragments: nilIfEqual(main: navigatorDeclarationFragments, variant: navigatorDeclarationFragmentsVariant) ) } @@ -512,7 +623,9 @@ extension LinkDestinationSummary { platforms: platforms, taskGroups: taskGroups, usr: usr, - declarationFragments: declaration, + plainTextDeclaration: plainTextDeclaration, + subheadingDeclarationFragments: subheadingDeclarationFragments, + navigatorDeclarationFragments: navigatorDeclarationFragments, redirects: redirects, topicImages: topicImages.nilIfEmpty, references: references.nilIfEmpty, @@ -580,7 +693,7 @@ extension LinkDestinationSummary { platforms: platforms, taskGroups: [], // Landmarks have no children usr: nil, // Only symbols have a USR - declarationFragments: nil, // Only symbols have declarations + subheadingDeclarationFragments: nil, // Only symbols have declarations redirects: (landmark as? (any Redirected))?.redirects?.map { $0.oldPath }, topicImages: nil, // Landmarks doesn't have topic images references: nil, // Landmarks have no references, since only topic image references is currently supported @@ -594,9 +707,10 @@ extension LinkDestinationSummary { // Add Codable methods—which include an initializer—in an extension so that it doesn't override the member-wise initializer. extension LinkDestinationSummary { enum CodingKeys: String, CodingKey { - case kind, referenceURL, title, abstract, language, taskGroups, usr, availableLanguages, platforms, redirects, topicImages, references, variants + case kind, referenceURL, title, abstract, language, taskGroups, usr, availableLanguages, platforms, redirects, topicImages, references, variants, plainTextDeclaration case relativePresentationURL = "path" - case declarationFragments = "fragments" + case subheadingDeclarationFragments = "fragments" + case navigatorDeclarationFragments = "navigatorFragments" } public func encode(to encoder: any Encoder) throws { @@ -626,7 +740,9 @@ extension LinkDestinationSummary { try container.encodeIfPresent(platforms, forKey: .platforms) try container.encodeIfPresent(taskGroups, forKey: .taskGroups) try container.encodeIfPresent(usr, forKey: .usr) - try container.encodeIfPresent(declarationFragments, forKey: .declarationFragments) + try container.encodeIfPresent(plainTextDeclaration, forKey: .plainTextDeclaration) + try container.encodeIfPresent(subheadingDeclarationFragments, forKey: .subheadingDeclarationFragments) + try container.encodeIfPresent(navigatorDeclarationFragments, forKey: .navigatorDeclarationFragments) try container.encodeIfPresent(redirects, forKey: .redirects) try container.encodeIfPresent(topicImages, forKey: .topicImages) try container.encodeIfPresent(references?.map { CodableRenderReference($0) }, forKey: .references) @@ -682,7 +798,9 @@ extension LinkDestinationSummary { platforms = try container.decodeIfPresent([AvailabilityRenderItem].self, forKey: .platforms) taskGroups = try container.decodeIfPresent([TaskGroup].self, forKey: .taskGroups) usr = try container.decodeIfPresent(String.self, forKey: .usr) - declarationFragments = try container.decodeIfPresent(DeclarationFragments.self, forKey: .declarationFragments) + plainTextDeclaration = try container.decodeIfPresent(String.self, forKey: .plainTextDeclaration) + subheadingDeclarationFragments = try container.decodeIfPresent(DeclarationFragments.self, forKey: .subheadingDeclarationFragments) + navigatorDeclarationFragments = try container.decodeIfPresent(DeclarationFragments.self, forKey: .navigatorDeclarationFragments) redirects = try container.decodeIfPresent([URL].self, forKey: .redirects) topicImages = try container.decodeIfPresent([TopicImage].self, forKey: .topicImages) references = try container.decodeIfPresent([CodableRenderReference].self, forKey: .references).map { decodedReferences in @@ -695,9 +813,10 @@ extension LinkDestinationSummary { extension LinkDestinationSummary.Variant { enum CodingKeys: String, CodingKey { - case traits, kind, title, abstract, language, usr, taskGroups + case traits, kind, title, abstract, language, usr, taskGroups, plainTextDeclaration case relativePresentationURL = "path" case declarationFragments = "fragments" + case navigatorDeclarationFragments = "navigatorFragments" } public func encode(to encoder: any Encoder) throws { @@ -721,7 +840,9 @@ extension LinkDestinationSummary.Variant { } } try container.encodeIfPresent(usr, forKey: .usr) - try container.encodeIfPresent(declarationFragments, forKey: .declarationFragments) + try container.encodeIfPresent(plainTextDeclaration, forKey: .plainTextDeclaration) + try container.encodeIfPresent(subheadingDeclarationFragments, forKey: .declarationFragments) + try container.encodeIfPresent(navigatorDeclarationFragments, forKey: .navigatorDeclarationFragments) try container.encodeIfPresent(taskGroups, forKey: .taskGroups) } @@ -762,7 +883,10 @@ extension LinkDestinationSummary.Variant { title = try container.decodeIfPresent(String.self, forKey: .title) abstract = try container.decodeIfPresent(LinkDestinationSummary.Abstract?.self, forKey: .abstract) usr = try container.decodeIfPresent(String?.self, forKey: .usr) - declarationFragments = try container.decodeIfPresent(LinkDestinationSummary.DeclarationFragments?.self, forKey: .declarationFragments) + plainTextDeclaration = try container.decodeIfPresent(String?.self, forKey: .plainTextDeclaration) + subheadingDeclarationFragments = try container.decodeIfPresent(LinkDestinationSummary.DeclarationFragments?.self, forKey: .declarationFragments) + navigatorDeclarationFragments = try container + .decodeIfPresent(LinkDestinationSummary.DeclarationFragments?.self, forKey: .navigatorDeclarationFragments) taskGroups = try container.decodeIfPresent([LinkDestinationSummary.TaskGroup]?.self, forKey: .taskGroups) } } @@ -787,7 +911,7 @@ extension LinkDestinationSummary { guard lhs.availableLanguages == rhs.availableLanguages else { return false } guard lhs.platforms == rhs.platforms else { return false } guard lhs.taskGroups == rhs.taskGroups else { return false } - guard lhs.declarationFragments == rhs.declarationFragments else { return false } + guard lhs.subheadingDeclarationFragments == rhs.subheadingDeclarationFragments else { return false } guard lhs.redirects == rhs.redirects else { return false } guard lhs.topicImages == rhs.topicImages else { return false } guard lhs.variants == rhs.variants else { return false } @@ -871,3 +995,10 @@ private extension Collection { isEmpty ? nil : self } } + +private extension Symbol { + func plainTextDeclaration(for trait: DocumentationDataVariantsTrait) -> String? { + guard let fullDeclaration = (self.declarationVariants[trait] ?? self.declaration).mainRenderFragments() else { return nil } + return fullDeclaration.declarationFragments.map(\.spelling).joined().split(whereSeparator: { $0.isWhitespace || $0.isNewline }).joined(separator: " ") + } +} diff --git a/Sources/SwiftDocC/SwiftDocC.docc/Resources/LinkableEntities.json b/Sources/SwiftDocC/SwiftDocC.docc/Resources/LinkableEntities.json index 6f156a7423..a07d1c18a2 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/Resources/LinkableEntities.json +++ b/Sources/SwiftDocC/SwiftDocC.docc/Resources/LinkableEntities.json @@ -68,12 +68,21 @@ "usr": { "type": "string" }, + "plainTextDeclaration": { + "type": "string" + }, "fragments": { "type": "array", "items": { "$ref": "#/components/schemas/DeclarationToken" } }, + "navigatorFragments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeclarationToken" + } + }, "topicImages": { "type": "array", "items": { @@ -158,6 +167,10 @@ "type": "string", "nullable": true }, + "plainTextDeclaration": { + "type": "string", + "nullable": true + }, "fragments": { "type": "array", "items": { @@ -165,6 +178,13 @@ }, "nullable": true }, + "navigatorFragments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeclarationToken" + }, + "nullable": true + }, "taskGroups": { "type": "array", "items": { diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index d0602e48ca..d2c790caa0 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -41,6 +41,11 @@ class ExternalRenderNodeTests: XCTestCase { title: "SwiftSymbol", kind: .class, language: .swift, + declarationFragments: .init(declarationFragments: [ + .init(kind: .keyword, spelling: "class", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .identifier, spelling: "SwiftSymbol", preciseIdentifier: nil) + ]), platforms: [.init(name: "iOS", introduced: nil, isBeta: true)] ) ) @@ -50,6 +55,49 @@ class ExternalRenderNodeTests: XCTestCase { title: "ObjCSymbol", kind: .function, language: .objectiveC, + declarationFragments: .init(declarationFragments: [ + .init(kind: .text, spelling: "- ", preciseIdentifier: nil), + .init(kind: .text, spelling: "(", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "void", preciseIdentifier: nil), + .init(kind: .text, spelling: ") ", preciseIdentifier: nil), + .init(kind: .identifier, spelling: "ObjCSymbol", preciseIdentifier: nil) + ]), + platforms: [.init(name: "macOS", introduced: nil, isBeta: false)] + ) + ) + externalResolver.entitiesToReturn["/path/to/external/navigatorTitleSwiftSymbol"] = .success( + .init( + referencePath: "/path/to/external/navigatorTitleSwiftSymbol", + title: "NavigatorTitleSwiftSymbol (title)", + kind: .class, + language: .swift, + declarationFragments: .init(declarationFragments: [ + .init(kind: .keyword, spelling: "class", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .identifier, spelling: "NavigatorTitleSwiftSymbol", preciseIdentifier: nil) + ]), + navigatorTitle: .init(declarationFragments: [ + .init(kind: .identifier, spelling: "NavigatorTitleSwiftSymbol (navigator title)", preciseIdentifier: nil) + ]), + platforms: [.init(name: "iOS", introduced: nil, isBeta: true)] + ) + ) + externalResolver.entitiesToReturn["/path/to/external/navigatorTitleObjCSymbol"] = .success( + .init( + referencePath: "/path/to/external/navigatorTitleObjCSymbol", + title: "NavigatorTitleObjCSymbol (title)", + kind: .function, + language: .objectiveC, + declarationFragments: .init(declarationFragments: [ + .init(kind: .text, spelling: "- ", preciseIdentifier: nil), + .init(kind: .text, spelling: "(", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "void", preciseIdentifier: nil), + .init(kind: .text, spelling: ") ", preciseIdentifier: nil), + .init(kind: .identifier, spelling: "ObjCSymbol", preciseIdentifier: nil) + ]), + navigatorTitle: .init(declarationFragments: [ + .init(kind: .identifier, spelling: "NavigatorTitleObjCSymbol (navigator title)", preciseIdentifier: nil) + ]), platforms: [.init(name: "macOS", introduced: nil, isBeta: false)] ) ) @@ -132,13 +180,13 @@ class ExternalRenderNodeTests: XCTestCase { title: swiftTitle, availableLanguages: [.swift, .objectiveC], usr: "some-unique-symbol-id", - declarationFragments: swiftFragments, + subheadingDeclarationFragments: swiftFragments, variants: [ .init( traits: [.interfaceLanguage(SourceLanguage.objectiveC.id)], language: .objectiveC, title: objcTitle, - declarationFragments: objcFragments + subheadingDeclarationFragments: objcFragments ) ] ) @@ -152,12 +200,14 @@ class ExternalRenderNodeTests: XCTestCase { ) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) XCTAssertFalse(swiftNavigatorExternalRenderNode.metadata.isBeta) - + XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.fragments, swiftFragments) + let objcNavigatorExternalRenderNode = try XCTUnwrap( NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage(SourceLanguage.objectiveC.id)) ) XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, objcTitle) XCTAssertFalse(objcNavigatorExternalRenderNode.metadata.isBeta) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.fragments, objcFragments) } func testNavigatorWithExternalNodes() async throws { @@ -218,21 +268,114 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.count(where: \.isExternal), 0) XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.count(where: \.isExternal), 0) + + func externalTopLevelNodes(for language: SourceLanguage) -> [RenderIndex.Node]? { + renderIndex.interfaceLanguages[language.id]?.first?.children?.filter(\.isExternal) + } + // Verify that the curated external links are part of the index. - let swiftExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) - let objcExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) + let swiftExternalNodes = try XCTUnwrap(externalTopLevelNodes(for: .swift)) XCTAssertEqual(swiftExternalNodes.count, 2) + + let objcExternalNodes = try XCTUnwrap(externalTopLevelNodes(for: .objectiveC)) XCTAssertEqual(objcExternalNodes.count, 2) - XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle", "SwiftSymbol"]) - XCTAssertEqual(objcExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) - XCTAssert(swiftExternalNodes.first?.isBeta == false) - XCTAssert(swiftExternalNodes.last?.isBeta == true) - XCTAssert(objcExternalNodes.first?.isBeta == true) - XCTAssert(objcExternalNodes.last?.isBeta == false) - XCTAssertEqual(swiftExternalNodes.map(\.type), ["article", "class"]) - XCTAssertEqual(objcExternalNodes.map(\.type), ["article", "func"]) + + let swiftArticleExternalNode = try XCTUnwrap(swiftExternalNodes.first(where: { $0.path == "/path/to/external/swiftarticle" })) + let swiftSymbolExternalNode = try XCTUnwrap(swiftExternalNodes.first(where: { $0.path == "/path/to/external/swiftsymbol" })) + let objcArticleExternalNode = try XCTUnwrap(objcExternalNodes.first(where: { $0.path == "/path/to/external/objcarticle" })) + let objcSymbolExternalNode = try XCTUnwrap(objcExternalNodes.first(where: { $0.path == "/path/to/external/objcsymbol" })) + + XCTAssertEqual(swiftArticleExternalNode.title, "SwiftArticle") + XCTAssertEqual(swiftArticleExternalNode.isBeta, false) + XCTAssertEqual(swiftArticleExternalNode.type, "article") + + XCTAssertEqual(swiftSymbolExternalNode.title, "SwiftSymbol") // Classes don't use declaration fragments in their navigator title + XCTAssertEqual(swiftSymbolExternalNode.isBeta, true) + XCTAssertEqual(swiftSymbolExternalNode.type, "class") + + XCTAssertEqual(objcArticleExternalNode.title, "ObjCArticle") + XCTAssertEqual(objcArticleExternalNode.isBeta, true) + XCTAssertEqual(objcArticleExternalNode.type, "article") + + XCTAssertEqual(objcSymbolExternalNode.title, "- (void) ObjCSymbol") + XCTAssertEqual(objcSymbolExternalNode.isBeta, false) + XCTAssertEqual(objcSymbolExternalNode.type, "func") } + func testNavigatorWithExternalNodesWithNavigatorTitle() async throws { + let catalog = Folder(name: "ModuleName.docc", content: [ + Folder(name: "swift", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .swift, kind: .class, pathComponents: ["SomeClass"]) + ])) + ]), + Folder(name: "clang", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", language: .objectiveC, kind: .class, pathComponents: ["TLASomeClass"]) + ])) + ]), + + InfoPlist(identifier: "some.custom.identifier"), + + TextFile(name: "ModuleName.md", utf8Content: """ + # ``ModuleName`` + + Curate a few external language-specific symbols and articles + + ## Topics + + ### External Reference + + - + - + """), + ]) + + var configuration = DocumentationContext.Configuration() + let externalResolver = generateExternalResolver() + configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") + + let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let targetURL = try createTemporaryDirectory() + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) + builder.setup() + for externalLink in context.externalCache { + let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + try builder.index(renderNode: externalRenderNode) + } + for identifier in context.knownPages { + let entity = try context.entity(with: identifier) + let renderNode = try XCTUnwrap(converter.renderNode(for: entity)) + try builder.index(renderNode: renderNode) + } + builder.finalize() + let renderIndex = try RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")) + + // Verify that there are no uncurated external links at the top level + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.count(where: \.isExternal), 0) + XCTAssertEqual(renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.count(where: \.isExternal), 0) + + func externalTopLevelNodes(for language: SourceLanguage) -> [RenderIndex.Node]? { + renderIndex.interfaceLanguages[language.id]?.first?.children?.filter(\.isExternal) + } + + // Verify that the curated external links are part of the index. + let swiftExternalNodes = try XCTUnwrap(externalTopLevelNodes(for: .swift)) + let objcExternalNodes = try XCTUnwrap(externalTopLevelNodes(for: .objectiveC)) + + XCTAssertEqual(swiftExternalNodes.count, 1) + XCTAssertEqual(objcExternalNodes.count, 1) + + let swiftSymbolExternalNode = try XCTUnwrap(swiftExternalNodes.first) + let objcSymbolExternalNode = try XCTUnwrap(objcExternalNodes.first) + + XCTAssertEqual(swiftSymbolExternalNode.title, "NavigatorTitleSwiftSymbol (title)") // Swift types prefer not using the navigator title where possible + XCTAssertEqual(objcSymbolExternalNode.title, "NavigatorTitleObjCSymbol (navigator title)") // Objective C types prefer using the navigator title where possible + } + func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() async throws { let catalog = Folder(name: "ModuleName.docc", content: [ Folder(name: "swift", content: [ @@ -299,7 +442,7 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(swiftExternalNodes.count, 1) XCTAssertEqual(objcExternalNodes.count, 1) XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle"]) - XCTAssertEqual(objcExternalNodes.map(\.title), ["ObjCSymbol"]) + XCTAssertEqual(objcExternalNodes.map(\.title), ["- (void) ObjCSymbol"]) XCTAssertEqual(swiftExternalNodes.map(\.type), ["article"]) XCTAssertEqual(objcExternalNodes.map(\.type), ["func"]) } @@ -324,13 +467,13 @@ class ExternalRenderNodeTests: XCTestCase { availableLanguages: [.swift, .objectiveC], platforms: [.init(name: "Platform name", introduced: "1.2.3", isBeta: true)], usr: "some-unique-symbol-id", - declarationFragments: swiftFragments, + subheadingDeclarationFragments: swiftFragments, variants: [ .init( traits: [.interfaceLanguage(SourceLanguage.objectiveC.id)], language: .objectiveC, title: objcTitle, - declarationFragments: objcFragments + subheadingDeclarationFragments: objcFragments ) ] ) diff --git a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift index 0481fb8237..acc19fa5a6 100644 --- a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift @@ -89,7 +89,7 @@ final class RenderIndexTests: XCTestCase { }, { "path": "/documentation/mixedlanguageframework/bar", - "title": "Bar", + "title": "Bar (objective c)", "type": "class", "children": [ { diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 6e2526397e..84cb5e7a11 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -45,7 +45,7 @@ class ExternalReferenceResolverTests: XCTestCase { referenceURL: reference.url, title: resolvedEntityTitle, availableLanguages: [resolvedEntityLanguage], - declarationFragments: resolvedEntityDeclarationFragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, + subheadingDeclarationFragments: resolvedEntityDeclarationFragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, variants: [] ) } diff --git a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift index 7ea89aa72c..d02463b820 100644 --- a/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift +++ b/Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift @@ -28,6 +28,7 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { var kind = DocumentationNode.Kind.article var language = SourceLanguage.swift var declarationFragments: SymbolGraph.Symbol.DeclarationFragments? = nil + var navigatorTitle: SymbolGraph.Symbol.DeclarationFragments? = nil var topicImages: [(TopicImage, alt: String)]? = nil var platforms: [AvailabilityRenderItem]? = nil } @@ -92,6 +93,8 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource { title: entityInfo.title, availableLanguages: [entityInfo.language], platforms: entityInfo.platforms, + subheadingDeclarationFragments: entityInfo.declarationFragments?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, + navigatorDeclarationFragments: entityInfo.navigatorTitle?.declarationFragments.map { .init(fragment: $0, identifier: nil) }, topicImages: entityInfo.topicImages?.map(\.0), references: entityInfo.topicImages?.map { topicImage, altText in ImageReference(identifier: topicImage.identifier, altText: altText, imageAsset: assetsToReturn[topicImage.identifier.identifier] ?? .init()) diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index a51ef8b102..5606958101 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -113,7 +113,9 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(pageSummary.platforms, renderNode.metadata.platforms) XCTAssertEqual(pageSummary.redirects, nil) XCTAssertNil(pageSummary.usr, "Only symbols have USRs") - XCTAssertNil(pageSummary.declarationFragments, "Only symbols have declaration fragments") + XCTAssertNil(pageSummary.plainTextDeclaration, "Only symbols have a plain text declaration") + XCTAssertNil(pageSummary.subheadingDeclarationFragments, "Only symbols have subheading declaration fragments") + XCTAssertNil(pageSummary.navigatorDeclarationFragments, "Only symbols have navigator titles") XCTAssertNil(pageSummary.abstract, "There is no text to use as an abstract for the tutorial page") XCTAssertNil(pageSummary.topicImages, "The tutorial page doesn't have any topic images") XCTAssertNil(pageSummary.references, "Since the tutorial page doesn't have any topic images it also doesn't have any references") @@ -131,7 +133,9 @@ class LinkDestinationSummaryTests: XCTestCase { URL(string: "old/path/to/this/landmark")!, ]) XCTAssertNil(sectionSummary.usr, "Only symbols have USRs") - XCTAssertNil(sectionSummary.declarationFragments, "Only symbols have declaration fragments") + XCTAssertNil(sectionSummary.plainTextDeclaration, "Only symbols have a plain text declaration") + XCTAssertNil(sectionSummary.subheadingDeclarationFragments, "Only symbols have subheading declaration fragments") + XCTAssertNil(sectionSummary.navigatorDeclarationFragments, "Only symbols have navigator titles") XCTAssertEqual(sectionSummary.abstract, [ .text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt"), .text(" "), @@ -180,11 +184,15 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(summary.availableLanguages, [.swift]) XCTAssertEqual(summary.platforms, renderNode.metadata.platforms) XCTAssertEqual(summary.usr, "s:5MyKit0A5ClassC") - XCTAssertEqual(summary.declarationFragments, [ + XCTAssertEqual(summary.plainTextDeclaration, "class MyClass") + XCTAssertEqual(summary.subheadingDeclarationFragments, [ .init(text: "class", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "MyClass", kind: .identifier, identifier: nil), ]) + XCTAssertEqual(summary.navigatorDeclarationFragments, [ + .init(text: "MyClassNavigator", kind: .identifier, identifier: nil), + ]) XCTAssertNil(summary.topicImages) XCTAssertNil(summary.references) @@ -219,13 +227,17 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(summary.availableLanguages, [.swift]) XCTAssertEqual(summary.platforms, renderNode.metadata.platforms) XCTAssertEqual(summary.usr, "s:5MyKit0A5ProtocolP") - XCTAssertEqual(summary.declarationFragments, [ + XCTAssertEqual(summary.plainTextDeclaration, "protocol MyProtocol : Hashable") + XCTAssertEqual(summary.subheadingDeclarationFragments, [ .init(text: "protocol", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "MyProtocol", kind: .identifier, identifier: nil), .init(text: " : ", kind: .text, identifier: nil), .init(text: "Hashable", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "p:hPP"), ]) + XCTAssertEqual(summary.navigatorDeclarationFragments, [ + .init(text: "MyProtocol", kind: .identifier, identifier: nil), + ]) XCTAssertNil(summary.topicImages) XCTAssertNil(summary.references) @@ -250,7 +262,8 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(summary.availableLanguages, [.swift]) XCTAssertEqual(summary.platforms, renderNode.metadata.platforms) XCTAssertEqual(summary.usr, "s:5MyKit0A5ClassC10myFunctionyyF") - XCTAssertEqual(summary.declarationFragments, [ + XCTAssertEqual(summary.plainTextDeclaration, "func myFunction(for name...)") + XCTAssertEqual(summary.subheadingDeclarationFragments, [ .init(text: "func", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "myFunction", kind: .identifier, identifier: nil), @@ -261,6 +274,7 @@ class LinkDestinationSummaryTests: XCTestCase { .init(text: "...", kind: .text, identifier: nil), .init(text: ")", kind: .text, identifier: nil) ]) + XCTAssertNil(summary.navigatorDeclarationFragments, "This symbol doesn't have a navigator title") XCTAssertNil(summary.topicImages) XCTAssertNil(summary.references) @@ -285,13 +299,24 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(summary.availableLanguages, [.swift]) XCTAssertEqual(summary.platforms, renderNode.metadata.platforms) XCTAssertEqual(summary.usr, "s:5MyKit14globalFunction_11consideringy10Foundation4DataV_SitF") - XCTAssertEqual(summary.declarationFragments, [ + XCTAssertEqual(summary.plainTextDeclaration, "func globalFunction(_: Data, considering: Int)") + XCTAssertEqual(summary.subheadingDeclarationFragments, [ .init(text: "func", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "globalFunction", kind: .identifier, identifier: nil), .init(text: "(", kind: .text, identifier: nil), - .init(text: "_", kind: .identifier, identifier: nil), + .init(text: "Data", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:10Foundation4DataV"), + .init(text: ", ", kind: .text, identifier: nil), + .init(text: "considering", kind: .identifier, identifier: nil), .init(text: ": ", kind: .text, identifier: nil), + .init(text: "Int", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:Si"), + .init(text: ")", kind: .text, identifier: nil) + ]) + XCTAssertEqual(summary.navigatorDeclarationFragments, [ + .init(text: "func", kind: .keyword, identifier: nil), + .init(text: " ", kind: .text, identifier: nil), + .init(text: "globalFunction", kind: .identifier, identifier: nil), + .init(text: "(", kind: .text, identifier: nil), .init(text: "Data", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:10Foundation4DataV"), .init(text: ", ", kind: .text, identifier: nil), .init(text: "considering", kind: .identifier, identifier: nil), @@ -342,7 +367,8 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(summary.availableLanguages, [.swift]) XCTAssertEqual(summary.platforms, renderNode.metadata.platforms) XCTAssertEqual(summary.usr, "s:5MyKit0A5ClassC10myFunctionyyF") - XCTAssertEqual(summary.declarationFragments, [ + XCTAssertEqual(summary.plainTextDeclaration, "func myFunction(for name...)") + XCTAssertEqual(summary.subheadingDeclarationFragments, [ .init(text: "func", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "myFunction", kind: .identifier, identifier: nil), @@ -353,7 +379,8 @@ class LinkDestinationSummaryTests: XCTestCase { .init(text: "...", kind: .text, identifier: nil), .init(text: ")", kind: .text, identifier: nil) ]) - + XCTAssertNil(summary.navigatorDeclarationFragments, "This symbol doesn't have a navigator title") + XCTAssertEqual(summary.topicImages, [ TopicImage( type: .card, @@ -454,12 +481,15 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(summary.availableLanguages.sorted(), [.swift, .objectiveC]) XCTAssertEqual(summary.platforms, renderNode.metadata.platforms) XCTAssertEqual(summary.usr, "c:objc(cs)Bar") - - XCTAssertEqual(summary.declarationFragments, [ + XCTAssertEqual(summary.plainTextDeclaration, "class Bar") + XCTAssertEqual(summary.subheadingDeclarationFragments, [ .init(text: "class", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "Bar", kind: .identifier, identifier: nil) ]) + XCTAssertEqual(summary.navigatorDeclarationFragments, [ + .init(text: "Bar", kind: .identifier, identifier: nil) + ]) XCTAssertNil(summary.topicImages) XCTAssertNil(summary.references) @@ -468,14 +498,18 @@ class LinkDestinationSummaryTests: XCTestCase { // Check variant content that is different XCTAssertEqual(variant.language, .objectiveC) - XCTAssertEqual(variant.declarationFragments, [ + XCTAssertEqual(variant.plainTextDeclaration, "@interface Bar : NSObject") + XCTAssertEqual(variant.subheadingDeclarationFragments, [ .init(text: "@interface", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "Bar", kind: .identifier, identifier: nil), .init(text: " : ", kind: .text, identifier: nil), .init(text: "NSObject", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "c:objc(cs)NSObject"), ]) - + XCTAssertEqual(variant.navigatorDeclarationFragments, [ + .init(text: "Bar (objective c)", kind: .identifier, identifier: nil), + ]) + // Check variant content that is the same as the summarized element XCTAssertEqual(variant.title, nil) XCTAssertEqual(variant.abstract, nil) @@ -514,23 +548,23 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(summary.availableLanguages.sorted(), [.swift, .objectiveC]) XCTAssertEqual(summary.platforms, renderNode.metadata.platforms) XCTAssertEqual(summary.usr, "c:objc(cs)Bar(cm)myStringFunction:error:") - XCTAssertEqual(summary.declarationFragments, [ + XCTAssertEqual(summary.plainTextDeclaration, "class func myStringFunction(_ string: String) throws -> String") + XCTAssertEqual(summary.subheadingDeclarationFragments, [ .init(text: "class", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "func", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "myStringFunction", kind: .identifier, identifier: nil), .init(text: "(", kind: .text, identifier: nil), - .init(text: "_", kind: .externalParam, identifier: nil), - .init(text: " ", kind: .text, identifier: nil), - .init(text: "string", kind: .internalParam, identifier: nil), - .init(text: ": ", kind: .text, identifier: nil), .init(text: "String", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:SS"), .init(text: ") ", kind: .text, identifier: nil), .init(text: "throws", kind: .keyword, identifier: nil), .init(text: " -> ", kind: .text, identifier: nil), .init(text: "String", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:SS") ]) + XCTAssertEqual(summary.navigatorDeclarationFragments, [ + .init(text: "myStringFunction:error: (navigator title)", kind: .identifier, identifier: nil), + ]) XCTAssertNil(summary.topicImages) XCTAssertNil(summary.references) @@ -540,20 +574,13 @@ class LinkDestinationSummaryTests: XCTestCase { // Check variant content that is different XCTAssertEqual(variant.language, .objectiveC) XCTAssertEqual(variant.title, "myStringFunction:error:") - XCTAssertEqual(variant.declarationFragments, [ - .init(text: "+ (", kind: .text, identifier: nil), - .init(text: "NSString", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "c:objc(cs)NSString"), - .init(text: " *) ", kind: .text, identifier: nil), - .init(text: "myStringFunction", kind: .identifier, identifier: nil), - .init(text: ": (", kind: .text, identifier: nil), - .init(text: "NSString", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "c:objc(cs)NSString"), - .init(text: " *)string", kind: .text, identifier: nil), - .init(text: "error", kind: .identifier, identifier: nil), - .init(text: ": (", kind: .text, identifier: nil), - .init(text: "NSError", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "c:objc(cs)NSError"), - .init(text: " **)error;", kind: .text, identifier: nil) + XCTAssertEqual(variant.plainTextDeclaration, "+ (NSString *) myStringFunction: (NSString *)string error: (NSError **)error;") + XCTAssertEqual(variant.subheadingDeclarationFragments, [ + .init(text: "+ ", kind: .text, identifier: nil), + .init(text: "myStringFunction:error:", kind: .identifier, identifier: nil) ]) - + XCTAssertEqual(variant.navigatorDeclarationFragments, .none, "Navigator title is the same across variants") + // Check variant content that is the same as the summarized element XCTAssertEqual(variant.abstract, nil) XCTAssertEqual(variant.usr, nil) @@ -686,7 +713,7 @@ class LinkDestinationSummaryTests: XCTestCase { XCTAssertEqual(decoded.title, "ClassName") XCTAssertEqual(decoded.abstract?.plainText, "A brief explanation of my class.") XCTAssertEqual(decoded.relativePresentationURL.absoluteString, "documentation/MyKit/ClassName") - XCTAssertEqual(decoded.declarationFragments, [ + XCTAssertEqual(decoded.subheadingDeclarationFragments, [ .init(text: "class", kind: .keyword, identifier: nil), .init(text: " ", kind: .text, identifier: nil), .init(text: "ClassName", kind: .identifier, identifier: nil), diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift index 02638bd029..e4acc7156a 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift @@ -556,7 +556,7 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { "myStringFunction:error:", ], referenceFragments: [ - "typedef enum Foo : NSString {\n ...\n} Foo;", + "+ myStringFunction:error:", ], failureMessage: { fieldName in "Objective-C variant of 'MyArticle' article has unexpected content for '\(fieldName)'." diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift index dcd51bf89e..8a8e4ccc7c 100644 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift @@ -85,7 +85,7 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { .init(name: "secondOS", introduced: "4.5.6", isBeta: false), ], usr: "some-unique-symbol-id", - declarationFragments: .init([ + subheadingDeclarationFragments: .init([ .init(text: "struct", kind: .keyword, preciseIdentifier: nil), .init(text: " ", kind: .text, preciseIdentifier: nil), .init(text: "declaration fragment", kind: .identifier, preciseIdentifier: nil), @@ -127,7 +127,7 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { .text(" and a link: "), .reference(identifier: linkedVariantReference, isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil) ], - declarationFragments: .init([ + subheadingDeclarationFragments: .init([ .init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil) ]) ) @@ -341,12 +341,6 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { .text("."), ], availableLanguages: [.swift], - platforms: nil, - taskGroups: nil, - usr: nil, - declarationFragments: nil, - redirects: nil, - topicImages: nil, references: [originalLinkedImage, originalLinkedTopic], variants: [] ) diff --git a/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json b/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json index ec1390b35a..a845cec637 100644 --- a/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json +++ b/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json @@ -136,6 +136,12 @@ }, "names" : { "title" : "Bar", + "navigator": [ + { + "kind": "identifier", + "spelling": "Bar (objective c)" + } + ], "subHeading" : [ { "kind" : "keyword", @@ -195,7 +201,7 @@ }, { "kind" : "text", - "spelling" : " *)string" + "spelling" : " *)string " }, { "kind" : "identifier", @@ -366,46 +372,13 @@ } ], "subHeading" : [ - { - "kind" : "keyword", - "spelling" : "typedef" - }, - { - "kind" : "text", - "spelling" : " " - }, - { - "kind" : "keyword", - "spelling" : "enum" - }, { "kind" : "text", - "spelling" : " " + "spelling" : "+ " }, { "kind" : "identifier", - "spelling" : "Foo" - }, - { - "kind" : "text", - "spelling" : " : " - }, - { - "kind" : "typeIdentifier", - "spelling" : "NSString", - "preciseIdentifier": "c:@T@NSInteger" - }, - { - "kind": "text", - "spelling": " {\n ...\n} " - }, - { - "kind": "identifier", - "spelling": "Foo" - }, - { - "kind": "text", - "spelling": ";" + "spelling" : "myStringFunction:error:" } ] }, @@ -485,7 +458,7 @@ "text" : "This is the foo's description." } ] - }, + } }, { "accessLevel" : "public", @@ -570,7 +543,7 @@ { "kind" : "typeIdentifier", "spelling" : "NSString", - "preciseIdentifier": "c:@T@NSInteger", + "preciseIdentifier": "c:@T@NSInteger" }, { "kind": "text", @@ -630,7 +603,7 @@ "kind" : "identifier", "spelling" : "first" } - ], + ] }, "pathComponents" : [ "Foo", @@ -677,7 +650,7 @@ "kind" : "identifier", "spelling" : "fourth" } - ], + ] }, "pathComponents" : [ "Foo", @@ -724,7 +697,7 @@ "kind" : "identifier", "spelling" : "second" } - ], + ] }, "pathComponents" : [ "Foo", @@ -771,7 +744,7 @@ "kind" : "identifier", "spelling" : "third" } - ], + ] }, "pathComponents" : [ "Foo", diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift index 85fc99fa13..72f42ba478 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift @@ -3218,7 +3218,7 @@ private extension LinkDestinationSummary { platforms: platforms, taskGroups: taskGroups, usr: usr, - declarationFragments: nil, + subheadingDeclarationFragments: nil, redirects: redirects, topicImages: topicImages, references: references, From 5ed689da9bfde68ba97ec8cdbba7b9f2a8305629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 26 Sep 2025 17:48:42 +0200 Subject: [PATCH 40/90] Update the bin/benchmark script to Swift 6 (#1297) --- bin/benchmark/Package.swift | 4 +- .../benchmark/BenchmarkResultSeries.swift | 2 +- .../Sources/benchmark/Commands.swift | 7 ++- .../Sources/benchmark/Diff/DiffAnalysis.swift | 4 ++ .../benchmark/Diff/DiffResultsTable.swift | 63 ++++++++++++++----- .../Subcommands/CommitCommands.swift | 3 +- .../Sources/benchmark/Subcommands/Diff.swift | 11 ++-- .../benchmark/Subcommands/Measure.swift | 4 +- .../benchmark/Subcommands/RenderTrend.swift | 5 +- 9 files changed, 73 insertions(+), 30 deletions(-) diff --git a/bin/benchmark/Package.swift b/bin/benchmark/Package.swift index 7d52848664..046395b282 100644 --- a/bin/benchmark/Package.swift +++ b/bin/benchmark/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.5 +// swift-tools-version: 6.0 /* This source file is part of the Swift.org open source project @@ -14,7 +14,7 @@ import PackageDescription let package = Package( name: "benchmark", platforms: [ - .macOS(.v12) + .macOS(.v13) ], products: [ .executable( diff --git a/bin/benchmark/Sources/benchmark/BenchmarkResultSeries.swift b/bin/benchmark/Sources/benchmark/BenchmarkResultSeries.swift index c0f2d7582f..008a206e46 100644 --- a/bin/benchmark/Sources/benchmark/BenchmarkResultSeries.swift +++ b/bin/benchmark/Sources/benchmark/BenchmarkResultSeries.swift @@ -77,7 +77,7 @@ struct BenchmarkResultSeries: Codable, Equatable { /// The list of metrics gathered in these benchmark runs. public var metrics: [MetricSeries] - static var empty = BenchmarkResultSeries(platformName: "", timestamp: Date(), doccArguments: [], metrics: []) + static let empty = BenchmarkResultSeries(platformName: "", timestamp: Date(), doccArguments: [], metrics: []) enum Error: Swift.Error, CustomStringConvertible { case addedResultHasDifferentConfiguration diff --git a/bin/benchmark/Sources/benchmark/Commands.swift b/bin/benchmark/Sources/benchmark/Commands.swift index 3c41f28a27..4eb1fff713 100644 --- a/bin/benchmark/Sources/benchmark/Commands.swift +++ b/bin/benchmark/Sources/benchmark/Commands.swift @@ -12,15 +12,16 @@ import ArgumentParser import Foundation @main -struct BenchmarkCommand: ParsableCommand { - static var configuration = CommandConfiguration( +struct BenchmarkCommand: @MainActor ParsableCommand { + @MainActor + static let configuration = CommandConfiguration( abstract: "A utility for performing benchmarks for Swift-DocC.", subcommands: [Measure.self, Diff.self, CompareTo.self, MeasureCommits.self, RenderTrend.self], defaultSubcommand: Measure.self) } let doccProjectRootURL: URL = { - let url = URL(fileURLWithPath: #file) + let url = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() // Commands.swift .deletingLastPathComponent() // benchmark .deletingLastPathComponent() // Sources diff --git a/bin/benchmark/Sources/benchmark/Diff/DiffAnalysis.swift b/bin/benchmark/Sources/benchmark/Diff/DiffAnalysis.swift index cffe3e6c49..a6874188da 100644 --- a/bin/benchmark/Sources/benchmark/Diff/DiffAnalysis.swift +++ b/bin/benchmark/Sources/benchmark/Diff/DiffAnalysis.swift @@ -22,6 +22,7 @@ extension DiffResults { } } + @MainActor static func analyze(before beforeMetric: BenchmarkResultSeries.MetricSeries?, after afterMetric: BenchmarkResultSeries.MetricSeries) throws -> DiffResults.MetricAnalysis { guard let beforeMetric else { return DiffResults.MetricAnalysis( @@ -129,6 +130,7 @@ extension DiffResults { ) } + @MainActor private static func inputBiasDescription(metric: BenchmarkResultSeries.MetricSeries, sampleName: String, numbers: [Double]) -> String { // Turn the single metric series into an array of single values metric series to render the trend bars. @@ -164,6 +166,7 @@ extension DiffResults { } extension BenchmarkResultSeries.MetricSeries.ValueSeries { + @MainActor func formatted() -> String { switch self { case .duration(let value): @@ -190,6 +193,7 @@ extension BenchmarkResultSeries.MetricSeries.ValueSeries { } #if os(macOS) || os(iOS) +@MainActor private let durationFormatter: MeasurementFormatter = { let fmt = MeasurementFormatter() fmt.unitStyle = .medium diff --git a/bin/benchmark/Sources/benchmark/Diff/DiffResultsTable.swift b/bin/benchmark/Sources/benchmark/Diff/DiffResultsTable.swift index be8c39db52..2060053326 100644 --- a/bin/benchmark/Sources/benchmark/Diff/DiffResultsTable.swift +++ b/bin/benchmark/Sources/benchmark/Diff/DiffResultsTable.swift @@ -11,18 +11,40 @@ import Foundation struct DiffResultsTable { - static var columns: [(name: String, width: Int)] = [ - ("Metric", 40), - ("Change", 15), - ("Before", 20), - ("After", 20), - ] - static var totalWidth: Int { - return columns.reduce(0, { $0 + $1.width + 3 }) - 1 + struct Columns { + typealias Column = (name: String, width: Int) + var data: [Column] + + init() { + data = [ + ("Metric", 40), + ("Change", 15), + ("Before", 20), + ("After", 20), + ] + } + + var beforeInfo: Column { + get { data[2] } + set { data[2] = newValue } + } + + var afterInfo: Column { + get { data[3] } + set { data[3] = newValue } + } + + var totalWidth: Int { + data.reduce(0, { $0 + $1.width + 3 }) - 1 + } + + var names: [String] { + data.map { $0.name } + } } private(set) var output: String - init(results: DiffResults) { + init(results: DiffResults, columns: Columns) { var output = "" let allWarnings = results.analysis.flatMap { $0.warnings ?? [] } @@ -30,9 +52,10 @@ struct DiffResultsTable { output += "\(warning)\n" } - output += "┌\(String(repeating: "─", count: Self.totalWidth))┐\n" - output += Self.formattedRow(columnValues: Self.columns.map { $0.name }) - output += "├\(String(repeating: "─", count: Self.totalWidth))┤\n" + let totalWidth = columns.totalWidth + output += "┌\(String(repeating: "─", count: totalWidth))┐\n" + output += Self.formattedRow(columns: columns) + output += "├\(String(repeating: "─", count: totalWidth))┤\n" var footnoteCounter = 0 @@ -61,10 +84,16 @@ struct DiffResultsTable { footnoteCounter += footnotes.count } - output += Self.formattedRow(columnValues: [analysis.metricName, change, analysis.before ?? "-", analysis.after], colorInfo: colorInfo) + var analysisColumns = columns + analysisColumns.data[0].name = analysis.metricName + analysisColumns.data[1].name = change + analysisColumns.data[2].name = analysis.before ?? "-" + analysisColumns.data[3].name = analysis.after + + output += Self.formattedRow(columns: analysisColumns, colorInfo: colorInfo) } - output += "└\(String(repeating: "─", count: Self.totalWidth))┘\n" + output += "└\(String(repeating: "─", count: totalWidth))┘\n" let allFootnotes = results.analysis.flatMap { $0.footnotes ?? [] } if !allFootnotes.isEmpty { @@ -117,9 +146,9 @@ struct DiffResultsTable { let upTo: String.Index } - private static func formattedRow(columnValues: [String], colorInfo: [ColumnColorInfo] = []) -> String { - let values: [String] = columnValues.enumerated().map { (index, value) in - let row = value.padding(toLength: Self.columns[index].width, withPad: " ", startingAt: 0) + private static func formattedRow(columns: Columns, colorInfo: [ColumnColorInfo] = []) -> String { + let values: [String] = columns.names.enumerated().map { (index, value) in + let row = value.padding(toLength: columns.data[index].width, withPad: " ", startingAt: 0) if let colorInfo = colorInfo.first(where: { $0.index == index }) { return String(row[.. String { var output = "" From 41bc4c4fd60ca5577d3ae9b65009a3371d5b7248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 26 Sep 2025 18:03:10 +0200 Subject: [PATCH 41/90] Avoid a few possible unnecessary copies in low level implementation details (#1296) * Declare a few private types as non-copyable to avoid unintentional copies of data in low level implementation details * Avoid potential unnecessary copies when highlighting tutorial code steps --- .../PathHierarchy+PathComponent.swift | 2 +- .../PathHierarchy+TypeSignature.swift | 4 +- ...ierarchy+TypeSignatureDisambiguation.swift | 6 +-- .../Rendering/Tutorial/LineHighlighter.swift | 43 +++++++------------ .../SwiftDocC/Utility/CollectionChanges.swift | 4 +- 5 files changed, 24 insertions(+), 35 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+PathComponent.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+PathComponent.swift index 531ad22f76..f067c735b9 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+PathComponent.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+PathComponent.swift @@ -210,7 +210,7 @@ extension PathHierarchy.PathParser { } } -private struct PathComponentScanner { +private struct PathComponentScanner: ~Copyable { private var remaining: Substring static let separator: Character = "/" diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift index 481615a90a..06d33cdf38 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift @@ -230,7 +230,7 @@ extension PathHierarchy { } /// A small helper type that tracks the scope of nested brackets; `()`, `[]`, or `<>`. - private struct SwiftBracketsStack { + private struct SwiftBracketsStack: ~Copyable { enum Bracket { case angle // <> case square // [] @@ -522,7 +522,7 @@ extension PathHierarchy.PathParser { // MARK: Scanning a substring -private struct StringScanner { +private struct StringScanner: ~Copyable { private var remaining: Substring init(_ original: Substring) { diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignatureDisambiguation.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignatureDisambiguation.swift index 336e4d853e..a05904d76e 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignatureDisambiguation.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignatureDisambiguation.swift @@ -63,7 +63,7 @@ extension PathHierarchy.DisambiguationContainer { } } - private static func _minimalSuggestedDisambiguationForFewParameters(typeNames: Table) -> [[String]?] { + private static func _minimalSuggestedDisambiguationForFewParameters(typeNames: consuming Table) -> [[String]?] { typealias IntSet = _TinySmallValueIntSet // We find the minimal suggested type-signature disambiguation in two steps. // @@ -216,7 +216,7 @@ extension PathHierarchy.DisambiguationContainer { } } - private static func _minimalSuggestedDisambiguationForManyParameters(typeNames: Table) -> [[String]?] { + private static func _minimalSuggestedDisambiguationForManyParameters(typeNames: consuming Table) -> [[String]?] { // If there are more than 64 parameters or more than 64 overloads we only try to disambiguate by a single type name. // // In practice, the number of parameters goes down rather quickly. @@ -418,7 +418,7 @@ extension _TinySmallValueIntSet { // MARK: Table /// A fixed-size grid of elements. -private struct Table { +private struct Table: ~Copyable { typealias Size = (width: Int, height: Int) @usableFromInline let size: Size diff --git a/Sources/SwiftDocC/Model/Rendering/Tutorial/LineHighlighter.swift b/Sources/SwiftDocC/Model/Rendering/Tutorial/LineHighlighter.swift index d88fa5bab6..a4aca9faf7 100644 --- a/Sources/SwiftDocC/Model/Rendering/Tutorial/LineHighlighter.swift +++ b/Sources/SwiftDocC/Model/Rendering/Tutorial/LineHighlighter.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2023 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 @@ -30,23 +30,6 @@ import Foundation ``` */ public struct LineHighlighter { - /** - A local utility type to hold incremental results. - */ - private struct IncrementalResult { - /// The previous ``Code`` element to compare. - /// If this is the first ``Step``'s ``Code``, this will be `nil`. - let previousCode: Code? - - /// The highlight results accumulated so far. - let results: [Result] - - init(previousCode: Code? = nil, results: [Result] = []) { - self.previousCode = previousCode - self.results = results - } - } - /** The final resulting highlights for a given file. */ @@ -100,7 +83,7 @@ public struct LineHighlighter { } /// The lines in the `resource` file. - private func lines(of resource: ResourceReference) -> [String]? { + private func lines(of resource: borrowing ResourceReference) -> [String]? { let fileContent: String? // Check if the file is a local asset that can be read directly from the context if let fileData = try? context.resource(with: resource) { @@ -118,7 +101,7 @@ public struct LineHighlighter { } /// Returns the line highlights between two files. - private func lineHighlights(old: ResourceReference, new: ResourceReference) -> Result { + private func lineHighlights(old: borrowing ResourceReference, new: ResourceReference) -> Result { // Retrieve the contents of the current file and the file we're comparing against. guard let oldLines = lines(of: old), let newLines = lines(of: new) else { return Result(file: new, highlights: []) @@ -138,7 +121,7 @@ public struct LineHighlighter { } /// Returns the line highlights between two ``Code`` elements. - private func lineHighlights(old: Code?, new: Code) -> Result { + private func lineHighlights(old: consuming Code?, new: borrowing Code) -> Result { if let previousFileOverride = new.previousFileReference { guard !new.shouldResetDiff else { return Result(file: new.fileReference, highlights: []) @@ -157,11 +140,17 @@ public struct LineHighlighter { /// The highlights to apply for the given ``TutorialSection``. var highlights: [Result] { - return tutorialSection.stepsContent?.steps - .compactMap { $0.code } - .reduce(IncrementalResult(), { (incrementalResult, newCode) -> IncrementalResult in - let result = lineHighlights(old: incrementalResult.previousCode, new: newCode) - return IncrementalResult(previousCode: newCode, results: incrementalResult.results + [result]) - }).results ?? [] + guard let steps = tutorialSection.stepsContent?.steps else { return [] } + + var previousCode: Code? = nil + var results: [Result] = [] + + for step in steps { + guard let newCode = step.code else { continue } + results.append(lineHighlights(old: previousCode, new: newCode)) + previousCode = newCode + } + + return results } } diff --git a/Sources/SwiftDocC/Utility/CollectionChanges.swift b/Sources/SwiftDocC/Utility/CollectionChanges.swift index 597fc28583..a64328cf04 100644 --- a/Sources/SwiftDocC/Utility/CollectionChanges.swift +++ b/Sources/SwiftDocC/Utility/CollectionChanges.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -65,7 +65,7 @@ struct CollectionChanges { /// /// - Important: /// Removals need to be applied in reverse order. All removals need to be applied before applying any insertions. Insertions need to be applied in order. -private struct ChangeSegmentBuilder { +private struct ChangeSegmentBuilder: ~Copyable { typealias Segment = CollectionChanges.Segment private(set) var segments: [Segment] From 527558e009d50ae7af0ab9ec718235fbec04b888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 29 Sep 2025 10:15:53 +0200 Subject: [PATCH 42/90] Improve handling of snippet symbol graph files (#1302) * Separate snippets from other symbols rdar://147926589 rdar://161164434 #1280 * Rename tests to clarify what they're verifying * Improve tests around optional snippet prefix components * Add additional test about resolving and rendering snippets * Make it easier to verify the diagnostic log output in tests * Add additional test about snippet warnings Also, update the behavior when a snippet slice is misspelled to match what's described in the warning. * Move snippet diagnostic creation to resolver type * Add near-miss suggestions for snippet paths and slices * Only highlight the misspelled portion of snippet paths * Update user-facing documentation about optional snippet paths prefixes * Clarify that Snippet argument parsing problems are reported elsewhere * Fix typo in code comment * Update docs about earliest version with an optional snippet path prefix --- .../Infrastructure/DocumentationContext.swift | 5 + .../Link Resolution/PathHierarchy+Error.swift | 4 +- .../Link Resolution/SnippetResolver.swift | 156 ++++++++++ .../Symbol Graph/SymbolGraphLoader.swift | 23 +- .../SwiftDocC/Model/DocumentationNode.swift | 2 +- .../Semantics/MarkupReferenceResolver.swift | 28 +- .../Semantics/Snippets/Snippet.swift | 78 ++--- .../DocCDocumentation.docc/DocC.symbols.json | 55 +++- .../adding-code-snippets-to-your-content.md | 29 +- .../NonInclusiveLanguageCheckerTests.swift | 2 +- .../DocumentationContextTests.swift | 8 +- .../Infrastructure/PathHierarchyTests.swift | 25 -- .../Infrastructure/SnippetResolverTests.swift | 272 ++++++++++++++++++ .../Reference/TabNavigatorTests.swift | 14 +- .../Semantics/SnippetTests.swift | 63 ++-- .../Semantics/SymbolTests.swift | 3 +- .../XCTestCase+LoadingTestData.swift | 15 +- 17 files changed, 636 insertions(+), 146 deletions(-) create mode 100644 Sources/SwiftDocC/Infrastructure/Link Resolution/SnippetResolver.swift create mode 100644 Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 654185a776..5ab0f7b6a5 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -81,6 +81,9 @@ public class DocumentationContext { /// > Important: The topic graph has no awareness of source language specific edges. var topicGraph = TopicGraph() + /// Will be assigned during context initialization + var snippetResolver: SnippetResolver! + /// User-provided global options for this documentation conversion. var options: Options? @@ -2037,6 +2040,8 @@ public class DocumentationContext { knownDisambiguatedPathComponents: configuration.convertServiceConfiguration.knownDisambiguatedSymbolPathComponents )) } + + self.snippetResolver = SnippetResolver(symbolGraphLoader: symbolGraphLoader) } catch { // Pipe the error out of the dispatch queue. discoveryError.sync({ diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Error.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Error.swift index 67851a2126..e02a6f4c00 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Error.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Error.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -285,7 +285,7 @@ private extension PathHierarchy.Node { } } -private extension SourceRange { +extension SourceRange { static func makeRelativeRange(startColumn: Int, endColumn: Int) -> SourceRange { return SourceLocation(line: 0, column: startColumn, source: nil) ..< SourceLocation(line: 0, column: endColumn, source: nil) } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/SnippetResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/SnippetResolver.swift new file mode 100644 index 0000000000..4c98f7354d --- /dev/null +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/SnippetResolver.swift @@ -0,0 +1,156 @@ +/* + 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 SymbolKit +import Markdown + +/// A type that resolves snippet paths. +final class SnippetResolver { + typealias SnippetMixin = SymbolKit.SymbolGraph.Symbol.Snippet + typealias Explanation = Markdown.Document + + /// Information about a resolved snippet + struct ResolvedSnippet { + fileprivate var path: String // For use in diagnostics + var mixin: SnippetMixin + var explanation: Explanation? + } + /// A snippet that has been resolved, either successfully or not. + enum SnippetResolutionResult { + case success(ResolvedSnippet) + case failure(TopicReferenceResolutionErrorInfo) + } + + private var snippets: [String: ResolvedSnippet] = [:] + + init(symbolGraphLoader: SymbolGraphLoader) { + var snippets: [String: ResolvedSnippet] = [:] + + for graph in symbolGraphLoader.snippetSymbolGraphs.values { + for symbol in graph.symbols.values { + guard let snippetMixin = symbol[mixin: SnippetMixin.self] else { continue } + + let path: String = if symbol.pathComponents.first == "Snippets" { + symbol.pathComponents.dropFirst().joined(separator: "/") + } else { + symbol.pathComponents.joined(separator: "/") + } + + snippets[path] = .init(path: path, mixin: snippetMixin, explanation: symbol.docComment.map { + Document(parsing: $0.lines.map(\.text).joined(separator: "\n"), options: .parseBlockDirectives) + }) + } + } + + self.snippets = snippets + } + + func resolveSnippet(path authoredPath: String) -> SnippetResolutionResult { + // Snippet paths are relative to the root of the Swift Package. + // The first two components are always the same (the package name followed by "Snippets"). + // The later components can either be subdirectories of the "Snippets" directory or the base name of a snippet '.swift' file (without the extension). + + // Drop the common package name + "Snippets" prefix (that's always the same), if the authored path includes it. + // This enables the author to omit this prefix (but include it for backwards compatibility with older DocC versions). + var components = authoredPath.split(separator: "/", omittingEmptySubsequences: true) + + // It's possible that the package name is "Snippets", resulting in two identical components. Skip until the last of those two. + if let snippetsPrefixIndex = components.prefix(2).lastIndex(of: "Snippets"), + // Don't search for an empty string if the snippet happens to be named "Snippets" + let relativePathStart = components.index(snippetsPrefixIndex, offsetBy: 1, limitedBy: components.endIndex - 1) + { + components.removeFirst(relativePathStart) + } + + let path = components.joined(separator: "/") + if let found = snippets[path] { + return .success(found) + } else { + let replacementRange = SourceRange.makeRelativeRange(startColumn: authoredPath.utf8.count - path.utf8.count, length: path.utf8.count) + + let nearMisses = NearMiss.bestMatches(for: snippets.keys, against: path) + let solutions = nearMisses.map { candidate in + Solution(summary: "\(Self.replacementOperationDescription(from: path, to: candidate))", replacements: [ + Replacement(range: replacementRange, replacement: candidate) + ]) + } + + return .failure(.init("Snippet named '\(path)' couldn't be found", solutions: solutions, rangeAdjustment: replacementRange)) + } + } + + func validate(slice: String, for resolvedSnippet: ResolvedSnippet) -> TopicReferenceResolutionErrorInfo? { + guard resolvedSnippet.mixin.slices[slice] == nil else { + return nil + } + let replacementRange = SourceRange.makeRelativeRange(startColumn: 0, length: slice.utf8.count) + + let nearMisses = NearMiss.bestMatches(for: resolvedSnippet.mixin.slices.keys, against: slice) + let solutions = nearMisses.map { candidate in + Solution(summary: "\(Self.replacementOperationDescription(from: slice, to: candidate))", replacements: [ + Replacement(range: replacementRange, replacement: candidate) + ]) + } + + return .init("Slice named '\(slice)' doesn't exist in snippet '\(resolvedSnippet.path)'", solutions: solutions) + } +} + +// MARK: Diagnostics + +extension SnippetResolver { + static func unknownSnippetSliceProblem(source: URL?, range: SourceRange?, errorInfo: TopicReferenceResolutionErrorInfo) -> Problem { + _problem(source: source, range: range, errorInfo: errorInfo, id: "org.swift.docc.unknownSnippetPath") + } + + static func unresolvedSnippetPathProblem(source: URL?, range: SourceRange?, errorInfo: TopicReferenceResolutionErrorInfo) -> Problem { + _problem(source: source, range: range, errorInfo: errorInfo, id: "org.swift.docc.unresolvedSnippetPath") + } + + private static func _problem(source: URL?, range: SourceRange?, errorInfo: TopicReferenceResolutionErrorInfo, id: String) -> Problem { + var solutions: [Solution] = [] + var notes: [DiagnosticNote] = [] + if let range { + if let note = errorInfo.note, let source { + notes.append(DiagnosticNote(source: source, range: range, message: note)) + } + + solutions.append(contentsOf: errorInfo.solutions(referenceSourceRange: range)) + } + + let diagnosticRange: SourceRange? + if var rangeAdjustment = errorInfo.rangeAdjustment, let range { + rangeAdjustment.offsetWithRange(range) + assert(rangeAdjustment.lowerBound.column >= 0, """ + Unresolved snippet reference range adjustment created range with negative column. + Source: \(source?.absoluteString ?? "nil") + Range: \(rangeAdjustment.lowerBound.description):\(rangeAdjustment.upperBound.description) + Summary: \(errorInfo.message) + """) + diagnosticRange = rangeAdjustment + } else { + diagnosticRange = range + } + + let diagnostic = Diagnostic(source: source, severity: .warning, range: diagnosticRange, identifier: id, summary: errorInfo.message, notes: notes) + return Problem(diagnostic: diagnostic, possibleSolutions: solutions) + } + + private static func replacementOperationDescription(from: some StringProtocol, to: some StringProtocol) -> String { + if from.isEmpty { + return "Insert \(to.singleQuoted)" + } + if to.isEmpty { + return "Remove \(from.singleQuoted)" + } + return "Replace \(from.singleQuoted) with \(to.singleQuoted)" + } +} diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift index 4418b6ad60..620d184122 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift @@ -17,6 +17,7 @@ import SymbolKit /// which makes detecting symbol collisions and overloads easier. struct SymbolGraphLoader { private(set) var symbolGraphs: [URL: SymbolKit.SymbolGraph] = [:] + private(set) var snippetSymbolGraphs: [URL: SymbolKit.SymbolGraph] = [:] private(set) var unifiedGraphs: [String: SymbolKit.UnifiedSymbolGraph] = [:] private(set) var graphLocations: [String: [SymbolKit.GraphCollector.GraphKind]] = [:] private let dataProvider: any DataProvider @@ -57,7 +58,7 @@ struct SymbolGraphLoader { let loadingLock = Lock() - var loadedGraphs = [URL: (usesExtensionSymbolFormat: Bool?, graph: SymbolKit.SymbolGraph)]() + var loadedGraphs = [URL: (usesExtensionSymbolFormat: Bool?, isSnippetGraph: Bool, graph: SymbolKit.SymbolGraph)]() var loadError: (any Error)? let loadGraphAtURL: (URL) -> Void = { [dataProvider] symbolGraphURL in @@ -98,9 +99,13 @@ struct SymbolGraphLoader { usesExtensionSymbolFormat = symbolGraph.symbols.isEmpty ? nil : containsExtensionSymbols } + // If the graph doesn't have any symbols we treat it as a regular, but empty, graph. + // v + let isSnippetGraph = symbolGraph.symbols.values.first?.kind.identifier.isSnippetKind == true + // Store the decoded graph in `loadedGraphs` loadingLock.sync { - loadedGraphs[symbolGraphURL] = (usesExtensionSymbolFormat, symbolGraph) + loadedGraphs[symbolGraphURL] = (usesExtensionSymbolFormat, isSnippetGraph, symbolGraph) } } catch { // If the symbol graph was invalid, store the error @@ -140,8 +145,9 @@ struct SymbolGraphLoader { let mergeSignpostHandle = signposter.beginInterval("Build unified symbol graph", id: signposter.makeSignpostID()) let graphLoader = GraphCollector(extensionGraphAssociationStrategy: usingExtensionSymbolFormat ? .extendingGraph : .extendedGraph) - // feed the loaded graphs into the `graphLoader` - for (url, (_, graph)) in loadedGraphs { + + // feed the loaded non-snippet graphs into the `graphLoader` + for (url, (_, isSnippets, graph)) in loadedGraphs where !isSnippets { graphLoader.mergeSymbolGraph(graph, at: url) } @@ -151,7 +157,8 @@ struct SymbolGraphLoader { throw loadError } - self.symbolGraphs = loadedGraphs.mapValues(\.graph) + self.symbolGraphs = loadedGraphs.compactMapValues({ _, isSnippets, graph in isSnippets ? nil : graph }) + self.snippetSymbolGraphs = loadedGraphs.compactMapValues({ _, isSnippets, graph in isSnippets ? graph : nil }) (self.unifiedGraphs, self.graphLocations) = graphLoader.finishLoading( createOverloadGroups: FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled ) @@ -519,3 +526,9 @@ private extension SymbolGraph.Symbol.Availability.AvailabilityItem { domain?.rawValue.lowercased() == platform.rawValue.lowercased() } } + +extension SymbolGraph.Symbol.KindIdentifier { + var isSnippetKind: Bool { + self == .snippet || self == .snippetGroup + } +} diff --git a/Sources/SwiftDocC/Model/DocumentationNode.swift b/Sources/SwiftDocC/Model/DocumentationNode.swift index 803ddb7761..0ff7e33841 100644 --- a/Sources/SwiftDocC/Model/DocumentationNode.swift +++ b/Sources/SwiftDocC/Model/DocumentationNode.swift @@ -903,7 +903,7 @@ private extension BlockDirective { } } -extension [String] { +extension Collection { /// Strip the minimum leading whitespace from all the strings in this array, as follows: /// - Find the line with least amount of leading whitespace. Ignore blank lines during this search. diff --git a/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift b/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift index 8f34a30d46..00b57e5899 100644 --- a/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift +++ b/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift @@ -21,11 +21,6 @@ private func disabledLinkDestinationProblem(reference: ResolvedTopicReference, r return Problem(diagnostic: Diagnostic(source: range?.source, severity: severity, range: range, identifier: "org.swift.docc.disabledLinkDestination", summary: "The topic \(reference.path.singleQuoted) cannot be linked to."), possibleSolutions: []) } -private func unknownSnippetSliceProblem(snippetPath: String, slice: String, range: SourceRange?) -> Problem { - let diagnostic = Diagnostic(source: range?.source, severity: .warning, range: range, identifier: "org.swift.docc.unknownSnippetSlice", summary: "Snippet slice \(slice.singleQuoted) does not exist in snippet \(snippetPath.singleQuoted); this directive will be ignored") - return Problem(diagnostic: diagnostic, possibleSolutions: []) -} - private func removedLinkDestinationProblem(reference: ResolvedTopicReference, range: SourceRange?, severity: DiagnosticSeverity) -> Problem { var solutions = [Solution]() if let range, reference.pathComponents.count > 3 { @@ -171,24 +166,21 @@ struct MarkupReferenceResolver: MarkupRewriter { let source = blockDirective.range?.source switch blockDirective.name { case Snippet.directiveName: - var problems = [Problem]() - guard let snippet = Snippet(from: blockDirective, source: source, for: bundle, problems: &problems) else { + var ignoredParsingProblems = [Problem]() // Any argument parsing problems have already been reported elsewhere + guard let snippet = Snippet(from: blockDirective, source: source, for: bundle, problems: &ignoredParsingProblems) else { return blockDirective } - if let resolved = resolveAbsoluteSymbolLink(unresolvedDestination: snippet.path, elementRange: blockDirective.range) { - var argumentText = "path: \"\(resolved.absoluteString)\"" + switch context.snippetResolver.resolveSnippet(path: snippet.path) { + case .success(let resolvedSnippet): if let requestedSlice = snippet.slice, - let snippetMixin = try? context.entity(with: resolved).symbol? - .mixins[SymbolGraph.Symbol.Snippet.mixinKey] as? SymbolGraph.Symbol.Snippet { - guard snippetMixin.slices[requestedSlice] != nil else { - problems.append(unknownSnippetSliceProblem(snippetPath: snippet.path, slice: requestedSlice, range: blockDirective.nameRange)) - return blockDirective - } - argumentText.append(", slice: \"\(requestedSlice)\"") + let errorInfo = context.snippetResolver.validate(slice: requestedSlice, for: resolvedSnippet) + { + problems.append(SnippetResolver.unknownSnippetSliceProblem(source: source, range: blockDirective.arguments()["slice"]?.valueRange, errorInfo: errorInfo)) } - return BlockDirective(name: Snippet.directiveName, argumentText: argumentText, children: []) - } else { + return blockDirective + case .failure(let errorInfo): + problems.append(SnippetResolver.unresolvedSnippetPathProblem(source: source, range: blockDirective.arguments()["path"]?.valueRange, errorInfo: errorInfo)) return blockDirective } case ImageMedia.directiveName: diff --git a/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift b/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift index 417f4c5dc3..5dc54c091a 100644 --- a/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift +++ b/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift @@ -14,31 +14,40 @@ import SymbolKit /// Embeds a code example from the project's code snippets. /// +/// Use a `Snippet` directive to embed a code example from the project's "Snippets" directory on the page. +/// The `path` argument is the relative path from the package's top-level "Snippets" directory to your snippet file without the `.swift` extension. +/// /// ```markdown -/// @Snippet(path: "my-package/Snippets/example-snippet", slice: "setup") +/// @Snippet(path: "example-snippet", slice: "setup") /// ``` /// -/// Place the `Snippet` directive to embed a code example from the project's snippet directory. -/// The path that references the snippet is identified with three parts: -/// -/// 1. The package name as defined in `Package.swift` +/// If you prefer, you can specify the relative path from the package's _root_ directory (by including a "Snippets/" prefix). +/// You can also include the package name---as defined in `Package.swift`---before the "Snippets/" prefix. +/// Neither of these leading path components are necessary because all your snippet code files are always located in your package's "Snippets" directory. /// -/// 2. The directory path to the snippet file, starting with "Snippets". +/// > Earlier Versions: +/// > Before Swift-DocC 6.2.1, the `@Snippet` path needed to include both the package name component and the "Snippets" component: +/// > +/// > ```markdown +/// > @Snippet(path: "my-package/Snippets/example-snippet") +/// > ``` /// -/// 3. The name of your snippet file without the `.swift` extension +/// You can define named slices of your snippet by annotating the snippet file with `// snippet.` and `// snippet.end` lines. +/// A named slice automatically ends at the start of the next named slice, without an explicit `snippet.end` annotation. /// -/// If the snippet had slices annotated within it, an individual slice of the snippet can be referenced with the `slice` option. -/// Without the option defined, the directive embeds the entire snippet. +/// If the referenced snippet includes annotated slices, you can limit the embedded code example to a certain line range by specifying a `slice` name. +/// By default, the embedded code example includes the full snippet. For more information, see . public final class Snippet: Semantic, AutomaticDirectiveConvertible { public static let introducedVersion = "5.7" public let originalMarkup: BlockDirective - /// The path components of a symbol link that would be used to resolve a reference to a snippet, - /// only occurring as a block directive argument. + /// The relative path from your package's top-level "Snippets" directory to the snippet file that you want to embed in the page, without the `.swift` file extension. @DirectiveArgumentWrapped public var path: String - /// An optional named range to limit the lines shown. + /// The name of a snippet slice to limit the embedded code example to a certain line range. + /// + /// By default, the embedded code example includes the full snippet. @DirectiveArgumentWrapped public var slice: String? = nil @@ -65,30 +74,33 @@ public final class Snippet: Semantic, AutomaticDirectiveConvertible { extension Snippet: RenderableDirectiveConvertible { func render(with contentCompiler: inout RenderContentCompiler) -> [any RenderContent] { - guard let snippet = Snippet(from: originalMarkup, for: contentCompiler.bundle) else { + guard case .success(let resolvedSnippet) = contentCompiler.context.snippetResolver.resolveSnippet(path: path) else { + return [] + } + let mixin = resolvedSnippet.mixin + + if let slice { + guard let sliceRange = mixin.slices[slice] else { + // The warning says that unrecognized snippet slices will ignore the entire snippet. return [] } + // Render only this slice without the explanatory content. + let lines = mixin.lines + // Trim the lines + .dropFirst(sliceRange.startIndex).prefix(sliceRange.endIndex - sliceRange.startIndex) + // Trim the whitespace + .linesWithoutLeadingWhitespace() + // Make dedicated copies of each line because the RenderBlockContent.codeListing requires it. + .map { String($0) } - guard let snippetReference = contentCompiler.resolveSymbolReference(destination: snippet.path), - let snippetEntity = try? contentCompiler.context.entity(with: snippetReference), - let snippetSymbol = snippetEntity.symbol, - let snippetMixin = snippetSymbol.mixins[SymbolGraph.Symbol.Snippet.mixinKey] as? SymbolGraph.Symbol.Snippet else { - return [] - } + return [RenderBlockContent.codeListing(.init(syntax: mixin.language, code: lines, metadata: nil))] + } else { + // Render the full snippet and its explanatory content. + let fullCode = RenderBlockContent.codeListing(.init(syntax: mixin.language, code: mixin.lines, metadata: nil)) - if let requestedSlice = snippet.slice, - let requestedLineRange = snippetMixin.slices[requestedSlice] { - // Render only the slice. - let lineRange = requestedLineRange.lowerBound.. Earlier Versions:" }, { - "text" : "" + "text" : "> Before Swift-DocC 6.2.1, the `@Snippet` path needed to include both the package name component and the \"Snippets\" component:" }, { - "text" : "2. The directory path to the snippet file, starting with \"Snippets\"." + "text" : ">" }, { - "text" : "" + "text" : "> ```markdown" + }, + { + "text" : "> @Snippet(path: \"my-package\/Snippets\/example-snippet\")" }, { - "text" : "3. The name of your snippet file without the `.swift` extension" + "text" : "> ```" }, { "text" : "" }, { - "text" : "If the snippet had slices annotated within it, an individual slice of the snippet can be referenced with the `slice` option." + "text" : "You can define named slices of your snippet by annotating the snippet file with `\/\/ snippet.` and `\/\/ snippet.end` lines." }, { - "text" : "Without the option defined, the directive embeds the entire snippet." + "text" : "A named slice automatically ends at the start of the next named slice, without an explicit `snippet.end` annotation." }, { - "text" : "- Parameters:" + "text" : "" }, { - "text" : " - path: The path components of a symbol link that would be used to resolve a reference to a snippet," + "text" : "If the referenced snippet includes annotated slices, you can limit the embedded code example to a certain line range by specifying a `slice` name." }, { - "text" : " only occurring as a block directive argument." + "text" : "By default, the embedded code example includes the full snippet. For more information, see ." + }, + { + "text" : "- Parameters:" + }, + { + "text" : " - path: The relative path from your package's top-level \"Snippets\" directory to the snippet file that you want to embed in the page, without the `.swift` file extension." }, { "text" : " **(required)**" }, { - "text" : " - slice: An optional named range to limit the lines shown." + "text" : " - slice: The name of a snippet slice to limit the embedded code example to a certain line range." }, { "text" : " **(optional)**" + }, + { + "text" : " " + }, + { + "text" : " By default, the embedded code example includes the full snippet." } ] }, diff --git a/Sources/docc/DocCDocumentation.docc/adding-code-snippets-to-your-content.md b/Sources/docc/DocCDocumentation.docc/adding-code-snippets-to-your-content.md index c654ef01d9..dc336d20b4 100644 --- a/Sources/docc/DocCDocumentation.docc/adding-code-snippets-to-your-content.md +++ b/Sources/docc/DocCDocumentation.docc/adding-code-snippets-to-your-content.md @@ -91,16 +91,21 @@ swift run example-snippet To embed your snippet in an article or within the symbol reference pages, use the `@Snippet` directive. ```markdown -@Snippet(path: "my-package/Snippets/example-snippet") +@Snippet(path: "example-snippet") ``` -The `path` argument has three parts: +The `path` argument is the relative path from the package's top-level "Snippets" directory to your snippet file without the `.swift` extension. -1. The package name as defined in `Package.swift` +If you prefer, you can specify the relative path from the package's _root_ directory (by including a "Snippets/" prefix). +You can also include the package name---as defined in `Package.swift`---before the "Snippets/" prefix. +Neither of these leading path components are necessary because all your snippet code files are always located in your package's "Snippets" directory. -2. The directory path to the snippet file, starting with "Snippets". - -3. The name of your snippet file without the `.swift` extension +> Earlier Versions: +> Before Swift-DocC 6.2.1, the `@Snippet` path needed to include both the package name component and the "Snippets" component: +> +> ```markdown +> @Snippet(path: "my-package/Snippets/example-snippet") +> ``` A snippet reference displays as a block between other paragraphs. In the example package above, the `YourProject.md` file might contain this markdown: @@ -114,7 +119,7 @@ Add a single sentence or sentence fragment, which DocC uses as the page’s abst Add one or more paragraphs that introduce your content overview. -@Snippet(path: "YourProject/Snippets/example-snippet") +@Snippet(path: "example-snippet") ``` If your snippet code requires setup — like imports or variable definitions — that distract from the snippet's main focus, you can add `// snippet.hide` and `// snippet.show` lines in the snippet code to exclude the lines in between from displaying in your documentation. @@ -145,12 +150,12 @@ Replace `YourTarget` with a target from your package to preview: swift package --disable-sandbox preview-documentation --target YourTarget ``` -### Slice up your snippet to break it up in your content. +### Slice up your snippet to break it up in your content Long snippets dropped into documentation can result in a wall of text that is harder to parse and understand. Instead, annotate non-overlapping slices in the snippet, which allows you to reference and embed the slice portion of the example code. -Annotating slices in a snippet looks similiar to annotating `snippet.show` and `snippet.hide`. +Annotating slices in a snippet looks similar to annotating `snippet.show` and `snippet.hide`. You define the slice's identity in the comment, and that slice continues until the next instance of `// snippet.end` appears on a new line. When selecting your identifiers, use URL-compatible path characters. @@ -174,7 +179,7 @@ For example, the follow code examples are effectively the same: var item = MyObject.init() // snippet.end -// snipppet.configure +// snippet.configure item.size = 3 // snippet.end ``` @@ -183,7 +188,7 @@ item.size = 3 // snippet.setup var item = MyObject.init() -// snipppet.configure +// snippet.configure item.size = 3 ``` @@ -191,7 +196,7 @@ Use the `@Snippet` directive with the `slice` parameter to embed that slice as s Extending the earlier snippet example, the slice `setup` would be referenced with ```markdown -@Snippet(path: "my-package/Snippets/example-snippet", slice: "setup") +@Snippet(path: "example-snippet", slice: "setup") ``` ## Topics diff --git a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift index 2dd37d3c3e..1073bc693e 100644 --- a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift +++ b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift @@ -203,7 +203,7 @@ func aBlackListedFunc() { ]) var configuration = DocumentationContext.Configuration() configuration.externalMetadata.diagnosticLevel = severity - let (_, context) = try await loadBundle(catalog: catalog, diagnosticEngine: .init(filterLevel: severity), configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, diagnosticFilterLevel: severity, configuration: configuration) // Verify that checker diagnostics were emitted or not, depending on the diagnostic level set. XCTAssertEqual(context.problems.contains(where: { $0.diagnostic.identifier == "org.swift.docc.NonInclusiveLanguage" }), enabled) diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index 11326bef88..0243657e85 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -2229,7 +2229,7 @@ let expected = """ """), ]) - let (bundle, context) = try await loadBundle(catalog: catalog, diagnosticEngine: .init(filterLevel: .information)) + let (bundle, context) = try await loadBundle(catalog: catalog, diagnosticFilterLevel: .information) XCTAssertNil(context.soleRootModuleReference) let curationDiagnostics = context.problems.filter({ $0.diagnostic.identifier == "org.swift.docc.ArticleUncurated" }).map(\.diagnostic) @@ -5315,7 +5315,7 @@ let expected = """ func testContextDiagnosesInsufficientDisambiguationWithCorrectRange() async throws { // This test deliberately does not turn on the overloads feature // to ensure the symbol link below does not accidentally resolve correctly. - for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind { + for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind && !symbolKindID.isSnippetKind { // Generate a 4 symbols with the same name for every non overloadable symbol kind let symbols: [SymbolGraph.Symbol] = [ makeSymbol(id: "first-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]), @@ -5369,7 +5369,7 @@ let expected = """ func testContextDiagnosesIncorrectDisambiguationWithCorrectRange() async throws { // This test deliberately does not turn on the overloads feature // to ensure the symbol link below does not accidentally resolve correctly. - for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind { + for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind && !symbolKindID.isSnippetKind { // Generate a 4 symbols with the same name for every non overloadable symbol kind let symbols: [SymbolGraph.Symbol] = [ makeSymbol(id: "first-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]), @@ -5421,7 +5421,7 @@ let expected = """ func testContextDiagnosesIncorrectSymbolNameWithCorrectRange() async throws { // This test deliberately does not turn on the overloads feature // to ensure the symbol link below does not accidentally resolve correctly. - for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind { + for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind && !symbolKindID.isSnippetKind { // Generate a 4 symbols with the same name for every non overloadable symbol kind let symbols: [SymbolGraph.Symbol] = [ makeSymbol(id: "first-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]), diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index 2f3a3014ed..c7c54f5495 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -2337,31 +2337,6 @@ class PathHierarchyTests: XCTestCase { } } - func testSnippets() async throws { - let (_, context) = try await testBundleAndContext(named: "Snippets") - let tree = context.linkResolver.localResolver.pathHierarchy - - try assertFindsPath("/Snippets/Snippets/MySnippet", in: tree, asSymbolID: "$snippet__Test.Snippets.MySnippet") - - let paths = tree.caseInsensitiveDisambiguatedPaths() - XCTAssertEqual(paths["$snippet__Test.Snippets.MySnippet"], - "/Snippets/Snippets/MySnippet") - - // Test relative links from the article that overlap with the snippet's path - let snippetsArticleID = try tree.find(path: "/Snippets/Snippets", onlyFindSymbols: false) - XCTAssertEqual(try tree.findSymbol(path: "MySnippet", parent: snippetsArticleID).identifier.precise, "$snippet__Test.Snippets.MySnippet") - XCTAssertEqual(try tree.findSymbol(path: "Snippets/MySnippet", parent: snippetsArticleID).identifier.precise, "$snippet__Test.Snippets.MySnippet") - XCTAssertEqual(try tree.findSymbol(path: "Snippets/Snippets/MySnippet", parent: snippetsArticleID).identifier.precise, "$snippet__Test.Snippets.MySnippet") - XCTAssertEqual(try tree.findSymbol(path: "/Snippets/Snippets/MySnippet", parent: snippetsArticleID).identifier.precise, "$snippet__Test.Snippets.MySnippet") - - // Test relative links from another article (which doesn't overlap with the snippet's path) - let sliceArticleID = try tree.find(path: "/Snippets/SliceIndentation", onlyFindSymbols: false) - XCTAssertThrowsError(try tree.findSymbol(path: "MySnippet", parent: sliceArticleID)) - XCTAssertEqual(try tree.findSymbol(path: "Snippets/MySnippet", parent: sliceArticleID).identifier.precise, "$snippet__Test.Snippets.MySnippet") - XCTAssertEqual(try tree.findSymbol(path: "Snippets/Snippets/MySnippet", parent: sliceArticleID).identifier.precise, "$snippet__Test.Snippets.MySnippet") - XCTAssertEqual(try tree.findSymbol(path: "/Snippets/Snippets/MySnippet", parent: sliceArticleID).identifier.precise, "$snippet__Test.Snippets.MySnippet") - } - func testInheritedOperators() async throws { let (_, context) = try await testBundleAndContext(named: "InheritedOperators") let tree = context.linkResolver.localResolver.pathHierarchy diff --git a/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift new file mode 100644 index 0000000000..de88a9ab40 --- /dev/null +++ b/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift @@ -0,0 +1,272 @@ +/* + 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 XCTest +@testable import SwiftDocC +import SymbolKit +import SwiftDocCTestUtilities + +class SnippetResolverTests: XCTestCase { + + let optionalPathPrefixes = [ + // The module name as the first component + "/ModuleName/Snippets/", + "ModuleName/Snippets/", + + // The catalog name as the first component + "/Something/Snippets/", + "Something/Snippets/", + + // Snippets repeated as the first component + "/Snippets/Snippets/", + "Snippets/Snippets/", + + // Only the "Snippets" prefix + "/Snippets/", + "Snippets/", + + // No prefix + "/", + "", + ] + + func testRenderingSnippetsWithOptionalPathPrefixes() async throws { + for pathPrefix in optionalPathPrefixes { + let (problems, _, snippetRenderBlocks) = try await makeSnippetContext( + snippets: [ + makeSnippet( + pathComponents: ["Snippets", "First"], + explanation: """ + Some _formatted_ **content** that provides context to the snippet. + """, + code: """ + // Some code comment + print("Hello, world!") + """, + slices: ["comment": 0..<1] + ), + makeSnippet( + pathComponents: ["Snippets", "Path", "To", "Second"], + explanation: nil, + code: """ + print("1 + 2 = \\(1+2)") + """ + ) + ], + rootContent: """ + @Snippet(path: \(pathPrefix)First) + + @Snippet(path: \(pathPrefix)Path/To/Second) + + @Snippet(path: \(pathPrefix)First, slice: comment) + """ + ) + + // These links should all resolve, regardless of optional prefix + XCTAssertTrue(problems.isEmpty, "Unexpected problems for path prefix '\(pathPrefix)': \(problems.map(\.diagnostic.summary))") + + // Because the snippet links resolved, their content should render on the page. + + // The explanation for the first snippet + if case .paragraph(let paragraph) = snippetRenderBlocks.first { + XCTAssertEqual(paragraph.inlineContent, [ + .text("Some "), + .emphasis(inlineContent: [.text("formatted")]), + .text(" "), + .strong(inlineContent: [.text("content")]), + .text(" that provides context to the snippet."), + ]) + } else { + XCTFail("Missing expected rendered explanation.") + } + + // The first snippet code + if case .codeListing(let codeListing) = snippetRenderBlocks.dropFirst().first { + XCTAssertEqual(codeListing.syntax, "swift") + XCTAssertEqual(codeListing.code, [ + #"// Some code comment"#, + #"print("Hello, world!")"#, + ]) + } else { + XCTFail("Missing expected rendered code block.") + } + + // The second snippet (without an explanation) + if case .codeListing(let codeListing) = snippetRenderBlocks.dropFirst(2).first { + XCTAssertEqual(codeListing.syntax, "swift") + XCTAssertEqual(codeListing.code, [ + #"print("1 + 2 = \(1+2)")"# + ]) + } else { + XCTFail("Missing expected rendered code block.") + } + + // The third snippet is a slice, so it doesn't display its explanation + if case .codeListing(let codeListing) = snippetRenderBlocks.dropFirst(3).first { + XCTAssertEqual(codeListing.syntax, "swift") + XCTAssertEqual(codeListing.code, [ + #"// Some code comment"#, + ]) + } else { + XCTFail("Missing expected rendered code block.") + } + + XCTAssertNil(snippetRenderBlocks.dropFirst(4).first, "There's no more content after the snippets") + } + } + + func testWarningsAboutMisspelledSnippetPathsAndMisspelledSlice() async throws { + for pathPrefix in optionalPathPrefixes.prefix(1) { + let (problems, logOutput, snippetRenderBlocks) = try await makeSnippetContext( + snippets: [ + makeSnippet( + pathComponents: ["Snippets", "First"], + explanation: """ + Some _formatted_ **content** that provides context to the snippet. + """, + code: """ + // Some code comment + print("Hello, world!") + """, + slices: [ + "comment": 0..<1, + "print": 1..<2, + ] + ), + ], + rootContent: """ + @Snippet(path: \(pathPrefix)Frst) + + @Snippet(path: \(pathPrefix)First, slice: commt) + """ + ) + + // The first snippet has a misspelled path and the second has a misspelled slice + XCTAssertEqual(problems.map(\.diagnostic.summary), [ + "Snippet named 'Frst' couldn't be found", + "Slice named 'commt' doesn't exist in snippet 'First'", + ]) + + // Verify that the suggested solutions correct the issues. + let rootMarkupContent = """ + # Heading + + Abstract + + ## Subheading + + @Snippet(path: \(pathPrefix)Frst) + + @Snippet(path: \(pathPrefix)First, slice: commt) + """ + do { + let snippetPathProblem = try XCTUnwrap(problems.first) + let solution = try XCTUnwrap(snippetPathProblem.possibleSolutions.first) + let modifiedLines = try solution.applyTo(rootMarkupContent).components(separatedBy: "\n") + XCTAssertEqual(modifiedLines[6], "@Snippet(path: \(pathPrefix)First)") + } + do { + let snippetSliceProblem = try XCTUnwrap(problems.last) + let solution = try XCTUnwrap(snippetSliceProblem.possibleSolutions.first) + let modifiedLines = try solution.applyTo(rootMarkupContent).components(separatedBy: "\n") + XCTAssertEqual(modifiedLines[8], "@Snippet(path: \(pathPrefix)First, slice: comment)") + } + + let prefixLength = pathPrefix.count + XCTAssertEqual(logOutput, """ + \u{001B}[1;33mwarning: Snippet named 'Frst' couldn't be found\u{001B}[0;0m + --> ModuleName.md:7:\(16 + prefixLength)-7:\(20 + prefixLength) + 5 | ## Overview + 6 | + 7 + @Snippet(path: \(pathPrefix)\u{001B}[1;32mFrst\u{001B}[0;0m) + | \(String(repeating: " ", count: prefixLength)) ╰─\u{001B}[1;39msuggestion: Replace 'Frst' with 'First'\u{001B}[0;0m + 8 | + 9 | @Snippet(path: \(pathPrefix)First, slice: commt) + + \u{001B}[1;33mwarning: Slice named 'commt' doesn't exist in snippet 'First'\u{001B}[0;0m + --> ModuleName.md:9:\(30 + prefixLength)-9:\(35 + prefixLength) + 7 | @Snippet(path: \(pathPrefix)Frst) + 8 | + 9 + @Snippet(path: \(pathPrefix)First, slice: \u{001B}[1;32mcommt\u{001B}[0;0m) + | \(String(repeating: " ", count: prefixLength)) ╰─\u{001B}[1;39msuggestion: Replace 'commt' with 'comment'\u{001B}[0;0m + + """) + + // Because the snippet links failed to resolve, their content shouldn't render on the page. + XCTAssertTrue(snippetRenderBlocks.isEmpty, "There's no more content after the snippets") + } + } + + private func makeSnippetContext( + snippets: [SymbolGraph.Symbol], + rootContent: String, + file: StaticString = #filePath, + line: UInt = #line + ) async throws -> ([Problem], logOutput: String, some Collection) { + let catalog = Folder(name: "Something.docc", content: [ + JSONFile(name: "something-snippets.symbols.json", content: makeSymbolGraph(moduleName: "Snippets", symbols: snippets)), + // Include a "real" module that's separate from the snippet symbol graph. + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName")), + + TextFile(name: "ModuleName.md", utf8Content: """ + # ``ModuleName`` + + Always include an abstract here before the custom markup + + ## Overview + + \(rootContent) + """) + ]) + // We make the "Overview" heading explicit above so that the rendered page will always have a `primaryContentSections`. + // This makes it easier for the test to then + + let logStore = LogHandle.LogStorage() + let (_, context) = try await loadBundle(catalog: catalog, logOutput: LogHandle.memory(logStore)) + + XCTAssertEqual(context.knownIdentifiers.count, 1, "The snippets don't have their own identifiers", file: file, line: line) + + let reference = try XCTUnwrap(context.soleRootModuleReference, file: file, line: line) + let moduleNode = try context.entity(with: reference) + let renderNode = DocumentationNodeConverter(bundle: context.bundle, context: context).convert(moduleNode) + + let renderBlocks = try XCTUnwrap(renderNode.primaryContentSections.first as? ContentRenderSection, file: file, line: line).content + + if case .heading(let heading) = renderBlocks.first { + XCTAssertEqual(heading.level, 2, file: file, line: line) + XCTAssertEqual(heading.text, "Overview", file: file, line: line) + } else { + XCTFail("The rendered page is missing the 'Overview' heading. Something unexpected is happening with the page content.", file: file, line: line) + } + + return (context.problems.sorted(by: \.diagnostic.range!.lowerBound.line), logStore.text, renderBlocks.dropFirst()) + } + + private func makeSnippet( + pathComponents: [String], + explanation: String?, + code: String, + slices: [String: Range] = [:] + ) -> SymbolGraph.Symbol { + makeSymbol( + id: "$snippet__module-name.\(pathComponents.map { $0.lowercased() }.joined(separator: "."))", + kind: .snippet, + pathComponents: pathComponents, + docComment: explanation, + otherMixins: [ + SymbolGraph.Symbol.Snippet( + language: SourceLanguage.swift.id, + lines: code.components(separatedBy: "\n"), + slices: slices + ) + ] + ) + } +} diff --git a/Tests/SwiftDocCTests/Semantics/Reference/TabNavigatorTests.swift b/Tests/SwiftDocCTests/Semantics/Reference/TabNavigatorTests.swift index 835610146d..cef67f3caf 100644 --- a/Tests/SwiftDocCTests/Semantics/Reference/TabNavigatorTests.swift +++ b/Tests/SwiftDocCTests/Semantics/Reference/TabNavigatorTests.swift @@ -160,15 +160,11 @@ class TabNavigatorTests: XCTestCase { XCTAssertNotNil(tabNavigator) - // UnresolvedTopicReference warning expected since the reference to the snippet "Snippets/Snippets/MySnippet" - // should fail to resolve here and then nothing would be added to the content. - XCTAssertEqual( - problems, - ["23: warning – org.swift.docc.unresolvedTopicReference"] - ) + // One warning is expected. This empty context has no snippets so the "Snippets/Snippets/MySnippet" path should fail to resolve. + XCTAssertEqual(problems, [ + "23: warning – org.swift.docc.unresolvedSnippetPath" + ]) - - XCTAssertEqual(renderBlockContent.count, 1) XCTAssertEqual( renderBlockContent.first, @@ -202,6 +198,8 @@ class TabNavigatorTests: XCTestCase { "Hey there.", .small(RenderBlockContent.Small(inlineContent: [.text("Hey but small.")])), + + // Because the the "Snippets/Snippets/MySnippet" snippet failed to resolve, we're not including any snippet content here. ] ), ] diff --git a/Tests/SwiftDocCTests/Semantics/SnippetTests.swift b/Tests/SwiftDocCTests/Semantics/SnippetTests.swift index 25e4bde699..5eda29ec26 100644 --- a/Tests/SwiftDocCTests/Semantics/SnippetTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SnippetTests.swift @@ -15,8 +15,8 @@ import XCTest import Markdown class SnippetTests: XCTestCase { - func testNoPath() async throws { - let (bundle, _) = try await testBundleAndContext(named: "Snippets") + func testWarningAboutMissingPathPath() async throws { + let (bundle, _) = try await testBundleAndContext() let source = """ @Snippet() """ @@ -29,8 +29,8 @@ class SnippetTests: XCTestCase { XCTAssertEqual("org.swift.docc.HasArgument.path", problems[0].diagnostic.identifier) } - func testHasInnerContent() async throws { - let (bundle, _) = try await testBundleAndContext(named: "Snippets") + func testWarningAboutInnerContent() async throws { + let (bundle, _) = try await testBundleAndContext() let source = """ @Snippet(path: "path/to/snippet") { This content shouldn't be here. @@ -45,8 +45,8 @@ class SnippetTests: XCTestCase { XCTAssertEqual("org.swift.docc.Snippet.NoInnerContentAllowed", problems[0].diagnostic.identifier) } - func testLinkResolves() async throws { - let (bundle, _) = try await testBundleAndContext(named: "Snippets") + func testParsesPath() async throws { + let (bundle, _) = try await testBundleAndContext() let source = """ @Snippet(path: "Test/Snippets/MySnippet") """ @@ -58,23 +58,50 @@ class SnippetTests: XCTestCase { XCTAssertNotNil(snippet) XCTAssertTrue(problems.isEmpty) } + func testLinkResolvesWithoutOptionalPrefix() async throws { + let (bundle, context) = try await testBundleAndContext(named: "Snippets") + + for snippetPath in [ + "/Test/Snippets/MySnippet", + "Test/Snippets/MySnippet", + "Snippets/MySnippet", + "MySnippet", + ] { + let source = """ + @Snippet(path: "\(snippetPath)") + """ + let document = Document(parsing: source, options: .parseBlockDirectives) + var resolver = MarkupReferenceResolver(context: context, bundle: bundle, rootReference: try XCTUnwrap(context.soleRootModuleReference)) + _ = resolver.visit(document) + XCTAssertTrue(resolver.problems.isEmpty, "Unexpected problems: \(resolver.problems.map(\.diagnostic.summary))") + } + } - func testUnresolvedSnippetPathDiagnostic() async throws { + func testWarningAboutUnresolvedSnippetPath() async throws { let (bundle, context) = try await testBundleAndContext(named: "Snippets") - let source = """ - @Snippet(path: "Test/Snippets/DoesntExist") - """ - let document = Document(parsing: source, options: .parseBlockDirectives) - var resolver = MarkupReferenceResolver(context: context, bundle: bundle, rootReference: context.rootModules[0]) - _ = resolver.visit(document) - XCTAssertEqual(1, resolver.problems.count) - resolver.problems.first.map { - XCTAssertEqual("org.swift.docc.unresolvedTopicReference", $0.diagnostic.identifier) + + for snippetPath in [ + "/Test/Snippets/DoesNotExist", + "Test/Snippets/DoesNotExist", + "Snippets/DoesNotExist", + "DoesNotExist", + ] { + let source = """ + @Snippet(path: "\(snippetPath)") + """ + let document = Document(parsing: source, options: .parseBlockDirectives) + var resolver = MarkupReferenceResolver(context: context, bundle: bundle, rootReference: try XCTUnwrap(context.soleRootModuleReference)) + _ = resolver.visit(document) + XCTAssertEqual(1, resolver.problems.count) + let problem = try XCTUnwrap(resolver.problems.first) + XCTAssertEqual(problem.diagnostic.identifier, "org.swift.docc.unresolvedSnippetPath") + XCTAssertEqual(problem.diagnostic.summary, "Snippet named 'DoesNotExist' couldn't be found") + XCTAssertEqual(problem.possibleSolutions.count, 0) } } - func testSliceResolves() async throws { - let (bundle, _) = try await testBundleAndContext(named: "Snippets") + func testParsesSlice() async throws { + let (bundle, _) = try await testBundleAndContext() let source = """ @Snippet(path: "Test/Snippets/MySnippet", slice: "foo") """ diff --git a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift index c0157c6a26..063c8c997d 100644 --- a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift @@ -1611,8 +1611,7 @@ class SymbolTests: XCTestCase { ) } - let diagnosticEngine = DiagnosticEngine(filterLevel: diagnosticEngineFilterLevel) - let (_, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: catalogContent), diagnosticEngine: diagnosticEngine) + let (_, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: catalogContent), diagnosticFilterLevel: diagnosticEngineFilterLevel) let node = try XCTUnwrap(context.documentationCache[methodUSR], file: file, line: line) diff --git a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift index 754a05314c..4207fc3d1c 100644 --- a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift +++ b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift @@ -43,21 +43,30 @@ extension XCTestCase { /// - Parameters: /// - catalog: The directory structure of the documentation catalog /// - otherFileSystemDirectories: Any other directories in the test file system. - /// - diagnosticEngine: The diagnostic engine for the created context. + /// - diagnosticFilterLevel: The minimum severity for diagnostics to emit. + /// - logOutput: An output stream to capture log output from creating the context. /// - configuration: Configuration for the created context. /// - Returns: The loaded documentation bundle and context for the given catalog input. func loadBundle( catalog: Folder, otherFileSystemDirectories: [Folder] = [], - diagnosticEngine: DiagnosticEngine = .init(), + diagnosticFilterLevel: DiagnosticSeverity = .warning, + logOutput: some TextOutputStream = LogHandle.none, configuration: DocumentationContext.Configuration = .init() ) async throws -> (DocumentationBundle, DocumentationContext) { let fileSystem = try TestFileSystem(folders: [catalog] + otherFileSystemDirectories) + let catalogURL = URL(fileURLWithPath: "/\(catalog.name)") + + let diagnosticEngine = DiagnosticEngine(filterLevel: diagnosticFilterLevel) + diagnosticEngine.add(DiagnosticConsoleWriter(logOutput, formattingOptions: [], baseURL: catalogURL, highlight: true, dataProvider: fileSystem)) let (bundle, dataProvider) = try DocumentationContext.InputsProvider(fileManager: fileSystem) - .inputsAndDataProvider(startingPoint: URL(fileURLWithPath: "/\(catalog.name)"), options: .init()) + .inputsAndDataProvider(startingPoint: catalogURL, options: .init()) let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) + + diagnosticEngine.flush() // Write to the logOutput + return (bundle, context) } From 3dc6dd0657c916ae3b9031681b3ed7a1da1f9b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 29 Sep 2025 17:33:53 +0200 Subject: [PATCH 43/90] Fix test that became flaky after recent snippets change (#1308) Depending on which symbol was _first_ in the dictionary of symbols, the entire symbol graph was categorized as either a snippet symbol graph or a regular symbol graph. However, real symbol graph files don't mix snippets and framework symbols like that. This fixes the test to don't mix snippets with real symbols. It also stops adding module nodes inside the symbol graph, because that also doesn't match real symbol graph files. --- .../DocumentationContext/DocumentationContextTests.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index 0243657e85..acc5b9d502 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -4935,7 +4935,11 @@ let expected = """ func testContextDoesNotRecognizeNonOverloadableSymbolKinds() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let nonOverloadableKindIDs = SymbolGraph.Symbol.KindIdentifier.allCases.filter { !$0.isOverloadableKind } + let nonOverloadableKindIDs = SymbolGraph.Symbol.KindIdentifier.allCases.filter { + !$0.isOverloadableKind && + !$0.isSnippetKind && // avoid mixing snippets with "real" symbols + $0 != .module // avoid creating multiple modules + } // Generate a 4 symbols with the same name for every non overloadable symbol kind let symbols: [SymbolGraph.Symbol] = nonOverloadableKindIDs.flatMap { [ makeSymbol(id: "first-\($0.identifier)-id", kind: $0, pathComponents: ["SymbolName"]), From cf6dde16aead6ae15d0bd448acd96ec353d09d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 29 Sep 2025 18:13:28 +0200 Subject: [PATCH 44/90] Adopt typed throws to provide callers with more information about possible raised errors (#1295) * Use typed throws in path hierarchy to avoid handing impossible cases * Use typed throws in some methods that only raise a single error * Preserve typed error information in helpers --- .../Infrastructure/DocumentationContext.swift | 2 +- .../ExternalPathHierarchyResolver.swift | 4 +- .../Link Resolution/LinkResolver.swift | 4 +- .../Link Resolution/PathHierarchy+Find.swift | 75 ++++++++++--------- .../PathHierarchyBasedLinkResolver.swift | 2 +- .../ExtendedTypeFormatTransformation.swift | 4 +- .../SwiftDocC/Semantics/Symbol/Symbol.swift | 4 +- .../RangeReplaceableCollection+Group.swift | 4 +- .../Sequence+FirstMap.swift | 4 +- .../SwiftDocC/Utility/Synchronization.swift | 6 +- 10 files changed, 53 insertions(+), 56 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 5ab0f7b6a5..b4200ae07d 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -2707,7 +2707,7 @@ public class DocumentationContext { - Returns: A ``DocumentationNode`` with the given identifier. - Throws: ``ContextError/notFound(_:)`` if a documentation node with the given identifier was not found. */ - public func entity(with reference: ResolvedTopicReference) throws -> DocumentationNode { + public func entity(with reference: ResolvedTopicReference) throws(ContextError) -> DocumentationNode { if let cached = documentationCache[reference] { return cached } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift index 55bb272813..7efffba881 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift @@ -44,12 +44,10 @@ final class ExternalPathHierarchyResolver { } return .success(foundReference) - } catch let error as PathHierarchy.Error { + } catch { return .failure(unresolvedReference, error.makeTopicReferenceResolutionErrorInfo() { collidingNode in self.fullName(of: collidingNode) // If the link was ambiguous, determine the full name of each colliding node to be presented in the link diagnostic. }) - } catch { - fatalError("Only PathHierarchy.Error errors are raised from the symbol link resolution code above.") } } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift index 27cd2d2a1a..de1a3bbbf0 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift @@ -67,7 +67,7 @@ public class LinkResolver { do { return try localResolver.resolve(unresolvedReference, in: parent, fromSymbolLink: isCurrentlyResolvingSymbolLink) - } catch let error as PathHierarchy.Error { + } catch { // Check if there's a known external resolver for this module. if case .moduleNotFound(_, let remainingPathComponents, _) = error, let resolver = externalResolvers[remainingPathComponents.first!.full] { let result = resolver.resolve(unresolvedReference, fromSymbolLink: isCurrentlyResolvingSymbolLink) @@ -86,8 +86,6 @@ public class LinkResolver { } else { return .failure(unresolvedReference, error.makeTopicReferenceResolutionErrorInfo() { localResolver.fullName(of: $0, in: context) }) } - } catch { - fatalError("Only SymbolPathTree.Error errors are raised from the symbol link resolution code above.") } } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift index 0ead6bbe16..a4ea9ad67c 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -20,11 +20,11 @@ extension PathHierarchy { /// - onlyFindSymbols: Whether or not only symbol matches should be found. /// - Returns: Returns the unique identifier for the found match or raises an error if no match can be found. /// - Throws: Raises a ``PathHierarchy/Error`` if no match can be found. - func find(path rawPath: String, parent: ResolvedIdentifier? = nil, onlyFindSymbols: Bool) throws -> ResolvedIdentifier { + func find(path rawPath: String, parent: ResolvedIdentifier? = nil, onlyFindSymbols: Bool) throws(Error) -> ResolvedIdentifier { return try findNode(path: rawPath, parentID: parent, onlyFindSymbols: onlyFindSymbols).identifier } - private func findNode(path rawPath: String, parentID: ResolvedIdentifier?, onlyFindSymbols: Bool) throws -> Node { + private func findNode(path rawPath: String, parentID: ResolvedIdentifier?, onlyFindSymbols: Bool) throws(Error) -> Node { // The search for a documentation element can be though of as 3 steps: // - First, parse the path into structured path components. // - Second, find nodes that match the beginning of the path as starting points for the search @@ -79,7 +79,7 @@ extension PathHierarchy { } // A function to avoid repeating the - func searchForNodeInModules() throws -> Node { + func searchForNodeInModules() throws(Error) -> Node { // Note: This captures `parentID`, `remaining`, and `rawPathForError`. if let moduleMatch = modules.first(where: { $0.matches(firstComponent) }) { return try searchForNode(descendingFrom: moduleMatch, pathComponents: remaining.dropFirst(), onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPath) @@ -87,7 +87,7 @@ extension PathHierarchy { if modules.count == 1 { do { return try searchForNode(descendingFrom: modules.first!, pathComponents: remaining, onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPath) - } catch let error as PathHierarchy.Error { + } catch { switch error { case .notFound: // Ignore this error and raise an error about not finding the module instead. @@ -129,7 +129,7 @@ extension PathHierarchy { } // A recursive function to traverse up the path hierarchy searching for the matching node - func searchForNodeUpTheHierarchy(from startingPoint: Node?, path: ArraySlice) throws -> Node { + func searchForNodeUpTheHierarchy(from startingPoint: Node?, path: ArraySlice) throws(Error) -> Node { guard let possibleStartingPoint = startingPoint else { // If the search has reached the top of the hierarchy, check the modules as a base case to break the recursion. do { @@ -147,7 +147,7 @@ extension PathHierarchy { let firstComponent = path.first! // Keep track of the inner most error and raise that if no node is found. - var innerMostError: (any Swift.Error)? + var innerMostError: Error? // If the starting point's children match this component, descend the path hierarchy from there. if possibleStartingPoint.anyChildMatches(firstComponent) { @@ -211,7 +211,7 @@ extension PathHierarchy { pathComponents: ArraySlice, onlyFindSymbols: Bool, rawPathForError: String - ) throws -> Node { + ) throws(Error) -> Node { // All code paths through this function wants to perform extra verification on the return value before returning it to the caller. // To accomplish that, the core implementation happens in `_innerImplementation`, which is called once, right below its definition. @@ -220,7 +220,7 @@ extension PathHierarchy { pathComponents: ArraySlice, onlyFindSymbols: Bool, rawPathForError: String - ) throws -> Node { + ) throws(Error) -> Node { var node = startingPoint var remaining = pathComponents[...] @@ -234,21 +234,13 @@ extension PathHierarchy { while true { let (children, pathComponent) = try findChildContainer(node: &node, remaining: remaining, rawPathForError: rawPathForError) + let child: PathHierarchy.Node? do { - guard let child = try children.find(pathComponent.disambiguation) else { - // The search has ended with a node that doesn't have a child matching the next path component. - throw makePartialResultError(node: node, remaining: remaining, rawPathForError: rawPathForError) - } - node = child - remaining = remaining.dropFirst() - if remaining.isEmpty { - // If all path components are consumed, then the match is found. - return child - } - } catch DisambiguationContainer.Error.lookupCollision(let collisions) { - func handleWrappedCollision() throws -> Node { - let match = try handleCollision(node: node, remaining: remaining, collisions: collisions, onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPathForError) - return match + child = try children.find(pathComponent.disambiguation) + } catch { + let collisions = error.collisions + func handleWrappedCollision() throws(Error) -> Node { + try handleCollision(node: node, remaining: remaining, collisions: collisions, onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPathForError) } // When there's a collision, use the remaining path components to try and narrow down the possible collisions. @@ -314,6 +306,17 @@ extension PathHierarchy { // Couldn't resolve the collision by look ahead. return try handleWrappedCollision() } + + guard let child else { + // The search has ended with a node that doesn't have a child matching the next path component. + throw makePartialResultError(node: node, remaining: remaining, rawPathForError: rawPathForError) + } + node = child + remaining = remaining.dropFirst() + if remaining.isEmpty { + // If all path components are consumed, then the match is found. + return child + } } } @@ -336,7 +339,7 @@ extension PathHierarchy { collisions: [(node: PathHierarchy.Node, disambiguation: String)], onlyFindSymbols: Bool, rawPathForError: String - ) throws -> Node { + ) throws(Error) -> Node { if let favoredMatch = collisions.singleMatch({ !$0.node.isDisfavoredInLinkCollisions }) { return favoredMatch.node } @@ -421,7 +424,7 @@ extension PathHierarchy { node: inout Node, remaining: ArraySlice, rawPathForError: String - ) throws -> (DisambiguationContainer, PathComponent) { + ) throws(Error) -> (DisambiguationContainer, PathComponent) { var pathComponent = remaining.first! if let match = node.children[pathComponent.full] { // The path component parsing may treat dash separated words as disambiguation information. @@ -439,12 +442,10 @@ extension PathHierarchy { // MARK: Disambiguation Container extension PathHierarchy.DisambiguationContainer { - /// Errors finding values in the disambiguation tree - enum Error: Swift.Error { - /// Multiple matches found. - /// - /// Includes a list of values paired with their missing disambiguation suffixes. - case lookupCollision([(node: PathHierarchy.Node, disambiguation: String)]) + /// Multiple matches found. + struct LookupCollisionError: Swift.Error { + /// A list of values paired with their missing disambiguation suffixes. + let collisions: [(node: PathHierarchy.Node, disambiguation: String)] } /// Attempts to find the only element in the disambiguation container without using any disambiguation information. @@ -464,7 +465,7 @@ extension PathHierarchy.DisambiguationContainer { /// - No match is found; indicated by a `nil` return value. /// - Exactly one match is found; indicated by a non-nil return value. /// - More than one match is found; indicated by a raised error listing the matches and their missing disambiguation. - func find(_ disambiguation: PathHierarchy.PathComponent.Disambiguation?) throws -> PathHierarchy.Node? { + func find(_ disambiguation: PathHierarchy.PathComponent.Disambiguation?) throws(LookupCollisionError) -> PathHierarchy.Node? { if disambiguation == nil, let match = singleMatch() { return match } @@ -478,13 +479,13 @@ extension PathHierarchy.DisambiguationContainer { let matches = storage.filter({ $0.kind == kind }) guard matches.count <= 1 else { // Suggest not only hash disambiguation, but also type signature disambiguation. - throw Error.lookupCollision(Self.disambiguatedValues(for: matches).map { ($0.value, $0.disambiguation.makeSuffix()) }) + throw LookupCollisionError(collisions: Self.disambiguatedValues(for: matches).map { ($0.value, $0.disambiguation.makeSuffix()) }) } return matches.first?.node case (nil, let hash?): let matches = storage.filter({ $0.hash == hash }) guard matches.count <= 1 else { - throw Error.lookupCollision(matches.map { ($0.node, "-" + $0.kind!) }) // An element wouldn't match if it didn't have kind disambiguation. + throw LookupCollisionError(collisions: matches.map { ($0.node, "-" + $0.kind!) }) // An element wouldn't match if it didn't have kind disambiguation. } return matches.first?.node case (nil, nil): @@ -498,13 +499,13 @@ extension PathHierarchy.DisambiguationContainer { case (let parameterTypes?, nil): let matches = storage.filter({ typesMatch(provided: parameterTypes, actual: $0.parameterTypes) }) guard matches.count <= 1 else { - throw Error.lookupCollision(matches.map { ($0.node, "->" + formattedTypes($0.parameterTypes)!) }) // An element wouldn't match if it didn't have parameter type disambiguation. + throw LookupCollisionError(collisions: matches.map { ($0.node, "->" + formattedTypes($0.parameterTypes)!) }) // An element wouldn't match if it didn't have parameter type disambiguation. } return matches.first?.node case (nil, let returnTypes?): let matches = storage.filter({ typesMatch(provided: returnTypes, actual: $0.returnTypes) }) guard matches.count <= 1 else { - throw Error.lookupCollision(matches.map { ($0.node, "-" + formattedTypes($0.returnTypes)!) }) // An element wouldn't match if it didn't have return type disambiguation. + throw LookupCollisionError(collisions: matches.map { ($0.node, "-" + formattedTypes($0.returnTypes)!) }) // An element wouldn't match if it didn't have return type disambiguation. } return matches.first?.node case (nil, nil): @@ -515,7 +516,7 @@ extension PathHierarchy.DisambiguationContainer { } // Disambiguate by a mix of kinds and USRs - throw Error.lookupCollision(self.disambiguatedValues().map { ($0.value, $0.disambiguation.makeSuffix()) }) + throw LookupCollisionError(collisions: self.disambiguatedValues().map { ($0.value, $0.disambiguation.makeSuffix()) }) } } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift index 4dc85a2968..597b6af777 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift @@ -228,7 +228,7 @@ final class PathHierarchyBasedLinkResolver { /// - isCurrentlyResolvingSymbolLink: Whether or not the documentation link is a symbol link. /// - context: The documentation context to resolve the link in. /// - Returns: The result of resolving the reference. - func resolve(_ unresolvedReference: UnresolvedTopicReference, in parent: ResolvedTopicReference, fromSymbolLink isCurrentlyResolvingSymbolLink: Bool) throws -> TopicReferenceResolutionResult { + func resolve(_ unresolvedReference: UnresolvedTopicReference, in parent: ResolvedTopicReference, fromSymbolLink isCurrentlyResolvingSymbolLink: Bool) throws(PathHierarchy.Error) -> TopicReferenceResolutionResult { let parentID = resolvedReferenceMap[parent] let found = try pathHierarchy.find(path: Self.path(for: unresolvedReference), parent: parentID, onlyFindSymbols: isCurrentlyResolvingSymbolLink) guard let foundReference = resolvedReferenceMap[found] else { diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/ExtendedTypeFormatTransformation.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/ExtendedTypeFormatTransformation.swift index e9d2e42e73..680d8e666b 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/ExtendedTypeFormatTransformation.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/ExtendedTypeFormatTransformation.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 @@ -569,7 +569,7 @@ extension ExtendedTypeFormatTransformation { // MARK: Apply Mappings to SymbolGraph private extension SymbolGraph { - mutating func apply(compactMap include: (SymbolGraph.Symbol) throws -> SymbolGraph.Symbol?) rethrows { + mutating func apply(compactMap include: (SymbolGraph.Symbol) throws(Error) -> SymbolGraph.Symbol?) throws(Error) { for (key, symbol) in self.symbols { self.symbols.removeValue(forKey: key) if let newSymbol = try include(symbol) { diff --git a/Sources/SwiftDocC/Semantics/Symbol/Symbol.swift b/Sources/SwiftDocC/Semantics/Symbol/Symbol.swift index a62aac5ba7..0b6150d359 100644 --- a/Sources/SwiftDocC/Semantics/Symbol/Symbol.swift +++ b/Sources/SwiftDocC/Semantics/Symbol/Symbol.swift @@ -461,14 +461,14 @@ extension Symbol { /// When building multi-platform documentation symbols might have more than one declaration /// depending on variances in their implementation across platforms (e.g. use `NSPoint` vs `CGPoint` parameter in a method). /// This method finds matching symbols between graphs and merges their declarations in case there are differences. - func mergeDeclaration(mergingDeclaration: SymbolGraph.Symbol.DeclarationFragments, identifier: String, symbolAvailability: SymbolGraph.Symbol.Availability?, alternateSymbols: SymbolGraph.Symbol.AlternateSymbols?, selector: UnifiedSymbolGraph.Selector) throws { + func mergeDeclaration(mergingDeclaration: SymbolGraph.Symbol.DeclarationFragments, identifier: String, symbolAvailability: SymbolGraph.Symbol.Availability?, alternateSymbols: SymbolGraph.Symbol.AlternateSymbols?, selector: UnifiedSymbolGraph.Selector) throws(DocumentationContext.ContextError) { let trait = DocumentationDataVariantsTrait(for: selector) let platformName = selector.platform func merge( _ mergingValue: Value, into variants: inout DocumentationDataVariants<[[PlatformName?] : Value]> - ) throws { + ) throws(DocumentationContext.ContextError) { guard let platformName else { variants[trait]?[[nil]] = mergingValue return diff --git a/Sources/SwiftDocC/Utility/FoundationExtensions/RangeReplaceableCollection+Group.swift b/Sources/SwiftDocC/Utility/FoundationExtensions/RangeReplaceableCollection+Group.swift index 8aaf30ec91..ba2384bf38 100644 --- a/Sources/SwiftDocC/Utility/FoundationExtensions/RangeReplaceableCollection+Group.swift +++ b/Sources/SwiftDocC/Utility/FoundationExtensions/RangeReplaceableCollection+Group.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 @@ -15,7 +15,7 @@ extension RangeReplaceableCollection { /// /// - Parameter belongsInGroupWithPrevious: A check whether the given element belongs in the same group as the previous element /// - Returns: An array of subsequences of elements that belong together. - func group(asLongAs belongsInGroupWithPrevious: (_ previous: Element, _ current: Element) throws -> Bool) rethrows -> [SubSequence] { + func group(asLongAs belongsInGroupWithPrevious: (_ previous: Element, _ current: Element) throws(Error) -> Bool) throws(Error) -> [SubSequence] { var result = [SubSequence]() let indexPairs = zip(indices, indices.dropFirst()) diff --git a/Sources/SwiftDocC/Utility/FoundationExtensions/Sequence+FirstMap.swift b/Sources/SwiftDocC/Utility/FoundationExtensions/Sequence+FirstMap.swift index c51d041d8b..e9502eec90 100644 --- a/Sources/SwiftDocC/Utility/FoundationExtensions/Sequence+FirstMap.swift +++ b/Sources/SwiftDocC/Utility/FoundationExtensions/Sequence+FirstMap.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 @@ -30,7 +30,7 @@ extension Sequence { /// - Parameter predicate: A mapping closure that accepts an element of this sequence as its parameter and returns a transformed value or `nil`. /// - Throws: Any error that's raised by the mapping closure. /// - Returns: The first mapped, non-nil value, or `nil` if the mapping closure returned `nil` for every value in the sequence. - func mapFirst(where predicate: (Element) throws -> T?) rethrows -> T? { + func mapFirst(where predicate: (Element) throws(Error) -> Result?) throws(Error) -> Result? { for element in self { if let result = try predicate(element) { return result diff --git a/Sources/SwiftDocC/Utility/Synchronization.swift b/Sources/SwiftDocC/Utility/Synchronization.swift index 7e243bbe9b..80395c176f 100644 --- a/Sources/SwiftDocC/Utility/Synchronization.swift +++ b/Sources/SwiftDocC/Utility/Synchronization.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 @@ -77,7 +77,7 @@ public class Synchronized { /// - Parameter block: A throwing block of work that optionally returns a value. /// - Returns: Returns the returned value of `block`, if any. @discardableResult - public func sync(_ block: (inout Value) throws -> Result) rethrows -> Result { + public func sync(_ block: (inout Value) throws(Error) -> Result) throws(Error) -> Result { #if os(macOS) || os(iOS) os_unfair_lock_lock(lock) defer { os_unfair_lock_unlock(lock) } @@ -116,7 +116,7 @@ extension Lock { } @discardableResult - package func sync(_ block: () throws -> Result) rethrows -> Result { + package func sync(_ block: () throws(Error) -> Result) throws(Error) -> Result { #if os(macOS) || os(iOS) os_unfair_lock_lock(lock) defer { os_unfair_lock_unlock(lock) } From bb2cdc7c4b627e3a7ce1304570157290732d9667 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 1 Oct 2025 10:47:33 +0200 Subject: [PATCH 45/90] Fix error when merging a single archive (#1218) * Fix error when merging a single archive * Prevent creation of empty directories in merge --- .../MergeAction+SynthesizedLandingPage.swift | 5 +- .../Action/Actions/Merge/MergeAction.swift | 2 + .../MergeActionTests.swift | 60 ++++++++++++++++--- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift index 0b76352f92..0c33da016c 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift @@ -36,7 +36,10 @@ extension MergeAction { func readRootNodeRenderReferencesIn(dataDirectory: URL) throws -> RootRenderReferences { func inner(url: URL) throws -> [RootRenderReferences.Information] { - try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: []) + // Path might not exist (e.g. tutorials for a reference-only archive) + guard let contents = try? fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: []) + else { return [] } + return try contents .compactMap { guard $0.pathExtension == "json" else { return nil diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift index cc94bc1b80..d68fa32af6 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift @@ -65,6 +65,8 @@ struct MergeAction: AsyncAction { let fromDirectory = archive.appendingPathComponent(directoryToCopy, isDirectory: true) let toDirectory = targetURL.appendingPathComponent(directoryToCopy, isDirectory: true) + guard fileManager.directoryExists(atPath: fromDirectory.path) else { continue } + // Ensure that the destination directory exist in case the first archive didn't have that kind of pages. // This is necessary when merging a reference-only archive with a tutorial-only archive. try? fileManager.createDirectory(at: toDirectory, withIntermediateDirectories: false, attributes: nil) diff --git a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift index 0853d154e4..74f058a2b5 100644 --- a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift @@ -659,7 +659,50 @@ class MergeActionTests: XCTestCase { "doc://org.swift.test/documentation/second.json", ]) } - + + func testSingleReferenceOnlyArchiveMerging() async throws { + let fileSystem = try TestFileSystem( + folders: [ + Folder(name: "Output.doccarchive", content: []), + Self.makeArchive( + name: "First", + documentationPages: [ + "First", + "First/SomeClass", + "First/SomeClass/someProperty", + "First/SomeClass/someFunction(:_)", + ], + tutorialPages: [] + ), + ] + ) + + let logStorage = LogHandle.LogStorage() + let action = MergeAction( + archives: [ + URL(fileURLWithPath: "/First.doccarchive"), + ], + landingPageInfo: testLandingPageInfo, + outputURL: URL(fileURLWithPath: "/Output.doccarchive"), + fileManager: fileSystem + ) + + _ = try await action.perform(logHandle: .memory(logStorage)) + XCTAssertEqual(logStorage.text, "", "The action didn't log anything") + + let synthesizedRootNode = try fileSystem.renderNode(atPath: "/Output.doccarchive/data/documentation.json") + XCTAssertEqual(synthesizedRootNode.metadata.title, "Test Landing Page Name") + XCTAssertEqual(synthesizedRootNode.metadata.roleHeading, "Test Landing Page Kind") + XCTAssertEqual(synthesizedRootNode.topicSectionsStyle, .detailedGrid) + XCTAssertEqual(synthesizedRootNode.topicSections.flatMap { [$0.title].compactMap({ $0 }) + $0.identifiers }, [ + // No title + "doc://org.swift.test/documentation/first.json", + ]) + XCTAssertEqual(synthesizedRootNode.references.keys.sorted(), [ + "doc://org.swift.test/documentation/first.json", + ]) + } + func testErrorWhenArchivesContainOverlappingData() async throws { let fileSystem = try TestFileSystem( folders: [ @@ -953,14 +996,13 @@ class MergeActionTests: XCTestCase { Output.doccarchive/ ├─ data/ │ ├─ documentation.json - │ ├─ documentation/ - │ │ ├─ first.json - │ │ ├─ first/ - │ │ │ ╰─ article.json - │ │ ├─ second.json - │ │ ╰─ second/ - │ │ ╰─ article.json - │ ╰─ tutorials/ + │ ╰─ documentation/ + │ ├─ first.json + │ ├─ first/ + │ │ ╰─ article.json + │ ├─ second.json + │ ╰─ second/ + │ ╰─ article.json ├─ downloads/ │ ├─ First/ │ ╰─ Second/ From 7f0d3f60a746cb301d89a972ded8dceec115d14b Mon Sep 17 00:00:00 2001 From: Jesse Haigh Date: Fri, 3 Oct 2025 10:57:49 -0600 Subject: [PATCH 46/90] Add copy-to-clipboard support for code blocks (with 'nocopy' annotation to disable) (#1273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds a `copyToClipboard` property on `CodeListing` * Introduces the `enable-experimental-code-block-annotations` feature flag * Supports disabling copy with the `nocopy` annotation, parsed from the code block’s language line * Updates the OpenAPI spec to include `copyToClipboard` Co-authored-by: Jesse Haigh --- .../Checkers/InvalidCodeBlockOption.swift | 62 ++++++++++ .../Infrastructure/DocumentationContext.swift | 2 +- .../Workspace/FeatureFlags+Info.swift | 13 +++ .../Content/RenderBlockContent.swift | 25 +++- .../Rendering/RenderContentCompiler.swift | 36 +++++- .../Resources/RenderNode.spec.json | 3 + Sources/SwiftDocC/Utility/FeatureFlags.swift | 5 +- .../ConvertAction+CommandInitialization.swift | 2 +- .../ArgumentParsing/Subcommands/Convert.swift | 16 ++- .../InvalidCodeBlockOptionTests.swift | 108 ++++++++++++++++++ .../Model/RenderContentMetadataTests.swift | 2 +- .../Model/RenderNodeSerializationTests.swift | 10 +- .../RenderContentCompilerTests.swift | 93 +++++++++++++++ .../Utility/ListItemExtractorTests.swift | 4 +- features.json | 3 + 15 files changed, 367 insertions(+), 17 deletions(-) create mode 100644 Sources/SwiftDocC/Checker/Checkers/InvalidCodeBlockOption.swift create mode 100644 Tests/SwiftDocCTests/Checker/Checkers/InvalidCodeBlockOptionTests.swift diff --git a/Sources/SwiftDocC/Checker/Checkers/InvalidCodeBlockOption.swift b/Sources/SwiftDocC/Checker/Checkers/InvalidCodeBlockOption.swift new file mode 100644 index 0000000000..ca8bd2cb5b --- /dev/null +++ b/Sources/SwiftDocC/Checker/Checkers/InvalidCodeBlockOption.swift @@ -0,0 +1,62 @@ +/* + 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 +*/ + +internal import Foundation +internal import Markdown + +/** + Code blocks can have a `nocopy` option after the \`\`\`, in the language line. +`nocopy` can be immediately after the \`\`\` or after a specified language and a comma (`,`). + */ +internal struct InvalidCodeBlockOption: Checker { + var problems = [Problem]() + + /// Parsing options for code blocks + private let knownOptions = RenderBlockContent.CodeListing.knownOptions + + private var sourceFile: URL? + + /// Creates a new checker that detects documents with multiple titles. + /// + /// - Parameter sourceFile: The URL to the documentation file that the checker checks. + init(sourceFile: URL?) { + self.sourceFile = sourceFile + } + + mutating func visitCodeBlock(_ codeBlock: CodeBlock) { + let info = codeBlock.language?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + guard !info.isEmpty else { return } + + let tokens = info + .split(separator: ",") + .map { $0.trimmingCharacters(in: .whitespaces) } + .filter { !$0.isEmpty } + + guard !tokens.isEmpty else { return } + + for token in tokens { + // if the token is an exact match, we don't need to do anything + guard !knownOptions.contains(token) else { continue } + + let matches = NearMiss.bestMatches(for: knownOptions, against: token) + + if !matches.isEmpty { + let diagnostic = Diagnostic(source: sourceFile, severity: .warning, range: codeBlock.range, identifier: "org.swift.docc.InvalidCodeBlockOption", summary: "Unknown option \(token.singleQuoted) in code block.") + let possibleSolutions = matches.map { candidate in + Solution( + summary: "Replace \(token.singleQuoted) with \(candidate.singleQuoted).", + replacements: [] + ) + } + problems.append(Problem(diagnostic: diagnostic, possibleSolutions: possibleSolutions)) + } + } + } +} diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index b4200ae07d..87fd41cd1c 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -273,6 +273,7 @@ public class DocumentationContext { MissingAbstract(sourceFile: source).any(), NonOverviewHeadingChecker(sourceFile: source).any(), SeeAlsoInTopicsHeadingChecker(sourceFile: source).any(), + InvalidCodeBlockOption(sourceFile: source).any(), ]) checker.visit(document) diagnosticEngine.emit(checker.problems) @@ -2457,7 +2458,6 @@ public class DocumentationContext { } } } - /// A closure type getting the information about a reference in a context and returns any possible problems with it. public typealias ReferenceCheck = (DocumentationContext, ResolvedTopicReference) -> [Problem] diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/FeatureFlags+Info.swift b/Sources/SwiftDocC/Infrastructure/Workspace/FeatureFlags+Info.swift index dd62465ddd..4f95feb9b2 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/FeatureFlags+Info.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/FeatureFlags+Info.swift @@ -37,11 +37,20 @@ extension DocumentationBundle.Info { self.unknownFeatureFlags = [] } + /// This feature flag corresponds to ``FeatureFlags/isExperimentalCodeBlockAnnotationsEnabled``. + public var experimentalCodeBlockAnnotations: Bool? + + public init(experimentalCodeBlockAnnotations: Bool? = nil) { + self.experimentalCodeBlockAnnotations = experimentalCodeBlockAnnotations + self.unknownFeatureFlags = [] + } + /// A list of decoded feature flag keys that didn't match a known feature flag. public let unknownFeatureFlags: [String] enum CodingKeys: String, CodingKey, CaseIterable { case experimentalOverloadedSymbolPresentation = "ExperimentalOverloadedSymbolPresentation" + case experimentalCodeBlockAnnotations = "ExperimentalCodeBlockAnnotations" } struct AnyCodingKeys: CodingKey { @@ -66,6 +75,9 @@ extension DocumentationBundle.Info { switch codingKey { case .experimentalOverloadedSymbolPresentation: self.experimentalOverloadedSymbolPresentation = try values.decode(Bool.self, forKey: flagName) + + case .experimentalCodeBlockAnnotations: + self.experimentalCodeBlockAnnotations = try values.decode(Bool.self, forKey: flagName) } } else { unknownFeatureFlags.append(flagName.stringValue) @@ -79,6 +91,7 @@ extension DocumentationBundle.Info { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(experimentalOverloadedSymbolPresentation, forKey: .experimentalOverloadedSymbolPresentation) + try container.encode(experimentalCodeBlockAnnotations, forKey: .experimentalCodeBlockAnnotations) } } } diff --git a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift index 7c4695f2a6..85ac031825 100644 --- a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift +++ b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift @@ -124,12 +124,26 @@ public enum RenderBlockContent: Equatable { public var code: [String] /// Additional metadata for this code block. public var metadata: RenderContentMetadata? + public var copyToClipboard: Bool + + public enum OptionName: String, CaseIterable { + case nocopy + + init?(caseInsensitive raw: some StringProtocol) { + self.init(rawValue: raw.lowercased()) + } + } + + public static var knownOptions: Set { + Set(OptionName.allCases.map(\.rawValue)) + } /// Make a new `CodeListing` with the given data. - public init(syntax: String?, code: [String], metadata: RenderContentMetadata?) { + public init(syntax: String?, code: [String], metadata: RenderContentMetadata?, copyToClipboard: Bool = FeatureFlags.current.isExperimentalCodeBlockAnnotationsEnabled) { self.syntax = syntax self.code = code self.metadata = metadata + self.copyToClipboard = copyToClipboard } } @@ -697,7 +711,7 @@ extension RenderBlockContent.Table: Codable { extension RenderBlockContent: Codable { private enum CodingKeys: CodingKey { case type - case inlineContent, content, caption, style, name, syntax, code, level, text, items, media, runtimePreview, anchor, summary, example, metadata, start + case inlineContent, content, caption, style, name, syntax, code, level, text, items, media, runtimePreview, anchor, summary, example, metadata, start, copyToClipboard case request, response case header, rows case numberOfColumns, columns @@ -719,11 +733,13 @@ extension RenderBlockContent: Codable { } self = try .aside(.init(style: style, content: container.decode([RenderBlockContent].self, forKey: .content))) case .codeListing: + let copy = FeatureFlags.current.isExperimentalCodeBlockAnnotationsEnabled self = try .codeListing(.init( syntax: container.decodeIfPresent(String.self, forKey: .syntax), code: container.decode([String].self, forKey: .code), - metadata: container.decodeIfPresent(RenderContentMetadata.self, forKey: .metadata) - )) + metadata: container.decodeIfPresent(RenderContentMetadata.self, forKey: .metadata), + copyToClipboard: container.decodeIfPresent(Bool.self, forKey: .copyToClipboard) ?? copy + )) case .heading: self = try .heading(.init(level: container.decode(Int.self, forKey: .level), text: container.decode(String.self, forKey: .text), anchor: container.decodeIfPresent(String.self, forKey: .anchor))) case .orderedList: @@ -826,6 +842,7 @@ extension RenderBlockContent: Codable { try container.encode(l.syntax, forKey: .syntax) try container.encode(l.code, forKey: .code) try container.encodeIfPresent(l.metadata, forKey: .metadata) + try container.encode(l.copyToClipboard, forKey: .copyToClipboard) case .heading(let h): try container.encode(h.level, forKey: .level) try container.encode(h.text, forKey: .text) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift b/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift index 58fabccede..e79e6fdfcd 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift @@ -47,7 +47,41 @@ struct RenderContentCompiler: MarkupVisitor { mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> [any RenderContent] { // Default to the bundle's code listing syntax if one is not explicitly declared in the code block. - return [RenderBlockContent.codeListing(.init(syntax: codeBlock.language ?? bundle.info.defaultCodeListingLanguage, code: codeBlock.code.splitByNewlines, metadata: nil))] + + if FeatureFlags.current.isExperimentalCodeBlockAnnotationsEnabled { + + func parseLanguageString(_ input: String?) -> (lang: String? , tokens: [RenderBlockContent.CodeListing.OptionName]) { + guard let input else { return (lang: nil, tokens: []) } + let parts = input + .split(separator: ",") + .map { $0.trimmingCharacters(in: .whitespaces) } + var lang: String? = nil + var options: [RenderBlockContent.CodeListing.OptionName] = [] + + for part in parts { + if let opt = RenderBlockContent.CodeListing.OptionName(caseInsensitive: part) { + options.append(opt) + } else if lang == nil { + lang = String(part) + } + } + return (lang, options) + } + + let options = parseLanguageString(codeBlock.language) + + let listing = RenderBlockContent.CodeListing( + syntax: options.lang ?? bundle.info.defaultCodeListingLanguage, + code: codeBlock.code.splitByNewlines, + metadata: nil, + copyToClipboard: !options.tokens.contains(.nocopy) + ) + + return [RenderBlockContent.codeListing(listing)] + + } else { + return [RenderBlockContent.codeListing(.init(syntax: codeBlock.language ?? bundle.info.defaultCodeListingLanguage, code: codeBlock.code.splitByNewlines, metadata: nil, copyToClipboard: false))] + } } mutating func visitHeading(_ heading: Heading) -> [any RenderContent] { diff --git a/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json b/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json index 4ced315007..628a2d98f6 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json +++ b/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json @@ -805,6 +805,9 @@ }, "metadata": { "$ref": "#/components/schemas/RenderContentMetadata" + }, + "copyToClipboard": { + "type": "boolean" } } }, diff --git a/Sources/SwiftDocC/Utility/FeatureFlags.swift b/Sources/SwiftDocC/Utility/FeatureFlags.swift index def7e642d1..538e55781f 100644 --- a/Sources/SwiftDocC/Utility/FeatureFlags.swift +++ b/Sources/SwiftDocC/Utility/FeatureFlags.swift @@ -13,7 +13,10 @@ public struct FeatureFlags: Codable { /// The current feature flags that Swift-DocC uses to conditionally enable /// (usually experimental) behavior in Swift-DocC. public static var current = FeatureFlags() - + + /// Whether or not experimental annotation of code blocks is enabled. + public var isExperimentalCodeBlockAnnotationsEnabled = false + /// Whether or not experimental support for device frames on images and video is enabled. public var isExperimentalDeviceFrameSupportEnabled = false diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift index e8c8a31b45..c23c8c55bb 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift @@ -19,7 +19,7 @@ extension ConvertAction { public init(fromConvertCommand convert: Docc.Convert, withFallbackTemplate fallbackTemplateURL: URL? = nil) throws { var standardError = LogHandle.standardError let outOfProcessResolver: OutOfProcessReferenceResolver? - + FeatureFlags.current.isExperimentalCodeBlockAnnotationsEnabled = convert.featureFlags.enableExperimentalCodeBlockAnnotations FeatureFlags.current.isExperimentalDeviceFrameSupportEnabled = convert.enableExperimentalDeviceFrameSupport FeatureFlags.current.isExperimentalLinkHierarchySerializationEnabled = convert.enableExperimentalLinkHierarchySerialization FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled = convert.enableExperimentalOverloadedSymbolPresentation diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift index 95b0d098c5..aa427bdb1c 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift @@ -475,7 +475,13 @@ extension Docc { struct FeatureFlagOptions: ParsableArguments { @Flag(help: "Allows for custom templates, like `header.html`.") var experimentalEnableCustomTemplates = false - + + @Flag( + name: .customLong("enable-experimental-code-block-annotations"), + help: "Support annotations for code blocks." + ) + var enableExperimentalCodeBlockAnnotations = false + @Flag(help: .hidden) var enableExperimentalDeviceFrameSupport = false @@ -558,6 +564,14 @@ extension Docc { } + /// A user-provided value that is true if the user enables experimental support for code block annotation. + /// + /// Defaults to false. + public var enableExperimentalCodeBlocAnnotations: Bool { + get { featureFlags.enableExperimentalCodeBlockAnnotations } + set { featureFlags.enableExperimentalCodeBlockAnnotations = newValue} + } + /// A user-provided value that is true if the user enables experimental support for device frames. /// /// Defaults to false. diff --git a/Tests/SwiftDocCTests/Checker/Checkers/InvalidCodeBlockOptionTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/InvalidCodeBlockOptionTests.swift new file mode 100644 index 0000000000..2def720f13 --- /dev/null +++ b/Tests/SwiftDocCTests/Checker/Checkers/InvalidCodeBlockOptionTests.swift @@ -0,0 +1,108 @@ +/* + 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 XCTest +@testable import SwiftDocC +import Markdown + +class InvalidCodeBlockOptionTests: XCTestCase { + + func testNoOptions() { + let markupSource = """ +``` +let a = 1 +``` +""" + let document = Document(parsing: markupSource, options: []) + var checker = InvalidCodeBlockOption(sourceFile: nil) + checker.visit(document) + XCTAssertTrue(checker.problems.isEmpty) + XCTAssertEqual(RenderBlockContent.CodeListing.knownOptions, ["nocopy"]) + } + + func testOption() { + let markupSource = """ +```nocopy +let a = 1 +``` +""" + let document = Document(parsing: markupSource, options: []) + var checker = InvalidCodeBlockOption(sourceFile: nil) + checker.visit(document) + XCTAssertTrue(checker.problems.isEmpty) + } + + func testMultipleOptionTypos() { + let markupSource = """ +```nocoy +let b = 2 +``` + +```nocoy +let c = 3 +``` +""" + let document = Document(parsing: markupSource, options: []) + var checker = InvalidCodeBlockOption(sourceFile: URL(fileURLWithPath: #file)) + checker.visit(document) + XCTAssertEqual(2, checker.problems.count) + + for problem in checker.problems { + XCTAssertEqual("org.swift.docc.InvalidCodeBlockOption", problem.diagnostic.identifier) + XCTAssertEqual(problem.diagnostic.summary, "Unknown option 'nocoy' in code block.") + XCTAssertEqual(problem.possibleSolutions.map(\.summary), ["Replace 'nocoy' with 'nocopy'."]) + } + } + + func testOptionDifferentTypos() throws { + let markupSource = """ +```swift, nocpy +let d = 4 +``` + +```unknown, nocpoy +let e = 5 +``` + +```nocopy +let f = 6 +``` + +```ncopy +let g = 7 +``` +""" + let document = Document(parsing: markupSource, options: []) + var checker = InvalidCodeBlockOption(sourceFile: URL(fileURLWithPath: #file)) + checker.visit(document) + + XCTAssertEqual(3, checker.problems.count) + + let summaries = checker.problems.map { $0.diagnostic.summary } + XCTAssertEqual(summaries, [ + "Unknown option 'nocpy' in code block.", + "Unknown option 'nocpoy' in code block.", + "Unknown option 'ncopy' in code block.", + ]) + + for problem in checker.problems { + XCTAssertEqual( + "org.swift.docc.InvalidCodeBlockOption", + problem.diagnostic.identifier + ) + + XCTAssertEqual(problem.possibleSolutions.count, 1) + let solution = try XCTUnwrap(problem.possibleSolutions.first) + XCTAssert(solution.summary.hasSuffix("with 'nocopy'.")) + + } + } +} + diff --git a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift index 23da7c1241..3d7d0c44ea 100644 --- a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift @@ -54,7 +54,7 @@ class RenderContentMetadataTests: XCTestCase { RenderInlineContent.text("Content"), ]) - let code = RenderBlockContent.codeListing(.init(syntax: nil, code: [], metadata: metadata)) + let code = RenderBlockContent.codeListing(.init(syntax: nil, code: [], metadata: metadata, copyToClipboard: false)) let data = try JSONEncoder().encode(code) let roundtrip = try JSONDecoder().decode(RenderBlockContent.self, from: data) diff --git a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift index d539531a31..0f669cd1cd 100644 --- a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift @@ -44,7 +44,7 @@ class RenderNodeSerializationTests: XCTestCase { .strong(inlineContent: [.text("Project > Run")]), .text(" menu item, or the following code:"), ])), - .codeListing(.init(syntax: "swift", code: ["xcrun xcodebuild -h", "xcrun xcodebuild build -configuration Debug"], metadata: nil)), + .codeListing(.init(syntax: "swift", code: ["xcrun xcodebuild -h", "xcrun xcodebuild build -configuration Debug"], metadata: nil, copyToClipboard: false)), ])) ] @@ -71,16 +71,16 @@ class RenderNodeSerializationTests: XCTestCase { let assessment1 = TutorialAssessmentsRenderSection.Assessment(title: [.paragraph(.init(inlineContent: [.text("Lorem ipsum dolor sit amet?")]))], content: nil, choices: [ - .init(content: [.codeListing(.init(syntax: "swift", code: ["override func viewDidLoad() {", "super.viewDidLoad()", "}"], metadata: nil))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "That's right!"), - .init(content: [.codeListing(.init(syntax: "swift", code: ["sceneView.delegate = self"], metadata: nil))], isCorrect: false, justification: [.paragraph(.init(inlineContent: [.text("It's incorrect because...")]))], reaction: "Not quite."), + .init(content: [.codeListing(.init(syntax: "swift", code: ["override func viewDidLoad() {", "super.viewDidLoad()", "}"], metadata: nil, copyToClipboard: false))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "That's right!"), + .init(content: [.codeListing(.init(syntax: "swift", code: ["sceneView.delegate = self"], metadata: nil, copyToClipboard: false))], isCorrect: false, justification: [.paragraph(.init(inlineContent: [.text("It's incorrect because...")]))], reaction: "Not quite."), .init(content: [.paragraph(.init(inlineContent: [.text("None of the above.")]))], isCorrect: false, justification: [.paragraph(.init(inlineContent: [.text("It's incorrect because...")]))], reaction: nil), ]) let assessment2 = TutorialAssessmentsRenderSection.Assessment(title: [.paragraph(.init(inlineContent: [.text("Duis aute irure dolor in reprehenderit?")]))], content: [.paragraph(.init(inlineContent: [.text("What is the airspeed velocity of an unladen swallow?")]))], choices: [ - .init(content: [.codeListing(.init(syntax: "swift", code: ["super.viewWillAppear()"], metadata: nil))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "Correct."), - .init(content: [.codeListing(.init(syntax: "swift", code: ["sceneView.delegate = self"], metadata: nil))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "Yep."), + .init(content: [.codeListing(.init(syntax: "swift", code: ["super.viewWillAppear()"], metadata: nil, copyToClipboard: false))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "Correct."), + .init(content: [.codeListing(.init(syntax: "swift", code: ["sceneView.delegate = self"], metadata: nil, copyToClipboard: false))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "Yep."), .init(content: [.paragraph(.init(inlineContent: [.text("None of the above.")]))], isCorrect: false, justification: [.paragraph(.init(inlineContent: [.text("It's incorrect because...")]))], reaction: "Close!"), ]) diff --git a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift index 8a23b1324a..ef3604fd9c 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift @@ -223,4 +223,97 @@ class RenderContentCompilerTests: XCTestCase { XCTAssertEqual(documentThematicBreak, thematicBreak) } } + + func testCopyToClipboard() async throws { + enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) + + let (bundle, context) = try await testBundleAndContext() + var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + + let source = #""" + ```swift + let x = 1 + ``` + """# + let document = Document(parsing: source) + + let result = document.children.flatMap { compiler.visit($0) } + + let renderCodeBlock = try XCTUnwrap(result[0] as? RenderBlockContent) + guard case let .codeListing(codeListing) = renderCodeBlock else { + XCTFail("Expected RenderBlockContent.codeListing") + return + } + + XCTAssertEqual(codeListing.copyToClipboard, true) + } + + func testNoCopyToClipboard() async throws { + enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) + + let (bundle, context) = try await testBundleAndContext() + var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + + let source = #""" + ```swift, nocopy + let x = 1 + ``` + """# + let document = Document(parsing: source) + + let result = document.children.flatMap { compiler.visit($0) } + + let renderCodeBlock = try XCTUnwrap(result[0] as? RenderBlockContent) + guard case let .codeListing(codeListing) = renderCodeBlock else { + XCTFail("Expected RenderBlockContent.codeListing") + return + } + + XCTAssertEqual(codeListing.copyToClipboard, false) + } + + func testCopyToClipboardNoFeatureFlag() async throws { + let (bundle, context) = try await testBundleAndContext() + var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + + let source = #""" + ```swift + let x = 1 + ``` + """# + let document = Document(parsing: source) + + let result = document.children.flatMap { compiler.visit($0) } + + let renderCodeBlock = try XCTUnwrap(result[0] as? RenderBlockContent) + guard case let .codeListing(codeListing) = renderCodeBlock else { + XCTFail("Expected RenderBlockContent.codeListing") + return + } + + XCTAssertEqual(codeListing.copyToClipboard, false) + } + + func testNoCopyToClipboardNoFeatureFlag() async throws { + let (bundle, context) = try await testBundleAndContext() + var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + + let source = #""" + ```swift, nocopy + let x = 1 + ``` + """# + let document = Document(parsing: source) + + let result = document.children.flatMap { compiler.visit($0) } + + let renderCodeBlock = try XCTUnwrap(result[0] as? RenderBlockContent) + guard case let .codeListing(codeListing) = renderCodeBlock else { + XCTFail("Expected RenderBlockContent.codeListing") + return + } + + XCTAssertEqual(codeListing.syntax, "swift, nocopy") + XCTAssertEqual(codeListing.copyToClipboard, false) + } } diff --git a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift index 59fe23e12b..cdf61e5f3d 100644 --- a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift +++ b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift @@ -514,8 +514,8 @@ class ListItemExtractorTests: XCTestCase { // ``` // Inner code block // ``` - .codeListing(.init(syntax: nil, code: ["Inner code block"], metadata: nil)), - + .codeListing(.init(syntax: nil, code: ["Inner code block"], metadata: nil, copyToClipboard: false)), + // > Warning: Inner aside, with ``ThirdNotFoundSymbol`` link .aside(.init(style: .init(asideKind: .warning), content: [ .paragraph(.init(inlineContent: [ diff --git a/features.json b/features.json index a14d784fe4..31e8b0e7d3 100644 --- a/features.json +++ b/features.json @@ -1,5 +1,8 @@ { "features": [ + { + "name": "code-blocks" + }, { "name": "diagnostics-file" }, From 82cc6c8138c9e440ac9fca86877dec01435c38b6 Mon Sep 17 00:00:00 2001 From: Prajwal Nadig Date: Mon, 6 Oct 2025 16:54:07 +0100 Subject: [PATCH 47/90] Only curate external non-symbols in different lang (#1269) * Minor fixes to grammar and documentation * Only curate external non-symbols in different lang PR #757 enabled DocC to include manually curated non-symbol nodes in the topics section, irrespective of the node language. This allows curating articles across documentation in different languages, even though the article's language is considered to be "Swift". However, the logic was too permissive, and allowed curating non-symbol nodes within the same bundle but across different languages. This is incorrect, since the target page should be annotated with the `SupportedLanguage` directive to enable curating it in a different language. A bespoke mechanism to curate an internal reference correctly is not necessary. This patch tightens the logic to only allow curating external references in a different language than the source page language, and removes the existing test for the undesired behaviour of including local references to cross-language symbols. The changes in PR #757 were not propagated to the navigator, resulting in the references being present in the topics section but not in the sidebar. This patch also introduces the curation logic in the navigator to maintain parity with the topics section. The navigator tests for external render nodes have been modified accordingly to reflect this change. rdar://155522179 --- .../Navigator/AvailabilityIndex+Ext.swift | 2 +- .../Indexing/Navigator/NavigatorIndex.swift | 53 ++++++++---- .../Infrastructure/DocumentationContext.swift | 5 ++ .../Rendering/RenderNodeTranslator.swift | 11 ++- .../Indexing/ExternalRenderNodeTests.swift | 23 ++++-- .../SemaToRenderNodeMultiLanguageTests.swift | 82 ------------------- 6 files changed, 69 insertions(+), 107 deletions(-) diff --git a/Sources/SwiftDocC/Indexing/Navigator/AvailabilityIndex+Ext.swift b/Sources/SwiftDocC/Indexing/Navigator/AvailabilityIndex+Ext.swift index 1309a484c7..1cf55b1e8a 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/AvailabilityIndex+Ext.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/AvailabilityIndex+Ext.swift @@ -83,7 +83,7 @@ public struct InterfaceLanguage: Hashable, CustomStringConvertible, Codable, Equ /// > ``from(string:)`` function. public let id: String - /// A mask to use to identify the interface language.. + /// A mask to use to identify the interface language. public let mask: ID diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift index 4024bf3f93..a712b13520 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift @@ -219,12 +219,12 @@ public class NavigatorIndex { } /** - Initialize an `NavigatorIndex` from a given path with an empty tree. + Initialize a `NavigatorIndex` from a given path with an empty tree. - Parameter url: The URL pointing to the path from which the index should be read. - Parameter bundleIdentifier: The name of the bundle the index is referring to. - - Note: Don't exposed this initializer as it's used **ONLY** for building an index. + - Note: Don't expose this initializer as it's used **ONLY** for building an index. */ fileprivate init(withEmptyTree url: URL, bundleIdentifier: String) throws { self.url = url @@ -364,14 +364,14 @@ public class NavigatorIndex { Read a tree on disk from a given path. The read is atomically performed, which means it reads all the content of the file from the disk and process the tree from loaded data. The queue is used to load the data for a given timeout period, after that, the queue is used to schedule another read after a given delay. - This approach ensures that the used queue doesn't stall while loading the content from the disk keeping the used queue responsive. + This approach ensures that the used queue doesn't stall while loading the content from the disk keeping the used queue responsive. - Parameters: - - timeout: The amount of time we can load a batch of items from data, once the timeout time pass, + - timeout: The duration for which we can load a batch of items from data. Once the timeout duration passes, the reading process will reschedule asynchronously using the given queue. - - delay: The delay to wait before schedule the next read. Default: 0.01 seconds. + - delay: The duration to wait for before scheduling the next read. Default: 0.01 seconds. - queue: The queue to use. - - broadcast: The callback to update get updates of the current process. + - broadcast: The callback to receive updates on the status of the current process. - Note: Do not access the navigator tree root node or the map from identifier to node from a different thread than the one the queue is using while the read is performed, this may cause data inconsistencies. For that please use the broadcast callback that notifies which items have been loaded. @@ -455,6 +455,17 @@ extension NavigatorIndex { self.fragment = fragment self.languageIdentifier = languageIdentifier } + + /// Compare an identifier with another one, ignoring the identifier language. + /// + /// Used when curating cross-language references in multi-language frameworks. + /// + /// - Parameter other: The other identifier to compare with. + func isEquivalentIgnoringLanguage(to other: Identifier) -> Bool { + return self.bundleIdentifier == other.bundleIdentifier && + self.path == other.path && + self.fragment == other.fragment + } } /** @@ -884,7 +895,7 @@ extension NavigatorIndex { /// - emitJSONRepresentation: Whether or not a JSON representation of the index should /// be written to disk. /// - /// Defaults to `false`. + /// Defaults to `true`. /// /// - emitLMDBRepresentation: Whether or not an LMDB representation of the index should /// written to disk. @@ -917,7 +928,7 @@ extension NavigatorIndex { let (nodeID, parent) = nodesMultiCurated[index] let placeholders = identifierToChildren[nodeID]! for reference in placeholders { - if let child = identifierToNode[reference] { + if let child = identifierToNode[reference] ?? externalNonSymbolNode(for: reference) { parent.add(child: child) pendingUncuratedReferences.remove(reference) if !multiCurated.keys.contains(reference) && reference.fragment == nil { @@ -938,7 +949,7 @@ extension NavigatorIndex { for (nodeIdentifier, placeholders) in identifierToChildren { for reference in placeholders { let parent = identifierToNode[nodeIdentifier]! - if let child = identifierToNode[reference] { + if let child = identifierToNode[reference] ?? externalNonSymbolNode(for: reference) { let needsCopy = multiCurated[reference] != nil parent.add(child: (needsCopy) ? child.copy() : child) pendingUncuratedReferences.remove(reference) @@ -969,14 +980,11 @@ extension NavigatorIndex { // page types as symbol nodes on the assumption that an unknown page type is a // symbol kind added in a future version of Swift-DocC. // Finally, don't add external references to the root; if they are not referenced within the navigation tree, they should be dropped altogether. - if let node = identifierToNode[nodeID], PageType(rawValue: node.item.pageType)?.isSymbolKind == false , !node.item.isExternal { + if let node = identifierToNode[nodeID], PageType(rawValue: node.item.pageType)?.isSymbolKind == false, !node.item.isExternal { // If an uncurated page has been curated in another language, don't add it to the top-level. if curatedReferences.contains(where: { curatedNodeID in - // Compare all the identifier's properties for equality, except for its language. - curatedNodeID.bundleIdentifier == nodeID.bundleIdentifier - && curatedNodeID.path == nodeID.path - && curatedNodeID.fragment == nodeID.fragment + curatedNodeID.isEquivalentIgnoringLanguage(to: nodeID) }) { continue } @@ -1256,7 +1264,22 @@ extension NavigatorIndex { problem = Problem(diagnostic: diagnostic, possibleSolutions: []) problems.append(problem) } - + + /// Find an external node for the reference that is not of a symbol kind. The source language + /// of the reference is ignored during this lookup since the reference assumes the target node + /// to be of the same language as the page that it is curated in. This may or may not be true + /// since non-symbol kinds (articles, tutorials, etc.) are not tied to a language. + // This is a workaround for https://github.com/swiftlang/swift-docc/issues/240. + // FIXME: This should ideally be solved by making the article language-agnostic rather + // than accomodating the "Swift" language and special-casing for non-symbol nodes. + func externalNonSymbolNode(for reference: NavigatorIndex.Identifier) -> NavigatorTree.Node? { + identifierToNode + .first { identifier, node in + identifier.isEquivalentIgnoringLanguage(to: reference) + && PageType.init(rawValue: node.item.pageType)?.isSymbolKind == false + && node.item.isExternal + }?.value + } /// Build the index using the render nodes files in the provided documentation archive. /// - Returns: A list containing all the errors encountered during indexing. diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 87fd41cd1c..03d7b834c4 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -2756,6 +2756,11 @@ public class DocumentationContext { ).isSymbol } + /// Returns whether the given reference resolves to an external entity. + func isExternal(reference: ResolvedTopicReference) -> Bool { + externalCache[reference] != nil + } + // MARK: - Relationship queries /// Fetch the child nodes of a documentation node with the given `reference`, optionally filtering to only children of the given `kind`. diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index aad67d6b85..00cd12c114 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -1054,10 +1054,13 @@ public struct RenderNodeTranslator: SemanticVisitor { return true } - guard context.isSymbol(reference: reference) else { - // If the reference corresponds to any kind except Symbol - // (e.g., Article, Tutorial, SampleCode...), allow the topic - // to appear independently of the source language it belongs to. + // If this is a reference to a non-symbol kind (article, tutorial, sample code, etc.), + // and is external to the bundle, then curate the topic irrespective of the source + // language of the page or reference, since non-symbol kinds are not tied to a language. + // This is a workaround for https://github.com/swiftlang/swift-docc/issues/240. + // FIXME: This should ideally be solved by making the article language-agnostic rather + // than accomodating the "Swift" language and special-casing for non-symbol nodes. + if !context.isSymbol(reference: reference) && context.isExternal(reference: reference) { return true } diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index d2c790caa0..270d5130a0 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -275,10 +275,10 @@ class ExternalRenderNodeTests: XCTestCase { // Verify that the curated external links are part of the index. let swiftExternalNodes = try XCTUnwrap(externalTopLevelNodes(for: .swift)) - XCTAssertEqual(swiftExternalNodes.count, 2) + XCTAssertEqual(swiftExternalNodes.count, 3) let objcExternalNodes = try XCTUnwrap(externalTopLevelNodes(for: .objectiveC)) - XCTAssertEqual(objcExternalNodes.count, 2) + XCTAssertEqual(objcExternalNodes.count, 3) let swiftArticleExternalNode = try XCTUnwrap(swiftExternalNodes.first(where: { $0.path == "/path/to/external/swiftarticle" })) let swiftSymbolExternalNode = try XCTUnwrap(swiftExternalNodes.first(where: { $0.path == "/path/to/external/swiftsymbol" })) @@ -300,6 +300,19 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(objcSymbolExternalNode.title, "- (void) ObjCSymbol") XCTAssertEqual(objcSymbolExternalNode.isBeta, false) XCTAssertEqual(objcSymbolExternalNode.type, "func") + + // External articles curated in the Topics section appear in all language variants. This is a workaround for https://github.com/swiftlang/swift-docc/issues/240. + // FIXME: This should ideally be solved by making the article language-agnostic rather than accomodating the "Swift" language and special-casing for non-symbols. + let swiftArticleInObjcTree = try XCTUnwrap(objcExternalNodes.first(where: { $0.path == "/path/to/external/swiftarticle" })) + let objcArticleInSwiftTree = try XCTUnwrap(swiftExternalNodes.first(where: { $0.path == "/path/to/external/objcarticle" })) + + XCTAssertEqual(swiftArticleInObjcTree.title, "SwiftArticle") + XCTAssertEqual(swiftArticleInObjcTree.isBeta, false) + XCTAssertEqual(swiftArticleInObjcTree.type, "article") + + XCTAssertEqual(objcArticleInSwiftTree.title, "ObjCArticle") + XCTAssertEqual(objcArticleInSwiftTree.isBeta, true) + XCTAssertEqual(objcArticleInSwiftTree.type, "article") } func testNavigatorWithExternalNodesWithNavigatorTitle() async throws { @@ -440,11 +453,11 @@ class ExternalRenderNodeTests: XCTestCase { let swiftExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.swift.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) let objcExternalNodes = (renderIndex.interfaceLanguages[SourceLanguage.objectiveC.id]?.first?.children?.filter(\.isExternal) ?? []).sorted(by: \.title) XCTAssertEqual(swiftExternalNodes.count, 1) - XCTAssertEqual(objcExternalNodes.count, 1) + XCTAssertEqual(objcExternalNodes.count, 2) XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle"]) - XCTAssertEqual(objcExternalNodes.map(\.title), ["- (void) ObjCSymbol"]) + XCTAssertEqual(objcExternalNodes.map(\.title), ["- (void) ObjCSymbol", "SwiftArticle"]) XCTAssertEqual(swiftExternalNodes.map(\.type), ["article"]) - XCTAssertEqual(objcExternalNodes.map(\.type), ["func"]) + XCTAssertEqual(objcExternalNodes.map(\.type), ["func", "article"]) } func testExternalRenderNodeVariantRepresentationWhenIsBeta() throws { diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift index e4acc7156a..861d1fd3c3 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift @@ -878,88 +878,6 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { defaultLanguage: .swift ) } - - func testArticlesAreIncludedInAllVariantsTopicsSection() async throws { - let outputConsumer = try await renderNodeConsumer( - for: "MixedLanguageFramework", - configureBundle: { bundleURL in - try """ - # ObjCArticle - - @Metadata { - @SupportedLanguage(objc) - } - - This article has Objective-C as the source language. - - ## Topics - """.write(to: bundleURL.appendingPathComponent("ObjCArticle.md"), atomically: true, encoding: .utf8) - try """ - # SwiftArticle - - @Metadata { - @SupportedLanguage(swift) - } - - This article has Swift as the source language. - """.write(to: bundleURL.appendingPathComponent("SwiftArticle.md"), atomically: true, encoding: .utf8) - try """ - # ``MixedLanguageFramework`` - - This symbol has a Swift and Objective-C variant. - - ## Topics - - - - - - - ``_MixedLanguageFrameworkVersionNumber`` - - ``SwiftOnlyStruct`` - - """.write(to: bundleURL.appendingPathComponent("MixedLanguageFramework.md"), atomically: true, encoding: .utf8) - } - ) - assertIsAvailableInLanguages( - try outputConsumer.renderNode( - withTitle: "ObjCArticle" - ), - languages: ["occ"], - defaultLanguage: .objectiveC - ) - assertIsAvailableInLanguages( - try outputConsumer.renderNode( - withTitle: "_MixedLanguageFrameworkVersionNumber" - ), - languages: ["occ"], - defaultLanguage: .objectiveC - ) - - let renderNode = try outputConsumer.renderNode(withIdentifier: "MixedLanguageFramework") - - // Topic identifiers in the Swift variant of the `MixedLanguageFramework` symbol - let swiftTopicIDs = renderNode.topicSections.flatMap(\.identifiers) - - let data = try renderNode.encodeToJSON() - let variantRenderNode = try RenderNodeVariantOverridesApplier() - .applyVariantOverrides(in: data, for: [.interfaceLanguage("occ")]) - let objCRenderNode = try RenderJSONDecoder.makeDecoder().decode(RenderNode.self, from: variantRenderNode) - // Topic identifiers in the ObjC variant of the `MixedLanguageFramework` symbol - let objCTopicIDs = objCRenderNode.topicSections.flatMap(\.identifiers) - - - // Verify that articles are included in the Topics section of both symbol - // variants regardless of their perceived language. - XCTAssertTrue(swiftTopicIDs.contains("doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/ObjCArticle")) - XCTAssertTrue(swiftTopicIDs.contains("doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/SwiftArticle")) - XCTAssertTrue(objCTopicIDs.contains("doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/SwiftArticle")) - XCTAssertTrue(objCTopicIDs.contains("doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/ObjCArticle")) - - // Verify that language specific symbols are dropped from the Topics section in the - // variants for languages where the symbol isn't available. - XCTAssertTrue(swiftTopicIDs.contains("doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/SwiftOnlyStruct")) - XCTAssertFalse(swiftTopicIDs.contains("doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/_MixedLanguageFrameworkVersionNumber")) - XCTAssertTrue(objCTopicIDs.contains("doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/_MixedLanguageFrameworkVersionNumber")) - XCTAssertFalse(objCTopicIDs.contains("doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/SwiftOnlyStruct")) - } func testAutomaticSeeAlsoSectionElementLimit() async throws { let (bundle, context) = try await loadBundle(catalog: From c1baea36d6239024a7141d6c42bc5216b744457c Mon Sep 17 00:00:00 2001 From: Jesse Haigh Date: Tue, 7 Oct 2025 09:15:47 -0600 Subject: [PATCH 48/90] Add experimental code block options: highlight, strikeout, wrap, showLineNumbers (#1287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update docs * copy by default * add feature flag 'enable-experimental-code-block-annotations' for copy-to-clipboard and other code block annotations * add more tests, remove docs, code cleanup * add code block options onto RenderBlockContent.CodeListing * remaining PR feedback * copy by default * add feature flag 'enable-experimental-code-block-annotations' for copy-to-clipboard and other code block annotations * WIP wrap and highlight * fix tests * WIP tests, WIP parsing for wrap and highlight * change parsing to handle values after = and arrays * add strikeout option * parse strikeout option, solution for language not as the first option on language line, tests * validate array values in code block options for highlight and strikeout * showLineNumbers option * remove trailing comma * test showLineNumbers * PR feedback * fix feature flag on new tests * remove optional return type * update JSON structure for extensibility * update RenderNode.spec to reflect using Range in LineAnnotation * update feature name * Update Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift Co-authored-by: David Rönnqvist * Update Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift Co-authored-by: David Rönnqvist * require LineAnnotation properties style and range * fix CodeListing initializers in Snippet * fix typo * add copy-to-clipboard button back to snippets when feature flag is present --------- Co-authored-by: Jesse Haigh Co-authored-by: David Rönnqvist --- .../Checkers/InvalidCodeBlockOption.swift | 67 ++- .../Content/RenderBlockContent.swift | 235 +++++++++- .../Rendering/RenderContentCompiler.swift | 28 +- .../Semantics/Snippets/Snippet.swift | 7 +- .../Resources/RenderNode.spec.json | 45 ++ .../ConvertAction+CommandInitialization.swift | 2 +- .../ArgumentParsing/Subcommands/Convert.swift | 2 +- .../formatting-your-documentation-content.md | 90 ++-- .../InvalidCodeBlockOptionTests.swift | 57 ++- .../Model/RenderContentMetadataTests.swift | 2 +- .../Model/RenderNodeSerializationTests.swift | 10 +- .../RenderContentCompilerTests.swift | 403 +++++++++++++++++- .../Utility/ListItemExtractorTests.swift | 2 +- features.json | 2 +- 14 files changed, 839 insertions(+), 113 deletions(-) diff --git a/Sources/SwiftDocC/Checker/Checkers/InvalidCodeBlockOption.swift b/Sources/SwiftDocC/Checker/Checkers/InvalidCodeBlockOption.swift index ca8bd2cb5b..e483fa76c7 100644 --- a/Sources/SwiftDocC/Checker/Checkers/InvalidCodeBlockOption.swift +++ b/Sources/SwiftDocC/Checker/Checkers/InvalidCodeBlockOption.swift @@ -19,7 +19,7 @@ internal struct InvalidCodeBlockOption: Checker { var problems = [Problem]() /// Parsing options for code blocks - private let knownOptions = RenderBlockContent.CodeListing.knownOptions + private let knownOptions = RenderBlockContent.CodeBlockOptions.knownOptions private var sourceFile: URL? @@ -31,32 +31,67 @@ internal struct InvalidCodeBlockOption: Checker { } mutating func visitCodeBlock(_ codeBlock: CodeBlock) { - let info = codeBlock.language?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - guard !info.isEmpty else { return } + let (lang, tokens) = RenderBlockContent.CodeBlockOptions.tokenizeLanguageString(codeBlock.language) - let tokens = info - .split(separator: ",") - .map { $0.trimmingCharacters(in: .whitespaces) } - .filter { !$0.isEmpty } + func matches(token: RenderBlockContent.CodeBlockOptions.OptionName, value: String?) { + guard token == .unknown, let value = value else { return } - guard !tokens.isEmpty else { return } - - for token in tokens { - // if the token is an exact match, we don't need to do anything - guard !knownOptions.contains(token) else { continue } - - let matches = NearMiss.bestMatches(for: knownOptions, against: token) + let matches = NearMiss.bestMatches(for: knownOptions, against: value) if !matches.isEmpty { - let diagnostic = Diagnostic(source: sourceFile, severity: .warning, range: codeBlock.range, identifier: "org.swift.docc.InvalidCodeBlockOption", summary: "Unknown option \(token.singleQuoted) in code block.") + let diagnostic = Diagnostic(source: sourceFile, severity: .warning, range: codeBlock.range, identifier: "org.swift.docc.InvalidCodeBlockOption", summary: "Unknown option \(value.singleQuoted) in code block.") let possibleSolutions = matches.map { candidate in Solution( - summary: "Replace \(token.singleQuoted) with \(candidate.singleQuoted).", + summary: "Replace \(value.singleQuoted) with \(candidate.singleQuoted).", replacements: [] ) } problems.append(Problem(diagnostic: diagnostic, possibleSolutions: possibleSolutions)) + } else if lang == nil { + let diagnostic = Diagnostic(source: sourceFile, severity: .warning, range: codeBlock.range, identifier: "org.swift.docc.InvalidCodeBlockOption", summary: "Unknown option \(value.singleQuoted) in code block.") + let possibleSolutions = + Solution( + summary: "If \(value.singleQuoted) is the language for this code block, then write \(value.singleQuoted) as the first option.", + replacements: [] + ) + problems.append(Problem(diagnostic: diagnostic, possibleSolutions: [possibleSolutions])) + } + } + + func validateArrayIndices(token: RenderBlockContent.CodeBlockOptions.OptionName, value: String?) { + guard token == .highlight || token == .strikeout, let value = value else { return } + // code property ends in a newline. this gives us a bogus extra line. + let lineCount: Int = codeBlock.code.split(omittingEmptySubsequences: false, whereSeparator: { $0.isNewline }).count - 1 + + let indices = RenderBlockContent.CodeBlockOptions.parseCodeBlockOptionsArray(value) + + if !value.isEmpty, indices.isEmpty { + let diagnostic = Diagnostic(source: sourceFile, severity: .warning, range: codeBlock.range, identifier: "org.swift.docc.InvalidCodeBlockOption", summary: "Could not parse \(token.rawValue.singleQuoted) indices from \(value.singleQuoted). Expected an integer (e.g. 3) or an array (e.g. [1, 3, 5])") + problems.append(Problem(diagnostic: diagnostic, possibleSolutions: [])) + return } + + let invalid = indices.filter { $0 < 1 || $0 > lineCount } + guard !invalid.isEmpty else { return } + + let diagnostic = Diagnostic(source: sourceFile, severity: .warning, range: codeBlock.range, identifier: "org.swift.docc.InvalidCodeBlockOption", summary: "Invalid \(token.rawValue.singleQuoted) index\(invalid.count == 1 ? "" : "es") in \(value.singleQuoted) for a code block with \(lineCount) line\(lineCount == 1 ? "" : "s"). Valid range is 1...\(lineCount).") + let solutions: [Solution] = { + if invalid.contains(where: {$0 == lineCount + 1}) { + return [Solution( + summary: "If you intended the last line, change '\(lineCount + 1)' to \(lineCount).", + replacements: [] + )] + } + return [] + }() + problems.append(Problem(diagnostic: diagnostic, possibleSolutions: solutions)) + } + + for (token, value) in tokens { + matches(token: token, value: value) + validateArrayIndices(token: token, value: value) } + // check if first token (lang) might be a typo + matches(token: .unknown, value: lang) } } diff --git a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift index 85ac031825..2b74a95e30 100644 --- a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift +++ b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift @@ -124,10 +124,60 @@ public enum RenderBlockContent: Equatable { public var code: [String] /// Additional metadata for this code block. public var metadata: RenderContentMetadata? + /// Annotations for code blocks + public var options: CodeBlockOptions? + + /// Make a new `CodeListing` with the given data. + public init(syntax: String?, code: [String], metadata: RenderContentMetadata?, options: CodeBlockOptions?) { + self.syntax = syntax + self.code = code + self.metadata = metadata + self.options = options + } + } + + public struct CodeBlockOptions: Equatable { + public var language: String? public var copyToClipboard: Bool + public var showLineNumbers: Bool + public var wrap: Int + public var lineAnnotations: [LineAnnotation] + + public struct Position: Equatable, Comparable, Codable { + public static func < (lhs: RenderBlockContent.CodeBlockOptions.Position, rhs: RenderBlockContent.CodeBlockOptions.Position) -> Bool { + if lhs.line == rhs.line, let lhsCharacter = lhs.character, let rhsCharacter = rhs.character { + return lhsCharacter < rhsCharacter + } + return lhs.line < rhs.line + } + + public init(line: Int, character: Int? = nil) { + self.line = line + self.character = character + } + + public var line: Int + public var character: Int? + } + + public struct LineAnnotation: Equatable, Codable { + public var style: String + public var range: Range + + public init(style: String, range: Range) { + self.style = style + self.range = range + } + } public enum OptionName: String, CaseIterable { + case _nonFrozenEnum_useDefaultCase case nocopy + case wrap + case highlight + case showLineNumbers + case strikeout + case unknown init?(caseInsensitive raw: some StringProtocol) { self.init(rawValue: raw.lowercased()) @@ -138,12 +188,165 @@ public enum RenderBlockContent: Equatable { Set(OptionName.allCases.map(\.rawValue)) } - /// Make a new `CodeListing` with the given data. - public init(syntax: String?, code: [String], metadata: RenderContentMetadata?, copyToClipboard: Bool = FeatureFlags.current.isExperimentalCodeBlockAnnotationsEnabled) { - self.syntax = syntax - self.code = code - self.metadata = metadata + // empty initializer with default values + public init() { + self.language = "" + self.copyToClipboard = FeatureFlags.current.isExperimentalCodeBlockAnnotationsEnabled + self.showLineNumbers = false + self.wrap = 0 + self.lineAnnotations = [] + } + + public init(parsingLanguageString language: String?) { + let (lang, tokens) = Self.tokenizeLanguageString(language) + + self.language = lang + self.copyToClipboard = !tokens.contains { $0.name == .nocopy } + self.showLineNumbers = tokens.contains { $0.name == .showLineNumbers } + + if let wrapString = tokens.first(where: { $0.name == .wrap })?.value, + let wrapValue = Int(wrapString) { + self.wrap = wrapValue + } else { + self.wrap = 0 + } + + var annotations: [LineAnnotation] = [] + + if let highlightString = tokens.first(where: { $0.name == .highlight })?.value { + let highlightValue = Self.parseCodeBlockOptionsArray(highlightString) + for line in highlightValue { + let pos = Position(line: line, character: nil) + let range = pos.. [Int] { + guard var s = value?.trimmingCharacters(in: .whitespaces), !s.isEmpty else { return [] } + + if s.hasPrefix("[") && s.hasSuffix("]") { + s.removeFirst() + s.removeLast() + } + + return s.split(separator: ",").compactMap { Int($0.trimmingCharacters(in: .whitespaces)) } + } + + /// A function that parses the language line options on code blocks, returning the language and tokens, an array of OptionName and option values + static internal func tokenizeLanguageString(_ input: String?) -> (lang: String?, tokens: [(name: OptionName, value: String?)]) { + guard let input else { return (lang: nil, tokens: []) } + + let parts = parseLanguageString(input) + var tokens: [(OptionName, String?)] = [] + var lang: String? = nil + + for (index, part) in parts.enumerated() { + if let eq = part.firstIndex(of: "=") { + let key = part[.. [Substring] { + + guard let input else { return [] } + var parts: [Substring] = [] + var start = input.startIndex + var i = input.startIndex + + var bracketDepth = 0 + + while i < input.endIndex { + let c = input[i] + + if c == "[" { bracketDepth += 1 } + else if c == "]" { bracketDepth = max(0, bracketDepth - 1) } + else if c == "," && bracketDepth == 0 { + let seq = input[start.. (lang: String? , tokens: [RenderBlockContent.CodeListing.OptionName]) { - guard let input else { return (lang: nil, tokens: []) } - let parts = input - .split(separator: ",") - .map { $0.trimmingCharacters(in: .whitespaces) } - var lang: String? = nil - var options: [RenderBlockContent.CodeListing.OptionName] = [] - - for part in parts { - if let opt = RenderBlockContent.CodeListing.OptionName(caseInsensitive: part) { - options.append(opt) - } else if lang == nil { - lang = String(part) - } - } - return (lang, options) - } - - let options = parseLanguageString(codeBlock.language) - + let codeBlockOptions = RenderBlockContent.CodeBlockOptions(parsingLanguageString: codeBlock.language) let listing = RenderBlockContent.CodeListing( - syntax: options.lang ?? bundle.info.defaultCodeListingLanguage, + syntax: codeBlockOptions.language ?? bundle.info.defaultCodeListingLanguage, code: codeBlock.code.splitByNewlines, metadata: nil, - copyToClipboard: !options.tokens.contains(.nocopy) + options: codeBlockOptions ) return [RenderBlockContent.codeListing(listing)] } else { - return [RenderBlockContent.codeListing(.init(syntax: codeBlock.language ?? bundle.info.defaultCodeListingLanguage, code: codeBlock.code.splitByNewlines, metadata: nil, copyToClipboard: false))] + return [RenderBlockContent.codeListing(.init(syntax: codeBlock.language ?? bundle.info.defaultCodeListingLanguage, code: codeBlock.code.splitByNewlines, metadata: nil, options: nil))] } } diff --git a/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift b/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift index 5dc54c091a..a6f7f34dbe 100644 --- a/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift +++ b/Sources/SwiftDocC/Semantics/Snippets/Snippet.swift @@ -92,11 +92,14 @@ extension Snippet: RenderableDirectiveConvertible { .linesWithoutLeadingWhitespace() // Make dedicated copies of each line because the RenderBlockContent.codeListing requires it. .map { String($0) } + + let options = RenderBlockContent.CodeBlockOptions() - return [RenderBlockContent.codeListing(.init(syntax: mixin.language, code: lines, metadata: nil))] + return [RenderBlockContent.codeListing(.init(syntax: mixin.language, code: lines, metadata: nil, options: options))] } else { // Render the full snippet and its explanatory content. - let fullCode = RenderBlockContent.codeListing(.init(syntax: mixin.language, code: mixin.lines, metadata: nil)) + let options = RenderBlockContent.CodeBlockOptions() + let fullCode = RenderBlockContent.codeListing(.init(syntax: mixin.language, code: mixin.lines, metadata: nil, options: options)) var content: [any RenderContent] = resolvedSnippet.explanation?.children.flatMap { contentCompiler.visit($0) } ?? [] content.append(fullCode) diff --git a/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json b/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json index 628a2d98f6..4f25303ac7 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json +++ b/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json @@ -781,6 +781,39 @@ } } }, + "LineAnnotation": { + "type": "object", + "properties": { + "style": { + "type": "string", + "enum": ["highlight", "strikeout"] + }, + "range": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Position" + } + } + }, + "required": [ + "style", + "range" + ] + }, + "Position": { + "type": "object", + "properties": { + "line": { + "type": "integer" + }, + "character": { + "type": "integer" + } + }, + "required": [ + "line" + ] + }, "CodeListing": { "type": "object", "required": [ @@ -808,6 +841,18 @@ }, "copyToClipboard": { "type": "boolean" + }, + "showLineNumbers": { + "type": "boolean" + }, + "wrap": { + "type": "integer" + }, + "lineAnnotations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LineAnnotation" + } } } }, diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift index c23c8c55bb..4d7272d3a2 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift @@ -19,7 +19,7 @@ extension ConvertAction { public init(fromConvertCommand convert: Docc.Convert, withFallbackTemplate fallbackTemplateURL: URL? = nil) throws { var standardError = LogHandle.standardError let outOfProcessResolver: OutOfProcessReferenceResolver? - FeatureFlags.current.isExperimentalCodeBlockAnnotationsEnabled = convert.featureFlags.enableExperimentalCodeBlockAnnotations + FeatureFlags.current.isExperimentalCodeBlockAnnotationsEnabled = convert.enableExperimentalCodeBlockAnnotations FeatureFlags.current.isExperimentalDeviceFrameSupportEnabled = convert.enableExperimentalDeviceFrameSupport FeatureFlags.current.isExperimentalLinkHierarchySerializationEnabled = convert.enableExperimentalLinkHierarchySerialization FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled = convert.enableExperimentalOverloadedSymbolPresentation diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift index aa427bdb1c..a33715a625 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift @@ -567,7 +567,7 @@ extension Docc { /// A user-provided value that is true if the user enables experimental support for code block annotation. /// /// Defaults to false. - public var enableExperimentalCodeBlocAnnotations: Bool { + public var enableExperimentalCodeBlockAnnotations: Bool { get { featureFlags.enableExperimentalCodeBlockAnnotations } set { featureFlags.enableExperimentalCodeBlockAnnotations = newValue} } diff --git a/Sources/docc/DocCDocumentation.docc/formatting-your-documentation-content.md b/Sources/docc/DocCDocumentation.docc/formatting-your-documentation-content.md index 07d44ee0c9..9793f97170 100644 --- a/Sources/docc/DocCDocumentation.docc/formatting-your-documentation-content.md +++ b/Sources/docc/DocCDocumentation.docc/formatting-your-documentation-content.md @@ -4,35 +4,35 @@ Enhance your content's presentation with special formatting and styling for text ## Overview -Use [Markdown](https://daringfireball.net/projects/markdown/syntax), a -lightweight markup language, to give structure and style to your documentation. -DocC includes a custom dialect of Markdown, documentation markup, which -extends Markdown's syntax to include features like symbol linking, improved +Use [Markdown](https://daringfireball.net/projects/markdown/syntax), a +lightweight markup language, to give structure and style to your documentation. +DocC includes a custom dialect of Markdown, documentation markup, which +extends Markdown's syntax to include features like symbol linking, improved image support, term lists, and asides. -To ensure consistent structure and styling, use DocC's documentation markup for +To ensure consistent structure and styling, use DocC's documentation markup for all of the documentation you write. ### Add a Page Title and Section Headers -To add a page title, precede the text you want to use with a hash (`#`) and a +To add a page title, precede the text you want to use with a hash (`#`) and a space. For the page title of an article or API collection, use plain text only. ```markdown # Getting Started with Sloths ``` -> Important: Page titles must be the first line of content in a documentation +> Important: Page titles must be the first line of content in a documentation file. One or more empty lines can precede the page title. -For the page title of a landing page, enter a symbol link by wrapping the framework's +For the page title of a landing page, enter a symbol link by wrapping the framework's module name within a set of double backticks (\`\`). ```markdown # ``SlothCreator`` ``` -For a documentation extension file, enter a symbol link by wrapping the path to the symbol +For a documentation extension file, enter a symbol link by wrapping the path to the symbol within double backticks (\`\`). The path may start with the framework's module name or with the name of a top-level symbol in the module. @@ -48,41 +48,41 @@ The following example shows a documentation extension link to the same symbol st # ``CareSchedule/Event`` ``` -Augment every page title with a short and concise single-sentence abstract or -summary that provides additional information about the content. Add the summary +Augment every page title with a short and concise single-sentence abstract or +summary that provides additional information about the content. Add the summary using a new paragraph directly below the page title. ```markdown # Getting Started with Sloths Create a sloth and assign personality traits and abilities. -``` +``` -To add a header for an Overview or a Discussion section, use a double hash +To add a header for an Overview or a Discussion section, use a double hash (`##`) and a space, and then include either term in plain text. ```markdown ## Overview ``` -For all other section headers, use a triple hash (`###`) and a space, and then +For all other section headers, use a triple hash (`###`) and a space, and then add the title of the header in plain text. ```markdown ### Create a Sloth ``` -Use this type of section header in framework landing pages, top-level pages, -articles, and occasionally in symbol reference pages where you need to +Use this type of section header in framework landing pages, top-level pages, +articles, and occasionally in symbol reference pages where you need to provide more detail. ### Format Text in Bold, Italics, and Code Voice -DocC provides three ways to format the text in your documentation. You can -apply bold or italic styling, or you can use code voice, which renders the +DocC provides three ways to format the text in your documentation. You can +apply bold or italic styling, or you can use code voice, which renders the specified text in a monospace font. -To add bold styling, wrap the text in a pair of double asterisks (`**`). +To add bold styling, wrap the text in a pair of double asterisks (`**`). Alternatively, use double underscores (`__`). The following example uses bold styling for the names of the sloths: @@ -92,42 +92,42 @@ The following example uses bold styling for the names of the sloths: __Silly Sloth__: Prefers twigs for breakfast. ``` -Use italicized text to introduce new or alternative terms to the reader. To add -italic styling, wrap the text in a set of single underscores (`_`) or single +Use italicized text to introduce new or alternative terms to the reader. To add +italic styling, wrap the text in a set of single underscores (`_`) or single asterisks (`*`). -The following example uses italics for the words _metabolism_ and _habitat_: +The following example uses italics for the words _metabolism_ and _habitat_: ```markdown A sloth's _metabolism_ is highly dependent on its *habitat*. ``` -Use code voice to refer to symbols inline, or to include short code fragments, -such as class names or method signatures. To add code voice, wrap the text in +Use code voice to refer to symbols inline, or to include short code fragments, +such as class names or method signatures. To add code voice, wrap the text in a set of backticks (\`). -In the following example, DocC renders the words _ice_, _fire_, _wind_, and +In the following example, DocC renders the words _ice_, _fire_, _wind_, and _lightning_ in a monospace font: ```markdown -If your sloth possesses one of the special powers: `ice`, `fire`, +If your sloth possesses one of the special powers: `ice`, `fire`, `wind`, or `lightning`. ``` -> Note: To include multiple lines of code, use a code listing instead. For more +> Note: To include multiple lines of code, use a code listing instead. For more information, see . ### Add Code Listings -DocC includes support for code listings, or fenced code blocks, which allow you -to go beyond the basic declaration sections you find in symbol reference pages, -and to provide more complete code examples for adopters of your framework. You can -include code listings in your in-source symbol documentation, in extension +DocC includes support for code listings, or fenced code blocks, which allow you +to go beyond the basic declaration sections you find in symbol reference pages, +and to provide more complete code examples for adopters of your framework. You can +include code listings in your in-source symbol documentation, in extension files, and in articles and tutorials. -To create a code listing, start a new paragraph and add three backticks -(\`\`\`). Then, directly following the backticks, add the name of the -programming language in lowercase text. Add one or more lines of code, and then +To create a code listing, start a new paragraph and add three backticks +(\`\`\`). Then, directly following the backticks, add the name of the +programming language in lowercase text. Add one or more lines of code, and then add a new line and terminate the code listing by adding another three backticks: ```swift @@ -139,11 +139,11 @@ add a new line and terminate the code listing by adding another three backticks: } ``` -> Important: When formatting your code listing, use spaces to indent lines -instead of tabs so that DocC preserves the indentation when compiling your +> Important: When formatting your code listing, use spaces to indent lines +instead of tabs so that DocC preserves the indentation when compiling your documentation. -DocC uses the programming language you specify to apply the correct syntax +DocC uses the programming language you specify to apply the correct syntax color formatting. For the example above, DocC generates the following: ```swift @@ -191,12 +191,12 @@ DocC supports the following list types: | ------------- | ------------------------------------------------------ | | Bulleted list | Groups items that can appear in any order. | | Numbered list | Delineates a sequence of events in a particular order. | -| Term list | Defines a series of term-definition pairs. | +| Term list | Defines a series of term-definition pairs. | -> Important: Don't add images or code listings between list items. Bulleted and +> Important: Don't add images or code listings between list items. Bulleted and numbered lists must contain two or more items. -To create a bulleted list, precede each of the list's items with an asterisk (`*`) and a +To create a bulleted list, precede each of the list's items with an asterisk (`*`) and a space. Alternatively, use a dash (`-`) or a plus sign (`+`) instead of an asterisk (`*`); the list markers are interchangeable. ```markdown @@ -206,7 +206,7 @@ space. Alternatively, use a dash (`-`) or a plus sign (`+`) instead of an asteri + Lightning ``` -To create a numbered list, precede each of the list's items with the number of the step, then a period (`.`) and a space. +To create a numbered list, precede each of the list's items with the number of the step, then a period (`.`) and a space. ```markdown 1. Give the sloth some food. @@ -215,8 +215,8 @@ To create a numbered list, precede each of the list's items with the number of t 4. Put the sloth to bed. ``` -To create a term list, precede each term with a dash (`-`) and a -space, the `term` keyword, and another space. Then add a colon (`:`), a space, and the definition after the term. +To create a term list, precede each term with a dash (`-`) and a +space, the `term` keyword, and another space. Then add a colon (`:`), a space, and the definition after the term. ```markdown - term Ice: Ice sloths thrive below freezing temperatures. @@ -225,8 +225,8 @@ space, the `term` keyword, and another space. Then add a colon (`:`), a space, a - term Lightning: Lightning sloths thrive in stormy climates. ``` -A list item's text, including terms and their definitions, can use the same -style attributes as other text, and include links to other content, including +A list item's text, including terms and their definitions, can use the same +style attributes as other text, and include links to other content, including symbols. diff --git a/Tests/SwiftDocCTests/Checker/Checkers/InvalidCodeBlockOptionTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/InvalidCodeBlockOptionTests.swift index 2def720f13..e67b5d8ff7 100644 --- a/Tests/SwiftDocCTests/Checker/Checkers/InvalidCodeBlockOptionTests.swift +++ b/Tests/SwiftDocCTests/Checker/Checkers/InvalidCodeBlockOptionTests.swift @@ -24,7 +24,6 @@ let a = 1 var checker = InvalidCodeBlockOption(sourceFile: nil) checker.visit(document) XCTAssertTrue(checker.problems.isEmpty) - XCTAssertEqual(RenderBlockContent.CodeListing.knownOptions, ["nocopy"]) } func testOption() { @@ -67,7 +66,7 @@ let c = 3 let d = 4 ``` -```unknown, nocpoy +```haskell, nocpoy let e = 5 ``` @@ -104,5 +103,59 @@ let g = 7 } } + + func testLanguageNotFirst() { + let markupSource = """ +```nocopy, swift, highlight=[1] +let b = 2 +``` +""" + let document = Document(parsing: markupSource, options: []) + var checker = InvalidCodeBlockOption(sourceFile: URL(fileURLWithPath: #file)) + checker.visit(document) + XCTAssertEqual(1, checker.problems.count) + + for problem in checker.problems { + XCTAssertEqual("org.swift.docc.InvalidCodeBlockOption", problem.diagnostic.identifier) + XCTAssertEqual(problem.diagnostic.summary, "Unknown option 'swift' in code block.") + XCTAssertEqual(problem.possibleSolutions.map(\.summary), ["If 'swift' is the language for this code block, then write 'swift' as the first option."]) + } + } + + func testInvalidHighlightIndex() throws { + let markupSource = """ +```swift, nocopy, highlight=[2] +let b = 2 +``` +""" + let document = Document(parsing: markupSource, options: []) + var checker = InvalidCodeBlockOption(sourceFile: URL(fileURLWithPath: #file)) + checker.visit(document) + XCTAssertEqual(1, checker.problems.count) + let problem = try XCTUnwrap(checker.problems.first) + + XCTAssertEqual("org.swift.docc.InvalidCodeBlockOption", problem.diagnostic.identifier) + XCTAssertEqual(problem.diagnostic.summary, "Invalid 'highlight' index in '[2]' for a code block with 1 line. Valid range is 1...1.") + XCTAssertEqual(problem.possibleSolutions.map(\.summary), ["If you intended the last line, change '2' to 1."]) + } + + func testInvalidHighlightandStrikeoutIndex() throws { + let markupSource = """ +```swift, nocopy, highlight=[0], strikeout=[-1, 4] +let a = 1 +let b = 2 +let c = 3 +``` +""" + let document = Document(parsing: markupSource, options: []) + var checker = InvalidCodeBlockOption(sourceFile: URL(fileURLWithPath: #file)) + checker.visit(document) + XCTAssertEqual(2, checker.problems.count) + + XCTAssertEqual("org.swift.docc.InvalidCodeBlockOption", checker.problems[0].diagnostic.identifier) + XCTAssertEqual(checker.problems[0].diagnostic.summary, "Invalid 'highlight' index in '[0]' for a code block with 3 lines. Valid range is 1...3.") + XCTAssertEqual(checker.problems[1].diagnostic.summary, "Invalid 'strikeout' indexes in '[-1, 4]' for a code block with 3 lines. Valid range is 1...3.") + XCTAssertEqual(checker.problems[1].possibleSolutions.map(\.summary), ["If you intended the last line, change '4' to 3."]) + } } diff --git a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift index 3d7d0c44ea..b803126147 100644 --- a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift @@ -54,7 +54,7 @@ class RenderContentMetadataTests: XCTestCase { RenderInlineContent.text("Content"), ]) - let code = RenderBlockContent.codeListing(.init(syntax: nil, code: [], metadata: metadata, copyToClipboard: false)) + let code = RenderBlockContent.codeListing(.init(syntax: nil, code: [], metadata: metadata, options: nil)) let data = try JSONEncoder().encode(code) let roundtrip = try JSONDecoder().decode(RenderBlockContent.self, from: data) diff --git a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift index 0f669cd1cd..fc63eb88e5 100644 --- a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift @@ -44,7 +44,7 @@ class RenderNodeSerializationTests: XCTestCase { .strong(inlineContent: [.text("Project > Run")]), .text(" menu item, or the following code:"), ])), - .codeListing(.init(syntax: "swift", code: ["xcrun xcodebuild -h", "xcrun xcodebuild build -configuration Debug"], metadata: nil, copyToClipboard: false)), + .codeListing(.init(syntax: "swift", code: ["xcrun xcodebuild -h", "xcrun xcodebuild build -configuration Debug"], metadata: nil, options: nil)), ])) ] @@ -71,16 +71,16 @@ class RenderNodeSerializationTests: XCTestCase { let assessment1 = TutorialAssessmentsRenderSection.Assessment(title: [.paragraph(.init(inlineContent: [.text("Lorem ipsum dolor sit amet?")]))], content: nil, choices: [ - .init(content: [.codeListing(.init(syntax: "swift", code: ["override func viewDidLoad() {", "super.viewDidLoad()", "}"], metadata: nil, copyToClipboard: false))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "That's right!"), - .init(content: [.codeListing(.init(syntax: "swift", code: ["sceneView.delegate = self"], metadata: nil, copyToClipboard: false))], isCorrect: false, justification: [.paragraph(.init(inlineContent: [.text("It's incorrect because...")]))], reaction: "Not quite."), + .init(content: [.codeListing(.init(syntax: "swift", code: ["override func viewDidLoad() {", "super.viewDidLoad()", "}"], metadata: nil, options: nil))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "That's right!"), + .init(content: [.codeListing(.init(syntax: "swift", code: ["sceneView.delegate = self"], metadata: nil, options: nil))], isCorrect: false, justification: [.paragraph(.init(inlineContent: [.text("It's incorrect because...")]))], reaction: "Not quite."), .init(content: [.paragraph(.init(inlineContent: [.text("None of the above.")]))], isCorrect: false, justification: [.paragraph(.init(inlineContent: [.text("It's incorrect because...")]))], reaction: nil), ]) let assessment2 = TutorialAssessmentsRenderSection.Assessment(title: [.paragraph(.init(inlineContent: [.text("Duis aute irure dolor in reprehenderit?")]))], content: [.paragraph(.init(inlineContent: [.text("What is the airspeed velocity of an unladen swallow?")]))], choices: [ - .init(content: [.codeListing(.init(syntax: "swift", code: ["super.viewWillAppear()"], metadata: nil, copyToClipboard: false))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "Correct."), - .init(content: [.codeListing(.init(syntax: "swift", code: ["sceneView.delegate = self"], metadata: nil, copyToClipboard: false))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "Yep."), + .init(content: [.codeListing(.init(syntax: "swift", code: ["super.viewWillAppear()"], metadata: nil, options: nil))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "Correct."), + .init(content: [.codeListing(.init(syntax: "swift", code: ["sceneView.delegate = self"], metadata: nil, options: nil))], isCorrect: true, justification: [.paragraph(.init(inlineContent: [.text("It's correct because...")]))], reaction: "Yep."), .init(content: [.paragraph(.init(inlineContent: [.text("None of the above.")]))], isCorrect: false, justification: [.paragraph(.init(inlineContent: [.text("It's incorrect because...")]))], reaction: "Close!"), ]) diff --git a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift index ef3604fd9c..28af2d014d 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift @@ -13,6 +13,8 @@ import Markdown @testable import SwiftDocC import XCTest +typealias Position = RenderBlockContent.CodeBlockOptions.Position + class RenderContentCompilerTests: XCTestCase { func testLinkOverrideTitle() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") @@ -245,7 +247,7 @@ class RenderContentCompilerTests: XCTestCase { return } - XCTAssertEqual(codeListing.copyToClipboard, true) + XCTAssertEqual(codeListing.options?.copyToClipboard, true) } func testNoCopyToClipboard() async throws { @@ -269,7 +271,32 @@ class RenderContentCompilerTests: XCTestCase { return } - XCTAssertEqual(codeListing.copyToClipboard, false) + XCTAssertEqual(codeListing.options?.copyToClipboard, false) + } + + func testCopyToClipboardNoLang() async throws { + enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) + + let (bundle, context) = try await testBundleAndContext() + var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + + let source = #""" + ```nocopy + let x = 1 + ``` + """# + let document = Document(parsing: source) + + let result = document.children.flatMap { compiler.visit($0) } + + let renderCodeBlock = try XCTUnwrap(result[0] as? RenderBlockContent) + guard case let .codeListing(codeListing) = renderCodeBlock else { + XCTFail("Expected RenderBlockContent.codeListing") + return + } + + XCTAssertEqual(codeListing.syntax, nil) + XCTAssertEqual(codeListing.options?.copyToClipboard, false) } func testCopyToClipboardNoFeatureFlag() async throws { @@ -282,7 +309,6 @@ class RenderContentCompilerTests: XCTestCase { ``` """# let document = Document(parsing: source) - let result = document.children.flatMap { compiler.visit($0) } let renderCodeBlock = try XCTUnwrap(result[0] as? RenderBlockContent) @@ -291,7 +317,7 @@ class RenderContentCompilerTests: XCTestCase { return } - XCTAssertEqual(codeListing.copyToClipboard, false) + XCTAssertEqual(codeListing.options?.copyToClipboard, nil) } func testNoCopyToClipboardNoFeatureFlag() async throws { @@ -314,6 +340,373 @@ class RenderContentCompilerTests: XCTestCase { } XCTAssertEqual(codeListing.syntax, "swift, nocopy") - XCTAssertEqual(codeListing.copyToClipboard, false) + XCTAssertEqual(codeListing.options?.copyToClipboard, nil) + } + + func testShowLineNumbers() async throws { + enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) + + let (bundle, context) = try await testBundleAndContext() + var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + + let source = #""" + ```swift, showLineNumbers + let a = 1 + let b = 2 + let c = 3 + let d = 4 + let e = 5 + ``` + """# + let document = Document(parsing: source) + + let result = document.children.flatMap { compiler.visit($0) } + + let renderCodeBlock = try XCTUnwrap(result[0] as? RenderBlockContent) + guard case let .codeListing(codeListing) = renderCodeBlock else { + XCTFail("Expected RenderBlockContent.codeListing") + return + } + + XCTAssertEqual(codeListing.options?.showLineNumbers, true) + } + + func testLowercaseShowLineNumbers() async throws { + enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) + + let (bundle, context) = try await testBundleAndContext() + var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + + let source = #""" + ```swift, showlinenumbers + let a = 1 + let b = 2 + let c = 3 + let d = 4 + let e = 5 + ``` + """# + let document = Document(parsing: source) + + let result = document.children.flatMap { compiler.visit($0) } + + let renderCodeBlock = try XCTUnwrap(result[0] as? RenderBlockContent) + guard case let .codeListing(codeListing) = renderCodeBlock else { + XCTFail("Expected RenderBlockContent.codeListing") + return + } + + XCTAssertEqual(codeListing.options?.showLineNumbers, true) + } + + func testWrapAndHighlight() async throws { + enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) + + let (bundle, context) = try await testBundleAndContext() + var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + + let source = #""" + ```swift, wrap=20, highlight=[2] + let a = 1 + let b = 2 + let c = 3 + let d = 4 + let e = 5 + ``` + """# + + let document = Document(parsing: source) + + let result = document.children.flatMap { compiler.visit($0) } + + let renderCodeBlock = try XCTUnwrap(result[0] as? RenderBlockContent) + guard case let .codeListing(codeListing) = renderCodeBlock else { + XCTFail("Expected RenderBlockContent.codeListing") + return + } + + XCTAssertEqual(codeListing.syntax, "swift") + XCTAssertEqual(codeListing.options?.wrap, 20) + let line = Position(line: 2) + XCTAssertEqual(codeListing.options?.lineAnnotations, + [RenderBlockContent.CodeBlockOptions.LineAnnotation( + style: "highlight", + range: line.. Warning: Inner aside, with ``ThirdNotFoundSymbol`` link .aside(.init(style: .init(asideKind: .warning), content: [ diff --git a/features.json b/features.json index 31e8b0e7d3..014b9bf65c 100644 --- a/features.json +++ b/features.json @@ -1,7 +1,7 @@ { "features": [ { - "name": "code-blocks" + "name": "code-block-annotations" }, { "name": "diagnostics-file" From f982c41786a02522e807e238895a925bae8e4352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 7 Oct 2025 17:56:21 +0200 Subject: [PATCH 49/90] Update deprecation messages to give the API a full minor release before being removed (#1310) --- .../Checkers/AbstractContainsFormattedTextOnly.swift | 2 +- .../SwiftDocC/LinkTargets/LinkDestinationSummary.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift b/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift index a2ed042d6c..f41e467cf6 100644 --- a/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift +++ b/Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift @@ -14,7 +14,7 @@ public import Markdown /** A document's abstract may only contain formatted text. Images and links are not allowed. */ -@available(*, deprecated, message: "This check is no longer applicable. This deprecated API will be removed after 6.3 is released") +@available(*, deprecated, message: "This check is no longer applicable. This deprecated API will be removed after 6.4 is released") public struct AbstractContainsFormattedTextOnly: Checker { public var problems: [Problem] = [Problem]() private var sourceFile: URL? diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index a092359f28..db64f1939a 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -226,7 +226,7 @@ public struct LinkDestinationSummary: Codable, Equatable { /// If the summarized element has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. public let subheadingDeclarationFragments: VariantValue - @available(*, deprecated, renamed: "subheadingDeclarationFragments", message: "Use 'subheadingDeclarationFragments' instead. This deprecated API will be removed after 6.3 is released.") + @available(*, deprecated, renamed: "subheadingDeclarationFragments", message: "Use 'subheadingDeclarationFragments' instead. This deprecated API will be removed after 6.4 is released.") public var declarationFragments: VariantValue { subheadingDeclarationFragments } @@ -241,7 +241,7 @@ public struct LinkDestinationSummary: Codable, Equatable { /// Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. /// /// If the summarized element has an image but the variant doesn't, this property will be `Optional.some(nil)`. - @available(*, deprecated, message: "`TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") + @available(*, deprecated, message: "`TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.4 is released") public let topicImages: VariantValue<[TopicImage]?> = nil /// Creates a new summary variant with the values that are different from the main summarized values. @@ -284,7 +284,7 @@ public struct LinkDestinationSummary: Codable, Equatable { self.navigatorDeclarationFragments = navigatorDeclarationFragments } - @available(*, deprecated, renamed: "init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:)", message: "Use `init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:)` instead. `TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.3 is released") + @available(*, deprecated, renamed: "init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:)", message: "Use `init(traits:kind:language:relativePresentationURL:title:abstract:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:)` instead. `TopicRenderReference` doesn't support variant specific topic images. This property will be removed after 6.4 is released") public init( traits: [RenderNode.Variant.Trait], kind: VariantValue = nil, @@ -375,7 +375,7 @@ public struct LinkDestinationSummary: Codable, Equatable { self.variants = variants } - @available(*, deprecated, renamed: "init(kind:language:relativePresentationURL:referenceURL:title:abstract:availableLanguages:platforms:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:redirects:topicImages:references:variants:)", message: "Use `init(kind:language:relativePresentationURL:referenceURL:title:abstract:availableLanguages:platforms:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:redirects:topicImages:references:variants:)` instead. This property will be removed after 6.3 is released") + @available(*, deprecated, renamed: "init(kind:language:relativePresentationURL:referenceURL:title:abstract:availableLanguages:platforms:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:redirects:topicImages:references:variants:)", message: "Use `init(kind:language:relativePresentationURL:referenceURL:title:abstract:availableLanguages:platforms:taskGroups:usr:plainTextDeclaration:subheadingDeclarationFragments:navigatorDeclarationFragments:redirects:topicImages:references:variants:)` instead. This property will be removed after 6.4 is released") public init( kind: DocumentationNode.Kind, language: SourceLanguage, From eeabad0590d2e0774cc76c7cb7381947893678de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 7 Oct 2025 18:03:25 +0200 Subject: [PATCH 50/90] Add a bit more public API documentation about the V2 communication protocol for external link resolver processes/executables (#1313) * Add a bit more public API documentation about the V2 communication protocol for external link resolver processes/executables rdar://161721734 * Add additional public API documentation about the expected `referenceURL` for successful link requests. * Use 3 hyphens instead of em-dashes in in-source doc comments --- ...ocessReferenceResolver+Communication.swift | 36 +++++++++++++++---- .../OutOfProcessReferenceResolver.swift | 16 +++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift index b60dce179a..72b321d52d 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift @@ -37,7 +37,7 @@ extension OutOfProcessReferenceResolver { // MARK: Request & Response - /// Request messages that DocC sends to the external link resolver. + /// Request messages that DocC sends to the configured external link resolver. /// /// ## Topics /// ### Base requests @@ -49,9 +49,17 @@ extension OutOfProcessReferenceResolver { public enum RequestV2: Codable { /// A request to resolve a link /// - /// Your external resolver + /// Your external resolver should respond with either: + /// - a ``ResponseV2/resolved(_:)`` message, with information about the requested link. + /// - a ``ResponseV2/failure(_:)`` message, with human-readable information about the problem that the external link resolver encountered while resolving the link. case link(String) /// A request to resolve a symbol based on its precise identifier. + /// + /// Your external resolver should respond with either: + /// - a ``ResponseV2/resolved(_:)`` message, with information about the requested symbol. + /// - an empty ``ResponseV2/failure(_:)`` message. + /// DocC doesn't display any diagnostics about failed symbol requests because they wouldn't be actionable; + /// because they're entirely automatic---based on unique identifiers in the symbol graph files---rather than authored references in the content. case symbol(String) // This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled, @@ -88,20 +96,31 @@ extension OutOfProcessReferenceResolver { } } - /// A response message from the external link resolver. + /// Response messages that the external link resolver sends back to DocC for each received request.. /// /// If your external resolver sends a response that's associated with a capability that DocC hasn't declared support for, then DocC will fail to handle the response. public enum ResponseV2: Codable { - /// The initial identifier and capabilities message. + /// The initial identifier-and-capabilities message. /// /// Your external link resolver should send this message, exactly once, after it has launched to signal that its ready to receive requests. /// /// The capabilities that your external link resolver declares in this message determines which optional request messages that DocC will send. /// If your resolver doesn't declare _any_ capabilities it only needs to handle the 3 default requests. See . case identifierAndCapabilities(DocumentationBundle.Identifier, Capabilities) - /// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol. + /// A response with human-readable information about the problem that the external link resolver encountered while resolving the requested link or symbol. + /// + /// - Note: DocC doesn't display any diagnostics about failed ``RequestV2/symbol(_:)`` requests because they wouldn't be actionable; + /// because they're entirely automatic---based on unique identifiers in the symbol graph files---rather than authored references in the content. + /// + /// Your external link resolver still needs to send a response to each request but it doesn't need to include details about the failure when responding to a ``RequestV2/symbol(_:)`` request. case failure(DiagnosticInformation) - /// A response with the resolved information about the requested topic or symbol. + /// A response with the resolved information about the requested link or symbol. + /// + /// The ``LinkDestinationSummary/referenceURL`` can have a "path" and "fragment" components that are different from what DocC sent in the ``RequestV2/link(_:)`` request. + /// For example; if your resolver supports different spellings of the link---corresponding to a page's different names in different language representations---you can return the common reference URL identifier for that page for all link spellings. + /// + /// - Note: DocC expects the resolved ``LinkDestinationSummary/referenceURL`` to have a "host" component that matches the ``DocumentationBundle/Identifier`` that your resolver provided in its initial ``identifierAndCapabilities(_:_:)`` handshake message. + /// Responding with a ``LinkDestinationSummary/referenceURL`` that doesn't match the resolver's provided ``DocumentationBundle/Identifier`` is undefined behavior. case resolved(LinkDestinationSummary) // This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled, @@ -159,6 +178,11 @@ extension OutOfProcessReferenceResolver { extension OutOfProcessReferenceResolver.ResponseV2 { /// Information about why the external resolver failed to resolve the `link(_:)`, or `symbol(_:)` request. + /// + /// - Note: DocC doesn't display any diagnostics about failed ``RequestV2/symbol(_:)`` requests because they wouldn't be actionable; + /// because they're entirely automatic---based on unique identifiers in the symbol graph files---rather than authored references in the content. + /// + /// Your external link resolver still needs to send a response to each request but it doesn't need to include details about the failure when responding to a ``RequestV2/symbol(_:)`` request. public struct DiagnosticInformation: Codable { /// A brief user-facing summary of the issue that caused the external resolver to fail. public var summary: String diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift index d062ed0820..06252d82e1 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift @@ -60,9 +60,25 @@ private import Markdown /// /// ## Topics /// +/// ### Messages +/// +/// Requests that DocC sends to your link resolver executable and the responses that it should send back. +/// /// - ``RequestV2`` /// - ``ResponseV2`` /// +/// ### Finding common capabilities +/// +/// Ways that your link resolver executable can signal any optional capabilities that it supports. +/// +/// - ``ResponseV2/identifierAndCapabilities(_:_:)`` +/// - ``Capabilities`` +/// +/// ### Deprecated messages +/// +/// - ``Request`` +/// - ``Response`` +/// /// ## See Also /// - ``DocumentationContext/externalDocumentationSources`` /// - ``DocumentationContext/globalExternalSymbolResolver`` From c5f99a27bbf3ebadda5baaa79293a68faa9f90b1 Mon Sep 17 00:00:00 2001 From: Raluca Coutinho <38136087+RalucaP@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:35:49 +0200 Subject: [PATCH 51/90] Fix missing platforms in declaration fragments (#1314) * Update tests for platform expansion in declaration fragments Updates test assertions to expect iOS + iPadOS + Mac Catalyst when iOS is present in declaration platforms, using Set-based comparisons for order-independent testing. The modified tests are temporarily skipped until the platform expansion functionality is implemented in the next commit. * Expand iOS platform to include iPadOS and Mac Catalyst in declaration fragments Automatically expands iOS to include its fallback platforms (iPadOS and Mac Catalyst) in declaration fragment platform arrays to ensure consistent platform representation. Uses the existing DefaultAvailability.fallbackPlatforms mapping to determine which platforms should be included when a base platform is present. rdar://158142013 --- .../DeclarationsSectionTranslator.swift | 29 +++++++++++++++++-- .../Model/SemaToRenderNodeTests.swift | 4 +-- .../DeclarationsRenderSectionTests.swift | 4 +-- ...derNodeTranslatorSymbolVariantsTests.swift | 2 +- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift index 17caa3947e..7e255a2883 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.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 @@ -187,6 +187,26 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { return declarations } + /// Returns the given platforms with any missing fallback platforms added. + /// + /// This function uses the centralized `DefaultAvailability.fallbackPlatforms` mapping to ensure + /// consistency with platform expansion logic used throughout the codebase. + /// + /// For example, when iOS is present in the platforms array, this function adds iPadOS and Mac Catalyst + /// if they are not already included. + /// + /// - Parameter platforms: The original platforms array. + /// - Returns: The platforms array with fallback platforms added where applicable. + func expandPlatformsWithFallbacks(_ platforms: [PlatformName?]) -> [PlatformName?] { + guard !platforms.isEmpty else { return platforms } + + // Add fallback platforms if their primary platform is present but the fallback is missing + let fallbacks = DefaultAvailability.fallbackPlatforms.compactMap { fallback, primary in + platforms.contains(primary) && !platforms.contains(fallback) ? fallback : nil + } + return platforms + fallbacks + } + func comparePlatformNames(_ lhs: PlatformName?, _ rhs: PlatformName?) -> Bool { guard let lhsValue = lhs, let rhsValue = rhs else { return lhs == nil @@ -204,6 +224,8 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { ] for pair in declaration { let (platforms, declaration) = pair + let expandedPlatforms = expandPlatformsWithFallbacks(platforms) + let platformNames = sortPlatformNames(expandedPlatforms) let renderedTokens: [DeclarationRenderSection.Token] let otherDeclarations: DeclarationRenderSection.OtherDeclarations? @@ -242,7 +264,7 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { declarations.append( DeclarationRenderSection( languages: languages, - platforms: sortPlatformNames(platforms), + platforms: platformNames, tokens: renderedTokens, otherDeclarations: otherDeclarations ) @@ -252,7 +274,8 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { if let alternateDeclarations = symbol.alternateDeclarationVariants[trait] { for pair in alternateDeclarations { let (platforms, decls) = pair - let platformNames = sortPlatformNames(platforms) + let expandedPlatforms = expandPlatformsWithFallbacks(platforms) + let platformNames = sortPlatformNames(expandedPlatforms) for alternateDeclaration in decls { let renderedTokens = alternateDeclaration.declarationFragments.map(translateFragment) diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 2a16c9db87..78f43efd5e 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -998,7 +998,7 @@ class SemaToRenderNodeTests: XCTestCase { return } - XCTAssertEqual(declarations.declarations[0].platforms, [PlatformName(operatingSystemName: "ios")]) + XCTAssertEqual(Set(declarations.declarations[0].platforms), Set([PlatformName(operatingSystemName: "ios"), PlatformName.iPadOS, PlatformName.catalyst])) XCTAssertEqual(declarations.declarations[0].tokens.count, 5) XCTAssertEqual(declarations.declarations[0].tokens.map { $0.text }.joined(), "protocol MyProtocol : Hashable") XCTAssertEqual(declarations.declarations[0].languages?.first, "swift") @@ -1257,7 +1257,7 @@ class SemaToRenderNodeTests: XCTestCase { return } - XCTAssertEqual(declarations.declarations[0].platforms, [PlatformName(operatingSystemName: "ios")]) + XCTAssertEqual(Set(declarations.declarations[0].platforms), Set([PlatformName(operatingSystemName: "ios"), PlatformName.iPadOS, PlatformName.catalyst])) XCTAssertEqual(declarations.declarations[0].tokens.count, 5) XCTAssertEqual(declarations.declarations[0].tokens.map { $0.text }.joined(), "protocol MyProtocol : Hashable") XCTAssertEqual(declarations.declarations[0].languages?.first, "swift") diff --git a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift index 42c72aef04..71f43bb446 100644 --- a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift @@ -157,7 +157,7 @@ class DeclarationsRenderSectionTests: XCTestCase { let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) XCTAssertEqual(declarationsSection.declarations.count, 2) - XCTAssert(declarationsSection.declarations.allSatisfy({ $0.platforms == [.iOS, .macOS] })) + XCTAssert(declarationsSection.declarations.allSatisfy({ Set($0.platforms) == Set([.iOS, .iPadOS, .macOS, .catalyst]) })) } func testPlatformSpecificDeclarations() async throws { @@ -223,7 +223,7 @@ class DeclarationsRenderSectionTests: XCTestCase { let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) XCTAssertEqual(declarationsSection.declarations.count, 2) - XCTAssertEqual(declarationsSection.declarations[0].platforms, [.iOS]) + XCTAssertEqual(Set(declarationsSection.declarations[0].platforms), Set([.iOS, .iPadOS, .catalyst])) XCTAssertEqual(declarationsSection.declarations[0].tokens.map(\.text).joined(), "init(_ content: OtherClass) throws") XCTAssertEqual(declarationsSection.declarations[1].platforms, [.macOS]) diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift index 31504330f9..d7f6d1ec4d 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift @@ -401,7 +401,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { }, assertAfterApplyingVariant: { renderNode in let declarationSection = try declarationSection(in: renderNode) - XCTAssertEqual(declarationSection.platforms, [.iOS]) + XCTAssertEqual(Set(declarationSection.platforms), Set([.iOS, .iPadOS, .catalyst])) XCTAssertEqual( declarationSection.tokens, From eb5ba13ac759c02d2190274d434c6af154431a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 10 Oct 2025 09:58:49 +0200 Subject: [PATCH 52/90] Remove redundant "bundle" parameters where a context is also available (#1309) * Rename context "bundle" property to "inputs" * Remove redundant "bundle" parameter from RenderHierarchyTranslator * Remove redundant "bundle" parameter from RenderContentCompiler * Update test to avoid using inputs that don't belong to the context * Remove redundant "bundle" parameter from MarkupReferenceResolver * Remove redundant "bundle" parameter from ReferenceResolver * Remove redundant "bundle" parameter from DocumentationContentRenderer * Remove redundant "bundle" parameter from RenderNodeTranslator * Remove redundant "bundle" parameter from DocumentationNodeConverter * Remove redundant "bundle" parameter from DocumentationContextConverter * Remove redundant "bundle" parameter from GeneratedDocumentationTopics * Remove redundant "bundle" parameter from ConvertActionConverter * Remove redundant "bundle" parameter from AutomaticCuration.seeAlso * Remove redundant "bundle" parameter from RenderNodeTranslator.defaultAvailability * Remove redundant "bundle" parameter from DocumentationCurator * Remove unused DocumentationContext.unregister method * Remove redundant "bundle" parameter from PathHierarchyBasedLinkResolver.referencesForSymbols * Remove redundant "bundle" parameters from various private DocumentationContext methods * Remove two local "bundle" variables --- .../DocumentationContextConverter.swift | 28 +- .../DocumentationNodeConverter.swift | 17 +- .../Convert/ConvertService.swift | 10 +- .../ConvertActionConverter.swift | 15 +- .../Infrastructure/DocumentationContext.swift | 223 ++++------- .../Infrastructure/DocumentationCurator.swift | 10 +- .../Link Resolution/LinkResolver.swift | 22 +- .../PathHierarchyBasedLinkResolver.swift | 8 +- .../GeneratedDocumentationTopics.swift | 9 +- .../Topic Graph/AutomaticCuration.swift | 3 +- .../LinkTargets/LinkDestinationSummary.swift | 7 +- .../DocumentationContentRenderer.swift | 56 +-- .../Model/Rendering/LinkTitleResolver.swift | 4 +- .../RenderHierarchyTranslator.swift | 11 +- .../Rendering/RenderContentCompiler.swift | 13 +- .../Rendering/RenderContentConvertible.swift | 2 +- .../Model/Rendering/RenderContext.swift | 16 +- .../Rendering/RenderNodeTranslator.swift | 43 +-- .../Semantics/MarkupReferenceResolver.swift | 15 +- .../Semantics/ReferenceResolver.swift | 24 +- .../FilesAndFolders.swift | 24 +- .../Actions/Convert/ConvertAction.swift | 15 +- .../DocumentationContextConverterTests.swift | 20 +- .../Converter/RenderContextTests.swift | 5 +- .../Converter/RenderNodeCodableTests.swift | 6 +- .../TopicRenderReferenceEncoderTests.swift | 10 +- ...recatedDiagnosticsDigestWarningTests.swift | 8 +- .../Diagnostics/DiagnosticTests.swift | 4 +- .../Indexing/ExternalRenderNodeTests.swift | 30 +- .../Indexing/IndexingTests.swift | 18 +- .../Indexing/NavigatorIndexTests.swift | 78 ++-- .../Indexing/RenderIndexTests.swift | 16 +- .../Infrastructure/AnchorSectionTests.swift | 30 +- .../AutoCapitalizationTests.swift | 18 +- .../AutomaticCurationTests.swift | 74 ++-- .../DocumentationContextTests.swift | 41 +- .../DocumentationCuratorTests.swift | 42 +- .../ExternalPathHierarchyResolverTests.swift | 20 +- .../ExternalReferenceResolverTests.swift | 60 +-- .../Infrastructure/NodeTagsTests.swift | 10 +- .../ReferenceResolverTests.swift | 96 ++--- .../Infrastructure/SnippetResolverTests.swift | 2 +- .../SymbolBreadcrumbTests.swift | 4 +- .../SymbolDisambiguationTests.swift | 12 +- .../LinkDestinationSummaryTests.swift | 30 +- ...opertyListPossibleValuesSectionTests.swift | 12 +- .../Model/RenderContentMetadataTests.swift | 14 +- .../RenderHierarchyTranslatorTests.swift | 10 +- .../Model/RenderNodeDiffingBundleTests.swift | 18 +- .../Model/RenderNodeSerializationTests.swift | 32 +- .../SemaToRenderNodeMultiLanguageTests.swift | 10 +- .../Model/SemaToRenderNodeTests.swift | 362 +++++++++--------- ...OutOfProcessReferenceResolverV2Tests.swift | 6 +- .../Rendering/AutomaticSeeAlsoTests.swift | 30 +- .../AvailabilityRenderOrderTests.swift | 9 +- .../ConstraintsRenderSectionTests.swift | 48 +-- .../DeclarationsRenderSectionTests.swift | 22 +- .../Rendering/DefaultAvailabilityTests.swift | 28 +- .../DefaultCodeListingSyntaxTests.swift | 133 +++---- .../Rendering/DeprecationSummaryTests.swift | 52 +-- .../DocumentationContentRendererTests.swift | 4 +- .../Rendering/ExternalLinkTitleTests.swift | 4 +- .../Rendering/HeadingAnchorTests.swift | 6 +- .../MentionsRenderSectionTests.swift | 14 +- .../Rendering/PageKindTests.swift | 2 +- .../Rendering/PlatformAvailabilityTests.swift | 28 +- ...ropertyListDetailsRenderSectionTests.swift | 4 +- .../Rendering/RESTSymbolsTests.swift | 8 +- ...enderBlockContent_ThematicBreakTests.swift | 6 +- .../RenderContentCompilerTests.swift | 34 +- .../Rendering/RenderMetadataTests.swift | 22 +- ...derNodeTranslatorSymbolVariantsTests.swift | 17 +- .../Rendering/RenderNodeTranslatorTests.swift | 60 +-- .../SwiftDocCTests/Rendering/RoleTests.swift | 12 +- .../Rendering/SampleDownloadTests.swift | 6 +- .../Rendering/SymbolAvailabilityTests.swift | 6 +- .../Rendering/TermListTests.swift | 10 +- .../Semantics/DoxygenTests.swift | 6 +- .../MarkupReferenceResolverTests.swift | 4 +- .../Semantics/SnippetTests.swift | 8 +- .../Semantics/VideoMediaTests.swift | 8 +- .../TestRenderNodeOutputConsumer.swift | 4 +- .../Utility/ListItemExtractorTests.swift | 13 +- .../XCTestCase+LoadingTestData.swift | 30 +- .../ConvertActionIndexerTests.swift | 10 +- .../ConvertActionTests.swift | 4 +- .../MergeActionTests.swift | 12 +- 87 files changed, 1089 insertions(+), 1248 deletions(-) diff --git a/Sources/SwiftDocC/Converter/DocumentationContextConverter.swift b/Sources/SwiftDocC/Converter/DocumentationContextConverter.swift index 72e23665bb..ff9c438239 100644 --- a/Sources/SwiftDocC/Converter/DocumentationContextConverter.swift +++ b/Sources/SwiftDocC/Converter/DocumentationContextConverter.swift @@ -20,9 +20,6 @@ public class DocumentationContextConverter { /// The context the converter uses to resolve references it finds in the documentation node's content. let context: DocumentationContext - /// The bundle that contains the content from which the documentation node originated. - let bundle: DocumentationBundle - /// A context that contains common pre-rendered pieces of content. let renderContext: RenderContext @@ -43,12 +40,11 @@ public class DocumentationContextConverter { /// The remote source control repository where the documented module's source is hosted. let sourceRepository: SourceRepository? - /// Creates a new node converter for the given bundle and context. + /// Creates a new node converter for the given context. /// /// The converter uses bundle and context to resolve references to other documentation and describe the documentation hierarchy. /// /// - Parameters: - /// - bundle: The bundle that contains the content from which the documentation node originated. /// - context: The context that the converter uses to to resolve references it finds in the documentation node's content. /// - renderContext: A context that contains common pre-rendered pieces of content. /// - emitSymbolSourceFileURIs: Whether the documentation converter should include @@ -61,7 +57,6 @@ public class DocumentationContextConverter { /// - sourceRepository: The source repository where the documentation's sources are hosted. /// - symbolIdentifiersWithExpandedDocumentation: A list of symbol IDs that have version of their documentation page with more content that a renderer can link to. public init( - bundle: DocumentationBundle, context: DocumentationContext, renderContext: RenderContext, emitSymbolSourceFileURIs: Bool = false, @@ -69,7 +64,6 @@ public class DocumentationContextConverter { sourceRepository: SourceRepository? = nil, symbolIdentifiersWithExpandedDocumentation: [String]? = nil ) { - self.bundle = bundle self.context = context self.renderContext = renderContext self.shouldEmitSymbolSourceFileURIs = emitSymbolSourceFileURIs @@ -77,6 +71,25 @@ public class DocumentationContextConverter { self.sourceRepository = sourceRepository self.symbolIdentifiersWithExpandedDocumentation = symbolIdentifiersWithExpandedDocumentation } + @available(*, deprecated, renamed: "init(context:renderContext:emitSymbolSourceFileURIs:emitSymbolAccessLevels:sourceRepository:symbolIdentifiersWithExpandedDocumentation:)", message: "Use 'init(context:renderContext:emitSymbolSourceFileURIs:emitSymbolAccessLevels:sourceRepository:symbolIdentifiersWithExpandedDocumentation:)' instead. This deprecated API will be removed after 6.4 is released.") + public convenience init( + bundle _: DocumentationBundle, + context: DocumentationContext, + renderContext: RenderContext, + emitSymbolSourceFileURIs: Bool = false, + emitSymbolAccessLevels: Bool = false, + sourceRepository: SourceRepository? = nil, + symbolIdentifiersWithExpandedDocumentation: [String]? = nil + ) { + self.init( + context: context, + renderContext: renderContext, + emitSymbolSourceFileURIs: emitSymbolSourceFileURIs, + emitSymbolAccessLevels: emitSymbolAccessLevels, + sourceRepository: sourceRepository, + symbolIdentifiersWithExpandedDocumentation: symbolIdentifiersWithExpandedDocumentation + ) + } /// Converts a documentation node to a render node. /// @@ -91,7 +104,6 @@ public class DocumentationContextConverter { var translator = RenderNodeTranslator( context: context, - bundle: bundle, identifier: node.reference, renderContext: renderContext, emitSymbolSourceFileURIs: shouldEmitSymbolSourceFileURIs, diff --git a/Sources/SwiftDocC/Converter/DocumentationNodeConverter.swift b/Sources/SwiftDocC/Converter/DocumentationNodeConverter.swift index 77804359e8..51c19137b6 100644 --- a/Sources/SwiftDocC/Converter/DocumentationNodeConverter.swift +++ b/Sources/SwiftDocC/Converter/DocumentationNodeConverter.swift @@ -15,20 +15,19 @@ public struct DocumentationNodeConverter { /// The context the converter uses to resolve references it finds in the documentation node's content. let context: DocumentationContext - /// The bundle that contains the content from which the documentation node originated. - let bundle: DocumentationBundle - - /// Creates a new node converter for the given bundle and context. + /// Creates a new node converter for the given context. /// - /// The converter uses bundle and context to resolve references to other documentation and describe the documentation hierarchy. + /// The converter uses context to resolve references to other documentation and describe the documentation hierarchy. /// /// - Parameters: - /// - bundle: The bundle that contains the content from which the documentation node originated. /// - context: The context that the converter uses to to resolve references it finds in the documentation node's content. - public init(bundle: DocumentationBundle, context: DocumentationContext) { - self.bundle = bundle + public init(context: DocumentationContext) { self.context = context } + @available(*, deprecated, renamed: "init(context:)", message: "Use 'init(context:)' instead. This deprecated API will be removed after 6.4 is released.") + public init(bundle _: DocumentationBundle, context: DocumentationContext) { + self.init(context: context) + } /// Converts a documentation node to a render node. /// @@ -37,7 +36,7 @@ public struct DocumentationNodeConverter { /// - node: The documentation node to convert. /// - Returns: The render node representation of the documentation node. public func convert(_ node: DocumentationNode) -> RenderNode { - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) return translator.visit(node.semantic) as! RenderNode } } diff --git a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift index fc22a5c356..f55a9fe80b 100644 --- a/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift +++ b/Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift @@ -158,7 +158,7 @@ public struct ConvertService: DocumentationService { let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: configuration) // Precompute the render context - let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let renderContext = RenderContext(documentationContext: context) let symbolIdentifiersMeetingRequirementsForExpandedDocumentation: [String]? = request.symbolIdentifiersWithExpandedDocumentation?.compactMap { identifier, expandedDocsRequirement in guard let documentationNode = context.documentationCache[identifier] else { @@ -168,7 +168,6 @@ public struct ConvertService: DocumentationService { return documentationNode.meetsExpandedDocumentationRequirements(expandedDocsRequirement) ? identifier : nil } let converter = DocumentationContextConverter( - bundle: bundle, context: context, renderContext: renderContext, emitSymbolSourceFileURIs: request.emitSymbolSourceFileURIs, @@ -243,12 +242,11 @@ public struct ConvertService: DocumentationService { .compactMap { (value, isDocumentationExtensionContent) -> (ResolvedTopicReference, RenderReferenceStore.TopicContent)? in let (topicReference, article) = value - let bundle = context.bundle - guard bundle.id == topicReference.bundleID else { return nil } - let renderer = DocumentationContentRenderer(documentationContext: context, bundle: bundle) + guard context.inputs.id == topicReference.bundleID else { return nil } + let renderer = DocumentationContentRenderer(context: context) let documentationNodeKind: DocumentationNode.Kind = isDocumentationExtensionContent ? .unknownSymbol : .article - let overridingDocumentationNode = DocumentationContext.documentationNodeAndTitle(for: article, kind: documentationNodeKind, in: bundle)?.node + let overridingDocumentationNode = DocumentationContext.documentationNodeAndTitle(for: article, kind: documentationNodeKind, in: context.inputs)?.node var dependencies = RenderReferenceDependencies() let renderReference = renderer.renderReference(for: topicReference, with: overridingDocumentationNode, dependencies: &dependencies) diff --git a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift index 17a5db0a70..1673f7b33d 100644 --- a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift +++ b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift @@ -21,10 +21,9 @@ package enum ConvertActionConverter { static package let signposter = NoOpSignposterShim() #endif - /// Converts the documentation bundle in the given context and passes its output to a given consumer. + /// Converts the documentation in the given context and passes its output to a given consumer. /// /// - Parameters: - /// - bundle: The documentation bundle to convert. /// - context: The context that the bundle is a part of. /// - outputConsumer: The consumer that the conversion passes outputs of the conversion to. /// - sourceRepository: The source repository where the documentation's sources are hosted. @@ -32,7 +31,6 @@ package enum ConvertActionConverter { /// - documentationCoverageOptions: The level of experimental documentation coverage information that the conversion should pass to the consumer. /// - Returns: A list of problems that occurred during the conversion (excluding the problems that the context already encountered). package static func convert( - bundle: DocumentationBundle, context: DocumentationContext, outputConsumer: some ConvertOutputConsumer & ExternalNodeConsumer, sourceRepository: SourceRepository?, @@ -61,15 +59,14 @@ package enum ConvertActionConverter { // Precompute the render context let renderContext = signposter.withIntervalSignpost("Build RenderContext", id: signposter.makeSignpostID()) { - RenderContext(documentationContext: context, bundle: bundle) + RenderContext(documentationContext: context) } try outputConsumer.consume(renderReferenceStore: renderContext.store) // Copy images, sample files, and other static assets. - try outputConsumer.consume(assetsInBundle: bundle) + try outputConsumer.consume(assetsInBundle: context.inputs) let converter = DocumentationContextConverter( - bundle: bundle, context: context, renderContext: renderContext, sourceRepository: sourceRepository @@ -109,7 +106,7 @@ package enum ConvertActionConverter { // Here we're associating the external node with the **current** bundle's bundle ID. // This is needed because nodes are only considered children if the parent and child's bundle ID match. // Otherwise, the node will be considered as a separate root node and displayed separately. - let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: context.inputs.id) try outputConsumer.consume(externalRenderNode: externalRenderNode) } @@ -192,7 +189,7 @@ package enum ConvertActionConverter { if FeatureFlags.current.isExperimentalLinkHierarchySerializationEnabled { signposter.withIntervalSignpost("Serialize link hierarchy", id: signposter.makeSignpostID()) { do { - let serializableLinkInformation = try context.linkResolver.localResolver.prepareForSerialization(bundleID: bundle.id) + let serializableLinkInformation = try context.linkResolver.localResolver.prepareForSerialization(bundleID: context.inputs.id) try outputConsumer.consume(linkResolutionInformation: serializableLinkInformation) if !emitDigest { @@ -225,7 +222,7 @@ package enum ConvertActionConverter { break } - try outputConsumer.consume(buildMetadata: BuildMetadata(bundleDisplayName: bundle.displayName, bundleID: bundle.id)) + try outputConsumer.consume(buildMetadata: BuildMetadata(bundleDisplayName: context.inputs.displayName, bundleID: context.inputs.id)) // Log the finalized topic graph checksum. benchmark(add: Benchmark.TopicGraphHash(context: context)) diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 03d7b834c4..304cc6a980 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -70,8 +70,8 @@ public class DocumentationContext { /// The data provider that the context can use to read the contents of files that belong to ``bundle``. let dataProvider: any DataProvider - /// The documentation bundle that is registered with the context. - let bundle: DocumentationBundle + /// The collection of input files that the context was created from. + let inputs: DocumentationContext.Inputs /// A collection of configuration for this context. public let configuration: Configuration @@ -206,32 +206,19 @@ public class DocumentationContext { /// - configuration: A collection of configuration for the created context. /// - Throws: If an error is encountered while registering a documentation bundle. package init( - bundle: DocumentationBundle, + bundle inputs: DocumentationBundle, dataProvider: any DataProvider, diagnosticEngine: DiagnosticEngine = .init(), configuration: Configuration = .init() ) async throws { - self.bundle = bundle + self.inputs = inputs self.dataProvider = dataProvider self.diagnosticEngine = diagnosticEngine self.configuration = configuration self.linkResolver = LinkResolver(dataProvider: dataProvider) - ResolvedTopicReference.enableReferenceCaching(for: bundle.id) - try register(bundle) - } - - // Remove these when removing `registeredBundles` and `bundle(identifier:)`. - // These exist so that internal code that need to be compatible with legacy data providers can access the bundles without deprecation warnings. - @available(*, deprecated, renamed: "bundle", message: "REMOVE THIS") - var _registeredBundles: [DocumentationBundle] { - [bundle] - } - - @available(*, deprecated, renamed: "bundle", message: "REMOVE THIS") - func _bundle(identifier: String) -> DocumentationBundle? { - assert(bundle.id.rawValue == identifier, "New code shouldn't pass unknown bundle identifiers to 'DocumentationContext.bundle(identifier:)'.") - return bundle.id.rawValue == identifier ? bundle : nil + ResolvedTopicReference.enableReferenceCaching(for: inputs.id) + try register() } /// Perform semantic analysis on a given `document` at a given `source` location and append any problems found to `problems`. @@ -239,11 +226,10 @@ public class DocumentationContext { /// - Parameters: /// - document: The document to analyze. /// - source: The location of the document. - /// - bundle: The bundle that the document belongs to. /// - problems: A mutable collection of problems to update with any problem encountered during the semantic analysis. /// - Returns: The result of the semantic analysis. - private func analyze(_ document: Document, at source: URL, in bundle: DocumentationBundle, engine: DiagnosticEngine) -> Semantic? { - var analyzer = SemanticAnalyzer(source: source, bundle: bundle) + private func analyze(_ document: Document, at source: URL, engine: DiagnosticEngine) -> Semantic? { + var analyzer = SemanticAnalyzer(source: source, bundle: inputs) let result = analyzer.visit(document) engine.emit(analyzer.problems) return result @@ -319,12 +305,11 @@ public class DocumentationContext { /// /// - Parameters: /// - references: A list of references to local nodes to visit to collect links. - /// - localBundleID: The local bundle ID, used to identify and skip absolute fully qualified local links. - private func preResolveExternalLinks(references: [ResolvedTopicReference], localBundleID: DocumentationBundle.Identifier) { + private func preResolveExternalLinks(references: [ResolvedTopicReference]) { preResolveExternalLinks(semanticObjects: references.compactMap({ reference -> ReferencedSemanticObject? in guard let node = try? entity(with: reference), let semantic = node.semantic else { return nil } return (reference: reference, semantic: semantic) - }), localBundleID: localBundleID) + })) } /// A tuple of a semantic object and its reference in the topic graph. @@ -341,8 +326,7 @@ public class DocumentationContext { /// /// - Parameters: /// - semanticObjects: A list of semantic objects to visit to collect links. - /// - localBundleID: The local bundle ID, used to identify and skip absolute fully qualified local links. - private func preResolveExternalLinks(semanticObjects: [ReferencedSemanticObject], localBundleID: DocumentationBundle.Identifier) { + private func preResolveExternalLinks(semanticObjects: [ReferencedSemanticObject]) { // If there are no external resolvers added we will not resolve any links. guard !configuration.externalDocumentationConfiguration.sources.isEmpty else { return } @@ -350,7 +334,7 @@ public class DocumentationContext { semanticObjects.concurrentPerform { _, semantic in autoreleasepool { // Walk the node and extract external link references. - var externalLinksCollector = ExternalReferenceWalker(localBundleID: localBundleID) + var externalLinksCollector = ExternalReferenceWalker(localBundleID: inputs.id) externalLinksCollector.visit(semantic) // Avoid any synchronization overhead if there are no references to add. @@ -399,7 +383,7 @@ public class DocumentationContext { /** Attempt to resolve links in curation-only documentation, converting any ``TopicReferences`` from `.unresolved` to `.resolved` where possible. */ - private func resolveLinks(curatedReferences: Set, bundle: DocumentationBundle) { + private func resolveLinks(curatedReferences: Set) { let signpostHandle = signposter.beginInterval("Resolve links", id: signposter.makeSignpostID()) defer { signposter.endInterval("Resolve links", signpostHandle) @@ -445,7 +429,7 @@ public class DocumentationContext { return } - var resolver = ReferenceResolver(context: self, bundle: bundle, rootReference: reference, inheritanceParentReference: symbolOriginReference) + var resolver = ReferenceResolver(context: self, rootReference: reference, inheritanceParentReference: symbolOriginReference) // Update the node with the markup that contains resolved references instead of authored links. documentationNode.semantic = autoreleasepool { @@ -459,7 +443,7 @@ public class DocumentationContext { for alternateRepresentation in alternateRepresentations { let resolutionResult = resolver.resolve( alternateRepresentation.reference, - in: bundle.rootReference, + in: inputs.rootReference, range: alternateRepresentation.originalMarkup.range, severity: .warning ) @@ -556,12 +540,10 @@ public class DocumentationContext { /// - tutorialTableOfContentsResults: The list of temporary 'tutorial table-of-contents' pages. /// - tutorials: The list of temporary 'tutorial' pages. /// - tutorialArticles: The list of temporary 'tutorialArticle' pages. - /// - bundle: The bundle to resolve links against. private func resolveLinks( tutorialTableOfContents tutorialTableOfContentsResults: [SemanticResult], tutorials: [SemanticResult], - tutorialArticles: [SemanticResult], - bundle: DocumentationBundle + tutorialArticles: [SemanticResult] ) { let signpostHandle = signposter.beginInterval("Resolve links", id: signposter.makeSignpostID()) defer { @@ -575,7 +557,7 @@ public class DocumentationContext { for tableOfContentsResult in tutorialTableOfContentsResults { autoreleasepool { let url = tableOfContentsResult.source - var resolver = ReferenceResolver(context: self, bundle: bundle) + var resolver = ReferenceResolver(context: self) let tableOfContents = resolver.visit(tableOfContentsResult.value) as! TutorialTableOfContents diagnosticEngine.emit(resolver.problems) @@ -647,7 +629,7 @@ public class DocumentationContext { autoreleasepool { let url = tutorialResult.source let unresolvedTutorial = tutorialResult.value - var resolver = ReferenceResolver(context: self, bundle: bundle) + var resolver = ReferenceResolver(context: self) let tutorial = resolver.visit(unresolvedTutorial) as! Tutorial diagnosticEngine.emit(resolver.problems) @@ -681,7 +663,7 @@ public class DocumentationContext { autoreleasepool { let url = articleResult.source let unresolvedTutorialArticle = articleResult.value - var resolver = ReferenceResolver(context: self, bundle: bundle) + var resolver = ReferenceResolver(context: self) let article = resolver.visit(unresolvedTutorialArticle) as! TutorialArticle diagnosticEngine.emit(resolver.problems) @@ -712,7 +694,7 @@ public class DocumentationContext { // Articles are resolved in a separate pass } - private func registerDocuments(from bundle: DocumentationBundle) throws -> ( + private func registerDocuments() throws -> ( tutorialTableOfContentsResults: [SemanticResult], tutorials: [SemanticResult], tutorialArticles: [SemanticResult], @@ -732,7 +714,7 @@ public class DocumentationContext { let decodeError = Synchronized<(any Error)?>(nil) // Load and analyze documents concurrently - let analyzedDocuments: [(URL, Semantic)] = bundle.markupURLs.concurrentPerform { url, results in + let analyzedDocuments: [(URL, Semantic)] = inputs.markupURLs.concurrentPerform { url, results in guard decodeError.sync({ $0 == nil }) else { return } do { @@ -747,7 +729,7 @@ public class DocumentationContext { diagnosticEngine.emit(langChecker.problems) } - guard let analyzed = analyze(document, at: url, in: bundle, engine: diagnosticEngine) else { + guard let analyzed = analyze(document, at: url, engine: diagnosticEngine) else { return } @@ -774,8 +756,8 @@ public class DocumentationContext { // Store the references we encounter to ensure they're unique. The file name is currently the only part of the URL considered for the topic reference, so collisions may occur. let (url, analyzed) = analyzedDocument - let path = NodeURLGenerator.pathForSemantic(analyzed, source: url, bundle: bundle) - let reference = ResolvedTopicReference(bundleID: bundle.id, path: path, sourceLanguage: .swift) + let path = NodeURLGenerator.pathForSemantic(analyzed, source: url, bundle: inputs) + let reference = ResolvedTopicReference(bundleID: inputs.id, path: path, sourceLanguage: .swift) // Since documentation extensions' filenames have no impact on the URL of pages, there is no need to enforce unique filenames for them. // At this point we consider all articles with an H1 containing link a "documentation extension." @@ -899,8 +881,7 @@ public class DocumentationContext { private func nodeWithInitializedContent( reference: ResolvedTopicReference, - match foundDocumentationExtension: DocumentationContext.SemanticResult
?, - bundle: DocumentationBundle + match foundDocumentationExtension: DocumentationContext.SemanticResult
? ) -> DocumentationNode { guard var updatedNode = documentationCache[reference] else { fatalError("A topic reference that has already been resolved should always exist in the cache.") @@ -910,7 +891,7 @@ public class DocumentationContext { updatedNode.initializeSymbolContent( documentationExtension: foundDocumentationExtension?.value, engine: diagnosticEngine, - bundle: bundle + bundle: inputs ) // After merging the documentation extension into the symbol, warn about deprecation summary for non-deprecated symbols. @@ -1001,12 +982,8 @@ public class DocumentationContext { diagnosticEngine.emit(result.problems) } - /// Loads all graph files from a given `bundle` and merges them together while building the symbol relationships and loading any available markdown documentation for those symbols. - /// - /// - Parameter bundle: The bundle to load symbol graph files from. - /// - Returns: A pair of the references to all loaded modules and the hierarchy of all the loaded symbol's references. + /// Loads all graph files from the context's inputs and merges them together while building the symbol relationships and loading any available markdown documentation for those symbols. private func registerSymbols( - from bundle: DocumentationBundle, symbolGraphLoader: SymbolGraphLoader, documentationExtensions: [SemanticResult
] ) throws { @@ -1028,7 +1005,7 @@ public class DocumentationContext { // Build references for all symbols in all of this module's symbol graphs. let symbolReferences = signposter.withIntervalSignpost("Disambiguate references") { - linkResolver.localResolver.referencesForSymbols(in: symbolGraphLoader.unifiedGraphs, bundle: bundle, context: self) + linkResolver.localResolver.referencesForSymbols(in: symbolGraphLoader.unifiedGraphs, context: self) } // Set the index and cache storage capacity to avoid ad-hoc storage resizing. @@ -1083,7 +1060,7 @@ public class DocumentationContext { // Use the default module kind for this bundle if one was provided, // otherwise fall back to 'Framework' - let moduleKindDisplayName = bundle.info.defaultModuleKind ?? "Framework" + let moduleKindDisplayName = inputs.info.defaultModuleKind ?? "Framework" let moduleSymbol = SymbolGraph.Symbol( identifier: moduleIdentifier, names: SymbolGraph.Symbol.Names(title: moduleName, navigator: nil, subHeading: nil, prose: nil), @@ -1093,7 +1070,7 @@ public class DocumentationContext { kind: SymbolGraph.Symbol.Kind(parsedIdentifier: .module, displayName: moduleKindDisplayName), mixins: [:]) let moduleSymbolReference = SymbolReference(moduleName, interfaceLanguages: moduleInterfaceLanguages, defaultSymbol: moduleSymbol) - moduleReference = ResolvedTopicReference(symbolReference: moduleSymbolReference, moduleName: moduleName, bundle: bundle) + moduleReference = ResolvedTopicReference(symbolReference: moduleSymbolReference, moduleName: moduleName, bundle: inputs) signposter.withIntervalSignpost("Add symbols to topic graph", id: signposter.makeSignpostID()) { addSymbolsToTopicGraph(symbolGraph: unifiedSymbolGraph, url: fileURL, symbolReferences: symbolReferences, moduleReference: moduleReference) @@ -1184,7 +1161,7 @@ public class DocumentationContext { // FIXME: Resolve the link relative to the module https://github.com/swiftlang/swift-docc/issues/516 let reference = TopicReference.unresolved(.init(topicURL: url)) - switch resolve(reference, in: bundle.rootReference, fromSymbolLink: true) { + switch resolve(reference, in: inputs.rootReference, fromSymbolLink: true) { case .success(let resolved): if let existing = uncuratedDocumentationExtensions[resolved] { if symbolsWithMultipleDocumentationExtensionMatches[resolved] == nil { @@ -1242,27 +1219,19 @@ public class DocumentationContext { symbolsWithMultipleDocumentationExtensionMatches.removeAll() // Create inherited API collections - try GeneratedDocumentationTopics.createInheritedSymbolsAPICollections( - relationships: uniqueRelationships, - context: self, - bundle: bundle - ) + try GeneratedDocumentationTopics.createInheritedSymbolsAPICollections(relationships: uniqueRelationships, context: self) // Parse and prepare the nodes' content concurrently. let updatedNodes = signposter.withIntervalSignpost("Parse symbol markup", id: signposter.makeSignpostID()) { Array(documentationCache.symbolReferences).concurrentMap { finalReference in // Match the symbol's documentation extension and initialize the node content. let match = uncuratedDocumentationExtensions[finalReference] - let updatedNode = nodeWithInitializedContent( - reference: finalReference, - match: match, - bundle: bundle - ) + let updatedNode = nodeWithInitializedContent(reference: finalReference, match: match) - return (( + return ( node: updatedNode, matchedArticleURL: match?.source - )) + ) } } @@ -1288,14 +1257,14 @@ public class DocumentationContext { } // Resolve any external references first - preResolveExternalLinks(references: Array(moduleReferences.values) + combinedSymbols.keys.compactMap({ documentationCache.reference(symbolID: $0) }), localBundleID: bundle.id) + preResolveExternalLinks(references: Array(moduleReferences.values) + combinedSymbols.keys.compactMap({ documentationCache.reference(symbolID: $0) })) // Look up and add symbols that are _referenced_ in the symbol graph but don't exist in the symbol graph. try resolveExternalSymbols(in: combinedSymbols, relationships: combinedRelationshipsBySelector) for (selector, relationships) in combinedRelationshipsBySelector { // Build relationships in the completed graph - buildRelationships(relationships, selector: selector, bundle: bundle) + buildRelationships(relationships, selector: selector) // Merge into target symbols the member symbols that get rendered on the same page as target. populateOnPageMemberRelationships(from: relationships, selector: selector) } @@ -1310,16 +1279,11 @@ public class DocumentationContext { /// /// - Parameters: /// - symbolGraph: The symbol graph whose symbols to add in-memory relationships to. - /// - bundle: The bundle that the symbols belong to. - /// - problems: A mutable collection of problems to update with any problem encountered while building symbol relationships. + /// - selector: The platform and language selector to build relationships for. /// /// ## See Also /// - ``SymbolGraphRelationshipsBuilder`` - func buildRelationships( - _ relationships: Set, - selector: UnifiedSymbolGraph.Selector, - bundle: DocumentationBundle - ) { + func buildRelationships(_ relationships: Set, selector: UnifiedSymbolGraph.Selector) { // Find all of the relationships which refer to an extended module. let extendedModuleRelationships = ExtendedTypeFormatTransformation.collapsedExtendedModuleRelationships(from: relationships) @@ -1338,7 +1302,7 @@ public class DocumentationContext { SymbolGraphRelationshipsBuilder.addConformanceRelationship( edge: edge, selector: selector, - in: bundle, + in: inputs, localCache: documentationCache, externalCache: externalCache, engine: diagnosticEngine @@ -1348,7 +1312,7 @@ public class DocumentationContext { SymbolGraphRelationshipsBuilder.addImplementationRelationship( edge: edge, selector: selector, - in: bundle, + in: inputs, context: self, localCache: documentationCache, engine: diagnosticEngine @@ -1358,7 +1322,7 @@ public class DocumentationContext { SymbolGraphRelationshipsBuilder.addInheritanceRelationship( edge: edge, selector: selector, - in: bundle, + in: inputs, localCache: documentationCache, externalCache: externalCache, engine: diagnosticEngine @@ -1650,9 +1614,9 @@ public class DocumentationContext { } } - private func registerMiscResources(from bundle: DocumentationBundle) throws { - let miscResources = Set(bundle.miscResourceURLs) - try assetManagers[bundle.id, default: DataAssetManager()].register(data: miscResources) + private func registerMiscResources() throws { + let miscResources = Set(inputs.miscResourceURLs) + try assetManagers[inputs.id, default: DataAssetManager()].register(data: miscResources) } private func registeredAssets(withExtensions extensions: Set? = nil, inContexts contexts: [DataAsset.Context] = DataAsset.Context.allCases, forBundleID bundleID: DocumentationBundle.Identifier) -> [DataAsset] { @@ -1709,11 +1673,11 @@ public class DocumentationContext { } } - private func registerRootPages(from articles: Articles, in bundle: DocumentationBundle) { + private func registerRootPages(from articles: Articles) { // Create a root leaf node for all root page articles for article in articles { // Create the documentation data - guard let (documentation, title) = DocumentationContext.documentationNodeAndTitle(for: article, kind: .collection, in: bundle) else { continue } + guard let (documentation, title) = Self.documentationNodeAndTitle(for: article, kind: .collection, in: inputs) else { continue } let reference = documentation.reference // Create the documentation node @@ -1742,21 +1706,17 @@ public class DocumentationContext { /// /// - Parameters: /// - articles: Articles to register with the documentation cache. - /// - bundle: The bundle containing the articles. /// - Returns: The articles that were registered, with their topic graph node updated to what's been added to the topic graph. - private func registerArticles( - _ articles: DocumentationContext.Articles, - in bundle: DocumentationBundle - ) -> DocumentationContext.Articles { + private func registerArticles(_ articles: DocumentationContext.Articles) -> DocumentationContext.Articles { articles.map { article in - guard let (documentation, title) = DocumentationContext.documentationNodeAndTitle( + guard let (documentation, title) = Self.documentationNodeAndTitle( for: article, // By default, articles are available in the languages the module that's being documented // is available in. It's possible to override that behavior using the `@SupportedLanguage` // directive though; see its documentation for more details. availableSourceLanguages: soleRootModuleReference.map { sourceLanguages(for: $0) }, kind: .article, - in: bundle + in: inputs ) else { return article } @@ -1786,9 +1746,8 @@ public class DocumentationContext { /// /// - Parameters: /// - articles: On input, a list of articles. If an article is used as a root it is removed from this list. - /// - bundle: The bundle containing the articles. - private func synthesizeArticleOnlyRootPage(articles: inout [DocumentationContext.SemanticResult
], bundle: DocumentationBundle) { - let title = bundle.displayName + private func synthesizeArticleOnlyRootPage(articles: inout [DocumentationContext.SemanticResult
]) { + let title = inputs.displayName // An inner helper function to register a new root node from an article func registerAsNewRootNode(_ articleResult: SemanticResult
) { @@ -1796,7 +1755,7 @@ public class DocumentationContext { let title = articleResult.source.deletingPathExtension().lastPathComponent // Create a new root-looking reference let reference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: inputs.id, path: NodeURLGenerator.Path.documentation(path: title).stringValue, sourceLanguages: [DocumentationContext.defaultLanguage(in: nil /* article-only content has no source language information */)] ) @@ -1815,13 +1774,13 @@ public class DocumentationContext { } let article = Article( markup: articleResult.value.markup, - metadata: Metadata(from: metadataMarkup, for: bundle), + metadata: Metadata(from: metadataMarkup, for: inputs), redirects: articleResult.value.redirects, options: articleResult.value.options ) let graphNode = TopicGraph.Node(reference: reference, kind: .module, source: articleResult.topicGraphNode.source, title: title) - registerRootPages(from: [.init(value: article, source: articleResult.source, topicGraphNode: graphNode)], in: bundle) + registerRootPages(from: [.init(value: article, source: articleResult.source, topicGraphNode: graphNode)]) } if articles.count == 1 { @@ -1835,7 +1794,7 @@ public class DocumentationContext { let path = NodeURLGenerator.Path.documentation(path: title).stringValue let sourceLanguage = DocumentationContext.defaultLanguage(in: []) - let reference = ResolvedTopicReference(bundleID: bundle.id, path: path, sourceLanguages: [sourceLanguage]) + let reference = ResolvedTopicReference(bundleID: inputs.id, path: path, sourceLanguages: [sourceLanguage]) let graphNode = TopicGraph.Node(reference: reference, kind: .module, source: .external, title: title) topicGraph.addNode(graphNode) @@ -1848,7 +1807,7 @@ public class DocumentationContext { Heading(level: 1, Text(title)), metadataDirectiveMarkup ) - let metadata = Metadata(from: metadataDirectiveMarkup, for: bundle) + let metadata = Metadata(from: metadataDirectiveMarkup, for: inputs) let article = Article(markup: markup, metadata: metadata, redirects: nil, options: [:]) let documentationNode = DocumentationNode( reference: reference, @@ -1868,19 +1827,19 @@ public class DocumentationContext { /// - Parameters: /// - article: The article that will be used to create the returned documentation node. /// - kind: The kind that should be used to create the returned documentation node. - /// - bundle: The documentation bundle this article belongs to. + /// - inputs: The collection of inputs files that the article belongs to. /// - Returns: A documentation node and title for the given article semantic result. static func documentationNodeAndTitle( for article: DocumentationContext.SemanticResult
, availableSourceLanguages: Set? = nil, kind: DocumentationNode.Kind, - in bundle: DocumentationBundle + in inputs: DocumentationBundle ) -> (node: DocumentationNode, title: String)? { guard let articleMarkup = article.value.markup else { return nil } - let path = NodeURLGenerator.pathForSemantic(article.value, source: article.source, bundle: bundle) + let path = NodeURLGenerator.pathForSemantic(article.value, source: article.source, bundle: inputs) // Use the languages specified by the `@SupportedLanguage` directives if present. let availableSourceLanguages = article.value @@ -1900,7 +1859,7 @@ public class DocumentationContext { let defaultSourceLanguage = defaultLanguage(in: availableSourceLanguages) let reference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: inputs.id, path: path, sourceLanguages: availableSourceLanguages // FIXME: Pages in article-only catalogs should not be inferred as "Swift" as a fallback @@ -1978,11 +1937,11 @@ public class DocumentationContext { /** Register a documentation bundle with this context. */ - private func register(_ bundle: DocumentationBundle) throws { + private func register() throws { try shouldContinueRegistration() let currentFeatureFlags: FeatureFlags? - if let bundleFlags = bundle.info.featureFlags { + if let bundleFlags = inputs.info.featureFlags { currentFeatureFlags = FeatureFlags.current FeatureFlags.current.loadFlagsFromBundle(bundleFlags) @@ -2025,7 +1984,7 @@ public class DocumentationContext { discoveryGroup.async(queue: discoveryQueue) { [unowned self] in symbolGraphLoader = SymbolGraphLoader( - bundle: bundle, + bundle: inputs, dataProvider: dataProvider, symbolGraphTransformer: configuration.convertServiceConfiguration.symbolGraphTransformer ) @@ -2037,7 +1996,7 @@ public class DocumentationContext { hierarchyBasedResolver = signposter.withIntervalSignpost("Build PathHierarchy", id: signposter.makeSignpostID()) { PathHierarchyBasedLinkResolver(pathHierarchy: PathHierarchy( symbolGraphLoader: symbolGraphLoader, - bundleName: urlReadablePath(bundle.displayName), + bundleName: urlReadablePath(inputs.displayName), knownDisambiguatedPathComponents: configuration.convertServiceConfiguration.knownDisambiguatedSymbolPathComponents )) } @@ -2055,7 +2014,7 @@ public class DocumentationContext { discoveryGroup.async(queue: discoveryQueue) { [unowned self] in do { try signposter.withIntervalSignpost("Load resources", id: signposter.makeSignpostID()) { - try self.registerMiscResources(from: bundle) + try self.registerMiscResources() } } catch { // Pipe the error out of the dispatch queue. @@ -2081,7 +2040,7 @@ public class DocumentationContext { discoveryGroup.async(queue: discoveryQueue) { [unowned self] in do { result = try signposter.withIntervalSignpost("Load documents", id: signposter.makeSignpostID()) { - try self.registerDocuments(from: bundle) + try self.registerDocuments() } } catch { // Pipe the error out of the dispatch queue. @@ -2155,7 +2114,7 @@ public class DocumentationContext { } self.linkResolver.localResolver = hierarchyBasedResolver - hierarchyBasedResolver.addMappingForRoots(bundle: bundle) + hierarchyBasedResolver.addMappingForRoots(bundle: inputs) for tutorial in tutorials { hierarchyBasedResolver.addTutorial(tutorial) } @@ -2166,8 +2125,8 @@ public class DocumentationContext { hierarchyBasedResolver.addTutorialTableOfContents(tutorialTableOfContents) } - registerRootPages(from: rootPageArticles, in: bundle) - try registerSymbols(from: bundle, symbolGraphLoader: symbolGraphLoader, documentationExtensions: documentationExtensions) + registerRootPages(from: rootPageArticles) + try registerSymbols(symbolGraphLoader: symbolGraphLoader, documentationExtensions: documentationExtensions) // We don't need to keep the loader in memory after we've registered all symbols. symbolGraphLoader = nil @@ -2177,7 +2136,7 @@ public class DocumentationContext { !otherArticles.isEmpty, !configuration.convertServiceConfiguration.allowsRegisteringArticlesWithoutTechnologyRoot { - synthesizeArticleOnlyRootPage(articles: &otherArticles, bundle: bundle) + synthesizeArticleOnlyRootPage(articles: &otherArticles) } // Keep track of the root modules registered from symbol graph files, we'll need them to automatically @@ -2192,7 +2151,7 @@ public class DocumentationContext { // Articles that will be automatically curated can be resolved but they need to be pre registered before resolving links. let rootNodeForAutomaticCuration = soleRootModuleReference.flatMap(topicGraph.nodeWithReference(_:)) if configuration.convertServiceConfiguration.allowsRegisteringArticlesWithoutTechnologyRoot || rootNodeForAutomaticCuration != nil { - otherArticles = registerArticles(otherArticles, in: bundle) + otherArticles = registerArticles(otherArticles) try shouldContinueRegistration() } @@ -2200,14 +2159,12 @@ public class DocumentationContext { preResolveExternalLinks(semanticObjects: tutorialTableOfContentsResults.map(referencedSemanticObject) + tutorials.map(referencedSemanticObject) + - tutorialArticles.map(referencedSemanticObject), - localBundleID: bundle.id) + tutorialArticles.map(referencedSemanticObject)) resolveLinks( tutorialTableOfContents: tutorialTableOfContentsResults, tutorials: tutorials, - tutorialArticles: tutorialArticles, - bundle: bundle + tutorialArticles: tutorialArticles ) // After the resolving links in tutorial content all the local references are known and can be added to the referenceIndex for fast lookup. @@ -2220,7 +2177,7 @@ public class DocumentationContext { } try shouldContinueRegistration() - var allCuratedReferences = try crawlSymbolCuration(in: linkResolver.localResolver.topLevelSymbols(), bundle: bundle) + var allCuratedReferences = try crawlSymbolCuration(in: linkResolver.localResolver.topLevelSymbols()) // Store the list of manually curated references if doc coverage is on. if configuration.experimentalCoverageConfiguration.shouldStoreManuallyCuratedReferences { @@ -2235,13 +2192,13 @@ public class DocumentationContext { } // Crawl the rest of the symbols that haven't been crawled so far in hierarchy pre-order. - allCuratedReferences = try crawlSymbolCuration(in: automaticallyCurated.map(\.symbol), bundle: bundle, initial: allCuratedReferences) + allCuratedReferences = try crawlSymbolCuration(in: automaticallyCurated.map(\.symbol), initial: allCuratedReferences) // Automatically curate articles that haven't been manually curated // Article curation is only done automatically if there is only one root module if let rootNode = rootNodeForAutomaticCuration { let articleReferences = try autoCurateArticles(otherArticles, startingFrom: rootNode) - allCuratedReferences = try crawlSymbolCuration(in: articleReferences, bundle: bundle, initial: allCuratedReferences) + allCuratedReferences = try crawlSymbolCuration(in: articleReferences, initial: allCuratedReferences) } // Remove curation paths that have been created automatically above @@ -2259,14 +2216,14 @@ public class DocumentationContext { linkResolver.localResolver.addAnchorForSymbols(localCache: documentationCache) // Fifth, resolve links in nodes that are added solely via curation - preResolveExternalLinks(references: Array(allCuratedReferences), localBundleID: bundle.id) - resolveLinks(curatedReferences: allCuratedReferences, bundle: bundle) + preResolveExternalLinks(references: Array(allCuratedReferences)) + resolveLinks(curatedReferences: allCuratedReferences) if configuration.convertServiceConfiguration.fallbackResolver != nil { // When the ``ConvertService`` builds documentation for a single page there won't be a module or root // reference to auto-curate the page under, so the regular local link resolution code path won't visit // the single page. To ensure that links are resolved, explicitly visit all pages. - resolveLinks(curatedReferences: Set(knownPages), bundle: bundle) + resolveLinks(curatedReferences: Set(knownPages)) } // We should use a read-only context during render time (rdar://65130130). @@ -2464,17 +2421,16 @@ public class DocumentationContext { /// Crawls the hierarchy of the given list of nodes, adding relationships in the topic graph for all resolvable task group references. /// - Parameters: /// - references: A list of references to crawl. - /// - bundle: A documentation bundle. /// - initial: A list of references to skip when crawling. /// - Returns: The references of all the symbols that were curated. @discardableResult - func crawlSymbolCuration(in references: [ResolvedTopicReference], bundle: DocumentationBundle, initial: Set = []) throws -> Set { + func crawlSymbolCuration(in references: [ResolvedTopicReference], initial: Set = []) throws -> Set { let signpostHandle = signposter.beginInterval("Curate symbols", id: signposter.makeSignpostID()) defer { signposter.endInterval("Curate symbols", signpostHandle) } - var crawler = DocumentationCurator(in: self, bundle: bundle, initial: initial) + var crawler = DocumentationCurator(in: self, initial: initial) for reference in references { try crawler.crawlChildren( @@ -2643,21 +2599,6 @@ public class DocumentationContext { analyzeTopicGraph() } - /** - Unregister a documentation bundle with this context and clear any cached resources associated with it. - */ - private func unregister(_ bundle: DocumentationBundle) { - let referencesToRemove = topicGraph.nodes.keys.filter { - $0.bundleID == bundle.id - } - - for reference in referencesToRemove { - topicGraph.edges[reference]?.removeAll(where: { $0.bundleID == bundle.id }) - topicGraph.reverseEdges[reference]?.removeAll(where: { $0.bundleID == bundle.id }) - topicGraph.nodes[reference] = nil - } - } - // MARK: - Getting documentation relationships /** diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift b/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift index 1519965672..b470886fdc 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationCurator.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 @@ -17,14 +17,10 @@ struct DocumentationCurator { /// The documentation context to crawl. private let context: DocumentationContext - /// The current bundle. - private let bundle: DocumentationBundle - private(set) var problems = [Problem]() - init(in context: DocumentationContext, bundle: DocumentationBundle, initial: Set = []) { + init(in context: DocumentationContext, initial: Set = []) { self.context = context - self.bundle = bundle self.curatedNodes = initial } @@ -99,7 +95,7 @@ struct DocumentationCurator { // - "documentation/CatalogName/ArticleName" switch path.components(separatedBy: "/").count { case 0,1: - return NodeURLGenerator.Path.article(bundleName: bundle.displayName, articleName: path).stringValue + return NodeURLGenerator.Path.article(bundleName: context.inputs.displayName, articleName: path).stringValue case 2: return "\(NodeURLGenerator.Path.documentationFolder)/\(path)" default: diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift index de1a3bbbf0..d7a5f331cf 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift @@ -59,8 +59,8 @@ public class LinkResolver { // Check if this is a link to an external documentation source that should have previously been resolved in `DocumentationContext.preResolveExternalLinks(...)` if let bundleID = unresolvedReference.bundleID, - context.bundle.id != bundleID, - urlReadablePath(context.bundle.displayName) != bundleID.rawValue + context.inputs.id != bundleID, + urlReadablePath(context.inputs.displayName) != bundleID.rawValue { return .failure(unresolvedReference, TopicReferenceResolutionErrorInfo("No external resolver registered for '\(bundleID)'.")) } @@ -136,8 +136,8 @@ private final class FallbackResolverBasedLinkResolver { // Check if a fallback reference resolver should resolve this let referenceBundleID = unresolvedReference.bundleID ?? parent.bundleID guard let fallbackResolver = context.configuration.convertServiceConfiguration.fallbackResolver, - fallbackResolver.bundleID == context.bundle.id, - context.bundle.id == referenceBundleID || urlReadablePath(context.bundle.displayName) == referenceBundleID.rawValue + fallbackResolver.bundleID == context.inputs.id, + context.inputs.id == referenceBundleID || urlReadablePath(context.inputs.displayName) == referenceBundleID.rawValue else { return nil } @@ -155,16 +155,16 @@ private final class FallbackResolverBasedLinkResolver { ) allCandidateURLs.append(alreadyResolved.url) - let currentBundle = context.bundle + let currentInputs = context.inputs if !isCurrentlyResolvingSymbolLink { // First look up articles path allCandidateURLs.append(contentsOf: [ // First look up articles path - currentBundle.articlesDocumentationRootReference.url.appendingPathComponent(unresolvedReference.path), + currentInputs.articlesDocumentationRootReference.url.appendingPathComponent(unresolvedReference.path), // Then technology tutorials root path (for individual tutorial pages) - currentBundle.tutorialsContainerReference.url.appendingPathComponent(unresolvedReference.path), + currentInputs.tutorialsContainerReference.url.appendingPathComponent(unresolvedReference.path), // Then tutorials root path (for tutorial table of contents pages) - currentBundle.tutorialTableOfContentsContainer.url.appendingPathComponent(unresolvedReference.path), + currentInputs.tutorialTableOfContentsContainer.url.appendingPathComponent(unresolvedReference.path), ]) } // Try resolving in the local context (as child) @@ -179,8 +179,8 @@ private final class FallbackResolverBasedLinkResolver { // Check that the parent is not an article (ignoring if absolute or relative link) // because we cannot resolve in the parent context if it's not a symbol. - if parent.path.hasPrefix(currentBundle.documentationRootReference.path) && parentPath.count > 2 { - let rootPath = currentBundle.documentationRootReference.appendingPath(parentPath[2]) + if parent.path.hasPrefix(currentInputs.documentationRootReference.path) && parentPath.count > 2 { + let rootPath = currentInputs.documentationRootReference.appendingPath(parentPath[2]) let resolvedInRoot = rootPath.url.appendingPathComponent(unresolvedReference.path) // Confirm here that we we're not already considering this link. We only need to specifically @@ -190,7 +190,7 @@ private final class FallbackResolverBasedLinkResolver { } } - allCandidateURLs.append(currentBundle.documentationRootReference.url.appendingPathComponent(unresolvedReference.path)) + allCandidateURLs.append(currentInputs.documentationRootReference.url.appendingPathComponent(unresolvedReference.path)) for candidateURL in allCandidateURLs { guard let candidateReference = ValidatedURL(candidateURL).map({ UnresolvedTopicReference(topicURL: $0) }) else { diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift index 597b6af777..652580be28 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift @@ -264,8 +264,8 @@ final class PathHierarchyBasedLinkResolver { /// /// - Parameters: /// - symbolGraph: The complete symbol graph to walk through. - /// - bundle: The bundle to use when creating symbol references. - func referencesForSymbols(in unifiedGraphs: [String: UnifiedSymbolGraph], bundle: DocumentationBundle, context: DocumentationContext) -> [SymbolGraph.Symbol.Identifier: ResolvedTopicReference] { + /// - context: The context that the symbols are a part of. + func referencesForSymbols(in unifiedGraphs: [String: UnifiedSymbolGraph], context: DocumentationContext) -> [SymbolGraph.Symbol.Identifier: ResolvedTopicReference] { let disambiguatedPaths = pathHierarchy.caseInsensitiveDisambiguatedPaths(includeDisambiguationForUnambiguousChildren: true, includeLanguage: true, allowAdvancedDisambiguation: false) var result: [SymbolGraph.Symbol.Identifier: ResolvedTopicReference] = [:] @@ -280,7 +280,7 @@ final class PathHierarchyBasedLinkResolver { pathComponents.count == componentsCount { let symbolReference = SymbolReference(pathComponents: pathComponents, interfaceLanguages: symbol.sourceLanguages) - return ResolvedTopicReference(symbolReference: symbolReference, moduleName: moduleName, bundle: bundle) + return ResolvedTopicReference(symbolReference: symbolReference, moduleName: moduleName, bundle: context.inputs) } guard let path = disambiguatedPaths[uniqueIdentifier] else { @@ -288,7 +288,7 @@ final class PathHierarchyBasedLinkResolver { } return ResolvedTopicReference( - bundleID: bundle.documentationRootReference.bundleID, + bundleID: context.inputs.documentationRootReference.bundleID, path: NodeURLGenerator.Path.documentationFolder + path, sourceLanguages: symbol.sourceLanguages ) diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift index 4ceb41fa38..cf461d3286 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift @@ -96,7 +96,7 @@ enum GeneratedDocumentationTopics { private static let defaultImplementationGroupTitle = "Default Implementations" - private static func createCollectionNode(parent: ResolvedTopicReference, title: String, identifiers: [ResolvedTopicReference], context: DocumentationContext, bundle: DocumentationBundle) throws { + private static func createCollectionNode(parent: ResolvedTopicReference, title: String, identifiers: [ResolvedTopicReference], context: DocumentationContext) throws { let automaticCurationSourceLanguage: SourceLanguage let automaticCurationSourceLanguages: Set automaticCurationSourceLanguage = identifiers.first?.sourceLanguage ?? .swift @@ -104,7 +104,7 @@ enum GeneratedDocumentationTopics { // Create the collection topic reference let collectionReference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: NodeURLGenerator.Path.documentationCuration( parentPath: parent.path, articleName: title @@ -227,8 +227,7 @@ enum GeneratedDocumentationTopics { /// - relationships: A set of relationships to inspect. /// - symbolsURLHierarchy: A symbol graph hierarchy as created during symbol registration. /// - context: A documentation context to update. - /// - bundle: The current documentation bundle. - static func createInheritedSymbolsAPICollections(relationships: Set, context: DocumentationContext, bundle: DocumentationBundle) throws { + static func createInheritedSymbolsAPICollections(relationships: Set, context: DocumentationContext) throws { var inheritanceIndex = InheritedSymbols() // Walk the symbol graph relationships and look for parent <-> child links that stem in a different module. @@ -258,7 +257,7 @@ enum GeneratedDocumentationTopics { for (typeReference, collections) in inheritanceIndex.implementingTypes where !collections.inheritedFromTypeName.isEmpty { for (_, collection) in collections.inheritedFromTypeName where !collection.identifiers.isEmpty { // Create a collection for the given provider type's inherited symbols - try createCollectionNode(parent: typeReference, title: collection.title, identifiers: collection.identifiers, context: context, bundle: bundle) + try createCollectionNode(parent: typeReference, title: collection.title, identifiers: collection.identifiers, context: context) } } } diff --git a/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift b/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift index be5516727a..d0deba7971 100644 --- a/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift +++ b/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.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 @@ -155,7 +155,6 @@ public struct AutomaticCuration { for node: DocumentationNode, withTraits variantsTraits: Set, context: DocumentationContext, - bundle: DocumentationBundle, renderContext: RenderContext?, renderer: DocumentationContentRenderer ) -> TaskGroup? { diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index db64f1939a..e6407e9057 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -431,15 +431,14 @@ public extension DocumentationNode { renderNode: RenderNode, includeTaskGroups: Bool = true ) -> [LinkDestinationSummary] { - let bundle = context.bundle - guard bundle.id == reference.bundleID else { + guard context.inputs.id == reference.bundleID else { // Don't return anything for external references that don't have a bundle in the context. return [] } - let urlGenerator = PresentationURLGenerator(context: context, baseURL: bundle.baseURL) + let urlGenerator = PresentationURLGenerator(context: context, baseURL: context.inputs.baseURL) let relativePresentationURL = urlGenerator.presentationURLForReference(reference).withoutHostAndPortAndScheme() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: reference) + var compiler = RenderContentCompiler(context: context, identifier: reference) let platforms = renderNode.metadata.platforms diff --git a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift index 465a58aa8a..0d6ee8dd40 100644 --- a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift +++ b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift @@ -47,18 +47,20 @@ extension RenderReferenceDependencies: Codable { /// A collection of functions that render a piece of documentation content. public class DocumentationContentRenderer { - let documentationContext: DocumentationContext - let bundle: DocumentationBundle + let context: DocumentationContext let urlGenerator: PresentationURLGenerator - /// Creates a new content renderer for the given documentation context and bundle. + /// Creates a new content renderer for the given documentation context. /// - Parameters: - /// - documentationContext: A documentation context. - /// - bundle: A documentation bundle. - public init(documentationContext: DocumentationContext, bundle: DocumentationBundle) { - self.documentationContext = documentationContext - self.bundle = bundle - self.urlGenerator = PresentationURLGenerator(context: documentationContext, baseURL: bundle.baseURL) + /// - context: A documentation context. + public init(context: DocumentationContext) { + self.context = context + self.urlGenerator = PresentationURLGenerator(context: context, baseURL: context.inputs.baseURL) + } + + @available(*, deprecated, renamed: "init(context:)", message: "Use 'init(context:)' instead. This deprecated API will be removed after 6.4 is released.") + public convenience init(documentationContext: DocumentationContext, bundle _: DocumentationBundle) { + self.init(context: documentationContext) } /// For symbol nodes, returns the declaration render section if any. @@ -171,7 +173,7 @@ public class DocumentationContentRenderer { // Generates a generic conformance section for the given reference. func conformanceSectionFor(_ reference: ResolvedTopicReference, collectedConstraints: [TopicReference: [SymbolGraph.Symbol.Swift.GenericConstraint]]) -> ConformanceSection? { - guard let node = try? documentationContext.entity(with: reference), + guard let node = try? context.entity(with: reference), let symbol = node.symbol else { // Couldn't find the node for this reference return nil @@ -192,8 +194,8 @@ public class DocumentationContentRenderer { } let isLeaf = SymbolReference.isLeaf(symbol) - let parentName = documentationContext.parents(of: reference).first - .flatMap { try? documentationContext.entity(with: $0).symbol?.names.title } + let parentName = context.parents(of: reference).first + .flatMap { try? context.entity(with: $0).symbol?.names.title } let options = ConformanceSection.ConstraintRenderOptions( isLeaf: isLeaf, @@ -211,7 +213,7 @@ public class DocumentationContentRenderer { // We verify that this is a symbol with defined availability // and that we're feeding in a current set of platforms to the context. guard let symbol = node.semantic as? Symbol, - let currentPlatforms = documentationContext.configuration.externalMetadata.currentPlatforms, + let currentPlatforms = context.configuration.externalMetadata.currentPlatforms, !currentPlatforms.isEmpty, let symbolAvailability = symbol.availability?.availability.filter({ !$0.isUnconditionallyUnavailable }), // symbol that's unconditionally unavailable in all the platforms can't be in beta. !symbolAvailability.isEmpty // A symbol without availability items can't be in beta. @@ -230,7 +232,7 @@ public class DocumentationContentRenderer { guard let name = availability.domain.map({ PlatformName(operatingSystemName: $0.rawValue) }), // Use the display name of the platform when looking up the current platforms // as we expect that form on the command line. - let current = documentationContext.configuration.externalMetadata.currentPlatforms?[name.displayName] + let current = context.configuration.externalMetadata.currentPlatforms?[name.displayName] else { return false } @@ -287,14 +289,14 @@ public class DocumentationContentRenderer { /// /// - Returns: The rendered documentation node. func renderReference(for reference: ResolvedTopicReference, with overridingDocumentationNode: DocumentationNode? = nil, dependencies: inout RenderReferenceDependencies) -> TopicRenderReference { - let resolver = LinkTitleResolver(context: documentationContext, source: reference.url) + let resolver = LinkTitleResolver(context: context, source: reference.url) let titleVariants: DocumentationDataVariants - let node = try? overridingDocumentationNode ?? documentationContext.entity(with: reference) + let node = try? overridingDocumentationNode ?? context.entity(with: reference) if let node, let resolvedTitle = resolver.title(for: node) { titleVariants = resolvedTitle - } else if let anchorSection = documentationContext.nodeAnchorSections[reference] { + } else if let anchorSection = context.nodeAnchorSections[reference] { // No need to continue, return a section topic reference return TopicRenderReference( identifier: RenderReferenceIdentifier(reference.absoluteString), @@ -304,11 +306,11 @@ public class DocumentationContentRenderer { kind: .section, estimatedTime: nil ) - } else if let topicGraphOnlyNode = documentationContext.topicGraph.nodeWithReference(reference) { + } else if let topicGraphOnlyNode = context.topicGraph.nodeWithReference(reference) { // Some nodes are artificially inserted into the topic graph, // try resolving that way as a fallback after looking up `documentationCache`. titleVariants = .init(defaultVariantValue: topicGraphOnlyNode.title) - } else if let external = documentationContext.externalCache[reference] { + } else if let external = context.externalCache[reference] { let renderDependencies = external.makeRenderDependencies() dependencies.topicReferences.append(contentsOf: renderDependencies.topicReferences) @@ -326,7 +328,7 @@ public class DocumentationContentRenderer { // Topic render references require the URLs to be relative, even if they're external. let presentationURL = urlGenerator.presentationURLForReference(reference) - var contentCompiler = RenderContentCompiler(context: documentationContext, bundle: bundle, identifier: reference) + var contentCompiler = RenderContentCompiler(context: context, identifier: reference) let abstractContent: VariantCollection<[RenderInlineContent]> var abstractedNode = node @@ -337,7 +339,7 @@ public class DocumentationContentRenderer { path: reference.path, sourceLanguages: reference.sourceLanguages ) - abstractedNode = try? documentationContext.entity(with: containerReference) + abstractedNode = try? context.entity(with: containerReference) } func extractAbstract(from paragraph: Paragraph?) -> [RenderInlineContent] { @@ -397,13 +399,13 @@ public class DocumentationContentRenderer { renderReference.images = node?.metadata?.pageImages.compactMap { pageImage -> TopicImage? in guard let image = TopicImage( pageImage: pageImage, - with: documentationContext, + with: context, in: reference ) else { return nil } - guard let asset = documentationContext.resolveAsset( + guard let asset = context.resolveAsset( named: image.identifier.identifier, in: reference ) else { @@ -460,7 +462,7 @@ public class DocumentationContentRenderer { var result = [RenderNode.Tag]() /// Add an SPI tag to SPI symbols. - if let node = try? documentationContext.entity(with: reference), + if let node = try? context.entity(with: reference), let symbol = node.semantic as? Symbol, symbol.isSPI { result.append(.spi) @@ -479,7 +481,7 @@ public class DocumentationContentRenderer { /// Returns the task groups for a given node reference. func taskGroups(for reference: ResolvedTopicReference) -> [ReferenceGroup]? { - guard let node = try? documentationContext.entity(with: reference) else { return nil } + guard let node = try? context.entity(with: reference) else { return nil } let groups: [TaskGroup]? switch node.semantic { @@ -505,7 +507,7 @@ public class DocumentationContentRenderer { // For external links, verify they've resolved successfully and return `nil` otherwise. if linkHost != reference.bundleID.rawValue { - if let url = ValidatedURL(destination), case .success(let externalReference) = documentationContext.externallyResolvedLinks[url] { + if let url = ValidatedURL(destination), case .success(let externalReference) = context.externallyResolvedLinks[url] { return externalReference } return nil @@ -518,7 +520,7 @@ public class DocumentationContentRenderer { } let supportedLanguages = group.directives[SupportedLanguage.directiveName]?.compactMap { - SupportedLanguage(from: $0, source: nil, for: bundle)?.language + SupportedLanguage(from: $0, source: nil, for: context.inputs)?.language } return ReferenceGroup( diff --git a/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift b/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift index 73f85a43f4..2aceb26ecf 100644 --- a/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift +++ b/Sources/SwiftDocC/Model/Rendering/LinkTitleResolver.swift @@ -31,11 +31,11 @@ struct LinkTitleResolver { var problems = [Problem]() switch directive.name { case Tutorial.directiveName: - if let tutorial = Tutorial(from: directive, source: source, for: context.bundle, problems: &problems) { + if let tutorial = Tutorial(from: directive, source: source, for: context.inputs, problems: &problems) { return .init(defaultVariantValue: tutorial.intro.title) } case TutorialTableOfContents.directiveName: - if let overview = TutorialTableOfContents(from: directive, source: source, for: context.bundle, problems: &problems) { + if let overview = TutorialTableOfContents(from: directive, source: source, for: context.inputs, problems: &problems) { return .init(defaultVariantValue: overview.name) } default: break diff --git a/Sources/SwiftDocC/Model/Rendering/Navigation Tree/RenderHierarchyTranslator.swift b/Sources/SwiftDocC/Model/Rendering/Navigation Tree/RenderHierarchyTranslator.swift index 25c6708333..c62907e757 100644 --- a/Sources/SwiftDocC/Model/Rendering/Navigation Tree/RenderHierarchyTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/Navigation Tree/RenderHierarchyTranslator.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 @@ -13,18 +13,15 @@ import Foundation /// A hierarchy translator that converts a part of the topic graph into a hierarchy tree. struct RenderHierarchyTranslator { var context: DocumentationContext - var bundle: DocumentationBundle var collectedTopicReferences = Set() var linkReferences = [String: LinkReference]() - /// Creates a new translator for the given bundle in the given context. + /// Creates a new translator for the given context. /// - Parameters: /// - context: The documentation context for the conversion. - /// - bundle: The documentation bundle for the conversion. - init(context: DocumentationContext, bundle: DocumentationBundle) { + init(context: DocumentationContext) { self.context = context - self.bundle = bundle } static let assessmentsAnchor = urlReadableFragment(TutorialAssessmentsRenderSection.title) @@ -164,7 +161,7 @@ struct RenderHierarchyTranslator { let assessmentReference = ResolvedTopicReference(bundleID: tutorialReference.bundleID, path: tutorialReference.path, fragment: RenderHierarchyTranslator.assessmentsAnchor, sourceLanguage: .swift) renderHierarchyTutorial.landmarks.append(RenderHierarchyLandmark(reference: RenderReferenceIdentifier(assessmentReference.absoluteString), kind: .assessment)) - let urlGenerator = PresentationURLGenerator(context: context, baseURL: bundle.baseURL) + let urlGenerator = PresentationURLGenerator(context: context, baseURL: context.inputs.baseURL) let assessmentLinkReference = LinkReference( identifier: RenderReferenceIdentifier(assessmentReference.absoluteString), title: "Check Your Understanding", diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift b/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift index 8c72c6436d..2f7ddd4d31 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.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 @@ -20,7 +20,6 @@ extension RenderInlineContent: RenderContent {} struct RenderContentCompiler: MarkupVisitor { var context: DocumentationContext - var bundle: DocumentationBundle var identifier: ResolvedTopicReference var imageReferences: [String: ImageReference] = [:] var videoReferences: [String: VideoReference] = [:] @@ -28,9 +27,8 @@ struct RenderContentCompiler: MarkupVisitor { var collectedTopicReferences = GroupedSequence { $0.absoluteString } var linkReferences: [String: LinkReference] = [:] - init(context: DocumentationContext, bundle: DocumentationBundle, identifier: ResolvedTopicReference) { + init(context: DocumentationContext, identifier: ResolvedTopicReference) { self.context = context - self.bundle = bundle self.identifier = identifier } @@ -46,21 +44,18 @@ struct RenderContentCompiler: MarkupVisitor { } mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> [any RenderContent] { - // Default to the bundle's code listing syntax if one is not explicitly declared in the code block. - if FeatureFlags.current.isExperimentalCodeBlockAnnotationsEnabled { let codeBlockOptions = RenderBlockContent.CodeBlockOptions(parsingLanguageString: codeBlock.language) let listing = RenderBlockContent.CodeListing( - syntax: codeBlockOptions.language ?? bundle.info.defaultCodeListingLanguage, + syntax: codeBlockOptions.language ?? context.inputs.info.defaultCodeListingLanguage, code: codeBlock.code.splitByNewlines, metadata: nil, options: codeBlockOptions ) return [RenderBlockContent.codeListing(listing)] - } else { - return [RenderBlockContent.codeListing(.init(syntax: codeBlock.language ?? bundle.info.defaultCodeListingLanguage, code: codeBlock.code.splitByNewlines, metadata: nil, options: nil))] + return [RenderBlockContent.codeListing(.init(syntax: codeBlock.language ?? context.inputs.info.defaultCodeListingLanguage, code: codeBlock.code.splitByNewlines, metadata: nil, options: nil))] } } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContentConvertible.swift b/Sources/SwiftDocC/Model/Rendering/RenderContentConvertible.swift index 6e7325c800..18e358d94c 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContentConvertible.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContentConvertible.swift @@ -24,7 +24,7 @@ extension RenderableDirectiveConvertible { _ blockDirective: BlockDirective, with contentCompiler: inout RenderContentCompiler ) -> [any RenderContent] { - guard let directive = Self.init(from: blockDirective, for: contentCompiler.bundle) else { + guard let directive = Self.init(from: blockDirective, for: contentCompiler.context.inputs) else { return [] } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift b/Sources/SwiftDocC/Model/Rendering/RenderContext.swift index 26a299c100..367845ad35 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContext.swift @@ -17,21 +17,23 @@ import SymbolKit /// converting nodes in bulk, i.e. when converting a complete documentation model for example. public struct RenderContext { let documentationContext: DocumentationContext - let bundle: DocumentationBundle let renderer: DocumentationContentRenderer /// Creates a new render context. /// - Warning: Creating a render context pre-renders all content that the context provides. /// - Parameters: /// - documentationContext: A documentation context. - /// - bundle: A documentation bundle. - public init(documentationContext: DocumentationContext, bundle: DocumentationBundle) { + public init(documentationContext: DocumentationContext) { self.documentationContext = documentationContext - self.bundle = bundle - self.renderer = DocumentationContentRenderer(documentationContext: documentationContext, bundle: bundle) + self.renderer = DocumentationContentRenderer(context: documentationContext) createRenderedContent() } + @available(*, deprecated, renamed: "init(context:)", message: "Use 'init(context:)' instead. This deprecated API will be removed after 6.4 is released.") + public init(documentationContext: DocumentationContext, bundle _: DocumentationBundle) { + self.init(documentationContext: documentationContext) + } + /// The pre-rendered content per node reference. private(set) public var store = RenderReferenceStore() @@ -40,10 +42,8 @@ public struct RenderContext { private mutating func createRenderedContent() { let references = documentationContext.knownIdentifiers var topics = [ResolvedTopicReference: RenderReferenceStore.TopicContent]() - let renderer = self.renderer - let documentationContext = self.documentationContext - let renderContentFor: (ResolvedTopicReference) -> RenderReferenceStore.TopicContent = { reference in + let renderContentFor: (ResolvedTopicReference) -> RenderReferenceStore.TopicContent = { [renderer, documentationContext] reference in var dependencies = RenderReferenceDependencies() let renderReference = renderer.renderReference(for: reference, dependencies: &dependencies) let canonicalPath = documentationContext.shortestFinitePath(to: reference).flatMap { $0.isEmpty ? nil : $0 } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index 00cd12c114..8d88ec2128 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -134,7 +134,7 @@ public struct RenderNodeTranslator: SemanticVisitor { public mutating func visitTutorial(_ tutorial: Tutorial) -> (any RenderTree)? { var node = RenderNode(identifier: identifier, kind: .tutorial) - var hierarchyTranslator = RenderHierarchyTranslator(context: context, bundle: bundle) + var hierarchyTranslator = RenderHierarchyTranslator(context: context) if let hierarchy = hierarchyTranslator.visitTutorialTableOfContentsNode(identifier) { let tutorialTableOfContents = try! context.entity(with: hierarchy.tutorialTableOfContents).semantic as! TutorialTableOfContents @@ -315,7 +315,7 @@ public struct RenderNodeTranslator: SemanticVisitor { // Visits a container and expects the elements to be block level elements public mutating func visitMarkupContainer(_ markupContainer: MarkupContainer) -> (any RenderTree)? { - var contentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: identifier) + var contentCompiler = RenderContentCompiler(context: context, identifier: identifier) let content = markupContainer.elements.reduce(into: [], { result, item in result.append(contentsOf: contentCompiler.visit(item))}) as! [RenderBlockContent] collectedTopicReferences.append(contentsOf: contentCompiler.collectedTopicReferences) // Copy all the image references found in the markup container. @@ -327,7 +327,7 @@ public struct RenderNodeTranslator: SemanticVisitor { // Visits a collection of inline markup elements. public mutating func visitMarkup(_ markup: [any Markup]) -> (any RenderTree)? { - var contentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: identifier) + var contentCompiler = RenderContentCompiler(context: context, identifier: identifier) let content = markup.reduce(into: [], { result, item in result.append(contentsOf: contentCompiler.visit(item))}) as! [RenderInlineContent] collectedTopicReferences.append(contentsOf: contentCompiler.collectedTopicReferences) // Copy all the image references. @@ -401,7 +401,7 @@ public struct RenderNodeTranslator: SemanticVisitor { node.sections.append(visitResources(resources) as! ResourcesRenderSection) } - var hierarchyTranslator = RenderHierarchyTranslator(context: context, bundle: bundle) + var hierarchyTranslator = RenderHierarchyTranslator(context: context) if let (hierarchyVariants, _) = hierarchyTranslator.visitTutorialTableOfContentsNode(identifier, omittingChapters: true) { node.hierarchyVariants = hierarchyVariants collectedTopicReferences.append(contentsOf: hierarchyTranslator.collectedTopicReferences) @@ -419,7 +419,7 @@ public struct RenderNodeTranslator: SemanticVisitor { private mutating func createTopicRenderReferences() -> [String: any RenderReference] { var renderReferences: [String: any RenderReference] = [:] - let renderer = DocumentationContentRenderer(documentationContext: context, bundle: bundle) + let renderer = DocumentationContentRenderer(context: context) for reference in collectedTopicReferences { var renderReference: TopicRenderReference @@ -530,7 +530,7 @@ public struct RenderNodeTranslator: SemanticVisitor { } public mutating func visitTutorialReference(_ tutorialReference: TutorialReference) -> (any RenderTree)? { - switch context.resolve(tutorialReference.topic, in: bundle.rootReference) { + switch context.resolve(tutorialReference.topic, in: context.inputs.rootReference) { case let .failure(reference, _): return RenderReferenceIdentifier(reference.topicURL.absoluteString) case let .success(resolved): @@ -600,7 +600,7 @@ public struct RenderNodeTranslator: SemanticVisitor { public mutating func visitArticle(_ article: Article) -> (any RenderTree)? { var node = RenderNode(identifier: identifier, kind: .article) // Contains symbol references declared in the Topics section. - var topicSectionContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: identifier) + var topicSectionContentCompiler = RenderContentCompiler(context: context, identifier: identifier) node.metadata.title = article.title!.plainText @@ -624,7 +624,7 @@ public struct RenderNodeTranslator: SemanticVisitor { let documentationNode = try! context.entity(with: identifier) - var hierarchyTranslator = RenderHierarchyTranslator(context: context, bundle: bundle) + var hierarchyTranslator = RenderHierarchyTranslator(context: context) let hierarchyVariants = hierarchyTranslator.visitArticle(identifier) collectedTopicReferences.append(contentsOf: hierarchyTranslator.collectedTopicReferences) node.hierarchyVariants = hierarchyVariants @@ -806,7 +806,6 @@ public struct RenderNodeTranslator: SemanticVisitor { for: documentationNode, withTraits: allowedTraits, context: context, - bundle: bundle, renderContext: renderContext, renderer: contentRenderer ) { @@ -878,7 +877,7 @@ public struct RenderNodeTranslator: SemanticVisitor { public mutating func visitTutorialArticle(_ article: TutorialArticle) -> (any RenderTree)? { var node = RenderNode(identifier: identifier, kind: .article) - var hierarchyTranslator = RenderHierarchyTranslator(context: context, bundle: bundle) + var hierarchyTranslator = RenderHierarchyTranslator(context: context) guard let hierarchy = hierarchyTranslator.visitTutorialTableOfContentsNode(identifier) else { // This tutorial article is not curated, so we don't generate a render node. // We've warned about this during semantic analysis. @@ -1029,7 +1028,7 @@ public struct RenderNodeTranslator: SemanticVisitor { ) -> [TaskGroupRenderSection] { return topics.taskGroups.compactMap { group in let supportedLanguages = group.directives[SupportedLanguage.directiveName]?.compactMap { - SupportedLanguage(from: $0, source: nil, for: bundle)?.language + SupportedLanguage(from: $0, source: nil, for: context.inputs)?.language } // If the task group has a set of supported languages, see if it should render for the allowed traits. @@ -1204,7 +1203,7 @@ public struct RenderNodeTranslator: SemanticVisitor { let identifier = identifier.addingSourceLanguages(documentationNode.availableSourceLanguages) var node = RenderNode(identifier: identifier, kind: .symbol) - var contentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: identifier) + var contentCompiler = RenderContentCompiler(context: context, identifier: identifier) /* FIXME: We shouldn't be doing this kind of crawling here. @@ -1244,7 +1243,7 @@ public struct RenderNodeTranslator: SemanticVisitor { node.metadata.extendedModuleVariants = VariantCollection(from: symbol.extendedModuleVariants) - let defaultAvailability = defaultAvailability(for: bundle, moduleName: moduleName.symbolName, currentPlatforms: context.configuration.externalMetadata.currentPlatforms)? + let defaultAvailability = defaultAvailability(moduleName: moduleName.symbolName, currentPlatforms: context.configuration.externalMetadata.currentPlatforms)? .filter { $0.unconditionallyUnavailable != true } .sorted(by: AvailabilityRenderOrder.compare) @@ -1333,10 +1332,10 @@ public struct RenderNodeTranslator: SemanticVisitor { collectedTopicReferences.append(identifier) - let contentRenderer = DocumentationContentRenderer(documentationContext: context, bundle: bundle) + let contentRenderer = DocumentationContentRenderer(context: context) node.metadata.tags = contentRenderer.tags(for: identifier) - var hierarchyTranslator = RenderHierarchyTranslator(context: context, bundle: bundle) + var hierarchyTranslator = RenderHierarchyTranslator(context: context) let hierarchyVariants = hierarchyTranslator.visitSymbol(identifier) collectedTopicReferences.append(contentsOf: hierarchyTranslator.collectedTopicReferences) node.hierarchyVariants = hierarchyVariants @@ -1651,7 +1650,6 @@ public struct RenderNodeTranslator: SemanticVisitor { for: documentationNode, withTraits: allowedTraits, context: context, - bundle: bundle, renderContext: renderContext, renderer: contentRenderer ), !seeAlso.references.isEmpty { @@ -1777,7 +1775,6 @@ public struct RenderNodeTranslator: SemanticVisitor { } var context: DocumentationContext - var bundle: DocumentationBundle var identifier: ResolvedTopicReference var imageReferences: [String: ImageReference] = [:] var videoReferences: [String: VideoReference] = [:] @@ -1811,8 +1808,8 @@ public struct RenderNodeTranslator: SemanticVisitor { } /// The default availability for modules in a given bundle and module. - mutating func defaultAvailability(for bundle: DocumentationBundle, moduleName: String, currentPlatforms: [String: PlatformVersion]?) -> [AvailabilityRenderItem]? { - let identifier = BundleModuleIdentifier(bundle: bundle, moduleName: moduleName) + private mutating func defaultAvailability(moduleName: String, currentPlatforms: [String: PlatformVersion]?) -> [AvailabilityRenderItem]? { + let identifier = BundleModuleIdentifier(bundle: context.inputs, moduleName: moduleName) // Cached availability if let availability = bundleAvailability[identifier] { @@ -1820,7 +1817,7 @@ public struct RenderNodeTranslator: SemanticVisitor { } // Find default module availability if existing - guard let bundleDefaultAvailability = bundle.info.defaultAvailability, + guard let bundleDefaultAvailability = context.inputs.info.defaultAvailability, let moduleAvailability = bundleDefaultAvailability.modules[moduleName] else { return nil } @@ -1854,7 +1851,7 @@ public struct RenderNodeTranslator: SemanticVisitor { } private func variants(for documentationNode: DocumentationNode) -> [RenderNode.Variant] { - let generator = PresentationURLGenerator(context: context, baseURL: bundle.baseURL) + let generator = PresentationURLGenerator(context: context, baseURL: context.inputs.baseURL) var allVariants: [SourceLanguage: ResolvedTopicReference] = documentationNode.availableSourceLanguages.reduce(into: [:]) { partialResult, language in partialResult[language] = identifier @@ -2005,7 +2002,6 @@ public struct RenderNodeTranslator: SemanticVisitor { init( context: DocumentationContext, - bundle: DocumentationBundle, identifier: ResolvedTopicReference, renderContext: RenderContext? = nil, emitSymbolSourceFileURIs: Bool = false, @@ -2014,10 +2010,9 @@ public struct RenderNodeTranslator: SemanticVisitor { symbolIdentifiersWithExpandedDocumentation: [String]? = nil ) { self.context = context - self.bundle = bundle self.identifier = identifier self.renderContext = renderContext - self.contentRenderer = DocumentationContentRenderer(documentationContext: context, bundle: bundle) + self.contentRenderer = DocumentationContentRenderer(context: context) self.shouldEmitSymbolSourceFileURIs = emitSymbolSourceFileURIs self.shouldEmitSymbolAccessLevels = emitSymbolAccessLevels self.sourceRepository = sourceRepository diff --git a/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift b/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift index 00b57e5899..f70557855c 100644 --- a/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift +++ b/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift @@ -39,13 +39,11 @@ private func removedLinkDestinationProblem(reference: ResolvedTopicReference, ra */ struct MarkupReferenceResolver: MarkupRewriter { var context: DocumentationContext - var bundle: DocumentationBundle var problems = [Problem]() var rootReference: ResolvedTopicReference - init(context: DocumentationContext, bundle: DocumentationBundle, rootReference: ResolvedTopicReference) { + init(context: DocumentationContext, rootReference: ResolvedTopicReference) { self.context = context - self.bundle = bundle self.rootReference = rootReference } @@ -79,14 +77,14 @@ struct MarkupReferenceResolver: MarkupRewriter { return nil } - let uncuratedArticleMatch = context.uncuratedArticles[bundle.articlesDocumentationRootReference.appendingPathOfReference(unresolved)]?.source + let uncuratedArticleMatch = context.uncuratedArticles[context.inputs.articlesDocumentationRootReference.appendingPathOfReference(unresolved)]?.source problems.append(unresolvedReferenceProblem(source: range?.source, range: range, severity: severity, uncuratedArticleMatch: uncuratedArticleMatch, errorInfo: error, fromSymbolLink: fromSymbolLink)) return nil } } mutating func visitImage(_ image: Image) -> (any Markup)? { - if let reference = image.reference(in: bundle), !context.resourceExists(with: reference) { + if let reference = image.reference(in: context.inputs), !context.resourceExists(with: reference) { problems.append(unresolvedResourceProblem(resource: reference, source: image.range?.source, range: image.range, severity: .warning)) } @@ -167,7 +165,7 @@ struct MarkupReferenceResolver: MarkupRewriter { switch blockDirective.name { case Snippet.directiveName: var ignoredParsingProblems = [Problem]() // Any argument parsing problems have already been reported elsewhere - guard let snippet = Snippet(from: blockDirective, source: source, for: bundle, problems: &ignoredParsingProblems) else { + guard let snippet = Snippet(from: blockDirective, source: source, for: context.inputs, problems: &ignoredParsingProblems) else { return blockDirective } @@ -184,7 +182,7 @@ struct MarkupReferenceResolver: MarkupRewriter { return blockDirective } case ImageMedia.directiveName: - guard let imageMedia = ImageMedia(from: blockDirective, source: source, for: bundle) else { + guard let imageMedia = ImageMedia(from: blockDirective, source: source, for: context.inputs) else { return blockDirective } @@ -202,7 +200,7 @@ struct MarkupReferenceResolver: MarkupRewriter { return blockDirective case VideoMedia.directiveName: - guard let videoMedia = VideoMedia(from: blockDirective, source: source, for: bundle) else { + guard let videoMedia = VideoMedia(from: blockDirective, source: source, for: context.inputs) else { return blockDirective } @@ -240,4 +238,3 @@ struct MarkupReferenceResolver: MarkupRewriter { } } } - diff --git a/Sources/SwiftDocC/Semantics/ReferenceResolver.swift b/Sources/SwiftDocC/Semantics/ReferenceResolver.swift index 2bb44a90ec..399abe2033 100644 --- a/Sources/SwiftDocC/Semantics/ReferenceResolver.swift +++ b/Sources/SwiftDocC/Semantics/ReferenceResolver.swift @@ -95,9 +95,6 @@ struct ReferenceResolver: SemanticVisitor { /// The context to use to resolve references. var context: DocumentationContext - /// The bundle in which visited documents reside. - var bundle: DocumentationBundle - /// Problems found while trying to resolve references. var problems = [Problem]() @@ -106,10 +103,9 @@ struct ReferenceResolver: SemanticVisitor { /// If the documentation is inherited, the reference of the parent symbol. var inheritanceParentReference: ResolvedTopicReference? - init(context: DocumentationContext, bundle: DocumentationBundle, rootReference: ResolvedTopicReference? = nil, inheritanceParentReference: ResolvedTopicReference? = nil) { + init(context: DocumentationContext, rootReference: ResolvedTopicReference? = nil, inheritanceParentReference: ResolvedTopicReference? = nil) { self.context = context - self.bundle = bundle - self.rootReference = rootReference ?? bundle.rootReference + self.rootReference = rootReference ?? context.inputs.rootReference self.inheritanceParentReference = inheritanceParentReference } @@ -119,7 +115,7 @@ struct ReferenceResolver: SemanticVisitor { return .success(resolved) case let .failure(unresolved, error): - let uncuratedArticleMatch = context.uncuratedArticles[bundle.documentationRootReference.appendingPathOfReference(unresolved)]?.source + let uncuratedArticleMatch = context.uncuratedArticles[context.inputs.documentationRootReference.appendingPathOfReference(unresolved)]?.source problems.append(unresolvedReferenceProblem(source: range?.source, range: range, severity: severity, uncuratedArticleMatch: uncuratedArticleMatch, errorInfo: error, fromSymbolLink: false)) return .failure(unresolved, error) } @@ -172,9 +168,9 @@ struct ReferenceResolver: SemanticVisitor { // Change the context of the project file to `download` if let projectFiles = tutorial.projectFiles, - var resolvedDownload = context.resolveAsset(named: projectFiles.path, in: bundle.rootReference) { + var resolvedDownload = context.resolveAsset(named: projectFiles.path, in: rootReference) { resolvedDownload.context = .download - context.updateAsset(named: projectFiles.path, asset: resolvedDownload, in: bundle.rootReference) + context.updateAsset(named: projectFiles.path, asset: resolvedDownload, in: rootReference) } return Tutorial(originalMarkup: tutorial.originalMarkup, durationMinutes: tutorial.durationMinutes, projectFiles: tutorial.projectFiles, requirements: newRequirements, intro: newIntro, sections: newSections, assessments: newAssessments, callToActionImage: newCallToActionImage, redirects: tutorial.redirects) @@ -215,7 +211,7 @@ struct ReferenceResolver: SemanticVisitor { } mutating func visitMarkupContainer(_ markupContainer: MarkupContainer) -> Semantic { - var markupResolver = MarkupReferenceResolver(context: context, bundle: bundle, rootReference: rootReference) + var markupResolver = MarkupReferenceResolver(context: context, rootReference: rootReference) let parent = inheritanceParentReference let context = self.context @@ -315,7 +311,7 @@ struct ReferenceResolver: SemanticVisitor { // i.e. doc:/${SOME_TECHNOLOGY}/${PROJECT} or doc://${BUNDLE_ID}/${SOME_TECHNOLOGY}/${PROJECT} switch tutorialReference.topic { case .unresolved: - let maybeResolved = resolve(tutorialReference.topic, in: bundle.tutorialsContainerReference, + let maybeResolved = resolve(tutorialReference.topic, in: context.inputs.tutorialsContainerReference, range: tutorialReference.originalMarkup.range, severity: .warning) return TutorialReference(originalMarkup: tutorialReference.originalMarkup, tutorial: .resolved(maybeResolved)) @@ -369,10 +365,10 @@ struct ReferenceResolver: SemanticVisitor { visitMarkupContainer($0) as? MarkupContainer } // If there's a call to action with a local-file reference, change its context to `download` - if let downloadFile = article.metadata?.callToAction?.resolveFile(for: bundle, in: context, problems: &problems), - var resolvedDownload = context.resolveAsset(named: downloadFile.path, in: bundle.rootReference) { + if let downloadFile = article.metadata?.callToAction?.resolveFile(for: context.inputs, in: context, problems: &problems), + var resolvedDownload = context.resolveAsset(named: downloadFile.path, in: rootReference) { resolvedDownload.context = .download - context.updateAsset(named: downloadFile.path, asset: resolvedDownload, in: bundle.rootReference) + context.updateAsset(named: downloadFile.path, asset: resolvedDownload, in: rootReference) } return Article( diff --git a/Sources/SwiftDocCTestUtilities/FilesAndFolders.swift b/Sources/SwiftDocCTestUtilities/FilesAndFolders.swift index 248dcdc9db..406366c281 100644 --- a/Sources/SwiftDocCTestUtilities/FilesAndFolders.swift +++ b/Sources/SwiftDocCTestUtilities/FilesAndFolders.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 @@ -94,11 +94,17 @@ public struct InfoPlist: File, DataRepresentable { /// The information that the Into.plist file contains. public let content: Content - public init(displayName: String? = nil, identifier: String? = nil, defaultAvailability: [String: [DefaultAvailability.ModuleAvailability]]? = nil) { + public init( + displayName: String? = nil, + identifier: String? = nil, + defaultAvailability: [String: [DefaultAvailability.ModuleAvailability]]? = nil, + defaultCodeListingLanguage: String? = nil + ) { self.content = Content( displayName: displayName, identifier: identifier, - defaultAvailability: defaultAvailability + defaultAvailability: defaultAvailability, + defaultCodeListingLanguage: defaultCodeListingLanguage ) } @@ -106,25 +112,29 @@ public struct InfoPlist: File, DataRepresentable { public let displayName: String? public let identifier: String? public let defaultAvailability: [String: [DefaultAvailability.ModuleAvailability]]? - - fileprivate init(displayName: String?, identifier: String?, defaultAvailability: [String: [DefaultAvailability.ModuleAvailability]]?) { + public let defaultCodeListingLanguage: String? + + fileprivate init(displayName: String?, identifier: String?, defaultAvailability: [String: [DefaultAvailability.ModuleAvailability]]?, defaultCodeListingLanguage: String?) { self.displayName = displayName self.identifier = identifier self.defaultAvailability = defaultAvailability + self.defaultCodeListingLanguage = defaultCodeListingLanguage } public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: DocumentationBundle.Info.CodingKeys.self) + let container = try decoder.container(keyedBy: DocumentationContext.Inputs.Info.CodingKeys.self) displayName = try container.decodeIfPresent(String.self, forKey: .displayName) identifier = try container.decodeIfPresent(String.self, forKey: .id) defaultAvailability = try container.decodeIfPresent([String : [DefaultAvailability.ModuleAvailability]].self, forKey: .defaultAvailability) + defaultCodeListingLanguage = try container.decodeIfPresent(String.self, forKey: .defaultCodeListingLanguage) } public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: DocumentationBundle.Info.CodingKeys.self) + var container = encoder.container(keyedBy: DocumentationContext.Inputs.Info.CodingKeys.self) try container.encodeIfPresent(displayName, forKey: .displayName) try container.encodeIfPresent(identifier, forKey: .id) try container.encodeIfPresent(defaultAvailability, forKey: .defaultAvailability) + try container.encodeIfPresent(defaultCodeListingLanguage, forKey: .defaultCodeListingLanguage) } } diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift index 6f0af6647c..b6c98ecedd 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift @@ -178,12 +178,12 @@ public struct ConvertAction: AsyncAction { self.configuration = configuration - self.bundle = bundle + self.inputs = bundle self.dataProvider = dataProvider } let configuration: DocumentationContext.Configuration - private let bundle: DocumentationBundle + private let inputs: DocumentationBundle private let dataProvider: any DataProvider /// A block of extra work that tests perform to affect the time it takes to convert documentation @@ -286,10 +286,10 @@ public struct ConvertAction: AsyncAction { workingDirectory: temporaryFolder, fileManager: fileManager) - let indexer = try Indexer(outputURL: temporaryFolder, bundleID: bundle.id) + let indexer = try Indexer(outputURL: temporaryFolder, bundleID: inputs.id) let registerInterval = signposter.beginInterval("Register", id: signposter.makeSignpostID()) - let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) + let context = try await DocumentationContext(bundle: inputs, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration) signposter.endInterval("Register", registerInterval) let outputConsumer = ConvertFileWritingConsumer( @@ -300,7 +300,7 @@ public struct ConvertAction: AsyncAction { indexer: indexer, enableCustomTemplates: experimentalEnableCustomTemplates, transformForStaticHostingIndexHTML: transformForStaticHosting ? indexHTML : nil, - bundleID: bundle.id + bundleID: inputs.id ) if experimentalModifyCatalogWithGeneratedCuration, let catalogURL = rootURL { @@ -318,7 +318,6 @@ public struct ConvertAction: AsyncAction { do { conversionProblems = try signposter.withIntervalSignpost("Process") { try ConvertActionConverter.convert( - bundle: bundle, context: context, outputConsumer: outputConsumer, sourceRepository: sourceRepository, @@ -347,7 +346,7 @@ public struct ConvertAction: AsyncAction { let tableOfContentsFilename = CatalogTemplateKind.tutorialTopLevelFilename let source = rootURL?.appendingPathComponent(tableOfContentsFilename) var replacements = [Replacement]() - if let tableOfContentsTemplate = CatalogTemplateKind.tutorialTemplateFiles(bundle.displayName)[tableOfContentsFilename] { + if let tableOfContentsTemplate = CatalogTemplateKind.tutorialTemplateFiles(inputs.displayName)[tableOfContentsFilename] { replacements.append( Replacement( range: .init(line: 1, column: 1, source: source) ..< .init(line: 1, column: 1, source: source), @@ -438,7 +437,7 @@ public struct ConvertAction: AsyncAction { context: context, indexer: nil, transformForStaticHostingIndexHTML: nil, - bundleID: bundle.id + bundleID: inputs.id ) try outputConsumer.consume(benchmarks: Benchmark.main) diff --git a/Tests/SwiftDocCTests/Converter/DocumentationContextConverterTests.swift b/Tests/SwiftDocCTests/Converter/DocumentationContextConverterTests.swift index 381720b5b2..d9b63dfee5 100644 --- a/Tests/SwiftDocCTests/Converter/DocumentationContextConverterTests.swift +++ b/Tests/SwiftDocCTests/Converter/DocumentationContextConverterTests.swift @@ -14,14 +14,14 @@ import XCTest class DocumentationContextConverterTests: XCTestCase { func testRenderNodesAreIdentical() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // We'll use this to convert nodes ad-hoc - let perNodeConverter = DocumentationNodeConverter(bundle: bundle, context: context) + let perNodeConverter = DocumentationNodeConverter(context: context) // We'll use these to convert nodes in bulk - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let bulkNodeConverter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let renderContext = RenderContext(documentationContext: context) + let bulkNodeConverter = DocumentationContextConverter(context: context, renderContext: renderContext) let encoder = JSONEncoder() for identifier in context.knownPages { @@ -41,8 +41,8 @@ class DocumentationContextConverterTests: XCTestCase { } func testSymbolLocationsAreOnlyIncludedWhenRequested() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let renderContext = RenderContext(documentationContext: context) let fillIntroducedSymbolNode = try XCTUnwrap( context.documentationCache["s:14FillIntroduced19macOSOnlyDeprecatedyyF"] @@ -50,7 +50,6 @@ class DocumentationContextConverterTests: XCTestCase { do { let documentationContextConverter = DocumentationContextConverter( - bundle: bundle, context: context, renderContext: renderContext, emitSymbolSourceFileURIs: true) @@ -61,7 +60,6 @@ class DocumentationContextConverterTests: XCTestCase { do { let documentationContextConverter = DocumentationContextConverter( - bundle: bundle, context: context, renderContext: renderContext) @@ -71,8 +69,8 @@ class DocumentationContextConverterTests: XCTestCase { } func testSymbolAccessLevelsAreOnlyIncludedWhenRequested() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let renderContext = RenderContext(documentationContext: context) let fillIntroducedSymbolNode = try XCTUnwrap( context.documentationCache["s:14FillIntroduced19macOSOnlyDeprecatedyyF"] @@ -80,7 +78,6 @@ class DocumentationContextConverterTests: XCTestCase { do { let documentationContextConverter = DocumentationContextConverter( - bundle: bundle, context: context, renderContext: renderContext, emitSymbolAccessLevels: true @@ -92,7 +89,6 @@ class DocumentationContextConverterTests: XCTestCase { do { let documentationContextConverter = DocumentationContextConverter( - bundle: bundle, context: context, renderContext: renderContext) diff --git a/Tests/SwiftDocCTests/Converter/RenderContextTests.swift b/Tests/SwiftDocCTests/Converter/RenderContextTests.swift index 7ac0d4240f..3d9805df44 100644 --- a/Tests/SwiftDocCTests/Converter/RenderContextTests.swift +++ b/Tests/SwiftDocCTests/Converter/RenderContextTests.swift @@ -14,9 +14,8 @@ import XCTest class RenderContextTests: XCTestCase { func testCreatesRenderReferences() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - - let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let renderContext = RenderContext(documentationContext: context) // Verify render references are created for all topics XCTAssertEqual(Array(renderContext.store.topics.keys.sorted(by: { $0.absoluteString < $1.absoluteString })), context.knownIdentifiers.sorted(by: { $0.absoluteString < $1.absoluteString }), "Didn't create render references for all context topics.") diff --git a/Tests/SwiftDocCTests/Converter/RenderNodeCodableTests.swift b/Tests/SwiftDocCTests/Converter/RenderNodeCodableTests.swift index 2295ec79f7..94c1290118 100644 --- a/Tests/SwiftDocCTests/Converter/RenderNodeCodableTests.swift +++ b/Tests/SwiftDocCTests/Converter/RenderNodeCodableTests.swift @@ -170,7 +170,7 @@ class RenderNodeCodableTests: XCTestCase { } func testEncodeRenderNodeWithCustomTopicSectionStyle() async throws { - let (bundle, context) = try await testBundleAndContext() + let (_, context) = try await testBundleAndContext() var problems = [Problem]() let source = """ @@ -185,7 +185,7 @@ class RenderNodeCodableTests: XCTestCase { let document = Document(parsing: source, options: .parseBlockDirectives) let article = try XCTUnwrap( - Article(from: document.root, source: nil, for: bundle, problems: &problems) + Article(from: document.root, source: nil, for: context.inputs, problems: &problems) ) let reference = ResolvedTopicReference( @@ -203,7 +203,7 @@ class RenderNodeCodableTests: XCTestCase { ) context.topicGraph.addNode(topicGraphNode) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let node = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) XCTAssertEqual(node.topicSectionsStyle, .compactGrid) diff --git a/Tests/SwiftDocCTests/Converter/TopicRenderReferenceEncoderTests.swift b/Tests/SwiftDocCTests/Converter/TopicRenderReferenceEncoderTests.swift index c3014f1983..0686fe3243 100644 --- a/Tests/SwiftDocCTests/Converter/TopicRenderReferenceEncoderTests.swift +++ b/Tests/SwiftDocCTests/Converter/TopicRenderReferenceEncoderTests.swift @@ -156,10 +156,10 @@ class TopicRenderReferenceEncoderTests: XCTestCase { } /// Verifies that when JSON encoder should sort keys, the custom render reference cache - /// respects that setting and prints the referencs in alphabetical order. + /// respects that setting and prints the reference in alphabetical order. func testSortedReferences() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let converter = DocumentationNodeConverter(context: context) // Create a JSON encoder let encoder = RenderJSONEncoder.makeEncoder() @@ -218,8 +218,8 @@ class TopicRenderReferenceEncoderTests: XCTestCase { // Verifies that there is no extra comma at the end of the references list. func testRemovesLastReferencesListDelimiter() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let converter = DocumentationNodeConverter(context: context) // Create a JSON encoder let encoder = RenderJSONEncoder.makeEncoder() diff --git a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift index f8875ee299..8cfbad65db 100644 --- a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift +++ b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift @@ -23,12 +23,10 @@ class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { An empty root page """) ]) - let (bundle, context) = try await loadBundle(catalog: catalog) - + let (_, context) = try await loadBundle(catalog: catalog) let outputConsumer = TestOutputConsumer() _ = try ConvertActionConverter.convert( - bundle: bundle, context: context, outputConsumer: outputConsumer, sourceRepository: nil, @@ -49,12 +47,10 @@ class DeprecatedDiagnosticsDigestWarningTests: XCTestCase { This link will result in a warning: ``NotFound``. """) ]) - let (bundle, context) = try await loadBundle(catalog: catalog) - + let (_, context) = try await loadBundle(catalog: catalog) let outputConsumer = TestOutputConsumer() _ = try ConvertActionConverter.convert( - bundle: bundle, context: context, outputConsumer: outputConsumer, sourceRepository: nil, diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift index 17d945f5c1..f22e888977 100644 --- a/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift @@ -79,7 +79,7 @@ class DiagnosticTests: XCTestCase { /// Test offsetting diagnostic ranges func testOffsetDiagnostics() async throws { - let (bundle, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ + let (_, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ JSONFile(name: "SomeModuleName.symbols.json", content: makeSymbolGraph(moduleName: "SomeModuleName")) ])) @@ -87,7 +87,7 @@ class DiagnosticTests: XCTestCase { let markup = Document(parsing: content, source: URL(string: "/tmp/foo.symbols.json"), options: .parseSymbolLinks) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) - var resolver = ReferenceResolver(context: context, bundle: bundle, rootReference: moduleReference) + var resolver = ReferenceResolver(context: context, rootReference: moduleReference) // Resolve references _ = resolver.visitMarkup(markup) diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index 270d5130a0..1d5ed2b1cc 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -244,16 +244,16 @@ class ExternalRenderNodeTests: XCTestCase { var configuration = DocumentationContext.Configuration() let externalResolver = generateExternalResolver() configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver - let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, configuration: configuration) XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() - let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: context.inputs.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) builder.setup() for externalLink in context.externalCache { - let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: context.inputs.id) try builder.index(renderNode: externalRenderNode) } for identifier in context.knownPages { @@ -347,16 +347,16 @@ class ExternalRenderNodeTests: XCTestCase { var configuration = DocumentationContext.Configuration() let externalResolver = generateExternalResolver() configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver - let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, configuration: configuration) XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() - let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: context.inputs.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) builder.setup() for externalLink in context.externalCache { - let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: context.inputs.id) try builder.index(renderNode: externalRenderNode) } for identifier in context.knownPages { @@ -425,16 +425,16 @@ class ExternalRenderNodeTests: XCTestCase { var configuration = DocumentationContext.Configuration() let externalResolver = generateExternalResolver() configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver - let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, configuration: configuration) XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems.map(\.diagnostic.summary))") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() - let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: context.inputs.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) builder.setup() for externalLink in context.externalCache { - let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: bundle.id) + let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: context.inputs.id) try builder.index(renderNode: externalRenderNode) } for identifier in context.knownPages { diff --git a/Tests/SwiftDocCTests/Indexing/IndexingTests.swift b/Tests/SwiftDocCTests/Indexing/IndexingTests.swift index 758f370cb0..2b83a1e1d8 100644 --- a/Tests/SwiftDocCTests/Indexing/IndexingTests.swift +++ b/Tests/SwiftDocCTests/Indexing/IndexingTests.swift @@ -15,11 +15,11 @@ class IndexingTests: XCTestCase { // MARK: - Tutorial func testTutorial() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let tutorialReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift) let node = try context.entity(with: tutorialReference) let tutorial = node.semantic as! Tutorial - var converter = RenderNodeTranslator(context: context, bundle: bundle, identifier: tutorialReference) + var converter = RenderNodeTranslator(context: context, identifier: tutorialReference) let renderNode = converter.visit(tutorial) as! RenderNode let indexingRecords = try renderNode.indexingRecords(onPage: tutorialReference) XCTAssertEqual(4, indexingRecords.count) @@ -89,11 +89,11 @@ class IndexingTests: XCTestCase { // MARK: - Article func testArticle() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let articleReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/tutorials/Test-Bundle/TestTutorialArticle", sourceLanguage: .swift) let node = try context.entity(with: articleReference) let article = node.semantic as! TutorialArticle - var converter = RenderNodeTranslator(context: context, bundle: bundle, identifier: articleReference) + var converter = RenderNodeTranslator(context: context, identifier: articleReference) let renderNode = converter.visit(article) as! RenderNode let indexingRecords = try renderNode.indexingRecords(onPage: articleReference) @@ -187,11 +187,11 @@ class IndexingTests: XCTestCase { } func testRootPageIndexingRecord() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let articleReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", sourceLanguage: .swift) let node = try context.entity(with: articleReference) let article = node.semantic as! Symbol - var converter = RenderNodeTranslator(context: context, bundle: bundle, identifier: articleReference) + var converter = RenderNodeTranslator(context: context, identifier: articleReference) let renderNode = converter.visit(article) as! RenderNode let indexingRecords = try renderNode.indexingRecords(onPage: articleReference) @@ -207,8 +207,8 @@ class IndexingTests: XCTestCase { } func testSymbolIndexingRecord() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in - // Modify the documentaion to have default availability for MyKit so that there is platform availability + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + // Modify the documentation to have default availability for MyKit so that there is platform availability // information for MyProtocol (both in the render node and in the indexing record. let plistURL = url.appendingPathComponent("Info.plist") let plistData = try Data(contentsOf: plistURL) @@ -224,7 +224,7 @@ class IndexingTests: XCTestCase { let articleReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift) let node = try context.entity(with: articleReference) let article = node.semantic as! Symbol - var converter = RenderNodeTranslator(context: context, bundle: bundle, identifier: articleReference) + var converter = RenderNodeTranslator(context: context, identifier: articleReference) let renderNode = converter.visit(article) as! RenderNode let indexingRecords = try renderNode.indexingRecords(onPage: articleReference) diff --git a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift index 71ea0f8da4..bcab40af1e 100644 --- a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift @@ -457,9 +457,9 @@ Root } func testNavigatorIndexGeneration() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) var results = Set() // Create an index 10 times to ensure we have not non-deterministic behavior across builds @@ -646,10 +646,10 @@ Root """), ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: testBundleIdentifier) @@ -694,12 +694,12 @@ Root } func testNavigatorWithDifferentSwiftAndObjectiveCHierarchies() async throws { - let (_, bundle, context) = try await testBundleAndContext(named: "GeometricalShapes") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, _, context) = try await testBundleAndContext(named: "GeometricalShapes") + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) - let fromMemoryBuilder = NavigatorIndex.Builder(outputURL: try createTemporaryDirectory(), bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) - let fromDecodedBuilder = NavigatorIndex.Builder(outputURL: try createTemporaryDirectory(), bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) + let fromMemoryBuilder = NavigatorIndex.Builder(outputURL: try createTemporaryDirectory(), bundleIdentifier: context.inputs.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) + let fromDecodedBuilder = NavigatorIndex.Builder(outputURL: try createTemporaryDirectory(), bundleIdentifier: context.inputs.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) fromMemoryBuilder.setup() fromDecodedBuilder.setup() @@ -955,9 +955,9 @@ Root } func testNavigatorIndexUsingPageTitleGeneration() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) var results = Set() // Create an index 10 times to ensure we have not non-deterministic behavior across builds @@ -1004,8 +1004,8 @@ Root } func testNavigatorIndexGenerationNoPaths() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let converter = DocumentationNodeConverter(context: context) var results = Set() // Create an index 10 times to ensure we have not non-deterministic behavior across builds @@ -1081,9 +1081,9 @@ Root func testNavigatorIndexGenerationWithCuratedFragment() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) var results = Set() // Create an index 10 times to ensure we have no non-deterministic behavior across builds @@ -1144,9 +1144,9 @@ Root } func testNavigatorIndexAvailabilityGeneration() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: testBundleIdentifier, sortRootChildrenByName: true) @@ -1248,12 +1248,12 @@ Root } func testCustomIconsInNavigator() async throws { - let (bundle, context) = try await testBundleAndContext(named: "BookLikeContent") // This content has a @PageImage with the "icon" purpose - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, context) = try await testBundleAndContext(named: "BookLikeContent") // This content has a @PageImage with the "icon" purpose + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() - let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true) + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: context.inputs.id.rawValue, sortRootChildrenByName: true) builder.setup() for identifier in context.knownPages { @@ -1269,14 +1269,14 @@ Root let imageReference = try XCTUnwrap(renderIndex.references["plus.svg"]) XCTAssertEqual(imageReference.asset.variants.values.map(\.path).sorted(), [ - "/images/\(bundle.id)/plus.svg", + "/images/\(context.inputs.id)/plus.svg", ]) } func testNavigatorIndexDifferentHasherGeneration() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: testBundleIdentifier, sortRootChildrenByName: true) @@ -1723,8 +1723,8 @@ Root } func testNavigatorIndexAsReadOnlyFile() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let converter = DocumentationNodeConverter(context: context) let targetURL = try createTemporaryDirectory() let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: "org.swift.docc.test", sortRootChildrenByName: true) @@ -2050,11 +2050,11 @@ Root var configuration = DocumentationContext.Configuration() configuration.externalMetadata.currentPlatforms = platformMetadata - let (_, bundle, context) = try await testBundleAndContext(named: "AvailabilityBetaBundle", configuration: configuration) - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, _, context) = try await testBundleAndContext(named: "AvailabilityBetaBundle", configuration: configuration) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() - let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true) + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: context.inputs.id.rawValue, sortRootChildrenByName: true) builder.setup() for identifier in context.knownPages { let entity = try context.entity(with: identifier) @@ -2093,9 +2093,9 @@ Root } func generatedNavigatorIndex(for testBundleName: String, bundleIdentifier: String) async throws -> NavigatorIndex { - let (bundle, context) = try await testBundleAndContext(named: testBundleName) - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, context) = try await testBundleAndContext(named: testBundleName) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let targetURL = try createTemporaryDirectory() let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: bundleIdentifier, sortRootChildrenByName: true, groupByLanguage: true) diff --git a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift index acc19fa5a6..d2682c360d 100644 --- a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift @@ -648,10 +648,10 @@ final class RenderIndexTests: XCTestCase { ) try bundle.write(to: bundleDirectory) - let (_, loadedBundle, context) = try await loadBundle(from: bundleDirectory) + let (_, _, context) = try await loadBundle(from: bundleDirectory) XCTAssertEqual( - try generatedRenderIndex(for: loadedBundle, withIdentifier: "com.test.example", withContext: context), + try generatedRenderIndex(forIdentifier: "com.test.example", inContext: context), try RenderIndex.fromString(#""" { "interfaceLanguages": { @@ -737,17 +737,17 @@ final class RenderIndexTests: XCTestCase { } func generatedRenderIndex(for testBundleName: String, with bundleIdentifier: String) async throws -> RenderIndex { - let (bundle, context) = try await testBundleAndContext(named: testBundleName) - return try generatedRenderIndex(for: bundle, withIdentifier: bundleIdentifier, withContext: context) + let (_, context) = try await testBundleAndContext(named: testBundleName) + return try generatedRenderIndex(forIdentifier: bundleIdentifier, inContext: context) } - func generatedRenderIndex(for bundle: DocumentationBundle, withIdentifier bundleIdentifier: String, withContext context: DocumentationContext) throws -> RenderIndex { - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + func generatedRenderIndex(forIdentifier bundleIdentifier: String, inContext context: DocumentationContext) throws -> RenderIndex { + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let indexDirectory = try createTemporaryDirectory() let builder = NavigatorIndex.Builder( outputURL: indexDirectory, - bundleIdentifier: bundleIdentifier, + bundleIdentifier: context.inputs.id.rawValue, sortRootChildrenByName: true ) diff --git a/Tests/SwiftDocCTests/Infrastructure/AnchorSectionTests.swift b/Tests/SwiftDocCTests/Infrastructure/AnchorSectionTests.swift index 968cca594f..ff67afb939 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AnchorSectionTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AnchorSectionTests.swift @@ -17,19 +17,19 @@ import Markdown class AnchorSectionTests: XCTestCase { func testResolvingArticleSubsections() async throws { - let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + let (_, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") // Verify the sub-sections of the article have been collected in the context [ - ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TechnologyX/Article", fragment: "Article-Sub-Section", sourceLanguage: .swift), - ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TechnologyX/Article", fragment: "Article-Sub-Sub-Section", sourceLanguage: .swift), + ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/TechnologyX/Article", fragment: "Article-Sub-Section", sourceLanguage: .swift), + ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/TechnologyX/Article", fragment: "Article-Sub-Sub-Section", sourceLanguage: .swift), ] .forEach { sectionReference in XCTAssertTrue(context.nodeAnchorSections.keys.contains(sectionReference)) } // Load the module page - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/CoolFramework", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/CoolFramework", sourceLanguage: .swift) let entity = try context.entity(with: reference) // Extract the links from the discussion @@ -66,7 +66,7 @@ class AnchorSectionTests: XCTestCase { } // Verify collecting section render references - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(entity) let sectionReference = try XCTUnwrap(renderNode.references["doc://org.swift.docc.example/documentation/TechnologyX/Article#Article-Sub-Section"] as? TopicRenderReference) @@ -75,19 +75,19 @@ class AnchorSectionTests: XCTestCase { } func testResolvingSymbolSubsections() async throws { - let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + let (_, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") // Verify the sub-sections of the article have been collected in the context [ - ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/CoolFramework/CoolClass", fragment: "Symbol-Sub-Section", sourceLanguage: .swift), - ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/CoolFramework/CoolClass", fragment: "Symbol-Sub-Sub-Section", sourceLanguage: .swift), + ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/CoolFramework/CoolClass", fragment: "Symbol-Sub-Section", sourceLanguage: .swift), + ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/CoolFramework/CoolClass", fragment: "Symbol-Sub-Sub-Section", sourceLanguage: .swift), ] .forEach { sectionReference in XCTAssertTrue(context.nodeAnchorSections.keys.contains(sectionReference)) } // Load the module page - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/CoolFramework", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/CoolFramework", sourceLanguage: .swift) let entity = try context.entity(with: reference) // Extract the links from the discussion @@ -124,7 +124,7 @@ class AnchorSectionTests: XCTestCase { } // Verify collecting section render references - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(entity) let sectionReference = try XCTUnwrap(renderNode.references["doc://org.swift.docc.example/documentation/CoolFramework/CoolClass#Symbol-Sub-Section"] as? TopicRenderReference) @@ -133,19 +133,19 @@ class AnchorSectionTests: XCTestCase { } func testResolvingRootPageSubsections() async throws { - let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + let (_, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") // Verify the sub-sections of the article have been collected in the context [ - ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/CoolFramework", fragment: "Module-Sub-Section", sourceLanguage: .swift), - ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/CoolFramework", fragment: "Module-Sub-Sub-Section", sourceLanguage: .swift), + ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/CoolFramework", fragment: "Module-Sub-Section", sourceLanguage: .swift), + ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/CoolFramework", fragment: "Module-Sub-Sub-Section", sourceLanguage: .swift), ] .forEach { sectionReference in XCTAssertTrue(context.nodeAnchorSections.keys.contains(sectionReference)) } // Load the article page - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TechnologyX/Article", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/TechnologyX/Article", sourceLanguage: .swift) let entity = try context.entity(with: reference) // Extract the links from the discussion @@ -182,7 +182,7 @@ class AnchorSectionTests: XCTestCase { } // Verify collecting section render references - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(entity) let sectionReference = try XCTUnwrap(renderNode.references["doc://org.swift.docc.example/documentation/CoolFramework#Module-Sub-Section"] as? TopicRenderReference) diff --git a/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift b/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift index 8eb5ce13c3..f45a2d7119 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift @@ -60,18 +60,18 @@ class AutoCapitalizationTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 0) - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName/functionName(...)", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleName/functionName(...)", sourceLanguage: .swift) let node = try context.entity(with: reference) let symbol = try XCTUnwrap(node.semantic as? Symbol) let parameterSections = symbol.parametersSectionVariants XCTAssertEqual(parameterSections[.swift]?.parameters.map(\.name), ["one", "two", "three", "four", "five"]) let parameterSectionTranslator = ParametersSectionTranslator() - var renderNodeTranslator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var renderNodeTranslator = RenderNodeTranslator(context: context, identifier: reference) var renderNode = renderNodeTranslator.visit(symbol) as! RenderNode let translatedParameters = parameterSectionTranslator.translateSection(for: symbol, renderNode: &renderNode, renderNodeTranslator: &renderNodeTranslator) let paramsRenderSection = translatedParameters?.defaultValue?.section as! ParametersRenderSection @@ -105,18 +105,18 @@ class AutoCapitalizationTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 0) - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName/functionName(...)", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleName/functionName(...)", sourceLanguage: .swift) let node = try context.entity(with: reference) let symbol = try XCTUnwrap(node.semantic as? Symbol) let parameterSections = symbol.parametersSectionVariants XCTAssertEqual(parameterSections[.swift]?.parameters.map(\.name), ["one", "two", "three", "four", "five"]) let parameterSectionTranslator = ParametersSectionTranslator() - var renderNodeTranslator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var renderNodeTranslator = RenderNodeTranslator(context: context, identifier: reference) var renderNode = renderNodeTranslator.visit(symbol) as! RenderNode let translatedParameters = parameterSectionTranslator.translateSection(for: symbol, renderNode: &renderNode, renderNodeTranslator: &renderNodeTranslator) let paramsRenderSection = translatedParameters?.defaultValue?.section as! ParametersRenderSection @@ -146,16 +146,16 @@ class AutoCapitalizationTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: symbolGraph) ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.problems.count, 0) - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName/functionName(...)", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleName/functionName(...)", sourceLanguage: .swift) let node = try context.entity(with: reference) let symbol = try XCTUnwrap(node.semantic as? Symbol) let returnsSectionTranslator = ReturnsSectionTranslator() - var renderNodeTranslator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var renderNodeTranslator = RenderNodeTranslator(context: context, identifier: reference) var renderNode = renderNodeTranslator.visit(symbol) as! RenderNode let translatedReturns = returnsSectionTranslator.translateSection(for: symbol, renderNode: &renderNode, renderNodeTranslator: &renderNodeTranslator) let returnsRenderSection = translatedReturns?.defaultValue?.section as! ContentRenderSection diff --git a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift index c66d094540..d214b77338 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift @@ -38,9 +38,9 @@ class AutomaticCurationTests: XCTestCase { )) ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) - try assertRenderedPage(atPath: "/documentation/ModuleName/SomeClass", containsAutomaticTopicSectionFor: kind, context: context, bundle: bundle) + try assertRenderedPage(atPath: "/documentation/ModuleName/SomeClass", containsAutomaticTopicSectionFor: kind, context: context) } } @@ -83,10 +83,10 @@ class AutomaticCurationTests: XCTestCase { )), ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) - try assertRenderedPage(atPath: "/documentation/ModuleName", containsAutomaticTopicSectionFor: .extendedModule, context: context, bundle: bundle) - try assertRenderedPage(atPath: "/documentation/ModuleName/ExtendedModule", containsAutomaticTopicSectionFor: kind, context: context, bundle: bundle) + try assertRenderedPage(atPath: "/documentation/ModuleName", containsAutomaticTopicSectionFor: .extendedModule, context: context) + try assertRenderedPage(atPath: "/documentation/ModuleName/ExtendedModule", containsAutomaticTopicSectionFor: kind, context: context) } } @@ -94,12 +94,11 @@ class AutomaticCurationTests: XCTestCase { atPath path: String, containsAutomaticTopicSectionFor kind: SymbolGraph.Symbol.KindIdentifier, context: DocumentationContext, - bundle: DocumentationBundle, file: StaticString = #filePath, line: UInt = #line ) throws { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: path, sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: path, sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(node.semantic) as? RenderNode, file: file, line: line) for section in renderNode.topicSections { @@ -117,7 +116,7 @@ class AutomaticCurationTests: XCTestCase { } func testAutomaticTopicsSkippingCustomCuratedSymbols() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in // Curate some of members of SideClass in an API collection try """ # Some API collection @@ -136,11 +135,11 @@ class AutomaticCurationTests: XCTestCase { """.write(to: url.appendingPathComponent("sideclass.md"), atomically: true, encoding: .utf8) }) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) // Compile the render node to flex the automatic curator let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode // Verify that uncurated element `SideKit/SideClass/Element` is @@ -172,7 +171,7 @@ class AutomaticCurationTests: XCTestCase { for curatedIndices in variationsOfChildrenToCurate { let manualCuration = curatedIndices.map { "- <\(allExpectedChildren[$0])>" }.joined(separator: "\n") - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try """ # ``SideKit/SideClass`` @@ -186,10 +185,10 @@ class AutomaticCurationTests: XCTestCase { """.write(to: url.appendingPathComponent("documentation/sideclass.md"), atomically: true, encoding: .utf8) } - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) // Compile docs and verify the generated Topics section let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode // Verify that all the symbols are curated, either manually or automatically @@ -231,7 +230,7 @@ class AutomaticCurationTests: XCTestCase { } func testSeeAlsoSectionForAutomaticallyCuratedTopics() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: url.appendingPathComponent("sidekit.symbols.json"))) // Copy `SideClass` a handful of times @@ -348,8 +347,8 @@ class AutomaticCurationTests: XCTestCase { // The first topic section do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // SideKit includes the "Manually curated" task group and additional automatically created groups. @@ -364,8 +363,8 @@ class AutomaticCurationTests: XCTestCase { // The second topic section do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClassFour", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClassFour", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // The other symbols in the same topic section appear in this See Also section @@ -377,8 +376,8 @@ class AutomaticCurationTests: XCTestCase { // The second topic section do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClassSix", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClassSix", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // The other symbols in the same topic section appear in this See Also section @@ -389,22 +388,22 @@ class AutomaticCurationTests: XCTestCase { // The automatically curated symbols shouldn't have a See Also section do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClassEight", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClassEight", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode XCTAssertNil(renderNode.seeAlsoSections.first, "This symbol was automatically curated and shouldn't have a See Also section") } do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClassNine", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClassNine", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode XCTAssertNil(renderNode.seeAlsoSections.first, "This symbol was automatically curated and shouldn't have a See Also section") } do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClassTen", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClassTen", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode XCTAssertNil(renderNode.seeAlsoSections.first, "This symbol was automatically curated and shouldn't have a See Also section") @@ -421,17 +420,14 @@ class AutomaticCurationTests: XCTestCase { forResource: "TopLevelCuration.symbols", withExtension: "json", subdirectory: "Test Resources")! // Create a test bundle copy with the symbol graph from above - let (bundleURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in try? FileManager.default.copyItem(at: topLevelCurationSGFURL, to: url.appendingPathComponent("TopLevelCuration.symbols.json")) } - defer { - try? FileManager.default.removeItem(at: bundleURL) - } do { // Get the framework render node - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TestBed", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/TestBed", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify that `B` isn't automatically curated under the framework node @@ -443,8 +439,8 @@ class AutomaticCurationTests: XCTestCase { do { // Get the `A` render node - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TestBed/A", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/TestBed/A", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify that `B` was in fact curated under `A` @@ -825,7 +821,7 @@ class AutomaticCurationTests: XCTestCase { // Compile the render node to flex the automatic curator let symbol = protocolDocumentationNode.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: protocolDocumentationNode.reference) + var translator = RenderNodeTranslator(context: context, identifier: protocolDocumentationNode.reference) let renderNode = translator.visit(symbol) as! RenderNode XCTAssertEqual(renderNode.topicSections.count, 2) @@ -1242,12 +1238,12 @@ class AutomaticCurationTests: XCTestCase { """), ]) let catalogURL = try exampleDocumentation.write(inside: createTemporaryDirectory()) - let (_, bundle, context) = try await loadBundle(from: catalogURL) + let (_, _, context) = try await loadBundle(from: catalogURL) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName/SomeClass", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleName/SomeClass", sourceLanguage: .swift)) // Compile docs and verify the generated Topics section - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(node.semantic) as? RenderNode) // Verify that there are no duplicate sections in `SomeClass`'s "Topics" section diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index acc5b9d502..da3461c07f 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -1328,13 +1328,13 @@ class DocumentationContextTests: XCTestCase { """), ] + testData.symbolGraphFiles) - let (bundle, context) = try await loadBundle(catalog: testCatalog) - let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let (_, context) = try await loadBundle(catalog: testCatalog) + let renderContext = RenderContext(documentationContext: context) - let identifier = ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestOverview", sourceLanguage: .swift) + let identifier = ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/TestOverview", sourceLanguage: .swift) let node = try context.entity(with: identifier) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let renderNode = try XCTUnwrap(converter.renderNode(for: node)) XCTAssertEqual( @@ -1898,7 +1898,7 @@ let expected = """ """), ]) let bundleURL = try catalog.write(inside: createTemporaryDirectory()) - let (_, bundle, context) = try await loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) let problems = context.problems XCTAssertEqual(problems.count, 0, "Unexpected problems: \(problems.map(\.diagnostic.summary).sorted())") @@ -1924,7 +1924,7 @@ let expected = """ ) } - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: moduleReference) + var translator = RenderNodeTranslator(context: context, identifier: moduleReference) let renderNode = translator.visit(moduleSymbol) as! RenderNode // Verify that the resolved links rendered as links @@ -2425,7 +2425,7 @@ let expected = """ } func testPrefersNonSymbolsInDocLink() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "SymbolsWithSameNameAsModule") { url in + let (_, _, context) = try await testBundleAndContext(copying: "SymbolsWithSameNameAsModule") { url in // This bundle has a top-level struct named "Wrapper". Adding an article named "Wrapper.md" introduces a possibility for a link collision try """ # An article @@ -2451,8 +2451,8 @@ let expected = """ let moduleReference = try XCTUnwrap(context.rootModules.first) let moduleNode = try context.entity(with: moduleReference) - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let renderNode = try XCTUnwrap(converter.renderNode(for: moduleNode)) let curatedTopic = try XCTUnwrap(renderNode.topicSections.first?.identifiers.first) @@ -2502,7 +2502,7 @@ let expected = """ } func testDeclarationTokenKinds() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let myFunc = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) @@ -2516,7 +2516,7 @@ let expected = """ // Render declaration and compare token kinds with symbol graph let symbol = myFunc.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: myFunc.reference) + var translator = RenderNodeTranslator(context: context, identifier: myFunc.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode let declarationTokens = renderNode.primaryContentSections.mapFirst { section -> [String]? in @@ -2650,13 +2650,13 @@ let expected = """ } func testNavigatorTitle() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") func renderNodeForPath(path: String) throws -> (DocumentationNode, RenderNode) { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: path, sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: path, sourceLanguage: .swift) let node = try context.entity(with: reference) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode return (node, renderNode) @@ -3286,8 +3286,7 @@ let expected = """ ]) // Verify that the links are resolved in the render model. - let bundle = try XCTUnwrap(context.bundle) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(entity) XCTAssertEqual(renderNode.topicSections.map(\.anchor), [ @@ -5480,7 +5479,7 @@ let expected = """ let externalModuleName = "ExternalModuleName" func makeExternalDependencyFiles() async throws -> (SerializableLinkResolutionInformation, [LinkDestinationSummary]) { - let (bundle, context) = try await loadBundle( + let (_, context) = try await loadBundle( catalog: Folder(name: "Dependency.docc", content: [ JSONFile(name: "\(externalModuleName).symbols.json", content: makeSymbolGraph(moduleName: externalModuleName)), TextFile(name: "Extension.md", utf8Content: """ @@ -5492,14 +5491,14 @@ let expected = """ ) // Retrieve the link information from the dependency, as if '--enable-experimental-external-link-support' was passed to DocC - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let linkSummaries: [LinkDestinationSummary] = try context.knownPages.flatMap { reference in let entity = try context.entity(with: reference) let renderNode = try XCTUnwrap(converter.convert(entity)) return entity.externallyLinkableElementSummaries(context: context, renderNode: renderNode, includeTaskGroups: false) } - let linkResolutionInformation = try context.linkResolver.localResolver.prepareForSerialization(bundleID: bundle.id) + let linkResolutionInformation = try context.linkResolver.localResolver.prepareForSerialization(bundleID: context.inputs.id) return (linkResolutionInformation, linkSummaries) } @@ -5521,7 +5520,7 @@ let expected = """ URL(fileURLWithPath: "/path/to/SomeDependency.doccarchive") ] - let (bundle, context) = try await loadBundle( + let (_, context) = try await loadBundle( catalog: catalog, otherFileSystemDirectories: [ Folder(name: "path", content: [ @@ -5540,7 +5539,7 @@ let expected = """ let reference = try XCTUnwrap(context.soleRootModuleReference) let node = try context.entity(with: reference) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(node) let externalReference = "doc://Dependency/documentation/ExternalModuleName" diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift index 84321b58f9..cab832ec10 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift @@ -28,9 +28,9 @@ class DocumentationCuratorTests: XCTestCase { } func testCrawl() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var crawler = DocumentationCurator(in: context, bundle: bundle) + var crawler = DocumentationCurator(in: context) let mykit = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", sourceLanguage: .swift)) var symbolsWithCustomCuration = [ResolvedTopicReference]() @@ -75,7 +75,7 @@ class DocumentationCuratorTests: XCTestCase { } func testCrawlDiagnostics() async throws { - let (tempCatalogURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (tempCatalogURL, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in let extensionFile = url.appendingPathComponent("documentation/myfunction.md") try """ @@ -97,7 +97,7 @@ class DocumentationCuratorTests: XCTestCase { } let extensionFile = tempCatalogURL.appendingPathComponent("documentation/myfunction.md") - var crawler = DocumentationCurator(in: context, bundle: bundle) + var crawler = DocumentationCurator(in: context) let mykit = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", sourceLanguage: .swift)) XCTAssertNoThrow(try crawler.crawlChildren(of: mykit.reference, prepareForCuration: { _ in }, relateNodes: { _, _ in })) @@ -228,7 +228,7 @@ class DocumentationCuratorTests: XCTestCase { - ``NotFound`` """), ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual( context.problems.map(\.diagnostic.summary), [ @@ -266,7 +266,7 @@ class DocumentationCuratorTests: XCTestCase { ) // Verify that the rendered top-level page doesn't have an automatic "Classes" topic section anymore. - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) let rootRenderNode = converter.convert(try context.entity(with: moduleReference)) @@ -286,7 +286,7 @@ class DocumentationCuratorTests: XCTestCase { } func testModuleUnderTechnologyRoot() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "SourceLocations") { url in + let (_, _, context) = try await testBundleAndContext(copying: "SourceLocations") { url in try """ # Root curating a module @@ -303,7 +303,7 @@ class DocumentationCuratorTests: XCTestCase { """.write(to: url.appendingPathComponent("Root.md"), atomically: true, encoding: .utf8) } - let crawler = DocumentationCurator(in: context, bundle: bundle) + let crawler = DocumentationCurator(in: context) XCTAssert(context.problems.isEmpty, "Expected no problems. Found: \(context.problems.map(\.diagnostic.summary))") guard let moduleNode = context.documentationCache["SourceLocations"], @@ -319,7 +319,7 @@ class DocumentationCuratorTests: XCTestCase { } func testCuratorDoesNotRelateNodesWhenArticleLinksContainExtraPathComponents() async throws { - let (bundle, context) = try await loadBundle(catalog: + let (_, context) = try await loadBundle(catalog: Folder(name: "CatalogName.docc", content: [ TextFile(name: "Root.md", utf8Content: """ # Root @@ -395,7 +395,7 @@ class DocumentationCuratorTests: XCTestCase { ]) let rootPage = try context.entity(with: rootReference) - let renderer = DocumentationNodeConverter(bundle: bundle, context: context) + let renderer = DocumentationNodeConverter(context: context) let renderNode = renderer.convert(rootPage) XCTAssertEqual(renderNode.topicSections.map(\.title), [ @@ -459,9 +459,9 @@ class DocumentationCuratorTests: XCTestCase { } func testSymbolLinkResolving() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let crawler = DocumentationCurator(in: context, bundle: bundle) + let crawler = DocumentationCurator(in: context) // Resolve top-level symbol in module parent do { @@ -512,9 +512,9 @@ class DocumentationCuratorTests: XCTestCase { } func testLinkResolving() async throws { - let (sourceRoot, bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (sourceRoot, _, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var crawler = DocumentationCurator(in: context, bundle: bundle) + var crawler = DocumentationCurator(in: context) // Resolve and curate an article in module root (absolute link) do { @@ -567,7 +567,7 @@ class DocumentationCuratorTests: XCTestCase { } func testGroupLinkValidation() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { root in // Create a sidecar with invalid group links try! """ # ``SideKit`` @@ -607,7 +607,7 @@ class DocumentationCuratorTests: XCTestCase { """.write(to: root.appendingPathComponent("documentation").appendingPathComponent("api-collection.md"), atomically: true, encoding: .utf8) } - var crawler = DocumentationCurator(in: context, bundle: bundle) + var crawler = DocumentationCurator(in: context) let reference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit", sourceLanguage: .swift) try crawler.crawlChildren(of: reference, prepareForCuration: {_ in }) { (_, _) in } @@ -661,16 +661,16 @@ class DocumentationCuratorTests: XCTestCase { /// +-- MyArticle ( <--- This should be crawled even if we've mixed manual and automatic curation) /// ``` func testMixedManualAndAutomaticCuration() async throws { - let (bundle, context) = try await testBundleAndContext(named: "MixedManualAutomaticCuration") + let (_, context) = try await testBundleAndContext(named: "MixedManualAutomaticCuration") - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TestBed/TopClass/NestedEnum/SecondLevelNesting", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/TestBed/TopClass/NestedEnum/SecondLevelNesting", sourceLanguage: .swift) let entity = try context.entity(with: reference) let symbol = try XCTUnwrap(entity.semantic as? Symbol) // Verify the link was resolved and it's found in the node's topics task group. XCTAssertEqual("doc://com.test.TestBed/documentation/TestBed/MyArticle", symbol.topics?.taskGroups.first?.links.first?.destination) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(entity) // Verify the article identifier is included in the task group for the render node. @@ -678,7 +678,7 @@ class DocumentationCuratorTests: XCTestCase { // Verify that the ONLY curation for `TopClass/name` is the manual curation under `MyArticle` // and the automatic curation under `TopClass` is not present. - let nameReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TestBed/TopClass/name", sourceLanguage: .swift) + let nameReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/TestBed/TopClass/name", sourceLanguage: .swift) XCTAssertEqual(context.finitePaths(to: nameReference).map({ $0.map(\.path) }), [ ["/documentation/TestBed", "/documentation/TestBed/TopClass", "/documentation/TestBed/TopClass-API-Collection"], ["/documentation/TestBed", "/documentation/TestBed/TopClass", "/documentation/TestBed/TopClass/NestedEnum", "/documentation/TestBed/TopClass/NestedEnum/SecondLevelNesting", "/documentation/TestBed/MyArticle"], @@ -686,7 +686,7 @@ class DocumentationCuratorTests: XCTestCase { // Verify that the BOTH manual curations for `TopClass/age` are preserved // even if one of the manual curations overlaps with the inheritance edge from the symbol graph. - let ageReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/TestBed/TopClass/age", sourceLanguage: .swift) + let ageReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/TestBed/TopClass/age", sourceLanguage: .swift) XCTAssertEqual(context.finitePaths(to: ageReference).map({ $0.map(\.path) }), [ ["/documentation/TestBed", "/documentation/TestBed/TopClass"], ["/documentation/TestBed", "/documentation/TestBed/TopClass", "/documentation/TestBed/TopClass-API-Collection"], diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift index 9a62ddf51e..0d0bab01b5 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift @@ -699,7 +699,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { ) } - let (dependencyBundle, dependencyContext) = try await loadBundle( + let (_ , dependencyContext) = try await loadBundle( catalog: Folder(name: "Dependency.docc", content: [ InfoPlist(identifier: "com.example.dependency"), // This isn't necessary but makes it easier to distinguish the identifier from the module name in the external references. JSONFile(name: "Dependency.symbols.json", content: makeSymbolGraph(moduleName: "Dependency", symbols: symbols)) @@ -707,7 +707,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { ) // Retrieve the link information from the dependency, as if '--enable-experimental-external-link-support' was passed to DocC - let dependencyConverter = DocumentationContextConverter(bundle: dependencyBundle, context: dependencyContext, renderContext: .init(documentationContext: dependencyContext, bundle: dependencyBundle)) + let dependencyConverter = DocumentationContextConverter(context: dependencyContext, renderContext: .init(documentationContext: dependencyContext)) let linkSummaries: [LinkDestinationSummary] = try dependencyContext.knownPages.flatMap { reference in let entity = try dependencyContext.entity(with: reference) @@ -715,7 +715,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { return entity.externallyLinkableElementSummaries(context: dependencyContext, renderNode: renderNode, includeTaskGroups: false) } - let linkResolutionInformation = try dependencyContext.linkResolver.localResolver.prepareForSerialization(bundleID: dependencyBundle.id) + let linkResolutionInformation = try dependencyContext.linkResolver.localResolver.prepareForSerialization(bundleID: dependencyContext.inputs.id) XCTAssertEqual(linkResolutionInformation.pathHierarchy.nodes.count - linkResolutionInformation.nonSymbolPaths.count, 5 /* 4 symbols & 1 module */) XCTAssertEqual(linkSummaries.count, 5 /* 4 symbols & 1 module */) @@ -724,7 +724,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { configuration.externalDocumentationConfiguration.dependencyArchives = [URL(fileURLWithPath: "/Dependency.doccarchive")] // After building the dependency, - let (mainBundle, mainContext) = try await loadBundle( + let (_, mainContext) = try await loadBundle( catalog: Folder(name: "Main.docc", content: [ JSONFile(name: "Main.symbols.json", content: makeSymbolGraph( moduleName: "Main", @@ -794,11 +794,11 @@ class ExternalPathHierarchyResolverTests: XCTestCase { XCTAssertEqual(mainContext.knownPages.count, 3 /* 2 symbols & 1 module*/) - let mainConverter = DocumentationContextConverter(bundle: mainBundle, context: mainContext, renderContext: .init(documentationContext: mainContext, bundle: mainBundle)) + let mainConverter = DocumentationContextConverter(context: mainContext, renderContext: .init(documentationContext: mainContext)) // Check the relationships of 'SomeClass' do { - let reference = ResolvedTopicReference(bundleID: mainBundle.id, path: "/documentation/Main/SomeClass", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: mainContext.inputs.id, path: "/documentation/Main/SomeClass", sourceLanguage: .swift) let entity = try mainContext.entity(with: reference) let renderNode = try XCTUnwrap(mainConverter.renderNode(for: entity)) @@ -822,7 +822,7 @@ class ExternalPathHierarchyResolverTests: XCTestCase { // Check the declaration of 'someFunction' do { - let reference = ResolvedTopicReference(bundleID: mainBundle.id, path: "/documentation/Main/SomeClass/someFunction(parameter:)", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: mainContext.inputs.id, path: "/documentation/Main/SomeClass/someFunction(parameter:)", sourceLanguage: .swift) let entity = try mainContext.entity(with: reference) let renderNode = try XCTUnwrap(mainConverter.renderNode(for: entity)) @@ -991,16 +991,16 @@ class ExternalPathHierarchyResolverTests: XCTestCase { private func makeLinkResolversForTestBundle(named testBundleName: String, configuration: DocumentationContext.Configuration = .init()) async throws -> LinkResolvers { let bundleURL = try XCTUnwrap(Bundle.module.url(forResource: testBundleName, withExtension: "docc", subdirectory: "Test Bundles")) - let (_, bundle, context) = try await loadBundle(from: bundleURL, configuration: configuration) + let (_, _, context) = try await loadBundle(from: bundleURL, configuration: configuration) let localResolver = try XCTUnwrap(context.linkResolver.localResolver) - let resolverInfo = try localResolver.prepareForSerialization(bundleID: bundle.id) + let resolverInfo = try localResolver.prepareForSerialization(bundleID: context.inputs.id) let resolverData = try JSONEncoder().encode(resolverInfo) let roundtripResolverInfo = try JSONDecoder().decode(SerializableLinkResolutionInformation.self, from: resolverData) var entitySummaries = [LinkDestinationSummary]() - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) for reference in context.knownPages { let node = try context.entity(with: reference) let renderNode = converter.convert(node) diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 84cb5e7a11..803868d2ad 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -90,7 +90,7 @@ class ExternalReferenceResolverTests: XCTestCase { // Set the language of the externally resolved entity to 'data'. externalResolver.resolvedEntityLanguage = .data - let (_, bundle, context) = try await testBundleAndContext( + let (_, _, context) = try await testBundleAndContext( copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver] ) { url in @@ -112,9 +112,9 @@ class ExternalReferenceResolverTests: XCTestCase { try sideClassExtension.write(to: sideClassExtensionURL, atomically: true, encoding: .utf8) } - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let sideClassReference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift ) @@ -179,10 +179,10 @@ class ExternalReferenceResolverTests: XCTestCase { externalResolver.resolvedEntityTitle = "ClassName" externalResolver.resolvedEntityKind = resolvedEntityKind - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) + let converter = DocumentationNodeConverter(context: context) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) guard let fileURL = context.documentURL(for: node.reference) else { XCTFail("Unable to find the file for \(node.reference.path)") @@ -221,7 +221,7 @@ class ExternalReferenceResolverTests: XCTestCase { .init(kind: .identifier, spelling: "ClassName", preciseIdentifier: nil), ]) - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in try """ # ``SideKit/SideClass`` @@ -235,8 +235,8 @@ class ExternalReferenceResolverTests: XCTestCase { """.write(to: url.appendingPathComponent("documentation/sideclass.md"), atomically: true, encoding: .utf8) } - let converter = DocumentationNodeConverter(bundle: bundle, context: context) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) + let converter = DocumentationNodeConverter(context: context) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) let renderNode = converter.convert(node) @@ -278,10 +278,10 @@ class ExternalReferenceResolverTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = [externalResolver.bundleID: externalResolver] - let (bundle, context) = try await loadBundle(catalog: tempFolder, configuration: configuration) + let (_, context) = try await loadBundle(catalog: tempFolder, configuration: configuration) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/article", sourceLanguage: .swift)) + let converter = DocumentationNodeConverter(context: context) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/article", sourceLanguage: .swift)) let renderNode = converter.convert(node) @@ -313,7 +313,7 @@ class ExternalReferenceResolverTests: XCTestCase { externalResolver.resolvedEntityTitle = "Name of Sample" externalResolver.resolvedEntityKind = .sampleCode - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [externalResolver.bundleID: externalResolver]) { url in try """ # ``SideKit/SideClass`` @@ -327,8 +327,8 @@ class ExternalReferenceResolverTests: XCTestCase { """.write(to: url.appendingPathComponent("documentation/sideclass.md"), atomically: true, encoding: .utf8) } - let converter = DocumentationNodeConverter(bundle: bundle, context: context) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) + let converter = DocumentationNodeConverter(context: context) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) let renderNode = converter.convert(node) @@ -401,7 +401,7 @@ class ExternalReferenceResolverTests: XCTestCase { ), ] - let (_, bundle, context) = try await testBundleAndContext(copying: "SampleBundle", excludingPaths: ["MySample.md", "MyLocalSample.md"], externalResolvers: [externalResolver.bundleID: externalResolver]) { url in + let (_, _, context) = try await testBundleAndContext(copying: "SampleBundle", excludingPaths: ["MySample.md", "MyLocalSample.md"], externalResolvers: [externalResolver.bundleID: externalResolver]) { url in try """ # SomeSample @@ -426,8 +426,8 @@ class ExternalReferenceResolverTests: XCTestCase { """.write(to: url.appendingPathComponent("SomeSample.md"), atomically: true, encoding: .utf8) } - let converter = DocumentationNodeConverter(bundle: bundle, context: context) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SomeSample", sourceLanguage: .swift)) + let converter = DocumentationNodeConverter(context: context) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SomeSample", sourceLanguage: .swift)) let renderNode = converter.convert(node) @@ -641,7 +641,7 @@ class ExternalReferenceResolverTests: XCTestCase { // Copy the test bundle and add external links to the MyKit See Also. // We're using a See Also group, because external links aren't rendered in Topics groups. - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : resolver]) { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: ["com.external.testbundle" : resolver]) { url in try """ # ``MyKit`` MyKit module root symbol @@ -693,8 +693,8 @@ class ExternalReferenceResolverTests: XCTestCase { "The external reference resolver error message is included in that problem's error summary.") // Get MyKit symbol - let entity = try context.entity(with: .init(bundleID: bundle.id, path: "/documentation/MyKit", sourceLanguage: .swift)) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let entity = try context.entity(with: .init(bundleID: context.inputs.id, path: "/documentation/MyKit", sourceLanguage: .swift)) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(entity) let taskGroupLinks = try XCTUnwrap(renderNode.seeAlsoSections.first?.identifiers) @@ -794,7 +794,7 @@ class ExternalReferenceResolverTests: XCTestCase { ) ) - let (_, bundle, context) = try await testBundleAndContext( + let (_, _, context) = try await testBundleAndContext( copying: "MixedLanguageFramework", externalResolvers: [externalResolver.bundleID: externalResolver] ) { url in @@ -814,9 +814,9 @@ class ExternalReferenceResolverTests: XCTestCase { """ try mixedLanguageFrameworkExtension.write(to: url.appendingPathComponent("/MixedLanguageFramework.md"), atomically: true, encoding: .utf8) } - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let mixedLanguageFrameworkReference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/MixedLanguageFramework", sourceLanguage: .swift ) @@ -945,7 +945,7 @@ class ExternalReferenceResolverTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = [resolver.bundleID: resolver] - let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, configuration: configuration) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -961,10 +961,10 @@ class ExternalReferenceResolverTests: XCTestCase { ]) // Check the rendered SeeAlso sections for the two curated articles. - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) do { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/unit-test/First", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/unit-test/First", sourceLanguage: .swift) let node = try context.entity(with: reference) let rendered = converter.convert(node) @@ -978,7 +978,7 @@ class ExternalReferenceResolverTests: XCTestCase { } do { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/unit-test/Second", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/unit-test/Second", sourceLanguage: .swift) let node = try context.entity(with: reference) let rendered = converter.convert(node) @@ -1013,7 +1013,7 @@ class ExternalReferenceResolverTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = [resolver.bundleID: resolver] - let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, configuration: configuration) XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") @@ -1021,7 +1021,7 @@ class ExternalReferenceResolverTests: XCTestCase { // Check the curation on the root page let reference = try XCTUnwrap(context.soleRootModuleReference) let node = try context.entity(with: reference) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let rendered = converter.convert(node) XCTAssertEqual(rendered.seeAlsoSections.count, 1, "The page should only have the authored See Also section.") diff --git a/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift b/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift index 37157b55c9..4a34e693ca 100644 --- a/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift @@ -24,27 +24,27 @@ class NodeTagsTests: XCTestCase { let tempURL = try createTemporaryDirectory().appendingPathComponent("unit-tests.docc") try bundleFolder.write(to: tempURL) - let (_, bundle, context) = try await loadBundle(from: tempURL) + let (_, _, context) = try await loadBundle(from: tempURL) // Verify that `Test` is marked as SPI. - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Minimal_docs/Test", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/Minimal_docs/Test", sourceLanguage: .swift) let node = try XCTUnwrap(context.entity(with: reference)) let symbol = try XCTUnwrap(node.semantic as? Symbol) XCTAssertTrue(symbol.isSPI) // Verify the render node contains the SPI tag. - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) XCTAssertEqual(renderNode.metadata.tags, [.spi]) // Verify that the link to the node contains the SPI tag. - let moduleReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Minimal_docs", sourceLanguage: .swift) + let moduleReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/Minimal_docs", sourceLanguage: .swift) let moduleNode = try XCTUnwrap(context.entity(with: moduleReference)) let moduleSymbol = try XCTUnwrap(moduleNode.semantic as? Symbol) // Verify the render node contains the SPI tag. - var moduleTranslator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var moduleTranslator = RenderNodeTranslator(context: context, identifier: node.reference) let moduleRenderNode = try XCTUnwrap(moduleTranslator.visit(moduleSymbol) as? RenderNode) let linkReference = try XCTUnwrap(moduleRenderNode.references["doc://com.tests.spi/documentation/Minimal_docs/Test"] as? TopicRenderReference) diff --git a/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift index 3067845fe9..4a37493359 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift @@ -24,11 +24,11 @@ class ReferenceResolverTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try await testBundleAndContext() + let (_, context) = try await testBundleAndContext() var problems = [Problem]() - let intro = Intro(from: directive, source: nil, for: bundle, problems: &problems)! + let intro = Intro(from: directive, source: nil, for: context.inputs, problems: &problems)! - var resolver = ReferenceResolver(context: context, bundle: bundle) + var resolver = ReferenceResolver(context: context) _ = resolver.visitIntro(intro) XCTAssertEqual(resolver.problems.count, 1) } @@ -43,11 +43,11 @@ class ReferenceResolverTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try await testBundleAndContext() + let (_, context) = try await testBundleAndContext() var problems = [Problem]() - let contentAndMedia = ContentAndMedia(from: directive, source: nil, for: bundle, problems: &problems)! + let contentAndMedia = ContentAndMedia(from: directive, source: nil, for: context.inputs, problems: &problems)! - var resolver = ReferenceResolver(context: context, bundle: bundle) + var resolver = ReferenceResolver(context: context) _ = resolver.visit(contentAndMedia) XCTAssertEqual(resolver.problems.count, 1) } @@ -60,11 +60,11 @@ class ReferenceResolverTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try await testBundleAndContext() + let (_, context) = try await testBundleAndContext() var problems = [Problem]() - let intro = Intro(from: directive, source: nil, for: bundle, problems: &problems)! + let intro = Intro(from: directive, source: nil, for: context.inputs, problems: &problems)! - var resolver = ReferenceResolver(context: context, bundle: bundle) + var resolver = ReferenceResolver(context: context) guard let container = resolver.visit(intro).children.first as? MarkupContainer, let firstElement = container.elements.first, @@ -78,7 +78,7 @@ class ReferenceResolverTests: XCTestCase { // Tests all reference syntax formats to a child symbol func testReferencesToChildFromFramework() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit`` @@ -101,7 +101,7 @@ class ReferenceResolverTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify resolved links @@ -111,7 +111,7 @@ class ReferenceResolverTests: XCTestCase { // Test relative paths to non-child symbol func testReferencesToGrandChildFromFramework() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit`` @@ -127,7 +127,7 @@ class ReferenceResolverTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify resolved links @@ -137,7 +137,7 @@ class ReferenceResolverTests: XCTestCase { // Test references to a sibling symbol func testReferencesToSiblingFromFramework() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit/SideClass/myFunction()`` @@ -153,7 +153,7 @@ class ReferenceResolverTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass/myFunction()", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify resolved links @@ -163,7 +163,7 @@ class ReferenceResolverTests: XCTestCase { // Test references to symbols in root paths func testReferencesToTutorial() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit/SideClass/myFunction()`` @@ -179,7 +179,7 @@ class ReferenceResolverTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass/myFunction()", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify resolved links @@ -189,7 +189,7 @@ class ReferenceResolverTests: XCTestCase { // Test references to technology pages func testReferencesToTechnologyPages() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit/SideClass/myFunction()`` @@ -204,7 +204,7 @@ class ReferenceResolverTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass/myFunction()", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify resolved links @@ -214,7 +214,7 @@ class ReferenceResolverTests: XCTestCase { // Test external references func testExternalReferencesConsiderBundleIdentifier() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit/SideClass/myFunction()`` @@ -230,7 +230,7 @@ class ReferenceResolverTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass/myFunction()", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify resolved links @@ -346,7 +346,7 @@ class ReferenceResolverTests: XCTestCase { } func testRelativeReferencesToExtensionSymbols() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "BundleWithRelativePathAmbiguity") { root in + let (_, _, context) = try await testBundleAndContext(copying: "BundleWithRelativePathAmbiguity") { root in // We don't want the external target to be part of the archive as that is not // officially supported yet. try FileManager.default.removeItem(at: root.appendingPathComponent("Dependency.symbols.json")) @@ -378,7 +378,7 @@ class ReferenceResolverTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/BundleWithRelativePathAmbiguity/Dependency", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode let content = try XCTUnwrap(renderNode.primaryContentSections.first as? ContentRenderSection).content @@ -407,10 +407,10 @@ class ReferenceResolverTests: XCTestCase { } func testCuratedExtensionRemovesEmptyPage() async throws { - let (bundle, context) = try await testBundleAndContext(named: "ModuleWithSingleExtension") + let (_, context) = try await testBundleAndContext(named: "ModuleWithSingleExtension") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithSingleExtension", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleWithSingleExtension", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // The only children of the root topic should be the `MyNamespace` enum - i.e. the Swift @@ -421,11 +421,11 @@ class ReferenceResolverTests: XCTestCase { // Make sure that the symbol added in the extension is still present in the topic graph, // even though its synthetic "extended symbol" parents are not - XCTAssertNoThrow(try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithSingleExtension/Swift/Array/asdf", sourceLanguage: .swift))) + XCTAssertNoThrow(try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleWithSingleExtension/Swift/Array/asdf", sourceLanguage: .swift))) } func testCuratedExtensionWithDanglingReference() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "ModuleWithSingleExtension") { root in + let (_, _, context) = try await testBundleAndContext(copying: "ModuleWithSingleExtension") { root in let topLevelArticle = root.appendingPathComponent("ModuleWithSingleExtension.md") try FileManager.default.removeItem(at: topLevelArticle) @@ -445,16 +445,16 @@ class ReferenceResolverTests: XCTestCase { XCTAssertEqual(replacement.replacement, "`Swift/Array`") // Also make sure that the extension pages are still gone - let extendedModule = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithSingleExtension/Swift", sourceLanguage: .swift) + let extendedModule = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleWithSingleExtension/Swift", sourceLanguage: .swift) XCTAssertFalse(context.knownPages.contains(where: { $0 == extendedModule })) - let extendedStructure = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithSingleExtension/Swift/Array", sourceLanguage: .swift) + let extendedStructure = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleWithSingleExtension/Swift/Array", sourceLanguage: .swift) XCTAssertFalse(context.knownPages.contains(where: { $0 == extendedStructure })) // Load the RenderNode for the root article and make sure that the `Swift/Array` symbol link // is not rendered as a link - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithSingleExtension", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleWithSingleExtension", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode XCTAssertEqual(renderNode.abstract, [ @@ -522,10 +522,10 @@ class ReferenceResolverTests: XCTestCase { } func testCuratedExtensionWithAdditionalConformance() async throws { - let (bundle, context) = try await testBundleAndContext(named: "ModuleWithConformanceAndExtension") + let (_, context) = try await testBundleAndContext(named: "ModuleWithConformanceAndExtension") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithConformanceAndExtension/MyProtocol", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleWithConformanceAndExtension/MyProtocol", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode let conformanceSection = try XCTUnwrap(renderNode.relationshipSections.first(where: { $0.type == RelationshipsGroup.Kind.conformingTypes.rawValue })) @@ -538,10 +538,10 @@ class ReferenceResolverTests: XCTestCase { } func testExtensionWithEmptyDeclarationFragments() async throws { - let (bundle, context) = try await testBundleAndContext(named: "ModuleWithEmptyDeclarationFragments") + let (_, context) = try await testBundleAndContext(named: "ModuleWithEmptyDeclarationFragments") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleWithEmptyDeclarationFragments", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleWithEmptyDeclarationFragments", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Despite having an extension to Float, there are no symbols added by that extension, so @@ -560,11 +560,11 @@ class ReferenceResolverTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try await testBundleAndContext() + let (_, context) = try await testBundleAndContext() var problems = [Problem]() - let chapter = try XCTUnwrap(Chapter(from: directive, source: nil, for: bundle, problems: &problems)) - var resolver = ReferenceResolver(context: context, bundle: bundle) + let chapter = try XCTUnwrap(Chapter(from: directive, source: nil, for: context.inputs, problems: &problems)) + var resolver = ReferenceResolver(context: context) _ = resolver.visitChapter(chapter) XCTAssertFalse(resolver.problems.containsErrors) XCTAssertEqual(resolver.problems.count, 1) @@ -580,11 +580,11 @@ class ReferenceResolverTests: XCTestCase { Discussion link to ``SideKit``. """ - let (bundle, context) = try await testBundleAndContext() + let (_, context) = try await testBundleAndContext() let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) let article = try XCTUnwrap(Article(markup: document, metadata: nil, redirects: nil, options: [:])) - var resolver = ReferenceResolver(context: context, bundle: bundle) + var resolver = ReferenceResolver(context: context) let resolvedArticle = try XCTUnwrap(resolver.visitArticle(article) as? Article) let abstractSection = try XCTUnwrap(resolvedArticle.abstractSection) @@ -612,9 +612,9 @@ class ReferenceResolverTests: XCTestCase { } func testForwardsSymbolPropertiesThatAreUnmodifiedDuringLinkResolution() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var resolver = ReferenceResolver(context: context, bundle: bundle) + var resolver = ReferenceResolver(context: context) let symbol = try XCTUnwrap(context.documentationCache["s:5MyKit0A5ClassC"]?.semantic as? Symbol) @@ -768,7 +768,7 @@ class ReferenceResolverTests: XCTestCase { mixins: [:] ) - let (bundle, context) = try await testBundleAndContext() + let (_, context) = try await testBundleAndContext() let documentationExtensionContent = """ # ``Something`` @@ -785,7 +785,7 @@ class ReferenceResolverTests: XCTestCase { let article = Article( from: Document(parsing: documentationExtensionContent, source: documentationExtensionURL, options: [.parseSymbolLinks, .parseBlockDirectives]), source: documentationExtensionURL, - for: bundle, + for: context.inputs, problems: &ignoredProblems ) XCTAssert(ignoredProblems.isEmpty, "Unexpected problems creating article") @@ -801,7 +801,7 @@ class ReferenceResolverTests: XCTestCase { XCTAssertEqual(node.docChunks.count, 2, "This node has content from both the in-source comment and the documentation extension file.") - var resolver = ReferenceResolver(context: context, bundle: bundle) + var resolver = ReferenceResolver(context: context) _ = resolver.visitSymbol(node.semantic as! Symbol) let problems = resolver.problems.sorted(by: \.diagnostic.summary) diff --git a/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift index de88a9ab40..84e39551d5 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift @@ -235,7 +235,7 @@ class SnippetResolverTests: XCTestCase { let reference = try XCTUnwrap(context.soleRootModuleReference, file: file, line: line) let moduleNode = try context.entity(with: reference) - let renderNode = DocumentationNodeConverter(bundle: context.bundle, context: context).convert(moduleNode) + let renderNode = DocumentationNodeConverter(context: context).convert(moduleNode) let renderBlocks = try XCTUnwrap(renderNode.primaryContentSections.first as? ContentRenderSection, file: file, line: line).content diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolBreadcrumbTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolBreadcrumbTests.swift index d1d8f14fae..0389cea89f 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolBreadcrumbTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolBreadcrumbTests.swift @@ -140,7 +140,7 @@ class SymbolBreadcrumbTests: XCTestCase { file: StaticString = #filePath, line: UInt = #line ) { - var hierarchyTranslator = RenderHierarchyTranslator(context: context, bundle: bundle) + var hierarchyTranslator = RenderHierarchyTranslator(context: context) let hierarchyVariants = hierarchyTranslator.visitSymbol(reference) XCTAssertNotNil(hierarchyVariants.defaultValue, "Should always have default breadcrumbs", file: file, line: line) @@ -154,7 +154,7 @@ class SymbolBreadcrumbTests: XCTestCase { file: StaticString = #filePath, line: UInt = #line ) { - var hierarchyTranslator = RenderHierarchyTranslator(context: context, bundle: bundle) + var hierarchyTranslator = RenderHierarchyTranslator(context: context) let hierarchyVariants = hierarchyTranslator.visitSymbol(reference) XCTAssertNotNil(hierarchyVariants.defaultValue, "Should always have default breadcrumbs", file: file, line: line) diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift index 797ac40e49..160722cda1 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolDisambiguationTests.swift @@ -189,12 +189,12 @@ class SymbolDisambiguationTests: XCTestCase { } func testMixedLanguageFramework() async throws { - let (bundle, context) = try await testBundleAndContext(named: "MixedLanguageFramework") + let (inputs, context) = try await testBundleAndContext(named: "MixedLanguageFramework") - var loader = SymbolGraphLoader(bundle: bundle, dataProvider: context.dataProvider) + var loader = SymbolGraphLoader(bundle: inputs, dataProvider: context.dataProvider) try loader.loadAll() - let references = context.linkResolver.localResolver.referencesForSymbols(in: loader.unifiedGraphs, bundle: bundle, context: context).mapValues(\.path) + let references = context.linkResolver.localResolver.referencesForSymbols(in: loader.unifiedGraphs, context: context).mapValues(\.path) XCTAssertEqual(Set(references.keys), [ SymbolGraph.Symbol.Identifier(precise: "c:@CM@TestFramework@objc(cs)MixedLanguageClassConformingToProtocol(im)mixedLanguageMethod", interfaceLanguage: "swift"), .init(precise: "c:@E@Foo", interfaceLanguage: "swift"), @@ -321,7 +321,7 @@ class SymbolDisambiguationTests: XCTestCase { let uniqueSymbolCount = Set(swiftSymbols.map(\.preciseID) + objectiveCSymbols.map(\.preciseID)).count XCTAssertEqual(unified.symbols.count, uniqueSymbolCount) - let bundle = DocumentationBundle( + let inputs = DocumentationBundle( info: DocumentationBundle.Info( displayName: "SymbolDisambiguationTests", id: "com.test.SymbolDisambiguationTests"), @@ -335,8 +335,8 @@ class SymbolDisambiguationTests: XCTestCase { objcSymbolGraphURL: try JSONEncoder().encode(graph2), ], fallback: nil) - let context = try await DocumentationContext(bundle: bundle, dataProvider: provider) + let context = try await DocumentationContext(bundle: inputs, dataProvider: provider) - return context.linkResolver.localResolver.referencesForSymbols(in: ["SymbolDisambiguationTests": unified], bundle: bundle, context: context) + return context.linkResolver.localResolver.referencesForSymbols(in: ["SymbolDisambiguationTests": unified], context: context) } } diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 5606958101..5ac4655d92 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -90,11 +90,11 @@ class LinkDestinationSummaryTests: XCTestCase { InfoPlist(displayName: "TestBundle", identifier: "com.test.example") ]) - let (bundle, context) = try await loadBundle(catalog: catalogHierarchy) + let (_, context) = try await loadBundle(catalog: catalogHierarchy) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestBundle/Tutorial", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/TestBundle/Tutorial", sourceLanguage: .swift)) let renderNode = converter.convert(node) let summaries = node.externallyLinkableElementSummaries(context: context, renderNode: renderNode) @@ -151,8 +151,8 @@ class LinkDestinationSummaryTests: XCTestCase { } func testSymbolSummaries() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let converter = DocumentationNodeConverter(context: context) do { let symbolReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyClass", sourceLanguage: .swift) let node = try context.entity(with: symbolReference) @@ -334,7 +334,7 @@ class LinkDestinationSummaryTests: XCTestCase { } func testTopicImageReferences() async throws { - let (url, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (url, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in let extensionFile = """ # ``MyKit/MyClass/myFunction()`` @@ -349,7 +349,7 @@ class LinkDestinationSummaryTests: XCTestCase { let fileURL = url.appendingPathComponent("documentation").appendingPathComponent("myFunction.md") try extensionFile.write(to: fileURL, atomically: true, encoding: .utf8) } - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) do { let symbolReference = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift) @@ -438,24 +438,24 @@ class LinkDestinationSummaryTests: XCTestCase { summary.references = summary.references?.compactMap { (original: RenderReference) -> (any RenderReference)? in guard var imageRef = original as? ImageReference else { return nil } imageRef.asset.variants = imageRef.asset.variants.mapValues { variant in - return imageRef.destinationURL(for: variant.lastPathComponent, prefixComponent: bundle.id.rawValue) + return imageRef.destinationURL(for: variant.lastPathComponent, prefixComponent: context.inputs.id.rawValue) } imageRef.asset.metadata = .init(uniqueKeysWithValues: imageRef.asset.metadata.map { key, value in - return (imageRef.destinationURL(for: key.lastPathComponent, prefixComponent: bundle.id.rawValue), value) + return (imageRef.destinationURL(for: key.lastPathComponent, prefixComponent: context.inputs.id.rawValue), value) }) return imageRef as (any RenderReference) } - let encoded = try RenderJSONEncoder.makeEncoder(assetPrefixComponent: bundle.id.rawValue).encode(summary) + let encoded = try RenderJSONEncoder.makeEncoder(assetPrefixComponent: context.inputs.id.rawValue).encode(summary) let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded) XCTAssertEqual(decoded, summary) } } func testVariantSummaries() async throws { - let (bundle, context) = try await testBundleAndContext(named: "MixedLanguageFramework") - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let (_, context) = try await testBundleAndContext(named: "MixedLanguageFramework") + let converter = DocumentationNodeConverter(context: context) // Check a symbol that's represented as a class in both Swift and Objective-C do { @@ -801,11 +801,11 @@ class LinkDestinationSummaryTests: XCTestCase { JSONFile(name: "MyModule.symbols.json", content: symbolGraph), InfoPlist(displayName: "MyModule", identifier: "com.example.mymodule") ]) - let (bundle, context) = try await loadBundle(catalog: catalogHierarchy) + let (_, context) = try await loadBundle(catalog: catalogHierarchy) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyModule/MyClass/myFunc()", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyModule/MyClass/myFunc()", sourceLanguage: .swift)) let renderNode = converter.convert(node) let summaries = node.externallyLinkableElementSummaries(context: context, renderNode: renderNode) diff --git a/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift b/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift index 4547e14f40..2a5ddab8bf 100644 --- a/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift +++ b/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift @@ -82,18 +82,18 @@ class PropertyListPossibleValuesSectionTests: XCTestCase { } func testAbsenceOfPossibleValues() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "DictionaryData") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/DictionaryData/Artist", sourceLanguage: .swift)) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let (_, _, context) = try await testBundleAndContext(copying: "DictionaryData") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/DictionaryData/Artist", sourceLanguage: .swift)) + let converter = DocumentationNodeConverter(context: context) // Check that the `Possible Values` section is not rendered if the symbol don't define any possible value. XCTAssertNil(converter.convert(node).primaryContentSections.first(where: { $0.kind == .possibleValues}) as? PossibleValuesRenderSection) } func testUndocumentedPossibleValues() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "DictionaryData") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/DictionaryData/Month", sourceLanguage: .swift)) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let (_, _, context) = try await testBundleAndContext(copying: "DictionaryData") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/DictionaryData/Month", sourceLanguage: .swift)) + let converter = DocumentationNodeConverter(context: context) let possibleValuesSection = try XCTUnwrap(converter.convert(node).primaryContentSections.first(where: { $0.kind == .possibleValues}) as? PossibleValuesRenderSection) let possibleValues: [PossibleValuesRenderSection.NamedValue] = possibleValuesSection.values diff --git a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift index b803126147..5faa07d9e5 100644 --- a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift @@ -68,7 +68,7 @@ class RenderContentMetadataTests: XCTestCase { func testRenderingTables() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ | Column 1 | Column 2 | @@ -109,7 +109,7 @@ class RenderContentMetadataTests: XCTestCase { func testRenderingTableSpans() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ | one | two | three | @@ -162,7 +162,7 @@ class RenderContentMetadataTests: XCTestCase { func testRenderingTableColumnAlignments() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ | one | two | three | four | @@ -204,7 +204,7 @@ class RenderContentMetadataTests: XCTestCase { /// Verifies that a table with `nil` alignments and a table with all-unset alignments still compare as equal. func testRenderedTableEquality() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ | Column 1 | Column 2 | @@ -230,7 +230,7 @@ class RenderContentMetadataTests: XCTestCase { /// Verifies that two tables with otherwise-identical contents but different column alignments compare as unequal. func testRenderedTableInequality() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let decodedTableWithUnsetColumns: RenderBlockContent.Table do { @@ -277,7 +277,7 @@ class RenderContentMetadataTests: XCTestCase { func testStrikethrough() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ ~~Striken~~ text. @@ -300,7 +300,7 @@ class RenderContentMetadataTests: XCTestCase { func testHeadingAnchorShouldBeEncoded() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ ## テスト diff --git a/Tests/SwiftDocCTests/Model/RenderHierarchyTranslatorTests.swift b/Tests/SwiftDocCTests/Model/RenderHierarchyTranslatorTests.swift index 6fe80c94f4..00b8424964 100644 --- a/Tests/SwiftDocCTests/Model/RenderHierarchyTranslatorTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderHierarchyTranslatorTests.swift @@ -16,7 +16,7 @@ class RenderHierarchyTranslatorTests: XCTestCase { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let technologyReference = ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestOverview", sourceLanguage: .swift) - var translator = RenderHierarchyTranslator(context: context, bundle: bundle) + var translator = RenderHierarchyTranslator(context: context) let renderHierarchyVariants = translator.visitTutorialTableOfContentsNode(technologyReference)?.hierarchyVariants XCTAssertEqual(renderHierarchyVariants?.variants, [], "Unexpected variant hierarchies for tutorial table of content page") let renderHierarchy = renderHierarchyVariants?.defaultValue @@ -89,7 +89,7 @@ class RenderHierarchyTranslatorTests: XCTestCase { func testMultiplePaths() async throws { // Curate "TestTutorial" under MyKit as well as TechnologyX. - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in let myKitURL = root.appendingPathComponent("documentation/mykit.md") let text = try String(contentsOf: myKitURL).replacingOccurrences(of: "## Topics", with: """ ## Topics @@ -104,7 +104,7 @@ class RenderHierarchyTranslatorTests: XCTestCase { // Get a translated render node let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift) let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: identifier) + var translator = RenderNodeTranslator(context: context, identifier: identifier) let renderNode = translator.visit(node.semantic) as! RenderNode guard case .tutorials(let hierarchy) = renderNode.hierarchyVariants.defaultValue else { @@ -129,7 +129,7 @@ class RenderHierarchyTranslatorTests: XCTestCase { } func testLanguageSpecificHierarchies() async throws { - let (bundle, context) = try await testBundleAndContext(named: "GeometricalShapes") + let (_, context) = try await testBundleAndContext(named: "GeometricalShapes") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) // An inner function to assert the rendered hierarchy values for a given reference @@ -141,7 +141,7 @@ class RenderHierarchyTranslatorTests: XCTestCase { line: UInt = #line ) throws { let documentationNode = try context.entity(with: reference) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visit(documentationNode.semantic) as? RenderNode, file: file, line: line) if let expectedSwiftPaths { diff --git a/Tests/SwiftDocCTests/Model/RenderNodeDiffingBundleTests.swift b/Tests/SwiftDocCTests/Model/RenderNodeDiffingBundleTests.swift index 35c8d22a56..e910d461bf 100644 --- a/Tests/SwiftDocCTests/Model/RenderNodeDiffingBundleTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderNodeDiffingBundleTests.swift @@ -284,9 +284,9 @@ class RenderNodeDiffingBundleTests: XCTestCase { } func testNoDiffsWhenReconvertingSameBundle() async throws { - let (bundle, context) = try await testBundleAndContext(named: testBundleName) - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let (_, context) = try await testBundleAndContext(named: testBundleName) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) for identifier in context.knownPages { let entity = try context.entity(with: identifier) @@ -303,24 +303,24 @@ class RenderNodeDiffingBundleTests: XCTestCase { topicReferencePath: String, modification: @escaping (URL) throws -> () ) async throws -> JSONPatchDifferences { - let (bundleOriginal, contextOriginal) = try await testBundleAndContext(named: bundleName) + let (_, contextOriginal) = try await testBundleAndContext(named: bundleName) let nodeOriginal = try contextOriginal.entity(with: ResolvedTopicReference(bundleID: bundleID, path: topicReferencePath, sourceLanguage: .swift)) - var renderContext = RenderContext(documentationContext: contextOriginal, bundle: bundleOriginal) - var converter = DocumentationContextConverter(bundle: bundleOriginal, context: contextOriginal, renderContext: renderContext) + var renderContext = RenderContext(documentationContext: contextOriginal) + var converter = DocumentationContextConverter(context: contextOriginal, renderContext: renderContext) let renderNodeOriginal = try XCTUnwrap(converter.renderNode(for: nodeOriginal)) // Make copy of the bundle on disk, modify the document, and write it - let (_, bundleModified, contextModified) = try await testBundleAndContext(copying: bundleName) { url in + let (_, _, contextModified) = try await testBundleAndContext(copying: bundleName) { url in try modification(url) } let nodeModified = try contextModified.entity(with: ResolvedTopicReference(bundleID: bundleID, path: topicReferencePath, sourceLanguage: .swift)) - renderContext = RenderContext(documentationContext: contextModified, bundle: bundleModified) - converter = DocumentationContextConverter(bundle: bundleModified, context: contextModified, renderContext: renderContext) + renderContext = RenderContext(documentationContext: contextModified) + converter = DocumentationContextConverter(context: contextModified, renderContext: renderContext) let renderNodeModified = try XCTUnwrap(converter.renderNode(for: nodeModified)) diff --git a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift index fc63eb88e5..cf1fb17698 100644 --- a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift @@ -92,8 +92,8 @@ class RenderNodeSerializationTests: XCTestCase { } func testBundleRoundTrip() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) guard let tutorialDirective = node.markup as? BlockDirective else { XCTFail("Unexpected document structure, tutorial not found as first child.") @@ -101,22 +101,22 @@ class RenderNodeSerializationTests: XCTestCase { } var problems = [Problem]() - guard let tutorial = Tutorial(from: tutorialDirective, source: nil, for: bundle, problems: &problems) else { + guard let tutorial = Tutorial(from: tutorialDirective, source: nil, for: context.inputs, problems: &problems) else { XCTFail("Couldn't create tutorial from markup: \(problems)") return } XCTAssertEqual(problems.count, 1, "Found problems \(problems.map { DiagnosticConsoleWriter.formattedDescription(for: $0.diagnostic) }) analyzing tutorial markup") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(tutorial) as! RenderNode checkRoundTrip(renderNode) } func testTutorialArticleRoundTrip() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorialArticle", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/Test-Bundle/TestTutorialArticle", sourceLanguage: .swift)) guard let articleDirective = node.markup as? BlockDirective else { XCTFail("Unexpected document structure, article not found as first child.") @@ -124,14 +124,14 @@ class RenderNodeSerializationTests: XCTestCase { } var problems = [Problem]() - guard let article = TutorialArticle(from: articleDirective, source: nil, for: bundle, problems: &problems) else { + guard let article = TutorialArticle(from: articleDirective, source: nil, for: context.inputs, problems: &problems) else { XCTFail("Couldn't create article from markup: \(problems)") return } XCTAssertEqual(problems.count, 0, "Found problems \(problems.map { DiagnosticConsoleWriter.formattedDescription(for: $0.diagnostic) }) analyzing article markup") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(article) as! RenderNode checkRoundTrip(renderNode) @@ -140,8 +140,8 @@ class RenderNodeSerializationTests: XCTestCase { func testAssetReferenceDictionary() async throws { typealias JSONDictionary = [String: Any] - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) guard let tutorialDirective = node.markup as? BlockDirective else { XCTFail("Unexpected document structure, tutorial not found as first child.") @@ -149,14 +149,14 @@ class RenderNodeSerializationTests: XCTestCase { } var problems = [Problem]() - guard let tutorial = Tutorial(from: tutorialDirective, source: nil, for: bundle, problems: &problems) else { + guard let tutorial = Tutorial(from: tutorialDirective, source: nil, for: context.inputs, problems: &problems) else { XCTFail("Couldn't create tutorial from markup: \(problems)") return } XCTAssertEqual(problems.count, 1, "Found problems \(problems.map { DiagnosticConsoleWriter.formattedDescription(for: $0.diagnostic) }) analyzing tutorial markup") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(tutorial) as! RenderNode let data = try encode(renderNode: renderNode) @@ -192,8 +192,8 @@ class RenderNodeSerializationTests: XCTestCase { } func testDiffAvailability() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorialArticle", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/Test-Bundle/TestTutorialArticle", sourceLanguage: .swift)) guard let articleDirective = node.markup as? BlockDirective else { XCTFail("Unexpected document structure, article not found as first child.") @@ -201,12 +201,12 @@ class RenderNodeSerializationTests: XCTestCase { } var problems = [Problem]() - guard let article = TutorialArticle(from: articleDirective, source: nil, for: bundle, problems: &problems) else { + guard let article = TutorialArticle(from: articleDirective, source: nil, for: context.inputs, problems: &problems) else { XCTFail("Couldn't create article from markup: \(problems)") return } - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) var renderNode = translator.visit(article) as! RenderNode diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift index 861d1fd3c3..d3a3fc2446 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift @@ -449,7 +449,7 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { } func testSymbolLinkWorkInMultipleLanguages() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "MixedLanguageFramework") { url in + let (_, _, context) = try await testBundleAndContext(copying: "MixedLanguageFramework") { url in try """ # ``MixedLanguageFramework/Bar`` @@ -466,12 +466,12 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { """.write(to: url.appendingPathComponent("bar.md"), atomically: true, encoding: .utf8) } - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MixedLanguageFramework/Bar", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MixedLanguageFramework/Bar", sourceLanguage: .swift)) let symbol = try XCTUnwrap(node.semantic as? Symbol) XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems)") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) XCTAssert(context.problems.isEmpty, "Encountered unexpected problems: \(context.problems)") @@ -880,7 +880,7 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { } func testAutomaticSeeAlsoSectionElementLimit() async throws { - let (bundle, context) = try await loadBundle(catalog: + let (_, context) = try await loadBundle(catalog: Folder(name: "unit-test.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: (1...50).map { makeSymbol(id: "symbol-id-\($0)", kind: .class, pathComponents: ["SymbolName\($0)"]) @@ -902,7 +902,7 @@ class SemaToRenderNodeMixedLanguageTests: XCTestCase { XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) let moduleNode = try converter.convert(context.entity(with: moduleReference)) diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 78f43efd5e..73b4a508ed 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -32,7 +32,7 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertEqual(problems.count, 1, "Found problems \(DiagnosticConsoleWriter.formattedDescription(for: problems)) analyzing tutorial markup") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(tutorial) as! RenderNode @@ -417,7 +417,7 @@ class SemaToRenderNodeTests: XCTestCase { return } - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(tutorial) as! RenderNode let intro = renderNode.sections.compactMap { $0 as? IntroRenderSection }.first! XCTAssertEqual(RenderReferenceIdentifier(backgroundIdentifier), intro.backgroundImage) @@ -436,7 +436,7 @@ class SemaToRenderNodeTests: XCTestCase { let article = node.semantic as! TutorialArticle - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(article) as! RenderNode @@ -586,7 +586,7 @@ class SemaToRenderNodeTests: XCTestCase { // Verify we emit a diagnostic for the chapter with no tutorial references. XCTAssertEqual(problems.count, expectedProblemsCount, "Found problems \(DiagnosticConsoleWriter.formattedDescription(for: problems)) analyzing tutorial markup") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(tutorialTableOfContents) as! RenderNode @@ -822,7 +822,7 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertEqual(problems.count, 0, "Found problems \(DiagnosticConsoleWriter.formattedDescription(for: problems)) analyzing tutorial markup") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(tutorialTableOfContents) as! RenderNode @@ -958,7 +958,7 @@ class SemaToRenderNodeTests: XCTestCase { let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode guard renderNode.primaryContentSections.count == 4 else { @@ -1217,7 +1217,7 @@ class SemaToRenderNodeTests: XCTestCase { let testBundleURL = Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")! - let (_, bundle, context) = try await loadBundle( + let (_, _, context) = try await loadBundle( from: testBundleURL, externalResolvers: ["com.test.external": TestReferenceResolver()], externalSymbolResolver: TestSymbolResolver() @@ -1237,7 +1237,7 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertNotNil(context.externalCache["s:10Foundation4DataV"]) XCTAssertNotNil(context.externalCache["s:5Foundation0A5NSCodableP"]) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: myProtocol.reference) + var translator = RenderNodeTranslator(context: context, identifier: myProtocol.reference) let renderNode = translator.visit(myProtocolSymbol) as! RenderNode guard renderNode.primaryContentSections.count == 4 else { @@ -1321,7 +1321,7 @@ class SemaToRenderNodeTests: XCTestCase { let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode guard let conf = renderNode.metadata.conformance else { @@ -1341,7 +1341,7 @@ class SemaToRenderNodeTests: XCTestCase { let parent = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let parentSymbol = parent.semantic as! Symbol - var parentTranslator = RenderNodeTranslator(context: context, bundle: bundle, identifier: parent.reference) + var parentTranslator = RenderNodeTranslator(context: context, identifier: parent.reference) let parentRenderNode = parentTranslator.visit(parentSymbol) as! RenderNode guard let functionReference = parentRenderNode.references["doc://org.swift.docc.example/documentation/MyKit/MyClass/myFunction()"] as? TopicRenderReference else { @@ -1373,7 +1373,7 @@ class SemaToRenderNodeTests: XCTestCase { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode // Test conditional conformance for the conforming type @@ -1395,7 +1395,7 @@ class SemaToRenderNodeTests: XCTestCase { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode // Test conditional conformance for the conforming type @@ -1419,7 +1419,7 @@ class SemaToRenderNodeTests: XCTestCase { // Compile docs and verify contents let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode @@ -1484,7 +1484,7 @@ class SemaToRenderNodeTests: XCTestCase { // Compile docs and verify contents let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode @@ -1557,7 +1557,7 @@ class SemaToRenderNodeTests: XCTestCase { // Compile docs and verify contents let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode @@ -1595,7 +1595,7 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertTrue(problems.isEmpty) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(tutorial) as! RenderNode @@ -1641,7 +1641,7 @@ Document @1:1-11:19 markup.debugDescription(options: .printSourceLocations)) let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestTutorial", sourceLanguage: .swift)) + var contentTranslator = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestTutorial", sourceLanguage: .swift)) let renderContent = try XCTUnwrap(markup.children.reduce(into: [], { result, item in result.append(contentsOf: contentTranslator.visit(item))}) as? [RenderBlockContent]) let expectedContent: [RenderBlockContent] = [ .paragraph(.init(inlineContent: [ @@ -1683,7 +1683,7 @@ Document markup.debugDescription()) let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestTutorial", sourceLanguage: .swift)) + var contentTranslator = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestTutorial", sourceLanguage: .swift)) let renderContent = try XCTUnwrap(markup.children.reduce(into: [], { result, item in result.append(contentsOf: contentTranslator.visit(item))}) as? [RenderBlockContent]) let expectedContent: [RenderBlockContent] = [ .paragraph(.init(inlineContent: [ @@ -1707,19 +1707,19 @@ Document let document = Document(parsing: markupSource, options: []) let node = DocumentationNode(reference: ResolvedTopicReference(bundleID: "org.swift.docc", path: "/blah", sourceLanguage: .swift), kind: .article, sourceLanguage: .swift, name: .conceptual(title: "Title"), markup: document, semantic: Semantic()) - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + var translator = RenderNodeTranslator(context: context, identifier: node.reference) XCTAssertNotNil(translator.visit(MarkupContainer(document.children))) } func testCompileSymbolMetadata() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift)) // Compile docs and verify contents let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode @@ -1821,10 +1821,10 @@ Document try content.write(to: targetURL.appendingPathComponent("article2.md"), atomically: true, encoding: .utf8) - let (_, bundle, context) = try await loadBundle(from: targetURL) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Test-Bundle/article2", sourceLanguage: .swift)) + let (_, _, context) = try await loadBundle(from: targetURL) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/Test-Bundle/article2", sourceLanguage: .swift)) let article = node.semantic as! Article - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) return translator.visit(article) as! RenderNode } @@ -1918,7 +1918,7 @@ Document } func testRendersBetaViolators() async throws { - func makeTestBundle(currentPlatforms: [String : PlatformVersion]?, file: StaticString = #filePath, line: UInt = #line, referencePath: String) async throws -> (DocumentationBundle, DocumentationContext, ResolvedTopicReference) { + func makeTestBundle(currentPlatforms: [String : PlatformVersion]?, file: StaticString = #filePath, line: UInt = #line, referencePath: String) async throws -> (DocumentationContext, ResolvedTopicReference) { var configuration = DocumentationContext.Configuration() // Add missing platforms if their fallback platform is present. var currentPlatforms = currentPlatforms ?? [:] @@ -1927,33 +1927,33 @@ Document } configuration.externalMetadata.currentPlatforms = currentPlatforms - let (_, bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) + let (_, _, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) - let reference = ResolvedTopicReference(bundleID: bundle.id, path: referencePath, sourceLanguage: .swift) - return (bundle, context, reference) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: referencePath, sourceLanguage: .swift) + return (context, reference) } // Not a beta platform do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: nil, referencePath: "/documentation/MyKit/globalFunction(_:considering:)") + let (context, reference) = try await makeTestBundle(currentPlatforms: nil, referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) - let renderNode = DocumentationNodeConverter(bundle: bundle, context: context).convert(node) + let renderNode = DocumentationNodeConverter(context: context).convert(node) // Verify platform beta was plumbed all the way to the render JSON XCTAssertEqual(renderNode.metadata.platforms?.first?.isBeta, false) } - // Symbol with an empty set of availbility items. + // Symbol with an empty set of availability items. do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "Custom Name": PlatformVersion(VersionTriplet(100, 0, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) (node.semantic as? Symbol)?.availability = SymbolGraph.Symbol.Availability(availability: []) - let documentationContentRendered = DocumentationContentRenderer(documentationContext: context, bundle: bundle) + let documentationContentRendered = DocumentationContentRenderer(context: context) let isBeta = documentationContentRendered.isBeta(node) // Verify that the symbol is not beta since it does not contains availability info. XCTAssertFalse(isBeta) @@ -1961,12 +1961,12 @@ Document // Different platform is beta do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "tvOS": PlatformVersion(VersionTriplet(100, 0, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) - let renderNode = DocumentationNodeConverter(bundle: bundle, context: context).convert(node) + let renderNode = DocumentationNodeConverter(context: context).convert(node) // Verify platform beta was plumbed all the way to the render JSON XCTAssertEqual(renderNode.metadata.platforms?.first?.isBeta, false) @@ -1975,12 +1975,12 @@ Document // Beta platform but *not* matching the introduced version do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(100, 0, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) - let renderNode = DocumentationNodeConverter(bundle: bundle, context: context).convert(node) + let renderNode = DocumentationNodeConverter(context: context).convert(node) // Verify platform beta was plumbed all the way to the render JSON XCTAssertEqual(renderNode.metadata.platforms?.first?.isBeta, false) @@ -1989,12 +1989,12 @@ Document // Beta platform matching the introduced version do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) - let renderNode = DocumentationNodeConverter(bundle: bundle, context: context).convert(node) + let renderNode = DocumentationNodeConverter(context: context).convert(node) // Verify platform beta was plumbed all the way to the render JSON XCTAssertEqual(renderNode.metadata.platforms?.first(where: { $0.name == "macOS"})?.isBeta, true) @@ -2003,12 +2003,12 @@ Document // Beta platform earlier than the introduced version do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 14, 0), beta: true) ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) - let renderNode = DocumentationNodeConverter(bundle: bundle, context: context).convert(node) + let renderNode = DocumentationNodeConverter(context: context).convert(node) // Verify platform beta was plumbed all the way to the render JSON XCTAssertEqual(renderNode.metadata.platforms?.first(where: { $0.name == "macOS" })?.isBeta, true) @@ -2017,14 +2017,14 @@ Document // Set only some platforms to beta & the exact version globalFunction is being introduced at do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(9, 0, 0), beta: true), "tvOS": PlatformVersion(VersionTriplet(1, 0, 0), beta: true), ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) - let renderNode = DocumentationNodeConverter(bundle: bundle, context: context).convert(node) + let renderNode = DocumentationNodeConverter(context: context).convert(node) // Verify task group link is not in beta betas "iOS" is not being marked as beta XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/MyKit/globalFunction(_:considering:)"] as? TopicRenderReference)?.isBeta, false) @@ -2032,7 +2032,7 @@ Document // Set all platforms to beta & the exact version globalFunction is being introduced at to test beta SDK documentation do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(6, 0, 0), beta: true), "tvOS": PlatformVersion(VersionTriplet(13, 0, 0), beta: true), @@ -2040,7 +2040,7 @@ Document ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) - let renderNode = try XCTUnwrap(DocumentationNodeConverter(bundle: bundle, context: context).convert(node)) + let renderNode = try XCTUnwrap(DocumentationNodeConverter(context: context).convert(node)) // Verify task group link is beta XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/MyKit/globalFunction(_:considering:)"] as? TopicRenderReference)?.isBeta, true) @@ -2048,7 +2048,7 @@ Document // Set all platforms to beta where the symbol is available, // some platforms not beta but the symbol is not available there. - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(6, 0, 0), beta: true), "tvOS": PlatformVersion(VersionTriplet(13, 0, 0), beta: true), @@ -2058,7 +2058,7 @@ Document ], referencePath: "/documentation/MyKit/globalFunction(_:considering:)") let node = try context.entity(with: reference) - let renderNode = try XCTUnwrap(DocumentationNodeConverter(bundle: bundle, context: context).convert(node)) + let renderNode = try XCTUnwrap(DocumentationNodeConverter(context: context).convert(node)) // Verify task group link is beta XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/MyKit/globalFunction(_:considering:)"] as? TopicRenderReference)?.isBeta, true) @@ -2071,16 +2071,16 @@ Document renderReferenceSymbol.availability?.availability.append(SymbolGraph.Symbol.Availability.AvailabilityItem(domain: SymbolGraph.Symbol.Availability.Domain(rawValue: "ImaginaryOS"), introducedVersion: nil, deprecatedVersion: nil, obsoletedVersion: nil, message: nil, renamed: nil, isUnconditionallyDeprecated: false, isUnconditionallyUnavailable: true, willEventuallyBeDeprecated: false)) // Verify the rendered reference - let renderNode = try XCTUnwrap(DocumentationNodeConverter(bundle: bundle, context: context).convert(node)) + let renderNode = try XCTUnwrap(DocumentationNodeConverter(context: context).convert(node)) // Verify task group link is beta XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/MyKit/globalFunction(_:considering:)"] as? TopicRenderReference)?.isBeta, true) } // Set all platforms to beta & the exact version MyClass is being introduced. - // Expect the symbol to no be in beta sinceit does not have an introduced version for iOS + // Expect the symbol to no be in beta since it does not have an introduced version for iOS do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "macOS": PlatformVersion(VersionTriplet(10, 15, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(6, 0, 0), beta: true), "tvOS": PlatformVersion(VersionTriplet(13, 0, 0), beta: true), @@ -2088,7 +2088,7 @@ Document ], referencePath: "/documentation/MyKit") let node = try context.entity(with: reference) - let renderNode = try XCTUnwrap(DocumentationNodeConverter(bundle: bundle, context: context).convert(node)) + let renderNode = try XCTUnwrap(DocumentationNodeConverter(context: context).convert(node)) // Verify task group link is not in beta because `iOS` does not have an introduced version XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/MyKit/MyClass"] as? TopicRenderReference)?.isBeta, false) @@ -2096,12 +2096,12 @@ Document // Set all platforms as unconditionally unavailable and test that the symbol is not marked as beta. do { - let (bundle, context, reference) = try await makeTestBundle(currentPlatforms: [ + let (context, reference) = try await makeTestBundle(currentPlatforms: [ "iOS": PlatformVersion(VersionTriplet(100, 0, 0), beta: true) ], referencePath: "/documentation/MyKit/MyClass") let node = try context.entity(with: reference) (node.semantic as? Symbol)?.availability = SymbolGraph.Symbol.Availability(availability: [.init(domain: SymbolGraph.Symbol.Availability.Domain(rawValue: "iOS"), introducedVersion: nil, deprecatedVersion: nil, obsoletedVersion: nil, message: nil, renamed: nil, isUnconditionallyDeprecated: false, isUnconditionallyUnavailable: true, willEventuallyBeDeprecated: false)]) - let documentationContentRendered = DocumentationContentRenderer(documentationContext: context, bundle: bundle) + let documentationContentRendered = DocumentationContentRenderer(context: context) let isBeta = documentationContentRendered.isBeta(node) // Verify that the symbol is not beta since it's unavailable in all the platforms. XCTAssertFalse(isBeta) @@ -2109,22 +2109,22 @@ Document } func testRendersDeprecatedViolator() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Make the referenced symbol deprecated do { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift) let node = try context.entity(with: reference) (node.semantic as? Symbol)?.availability = SymbolGraph.Symbol.Availability(availability: [ SymbolGraph.Symbol.Availability.AvailabilityItem(domain: .init(rawValue: "iOS"), introducedVersion: nil, deprecatedVersion: .init(major: 13, minor: 0, patch: 0), obsoletedVersion: nil, message: nil, renamed: nil, isUnconditionallyDeprecated: false, isUnconditionallyUnavailable: false, willEventuallyBeDeprecated: false), ]) } - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift) let node = try context.entity(with: reference) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode // The reference is deprecated on all platforms @@ -2132,11 +2132,11 @@ Document } func testDoesNotRenderDeprecatedViolator() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Make the referenced symbol deprecated do { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift) let node = try context.entity(with: reference) (node.semantic as? Symbol)?.availability = SymbolGraph.Symbol.Availability(availability: [ SymbolGraph.Symbol.Availability.AvailabilityItem(domain: .init(rawValue: "iOS"), introducedVersion: .init(major: 13, minor: 0, patch: 0), deprecatedVersion: nil, obsoletedVersion: nil, message: nil, renamed: nil, isUnconditionallyDeprecated: false, isUnconditionallyUnavailable: false, willEventuallyBeDeprecated: false), @@ -2144,11 +2144,11 @@ Document ]) } - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift) let node = try context.entity(with: reference) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode // The reference is not deprecated on all platforms @@ -2156,11 +2156,11 @@ Document } func testRendersDeprecatedViolatorForUnconditionallyDeprecatedReference() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Make the referenced symbol deprecated do { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift) let node = try context.entity(with: reference) (node.semantic as? Symbol)?.availability = SymbolGraph.Symbol.Availability(availability: [ SymbolGraph.Symbol.Availability.AvailabilityItem(domain: .init(rawValue: "iOS"), introducedVersion: .init(major: 13, minor: 0, patch: 0), deprecatedVersion: nil, obsoletedVersion: nil, message: nil, renamed: nil, isUnconditionallyDeprecated: true, isUnconditionallyUnavailable: false, willEventuallyBeDeprecated: false), @@ -2168,11 +2168,11 @@ Document ]) } - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift) let node = try context.entity(with: reference) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode // Verify that the reference is deprecated on all platforms @@ -2180,13 +2180,12 @@ Document } func testRenderMetadataFragments() async throws { - // Check for fragments in metadata in render node - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode guard let fragments = renderNode.metadata.fragments else { @@ -2202,24 +2201,24 @@ Document } func testRenderMetadataExtendedModule() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) XCTAssertEqual(renderNode.metadata.extendedModule, "MyKit") } func testDefaultImplementations() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Verify that the render reference to a required symbol includes the 'required' key and the number of default implementations provided. do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideProtocol", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideProtocol", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode let requiredFuncReference = try XCTUnwrap(renderNode.references["doc://org.swift.docc.example/documentation/SideKit/SideProtocol/func()"]) @@ -2230,9 +2229,9 @@ Document // Verify that a required symbol includes a required metadata and default implementations do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideProtocol/func()", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideProtocol/func()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode // Verify that the render reference to a required symbol includes the 'required' key and the number of default implementations provided. @@ -2246,13 +2245,13 @@ Document } func testDefaultImplementationsNotListedInTopics() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Verify that a required symbol does not include default implementations in Topics groups do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideProtocol/func()", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideProtocol/func()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode // Test that default implementations are listed ONLY under Default Implementations and not Topics @@ -2262,13 +2261,12 @@ Document } func testNoStringMetadata() async throws { - // Check for fragments in metadata in render node - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode let encoded = try JSONEncoder().encode(renderNode) @@ -2291,13 +2289,12 @@ Document } func testRenderDeclarations() async throws { - // Check for fragments in metadata in render node - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode @@ -2312,11 +2309,11 @@ Document func testDocumentationRenderReferenceRoles() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode @@ -2332,11 +2329,11 @@ Document func testTutorialsRenderReferenceRoles() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestOverview", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/TestOverview", sourceLanguage: .swift)) let symbol = node.semantic as! TutorialTableOfContents - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode @@ -2351,9 +2348,9 @@ Document func testRemovingTrailingNewLinesInDeclaration() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/globalFunction(_:considering:)", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/globalFunction(_:considering:)", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol // Subheading with trailing "\n" @@ -2362,7 +2359,7 @@ Document // Navigator title with trailing "\n" XCTAssertEqual(symbol.navigator?.count, 11) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode // Verify trailing newline removed from subheading @@ -2374,11 +2371,11 @@ Document func testRenderManualSeeAlsoInArticles() async throws { // Check for fragments in metadata in render node - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Test-Bundle/article", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/Test-Bundle/article", sourceLanguage: .swift)) let article = node.semantic as! Article - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(article) as! RenderNode @@ -2397,10 +2394,10 @@ Document func testSafeSectionAnchorNames() async throws { // Check that heading's anchor was safe-ified - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode @@ -2417,13 +2414,13 @@ Document } func testDuplicateNavigatorTitleIsRemoved() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/globalFunction(_:considering:)", sourceLanguage: .swift) + let myFuncReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/globalFunction(_:considering:)", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) translator.collectedTopicReferences.append(myFuncReference) let renderNode = translator.visit(symbol) as! RenderNode @@ -2433,13 +2430,13 @@ Document } func testNonDuplicateNavigatorTitleIsRendered() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift) + let myFuncReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode let renderReference = try XCTUnwrap(renderNode.references[myFuncReference.absoluteString] as? TopicRenderReference) @@ -2476,7 +2473,7 @@ Document ] func testBareTechnology() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try """ @Tutorials(name: "<#text#>") { @Intro(title: "<#text#>") { @@ -2492,7 +2489,7 @@ Document """.write(to: url.appendingPathComponent("TestOverview.tutorial"), atomically: true, encoding: .utf8) } - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestOverview", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/TestOverview", sourceLanguage: .swift)) guard let tutorialTableOfContentsDirective = node.markup as? BlockDirective else { XCTFail("Unexpected document structure, tutorial table-of-contents not found as first child.") @@ -2500,38 +2497,38 @@ Document } var problems = [Problem]() - guard let tutorialTableOfContents = TutorialTableOfContents(from: tutorialTableOfContentsDirective, source: nil, for: bundle, problems: &problems) else { + guard let tutorialTableOfContents = TutorialTableOfContents(from: tutorialTableOfContentsDirective, source: nil, for: context.inputs, problems: &problems) else { XCTFail("Couldn't create tutorial from markup: \(problems)") return } XCTAssert(problems.filter { $0.diagnostic.severity == .error }.isEmpty, "Found errors when analyzing Tutorials overview.") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) // Verify we don't crash. _ = translator.visit(tutorialTableOfContents) do { - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) guard let technologyDirective = node.markup as? BlockDirective else { XCTFail("Unexpected document structure, tutorial not found as first child.") return } - guard let tutorial = Tutorial(from: technologyDirective, source: nil, for: bundle, problems: &problems) else { + guard let tutorial = Tutorial(from: technologyDirective, source: nil, for: context.inputs, problems: &problems) else { XCTFail("Couldn't create tutorial from markup: \(problems)") return } - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) XCTAssertNil(translator.visit(tutorial), "Render node for uncurated tutorial should not have been produced") } } func testBareTutorial() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try """ @Tutorial(time: <#number#>, projectFiles: <#.zip#>) { @Intro(title: "<#text#>") { @@ -2584,7 +2581,7 @@ Document """.write(to: url.appendingPathComponent("TestTutorial.tutorial"), atomically: true, encoding: .utf8) } - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/Test-Bundle/TestTutorial", sourceLanguage: .swift)) guard let technologyDirective = node.markup as? BlockDirective else { XCTFail("Unexpected document structure, tutorial not found as first child.") @@ -2592,14 +2589,14 @@ Document } var problems = [Problem]() - guard let tutorial = Tutorial(from: technologyDirective, source: nil, for: bundle, problems: &problems) else { + guard let tutorial = Tutorial(from: technologyDirective, source: nil, for: context.inputs, problems: &problems) else { XCTFail("Couldn't create tutorial from markup: \(problems)") return } XCTAssert(problems.filter { $0.diagnostic.severity == .error }.isEmpty, "Found errors when analyzing tutorial.") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) // Verify we don't crash. _ = translator.visit(tutorial) @@ -2609,27 +2606,24 @@ Document func testRenderAsides() async throws { let asidesSGFURL = Bundle.module.url( forResource: "Asides.symbols", withExtension: "json", subdirectory: "Test Resources")! - let (bundleURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in try? FileManager.default.copyItem(at: asidesSGFURL, to: url.appendingPathComponent("Asides.symbols.json")) } - defer { - try? FileManager.default.removeItem(at: bundleURL) - } // Both of these symbols have the same content; one just has its asides as list items and the other has blockquotes. let testReference: (ResolvedTopicReference) throws -> () = { myFuncReference in let node = try context.entity(with: myFuncReference) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(symbol) as! RenderNode let asides = try XCTUnwrap(renderNode.primaryContentSections.first(where: { $0.kind == .content }) as? ContentRenderSection) XCTAssertEqual(Array(asides.content.dropFirst()), self.asidesStressTest) } - let dashReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Asides/dashAsides()", sourceLanguage: .swift) - let quoteReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Asides/quoteAsides()", sourceLanguage: .swift) + let dashReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/Asides/dashAsides()", sourceLanguage: .swift) + let quoteReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/Asides/quoteAsides()", sourceLanguage: .swift) try testReference(dashReference) try testReference(quoteReference) @@ -2654,20 +2648,20 @@ Document let sgURL = Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols", withExtension: "json", subdirectory: "Test Bundles")! - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in // Replace the out-of-bundle origin with a symbol from the same bundle. try String(contentsOf: sgURL) .replacingOccurrences(of: #"identifier" : "s:OriginalUSR"#, with: #"identifier" : "s:5MyKit0A5MyProtocol0Afunc()"#) .write(to: url.appendingPathComponent("sidekit.symbols.json"), atomically: true, encoding: .utf8) }) - let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) + let myFuncReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) let symbol = try XCTUnwrap(node.semantic as? Symbol) // Verify that by default we inherit docs. do { - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) // Verify the expected inherited abstract text. @@ -2680,7 +2674,7 @@ Document let sgURL = Bundle.module.url( forResource: "LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols", withExtension: "json", subdirectory: "Test Bundles")! - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in // Replace the out-of-bundle origin with a symbol from the same bundle but // from the MyKit module. try String(contentsOf: sgURL) @@ -2688,13 +2682,13 @@ Document .write(to: url.appendingPathComponent("sidekit.symbols.json"), atomically: true, encoding: .utf8) }) - let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) + let myFuncReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) let symbol = try XCTUnwrap(node.semantic as? Symbol) // Verify that by default we inherit docs. do { - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) // Verify the expected default abstract text. @@ -2703,7 +2697,7 @@ Document } /// Tests that we generated an automatic abstract and remove source docs. func testDisabledDocInheritance() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") // Verify that the inherited docs which should be ignored are not reference resolved. // Verify inherited docs are reference resolved and their problems are recorded. @@ -2714,13 +2708,13 @@ Document return p.diagnostic.summary == "Resource 'my-inherited-image.png' couldn't be found" })) - let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) + let myFuncReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) let symbol = try XCTUnwrap(node.semantic as? Symbol) // Verify that by default we don't inherit docs and we generate default abstract. do { - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) // Verify the expected default abstract text. @@ -2734,7 +2728,7 @@ Document /// Tests doc extensions are matched to inherited symbols func testInheritedSymbolDocExtension() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try? """ # ``SideKit/SideClass/Element/inherited()`` Doc extension abstract. @@ -2743,13 +2737,13 @@ Document """.write(to: url.appendingPathComponent("inherited.md"), atomically: true, encoding: .utf8) }) - let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) + let myFuncReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) let symbol = try XCTUnwrap(node.semantic as? Symbol) // Verify the doc extension was matched to the inherited symbol. do { - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) // Verify the expected default abstract text. @@ -2877,7 +2871,7 @@ Document for testData in testData { let sgURL = Bundle.module.url(forResource: "LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols", withExtension: "json", subdirectory: "Test Bundles")! - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in // Replace the out-of-bundle origin with a symbol from the same bundle but // from the MyKit module. var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: sgURL)) @@ -2888,13 +2882,13 @@ Document .write(to: url.appendingPathComponent("sidekit.symbols.json")) }) - let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) + let myFuncReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) let symbol = try XCTUnwrap(node.semantic as? Symbol) // Verify the doc extension was matched to the inherited symbol. do { - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) // Verify the expected default abstract text. @@ -2911,20 +2905,20 @@ Document var configuration = DocumentationContext.Configuration() configuration.externalMetadata.inheritDocs = true - let (_, bundle, context) = try await loadBundle(from: bundleURL, configuration: configuration) + let (_, _, context) = try await loadBundle(from: bundleURL, configuration: configuration) // Verify that we don't reference resolve inherited docs. XCTAssertFalse(context.diagnosticEngine.problems.contains(where: { problem in problem.diagnostic.summary.contains("my-inherited-image.png") })) - let myFuncReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) + let myFuncReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass/Element/inherited()", sourceLanguage: .swift) let node = try context.entity(with: myFuncReference) let symbol = try XCTUnwrap(node.semantic as? Symbol) // Verify that by default we don't inherit docs and we generate default abstract. do { - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) // Verify the expected default abstract text. @@ -2951,13 +2945,13 @@ Document // Verifies that undocumented symbol gets a nil abstract. func testNonDocumentedSymbolNilAbstract() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/globalFunction(_:considering:)", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/globalFunction(_:considering:)", sourceLanguage: .swift) let node = try context.entity(with: reference) let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) // Verify that an undocumented symbol gets a nil abstract. @@ -3059,7 +3053,7 @@ Document /// Tests links to symbols that have deprecation summary in markdown appear deprecated. func testLinkToDeprecatedSymbolViaDirectiveIsDeprecated() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try """ # ``MyKit/MyProtocol`` @DeprecationSummary { @@ -3068,10 +3062,10 @@ Document """.write(to: url.appendingPathComponent("documentation").appendingPathComponent("myprotocol.md"), atomically: true, encoding: .utf8) }) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit", sourceLanguage: .swift)) let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode) let reference = try XCTUnwrap(renderNode.references["doc://org.swift.docc.example/documentation/MyKit/MyProtocol"] as? TopicRenderReference) @@ -3079,7 +3073,7 @@ Document } func testCustomSymbolDisplayNames() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], externalResolvers: [:], externalSymbolResolver: nil, configureBundle: { url in try """ # ``MyKit`` @@ -3109,9 +3103,9 @@ Document """.write(to: url.appendingPathComponent("documentation").appendingPathComponent("myprotocol.md"), atomically: true, encoding: .utf8) }) - let moduleReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit", sourceLanguage: .swift) - let protocolReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift) - let functionReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift) + let moduleReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit", sourceLanguage: .swift) + let protocolReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyProtocol", sourceLanguage: .swift) + let functionReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift) // Verify the MyKit module @@ -3124,7 +3118,7 @@ Document XCTAssertEqual(titleVariant.variant, "My custom conceptual name") } - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: moduleNode.reference) + var translator = RenderNodeTranslator(context: context, identifier: moduleNode.reference) let moduleRenderNode = try XCTUnwrap(translator.visit(moduleSymbol) as? RenderNode) XCTAssertEqual(moduleRenderNode.metadata.title, "My custom conceptual name") @@ -3170,7 +3164,7 @@ Document let functionNode = try context.entity(with: functionReference) let functionSymbol = try XCTUnwrap(functionNode.semantic as? Symbol) - translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: functionNode.reference) + translator = RenderNodeTranslator(context: context, identifier: functionNode.reference) let functionRenderNode = try XCTUnwrap(translator.visit(functionSymbol) as? RenderNode) XCTAssertTrue(functionRenderNode.metadata.modulesVariants.variants.isEmpty) // Test that the symbol name `MyKit` is not added as a related module. @@ -3209,7 +3203,7 @@ Document } func testVisitTutorialMediaWithoutExtension() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { url in try """ @Tutorials(name: "Technology X") { @Intro(title: "Technology X") { @@ -3231,17 +3225,17 @@ Document } """.write(to: url.appendingPathComponent("TestOverview.tutorial"), atomically: true, encoding: .utf8) } - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/tutorials/TestOverview", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/tutorials/TestOverview", sourceLanguage: .swift)) guard let technologyDirective = node.markup as? BlockDirective else { XCTFail("Unexpected document structure, tutorial not found as first child.") return } var problems = [Problem]() - guard let tutorialTableOfContents = TutorialTableOfContents(from: technologyDirective, source: nil, for: bundle, problems: &problems) else { + guard let tutorialTableOfContents = TutorialTableOfContents(from: technologyDirective, source: nil, for: context.inputs, problems: &problems) else { XCTFail("Couldn't create technology from markup: \(problems)") return } - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(tutorialTableOfContents) as? RenderNode) XCTAssertEqual(renderNode.references.count, 5) XCTAssertNotNil(renderNode.references["doc://org.swift.docc.example/tutorials/Test-Bundle/TestTutorial"] as? TopicRenderReference) @@ -3255,7 +3249,7 @@ Document } func testTopicsSectionWithAnonymousTopicGroup() async throws { - let (_, bundle, context) = try await testBundleAndContext( + let (_, _, context) = try await testBundleAndContext( copying: "LegacyBundle_DoNotUseInNewTests", configureBundle: { url in try """ @@ -3276,14 +3270,14 @@ Document ) let moduleReference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/Test-Bundle/article", sourceLanguage: .swift ) let moduleNode = try context.entity(with: moduleReference) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: moduleNode.reference) + var translator = RenderNodeTranslator(context: context, identifier: moduleNode.reference) let moduleRenderNode = try XCTUnwrap(translator.visit(moduleNode.semantic) as? RenderNode) XCTAssertEqual( @@ -3319,18 +3313,17 @@ Document """), ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) - let articleReference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/unit-test/Article", sourceLanguage: .swift ) let articleNode = try context.entity(with: articleReference) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: articleNode.reference) + var translator = RenderNodeTranslator(context: context, identifier: articleNode.reference) let articleRenderNode = try XCTUnwrap(translator.visit(articleNode.semantic) as? RenderNode) XCTAssertEqual( @@ -3346,7 +3339,7 @@ Document } func testLanguageSpecificTopicSections() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in + let (_, _, context) = try await testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in try """ # ``MixedFramework/MyObjectiveCClassObjectiveCName`` @@ -3383,7 +3376,7 @@ Document let documentationNode = try context.entity(with: reference) XCTAssertEqual(documentationNode.availableVariantTraits.count, 2, "This page has Swift and Objective-C variants") - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(documentationNode) let topicSectionsVariants = renderNode.topicSectionsVariants @@ -3437,7 +3430,7 @@ Document - ``SomeClass4`` """), ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssert(context.problems.isEmpty, "\(context.problems.map(\.diagnostic.summary))") let moduleReference = try XCTUnwrap(context.soleRootModuleReference) @@ -3469,13 +3462,12 @@ Document ], file: file, line: line) } - let nodeConverter = DocumentationNodeConverter(bundle: bundle, context: context) + let nodeConverter = DocumentationNodeConverter(context: context) assertExpectedTopicSections(nodeConverter.convert(documentationNode)) let contextConverter = DocumentationContextConverter( - bundle: bundle, context: context, - renderContext: RenderContext(documentationContext: context, bundle: bundle) + renderContext: RenderContext(documentationContext: context) ) try assertExpectedTopicSections(XCTUnwrap(contextConverter.renderNode(for: documentationNode))) } @@ -3511,14 +3503,14 @@ Document let tempURL = try createTemporaryDirectory() let bundleURL = try exampleDocumentation.write(inside: tempURL) - let (_, bundle, context) = try await loadBundle(from: bundleURL, diagnosticEngine: .init() /* no diagnostic consumers */) + let (_, _, context) = try await loadBundle(from: bundleURL, diagnosticEngine: .init() /* no diagnostic consumers */) let reference = try XCTUnwrap(context.soleRootModuleReference) let documentationNode = try context.entity(with: reference) XCTAssertEqual(documentationNode.availableVariantTraits.count, 1) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(documentationNode) let topicSection = renderNode.topicSectionsVariants.defaultValue @@ -3531,13 +3523,13 @@ Document } func testAutomaticCurationForRefinedSymbols() async throws { - let (_, bundle, context) = try await testBundleAndContext(named: "GeometricalShapes") + let (_, _, context) = try await testBundleAndContext(named: "GeometricalShapes") do { let root = try XCTUnwrap(context.soleRootModuleReference) let node = try context.entity(with: root) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(node) let swiftTopicSections = renderNode.topicSectionsVariants.defaultValue @@ -3567,10 +3559,10 @@ Document } do { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/GeometricalShapes/Circle", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/GeometricalShapes/Circle", sourceLanguage: .swift) let node = try context.entity(with: reference) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(node) let swiftTopicSections = renderNode.topicSectionsVariants.defaultValue @@ -3618,7 +3610,7 @@ Document let (bundle, context) = try await testBundleAndContext() - var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) + var contentTranslator = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) let renderContent = try XCTUnwrap(markup.children.reduce(into: [], { result, item in result.append(contentsOf: contentTranslator.visit(item))}) as? [RenderBlockContent]) let expectedContent: [RenderBlockContent] = [ @@ -3649,7 +3641,7 @@ Document )) ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) XCTAssertEqual(context.knownPages.map(\.path).sorted(), [ "/documentation/ModuleName", @@ -3662,7 +3654,7 @@ Document let unnamedStructReference = try XCTUnwrap(context.soleRootModuleReference).appendingPath("SomeContainer/struct_(unnamed)") let node = try context.entity(with: unnamedStructReference) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(node) XCTAssertEqual(renderNode.metadata.title, "struct (unnamed)") diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift index 8a8e4ccc7c..251a757592 100644 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift @@ -391,12 +391,8 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { let reference = try XCTUnwrap(context.soleRootModuleReference, "This example catalog only has a root page") let converter = DocumentationContextConverter( - bundle: context.bundle, context: context, - renderContext: RenderContext( - documentationContext: context, - bundle: context.bundle - ) + renderContext: RenderContext(documentationContext: context) ) let renderNode = try XCTUnwrap(converter.renderNode(for: context.entity(with: reference))) diff --git a/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift b/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift index 5ed6806cc6..4c264ec3e7 100644 --- a/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift +++ b/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift @@ -18,7 +18,7 @@ class AutomaticSeeAlsoTests: XCTestCase { /// Test that a symbol with no authored See Also and with no curated siblings /// does not have a See Also section. func testNoSeeAlso() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Extension that curates `SideClass` try """ # ``SideKit`` @@ -31,7 +31,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify there is no See Also @@ -41,7 +41,7 @@ class AutomaticSeeAlsoTests: XCTestCase { /// Test that a symbol with authored See Also and with no curated siblings /// does include an authored See Also section func testAuthoredSeeAlso() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Extension that curates `SideClass` try """ # ``SideKit`` @@ -62,7 +62,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify there is an authored See Also from markdown @@ -77,7 +77,7 @@ class AutomaticSeeAlsoTests: XCTestCase { /// Test that a symbol with authored See Also and with curated siblings /// does include both in See Also with authored section first func testAuthoredAndAutomaticSeeAlso() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Extension that curates `SideClass` try """ # ``SideKit`` @@ -105,7 +105,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify there is an authored See Also & automatically created See Also @@ -122,7 +122,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Verify that articles get same automatic See Also sections as symbols do { let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/Test-Bundle/sidearticle", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Article) as! RenderNode // Verify there is an automacially created See Also @@ -138,7 +138,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Duplicate of the `testAuthoredAndAutomaticSeeAlso()` test above // but with automatic see also creation disabled func testAuthoredSeeAlsoWithDisabledAutomaticSeeAlso() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit`` @@ -172,7 +172,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify there is an authored See Also but no automatically created See Also @@ -185,7 +185,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Verify that article without options directive still gets automatic See Also sections do { let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/Test-Bundle/sidearticle", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Article) as! RenderNode // Verify there is an automacially created See Also @@ -201,7 +201,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Duplicate of the `testAuthoredAndAutomaticSeeAlso()` test above // but with automatic see also creation globally disabled func testAuthoredSeeAlsoWithGloballyDisabledAutomaticSeeAlso() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") { root in /// Article that curates `SideClass` try """ # ``SideKit`` @@ -236,7 +236,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify there is an authored See Also but no automatically created See Also @@ -249,7 +249,7 @@ class AutomaticSeeAlsoTests: XCTestCase { // Verify that article without options directive still gets automatic See Also sections do { let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/Test-Bundle/sidearticle", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Article) as! RenderNode // Verify there is an automacially created See Also @@ -283,11 +283,11 @@ class AutomaticSeeAlsoTests: XCTestCase { let tempURL = try createTemporaryDirectory() let bundleURL = try exampleDocumentation.write(inside: tempURL) - let (_, bundle, context) = try await loadBundle(from: bundleURL) + let (_, _, context) = try await loadBundle(from: bundleURL) // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "MyKit", path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify there is a See Also with the resolved tutorial reference diff --git a/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift b/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift index da55f59b47..f0a76bd050 100644 --- a/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift +++ b/Tests/SwiftDocCTests/Rendering/AvailabilityRenderOrderTests.swift @@ -18,7 +18,7 @@ class AvailabilityRenderOrderTests: XCTestCase { forResource: "Availability.symbols", withExtension: "json", subdirectory: "Test Resources")! func testSortingAtRenderTime() async throws { - let (bundleURL, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { url in let availabilitySymbolGraphURL = url.appendingPathComponent("Availability.symbols.json") try? FileManager.default.copyItem(at: self.availabilitySGFURL, to: availabilitySymbolGraphURL) @@ -61,13 +61,10 @@ class AvailabilityRenderOrderTests: XCTestCase { let data = try jsonEncoder.encode(availabilitySymbolGraph) try data.write(to: availabilitySymbolGraphURL) } - defer { - try? FileManager.default.removeItem(at: bundleURL) - } - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Availability/MyStruct", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/Availability/MyStruct", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode // Verify that all the symbol's availabilities were sorted into the order diff --git a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift index cf83b6b49f..7b67c54998 100644 --- a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift @@ -19,7 +19,7 @@ fileprivate let jsonEncoder = JSONEncoder() class ConstraintsRenderSectionTests: XCTestCase { func testSingleConstraint() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -40,16 +40,16 @@ class ConstraintsRenderSectionTests: XCTestCase { } // Compile docs and verify contents - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode XCTAssertEqual(renderNode.metadata.conformance?.constraints.map(flattenInlineElements).joined(), "Label is Text.") } func testSingleRedundantConstraint() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -70,15 +70,15 @@ class ConstraintsRenderSectionTests: XCTestCase { } // Compile docs and verify contents - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode XCTAssertNil(renderNode.metadata.conformance) } func testSingleRedundantConstraintForLeaves() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -99,15 +99,15 @@ class ConstraintsRenderSectionTests: XCTestCase { } // Compile docs and verify contents - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode XCTAssertNil(renderNode.metadata.conformance) } func testPreservesNonRedundantConstraints() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -129,15 +129,15 @@ class ConstraintsRenderSectionTests: XCTestCase { } // Compile docs and verify contents - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode XCTAssertEqual(renderNode.metadata.conformance?.constraints.map(flattenInlineElements).joined(), "Element is MyClass.") } func testGroups2Constraints() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -159,15 +159,15 @@ class ConstraintsRenderSectionTests: XCTestCase { } // Compile docs and verify contents - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode XCTAssertEqual(renderNode.metadata.conformance?.constraints.map(flattenInlineElements).joined(), "Element conforms to MyProtocol and Equatable.") } func testGroups3Constraints() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -190,15 +190,15 @@ class ConstraintsRenderSectionTests: XCTestCase { } // Compile docs and verify contents - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction()", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode XCTAssertEqual(renderNode.metadata.conformance?.constraints.map(flattenInlineElements).joined(), "Element conforms to MyProtocol, Equatable, and Hashable.") } func testRenderReferences() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -220,9 +220,9 @@ class ConstraintsRenderSectionTests: XCTestCase { } // Compile docs and verify contents - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode guard let renderReference = renderNode.references.first(where: { (key, value) -> Bool in @@ -236,7 +236,7 @@ class ConstraintsRenderSectionTests: XCTestCase { } func testRenderReferencesWithNestedTypeInSelf() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in // Add constraints to `MyClass` let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) @@ -258,9 +258,9 @@ class ConstraintsRenderSectionTests: XCTestCase { } // Compile docs and verify contents - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode guard let renderReference = renderNode.references.first(where: { (key, value) -> Bool in diff --git a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift index 71f43bb446..24e21475fa 100644 --- a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift @@ -134,9 +134,9 @@ class DeclarationsRenderSectionTests: XCTestCase { } func testAlternateDeclarations() async throws { - let (bundle, context) = try await testBundleAndContext(named: "AlternateDeclarations") + let (_, context) = try await testBundleAndContext(named: "AlternateDeclarations") let reference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/AlternateDeclarations/MyClass/present(completion:)", sourceLanguage: .swift ) @@ -152,7 +152,7 @@ class DeclarationsRenderSectionTests: XCTestCase { })) // Verify that the rendered symbol displays both signatures - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) @@ -218,7 +218,7 @@ class DeclarationsRenderSectionTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) XCTAssertEqual(declarationsSection.declarations.count, 2) @@ -265,7 +265,7 @@ class DeclarationsRenderSectionTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) XCTAssertEqual(declarationsSection.declarations.count, 1) @@ -316,7 +316,7 @@ class DeclarationsRenderSectionTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) XCTAssertEqual(declarationsSection.declarations.count, 1) @@ -371,7 +371,7 @@ class DeclarationsRenderSectionTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) XCTAssertEqual(declarationsSection.declarations.count, 1) @@ -458,12 +458,12 @@ class DeclarationsRenderSectionTests: XCTestCase { JSONFile(name: "FancierOverloads.symbols.json", content: symbolGraph), ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) func assertDeclarations(for USR: String, file: StaticString = #filePath, line: UInt = #line) throws { let reference = try XCTUnwrap(context.documentationCache.reference(symbolID: USR), file: file, line: line) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol, file: file, line: line) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode, file: file, line: line) let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first, file: file, line: line) XCTAssertEqual(declarationsSection.declarations.count, 1, file: file, line: line) @@ -504,7 +504,7 @@ class DeclarationsRenderSectionTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) XCTAssertEqual(declarationsSection.declarations.count, 1) @@ -539,7 +539,7 @@ class DeclarationsRenderSectionTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first) XCTAssertEqual(declarationsSection.declarations.count, 1) diff --git a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift index 597ecbd05b..200fd3073b 100644 --- a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift @@ -73,7 +73,7 @@ class DefaultAvailabilityTests: XCTestCase { do { let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", fragment: nil, sourceLanguage: .swift) let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertEqual(renderNode.metadata.platforms?.map({ "\($0.name ?? "") \($0.introduced ?? "")" }).sorted(), expectedDefaultAvailability) @@ -83,7 +83,7 @@ class DefaultAvailabilityTests: XCTestCase { do { let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyClass/init()-3743d", fragment: nil, sourceLanguage: .swift) let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertEqual(renderNode.metadata.platforms?.map({ "\($0.name ?? "") \($0.introduced ?? "")" }).sorted(), ["Mac Catalyst ", "iOS ", "iPadOS ", "macOS 10.15.1"]) @@ -93,7 +93,7 @@ class DefaultAvailabilityTests: XCTestCase { do { let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyClass", fragment: nil, sourceLanguage: .swift) let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertNotEqual(renderNode.metadata.platforms?.map({ "\($0.name ?? "") \($0.introduced ?? "")" }), expectedDefaultAvailability) @@ -131,14 +131,14 @@ class DefaultAvailabilityTests: XCTestCase { JSONFile(name: "MyKit.symbols.json", content: makeSymbolGraph(moduleName: "MyKit")), ]) - let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, configuration: configuration) let reference = try XCTUnwrap(context.soleRootModuleReference, file: file, line: line) // Test whether we: // 1) Fallback on iOS when Mac Catalyst availability is missing // 2) Render [Beta] or not for Mac Catalyst's inherited iOS availability let node = try context.entity(with: reference) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertEqual(renderNode.metadata.platforms?.map({ "\($0.name ?? "") \($0.introduced ?? "")\($0.isBeta == true ? "(beta)" : "")" }).sorted(), expected, file: (file), line: line) @@ -177,7 +177,7 @@ class DefaultAvailabilityTests: XCTestCase { // Set a beta status for the docs (which would normally be set via command line argument) configuration.externalMetadata.currentPlatforms = ["macOS": PlatformVersion(VersionTriplet(10, 16, 0), beta: true)] - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) { (url) in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) { (url) in // Copy an Info.plist with default availability of macOS 10.15.1 try? FileManager.default.removeItem(at: url.appendingPathComponent("Info.plist")) try? FileManager.default.copyItem(at: self.infoPlistAvailabilityURL, to: url.appendingPathComponent("Info.plist")) @@ -187,7 +187,7 @@ class DefaultAvailabilityTests: XCTestCase { do { let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", fragment: nil, sourceLanguage: .swift) let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertEqual(renderNode.metadata.platforms?.map({ "\($0.name ?? "") \($0.introduced ?? "")\($0.isBeta == true ? "(beta)" : "")" }).sorted(), [ @@ -202,7 +202,7 @@ class DefaultAvailabilityTests: XCTestCase { var configuration = DocumentationContext.Configuration() // Set a beta status for the docs (which would normally be set via command line argument) configuration.externalMetadata.currentPlatforms = ["iOS": PlatformVersion(VersionTriplet(14, 0, 0), beta: true)] - let (_, bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) + let (_, _, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests", configuration: configuration) do { let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit/MyClass/myFunction()", fragment: nil, sourceLanguage: .swift) @@ -218,7 +218,7 @@ class DefaultAvailabilityTests: XCTestCase { SymbolGraph.Symbol.Availability.AvailabilityItem(domain: .init(rawValue: "macOS"), introducedVersion: nil, deprecatedVersion: nil, obsoletedVersion: nil, message: nil, renamed: nil, isUnconditionallyDeprecated: false, isUnconditionallyUnavailable: true, willEventuallyBeDeprecated: false), ]) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode // Verify that the 'watchOS' & 'tvOS' platforms are filtered out because the symbol is unavailable @@ -344,7 +344,7 @@ class DefaultAvailabilityTests: XCTestCase { // Compile docs and verify contents let symbol = node.semantic as! Symbol - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) guard let renderNode = translator.visit(symbol) as? RenderNode else { XCTFail("Could not compile the node") @@ -641,7 +641,7 @@ class DefaultAvailabilityTests: XCTestCase { // Don't use default availability version. - var (bundle, context) = try await setupContext( + var (_, context) = try await setupContext( defaultAvailability: """ name @@ -667,14 +667,14 @@ class DefaultAvailabilityTests: XCTestCase { // Verify we remove the version from the module availability information. var identifier = ResolvedTopicReference(bundleID: "test", path: "/documentation/MyModule", fragment: nil, sourceLanguage: .swift) var node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: identifier) + var translator = RenderNodeTranslator(context: context, identifier: identifier) var renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertEqual(renderNode.metadata.platforms?.count, 1) XCTAssertEqual(renderNode.metadata.platforms?.first?.name, "iOS") XCTAssertEqual(renderNode.metadata.platforms?.first?.introduced, nil) // Add an extra default availability to test behaviour when mixin in source with default behaviour. - (bundle, context) = try await setupContext(defaultAvailability: """ + (_, context) = try await setupContext(defaultAvailability: """ name iOS @@ -710,7 +710,7 @@ class DefaultAvailabilityTests: XCTestCase { // Verify the module availability shows as expected. identifier = ResolvedTopicReference(bundleID: "test", path: "/documentation/MyModule", fragment: nil, sourceLanguage: .swift) node = try context.entity(with: identifier) - translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: identifier) + translator = RenderNodeTranslator(context: context, identifier: identifier) renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertEqual(renderNode.metadata.platforms?.count, 4) var moduleAvailability = try XCTUnwrap(renderNode.metadata.platforms?.first(where: {$0.name == "iOS"})) diff --git a/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift b/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift index 5f13c40852..002f93d25a 100644 --- a/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift @@ -11,105 +11,62 @@ import Foundation import XCTest @testable import SwiftDocC +import SwiftDocCTestUtilities class DefaultCodeBlockSyntaxTests: XCTestCase { - enum Errors: Error { - case noCodeBlockFound + func testCodeBlockWithoutAnyLanguageOrDefault() async throws { + let codeListing = try await makeCodeBlock(fenceLanguage: nil, infoPlistLanguage: nil) + XCTAssertEqual(codeListing.language, nil) } - var renderSectionWithLanguageDefault: ContentRenderSection! - var renderSectionWithoutLanguageDefault: ContentRenderSection! - - var testBundleWithLanguageDefault: DocumentationBundle! - var testBundleWithoutLanguageDefault: DocumentationBundle! - - override func setUp() async throws { - try await super.setUp() - - func renderSection(for bundle: DocumentationBundle, in context: DocumentationContext) throws -> ContentRenderSection { - let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/Test-Bundle/Default-Code-Listing-Syntax", fragment: nil, sourceLanguage: .swift) - - let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) - let renderNode = translator.visit(node.semantic) as! RenderNode - - return renderNode.primaryContentSections.first! as! ContentRenderSection - } - - let (_, bundleWithLanguageDefault, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") - - testBundleWithLanguageDefault = bundleWithLanguageDefault - - // Copy the bundle but explicitly set `defaultCodeListingLanguage` to `nil` to mimic having no default language set. - testBundleWithoutLanguageDefault = DocumentationBundle( - info: DocumentationBundle.Info( - displayName: testBundleWithLanguageDefault.displayName, - id: testBundleWithLanguageDefault.id, - defaultCodeListingLanguage: nil - ), - baseURL: testBundleWithLanguageDefault.baseURL, - symbolGraphURLs: testBundleWithLanguageDefault.symbolGraphURLs, - markupURLs: testBundleWithLanguageDefault.markupURLs, - miscResourceURLs: testBundleWithLanguageDefault.miscResourceURLs - ) - - renderSectionWithLanguageDefault = try renderSection(for: testBundleWithLanguageDefault, in: context) - renderSectionWithoutLanguageDefault = try renderSection(for: testBundleWithoutLanguageDefault, in: context) - } - - struct CodeListing { - var language: String? - var lines: [String] + func testExplicitFencedCodeBlockLanguage() async throws { + let codeListing = try await makeCodeBlock(fenceLanguage: "swift", infoPlistLanguage: nil) + XCTAssertEqual(codeListing.language, "swift") } - private func codeListing(at index: Int, in renderSection: ContentRenderSection, file: StaticString = #filePath, line: UInt = #line) throws -> CodeListing { - if case let .codeListing(l) = renderSection.content[index] { - return CodeListing(language: l.syntax, lines: l.code) - } - - XCTFail("Expected code listing at index \(index)", file: (file), line: line) - throw Errors.noCodeBlockFound + func testDefaultCodeBlockLanguage() async throws { + let codeListing = try await makeCodeBlock(fenceLanguage: nil, infoPlistLanguage: "swift") + XCTAssertEqual(codeListing.language, "swift") } - func testDefaultCodeBlockSyntaxForFencedCodeListingWithoutExplicitLanguage() throws { - let fencedCodeListing = try codeListing(at: 1, in: renderSectionWithLanguageDefault) - - XCTAssertEqual("swift", fencedCodeListing.language, "Default a language of 'CDDefaultCodeListingLanguage' if it is set in the 'Info.plist'") - - XCTAssertEqual(fencedCodeListing.lines, [ - "// With no language set, this should highlight to 'swift' because the 'CDDefaultCodeListingLanguage' key is set to 'swift'.", - "func foo()", - ]) + func testExplicitlySetLanguageOverridesDefaultLanguage() async throws { + let codeListing = try await makeCodeBlock(fenceLanguage: "objective-c", infoPlistLanguage: "swift") + XCTAssertEqual(codeListing.language, "objective-c", "The explicit language of the code listing should override the bundle's default language") } - func testDefaultCodeBlockSyntaxForNonFencedCodeListing() throws { - let indentedCodeListing = try codeListing(at: 2, in: renderSectionWithLanguageDefault) - - XCTAssertEqual("swift", indentedCodeListing.language, "Default a language of 'CDDefaultCodeListingLanguage' if it is set in the 'Info.plist'") - XCTAssertEqual(indentedCodeListing.lines, [ - "/// This is a non fenced code listing and should also default to the 'CDDefaultCodeListingLanguage' language.", - "func foo()", - ]) + private struct CodeListing { + var language: String? + var lines: [String] } - - func testExplicitlySetLanguageOverridesBundleDefault() throws { - let explicitlySetLanguageCodeListing = try codeListing(at: 3, in: renderSectionWithLanguageDefault) - - XCTAssertEqual("objective-c", explicitlySetLanguageCodeListing.language, "The explicit language of the code listing should override the bundle's default language") - - XCTAssertEqual(explicitlySetLanguageCodeListing.lines, [ - "/// This is a fenced code block with an explicit language set, and it should override the default language for the bundle.", - "- (void)foo;", + + private func makeCodeBlock(fenceLanguage: String?, infoPlistLanguage: String?) async throws -> CodeListing { + let catalog = Folder(name: "Something.docc", content: [ + InfoPlist(defaultCodeListingLanguage: infoPlistLanguage), + + TextFile(name: "Root.md", utf8Content: """ + # Root + + This article contains a code block + + ```\(fenceLanguage ?? "") + Some code goes + ``` + """) ]) - } - - func testHasNoLanguageWhenNoPlistKeySetAndNoExplicitLanguageProvided() throws { - let fencedCodeListing = try codeListing(at: 1, in: renderSectionWithoutLanguageDefault) - let indentedCodeListing = try codeListing(at: 2, in: renderSectionWithoutLanguageDefault) - let explicitlySetLanguageCodeListing = try codeListing(at: 3, in: renderSectionWithoutLanguageDefault) - - XCTAssertEqual(fencedCodeListing.language, nil) - XCTAssertEqual(indentedCodeListing.language, nil) - XCTAssertEqual(explicitlySetLanguageCodeListing.language, "objective-c") + + let (_, context) = try await loadBundle(catalog: catalog) + let reference = try XCTUnwrap(context.soleRootModuleReference) + let converter = DocumentationNodeConverter(context: context) + + let renderNode = converter.convert(try context.entity(with: reference)) + let renderSection = try XCTUnwrap(renderNode.primaryContentSections.first as? ContentRenderSection) + + guard case .codeListing(let codeListing)? = renderSection.content.last else { + struct Error: DescribedError { + let errorDescription = "Didn't fide code block is known markup" + } + throw Error() + } + return CodeListing(language: codeListing.syntax, lines: codeListing.code) } } diff --git a/Tests/SwiftDocCTests/Rendering/DeprecationSummaryTests.swift b/Tests/SwiftDocCTests/Rendering/DeprecationSummaryTests.swift index 9b6e4864fc..68184ffba0 100644 --- a/Tests/SwiftDocCTests/Rendering/DeprecationSummaryTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DeprecationSummaryTests.swift @@ -31,12 +31,12 @@ class DeprecationSummaryTests: XCTestCase { /// This test verifies that a symbol's deprecation summary comes from its sidecar doc /// and it's preferred over the original deprecation note in the code docs. func testAuthoredDeprecatedSummary() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/init()", sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass/init()", sourceLanguage: .swift)) // Compile docs and verify contents let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode, "Could not compile the node") XCTAssertEqual(renderNode.deprecationSummary?.firstParagraph, [.text("This initializer has been deprecated.")]) @@ -44,7 +44,7 @@ class DeprecationSummaryTests: XCTestCase { /// Test for a warning when symbol is not deprecated func testIncorrectlyAuthoredDeprecatedSummary() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: [], configureBundle: { url in // Add a sidecar file with wrong deprecated summary try """ # ``SideKit/SideClass`` @@ -63,11 +63,11 @@ class DeprecationSummaryTests: XCTestCase { }) // Verify the deprecation is still rendered. - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift)) // Compile docs and verify contents let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode, "Could not compile the node") XCTAssertEqual(renderNode.deprecationSummary?.firstParagraph, [.text("This class has been deprecated.")]) @@ -91,7 +91,7 @@ class DeprecationSummaryTests: XCTestCase { // Compile docs and verify contents let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) guard let renderNode = translator.visit(symbol) as? RenderNode else { XCTFail("Could not compile the node") @@ -112,10 +112,10 @@ class DeprecationSummaryTests: XCTestCase { } func testSymbolDeprecatedSummary() async throws { - let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + let (_, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") let node = try context.entity( with: ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/CoolFramework/CoolClass/doUncoolThings(with:)", sourceLanguage: .swift ) @@ -123,7 +123,7 @@ class DeprecationSummaryTests: XCTestCase { // Compile docs and verify contents let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode, "Could not compile the node") @@ -134,18 +134,18 @@ class DeprecationSummaryTests: XCTestCase { } func testDeprecationOverride() async throws { - let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") - let node = try context.entity( - with: ResolvedTopicReference( - bundleID: bundle.id, - path: "/documentation/CoolFramework/CoolClass/init()", - sourceLanguage: .swift - ) - ) - + let (_, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + let node = try context.entity( + with: ResolvedTopicReference( + bundleID: context.inputs.id, + path: "/documentation/CoolFramework/CoolClass/init()", + sourceLanguage: .swift + ) + ) + // Compile docs and verify contents let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode, "Could not compile the node") @@ -163,10 +163,10 @@ class DeprecationSummaryTests: XCTestCase { } func testDeprecationSummaryInDiscussionSection() async throws { - let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + let (_, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") let node = try context.entity( with: ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/CoolFramework/CoolClass/coolFunc()", sourceLanguage: .swift ) @@ -174,7 +174,7 @@ class DeprecationSummaryTests: XCTestCase { // Compile docs and verify contents let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode, "Could not compile the node") @@ -192,10 +192,10 @@ class DeprecationSummaryTests: XCTestCase { } func testDeprecationSummaryWithMultiLineCommentSymbol() async throws { - let (bundle, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") + let (_, context) = try await testBundleAndContext(named: "BundleWithLonelyDeprecationDirective") let node = try context.entity( with: ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/CoolFramework/CoolClass/init(config:cache:)", sourceLanguage: .swift ) @@ -203,7 +203,7 @@ class DeprecationSummaryTests: XCTestCase { // Compile docs and verify contents let symbol = try XCTUnwrap(node.semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(symbol) as? RenderNode, "Could not compile the node") diff --git a/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift b/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift index 09ca5e064a..daf92c4e17 100644 --- a/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift @@ -139,8 +139,8 @@ private extension DocumentationDataVariantsTrait { private extension DocumentationContentRendererTests { func makeDocumentationContentRenderer() async throws -> DocumentationContentRenderer { - let (bundle, context) = try await testBundleAndContext() - return DocumentationContentRenderer(documentationContext: context, bundle: bundle) + let (_, context) = try await testBundleAndContext() + return DocumentationContentRenderer(context: context) } var nodeWithSubheadingAndNavigatorVariants: DocumentationNode { diff --git a/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift b/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift index 704d560817..d533344d9f 100644 --- a/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift @@ -24,8 +24,8 @@ class ExternalLinkTitleTests: XCTestCase { semantic: Semantic()) - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let result = translator.visit(MarkupContainer(document.children)) as! [RenderBlockContent] return (translator, result) diff --git a/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift b/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift index 7565ed4c4a..778e190577 100644 --- a/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift @@ -34,12 +34,12 @@ class HeadingAnchorTests: XCTestCase { """), ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let reference = try XCTUnwrap(context.soleRootModuleReference) let node = try context.entity(with: reference) - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) let renderNode = try XCTUnwrap(converter.renderNode(for: node)) // Check heading anchors are encoded diff --git a/Tests/SwiftDocCTests/Rendering/MentionsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/MentionsRenderSectionTests.swift index 3f981b3ca0..720ae88a47 100644 --- a/Tests/SwiftDocCTests/Rendering/MentionsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/MentionsRenderSectionTests.swift @@ -17,19 +17,19 @@ class MentionsRenderSectionTests: XCTestCase { /// pointing to the correct article. func testMentionedInSectionFull() async throws { enableFeatureFlag(\.isMentionedInEnabled) - let (bundle, context) = try await createMentionedInTestBundle() + let (_, context) = try await createMentionedInTestBundle() let identifier = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/MentionedIn/MyClass", sourceLanguage: .swift ) let mentioningArticle = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/MentionedIn/ArticleMentioningSymbol", sourceLanguage: .swift ) let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode let mentionsSection = try XCTUnwrap(renderNode.primaryContentSections.mapFirst { $0 as? MentionsRenderSection }) XCTAssertEqual(1, mentionsSection.mentions.count) @@ -40,14 +40,14 @@ class MentionsRenderSectionTests: XCTestCase { /// If there are no qualifying mentions of a symbol, the Mentioned In section should not appear. func testMentionedInSectionEmpty() async throws { enableFeatureFlag(\.isMentionedInEnabled) - let (bundle, context) = try await createMentionedInTestBundle() + let (_, context) = try await createMentionedInTestBundle() let identifier = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/MentionedIn/MyClass/myFunction()", sourceLanguage: .swift ) let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode let mentionsSection = renderNode.primaryContentSections.mapFirst { $0 as? MentionsRenderSection } XCTAssertNil(mentionsSection) diff --git a/Tests/SwiftDocCTests/Rendering/PageKindTests.swift b/Tests/SwiftDocCTests/Rendering/PageKindTests.swift index 2f8c794c85..ac9add76fc 100644 --- a/Tests/SwiftDocCTests/Rendering/PageKindTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PageKindTests.swift @@ -23,7 +23,7 @@ class PageKindTests: XCTestCase { sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) return try XCTUnwrap(translator.visitArticle(article) as? RenderNode) } diff --git a/Tests/SwiftDocCTests/Rendering/PlatformAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/PlatformAvailabilityTests.swift index 59e48df45a..f013c1e4f4 100644 --- a/Tests/SwiftDocCTests/Rendering/PlatformAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PlatformAvailabilityTests.swift @@ -41,7 +41,7 @@ class PlatformAvailabilityTests: XCTestCase { sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let availability = try XCTUnwrap(renderNode.metadata.platformsVariants.defaultValue) XCTAssertEqual(availability.count, 1) @@ -60,7 +60,7 @@ class PlatformAvailabilityTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let availability = try XCTUnwrap(renderNode.metadata.platformsVariants.defaultValue) XCTAssertEqual(availability.count, 1) @@ -78,7 +78,7 @@ class PlatformAvailabilityTests: XCTestCase { sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let availability = try XCTUnwrap(renderNode.metadata.platformsVariants.defaultValue) XCTAssertEqual(availability.count, 3) @@ -106,7 +106,7 @@ class PlatformAvailabilityTests: XCTestCase { sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let availability = try XCTUnwrap(renderNode.metadata.platformsVariants.defaultValue) XCTAssertEqual(availability.count, 2) @@ -132,7 +132,7 @@ class PlatformAvailabilityTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let availability = try XCTUnwrap(renderNode.metadata.platformsVariants.defaultValue) XCTAssertEqual(availability.count, 5) @@ -164,14 +164,14 @@ class PlatformAvailabilityTests: XCTestCase { let platformMetadata = [ "iOS": PlatformVersion(VersionTriplet(16, 0, 0), beta: true), ] - let (bundle, context) = try await testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) + let (_, context) = try await testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) let reference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/AvailableArticle", sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let availability = try XCTUnwrap(renderNode.metadata.platformsVariants.defaultValue) XCTAssertEqual(availability.count, 1) @@ -187,14 +187,14 @@ class PlatformAvailabilityTests: XCTestCase { "macOS": PlatformVersion(VersionTriplet(12, 0, 0), beta: true), "watchOS": PlatformVersion(VersionTriplet(7, 0, 0), beta: true), ] - let (bundle, context) = try await testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) + let (_, context) = try await testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) let reference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/AvailabilityBundle/ComplexAvailable", sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let availability = try XCTUnwrap(renderNode.metadata.platformsVariants.defaultValue) XCTAssertEqual(availability.count, 3) @@ -219,14 +219,14 @@ class PlatformAvailabilityTests: XCTestCase { let platformMetadata = [ "iOS": PlatformVersion(VersionTriplet(16, 0, 0), beta: true), ] - let (bundle, context) = try await testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) + let (_, context) = try await testBundleWithConfiguredPlatforms(named: "AvailabilityBundle", platformMetadata: platformMetadata) let reference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let availability = try XCTUnwrap(renderNode.metadata.platformsVariants.defaultValue) XCTAssertEqual(availability.count, 1) diff --git a/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift index bfe5eddd00..a4790d4d28 100644 --- a/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift @@ -55,9 +55,9 @@ class PropertyListDetailsRenderSectionTests: XCTestCase { let catalog = Folder(name: "unit-test.docc", content: [ TextFile(name: "MyModule.symbols.json", utf8Content: symbolGraphString) ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let node = try XCTUnwrap(context.documentationCache["plist:propertylistkey"]) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(node) return try XCTUnwrap(renderNode.primaryContentSections.mapFirst(where: { $0 as? PropertyListDetailsRenderSection })) } diff --git a/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift b/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift index 8bb0b2c915..9de46ea5c3 100644 --- a/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift @@ -330,10 +330,10 @@ class RESTSymbolsTests: XCTestCase { )), ] + extraFiles ) - let (bundle, context) = try await loadBundle(catalog: catalog) - let moduleReference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName", sourceLanguage: .swift) + let (_, context) = try await loadBundle(catalog: catalog) + let moduleReference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleName", sourceLanguage: .swift) let moduleSymbol = try XCTUnwrap((try context.entity(with: moduleReference)).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: moduleReference) + var translator = RenderNodeTranslator(context: context, identifier: moduleReference) let renderNode = translator.visit(moduleSymbol) as! RenderNode return try XCTUnwrap((renderNode.references["doc://unit-test/documentation/ModuleName/plist-key-symbolname"] as? TopicRenderReference)) } @@ -425,6 +425,4 @@ class RESTSymbolsTests: XCTestCase { XCTAssertEqual(propertyListKeyNames.rawKey, "plist-key-symbolname") XCTAssertEqual(propertyListKeyNames.displayName, nil) } - - } diff --git a/Tests/SwiftDocCTests/Rendering/RenderBlockContent_ThematicBreakTests.swift b/Tests/SwiftDocCTests/Rendering/RenderBlockContent_ThematicBreakTests.swift index 0ec1d9c8db..5f2a70ee72 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderBlockContent_ThematicBreakTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderBlockContent_ThematicBreakTests.swift @@ -40,7 +40,7 @@ class RenderBlockContent_ThematicBreakTests: XCTestCase { let (bundle, context) = try await testBundleAndContext() - var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) + var contentTranslator = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) let renderContent = try XCTUnwrap(markup.children.reduce(into: [], { result, item in result.append(contentsOf: contentTranslator.visit(item))}) as? [RenderBlockContent]) let expectedContent: [RenderBlockContent] = [ @@ -67,7 +67,7 @@ class RenderBlockContent_ThematicBreakTests: XCTestCase { let (bundle, context) = try await testBundleAndContext() - var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) + var contentTranslator = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) let renderContent = try XCTUnwrap(markup.children.reduce(into: [], { result, item in result.append(contentsOf: contentTranslator.visit(item))}) as? [RenderBlockContent]) let expectedContent: [RenderBlockContent] = [ @@ -97,7 +97,7 @@ class RenderBlockContent_ThematicBreakTests: XCTestCase { let (bundle, context) = try await testBundleAndContext() - var contentTranslator = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) + var contentTranslator = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/TestThematicBreak", sourceLanguage: .swift)) let renderContent = try XCTUnwrap(markup.children.reduce(into: [], { result, item in result.append(contentsOf: contentTranslator.visit(item))}) as? [RenderBlockContent]) let expectedContent: [RenderBlockContent] = [ diff --git a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift index 28af2d014d..86d9f37d17 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift @@ -18,7 +18,7 @@ typealias Position = RenderBlockContent.CodeBlockOptions.Position class RenderContentCompilerTests: XCTestCase { func testLinkOverrideTitle() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ [Example](http://example.com) @@ -136,7 +136,7 @@ class RenderContentCompilerTests: XCTestCase { func testLineBreak() async throws { let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" Backslash before new line\ @@ -201,7 +201,7 @@ class RenderContentCompilerTests: XCTestCase { func testThematicBreak() async throws { let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" @@ -230,7 +230,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift @@ -254,7 +254,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift, nocopy @@ -278,7 +278,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```nocopy @@ -301,7 +301,7 @@ class RenderContentCompilerTests: XCTestCase { func testCopyToClipboardNoFeatureFlag() async throws { let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift @@ -322,7 +322,7 @@ class RenderContentCompilerTests: XCTestCase { func testNoCopyToClipboardNoFeatureFlag() async throws { let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift, nocopy @@ -347,7 +347,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift, showLineNumbers @@ -375,7 +375,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift, showlinenumbers @@ -403,7 +403,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift, wrap=20, highlight=[2] @@ -440,7 +440,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift, highlight=[2] @@ -473,7 +473,7 @@ class RenderContentCompilerTests: XCTestCase { func testHighlightNoFeatureFlag() async throws { let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift, highlight=[2] @@ -503,7 +503,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift, highlight=[1, 2, 3] @@ -550,7 +550,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```swift, strikeout=[3,5], highlight=[1, 2, 3] @@ -606,7 +606,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```showLineNumbers, highlight=[1, 2, 3], swift, wrap=20, strikeout=[3] @@ -660,7 +660,7 @@ class RenderContentCompilerTests: XCTestCase { enableFeatureFlag(\.isExperimentalCodeBlockAnnotationsEnabled) let (bundle, context) = try await testBundleAndContext() - var compiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" ```highlight=[5,3,4], strikeout=[3,1] diff --git a/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift b/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift index d07e2e629f..9eea805846 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift @@ -73,10 +73,10 @@ class RenderMetadataTests: XCTestCase { var typesOfPages = [Tutorial.self, TutorialTableOfContents.self, Article.self, TutorialArticle.self, Symbol.self] for bundleName in ["LegacyBundle_DoNotUseInNewTests"] { - let (bundle, context) = try await testBundleAndContext(named: bundleName) + let (_, context) = try await testBundleAndContext(named: bundleName) - let renderContext = RenderContext(documentationContext: context, bundle: bundle) - let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) for identifier in context.knownPages { let entity = try context.entity(with: identifier) let renderNode = try XCTUnwrap(converter.renderNode(for: entity)) @@ -93,14 +93,14 @@ class RenderMetadataTests: XCTestCase { /// Test that a bystanders symbol graph is loaded, symbols are merged into the main module /// and the bystanders are included in the render node metadata. func testRendersBystandersFromSymbolGraph() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in let bystanderSymbolGraphURL = Bundle.module.url( forResource: "MyKit@Foundation@_MyKit_Foundation.symbols", withExtension: "json", subdirectory: "Test Resources")! try FileManager.default.copyItem(at: bystanderSymbolGraphURL, to: url.appendingPathComponent("MyKit@Foundation@_MyKit_Foundation.symbols.json")) } // Verify the symbol from bystanders graph is present in the documentation context. - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass/myFunction1()", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass/myFunction1()", sourceLanguage: .swift) let entity = try XCTUnwrap(try? context.entity(with: reference)) let symbol = try XCTUnwrap(entity.semantic as? Symbol) @@ -108,7 +108,7 @@ class RenderMetadataTests: XCTestCase { XCTAssertEqual(symbol.crossImportOverlayModule?.bystanderModules, ["Foundation"]) // Verify the rendered metadata contains the bystanders - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(entity) XCTAssertEqual(renderNode.metadata.modules?.first?.name, "MyKit") XCTAssertEqual(renderNode.metadata.modules?.first?.relatedModules, ["Foundation"]) @@ -119,7 +119,7 @@ class RenderMetadataTests: XCTestCase { func testRendersBystanderExtensionsFromSymbolGraph() async throws { throw XCTSkip("Fails in CI. rdar://159615046") - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in let baseSymbolGraphURL = Bundle.module.url( forResource: "BaseKit.symbols", withExtension: "json", subdirectory: "Test Resources")! try FileManager.default.copyItem(at: baseSymbolGraphURL, to: url.appendingPathComponent("BaseKit.symbols.json")) @@ -129,7 +129,7 @@ class RenderMetadataTests: XCTestCase { } // Verify the symbol from bystanders graph is present in the documentation context. - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/BaseKit/OtherStruct/someFunc()", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/BaseKit/OtherStruct/someFunc()", sourceLanguage: .swift) let entity = try XCTUnwrap(try? context.entity(with: reference)) let symbol = try XCTUnwrap(entity.semantic as? Symbol) @@ -137,14 +137,14 @@ class RenderMetadataTests: XCTestCase { XCTAssertEqual(symbol.crossImportOverlayModule?.bystanderModules, ["BaseKit"]) // Verify the rendered metadata contains the bystanders - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(entity) XCTAssertEqual(renderNode.metadata.modules?.first?.name, "OverlayTest") XCTAssertEqual(renderNode.metadata.modules?.first?.relatedModules, ["BaseKit"]) } func testRendersExtensionSymbolsWithBystanderModules() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "BundleWithRelativePathAmbiguity") { root in + let (_, _, context) = try await testBundleAndContext(copying: "BundleWithRelativePathAmbiguity") { root in // We don't want the external target to be part of the archive as that is not // officially supported yet. try FileManager.default.removeItem(at: root.appendingPathComponent("Dependency.symbols.json")) @@ -152,7 +152,7 @@ class RenderMetadataTests: XCTestCase { // Get a translated render node let node = try context.entity(with: ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/BundleWithRelativePathAmbiguity/Dependency/AmbiguousType", sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode XCTAssertEqual(renderNode.metadata.modules?.first?.name, "BundleWithRelativePathAmbiguity") XCTAssertEqual(renderNode.metadata.modules?.first?.relatedModules, ["Dependency"]) diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift index d7f6d1ec4d..1cb095f54a 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift @@ -510,7 +510,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ])) ]) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let moduleReference = try XCTUnwrap(context.soleRootModuleReference) let dictionaryReference = moduleReference.appendingPath("SomeDictionary") @@ -521,7 +521,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { symbol.dictionaryKeysSection = dictionaryKeysSection context.documentationCache[dictionaryReference] = node - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(node) return try XCTUnwrap(renderNode.primaryContentSections.mapFirst(where: { $0 as? PropertiesRenderSection })) @@ -1167,10 +1167,10 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { assertAfterApplyingVariant: (RenderNode) throws -> () = { _ in }, assertDataAfterApplyingVariant: (Data) throws -> () = { _ in } ) async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: bundleName) + let (_, _, context) = try await testBundleAndContext(copying: bundleName) let identifier = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift ) @@ -1187,7 +1187,6 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { try assertMultiLanguageSemantic( symbol, context: context, - bundle: bundle, identifier: identifier, configureRenderNodeTranslator: configureRenderNodeTranslator, assertOriginalRenderNode: assertOriginalRenderNode, @@ -1204,10 +1203,10 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { assertAfterApplyingVariant: (RenderNode) throws -> () = { _ in }, assertDataAfterApplyingVariant: (Data) throws -> () = { _ in } ) async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") let identifier = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/documentation/Test-Bundle/article", sourceLanguage: .swift ) @@ -1224,7 +1223,6 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { try assertMultiLanguageSemantic( article, context: context, - bundle: bundle, identifier: identifier, assertOriginalRenderNode: assertOriginalRenderNode, assertAfterApplyingVariant: assertAfterApplyingVariant, @@ -1235,14 +1233,13 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { private func assertMultiLanguageSemantic( _ semantic: Semantic, context: DocumentationContext, - bundle: DocumentationBundle, identifier: ResolvedTopicReference, configureRenderNodeTranslator: (inout RenderNodeTranslator) -> () = { _ in }, assertOriginalRenderNode: (RenderNode) throws -> (), assertAfterApplyingVariant: (RenderNode) throws -> (), assertDataAfterApplyingVariant: (Data) throws -> () = { _ in } ) throws { - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: identifier) + var translator = RenderNodeTranslator(context: context, identifier: identifier) configureRenderNodeTranslator(&translator) diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift index c2fe00d4fe..aba174200b 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift @@ -21,7 +21,7 @@ class RenderNodeTranslatorTests: XCTestCase { let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: forSymbolPath, sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode guard let section = renderNode.primaryContentSections.last(where: { section -> Bool in @@ -294,7 +294,7 @@ class RenderNodeTranslatorTests: XCTestCase { let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Test-Bundle/article", sourceLanguage: .swift) let node = try context.entity(with: reference) let article = try XCTUnwrap(node.semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderedNode = translator.visit(article) as! RenderNode // Verify that the render reference to a section includes the container symbol's abstract @@ -346,7 +346,7 @@ class RenderNodeTranslatorTests: XCTestCase { let topicGraphNode = TopicGraph.Node(reference: reference, kind: .article, source: .file(url: URL(fileURLWithPath: "/path/to/article.md")), title: "My Article") context.topicGraph.addNode(topicGraphNode) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let node = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) XCTAssertEqual(node.topicSections.count, 2) @@ -376,7 +376,7 @@ class RenderNodeTranslatorTests: XCTestCase { }) let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let node = try XCTUnwrap(try? context.entity(with: reference)) // Test manual task groups and automatic symbol groups ordering @@ -503,7 +503,7 @@ class RenderNodeTranslatorTests: XCTestCase { }) let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Test-Bundle/article", sourceLanguage: .swift) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let node = try XCTUnwrap(try? context.entity(with: reference)) // Test the manual curation task groups @@ -606,7 +606,7 @@ class RenderNodeTranslatorTests: XCTestCase { // Verify "Default Implementations" group on the implementing type do { let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/Element", sourceLanguage: .swift) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let node = try XCTUnwrap(try? context.entity(with: reference)) let symbol = try XCTUnwrap(node.semantic as? Symbol) @@ -625,7 +625,7 @@ class RenderNodeTranslatorTests: XCTestCase { // Verify automatically generated api collection do { let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass/Element/Protocol-Implementations", sourceLanguage: .swift) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let node = try XCTUnwrap(try? context.entity(with: reference)) let article = try XCTUnwrap(node.semantic as? Article) @@ -654,7 +654,7 @@ class RenderNodeTranslatorTests: XCTestCase { } let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/FancyProtocol/SomeClass", sourceLanguage: .swift) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let node = try context.entity(with: reference) let symbol = try XCTUnwrap(node.semantic as? Symbol) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) @@ -857,7 +857,7 @@ class RenderNodeTranslatorTests: XCTestCase { let (_, bundle, context) = try await loadBundle(from: bundleURL) let reference = ResolvedTopicReference(bundleID: bundle.id, path: path, sourceLanguage: .swift) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let node = try context.entity(with: reference) let symbol = try XCTUnwrap(node.semantic as? Symbol) return try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) @@ -871,7 +871,7 @@ class RenderNodeTranslatorTests: XCTestCase { // Verify that the ordering of default implementations is deterministic for _ in 0..<100 { - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: structReference) + var translator = RenderNodeTranslator(context: context, identifier: structReference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let section = renderNode.topicSections.first(where: { $0.title == "Default Implementations" }) XCTAssertEqual(section?.identifiers, [ @@ -900,7 +900,7 @@ class RenderNodeTranslatorTests: XCTestCase { }) let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit/SideClass", sourceLanguage: .swift) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let node = try XCTUnwrap(try? context.entity(with: reference)) let symbol = try XCTUnwrap(node.semantic as? Symbol) @@ -930,7 +930,7 @@ class RenderNodeTranslatorTests: XCTestCase { let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit", sourceLanguage: .swift) let node = try context.entity(with: reference) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let symbol = try XCTUnwrap(node.semantic as? Symbol) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) @@ -950,7 +950,7 @@ class RenderNodeTranslatorTests: XCTestCase { let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SideKit", sourceLanguage: .swift) let node = try context.entity(with: reference) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let symbol = try XCTUnwrap(node.semantic as? Symbol) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) @@ -963,7 +963,7 @@ class RenderNodeTranslatorTests: XCTestCase { let (bundle, context) = try await testBundleAndContext(named: "Snippets") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Snippets/Snippets", sourceLanguage: .swift) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let discussion = try XCTUnwrap(renderNode.primaryContentSections.first(where: { $0.kind == .content }) as? ContentRenderSection) @@ -993,7 +993,7 @@ class RenderNodeTranslatorTests: XCTestCase { let (bundle, context) = try await testBundleAndContext(named: "Snippets") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Snippets/Snippets", sourceLanguage: .swift) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let discussion = try XCTUnwrap(renderNode.primaryContentSections.first(where: { $0.kind == .content }) as? ContentRenderSection) @@ -1017,7 +1017,7 @@ class RenderNodeTranslatorTests: XCTestCase { let (bundle, context) = try await testBundleAndContext(named: "Snippets") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Snippets/Snippets", sourceLanguage: .swift) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let discussion = try XCTUnwrap(renderNode.primaryContentSections.first(where: { $0.kind == .content }) as? ContentRenderSection) @@ -1048,7 +1048,7 @@ class RenderNodeTranslatorTests: XCTestCase { let (bundle, context) = try await testBundleAndContext(named: "Snippets") let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/Snippets/SliceIndentation", sourceLanguage: .swift) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let discussion = try XCTUnwrap(renderNode.primaryContentSections.first(where: { $0.kind == .content }) as? ContentRenderSection) @@ -1077,7 +1077,7 @@ class RenderNodeTranslatorTests: XCTestCase { sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let discussion = try XCTUnwrap( @@ -1106,7 +1106,7 @@ class RenderNodeTranslatorTests: XCTestCase { sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let discussion = try XCTUnwrap( @@ -1134,7 +1134,7 @@ class RenderNodeTranslatorTests: XCTestCase { sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let discussion = try XCTUnwrap( @@ -1171,7 +1171,7 @@ class RenderNodeTranslatorTests: XCTestCase { sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode) let encodedArticle = try JSONEncoder().encode(renderNode) @@ -1247,7 +1247,7 @@ class RenderNodeTranslatorTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let encodedSymbol = try JSONEncoder().encode(renderNode) @@ -1263,7 +1263,7 @@ class RenderNodeTranslatorTests: XCTestCase { sourceLanguage: .swift ) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) let encodedSymbol = try JSONEncoder().encode(renderNode) @@ -1341,7 +1341,7 @@ class RenderNodeTranslatorTests: XCTestCase { ) throws -> RenderNode { let reference = ResolvedTopicReference(bundleID: bundle.id, path: referencePath, sourceLanguage: .swift) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) return try XCTUnwrap(translator.visitArticle(symbol) as? RenderNode) } @@ -1433,7 +1433,7 @@ class RenderNodeTranslatorTests: XCTestCase { ) throws -> RenderNode { let reference = ResolvedTopicReference(bundleID: bundle.id, path: referencePath, sourceLanguage: .swift) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) return try XCTUnwrap(translator.visitArticle(symbol) as? RenderNode) } @@ -1461,7 +1461,7 @@ class RenderNodeTranslatorTests: XCTestCase { func testEncodesOverloadsInRenderNode() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) - let (bundle, context) = try await testBundleAndContext(named: "OverloadedSymbols") + let (_, context) = try await testBundleAndContext(named: "OverloadedSymbols") let overloadPreciseIdentifiers = ["s:8ShapeKit14OverloadedEnumO19firstTestMemberNameySdSiF", "s:8ShapeKit14OverloadedEnumO19firstTestMemberNameySdSfF", @@ -1474,7 +1474,7 @@ class RenderNodeTranslatorTests: XCTestCase { for (index, reference) in overloadReferences.indexed() { let documentationNode = try context.entity(with: reference) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let symbol = try XCTUnwrap(documentationNode.semantic as? Symbol) let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) @@ -1497,7 +1497,7 @@ class RenderNodeTranslatorTests: XCTestCase { } func testAlternateRepresentationsRenderedAsVariants() async throws { - let (bundle, context) = try await loadBundle(catalog: Folder( + let (_, context) = try await loadBundle(catalog: Folder( name: "unit-test.docc", content: [ TextFile(name: "Symbol.md", utf8Content: """ @@ -1547,9 +1547,9 @@ class RenderNodeTranslatorTests: XCTestCase { func renderNodeArticleFromReferencePath( referencePath: String ) throws -> RenderNode { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: referencePath, sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: referencePath, sourceLanguage: .swift) let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) return try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode) } diff --git a/Tests/SwiftDocCTests/Rendering/RoleTests.swift b/Tests/SwiftDocCTests/Rendering/RoleTests.swift index f05fe42e11..c944b9cb34 100644 --- a/Tests/SwiftDocCTests/Rendering/RoleTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RoleTests.swift @@ -25,14 +25,14 @@ class RoleTests: XCTestCase { ] func testNodeRoles() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") // Compile docs and verify contents for (path, expectedRole) in expectedRoles { let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: path, fragment: nil, sourceLanguage: .swift) do { let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertEqual(expectedRole, renderNode.metadata.role, "Unexpected role \(renderNode.metadata.role!.singleQuoted) for identifier \(identifier.path)") } catch { @@ -43,11 +43,11 @@ class RoleTests: XCTestCase { } func testDocumentationRenderReferenceRoles() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/documentation/MyKit", fragment: nil, sourceLanguage: .swift) let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/documentation/MyKit"] as? TopicRenderReference)?.role, "collection") @@ -56,11 +56,11 @@ class RoleTests: XCTestCase { } func testTutorialsRenderReferenceRoles() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") + let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests") let identifier = ResolvedTopicReference(bundleID: "org.swift.docc.example", path: "/tutorials/Test-Bundle/TestTutorial", fragment: nil, sourceLanguage: .swift) let node = try context.entity(with: identifier) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = translator.visit(node.semantic) as! RenderNode XCTAssertEqual((renderNode.references["doc://org.swift.docc.example/tutorials/TestOverview"] as? TopicRenderReference)?.role, "overview") diff --git a/Tests/SwiftDocCTests/Rendering/SampleDownloadTests.swift b/Tests/SwiftDocCTests/Rendering/SampleDownloadTests.swift index 39b7ab7d61..62089827e0 100644 --- a/Tests/SwiftDocCTests/Rendering/SampleDownloadTests.swift +++ b/Tests/SwiftDocCTests/Rendering/SampleDownloadTests.swift @@ -126,14 +126,14 @@ class SampleDownloadTests: XCTestCase { } private func renderNodeFromSampleBundle(at referencePath: String) async throws -> RenderNode { - let (bundle, context) = try await testBundleAndContext(named: "SampleBundle") + let (_, context) = try await testBundleAndContext(named: "SampleBundle") let reference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: referencePath, sourceLanguage: .swift ) let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) return try XCTUnwrap(translator.visitArticle(article) as? RenderNode) } diff --git a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift index 239d352230..c3ea5e5ab5 100644 --- a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift @@ -37,10 +37,10 @@ class SymbolAvailabilityTests: XCTestCase { )), ] ) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) let reference = try XCTUnwrap(context.soleRootModuleReference).appendingPath(symbolName) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: reference.path, sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: reference.path, sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) return try XCTUnwrap((translator.visit(node.semantic as! Symbol) as! RenderNode).metadata.platformsVariants.defaultValue) } diff --git a/Tests/SwiftDocCTests/Rendering/TermListTests.swift b/Tests/SwiftDocCTests/Rendering/TermListTests.swift index bb03e0c289..d5e496456c 100644 --- a/Tests/SwiftDocCTests/Rendering/TermListTests.swift +++ b/Tests/SwiftDocCTests/Rendering/TermListTests.swift @@ -86,12 +86,12 @@ class TermListTests: XCTestCase { var configuration = DocumentationContext.Configuration() configuration.externalDocumentationConfiguration.sources = ["com.external.testbundle": resolver] - let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + let (_, context) = try await loadBundle(catalog: catalog, configuration: configuration) - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/unit-test/Article", sourceLanguage: .swift) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/unit-test/Article", sourceLanguage: .swift) let entity = try context.entity(with: reference) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(entity) let overviewSection = try XCTUnwrap(renderNode.primaryContentSections.first as? ContentRenderSection) @@ -162,7 +162,7 @@ class TermListTests: XCTestCase { } let (bundle, context) = try await testBundleAndContext() - var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ - term First term : A paragraph that @@ -205,7 +205,7 @@ class TermListTests: XCTestCase { } let (bundle, context) = try await testBundleAndContext() - var renderContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ - Not a term list, and diff --git a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift b/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift index bd62af73f1..100ad050b7 100644 --- a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift +++ b/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift @@ -88,8 +88,8 @@ class DoxygenTests: XCTestCase { )), ]) - let (bundle, context) = try await loadBundle(catalog: catalog) - let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName/SomeClass", sourceLanguage: .swift) + let (_, context) = try await loadBundle(catalog: catalog) + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/ModuleName/SomeClass", sourceLanguage: .swift) // Verify the expected content in the in-memory model let node = try context.entity(with: reference) @@ -103,7 +103,7 @@ class DoxygenTests: XCTestCase { ]) // Verify the expected content in the render model - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) let renderNode = try XCTUnwrap(translator.visit(node.semantic) as? RenderNode) XCTAssertEqual(renderNode.abstract, [.text("This is an abstract.")]) diff --git a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift index b996285011..79c0914cb6 100644 --- a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift @@ -14,7 +14,7 @@ import Markdown class MarkupReferenceResolverTests: XCTestCase { func testArbitraryReferenceInComment() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") let source = """ @Comment { ``hello`` and ``world`` are 2 arbitrary symbol links. @@ -23,7 +23,7 @@ class MarkupReferenceResolverTests: XCTestCase { } """ let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) - var resolver = MarkupReferenceResolver(context: context, bundle: bundle, rootReference: context.rootModules[0]) + var resolver = MarkupReferenceResolver(context: context, rootReference: context.rootModules[0]) _ = resolver.visit(document) XCTAssertEqual(0, resolver.problems.count) } diff --git a/Tests/SwiftDocCTests/Semantics/SnippetTests.swift b/Tests/SwiftDocCTests/Semantics/SnippetTests.swift index 5eda29ec26..a692c5511a 100644 --- a/Tests/SwiftDocCTests/Semantics/SnippetTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SnippetTests.swift @@ -59,7 +59,7 @@ class SnippetTests: XCTestCase { XCTAssertTrue(problems.isEmpty) } func testLinkResolvesWithoutOptionalPrefix() async throws { - let (bundle, context) = try await testBundleAndContext(named: "Snippets") + let (_, context) = try await testBundleAndContext(named: "Snippets") for snippetPath in [ "/Test/Snippets/MySnippet", @@ -71,14 +71,14 @@ class SnippetTests: XCTestCase { @Snippet(path: "\(snippetPath)") """ let document = Document(parsing: source, options: .parseBlockDirectives) - var resolver = MarkupReferenceResolver(context: context, bundle: bundle, rootReference: try XCTUnwrap(context.soleRootModuleReference)) + var resolver = MarkupReferenceResolver(context: context, rootReference: try XCTUnwrap(context.soleRootModuleReference)) _ = resolver.visit(document) XCTAssertTrue(resolver.problems.isEmpty, "Unexpected problems: \(resolver.problems.map(\.diagnostic.summary))") } } func testWarningAboutUnresolvedSnippetPath() async throws { - let (bundle, context) = try await testBundleAndContext(named: "Snippets") + let (_, context) = try await testBundleAndContext(named: "Snippets") for snippetPath in [ "/Test/Snippets/DoesNotExist", @@ -90,7 +90,7 @@ class SnippetTests: XCTestCase { @Snippet(path: "\(snippetPath)") """ let document = Document(parsing: source, options: .parseBlockDirectives) - var resolver = MarkupReferenceResolver(context: context, bundle: bundle, rootReference: try XCTUnwrap(context.soleRootModuleReference)) + var resolver = MarkupReferenceResolver(context: context, rootReference: try XCTUnwrap(context.soleRootModuleReference)) _ = resolver.visit(document) XCTAssertEqual(1, resolver.problems.count) let problem = try XCTUnwrap(resolver.problems.first) diff --git a/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift b/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift index 010fb3125a..3fb78b1444 100644 --- a/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift +++ b/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift @@ -326,19 +326,19 @@ class VideoMediaTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, context) = try await loadBundle( + let (_, context) = try await loadBundle( catalog: Folder(name: "unit-test.docc", content: [ DataFile(name: "introvideo.mov", data: Data()) ]) ) var problems = [Problem]() - let video = VideoMedia(from: directive, source: nil, for: bundle, problems: &problems) + let video = VideoMedia(from: directive, source: nil, for: context.inputs, problems: &problems) let reference = ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "", sourceLanguage: .swift ) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference) + var translator = RenderNodeTranslator(context: context, identifier: reference) let videoMediaReference = translator.visitVideoMedia(video!) as! RenderReferenceIdentifier let videoMedia = translator.videoReferences[videoMediaReference.identifier] // Check that the video references in the node translator contains the alt text. diff --git a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift index 67c0f5e3a7..c9eca9a9e9 100644 --- a/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift +++ b/Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift @@ -89,15 +89,13 @@ extension XCTestCase { sourceRepository: SourceRepository? = nil, configureBundle: ((URL) throws -> Void)? = nil ) async throws -> TestRenderNodeOutputConsumer { - let (_, bundle, context) = try await testBundleAndContext( + let (_, _, context) = try await testBundleAndContext( copying: bundleName, configureBundle: configureBundle ) - let outputConsumer = TestRenderNodeOutputConsumer() _ = try ConvertActionConverter.convert( - bundle: bundle, context: context, outputConsumer: outputConsumer, sourceRepository: sourceRepository, diff --git a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift index e7885428e9..8639fd62a8 100644 --- a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift +++ b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift @@ -272,8 +272,7 @@ class ListItemExtractorTests: XCTestCase { line: UInt = #line ) async throws { // Build documentation for a module page with one tagged item with a lot of different - - let (bundle, context) = try await loadBundle( + let (_, context) = try await loadBundle( catalog: Folder(name: "Something.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName")), TextFile(name: "Extension.md", utf8Content: """ @@ -303,7 +302,7 @@ class ListItemExtractorTests: XCTestCase { ]) ) - try _assertExtractsRichContentFor(tagName: tagName, findModelContent: findModelContent, renderVerification: renderVerification, isAside: isAside, bundle: bundle, context: context, expectedLogText: """ + try _assertExtractsRichContentFor(tagName: tagName, findModelContent: findModelContent, renderVerification: renderVerification, isAside: isAside, context: context, expectedLogText: """ \u{001B}[1;33mwarning: 'FirstNotFoundSymbol' doesn't exist at '/ModuleName'\u{001B}[0;0m --> /Something.docc/Extension.md:5:\(49+tagName.count)-5:\(68+tagName.count) 3 | Some description of this module. @@ -338,8 +337,7 @@ class ListItemExtractorTests: XCTestCase { line: UInt = #line ) async throws { // Build documentation for a module page with one tagged item with a lot of different - - let (bundle, context) = try await loadBundle( + let (_, context) = try await loadBundle( catalog: Folder(name: "Something.docc", content: [ JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName")), TextFile(name: "Extension.md", utf8Content: """ @@ -370,7 +368,7 @@ class ListItemExtractorTests: XCTestCase { ]) ) - try _assertExtractsRichContentFor(tagName: tagName, findModelContent: findModelContent, renderVerification: renderVerification, isAside: false, bundle: bundle, context: context, expectedLogText: """ + try _assertExtractsRichContentFor(tagName: tagName, findModelContent: findModelContent, renderVerification: renderVerification, isAside: false, context: context, expectedLogText: """ \u{001B}[1;33mwarning: 'FirstNotFoundSymbol' doesn't exist at '/ModuleName'\u{001B}[0;0m --> /Something.docc/Extension.md:6:60-6:79 4 | @@ -401,7 +399,6 @@ class ListItemExtractorTests: XCTestCase { findModelContent: (Symbol) -> [any Markup]?, renderVerification: RenderVerification, isAside: Bool, - bundle: DocumentationBundle, context: DocumentationContext, expectedLogText: String, file: StaticString = #filePath, @@ -457,7 +454,7 @@ class ListItemExtractorTests: XCTestCase { return } - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let converter = DocumentationNodeConverter(context: context) let renderNode = converter.convert(node) let renderContent = try XCTUnwrap(findRenderContent(renderNode), "Didn't find any rendered content", file: file, line: line) diff --git a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift index 4207fc3d1c..3d3f508fe1 100644 --- a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift +++ b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift @@ -131,9 +131,9 @@ extension XCTestCase { } func renderNode(atPath path: String, fromTestBundleNamed testBundleName: String) async throws -> RenderNode { - let (bundle, context) = try await testBundleAndContext(named: testBundleName) - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: path, sourceLanguage: .swift)) - var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let (_, context) = try await testBundleAndContext(named: testBundleName) + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: path, sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, identifier: node.reference) return try XCTUnwrap(translator.visit(node.semantic) as? RenderNode) } @@ -208,8 +208,8 @@ extension XCTestCase { directive: Directive?, collectedReferences: [String : any RenderReference] ) { - let (bundle, context) = try await loadBundle(catalog: catalog) - return try parseDirective(directive, bundle: bundle, context: context, content: content, file: file, line: line) + let (_, context) = try await loadBundle(catalog: catalog) + return try parseDirective(directive, context: context, content: content, file: file, line: line) } func parseDirective( @@ -242,20 +242,17 @@ extension XCTestCase { directive: Directive?, collectedReferences: [String : any RenderReference] ) { - let bundle: DocumentationBundle let context: DocumentationContext - if let bundleName { - (bundle, context) = try await testBundleAndContext(named: bundleName) + (_, context) = try await testBundleAndContext(named: bundleName) } else { - (bundle, context) = try await testBundleAndContext() + (_, context) = try await testBundleAndContext() } - return try parseDirective(directive, bundle: bundle, context: context, content: content, file: file, line: line) + return try parseDirective(directive, context: context, content: content, file: file, line: line) } private func parseDirective( _ directive: Directive.Type, - bundle: DocumentationBundle, context: DocumentationContext, content: () -> String, file: StaticString = #filePath, @@ -273,15 +270,11 @@ extension XCTestCase { let blockDirectiveContainer = try XCTUnwrap(document.child(at: 0) as? BlockDirective, file: file, line: line) - var analyzer = SemanticAnalyzer(source: source, bundle: bundle) + var analyzer = SemanticAnalyzer(source: source, bundle: context.inputs) let result = analyzer.visit(blockDirectiveContainer) context.diagnosticEngine.emit(analyzer.problems) - var referenceResolver = MarkupReferenceResolver( - context: context, - bundle: bundle, - rootReference: bundle.rootReference - ) + var referenceResolver = MarkupReferenceResolver(context: context, rootReference: context.inputs.rootReference) _ = referenceResolver.visit(blockDirectiveContainer) context.diagnosticEngine.emit(referenceResolver.problems) @@ -313,9 +306,8 @@ extension XCTestCase { var contentCompiler = RenderContentCompiler( context: context, - bundle: bundle, identifier: ResolvedTopicReference( - bundleID: bundle.id, + bundleID: context.inputs.id, path: "/test-path-123", sourceLanguage: .swift ) diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift index 39fa7a46d8..4d3db2bdbf 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift @@ -17,11 +17,11 @@ class ConvertActionIndexerTests: XCTestCase { // Tests the standalone indexer func testConvertActionIndexer() async throws { - let (bundle, dataProvider) = try DocumentationContext.InputsProvider() + let (inputs, dataProvider) = try DocumentationContext.InputsProvider() .inputsAndDataProvider(startingPoint: testCatalogURL(named: "LegacyBundle_DoNotUseInNewTests"), options: .init()) - let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider) - let converter = DocumentationNodeConverter(bundle: bundle, context: context) + let context = try await DocumentationContext(bundle: inputs, dataProvider: dataProvider) + let converter = DocumentationNodeConverter(context: context) // Add /documentation/MyKit to the index, verify the tree dump do { @@ -29,7 +29,7 @@ class ConvertActionIndexerTests: XCTestCase { let renderNode = try converter.convert(context.entity(with: reference)) let tempIndexURL = try createTemporaryDirectory(named: "index") - let indexer = try ConvertAction.Indexer(outputURL: tempIndexURL, bundleID: bundle.id) + let indexer = try ConvertAction.Indexer(outputURL: tempIndexURL, bundleID: inputs.id) indexer.index(renderNode) XCTAssertTrue(indexer.finalize(emitJSON: false, emitLMDB: false).isEmpty) let treeDump = try XCTUnwrap(indexer.dumpTree()) @@ -54,7 +54,7 @@ class ConvertActionIndexerTests: XCTestCase { let renderNode2 = try converter.convert(context.entity(with: reference2)) let tempIndexURL = try createTemporaryDirectory(named: "index") - let indexer = try ConvertAction.Indexer(outputURL: tempIndexURL, bundleID: bundle.id) + let indexer = try ConvertAction.Indexer(outputURL: tempIndexURL, bundleID: inputs.id) indexer.index(renderNode1) indexer.index(renderNode2) XCTAssertTrue(indexer.finalize(emitJSON: false, emitLMDB: false).isEmpty) diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift index 72f42ba478..a4f76bcd53 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift @@ -2898,7 +2898,7 @@ class ConvertActionTests: XCTestCase { ) let (_, context) = try await action.perform(logHandle: .none) - let bundle = try XCTUnwrap(context.bundle, "Should have registered the generated test bundle.") + let bundle = try XCTUnwrap(context.inputs, "Should have registered the generated test bundle.") XCTAssertEqual(bundle.displayName, "MyKit") XCTAssertEqual(bundle.id, "MyKit") } @@ -2976,7 +2976,7 @@ class ConvertActionTests: XCTestCase { ) let (_, context) = try await action.perform(logHandle: .none) - let bundle = try XCTUnwrap(context.bundle, "Should have registered the generated test bundle.") + let bundle = try XCTUnwrap(context.inputs, "Should have registered the generated test bundle.") XCTAssertEqual(bundle.displayName, "Something") XCTAssertEqual(bundle.id, "com.example.test") } diff --git a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift index 74f058a2b5..88acc2a5c2 100644 --- a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift @@ -919,13 +919,13 @@ class MergeActionTests: XCTestCase { try fileSystem.createDirectory(at: catalogDir, withIntermediateDirectories: true) try fileSystem.addFolder(catalog, basePath: catalogDir.deletingLastPathComponent()) - let (bundle, dataProvider) = try DocumentationContext.InputsProvider(fileManager: fileSystem) + let (inputs, dataProvider) = try DocumentationContext.InputsProvider(fileManager: fileSystem) .inputsAndDataProvider(startingPoint: catalogDir, options: .init()) - XCTAssertEqual(bundle.miscResourceURLs.map(\.lastPathComponent), [ + XCTAssertEqual(inputs.miscResourceURLs.map(\.lastPathComponent), [ "\(name.lowercased())-card.png", ]) - let context = try await DocumentationContext(bundle: bundle, dataProvider: dataProvider, configuration: .init()) + let context = try await DocumentationContext(bundle: inputs, dataProvider: dataProvider, configuration: .init()) XCTAssert( context.problems.filter { $0.diagnostic.identifier != "org.swift.docc.SummaryContainsLink" }.isEmpty, @@ -936,11 +936,11 @@ class MergeActionTests: XCTestCase { let outputPath = baseOutputDir.appendingPathComponent("\(name).doccarchive", isDirectory: true) let realTempURL = try createTemporaryDirectory() // The navigator builder only support real file systems - let indexer = try ConvertAction.Indexer(outputURL: realTempURL, bundleID: bundle.id) + let indexer = try ConvertAction.Indexer(outputURL: realTempURL, bundleID: inputs.id) - let outputConsumer = ConvertFileWritingConsumer(targetFolder: outputPath, bundleRootFolder: catalogDir, fileManager: fileSystem, context: context, indexer: indexer, transformForStaticHostingIndexHTML: nil, bundleID: bundle.id) + let outputConsumer = ConvertFileWritingConsumer(targetFolder: outputPath, bundleRootFolder: catalogDir, fileManager: fileSystem, context: context, indexer: indexer, transformForStaticHostingIndexHTML: nil, bundleID: inputs.id) - let convertProblems = try ConvertActionConverter.convert(bundle: bundle, context: context, outputConsumer: outputConsumer, sourceRepository: nil, emitDigest: false, documentationCoverageOptions: .noCoverage) + let convertProblems = try ConvertActionConverter.convert(context: context, outputConsumer: outputConsumer, sourceRepository: nil, emitDigest: false, documentationCoverageOptions: .noCoverage) XCTAssert(convertProblems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary).joined(separator: "\n"))", file: file, line: line) let navigatorProblems = indexer.finalize(emitJSON: true, emitLMDB: false) From 8a486743d26707caff76177a44a65e097a9da26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 10 Oct 2025 10:03:05 +0200 Subject: [PATCH 53/90] Add documentation about recommended aspect ratios for @PageImage (#1317) rdar://144094866 --- .../Semantics/Metadata/PageImage.swift | 18 +++++++--- .../DocCDocumentation.docc/DocC.symbols.json | 36 +++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftDocC/Semantics/Metadata/PageImage.swift b/Sources/SwiftDocC/Semantics/Metadata/PageImage.swift index aae9c3d916..88451fdec3 100644 --- a/Sources/SwiftDocC/Semantics/Metadata/PageImage.swift +++ b/Sources/SwiftDocC/Semantics/Metadata/PageImage.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 @@ -14,9 +14,19 @@ public import Markdown /// Associates an image with a page. /// /// You can use this directive to set the image used when rendering a user-interface element representing the page. -/// For example, use the page image directive to customize the icon used to represent this page in the navigation sidebar, -/// or the card image used to represent this page when using the ``Links`` directive and the ``Links/VisualStyle/detailedGrid`` -/// visual style. +/// +/// Use the "icon" purpose to customize the icon that DocC uses to represent this page in the navigation sidebar. +/// For article pages, DocC also uses this icon to represent the article in topics sections and in ``Links`` directives that use the `list` visual style. +/// +/// > Tip: Page images with the "icon" purpose work best when they're square and when they have good visual clarity at small sizes (less than 20×20 points). +/// +/// Use the "card" purpose to customize the image that DocC uses to represent this page inside ``Links`` directives that use the either the `detailedGrid` or the `compactGrid` visual style. +/// For article pages, DocC also incorporates a partially faded out version of the card image in the background of the page itself. +/// +/// > Tip: Page images with the "card" purpose work best when they have a 16:9 aspect ratio. Currently, the largest size that DocC displays a card image is 640×360 points. +/// +/// If you specify an "icon" page image without specifying a "card" page image, DocC will use the icon as a fallback in places where the card image is preferred. +/// To avoid upscaled pixelated icons in these places, either configure a "card" page image as well or use a scalable vector image asset for the "icon" page image. public final class PageImage: Semantic, AutomaticDirectiveConvertible { public static let introducedVersion = "5.8" public let originalMarkup: BlockDirective diff --git a/Sources/docc/DocCDocumentation.docc/DocC.symbols.json b/Sources/docc/DocCDocumentation.docc/DocC.symbols.json index 8469494a8c..ef6eaf073b 100644 --- a/Sources/docc/DocCDocumentation.docc/DocC.symbols.json +++ b/Sources/docc/DocCDocumentation.docc/DocC.symbols.json @@ -4167,13 +4167,43 @@ "text" : "You can use this directive to set the image used when rendering a user-interface element representing the page." }, { - "text" : "For example, use the page image directive to customize the icon used to represent this page in the navigation sidebar," + "text" : "" + }, + { + "text" : "Use the \"icon\" purpose to customize the icon that DocC uses to represent this page in the navigation sidebar." + }, + { + "text" : "For article pages, DocC also uses this icon to represent the article in topics sections and in ``Links`` directives that use the `list` visual style." + }, + { + "text" : "" + }, + { + "text" : "> Tip: Page images with the \"icon\" purpose work best when they're square and when they have good visual clarity at small sizes (less than 20×20 points)." + }, + { + "text" : "" + }, + { + "text" : "Use the \"card\" purpose to customize the image that DocC uses to represent this page inside ``Links`` directives that use the either the `detailedGrid` or the `compactGrid` visual style." + }, + { + "text" : "For article pages, DocC also incorporates a partially faded out version of the card image in the background of the page itself." + }, + { + "text" : "" + }, + { + "text" : "> Tip: Page images with the \"card\" purpose work best when they have a 16:9 aspect ratio. Currently, the largest size that DocC displays a card image is 640×360 points." + }, + { + "text" : "" }, { - "text" : "or the card image used to represent this page when using the ``Links`` directive and the ``Links\/VisualStyle\/detailedGrid``" + "text" : "If you specify an \"icon\" page image without specifying a \"card\" page image, DocC will use the icon as a fallback in places where the card image is preferred." }, { - "text" : "visual style." + "text" : "To avoid upscaled pixelated icons in these places, either configure a \"card\" page image as well or use a scalable vector image asset for the \"icon\" page image." }, { "text" : "- Parameters:" From a1a8f7ceeec00a5ab10b9d2e05803e59fb6367fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 10 Oct 2025 10:10:40 +0200 Subject: [PATCH 54/90] Add missing comma in tutorial article (#1316) rdar://78876208 --- .../Tutorials Syntax/building-an-interactive-tutorial.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/building-an-interactive-tutorial.md b/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/building-an-interactive-tutorial.md index 0dca7877a1..539461b6ce 100644 --- a/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/building-an-interactive-tutorial.md +++ b/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/building-an-interactive-tutorial.md @@ -50,7 +50,7 @@ Use a text editor and the following listing to create a table of contents file n } ```` -The top level of the listing the includes a ``Tutorials`` directive. This directive and the directives it contains, define the structure of the page. +The top level of the listing the includes a ``Tutorials`` directive. This directive, and the directives it contains, define the structure of the page. Rename the table of contents file and replace the placeholder content with your custom content. Use the ``Intro`` directive to introduce the reader to your tutorial through engaging text and imagery. Next, use ``Chapter`` directives to reference the step-by-step pages. From 37d2f516f73e91f13e7464c488ee5ec1d44a1df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 10 Oct 2025 10:16:54 +0200 Subject: [PATCH 55/90] Use parenthesis instead of comma for examples inside an already comma-separated list (#1315) rdar://78883355 --- .../Tutorials Syntax/Top-Level Directives/Tutorial/Section.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/Top-Level Directives/Tutorial/Section.md b/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/Top-Level Directives/Tutorial/Section.md index e2b913614c..ccb8f15c7a 100644 --- a/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/Top-Level Directives/Tutorial/Section.md +++ b/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/Top-Level Directives/Tutorial/Section.md @@ -11,7 +11,7 @@ Displays a grouping of text, images, and tasks on a tutorial page. ## Overview -Use a `Section` directive to show a unit of work that consists of text, media, for example images and videos, and tasks on a tutorial page. A tutorial page must includes one or more sections. +Use a `Section` directive to show a unit of work that consists of text, media (for example images and videos), and tasks on a tutorial page. A tutorial page must includes one or more sections. ![A screenshot showing a section on a tutorial page. The section includes text, an image, and coding steps.](1) @@ -79,4 +79,4 @@ The following pages can include sections: - ``Stack`` - + From 5f905e40df957bddac62cdf62d9b04b3affd8535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Wed, 15 Oct 2025 17:59:52 +0200 Subject: [PATCH 56/90] Update 42 tests to stop using the "LegacyBundle_DoNotUseInNewTests" fixtures (#1319) * Avoid using legacy test fixture in some tests * Avoid using legacy test fixture in more tests --- .../Model/RenderContentMetadataTests.swift | 28 ++-- .../Rendering/ExternalLinkTitleTests.swift | 2 +- .../RenderContentCompilerTests.swift | 29 +++- .../Semantics/ImageMediaTests.swift | 127 +++++++----------- .../MarkupReferenceResolverTests.swift | 7 +- .../Semantics/MetadataTests.swift | 4 +- .../Semantics/MultipleChoiceTests.swift | 28 ++-- .../Semantics/Reference/LinksTests.swift | 4 +- .../SwiftDocCTests/Semantics/StackTests.swift | 7 +- .../SwiftDocCTests/Semantics/StepTests.swift | 7 +- .../SwiftDocCTests/Semantics/TileTests.swift | 14 +- .../Semantics/TutorialArticleTests.swift | 33 +++-- .../Semantics/TutorialTests.swift | 35 +++-- .../XCTestCase+LoadingTestData.swift | 15 +++ 14 files changed, 194 insertions(+), 146 deletions(-) diff --git a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift index 5faa07d9e5..291f377a72 100644 --- a/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderContentMetadataTests.swift @@ -67,8 +67,8 @@ class RenderContentMetadataTests: XCTestCase { } func testRenderingTables() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext() + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: context.inputs.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ | Column 1 | Column 2 | @@ -108,8 +108,8 @@ class RenderContentMetadataTests: XCTestCase { } func testRenderingTableSpans() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext() + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: context.inputs.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ | one | two | three | @@ -161,8 +161,8 @@ class RenderContentMetadataTests: XCTestCase { } func testRenderingTableColumnAlignments() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext() + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: context.inputs.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ | one | two | three | four | @@ -203,8 +203,8 @@ class RenderContentMetadataTests: XCTestCase { /// Verifies that a table with `nil` alignments and a table with all-unset alignments still compare as equal. func testRenderedTableEquality() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext() + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: context.inputs.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ | Column 1 | Column 2 | @@ -229,8 +229,8 @@ class RenderContentMetadataTests: XCTestCase { /// Verifies that two tables with otherwise-identical contents but different column alignments compare as unequal. func testRenderedTableInequality() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext() + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: context.inputs.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let decodedTableWithUnsetColumns: RenderBlockContent.Table do { @@ -276,8 +276,8 @@ class RenderContentMetadataTests: XCTestCase { } func testStrikethrough() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext() + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: context.inputs.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ ~~Striken~~ text. @@ -299,8 +299,8 @@ class RenderContentMetadataTests: XCTestCase { } func testHeadingAnchorShouldBeEncoded() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext() + var renderContentCompiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: context.inputs.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ ## テスト diff --git a/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift b/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift index d533344d9f..34d78065fa 100644 --- a/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ExternalLinkTitleTests.swift @@ -24,7 +24,7 @@ class ExternalLinkTitleTests: XCTestCase { semantic: Semantic()) - let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext() var translator = RenderNodeTranslator(context: context, identifier: node.reference) let result = translator.visit(MarkupContainer(document.children)) as! [RenderBlockContent] diff --git a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift index 86d9f37d17..29b3728990 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift @@ -11,14 +11,29 @@ import Foundation import Markdown @testable import SwiftDocC +import SwiftDocCTestUtilities import XCTest typealias Position = RenderBlockContent.CodeBlockOptions.Position class RenderContentCompilerTests: XCTestCase { func testLinkOverrideTitle() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "article.md", utf8Content: """ + # First + """), + TextFile(name: "article2.md", utf8Content: """ + # Second + """), + TextFile(name: "article3.md", utf8Content: """ + # Third + """), + + InfoPlist(identifier: "org.swift.docc.example") + ]) + + let (_, context) = try await loadBundle(catalog: catalog) + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: context.inputs.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = """ [Example](http://example.com) @@ -89,7 +104,7 @@ class RenderContentCompilerTests: XCTestCase { return } let link = RenderInlineContent.reference( - identifier: .init("doc://org.swift.docc.example/documentation/Test-Bundle/article"), + identifier: .init("doc://org.swift.docc.example/documentation/unit-test/article"), isActive: true, overridingTitle: "Custom Title", overridingTitleInlineContent: [.text("Custom Title")]) @@ -101,7 +116,7 @@ class RenderContentCompilerTests: XCTestCase { return } let link = RenderInlineContent.reference( - identifier: .init("doc://org.swift.docc.example/documentation/Test-Bundle/article2"), + identifier: .init("doc://org.swift.docc.example/documentation/unit-test/article2"), isActive: true, overridingTitle: "Custom Image Content ", overridingTitleInlineContent: [ @@ -125,7 +140,7 @@ class RenderContentCompilerTests: XCTestCase { return } let link = RenderInlineContent.reference( - identifier: .init("doc://org.swift.docc.example/documentation/Test-Bundle/article3"), + identifier: .init("doc://org.swift.docc.example/documentation/unit-test/article3"), isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil @@ -135,8 +150,8 @@ class RenderContentCompilerTests: XCTestCase { } func testLineBreak() async throws { - let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") - var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: bundle.id, path: "/path", fragment: nil, sourceLanguage: .swift)) + let (_, context) = try await testBundleAndContext() + var compiler = RenderContentCompiler(context: context, identifier: ResolvedTopicReference(bundleID: context.inputs.id, path: "/path", fragment: nil, sourceLanguage: .swift)) let source = #""" Backslash before new line\ diff --git a/Tests/SwiftDocCTests/Semantics/ImageMediaTests.swift b/Tests/SwiftDocCTests/Semantics/ImageMediaTests.swift index a3606604dd..e46f80845c 100644 --- a/Tests/SwiftDocCTests/Semantics/ImageMediaTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ImageMediaTests.swift @@ -96,45 +96,39 @@ class ImageMediaTests: XCTestCase { func testRenderImageDirectiveInReferenceMarkup() async throws { do { - let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, withAvailableAssetNames: ["figure1.jpg"]) { """ @Image(source: "figure1") """ } XCTAssertNotNil(image) - XCTAssertEqual(problems, []) - - XCTAssertEqual( - renderedContent, - [ - RenderBlockContent.paragraph(RenderBlockContent.Paragraph( - inlineContent: [.image( - identifier: RenderReferenceIdentifier("figure1"), - metadata: nil - )] - )) - ] - ) + XCTAssertEqual(renderedContent, [ + RenderBlockContent.paragraph(RenderBlockContent.Paragraph( + inlineContent: [.image( + identifier: RenderReferenceIdentifier("figure1"), + metadata: nil + )] + )) + ]) } do { - let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, withAvailableAssetNames: []) { """ @Image(source: "unknown-image") """ } XCTAssertNotNil(image) - XCTAssertEqual(problems, ["1: warning – org.swift.docc.unresolvedResource.Image"]) XCTAssertEqual(renderedContent, []) } } func testRenderImageDirectiveWithCaption() async throws { - let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, withAvailableAssetNames: ["figure1.jpg"]) { """ @Image(source: "figure1") { This is my caption. @@ -143,76 +137,61 @@ class ImageMediaTests: XCTestCase { } XCTAssertNotNil(image) - XCTAssertEqual(problems, []) - - XCTAssertEqual( - renderedContent, - [ - RenderBlockContent.paragraph(RenderBlockContent.Paragraph( - inlineContent: [.image( - identifier: RenderReferenceIdentifier("figure1"), - metadata: RenderContentMetadata(abstract: [.text("This is my caption.")]) - )] - )) - ] - ) + XCTAssertEqual(renderedContent, [ + RenderBlockContent.paragraph(RenderBlockContent.Paragraph( + inlineContent: [.image( + identifier: RenderReferenceIdentifier("figure1"), + metadata: RenderContentMetadata(abstract: [.text("This is my caption.")]) + )] + )) + ]) } func testImageDirectiveDiagnosesDeviceFrameByDefault() async throws { - let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, withAvailableAssetNames: ["figure1.jpg"]) { """ @Image(source: "figure1", deviceFrame: phone) """ } XCTAssertNotNil(image) - XCTAssertEqual(problems, ["1: warning – org.swift.docc.UnknownArgument"]) - - XCTAssertEqual( - renderedContent, - [ - RenderBlockContent.paragraph(RenderBlockContent.Paragraph( - inlineContent: [.image( - identifier: RenderReferenceIdentifier("figure1"), - metadata: nil - )] - )) - ] - ) + XCTAssertEqual(renderedContent, [ + RenderBlockContent.paragraph(RenderBlockContent.Paragraph( + inlineContent: [.image( + identifier: RenderReferenceIdentifier("figure1"), + metadata: nil + )] + )) + ]) } func testRenderImageDirectiveWithDeviceFrame() async throws { enableFeatureFlag(\.isExperimentalDeviceFrameSupportEnabled) - let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, withAvailableAssetNames: ["figure1.jpg"]) { """ @Image(source: "figure1", deviceFrame: phone) """ } XCTAssertNotNil(image) - XCTAssertEqual(problems, []) - - XCTAssertEqual( - renderedContent, - [ - RenderBlockContent.paragraph(RenderBlockContent.Paragraph( - inlineContent: [.image( - identifier: RenderReferenceIdentifier("figure1"), - metadata: RenderContentMetadata(deviceFrame: "phone") - )] - )) - ] - ) + XCTAssertEqual(renderedContent, [ + RenderBlockContent.paragraph(RenderBlockContent.Paragraph( + inlineContent: [.image( + identifier: RenderReferenceIdentifier("figure1"), + metadata: RenderContentMetadata(deviceFrame: "phone") + )] + )) + ]) } func testRenderImageDirectiveWithDeviceFrameAndCaption() async throws { enableFeatureFlag(\.isExperimentalDeviceFrameSupportEnabled) - let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, in: "BookLikeContent") { + let (renderedContent, problems, image) = try await parseDirective(ImageMedia.self, withAvailableAssetNames: ["figure1.jpg"]) { """ @Image(source: "figure1", deviceFrame: laptop) { This is my caption. @@ -221,43 +200,35 @@ class ImageMediaTests: XCTestCase { } XCTAssertNotNil(image) - XCTAssertEqual(problems, []) - - XCTAssertEqual( - renderedContent, - [ - RenderBlockContent.paragraph(RenderBlockContent.Paragraph( - inlineContent: [.image( - identifier: RenderReferenceIdentifier("figure1"), - metadata: RenderContentMetadata(abstract: [.text("This is my caption.")], deviceFrame: "laptop") - )] - )) - ] - ) + XCTAssertEqual(renderedContent, [ + RenderBlockContent.paragraph(RenderBlockContent.Paragraph( + inlineContent: [.image( + identifier: RenderReferenceIdentifier("figure1"), + metadata: RenderContentMetadata(abstract: [.text("This is my caption.")], deviceFrame: "laptop") + )] + )) + ]) } func testImageDirectiveDoesNotResolveVideoReference() async throws { // First check that the Video exists - let (_, videoProblems, _) = try await parseDirective(VideoMedia.self, in: "LegacyBundle_DoNotUseInNewTests") { + let (_, videoProblems, _) = try await parseDirective(VideoMedia.self, withAvailableAssetNames: ["introvideo.mp4"]) { """ @Video(source: "introvideo") """ } - XCTAssertEqual(videoProblems, []) // Then check that it doesn't resolve as an image - let (renderedContent, imageProblems, image) = try await parseDirective(ImageMedia.self, in: "LegacyBundle_DoNotUseInNewTests") { + let (renderedContent, imageProblems, image) = try await parseDirective(ImageMedia.self, withAvailableAssetNames: ["introvideo.mp4"]) { """ @Image(source: "introvideo") """ } - XCTAssertNotNil(image) - XCTAssertEqual(imageProblems, ["1: warning – org.swift.docc.unresolvedResource.Image"]) - XCTAssertEqual(renderedContent, []) } } + diff --git a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift index 79c0914cb6..97f3b48ab2 100644 --- a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift @@ -11,10 +11,15 @@ import XCTest @testable import SwiftDocC import Markdown +import SwiftDocCTestUtilities class MarkupReferenceResolverTests: XCTestCase { func testArbitraryReferenceInComment() async throws { - let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let catalog = Folder(name: "unit-test.docc", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName")) + ]) + + let (_, context) = try await loadBundle(catalog: catalog) let source = """ @Comment { ``hello`` and ``world`` are 2 arbitrary symbol links. diff --git a/Tests/SwiftDocCTests/Semantics/MetadataTests.swift b/Tests/SwiftDocCTests/Semantics/MetadataTests.swift index 8f7f713a82..63c7225b17 100644 --- a/Tests/SwiftDocCTests/Semantics/MetadataTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MetadataTests.swift @@ -418,10 +418,10 @@ class MetadataTests: XCTestCase { line: UInt = #line ) async throws -> (problems: [String], metadata: Metadata) { let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (inputs, _) = try await testBundleAndContext() var problems = [Problem]() - let article = Article(from: document, source: nil, for: bundle, problems: &problems) + let article = Article(from: document, source: nil, for: inputs, problems: &problems) let problemIDs = problems.map { problem -> String in let line = problem.diagnostic.range?.lowerBound.line.description ?? "unknown-line" diff --git a/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift b/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift index 87ac419248..042cad6bd8 100644 --- a/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift @@ -11,6 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown +import SwiftDocCTestUtilities class MultipleChoiceTests: XCTestCase { func testInvalidEmpty() async throws { @@ -19,12 +20,12 @@ class MultipleChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (inputs, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() XCTAssertEqual(MultipleChoice.directiveName, directive.name) - let multipleChoice = MultipleChoice(from: directive, source: nil, for: bundle, problems: &problems) + let multipleChoice = MultipleChoice(from: directive, source: nil, for: inputs, problems: &problems) XCTAssertNil(multipleChoice) XCTAssertEqual(3, problems.count) let diagnosticIdentifiers = Set(problems.map { $0.diagnostic.identifier }) @@ -53,12 +54,12 @@ class MultipleChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (inputs, _) = try await testBundleAndContext() try directive.map { directive in var problems = [Problem]() XCTAssertEqual(MultipleChoice.directiveName, directive.name) - let multipleChoice = MultipleChoice(from: directive, source: nil, for: bundle, problems: &problems) + let multipleChoice = MultipleChoice(from: directive, source: nil, for: inputs, problems: &problems) XCTAssertNotNil(multipleChoice) XCTAssertEqual(1, problems.count) let problem = try XCTUnwrap( @@ -101,12 +102,15 @@ class MultipleChoiceTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: [ + DataFile(name: "blah.png", data: Data()), + InfoPlist(identifier: "org.swift.docc.example") + ])) directive.map { directive in var problems = [Problem]() XCTAssertEqual(MultipleChoice.directiveName, directive.name) - let multipleChoice = MultipleChoice(from: directive, source: nil, for: bundle, problems: &problems) + let multipleChoice = MultipleChoice(from: directive, source: nil, for: context.inputs, problems: &problems) XCTAssertNotNil(multipleChoice) XCTAssertFalse(problems.isEmpty) problems.first.map { @@ -158,12 +162,12 @@ MultipleChoice @1:1-24:2 title: 'SwiftDocC.MarkupContainer' let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (inputs, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() XCTAssertEqual(MultipleChoice.directiveName, directive.name) - let multipleChoice = MultipleChoice(from: directive, source: nil, for: bundle, problems: &problems) + let multipleChoice = MultipleChoice(from: directive, source: nil, for: inputs, problems: &problems) XCTAssertNotNil(multipleChoice) XCTAssertTrue(problems.isEmpty) @@ -214,12 +218,12 @@ MultipleChoice @1:1-18:2 title: 'SwiftDocC.MarkupContainer' let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (inputs, _) = try await testBundleAndContext() directive.map { directive in var problems = [Problem]() XCTAssertEqual(MultipleChoice.directiveName, directive.name) - let multipleChoice = MultipleChoice(from: directive, source: nil, for: bundle, problems: &problems) + let multipleChoice = MultipleChoice(from: directive, source: nil, for: inputs, problems: &problems) XCTAssertNotNil(multipleChoice) XCTAssertTrue(problems.isEmpty) @@ -274,12 +278,12 @@ MultipleChoice @1:1-22:2 title: 'SwiftDocC.MarkupContainer' let document = Document(parsing: source, options: .parseBlockDirectives) let directive = try XCTUnwrap(document.child(at: 0) as? BlockDirective) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (inputs, _) = try await testBundleAndContext() var problems = [Problem]() XCTAssertEqual(MultipleChoice.directiveName, directive.name) - let multipleChoice = MultipleChoice(from: directive, source: nil, for: bundle, problems: &problems) + let multipleChoice = MultipleChoice(from: directive, source: nil, for: inputs, problems: &problems) XCTAssertNotNil(multipleChoice) XCTAssertEqual(1, problems.count) diff --git a/Tests/SwiftDocCTests/Semantics/Reference/LinksTests.swift b/Tests/SwiftDocCTests/Semantics/Reference/LinksTests.swift index 8cc0d75e80..3a26a0b1f5 100644 --- a/Tests/SwiftDocCTests/Semantics/Reference/LinksTests.swift +++ b/Tests/SwiftDocCTests/Semantics/Reference/LinksTests.swift @@ -17,7 +17,7 @@ import Markdown class LinksTests: XCTestCase { func testMissingBasicRequirements() async throws { do { - let (renderedContent, problems, links) = try await parseDirective(Links.self, in: "BookLikeContent") { + let (renderedContent, problems, links) = try await parseDirective(Links.self) { """ @Links(visualStyle: compactGrid) """ @@ -57,7 +57,7 @@ class LinksTests: XCTestCase { func testInvalidBodyContent() async throws { do { - let (renderedContent, problems, links) = try await parseDirective(Links.self, in: "BookLikeContent") { + let (renderedContent, problems, links) = try await parseDirective(Links.self) { """ @Links(visualStyle: compactGrid) { This is a paragraph of text in 'Links' directive. diff --git a/Tests/SwiftDocCTests/Semantics/StackTests.swift b/Tests/SwiftDocCTests/Semantics/StackTests.swift index 0e432bc69d..721db9aa3f 100644 --- a/Tests/SwiftDocCTests/Semantics/StackTests.swift +++ b/Tests/SwiftDocCTests/Semantics/StackTests.swift @@ -11,6 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown +import SwiftDocCTestUtilities class StackTests: XCTestCase { func testEmpty() async throws { @@ -78,12 +79,14 @@ class StackTests: XCTestCase { let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: [ + DataFile(name: "code4.png", data: Data()) + ])) directive.map { directive in var problems = [Problem]() XCTAssertEqual(Stack.directiveName, directive.name) - let stack = Stack(from: directive, source: nil, for: bundle, problems: &problems) + let stack = Stack(from: directive, source: nil, for: context.inputs, problems: &problems) XCTAssertNotNil(stack) XCTAssertEqual(1, problems.count) XCTAssertEqual( diff --git a/Tests/SwiftDocCTests/Semantics/StepTests.swift b/Tests/SwiftDocCTests/Semantics/StepTests.swift index d4a4029105..d8a1b576df 100644 --- a/Tests/SwiftDocCTests/Semantics/StepTests.swift +++ b/Tests/SwiftDocCTests/Semantics/StepTests.swift @@ -11,6 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown +import SwiftDocCTestUtilities class StepTests: XCTestCase { func testEmpty() async throws { @@ -46,9 +47,11 @@ class StepTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: [ + DataFile(name: "test.png", data: Data()) + ])) var problems = [Problem]() - let step = Step(from: directive, source: nil, for: bundle, problems: &problems) + let step = Step(from: directive, source: nil, for: context.inputs, problems: &problems) XCTAssertTrue(problems.isEmpty) XCTAssertNotNil(step) diff --git a/Tests/SwiftDocCTests/Semantics/TileTests.swift b/Tests/SwiftDocCTests/Semantics/TileTests.swift index 8fb8b7b148..979badc857 100644 --- a/Tests/SwiftDocCTests/Semantics/TileTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TileTests.swift @@ -26,7 +26,7 @@ class TileTests: XCTestCase { let source = "@\(directiveName)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tile) @@ -49,7 +49,7 @@ class TileTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tile) @@ -75,7 +75,7 @@ class TileTests: XCTestCase { let source = "@\(directiveName)" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tile) @@ -97,7 +97,7 @@ class TileTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNotNil(tile) @@ -120,7 +120,7 @@ class TileTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) // Destination is set. @@ -136,7 +136,7 @@ class TileTests: XCTestCase { """ let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) // Destination is nil. @@ -149,7 +149,7 @@ class TileTests: XCTestCase { let source = "@UnknownTile" let document = Document(parsing: source, options: .parseBlockDirectives) let directive = document.child(at: 0)! as! BlockDirective - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (bundle, _) = try await testBundleAndContext() var problems = [Problem]() let tile = Tile(from: directive, source: nil, for: bundle, problems: &problems) XCTAssertNil(tile) diff --git a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift index 17635d3408..086f0a7488 100644 --- a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift @@ -11,6 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown +import SwiftDocCTestUtilities class TutorialArticleTests: XCTestCase { func testEmpty() async throws { @@ -155,12 +156,15 @@ TutorialArticle @1:1-23:2 let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: [ + InfoPlist(identifier: "org.swift.docc.example"), + DataFile(name: "myimage.png", data: Data()) + ])) directive.map { directive in var problems = [Problem]() XCTAssertEqual(TutorialArticle.directiveName, directive.name) - let article = TutorialArticle(from: directive, source: nil, for: bundle, problems: &problems) + let article = TutorialArticle(from: directive, source: nil, for: context.inputs, problems: &problems) XCTAssertNotNil(article) XCTAssertEqual(0, problems.count) article.map { article in @@ -265,12 +269,18 @@ TutorialArticle @1:1-23:2 title: 'Basic Augmented Reality App' time: '20' let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: [ + InfoPlist(identifier: "org.swift.docc.example"), + DataFile(name: "customize-text-view.png", data: Data()), + DataFile(name: "this-is-leading.png", data: Data()), + DataFile(name: "this-is-trailing.png", data: Data()), + DataFile(name: "this-is-still-trailing.png", data: Data()) + ])) directive.map { directive in var problems = [Problem]() XCTAssertEqual(TutorialArticle.directiveName, directive.name) - let article = TutorialArticle(from: directive, source: nil, for: bundle, problems: &problems) + let article = TutorialArticle(from: directive, source: nil, for: context.inputs, problems: &problems) XCTAssertNotNil(article) XCTAssertEqual(3, problems.count) let arbitraryMarkupProblem = problems.first(where: { $0.diagnostic.identifier == "org.swift.docc.Stack.UnexpectedContent" }) @@ -361,12 +371,15 @@ TutorialArticle @1:1-81:2 let directive = document.child(at: 0) as? BlockDirective XCTAssertNotNil(directive) - let (bundle, _) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: [ + InfoPlist(identifier: "org.swift.docc.example"), + DataFile(name: "myimage.png", data: Data()) + ])) directive.map { directive in var problems = [Problem]() XCTAssertEqual(TutorialArticle.directiveName, directive.name) - let article = TutorialArticle(from: directive, source: nil, for: bundle, problems: &problems) + let article = TutorialArticle(from: directive, source: nil, for: context.inputs, problems: &problems) XCTAssertNotNil(article) XCTAssertEqual(0, problems.count) article.map { article in @@ -398,7 +411,7 @@ TutorialArticle @1:1-42:2 title: 'Basic Augmented Reality App' time: '20' let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TopicGraphTests", path: "/\(title)", sourceLanguage: .swift) let node = TopicGraph.Node(reference: reference, kind: .tutorialTableOfContents, source: .file(url: URL(fileURLWithPath: "/path/to/\(title)")), title: title) - let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext() context.topicGraph.addNode(node) let engine = DiagnosticEngine() @@ -417,7 +430,7 @@ TutorialArticle @1:1-42:2 title: 'Basic Augmented Reality App' time: '20' let reference = ResolvedTopicReference(bundleID: "org.swift.docc.TopicGraphTests", path: "/\(title)", sourceLanguage: .swift) let node = TopicGraph.Node(reference: reference, kind: .tutorialTableOfContents, source: .external, title: title) - let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") + let (_, context) = try await testBundleAndContext() context.topicGraph.addNode(node) let engine = DiagnosticEngine() @@ -437,7 +450,7 @@ TutorialArticle @1:1-42:2 title: 'Basic Augmented Reality App' time: '20' let range = SourceLocation(line: 1, column: 1, source: url)..( + _ directive: Directive.Type, + withAvailableAssetNames assetNames: [String], + content: () -> String, + file: StaticString = #filePath, + line: UInt = #line + ) async throws -> (renderBlockContent: [RenderBlockContent], problemIdentifiers: [String], directive: Directive?) { + let (_, context) = try await loadBundle(catalog: Folder(name: "Something.docc", content: assetNames.map { + DataFile(name: $0, data: Data()) + })) + + let (renderedContent, problems, directive, _) = try parseDirective(directive, context: context, content: content) + return (renderedContent, problems, directive) + } + private func parseDirective( _ directive: Directive.Type, context: DocumentationContext, From 44f3997426b8b369f54c6ffc51208057bbd3085a Mon Sep 17 00:00:00 2001 From: Prajwal Nadig Date: Wed, 15 Oct 2025 18:34:55 +0100 Subject: [PATCH 57/90] Fix `@SupportedLanguage` directive for articles (#1318) The `@SupportedLanguage` directive allows specifying a language that an article is available in. It can be used within the `@Metadata` directive like in the below example, where the article is made available in both Swift and Objective-C: ``` @Metadata { @SupportedLanguage(swift) @SupportedLanguage(objc) } ``` This directive is processed when creating the topic graph node for the article. The supported languages of an article need to be stored in the resolved topic reference that eventually gets serialised into the render node, which the navigator uses. When a catalog contains more than one module, any articles present are not registered in the documentation cache, since it is not possible to determine what module it is belongs to. In such cases, to correctly include the set of supported languages, they must be added to the reference in the topic graph node during creation, rather than creating the reference and later updating this information during registration. This patch adds the logic to store the set of supported languages during topic node creation for all articles, independent of the cache registration. rdar://160284853 --- .../Infrastructure/DocumentationContext.swift | 20 ++---- .../Rendering/RenderNodeTranslator.swift | 2 +- .../SwiftDocC/Semantics/Article/Article.swift | 12 ++++ .../Semantics/Metadata/Metadata.swift | 2 +- .../Indexing/NavigatorIndexTests.swift | 71 +++++++++++++++++++ .../DocumentationContextTests.swift | 42 +++++++++++ .../Semantics/ArticleTests.swift | 22 ++++++ 7 files changed, 156 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 304cc6a980..9d4d83216b 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -757,7 +757,7 @@ public class DocumentationContext { let (url, analyzed) = analyzedDocument let path = NodeURLGenerator.pathForSemantic(analyzed, source: url, bundle: inputs) - let reference = ResolvedTopicReference(bundleID: inputs.id, path: path, sourceLanguage: .swift) + var reference = ResolvedTopicReference(bundleID: inputs.id, path: path, sourceLanguage: .swift) // Since documentation extensions' filenames have no impact on the URL of pages, there is no need to enforce unique filenames for them. // At this point we consider all articles with an H1 containing link a "documentation extension." @@ -811,7 +811,11 @@ public class DocumentationContext { insertLandmarks(tutorialArticle.landmarks, from: topicGraphNode, source: url) } else if let article = analyzed as? Article { - + // If the article contains any `@SupportedLanguage` directives in the metadata, + // include those languages in the set of source languages for the reference. + if let supportedLanguages = article.supportedLanguages { + reference = reference.withSourceLanguages(supportedLanguages) + } // Here we create a topic graph node with the prepared data but we don't add it to the topic graph just yet // because we don't know where in the hierarchy the article belongs, we will add it later when crawling the manual curation via Topics task groups. let topicGraphNode = TopicGraph.Node(reference: reference, kind: .article, source: .file(url: url), title: article.title!.plainText) @@ -1842,17 +1846,7 @@ public class DocumentationContext { let path = NodeURLGenerator.pathForSemantic(article.value, source: article.source, bundle: inputs) // Use the languages specified by the `@SupportedLanguage` directives if present. - let availableSourceLanguages = article.value - .metadata - .flatMap { metadata in - let languages = Set( - metadata.supportedLanguages - .map(\.language) - ) - - return languages.isEmpty ? nil : languages - } - ?? availableSourceLanguages + let availableSourceLanguages = article.value.supportedLanguages ?? availableSourceLanguages // If available source languages are provided and it contains Swift, use Swift as the default language of // the article. diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index 8d88ec2128..a32006d4af 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -630,7 +630,7 @@ public struct RenderNodeTranslator: SemanticVisitor { node.hierarchyVariants = hierarchyVariants // Emit variants only if we're not compiling an article-only catalog to prevent renderers from - // advertising the page as "Swift", which is the language DocC assigns to pages in article only pages. + // advertising the page as "Swift", which is the language DocC assigns to pages in article only catalogs. // (github.com/swiftlang/swift-docc/issues/240). if let topLevelModule = context.soleRootModuleReference, try! context.entity(with: topLevelModule).kind.isSymbol diff --git a/Sources/SwiftDocC/Semantics/Article/Article.swift b/Sources/SwiftDocC/Semantics/Article/Article.swift index aec17835f9..42195c74c4 100644 --- a/Sources/SwiftDocC/Semantics/Article/Article.swift +++ b/Sources/SwiftDocC/Semantics/Article/Article.swift @@ -66,6 +66,18 @@ public final class Article: Semantic, MarkupConvertible, Abstracted, Redirected, return abstractSection?.paragraph } + /// The list of supported languages for the article, if present. + /// + /// This information is available via `@SupportedLanguage` in the `@Metadata` directive. + public var supportedLanguages: Set? { + guard let metadata = self.metadata else { + return nil + } + + let langs = metadata.supportedLanguages.map(\.language) + return langs.isEmpty ? nil : Set(langs) + } + /// An optional custom deprecation summary for a deprecated symbol. private(set) public var deprecationSummary: MarkupContainer? diff --git a/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift b/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift index 907c190506..4f172ea2a1 100644 --- a/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift +++ b/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift @@ -105,7 +105,7 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible { func validate(source: URL?, problems: inout [Problem]) -> Bool { // Check that something is configured in the metadata block - if documentationOptions == nil && technologyRoot == nil && displayName == nil && pageImages.isEmpty && customMetadata.isEmpty && callToAction == nil && availability.isEmpty && pageKind == nil && pageColor == nil && titleHeading == nil && redirects == nil && alternateRepresentations.isEmpty { + if documentationOptions == nil && technologyRoot == nil && displayName == nil && pageImages.isEmpty && customMetadata.isEmpty && callToAction == nil && availability.isEmpty && pageKind == nil && pageColor == nil && supportedLanguages.isEmpty && titleHeading == nil && redirects == nil && alternateRepresentations.isEmpty { let diagnostic = Diagnostic( source: source, severity: .information, diff --git a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift index bcab40af1e..6029282a9d 100644 --- a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift @@ -953,6 +953,77 @@ Root """ ) } + + // Bug: rdar://160284853 + // The supported languages of an article need to be stored in the resolved + // topic reference that eventually gets serialised into the render node, + // which the navigator uses. This must be done when creating the topic + // graph node, rather than updating the set of supported languages when + // registering the article. If a catalog contains more than one module, any + // articles present are not registered in the documentation cache, since it + // is not possible to determine what module it is belongs to. This test + // ensures that in such cases, the supported languages information is + // correctly included in the render node, and that the navigator is built + // correctly. + func testSupportedLanguageDirectiveForStandaloneArticles() async throws { + let catalog = Folder(name: "unit-test.docc", content: [ + InfoPlist(identifier: testBundleIdentifier), + TextFile(name: "UnitTest.md", utf8Content: """ + # UnitTest + + @Metadata { + @TechnologyRoot + @SupportedLanguage(data) + } + + ## Topics + + - + - ``Foo`` + """), + TextFile(name: "Article.md", utf8Content: """ + # Article + + Just a random article. + """), + // The correct way to configure a catalog is to have a single root module. If multiple modules, + // are present, it is not possible to determine which module an article is supposed to be + // registered with. We include multiple modules to prevent registering the articles in the + // documentation cache, to test if the supported languages are attached prior to registration. + JSONFile(name: "Foo.symbols.json", content: makeSymbolGraph(moduleName: "Foo", symbols: [ + makeSymbol(id: "some-symbol", language: SourceLanguage.data, kind: .class, pathComponents: ["SomeSymbol"]), + ])) + ]) + + let (_, context) = try await loadBundle(catalog: catalog) + + let renderContext = RenderContext(documentationContext: context) + let converter = DocumentationContextConverter(context: context, renderContext: renderContext) + + let targetURL = try createTemporaryDirectory() + let builder = NavigatorIndex.Builder(outputURL: targetURL, bundleIdentifier: testBundleIdentifier) + builder.setup() + + for identifier in context.knownPages { + let entity = try context.entity(with: identifier) + let renderNode = try XCTUnwrap(converter.renderNode(for: entity)) + try builder.index(renderNode: renderNode) + } + + builder.finalize() + + let navigatorIndex = try XCTUnwrap(builder.navigatorIndex) + + let expectedNavigator = """ +[Root] +┗╸UnitTest + ┣╸Article + ┗╸Foo + ┣╸Classes + ┗╸SomeSymbol +""" + XCTAssertEqual(navigatorIndex.navigatorTree.root.dumpTree(), expectedNavigator) + } func testNavigatorIndexUsingPageTitleGeneration() async throws { let (_, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests") diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index da3461c07f..e788f1b192 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -5772,6 +5772,48 @@ let expected = """ XCTAssertEqual(solution.replacements.first?.replacement, "") } + func testSupportedLanguageDirectiveForStandaloneArticles() async throws { + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Root.md", utf8Content: """ + # Root + + @Metadata { + @TechnologyRoot + @SupportedLanguage(objc) + @SupportedLanguage(data) + } + + ## Topics + + - + """), + TextFile(name: "Article.md", utf8Content: """ + # Article + + @Metadata { + @SupportedLanguage(objc) + @SupportedLanguage(data) + } + """), + // The correct way to configure a catalog is to have a single root module. If multiple modules, + // are present, it is not possible to determine which module an article is supposed to be + // registered with. We include multiple modules to prevent registering the articles in the + // documentation cache, to test if the supported languages are attached prior to registration. + JSONFile(name: "Foo.symbols.json", content: makeSymbolGraph(moduleName: "Foo")), + ]) + + let (bundle, context) = try await loadBundle(catalog: catalog) + + XCTAssert(context.problems.isEmpty, "Unexpected problems:\n\(context.problems.map(\.diagnostic.summary).joined(separator: "\n"))") + + do { + let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/unit-test/Article", sourceLanguage: .data) + // Find the topic graph node for the article + let node = context.topicGraph.nodes.first { $0.key == reference }?.value + // Ensure that the reference within the topic graph node contains the supported languages + XCTAssertEqual(node?.reference.sourceLanguages, [.objectiveC, .data]) + } + } } func assertEqualDumps(_ lhs: String, _ rhs: String, file: StaticString = #filePath, line: UInt = #line) { diff --git a/Tests/SwiftDocCTests/Semantics/ArticleTests.swift b/Tests/SwiftDocCTests/Semantics/ArticleTests.swift index 18968e24c1..84c032f057 100644 --- a/Tests/SwiftDocCTests/Semantics/ArticleTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ArticleTests.swift @@ -249,4 +249,26 @@ class ArticleTests: XCTestCase { XCTAssertNil(semantic.metadata?.pageKind) XCTAssertNil(semantic.metadata?.titleHeading) } + + func testSupportedLanguageDirective() async throws { + let source = """ + # Root + + @Metadata { + @SupportedLanguage(swift) + @SupportedLanguage(objc) + @SupportedLanguage(data) + } + """ + let document = Document(parsing: source, options: [.parseBlockDirectives]) + let (bundle, _) = try await testBundleAndContext() + var problems = [Problem]() + let article = Article(from: document, source: nil, for: bundle, problems: &problems) + + XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(DiagnosticConsoleWriter.formattedDescription(for: problems))") + + XCTAssertNotNil(article) + XCTAssertNotNil(article?.metadata, "Article should have a metadata container since the markup has a @Metadata directive") + XCTAssertEqual(article?.metadata?.supportedLanguages.map(\.language), [.swift, .objectiveC, .data]) + } } From 57c60f3a3db36380b674a616051449a17eccfbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Thu, 16 Oct 2025 12:34:32 +0200 Subject: [PATCH 58/90] Omit the "doc://" and identifier prefix from V2 `link(_:)` requests (#1320) * Omit the "doc://" and identifier prefix from V2 `link(_:)` requests rdar://162694487 * Remove repeated "link" word in code comment Co-authored-by: Andrea Fernandez Buitrago <15234535+anferbui@users.noreply.github.com> --------- Co-authored-by: Andrea Fernandez Buitrago <15234535+anferbui@users.noreply.github.com> --- ...ocessReferenceResolver+Communication.swift | 4 + .../OutOfProcessReferenceResolver.swift | 12 +-- ...OutOfProcessReferenceResolverV2Tests.swift | 76 +++++++++++++++++-- 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift index 72b321d52d..4f621ab942 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift @@ -49,6 +49,10 @@ extension OutOfProcessReferenceResolver { public enum RequestV2: Codable { /// A request to resolve a link /// + /// DocC omits the "doc:\/\/" and identifier prefix from the link string because it would be the same for every link request. + /// For example: if your resolver registers itself for the `"your.resolver.id"` identifier---by sending it in the ``ResponseV2/identifierAndCapabilities(_:_:)`` handshake message--- + /// and DocC encounters a `doc://your.resolver.id/path/to/some-page#some-fragment` link in any documentation content, DocC sends the `"/path/to/some-page#some-fragment"` link to your resolver. + /// /// Your external resolver should respond with either: /// - a ``ResponseV2/resolved(_:)`` message, with information about the requested link. /// - a ``ResponseV2/failure(_:)`` message, with human-readable information about the problem that the external link resolver encountered while resolving the link. diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift index 06252d82e1..566f50fe73 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift @@ -406,11 +406,12 @@ extension OutOfProcessReferenceResolver { private var linkCache: [String /* either a USR or an absolute UnresolvedTopicReference */: LinkDestinationSummary] = [:] func resolve(unresolvedReference: UnresolvedTopicReference) throws -> TopicReferenceResolutionResult { - let linkString = unresolvedReference.topicURL.absoluteString - if let cachedSummary = linkCache[linkString] { + let unresolvedReferenceString = unresolvedReference.topicURL.absoluteString + if let cachedSummary = linkCache[unresolvedReferenceString] { return .success( makeReference(for: cachedSummary) ) } + let linkString = unresolvedReference.topicURL.url.withoutHostAndPortAndScheme().standardized.absoluteString let response: ResponseV2 = try longRunningProcess.sendAndWait(request: RequestV2.link(linkString)) switch response { @@ -418,12 +419,13 @@ extension OutOfProcessReferenceResolver { throw Error.executableSentBundleIdentifierAgain case .failure(let diagnosticMessage): + let prefixLength = 2 /* for "//" */ + bundleID.rawValue.utf8.count let solutions: [Solution] = (diagnosticMessage.solutions ?? []).map { Solution(summary: $0.summary, replacements: $0.replacement.map { replacement in [Replacement( // The replacement ranges are relative to the link itself. - // To replace the entire link, we create a range from 0 to the original length, both offset by -4 (the "doc:" length) - range: SourceLocation(line: 0, column: -4, source: nil) ..< SourceLocation(line: 0, column: linkString.utf8.count - 4, source: nil), + // To replace only the path and fragment portion of the link, we create a range from 0 to the relative link string length, both offset by the bundle ID length + range: SourceLocation(line: 0, column: prefixLength, source: nil) ..< SourceLocation(line: 0, column: linkString.utf8.count + prefixLength, source: nil), replacement: replacement )] } ?? []) @@ -435,7 +437,7 @@ extension OutOfProcessReferenceResolver { case .resolved(let linkSummary): // Cache the information for the original authored link - linkCache[linkString] = linkSummary + linkCache[unresolvedReferenceString] = linkSummary // Cache the information for the resolved reference. That's what's will be used when returning the entity later. let reference = makeReference(for: linkSummary) linkCache[reference.absoluteString] = linkSummary diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift index 251a757592..b4a5aa1b36 100644 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift @@ -424,7 +424,7 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { let diagnosticInfo = OutOfProcessReferenceResolver.ResponseV2.DiagnosticInformation( summary: "Some external link issue summary", solutions: [ - .init(summary: "Some external solution", replacement: "some-replacement") + .init(summary: "Some external solution", replacement: "/some-replacement") ] ) let encodedDiagnostic = try String(decoding: JSONEncoder().encode(diagnosticInfo), as: UTF8.self) @@ -473,7 +473,7 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { let solution = try XCTUnwrap(problem.possibleSolutions.first) XCTAssertEqual(solution.summary, "Some external solution") XCTAssertEqual(solution.replacements.count, 1) - XCTAssertEqual(solution.replacements.first?.range.lowerBound, .init(line: 3, column: 65, source: nil)) + XCTAssertEqual(solution.replacements.first?.range.lowerBound, .init(line: 3, column: 87, source: nil)) XCTAssertEqual(solution.replacements.first?.range.upperBound, .init(line: 3, column: 97, source: nil)) // Verify the warning presentation @@ -493,7 +493,7 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { 1 | # My root page 2 | 3 + This page contains an external link that will fail to resolve: - | ╰─\(suggestion)suggestion: Some external solution\(clear) + | ╰─\(suggestion)suggestion: Some external solution\(clear) """) @@ -504,17 +504,81 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { XCTAssertEqual(try solution.applyTo(original), """ # My root page - This page contains an external link that will fail to resolve: + This page contains an external link that will fail to resolve: + """) + } + + func testOnlySendsPathAndFragmentInLinkRequest() async throws { + let externalBundleID: DocumentationBundle.Identifier = "com.example.test" + + let resolver: OutOfProcessReferenceResolver + let savedRequestsFile: URL + do { + let temporaryFolder = try createTemporaryDirectory() + savedRequestsFile = temporaryFolder.appendingPathComponent("saved-requests.txt") + + let executableLocation = temporaryFolder.appendingPathComponent("link-resolver-executable") + try """ + #!/bin/bash + echo '{"identifier":"\(externalBundleID)","capabilities": 0}' # Write this resolver's identifier & capabilities + read # Wait for docc to send a request + echo $REPLY >> \(savedRequestsFile.path) # Save the raw request string + echo '{"failure":"ignored error message"}' # Respond with an error message + # Repeat the same read-save-respond steps 2 more times + read # Wait for 2nd request + echo $REPLY >> \(savedRequestsFile.path) # Save the raw request + echo '{"failure":"ignored error message"}' # Respond + read # Wait for 3rd request + echo $REPLY >> \(savedRequestsFile.path) # Save the raw request + echo '{"failure":"ignored error message"}' # Respond + """.write(to: executableLocation, atomically: true, encoding: .utf8) + + // `0o0700` is `-rwx------` (read, write, & execute only for owner) + try FileManager.default.setAttributes([.posixPermissions: 0o0700], ofItemAtPath: executableLocation.path) + XCTAssert(FileManager.default.isExecutableFile(atPath: executableLocation.path)) + + resolver = try OutOfProcessReferenceResolver(processLocation: executableLocation, errorOutputHandler: { _ in }) + } + + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Something.md", utf8Content: """ + # My root page + + This page contains an 3 external links hat will fail to resolve: + - + - + - + """) + ]) + let inputDirectory = Folder(name: "path", content: [Folder(name: "to", content: [catalog])]) + + var configuration = DocumentationContext.Configuration() + configuration.externalDocumentationConfiguration.sources = [ + externalBundleID: resolver + ] + // Create the context, just to process all the documentation and make the 3 external link requests + _ = try await loadBundle(catalog: inputDirectory, configuration: configuration) + + // The requests can come in any order so we sort the output lines for easier comparison + let readRequests = try String(contentsOf: savedRequestsFile, encoding: .utf8) + .components(separatedBy: .newlines) + .filter { !$0.isEmpty } + .sorted(by: \.count) + .joined(separator: "\n") + XCTAssertEqual(readRequests, """ + {"link":"/some-link"} + {"link":"/path/to/some-link"} + {"link":"/path/to/some-link#some-fragment"} """) } func testEncodingAndDecodingRequests() throws { do { - let request = OutOfProcessReferenceResolver.RequestV2.link("doc://com.example/path/to/something") + let request = OutOfProcessReferenceResolver.RequestV2.link("/path/to/some-page#some-fragment") let data = try JSONEncoder().encode(request) if case .link(let link) = try JSONDecoder().decode(OutOfProcessReferenceResolver.RequestV2.self, from: data) { - XCTAssertEqual(link, "doc://com.example/path/to/something") + XCTAssertEqual(link, "/path/to/some-page#some-fragment") } else { XCTFail("Decoded the wrong type of request") } From 55fbcce8e39b962f8691fafa28d26dc93e703418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 21 Oct 2025 11:49:28 +0200 Subject: [PATCH 59/90] Avoid full URL parsing to trim a known prefix from a reference string (#1323) rdar://162860967 --- .../External Data/OutOfProcessReferenceResolver.swift | 5 ++++- .../OutOfProcessReferenceResolverV2Tests.swift | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift index 566f50fe73..25523e6520 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift @@ -411,7 +411,10 @@ extension OutOfProcessReferenceResolver { return .success( makeReference(for: cachedSummary) ) } - let linkString = unresolvedReference.topicURL.url.withoutHostAndPortAndScheme().standardized.absoluteString + let linkString = String( + unresolvedReferenceString.dropFirst(6) // "doc://" + .drop(while: { $0 != "/" }) // the known identifier (host component) + ) let response: ResponseV2 = try longRunningProcess.sendAndWait(request: RequestV2.link(linkString)) switch response { diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift index b4a5aa1b36..e4e42b500a 100644 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift @@ -531,6 +531,9 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { read # Wait for 3rd request echo $REPLY >> \(savedRequestsFile.path) # Save the raw request echo '{"failure":"ignored error message"}' # Respond + read # Wait for 4th request + echo $REPLY >> \(savedRequestsFile.path) # Save the raw request + echo '{"failure":"ignored error message"}' # Respond """.write(to: executableLocation, atomically: true, encoding: .utf8) // `0o0700` is `-rwx------` (read, write, & execute only for owner) @@ -544,10 +547,11 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { TextFile(name: "Something.md", utf8Content: """ # My root page - This page contains an 3 external links hat will fail to resolve: + This page contains an 4 external links that will fail to resolve: - - - + - """) ]) let inputDirectory = Folder(name: "path", content: [Folder(name: "to", content: [catalog])]) @@ -568,6 +572,7 @@ class OutOfProcessReferenceResolverV2Tests: XCTestCase { XCTAssertEqual(readRequests, """ {"link":"/some-link"} {"link":"/path/to/some-link"} + {"link":"//not-parsable-as-url"} {"link":"/path/to/some-link#some-fragment"} """) } From b27288dd99b0e2715ed1a2d5720cd0f23118c030 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Fri, 24 Oct 2025 02:21:25 -0600 Subject: [PATCH 60/90] accept sameShape constraint kind from SymbolKit (#1290) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * accept sameShape constraint kind from SymbolKit rdar://129338238 * review: create a test fixture for sameShape constraints * Use merged version of SymbolKit changes * Fix failure in newly added test --------- Co-authored-by: David Rönnqvist --- Package.resolved | 2 +- .../Rendering/Symbol/ConformanceSection.swift | 1 + .../ConstraintsRenderSectionTests.swift | 34 ++ .../SameShapeConstraint.symbols.json | 321 ++++++++++++++++++ 4 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 Tests/SwiftDocCTests/Test Resources/SameShapeConstraint.symbols.json diff --git a/Package.resolved b/Package.resolved index ffb8d1e20d..ca13d9b29a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -69,7 +69,7 @@ "location" : "https://github.com/swiftlang/swift-docc-symbolkit.git", "state" : { "branch" : "main", - "revision" : "96bce1cfad4f4d7e265c1eb46729ebf8a7695f4b" + "revision" : "65fa99b0b84db5c743415a00ee7f0099837aae1b" } }, { diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/ConformanceSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/ConformanceSection.swift index 7048c13d3a..73ca49b210 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/ConformanceSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/ConformanceSection.swift @@ -20,6 +20,7 @@ extension Constraint.Kind { case .conformance: return "conforms to" case .sameType: return "is" case .superclass: return "inherits" + case .sameShape: return "is the same shape as" } } } diff --git a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift index 7b67c54998..19bd1673ac 100644 --- a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift @@ -11,6 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC +import SwiftDocCTestUtilities import SymbolKit fileprivate let jsonDecoder = JSONDecoder() @@ -273,6 +274,39 @@ class ConstraintsRenderSectionTests: XCTestCase { // Verify we've removed the "Self." prefix in the type names XCTAssertEqual(renderReference.conformance?.constraints.map(flattenInlineElements).joined(), "Element conforms to MyProtocol and Index conforms to Equatable.") } + + func testRenderSameShape() async throws { + let symbolGraphFile = Bundle.module.url( + forResource: "SameShapeConstraint", + withExtension: "symbols.json", + subdirectory: "Test Resources" + )! + + let catalog = Folder(name: "unit-test.docc", content: [ + InfoPlist(displayName: "SameShapeConstraint", identifier: "com.test.example"), + CopyOfFile(original: symbolGraphFile), + ]) + + let (_, context) = try await loadBundle(catalog: catalog) + + // Compile docs and verify contents + let node = try context.entity(with: ResolvedTopicReference(bundleID: context.inputs.id, path: "/documentation/SameShapeConstraint/function(_:)", sourceLanguage: .swift)) + let symbol = node.semantic as! Symbol + var translator = RenderNodeTranslator(context: context, identifier: node.reference) + let renderNode = translator.visitSymbol(symbol) as! RenderNode + + guard let renderReference = renderNode.references.first(where: { (key, value) -> Bool in + return key.hasSuffix("function(_:)") + })?.value as? TopicRenderReference else { + XCTFail("Did not find render reference to function(_:)") + return + } + + // The symbol graph only defines constraints on the `swiftGenerics` mixin, + // which docc doesn't load or render. + // However, this test should still run without crashing on decoding the symbol graph. + XCTAssertEqual(renderReference.conformance?.constraints.map(flattenInlineElements).joined(), nil) + } } fileprivate func flattenInlineElements(el: RenderInlineContent) -> String { diff --git a/Tests/SwiftDocCTests/Test Resources/SameShapeConstraint.symbols.json b/Tests/SwiftDocCTests/Test Resources/SameShapeConstraint.symbols.json new file mode 100644 index 0000000000..183b29cd2e --- /dev/null +++ b/Tests/SwiftDocCTests/Test Resources/SameShapeConstraint.symbols.json @@ -0,0 +1,321 @@ +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 6, + "patch": 0 + }, + "generator": "Apple Swift version 6.2 (swiftlang-6.2.0.19.9 clang-1700.3.19.1)" + }, + "module": { + "name": "SameShapeConstraint", + "platform": { + "architecture": "arm64", + "vendor": "apple", + "operatingSystem": { + "name": "macosx", + "minimumVersion": { + "major": 12, + "minor": 4 + } + } + } + }, + "symbols": [ + { + "kind": { + "identifier": "swift.func", + "displayName": "Function" + }, + "identifier": { + "precise": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "function(_:)" + ], + "names": { + "title": "function(_:)", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "function" + }, + { + "kind": "text", + "spelling": "((" + }, + { + "kind": "keyword", + "spelling": "repeat" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element0", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element0L_xmfp" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element1", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element1L_q_mfp" + }, + { + "kind": "text", + "spelling": ")))" + } + ] + }, + "functionSignature": { + "parameters": [ + { + "name": "_", + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "_" + }, + { + "kind": "text", + "spelling": ": (" + }, + { + "kind": "keyword", + "spelling": "repeat" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element0", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element0L_xmfp" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element1", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element1L_q_mfp" + }, + { + "kind": "text", + "spelling": "))" + } + ] + } + ], + "returns": [ + { + "kind": "text", + "spelling": "()" + } + ] + }, + "swiftGenerics": { + "parameters": [ + { + "name": "Element0", + "index": 0, + "depth": 0 + }, + { + "name": "Element1", + "index": 1, + "depth": 0 + } + ], + "constraints": [ + { + "kind": "sameShape", + "lhs": "each Element0", + "rhs": "each Element1" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "function" + }, + { + "kind": "text", + "spelling": "(" + }, + { + "kind": "externalParam", + "spelling": "_" + }, + { + "kind": "text", + "spelling": ": (" + }, + { + "kind": "keyword", + "spelling": "repeat" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element0", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element0L_xmfp" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element1", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element1L_q_mfp" + }, + { + "kind": "text", + "spelling": "))) " + }, + { + "kind": "keyword", + "spelling": "where" + }, + { + "kind": "text", + "spelling": " (repeat (each " + }, + { + "kind": "typeIdentifier", + "spelling": "Element0" + }, + { + "kind": "text", + "spelling": ", each " + }, + { + "kind": "typeIdentifier", + "spelling": "Element1" + }, + { + "kind": "text", + "spelling": ")) : Any" + } + ], + "accessLevel": "public", + "location": { + "uri": "file:///path/to/SameShapeConstraint/SwiftClass.swift", + "position": { + "line": 9, + "character": 12 + } + } + } + ], + "relationships": [] +} From e15f4e10e60c1f554e1c27912804ab7272dbfaec Mon Sep 17 00:00:00 2001 From: Prajwal Nadig Date: Fri, 31 Oct 2025 14:11:55 +0000 Subject: [PATCH 61/90] Store all source languages in documentation node (#1326) The documentation node stores the list of source languages that the node is available in. Previously, this information was being stored in the render node as a set containing the singular source language of the node. This worked in the past when Swift was the only relevant language, but `sourceLanguage` is now relegated to a backwards-compatible property that queries `sourceLanguages` for the value it returns. This patch updates the documentation node constructors to correctly store the set of source languages in the node. rdar://163034867 --- .../SwiftDocC/Model/DocumentationNode.swift | 6 ++--- .../Model/DocumentationNodeTests.swift | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDocC/Model/DocumentationNode.swift b/Sources/SwiftDocC/Model/DocumentationNode.swift index 0ff7e33841..d0fe3e811f 100644 --- a/Sources/SwiftDocC/Model/DocumentationNode.swift +++ b/Sources/SwiftDocC/Model/DocumentationNode.swift @@ -238,7 +238,7 @@ public struct DocumentationNode { Symbol.Overloads(references: [], displayIndex: overloadData.overloadGroupIndex) }) - var languages = Set([reference.sourceLanguage]) + var languages = reference.sourceLanguages var operatingSystemName = platformName.map({ Set([$0]) }) ?? [] for (_, symbolAvailability) in symbolAvailabilityVariants.allValues { @@ -776,7 +776,7 @@ public struct DocumentationNode { let symbolAvailability = symbol.mixins[SymbolGraph.Symbol.Availability.mixinKey] as? SymbolGraph.Symbol.Availability - var languages = Set([reference.sourceLanguage]) + var languages = reference.sourceLanguages var operatingSystemName = platformName.map({ Set([$0]) }) ?? [] let availabilityDomains = symbolAvailability?.availability.compactMap({ $0.domain?.rawValue }) @@ -859,7 +859,7 @@ public struct DocumentationNode { self.semantic = article self.sourceLanguage = reference.sourceLanguage self.name = .conceptual(title: article.title?.title ?? "") - self.availableSourceLanguages = [reference.sourceLanguage] + self.availableSourceLanguages = reference.sourceLanguages self.docChunks = [DocumentationChunk(source: .documentationExtension, markup: articleMarkup)] self.markup = articleMarkup self.isVirtual = false diff --git a/Tests/SwiftDocCTests/Model/DocumentationNodeTests.swift b/Tests/SwiftDocCTests/Model/DocumentationNodeTests.swift index 5f17246fd5..669c3c3146 100644 --- a/Tests/SwiftDocCTests/Model/DocumentationNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/DocumentationNodeTests.swift @@ -76,4 +76,27 @@ class DocumentationNodeTests: XCTestCase { XCTAssertEqual(DocumentationNode.symbolKind(for: .typeConstant), .typeProperty) XCTAssertEqual(DocumentationNode.symbolKind(for: .object), .dictionary) } + + func testWithMultipleSourceLanguages() throws { + let sourceLanguages: Set = [.swift, .objectiveC] + // Test if articles contain all available source languages + let article = Article(markup: Document(parsing: "# Title", options: []), metadata: nil, redirects: nil, options: [:]) + let articleNode = try DocumentationNode( + reference: ResolvedTopicReference(bundleID: "org.swift.docc", path: "/blah", sourceLanguages: sourceLanguages), + article: article + ) + XCTAssertEqual(articleNode.availableSourceLanguages, sourceLanguages) + + // Test if symbols contain all available source languages + let symbol = makeSymbol(id: "blah", kind: .class, pathComponents: ["blah"]) + let symbolNode = DocumentationNode( + reference: ResolvedTopicReference(bundleID: "org.swift.docc", path: "/blah", sourceLanguages: sourceLanguages), + symbol: symbol, + platformName: nil, + moduleReference: ResolvedTopicReference(bundleID: "org.swift.docc", path: "/blah", sourceLanguages: sourceLanguages), + article: nil, + engine: DiagnosticEngine() + ) + XCTAssertEqual(symbolNode.availableSourceLanguages, sourceLanguages) + } } From 15dde00af7ac3433567d54af7041be6beac95c0d Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Mon, 3 Nov 2025 15:11:39 -0700 Subject: [PATCH 62/90] trim whitespace in doxygen formatter assertion (#1329) rdar://163704908 --- Tests/SwiftDocCTests/Semantics/DoxygenTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift b/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift index 100ad050b7..9832537c9e 100644 --- a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift +++ b/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift @@ -96,7 +96,7 @@ class DoxygenTests: XCTestCase { let symbol = try XCTUnwrap(node.semantic as? Symbol) XCTAssertEqual(symbol.abstract?.format(), "This is an abstract.") - XCTAssertEqual(symbol.discussion?.content.map { $0.format() }, [ + XCTAssertEqual(symbol.discussion?.content.map { $0.format().trimmingCharacters(in: .whitespacesAndNewlines) }, [ #"\abstract This is description with abstract."#, #"\discussion This is a discussion linking to ``doc://unit-test/documentation/ModuleName/AnotherClass`` and ``doc://unit-test/documentation/ModuleName/AnotherClass/prop``."#, #"\note This is a note linking to ``doc://unit-test/documentation/ModuleName/Class3`` and ``Class3/prop2``."# From 7b74f5cfb1148277c15af37659f910cae51dcd59 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Tue, 4 Nov 2025 10:11:24 -0800 Subject: [PATCH 63/90] build: add an initial CMake based build system for DocC (#818) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build: add an initial CMake based build system for DocC This allows us to cross-compile DocC and reduce the size of the binary by sharing the toolchain artifacts. * Update CONTRIBUTING.md Co-authored-by: David Rönnqvist --------- Co-authored-by: David Rönnqvist --- CMakeLists.txt | 42 ++ CONTRIBUTING.md | 27 +- Sources/CMakeLists.txt | 12 + Sources/SwiftDocC/CMakeLists.txt | 481 ++++++++++++++++++++++ Sources/SwiftDocCUtilities/CMakeLists.txt | 74 ++++ Sources/docc/CMakeLists.txt | 16 + 6 files changed, 651 insertions(+), 1 deletion(-) create mode 100644 CMakeLists.txt create mode 100644 Sources/CMakeLists.txt create mode 100644 Sources/SwiftDocC/CMakeLists.txt create mode 100644 Sources/SwiftDocCUtilities/CMakeLists.txt create mode 100644 Sources/docc/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..4cab897c1f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,42 @@ +#[[ +This source file is part of the Swift open source project + +Copyright © 2014 - 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 +#]] + +cmake_minimum_required(VERSION 3.24) + +project(SwiftDocC + LANGUAGES Swift) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) +set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY MultiThreadedDLL) +set(CMAKE_Swift_LANGUAGE_VERSION 5) + +enable_language(C) +include(GNUInstallDirs) + +# NOTE(compnerd) workaround CMake issues +add_compile_options("$<$:SHELL:-swift-version 5>") +add_compile_options("$<$:SHELL:-enable-upcoming-feature ConciseMagicFile>") +add_compile_options("$<$:SHELL:-enable-upcoming-feature ExistentialAny>") +add_compile_options("$<$:SHELL:-enable-upcoming-feature InternalImportsByDefault>") + +find_package(ArgumentParser) +find_package(SwiftASN1) +find_package(SwiftCrypto) +find_package(SwiftMarkdown) +find_package(LMDB) +find_package(SymbolKit) +find_package(cmark-gfm) + +add_compile_options("$<$:-package-name;SwiftDocC>") + +add_subdirectory(Sources) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b72111d846..da6d76fb8f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -350,7 +350,32 @@ by running the test suite in a Docker environment that simulates Swift on Linux. cd swift-docc swift run docc ``` - + +## Updating Build Rules + +In order to build DocC as part of the Windows toolchain distribution uniformly, +a parallel CMake based build exists. Note that this is **not** supported for +development purposes (you cannot execute the test suite with this build). + +CMake requires that the full file list is kept up-to-date. When adding or +removing files in a given module, the `CMakeLists.txt` list must be updated to +the file list. + +The 1-line script below lists all the Swift files in the current directory tree. +You can use the script's output to replace the list of files in the CMakeLists.txt file for each target that you added files to or removed files from. + +```bash +python -c "print('\n'.join((f'{chr(34)}{path}{chr(34)}' if ' ' in path else path) for path in sorted(str(path) for path in __import__('pathlib').Path('.').rglob('*.swift'))))" +``` + +This should provide the listing of files in the module that can be used to +update the `CMakeLists.txt` associated with the target. + +In the case that a new target is added to the project, the new directory would +need to add the new library or executable target (`add_library` and +`add_executable` respectively) and the new target subdirectory must be listed in +the `Sources/CMakeLists.txt` (via `add_subdirectory`). + ## Continuous Integration Swift-DocC uses [swift-ci](https://ci.swift.org) infrastructure for its continuous integration diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt new file mode 100644 index 0000000000..8f8e806655 --- /dev/null +++ b/Sources/CMakeLists.txt @@ -0,0 +1,12 @@ +#[[ +This source file is part of the Swift open source project + +Copyright © 2014 - 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 +#]] + +add_subdirectory(SwiftDocC) +add_subdirectory(SwiftDocCUtilities) +add_subdirectory(docc) diff --git a/Sources/SwiftDocC/CMakeLists.txt b/Sources/SwiftDocC/CMakeLists.txt new file mode 100644 index 0000000000..6a54c8c802 --- /dev/null +++ b/Sources/SwiftDocC/CMakeLists.txt @@ -0,0 +1,481 @@ +#[[ +This source file is part of the Swift open source project + +Copyright © 2014 - 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 +#]] + +add_library(SwiftDocC + Benchmark/Benchmark.swift + Benchmark/BenchmarkResults.swift + Benchmark/Metrics/Duration.swift + Benchmark/Metrics/ExternalTopicsHash.swift + Benchmark/Metrics/OutputSize.swift + Benchmark/Metrics/PeakMemory.swift + Benchmark/Metrics/TopicAnchorHash.swift + Benchmark/Metrics/TopicGraphHash.swift + Benchmark/Metrics.swift + "Catalog Processing/GeneratedCurationWriter.swift" + Checker/Checker.swift + Checker/Checkers/AbstractContainsFormattedTextOnly.swift + Checker/Checkers/DuplicateTopicsSection.swift + Checker/Checkers/InvalidAdditionalTitle.swift + Checker/Checkers/InvalidCodeBlockOption.swift + Checker/Checkers/MissingAbstract.swift + Checker/Checkers/NonInclusiveLanguageChecker.swift + Checker/Checkers/NonOverviewHeadingChecker.swift + Checker/Checkers/SeeAlsoInTopicsHeadingChecker.swift + Converter/DocumentationContextConverter.swift + Converter/DocumentationNodeConverter.swift + Converter/RenderNode+Coding.swift + Converter/Rewriter/RemoveHierarchyTransformation.swift + Converter/Rewriter/RemoveUnusedReferencesTransformation.swift + Converter/Rewriter/RenderNodeTransformationComposition.swift + Converter/Rewriter/RenderNodeTransformationContext.swift + Converter/Rewriter/RenderNodeTransformer.swift + Converter/Rewriter/RenderNodeTransforming.swift + Converter/TopicRenderReferenceEncoder.swift + Coverage/DocumentationCoverageOptions.swift + DocumentationService/Convert/ConvertService+DataProvider.swift + DocumentationService/Convert/ConvertService.swift + "DocumentationService/Convert/Fallback Link Resolution/ConvertServiceFallbackResolver.swift" + "DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift" + DocumentationService/DocumentationServer+createDefaultServer.swift + DocumentationService/ExternalReferenceResolverServiceClient.swift + DocumentationService/Models/DocumentationServer+Message.swift + DocumentationService/Models/DocumentationServer+MessageType.swift + DocumentationService/Models/DocumentationServer.swift + DocumentationService/Models/DocumentationServerError.swift + DocumentationService/Models/DocumentationServerProtocol.swift + DocumentationService/Models/DocumentationService.swift + DocumentationService/Models/Services/Convert/ConvertRequest.swift + DocumentationService/Models/Services/Convert/ConvertRequestContextWrapper.swift + DocumentationService/Models/Services/Convert/ConvertResponse.swift + DocumentationService/Models/Services/Convert/ConvertServiceError.swift + Indexing/Indexable.swift + Indexing/IndexingError.swift + Indexing/IndexingRecord.swift + Indexing/Navigator/AvailabilityIndex+Ext.swift + Indexing/Navigator/AvailabilityIndex.swift + Indexing/Navigator/NavigatorIndex.swift + Indexing/Navigator/NavigatorItem.swift + Indexing/Navigator/NavigatorTree.swift + Indexing/Navigator/RenderNode+NavigatorIndex.swift + Indexing/RenderBlockContent+TextIndexing.swift + Indexing/RenderIndexJSON/RenderIndex.swift + Indexing/RenderInlineContent+TextIndexing.swift + Indexing/RenderNode+Indexable.swift + Indexing/RenderNode+Relationships.swift + Indexing/RenderSection+TextIndexing.swift + Indexing/TutorialSectionsRenderSection+Indexable.swift + "Infrastructure/Bundle Assets/BundleData.swift" + "Infrastructure/Bundle Assets/DataAssetManager.swift" + "Infrastructure/Bundle Assets/SVGIDExtractor.swift" + "Infrastructure/Communication/Code colors/CodeColors.swift" + "Infrastructure/Communication/Code colors/CodeColorsPreferenceKey.swift" + "Infrastructure/Communication/Code colors/SRGBColor.swift" + Infrastructure/Communication/CommunicationBridge.swift + Infrastructure/Communication/Foundation/AnyCodable.swift + Infrastructure/Communication/Foundation/JSON.swift + Infrastructure/Communication/Message+Codable.swift + Infrastructure/Communication/Message.swift + Infrastructure/Communication/MessageType.swift + Infrastructure/Communication/WebKitCommunicationBridge.swift + Infrastructure/ContentCache.swift + Infrastructure/Context/DocumentationContext+Configuration.swift + Infrastructure/ConvertActionConverter.swift + Infrastructure/ConvertOutputConsumer.swift + Infrastructure/CoverageDataEntry.swift + Infrastructure/Diagnostics/ANSIAnnotation.swift + Infrastructure/Diagnostics/Diagnostic.swift + Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift + Infrastructure/Diagnostics/DiagnosticConsumer.swift + Infrastructure/Diagnostics/DiagnosticEngine.swift + Infrastructure/Diagnostics/DiagnosticFile.swift + Infrastructure/Diagnostics/DiagnosticFileWriter.swift + Infrastructure/Diagnostics/DiagnosticFormattingOptions.swift + Infrastructure/Diagnostics/DiagnosticNote.swift + Infrastructure/Diagnostics/DiagnosticSeverity.swift + Infrastructure/Diagnostics/ParseDirectiveArguments.swift + Infrastructure/Diagnostics/Problem.swift + Infrastructure/Diagnostics/Replacement.swift + Infrastructure/Diagnostics/Solution.swift + Infrastructure/Diagnostics/SourcePosition.swift + Infrastructure/Diagnostics/TerminalHelper.swift + Infrastructure/DocumentationBundle+Identifier.swift + Infrastructure/DocumentationBundle.swift + Infrastructure/DocumentationBundleFileTypes.swift + Infrastructure/DocumentationContext+Breadcrumbs.swift + Infrastructure/DocumentationContext.swift + Infrastructure/DocumentationCurator.swift + Infrastructure/Extensions/KindIdentifier+Curation.swift + Infrastructure/Extensions/SymbolGraph.Symbol.Location+URL.swift + "Infrastructure/External Data/ExternalDocumentationSource.swift" + "Infrastructure/External Data/ExternalMetadata.swift" + "Infrastructure/External Data/GlobalExternalSymbolResolver.swift" + "Infrastructure/External Data/OutOfProcessReferenceResolver+Communication.swift" + "Infrastructure/External Data/OutOfProcessReferenceResolver+DeprecatedCommunication.swift" + "Infrastructure/External Data/OutOfProcessReferenceResolver.swift" + "Infrastructure/Input Discovery/DataProvider.swift" + "Infrastructure/Input Discovery/DocumentationInputsProvider.swift" + "Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift" + "Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift" + "Infrastructure/Link Resolution/LinkResolver.swift" + "Infrastructure/Link Resolution/PathHierarchy+DisambiguatedPaths.swift" + "Infrastructure/Link Resolution/PathHierarchy+Dump.swift" + "Infrastructure/Link Resolution/PathHierarchy+Error.swift" + "Infrastructure/Link Resolution/PathHierarchy+Find.swift" + "Infrastructure/Link Resolution/PathHierarchy+PathComponent.swift" + "Infrastructure/Link Resolution/PathHierarchy+Serialization.swift" + "Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift" + "Infrastructure/Link Resolution/PathHierarchy+TypeSignatureDisambiguation.swift" + "Infrastructure/Link Resolution/PathHierarchy.swift" + "Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver+Breadcrumbs.swift" + "Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver+Overloads.swift" + "Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift" + "Infrastructure/Link Resolution/SnippetResolver.swift" + Infrastructure/NodeURLGenerator.swift + "Infrastructure/Symbol Graph/AccessControl+Comparable.swift" + "Infrastructure/Symbol Graph/ExtendedTypeFormatExtension.swift" + "Infrastructure/Symbol Graph/ExtendedTypeFormatTransformation.swift" + "Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift" + "Infrastructure/Symbol Graph/ResolvedTopicReference+Symbol.swift" + "Infrastructure/Symbol Graph/SymbolGraphConcurrentDecoder.swift" + "Infrastructure/Symbol Graph/SymbolGraphLoader.swift" + "Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift" + "Infrastructure/Symbol Graph/SymbolReference.swift" + "Infrastructure/Symbol Graph/UnresolvedTopicReference+Symbol.swift" + "Infrastructure/Topic Graph/AutomaticCuration.swift" + "Infrastructure/Topic Graph/TopicGraph.swift" + Infrastructure/Workspace/DefaultAvailability.swift + Infrastructure/Workspace/DocumentationBundle+Info.swift + Infrastructure/Workspace/DocumentationWorkspaceDataProvider.swift + Infrastructure/Workspace/FeatureFlags+Info.swift + LinkTargets/LinkDestinationSummary.swift + Model/AnchorSection.swift + Model/AvailabilityParser.swift + Model/BuildMetadata.swift + Model/DocumentationMarkup.swift + Model/DocumentationNode.swift + Model/Identifier.swift + Model/Kind.swift + Model/Markup+parsing.swift + Model/Name.swift + Model/ParametersAndReturnValidator.swift + Model/Rendering/Content/Extensions/RenderTermLists.swift + Model/Rendering/Content/RenderBlockContent+Capitalization.swift + Model/Rendering/Content/RenderBlockContent.swift + Model/Rendering/Content/RenderContentMetadata.swift + Model/Rendering/Content/RenderInlineContent.swift + Model/Rendering/Diffing/AnyRenderReference.swift + Model/Rendering/Diffing/AnyRenderSection.swift + Model/Rendering/Diffing/DifferenceBuilder.swift + Model/Rendering/Diffing/Differences.swift + Model/Rendering/Diffing/RenderNode+Diffable.swift + Model/Rendering/DocumentationContentRenderer.swift + Model/Rendering/LinkTitleResolver.swift + "Model/Rendering/Navigation Tree/RenderHierarchy.swift" + "Model/Rendering/Navigation Tree/RenderHierarchyChapter.swift" + "Model/Rendering/Navigation Tree/RenderHierarchyLandmark.swift" + "Model/Rendering/Navigation Tree/RenderHierarchyTranslator.swift" + "Model/Rendering/Navigation Tree/RenderHierarchyTutorial.swift" + "Model/Rendering/Navigation Tree/RenderReferenceHierarchy.swift" + "Model/Rendering/Navigation Tree/RenderTutorialsHierarchy.swift" + Model/Rendering/PresentationURLGenerator.swift + Model/Rendering/References/AssetReferences.swift + Model/Rendering/References/FileReference.swift + Model/Rendering/References/ImageReference.swift + Model/Rendering/References/LinkReference.swift + Model/Rendering/References/MediaReference.swift + Model/Rendering/References/RenderReference.swift + Model/Rendering/References/TopicColor.swift + Model/Rendering/References/TopicImage.swift + Model/Rendering/References/TopicRenderReference.swift + Model/Rendering/References/UnresolvedReference.swift + Model/Rendering/References/VideoReference.swift + Model/Rendering/RenderContentCompiler.swift + Model/Rendering/RenderContentConvertible.swift + Model/Rendering/RenderContext.swift + Model/Rendering/RenderNode/AnyMetadata.swift + Model/Rendering/RenderNode/CodableContentSection.swift + Model/Rendering/RenderNode/CodableRenderReference.swift + Model/Rendering/RenderNode/CodableRenderSection.swift + Model/Rendering/RenderNode/RenderMetadata.swift + Model/Rendering/RenderNode/RenderNode+Codable.swift + Model/Rendering/RenderNode.swift + Model/Rendering/RenderNode.Tag.swift + Model/Rendering/RenderNodeTranslator.swift + Model/Rendering/RenderNodeVariant.swift + Model/Rendering/RenderReferenceStore.swift + Model/Rendering/RenderSection.swift + Model/Rendering/RenderSectionTranslator/AttributesSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/DictionaryKeysSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/DiscussionSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/HTTPBodySectionTranslator.swift + Model/Rendering/RenderSectionTranslator/HTTPEndpointSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/HTTPParametersSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/HTTPResponsesSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/MentionsSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/ParametersSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/PlistDetailsSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/PossibleValuesSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/RenderSectionTranslator.swift + Model/Rendering/RenderSectionTranslator/ReturnsSectionTranslator.swift + Model/Rendering/SemanticVersion.swift + Model/Rendering/Symbol/AttributesRenderSection.swift + Model/Rendering/Symbol/AvailabilityRenderMetadataItem.swift + Model/Rendering/Symbol/AvailabilitySortOrder.swift + Model/Rendering/Symbol/ConformanceSection.swift + Model/Rendering/Symbol/ContentRenderSection.swift + Model/Rendering/Symbol/DeclarationRenderSection+SymbolGraph.swift + Model/Rendering/Symbol/DeclarationsRenderSection.swift + Model/Rendering/Symbol/DiffAvailability.swift + Model/Rendering/Symbol/MentionsRenderSection.swift + Model/Rendering/Symbol/ParameterRenderSection.swift + Model/Rendering/Symbol/PossibleValuesRenderSection.swift + Model/Rendering/Symbol/PropertiesRenderSection.swift + Model/Rendering/Symbol/PropertyListDetailsRenderSection.swift + Model/Rendering/Symbol/RelationshipsRenderSection.swift + Model/Rendering/Symbol/RESTBodyRenderSection.swift + Model/Rendering/Symbol/RESTEndpointRenderSection.swift + Model/Rendering/Symbol/RESTExampleRenderSection.swift + Model/Rendering/Symbol/RESTParametersRenderSection.swift + Model/Rendering/Symbol/RESTResponseRenderSection.swift + Model/Rendering/Symbol/SampleDownloadSection.swift + Model/Rendering/Symbol/TaskGroupRenderSection.swift + Model/Rendering/TopicsSectionStyle.swift + Model/Rendering/Tutorial/LineHighlighter.swift + Model/Rendering/Tutorial/References/DownloadReference.swift + Model/Rendering/Tutorial/References/XcodeRequirementReference.swift + Model/Rendering/Tutorial/Sections/IntroRenderSection.swift + Model/Rendering/Tutorial/Sections/TutorialAssessmentsRenderSection.swift + Model/Rendering/Tutorial/Sections/TutorialSectionsRenderSection.swift + "Model/Rendering/Tutorial Article/TutorialArticleSection.swift" + "Model/Rendering/Tutorials Overview/Resources/RenderTile.swift" + "Model/Rendering/Tutorials Overview/Sections/CallToActionSection.swift" + "Model/Rendering/Tutorials Overview/Sections/ContentAndMediaGroupSection.swift" + "Model/Rendering/Tutorials Overview/Sections/ContentAndMediaSection.swift" + "Model/Rendering/Tutorials Overview/Sections/ResourcesRenderSection.swift" + "Model/Rendering/Tutorials Overview/Sections/VolumeRenderSection.swift" + Model/Rendering/Variants/JSONPatchApplier.swift + Model/Rendering/Variants/JSONPatchOperation.swift + Model/Rendering/Variants/JSONPointer.swift + Model/Rendering/Variants/PatchOperation.swift + Model/Rendering/Variants/RenderNodeVariantOverridesApplier.swift + Model/Rendering/Variants/VariantCollection+Coding.swift + Model/Rendering/Variants/VariantCollection+Symbol.swift + Model/Rendering/Variants/VariantCollection+Variant.swift + Model/Rendering/Variants/VariantCollection.swift + Model/Rendering/Variants/VariantContainer.swift + Model/Rendering/Variants/VariantOverride.swift + Model/Rendering/Variants/VariantOverrides.swift + Model/Rendering/Variants/VariantPatchOperation.swift + Model/Section/Section.swift + Model/Section/Sections/Abstract.swift + Model/Section/Sections/AutomaticTaskGroupSection.swift + Model/Section/Sections/DefaultImplementations.swift + Model/Section/Sections/DeprecatedSection.swift + Model/Section/Sections/DictionaryKeysSection.swift + Model/Section/Sections/Discussion.swift + Model/Section/Sections/GroupedSection.swift + Model/Section/Sections/HTTPBodySection.swift + Model/Section/Sections/HTTPEndpointSection.swift + Model/Section/Sections/HTTPParametersSection.swift + Model/Section/Sections/HTTPResponsesSection.swift + Model/Section/Sections/ParametersSection.swift + Model/Section/Sections/PropertyListPossibleValuesSection.swift + Model/Section/Sections/Relationships.swift + Model/Section/Sections/ReturnsSection.swift + Model/Section/Sections/SeeAlso.swift + Model/Section/Sections/Topics.swift + Model/Semantics/DictionaryKey.swift + Model/Semantics/HTTPBody.swift + Model/Semantics/HTTPParameter.swift + Model/Semantics/HTTPResponse.swift + Model/Semantics/LegacyTag.swift + Model/Semantics/Parameter.swift + Model/Semantics/Return.swift + Model/Semantics/Throw.swift + Model/SourceLanguage.swift + Model/TaskGroup.swift + Semantics/Abstracted.swift + Semantics/Article/Article.swift + Semantics/Article/ArticleSymbolMentions.swift + Semantics/Article/MarkupConvertible.swift + Semantics/Comment.swift + Semantics/ContentAndMedia.swift + Semantics/DirectiveConvertable.swift + Semantics/DirectiveInfrastructure/AutomaticDirectiveConvertible.swift + Semantics/DirectiveInfrastructure/ChildDirectiveWrapper.swift + Semantics/DirectiveInfrastructure/ChildMarkdownWrapper.swift + Semantics/DirectiveInfrastructure/DirectiveArgumentValueConvertible.swift + Semantics/DirectiveInfrastructure/DirectiveArgumentWrapper.swift + Semantics/DirectiveInfrastructure/DirectiveIndex.swift + Semantics/DirectiveInfrastructure/DirectiveMirror.swift + Semantics/DirectiveInfrastructure/MarkupContaining.swift + Semantics/DirectiveParser.swift + Semantics/ExternalLinks/ExternalMarkupReferenceWalker.swift + Semantics/ExternalLinks/ExternalReferenceWalker.swift + "Semantics/General Purpose Analyses/DeprecatedArgument.swift" + "Semantics/General Purpose Analyses/Extract.swift" + "Semantics/General Purpose Analyses/HasArgumentOfType.swift" + "Semantics/General Purpose Analyses/HasAtLeastOne.swift" + "Semantics/General Purpose Analyses/HasAtMostOne.swift" + "Semantics/General Purpose Analyses/HasContent.swift" + "Semantics/General Purpose Analyses/HasExactlyOne.swift" + "Semantics/General Purpose Analyses/HasOnlyKnownArguments.swift" + "Semantics/General Purpose Analyses/HasOnlyKnownDirectives.swift" + "Semantics/General Purpose Analyses/HasOnlySequentialHeadings.swift" + Semantics/Graph/SymbolKind.Swift.swift + Semantics/Intro.swift + Semantics/Landmark.swift + Semantics/Layout.swift + Semantics/MarkupContainer.swift + Semantics/MarkupReferenceResolver.swift + Semantics/Media/ImageMedia.swift + Semantics/Media/Media.swift + Semantics/Media/VideoMedia.swift + Semantics/Metadata/AlternateRepresentation.swift + Semantics/Metadata/Availability.swift + Semantics/Metadata/CallToAction.swift + Semantics/Metadata/CustomMetadata.swift + Semantics/Metadata/DisplayName.swift + Semantics/Metadata/DocumentationExtension.swift + Semantics/Metadata/Metadata.swift + Semantics/Metadata/PageColor.swift + Semantics/Metadata/PageImage.swift + Semantics/Metadata/PageKind.swift + Semantics/Metadata/SupportedLanguage.swift + Semantics/Metadata/TechnologyRoot.swift + Semantics/Metadata/TitleHeading.swift + Semantics/Options/AutomaticArticleSubheading.swift + Semantics/Options/AutomaticSeeAlso.swift + Semantics/Options/AutomaticTitleHeading.swift + Semantics/Options/Options.swift + Semantics/Options/TopicsVisualStyle.swift + Semantics/Redirect.swift + Semantics/Redirected.swift + Semantics/Reference/Links.swift + Semantics/Reference/Row.swift + Semantics/Reference/Small.swift + Semantics/Reference/TabNavigator.swift + Semantics/ReferenceResolver.swift + Semantics/Semantic.swift + Semantics/SemanticAnalyzer.swift + Semantics/Snippets/Snippet.swift + Semantics/Symbol/DeprecationSummary.swift + Semantics/Symbol/DocumentationDataVariants+SymbolGraphSymbol.swift + Semantics/Symbol/DocumentationDataVariants.swift + Semantics/Symbol/PlatformName.swift + Semantics/Symbol/Relationship.swift + Semantics/Symbol/Symbol.swift + Semantics/Symbol/UnifiedSymbol+Extensions.swift + Semantics/Technology/Resources/Resources.swift + Semantics/Technology/Resources/Tile.swift + Semantics/Technology/TutorialTableOfContents.swift + Semantics/Technology/Volume/Chapter/Chapter.swift + Semantics/Technology/Volume/Chapter/TutorialReference.swift + Semantics/Technology/Volume/Volume.swift + Semantics/Timed.swift + Semantics/Titled.swift + Semantics/Tutorial/Assessments/Assessments.swift + "Semantics/Tutorial/Assessments/Multiple Choice/Choice/Choice.swift" + "Semantics/Tutorial/Assessments/Multiple Choice/Choice/Justification.swift" + "Semantics/Tutorial/Assessments/Multiple Choice/MultipleChoice.swift" + Semantics/Tutorial/Tasks/Steps/Code.swift + Semantics/Tutorial/Tasks/Steps/Step.swift + Semantics/Tutorial/Tasks/Steps/Steps.swift + Semantics/Tutorial/Tasks/TutorialSection.swift + Semantics/Tutorial/Tutorial.swift + Semantics/Tutorial/XcodeRequirement.swift + Semantics/TutorialArticle/Stack.swift + Semantics/TutorialArticle/TutorialArticle.swift + Semantics/Visitor/SemanticVisitor.swift + Semantics/Walker/SemanticWalker.swift + Semantics/Walker/Walkers/SemanticTreeDumper.swift + Servers/DocumentationSchemeHandler.swift + Servers/FileServer.swift + SourceRepository/SourceRepository.swift + Utility/Checksum.swift + Utility/Collection+ConcurrentPerform.swift + Utility/CollectionChanges.swift + Utility/DataStructures/BidirectionalMap.swift + Utility/DataStructures/GroupedSequence.swift + Utility/DispatchGroup+Async.swift + Utility/Errors/DescribedError.swift + Utility/Errors/ErrorWithProblems.swift + Utility/ExternalIdentifier.swift + Utility/FeatureFlags.swift + Utility/FileManagerProtocol+FilesSequence.swift + Utility/FileManagerProtocol.swift + Utility/FoundationExtensions/Array+baseType.swift + Utility/FoundationExtensions/AutoreleasepoolShim.swift + Utility/FoundationExtensions/CharacterSet.swift + Utility/FoundationExtensions/Collection+indexed.swift + Utility/FoundationExtensions/Dictionary+TypedValues.swift + Utility/FoundationExtensions/NoOpSignposterShim.swift + Utility/FoundationExtensions/Optional+baseType.swift + Utility/FoundationExtensions/PlainTextShim.swift + Utility/FoundationExtensions/RangeReplaceableCollection+Group.swift + Utility/FoundationExtensions/SendableMetatypeShim.swift + Utility/FoundationExtensions/Sequence+Categorize.swift + Utility/FoundationExtensions/Sequence+FirstMap.swift + Utility/FoundationExtensions/Sequence+RenderBlockContentElemenet.swift + Utility/FoundationExtensions/SortByKeyPath.swift + Utility/FoundationExtensions/String+Capitalization.swift + Utility/FoundationExtensions/String+Hashing.swift + Utility/FoundationExtensions/String+Path.swift + Utility/FoundationExtensions/String+SingleQuoted.swift + Utility/FoundationExtensions/String+Splitting.swift + Utility/FoundationExtensions/String+Whitespace.swift + Utility/FoundationExtensions/StringCollection+List.swift + Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift + Utility/FoundationExtensions/URL+Relative.swift + Utility/FoundationExtensions/URL+WithFragment.swift + Utility/FoundationExtensions/URL+WithoutHostAndPortAndScheme.swift + Utility/Graphs/DirectedGraph+Cycles.swift + Utility/Graphs/DirectedGraph+Paths.swift + Utility/Graphs/DirectedGraph+Traversal.swift + Utility/Graphs/DirectedGraph.swift + Utility/Language/EnglishLanguage.swift + Utility/Language/NativeLanguage.swift + Utility/ListItemUpdatable.swift + Utility/LMDB/LMDB+Database.swift + Utility/LMDB/LMDB+Environment.swift + Utility/LMDB/LMDB+Error.swift + Utility/LMDB/LMDB+Transaction.swift + Utility/LMDB/LMDB.swift + Utility/LogHandle.swift + Utility/MarkupExtensions/AnyLink.swift + Utility/MarkupExtensions/BlockDirectiveExtensions.swift + Utility/MarkupExtensions/DocumentExtensions.swift + Utility/MarkupExtensions/ImageExtensions.swift + Utility/MarkupExtensions/ListItemExtractor.swift + Utility/MarkupExtensions/MarkupChildrenExtensions.swift + Utility/MarkupExtensions/SourceRangeExtensions.swift + Utility/NearMiss.swift + Utility/RenderNodeDataExtractor.swift + Utility/SemanticVersion+Comparable.swift + Utility/SymbolGraphAvailability+Filter.swift + Utility/Synchronization.swift + Utility/ValidatedURL.swift + Utility/Version.swift) +target_link_libraries(SwiftDocC PUBLIC + SwiftMarkdown::Markdown + DocC::SymbolKit + LMDB::CLMDB + Crypto) +# FIXME(compnerd) workaround leaking dependencies +target_link_libraries(SwiftDocC PUBLIC + libcmark-gfm + libcmark-gfm-extensions) + +if(BUILD_SHARED_LIBS) + install(TARGETS SwiftDocC + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() diff --git a/Sources/SwiftDocCUtilities/CMakeLists.txt b/Sources/SwiftDocCUtilities/CMakeLists.txt new file mode 100644 index 0000000000..c419ba4807 --- /dev/null +++ b/Sources/SwiftDocCUtilities/CMakeLists.txt @@ -0,0 +1,74 @@ +#[[ +This source file is part of the Swift open source project + +Copyright © 2014 - 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 +#]] + +add_library(SwiftDocCUtilities STATIC + Action/Action.swift + Action/ActionResult.swift + Action/Actions/Action+MoveOutput.swift + Action/Actions/Convert/ConvertAction.swift + Action/Actions/Convert/ConvertFileWritingConsumer.swift + Action/Actions/Convert/CoverageDataEntry+generateSummary.swift + Action/Actions/Convert/Indexer.swift + Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift + Action/Actions/CoverageAction.swift + Action/Actions/EmitGeneratedCurationAction.swift + Action/Actions/IndexAction.swift + Action/Actions/Init/CatalogTemplate.swift + Action/Actions/Init/CatalogTemplateKind.swift + Action/Actions/Init/InitAction.swift + Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift + Action/Actions/Merge/MergeAction.swift + Action/Actions/PreviewAction.swift + Action/Actions/TransformForStaticHostingAction.swift + ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift + ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift + ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift + ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift + ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift + ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift + ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift + ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift + ArgumentParsing/Options/DirectoryPathOption.swift + ArgumentParsing/Options/DocumentationArchiveOption.swift + ArgumentParsing/Options/DocumentationBundleOption.swift + ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift + ArgumentParsing/Options/InitOptions.swift + ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift + ArgumentParsing/Options/PreviewOptions.swift + "ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift" + ArgumentParsing/Options/TemplateOption.swift + ArgumentParsing/Subcommands/Convert.swift + ArgumentParsing/Subcommands/EmitGeneratedCuration.swift + ArgumentParsing/Subcommands/Index.swift + ArgumentParsing/Subcommands/Init.swift + ArgumentParsing/Subcommands/Merge.swift + ArgumentParsing/Subcommands/Preview.swift + ArgumentParsing/Subcommands/ProcessArchive.swift + ArgumentParsing/Subcommands/ProcessCatalog.swift + ArgumentParsing/Subcommands/TransformForStaticHosting.swift + Docc.swift + PreviewServer/PreviewHTTPHandler.swift + PreviewServer/PreviewServer.swift + PreviewServer/RequestHandler/DefaultRequestHandler.swift + PreviewServer/RequestHandler/ErrorRequestHandler.swift + PreviewServer/RequestHandler/FileRequestHandler.swift + PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift + PreviewServer/RequestHandler/RequestHandlerFactory.swift + Transformers/StaticHostableTransformer.swift + Utility/DirectoryMonitor.swift + Utility/FoundationExtensions/Sequence+Unique.swift + Utility/FoundationExtensions/String+Path.swift + Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift + Utility/FoundationExtensions/URL+Relative.swift + Utility/PlatformArgumentParser.swift + Utility/Signal.swift + Utility/Throttle.swift) +target_link_libraries(SwiftDocCUtilities PUBLIC + ArgumentParser + SwiftDocC) diff --git a/Sources/docc/CMakeLists.txt b/Sources/docc/CMakeLists.txt new file mode 100644 index 0000000000..4289856b76 --- /dev/null +++ b/Sources/docc/CMakeLists.txt @@ -0,0 +1,16 @@ +#[[ +This source file is part of the Swift open source project + +Copyright © 2014 - 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 +#]] + +add_executable(docc + main.swift) +target_link_libraries(docc PRIVATE + SwiftDocCUtilities) + +install(TARGETS docc + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) From 45aa567543539e13d0b2551ef75e7e51a115cc25 Mon Sep 17 00:00:00 2001 From: Prajwal Nadig Date: Thu, 6 Nov 2025 13:59:56 +0000 Subject: [PATCH 64/90] Emit variants if any root module is a symbol kind (#1333) * Extract helper in `RenderNodeTranslatorTests` One of the test methods in `RenderNodeTranslatorTests` was duplicated multiple times. It has been extracted out of the tests and deduplicated. * Emit variants if any root module is a symbol When the render node translator visits articles, it emits variants for each language that the article is supported in. This step is skipped if an article-only catalog is being built, since it would otherwise lead to a redundant "Swift" language marker being rendered in the page. However, if a catalog contains more than one module, i.e. an article-only collection and a different module's symbol graph in the same catalog, then the variants are not emitted despite the possibility of the page being available in more than one language. This commit changes the boolean check to skip emitting variants only if all root modules are article-only. It does not affect the behaviour when building a correctly configured catalog that contains a sole root module. rdar://163926698 --- .../Rendering/RenderNodeTranslator.swift | 5 +- .../Indexing/NavigatorIndexTests.swift | 2 +- .../Rendering/RenderNodeTranslatorTests.swift | 92 ++++++++++++------- 3 files changed, 62 insertions(+), 37 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index a32006d4af..b01c820978 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -632,9 +632,8 @@ public struct RenderNodeTranslator: SemanticVisitor { // Emit variants only if we're not compiling an article-only catalog to prevent renderers from // advertising the page as "Swift", which is the language DocC assigns to pages in article only catalogs. // (github.com/swiftlang/swift-docc/issues/240). - if let topLevelModule = context.soleRootModuleReference, - try! context.entity(with: topLevelModule).kind.isSymbol - { + let isArticleOnlyCatalog = context.rootModules.allSatisfy { !context.isSymbol(reference: $0) } + if !isArticleOnlyCatalog { node.variants = variants(for: documentationNode) } diff --git a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift index 6029282a9d..16c63bede5 100644 --- a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift @@ -15,7 +15,7 @@ import SwiftDocCTestUtilities typealias Node = NavigatorTree.Node typealias PageType = NavigatorIndex.PageType -let testBundleIdentifier = "org.swift.docc.example" +private let testBundleIdentifier = "org.swift.docc.example" class NavigatorIndexingTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift index aba174200b..be2a95ea56 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift @@ -1334,31 +1334,22 @@ class RenderNodeTranslatorTests: XCTestCase { ), ] ) - let (bundle, context) = try await loadBundle(catalog: catalog) + let (_, context) = try await loadBundle(catalog: catalog) - func renderNodeArticleFromReferencePath( - referencePath: String - ) throws -> RenderNode { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: referencePath, sourceLanguage: .swift) - let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, identifier: reference) - return try XCTUnwrap(translator.visitArticle(symbol) as? RenderNode) - } - // Assert that articles that curates any symbol gets 'API Collection' assigned as the eyebrow title. - var renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/APICollection") + var renderNode = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/unit-test/APICollection") XCTAssertEqual(renderNode.metadata.roleHeading, "API Collection") // Assert that articles that curates only other articles don't get any value assigned as the eyebrow title. - renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/Collection") + renderNode = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/unit-test/Collection") XCTAssertEqual(renderNode.metadata.roleHeading, nil) // Assert that articles that don't curate anything else get 'Article' assigned as the eyebrow title. - renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/Article") + renderNode = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/unit-test/Article") XCTAssertEqual(renderNode.metadata.roleHeading, "Article") // Assert that articles that have a custom title heading the eyebrow title assigned properly. - renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/CustomRole") + renderNode = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/unit-test/CustomRole") XCTAssertEqual(renderNode.metadata.roleHeading, "Custom Role") // Assert that articles that have a custom page kind the eyebrow title assigned properly. - renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/SampleCode") + renderNode = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/unit-test/SampleCode") XCTAssertEqual(renderNode.metadata.roleHeading, "Sample Code") } @@ -1426,35 +1417,26 @@ class RenderNodeTranslatorTests: XCTestCase { ), ] ) - let (bundle, context) = try await loadBundle(catalog: catalog) - - func renderNodeArticleFromReferencePath( - referencePath: String - ) throws -> RenderNode { - let reference = ResolvedTopicReference(bundleID: bundle.id, path: referencePath, sourceLanguage: .swift) - let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator(context: context, identifier: reference) - return try XCTUnwrap(translator.visitArticle(symbol) as? RenderNode) - } + let (_, context) = try await loadBundle(catalog: catalog) // Assert that API collections disabling automatic title headings don't get any value assigned as the eyebrow title, // but that the node's role itself is unaffected. - var renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/APICollection") + var renderNode = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/unit-test/APICollection") XCTAssertEqual(renderNode.metadata.roleHeading, nil) XCTAssertEqual(renderNode.metadata.role, RenderMetadata.Role.collectionGroup.rawValue) // Assert that articles disabling automatic title headings don't get any value assigned as the eyebrow title, // but that the node's role itself is unaffected. - renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/Article") + renderNode = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/unit-test/Article") XCTAssertEqual(renderNode.metadata.roleHeading, nil) XCTAssertEqual(renderNode.metadata.role, RenderMetadata.Role.article.rawValue) // Assert that articles that have a custom title heading have the eyebrow title assigned properly, // even when automatic title headings are disabled. - renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/CustomRole") + renderNode = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/unit-test/CustomRole") XCTAssertEqual(renderNode.metadata.roleHeading, "Custom Role") XCTAssertEqual(renderNode.metadata.role, RenderMetadata.Role.article.rawValue) // Assert that articles that have a custom page kind have the eyebrow title assigned properly, // even when automatic title headings are disabled. - renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/SampleCode") + renderNode = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/unit-test/SampleCode") XCTAssertEqual(renderNode.metadata.roleHeading, "Sample Code") } @@ -1544,7 +1526,7 @@ class RenderNodeTranslatorTests: XCTestCase { ] )) - func renderNodeArticleFromReferencePath( + func renderNodeSymbolFromReferencePath( referencePath: String ) throws -> RenderNode { let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: referencePath, sourceLanguage: .swift) @@ -1554,7 +1536,7 @@ class RenderNodeTranslatorTests: XCTestCase { } // Assert that CounterpartSymbol's source languages have been added as source languages of Symbol - var renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/Symbol") + var renderNode = try renderNodeSymbolFromReferencePath(referencePath: "/documentation/unit-test/Symbol") XCTAssertEqual(renderNode.variants?.count, 2) XCTAssertEqual(renderNode.variants, [ .init(traits: [.interfaceLanguage("swift")], paths: ["/documentation/unit-test/symbol"]), @@ -1562,17 +1544,61 @@ class RenderNodeTranslatorTests: XCTestCase { ]) // Assert that alternate representations which can't be resolved are ignored - renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/OtherSymbol") + renderNode = try renderNodeSymbolFromReferencePath(referencePath: "/documentation/unit-test/OtherSymbol") XCTAssertEqual(renderNode.variants?.count, 1) XCTAssertEqual(renderNode.variants, [ .init(traits: [.interfaceLanguage("swift")], paths: ["/documentation/unit-test/othersymbol"]), ]) // Assert that duplicate alternate representations are not added as variants - renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/MultipleSwiftVariantsSymbol") + renderNode = try renderNodeSymbolFromReferencePath(referencePath: "/documentation/unit-test/MultipleSwiftVariantsSymbol") XCTAssertEqual(renderNode.variants?.count, 1) XCTAssertEqual(renderNode.variants, [ .init(traits: [.interfaceLanguage("swift")], paths: ["/documentation/unit-test/multipleswiftvariantssymbol"]), ]) } + + // Tests if variants are emitted in catalogs with more than one root module. + func testEmitVariantsInCatalogWithMultipleModules() async throws { + let (_, context) = try await loadBundle(catalog: Folder( + name: "UnitTest.docc", + content: [ + TextFile(name: "UnitTest.md", utf8Content: """ + # Unit test + + @Metadata { + @TechnologyRoot + @SupportedLanguage(swift) + @SupportedLanguage(occ) + } + + This is an article in a catalog containing a module different from the article-only collection. + """), + // The correct way to configure a catalog is to have a single + // root module. If multiple modules are present, it is not + // possible to determine which module an article is supposed to + // be registered with. This test includes another module to + // verify if the variants are correctly emitted when there is + // no sole root module. + JSONFile(name: "foo.symbols.json", content: makeSymbolGraph(moduleName: "foo")), + ] + )) + + let article = try renderNodeArticleFromReferencePath(context: context, referencePath: "/documentation/UnitTest") + XCTAssertEqual(article.variants?.count, 2) + XCTAssertEqual(article.variants, [ + .init(traits: [.interfaceLanguage("swift")], paths: ["/documentation/unittest"]), + .init(traits: [.interfaceLanguage("occ")], paths: ["/documentation/unittest"]) + ]) + } + + private func renderNodeArticleFromReferencePath( + context: DocumentationContext, + referencePath: String + ) throws -> RenderNode { + let reference = ResolvedTopicReference(bundleID: context.inputs.id, path: referencePath, sourceLanguage: .swift) + let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article) + var translator = RenderNodeTranslator(context: context, identifier: reference) + return try XCTUnwrap(translator.visitArticle(article) as? RenderNode) + } } From b81634931575317124d75d34d348144cf3f73523 Mon Sep 17 00:00:00 2001 From: Prajwal Nadig Date: Thu, 6 Nov 2025 15:33:05 +0000 Subject: [PATCH 65/90] Exclude CMakeLists.txt files from SwiftPM targets (#1335) SwiftPM requires that all files within a target are accounted for as part of the build. If there are extraneous files that do not form a part of the target, the package manager emits a warning about unhandled files. This patch updates Package.swift to exclude all CMakeLists.txt files from their relevant SwiftPM targets to remove this warning. --- Package.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index f954d00903..9ad6fa6d7a 100644 --- a/Package.swift +++ b/Package.swift @@ -48,6 +48,7 @@ let package = Package( .product(name: "CLMDB", package: "swift-lmdb"), .product(name: "Crypto", package: "swift-crypto"), ], + exclude: ["CMakeLists.txt"], swiftSettings: swiftSettings ), .testTarget( @@ -72,6 +73,7 @@ let package = Package( .product(name: "NIOHTTP1", package: "swift-nio", condition: .when(platforms: [.macOS, .iOS, .linux, .android])), .product(name: "ArgumentParser", package: "swift-argument-parser") ], + exclude: ["CMakeLists.txt"], swiftSettings: swiftSettings ), .testTarget( @@ -104,6 +106,7 @@ let package = Package( dependencies: [ .target(name: "SwiftDocCUtilities"), ], + exclude: ["CMakeLists.txt"], swiftSettings: swiftSettings ), @@ -125,7 +128,6 @@ let package = Package( ], swiftSettings: swiftSettings ), - ] ) From a211e3715646b20c22a6e9a19d7c48573d452ffa Mon Sep 17 00:00:00 2001 From: Ole Begemann Date: Mon, 10 Nov 2025 10:22:08 +0100 Subject: [PATCH 66/90] Fix typo in doc comment for NavigatorItem.init?(rawValue:) (#1340) It should be either "Parameters:" (plural) with a nested list of named parameters, or "Parameter x:" (singular). --- Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift index d3d1d3a30b..8107665a89 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorItem.swift @@ -112,7 +112,7 @@ public final class NavigatorItem: Serializable, Codable, Equatable, CustomString /** Initialize a `NavigatorItem` using raw data. - - Parameters rawValue: The `Data` from which the instance should be deserialized from. + - Parameter rawValue: The `Data` from which the instance should be deserialized from. */ required public init?(rawValue: Data) { let data = rawValue From d616aa5e089533820aedfbce4a5979dffac98698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 10 Nov 2025 10:33:20 +0100 Subject: [PATCH 67/90] Rename two internal targets for clarity (#1330) * Rename 'SwiftDocCUtilities' target to 'CommandLine' for clarity * Remove unnecessary prefix from 'SwiftDocCTestUtilities' target * Update library name in CMake files --- CONTRIBUTING.md | 4 ++-- Package.swift | 20 ++++++++-------- Sources/CMakeLists.txt | 2 +- .../Action/Action.swift | 0 .../Action/ActionResult.swift | 0 .../Action/Actions/Action+MoveOutput.swift | 0 .../Actions/Convert/ConvertAction.swift | 0 .../Convert/ConvertFileWritingConsumer.swift | 0 .../CoverageDataEntry+generateSummary.swift | 0 .../Action/Actions/Convert/Indexer.swift | 0 .../JSONEncodingRenderNodeWriter.swift | 0 .../Action/Actions/CoverageAction.swift | 0 .../Actions/EmitGeneratedCurationAction.swift | 0 .../Action/Actions/IndexAction.swift | 0 .../Action/Actions/Init/CatalogTemplate.swift | 0 .../Actions/Init/CatalogTemplateKind.swift | 0 .../Action/Actions/Init/InitAction.swift | 0 .../MergeAction+SynthesizedLandingPage.swift | 0 .../Action/Actions/Merge/MergeAction.swift | 0 .../Action/Actions/PreviewAction.swift | 0 .../TransformForStaticHostingAction.swift | 0 .../Action+performAndHandleResult.swift | 0 .../ConvertAction+CommandInitialization.swift | 0 ...CurationAction+CommandInitialization.swift | 0 .../IndexAction+CommandInitialization.swift | 0 .../InitAction+CommandInitialization.swift | 0 .../PreviewAction+CommandInitialization.swift | 0 ...cHostingAction+CommandInitialization.swift | 0 .../URLArgumentValidator.swift | 0 .../Options/DirectoryPathOption.swift | 0 .../Options/DocumentationArchiveOption.swift | 0 .../Options/DocumentationBundleOption.swift | 0 ...DocumentationCoverageOptionsArgument.swift | 2 +- .../ArgumentParsing/Options/InitOptions.swift | 0 .../OutOfProcessLinkResolverOption.swift | 0 .../Options/PreviewOptions.swift | 0 .../SourceRepositoryArguments.swift | 0 .../Options/TemplateOption.swift | 0 .../ArgumentParsing/Subcommands/Convert.swift | 0 .../Subcommands/EmitGeneratedCuration.swift | 0 .../ArgumentParsing/Subcommands/Index.swift | 0 .../ArgumentParsing/Subcommands/Init.swift | 0 .../ArgumentParsing/Subcommands/Merge.swift | 0 .../ArgumentParsing/Subcommands/Preview.swift | 0 .../Subcommands/ProcessArchive.swift | 0 .../Subcommands/ProcessCatalog.swift | 0 .../TransformForStaticHosting.swift | 0 .../CommandLine.docc/CommandLine.md} | 8 +++---- .../CommandLine}/Actions/InitAction.md | 2 +- .../CommandLine}/Extensions/Docc.md | 2 +- .../CommandLine.docc}/footer.html | 0 .../CommandLine.docc}/header.html | 0 .../Docc.swift | 0 .../PreviewServer/PreviewHTTPHandler.swift | 0 .../PreviewServer/PreviewServer.swift | 0 .../DefaultRequestHandler.swift | 0 .../RequestHandler/ErrorRequestHandler.swift | 0 .../RequestHandler/FileRequestHandler.swift | 0 .../HTTPResponseHead+FromRequest.swift | 0 .../RequestHandlerFactory.swift | 0 .../StaticHostableTransformer.swift | 0 .../Utility/DirectoryMonitor.swift | 0 .../Sequence+Unique.swift | 0 .../FoundationExtensions/String+Path.swift | 0 .../URL+IsAbsoluteWebURL.swift | 0 .../FoundationExtensions/URL+Relative.swift | 0 .../Utility/PlatformArgumentParser.swift | 0 .../Utility/Signal.swift | 0 .../Utility/Throttle.swift | 0 .../SwiftDocC/AddingFeatureFlags.md | 6 ++--- .../SwiftDocC/CompilerPipeline.md | 4 ++-- Sources/SwiftDocCUtilities/CMakeLists.txt | 4 ++-- .../FilesAndFolders.swift | 0 .../SymbolGraphCreation.swift | 0 .../TestFileSystem.swift | 0 .../XCTestCase+TemporaryDirectory.swift | 0 Sources/docc/CMakeLists.txt | 2 +- Sources/docc/main.swift | 4 ++-- ...nvertSubcommandSourceRepositoryTests.swift | 6 ++--- .../ConvertSubcommandTests.swift | 6 ++--- ...tationCoverageKindFilterOptionsTests.swift | 0 .../ArgumentParsing/ErrorMessageTests.swift | 4 ++-- .../MergeSubcommandTests.swift | 4 ++-- .../PreviewSubcommandTests.swift | 4 ++-- .../C+Extensions.swift | 0 .../ConvertActionIndexerTests.swift | 2 +- .../ConvertActionStaticHostableTests.swift | 6 ++--- .../ConvertActionTests.swift | 4 ++-- .../DirectoryMonitorTests.swift | 2 +- .../EmitGeneratedCurationsActionTests.swift | 6 ++--- .../FolderStructure.swift | 6 ++--- .../FolderStructureTests.swift | 2 +- .../HTMLTemplateDirectory.swift | 2 +- .../IndexActionTests.swift | 6 ++--- .../Init/InitActionTests.swift | 6 ++--- .../JSONEncodingRenderNodeWriterTests.swift | 4 ++-- .../MergeActionTests.swift | 4 ++-- .../PlatformArgumentParserTests.swift | 4 ++-- .../PreviewActionIntegrationTests.swift | 6 ++--- .../PreviewHTTPHandlerTests.swift | 4 ++-- .../DefaultRequestHandlerTests.swift | 4 ++-- .../ErrorRequestHandlerTests.swift | 2 +- .../FileRequestHandlerTests.swift | 4 ++-- .../PreviewServer/ServerTestUtils.swift | 4 ++-- .../ProblemTests.swift | 4 ++-- .../SemanticAnalyzerTests.swift | 4 ++-- .../ShadowFileManagerTemporaryDirectory.swift | 0 .../SignalTests.swift | 0 .../StaticHostableTransformerTests.swift | 6 ++--- .../StaticHostingBaseTest.swift | 0 .../Default Code Listing Syntax.md | 0 .../FillIntroduced.symbols.json | 0 .../Info.plist | 0 .../MyKit@SideKit.symbols.json | 0 .../TestOverview.tutorial | 0 .../TestTutorial.tutorial | 0 .../TestTutorial2.tutorial | 0 .../TestTutorialArticle.tutorial | 0 .../TutorialMediaWithSpaces.tutorial | 0 .../article.md | 0 .../article2.md | 0 .../article3.md | 0 .../documentation/myclass.md | 0 .../documentation/mykit.md | 0 .../documentation/myprotocol.md | 0 .../documentation/sideclass-init.md | 0 .../documentation/sidekit.md | 0 .../figure1.png | Bin .../figure1~dark.png | Bin .../helloworld.swift | 0 .../helloworld1.swift | 0 .../helloworld2.swift | 0 .../helloworld3.swift | 0 .../helloworld4.swift | 0 .../intro.png | Bin .../introposter.png | Bin .../introposter2.png | Bin .../introvideo.mp4 | Bin .../introvideo~dark.mp4 | Bin .../mykit-iOS.symbols.json | 0 .../project.zip | Bin .../sidekit.symbols.json | 0 .../something@2x.png | Bin .../step.png | Bin .../titled2up.png | Bin .../titled2upCapital.PNG | Bin .../with spaces.mp4 | Bin .../with spaces.png | Bin .../with spaces@2x.png | Bin .../MixedLanguageFramework.docc/Info.plist | 0 .../clang/MixedLanguageFramework.symbols.json | 0 .../swift/MixedLanguageFramework.symbols.json | 0 .../OverloadedSymbols.docc/Info.plist | 0 .../ShapeKit.symbols.json | 0 .../SingleArticleTestBundle.docc/Info.plist | 0 .../SingleArticleTestBundle.docc/article.md | 0 .../DeckKit-Objective-C.symbols.json | 0 .../Test Resources/Overview.tutorial | 0 .../Test Resources/Test Template/index.html | 0 .../TopLevelCuration.symbols.json | 0 .../Test Resources/UncuratedArticle.md | 0 .../Test Resources/image.png | Bin .../ThrottleTests.swift | 4 ++-- ...TransformForStaticHostingActionTests.swift | 6 ++--- .../Utility/DirectedGraphTests.swift | 0 .../Utility/FileTests.swift | 2 +- .../Utility/LogHandleTests.swift | 4 ++-- .../Utility/Sequence+UniqueTests.swift | 4 ++-- .../Utility/TestFileSystemTests.swift | 4 ++-- .../Utility/URL+IsAbsoluteWebURLTests.swift | 4 ++-- .../Utility/URL+RelativeTests.swift | 4 ++-- .../XCTestCase+enableFeatureFlag.swift | 0 .../XCTestCase+LoadingData.swift | 2 +- .../NonInclusiveLanguageCheckerTests.swift | 2 +- ...recatedDiagnosticsDigestWarningTests.swift | 2 +- ...icConsoleWriterDefaultFormattingTest.swift | 4 ++-- .../Diagnostics/DiagnosticTests.swift | 2 +- .../ConvertService/ConvertServiceTests.swift | 2 +- .../Indexing/ExternalRenderNodeTests.swift | 2 +- .../Indexing/NavigatorIndexTests.swift | 2 +- .../Indexing/RenderIndexTests.swift | 2 +- .../AutoCapitalizationTests.swift | 2 +- .../AutomaticCurationTests.swift | 2 +- .../Infrastructure/BundleDiscoveryTests.swift | 2 +- .../DocumentationContext+RootPageTests.swift | 2 +- .../DocumentationContextTests.swift | 2 +- .../DocumentationCuratorTests.swift | 2 +- .../ExternalPathHierarchyResolverTests.swift | 2 +- .../ExternalReferenceResolverTests.swift | 2 +- .../DocumentationInputsProviderTests.swift | 2 +- .../Infrastructure/NodeTagsTests.swift | 2 +- .../Infrastructure/PathHierarchyTests.swift | 2 +- .../Infrastructure/SnippetResolverTests.swift | 2 +- .../SymbolGraph/SymbolGraphLoaderTests.swift | 2 +- .../Infrastructure/SymbolReferenceTests.swift | 2 +- .../LinkDestinationSummaryTests.swift | 2 +- .../Model/LineHighlighterTests.swift | 2 +- .../ParametersAndReturnValidatorTests.swift | 2 +- ...opertyListPossibleValuesSectionTests.swift | 2 +- .../SemaToRenderNodeMultiLanguageTests.swift | 2 +- .../Model/SemaToRenderNodeTests.swift | 2 +- ...OutOfProcessReferenceResolverV1Tests.swift | 2 +- ...OutOfProcessReferenceResolverV2Tests.swift | 2 +- .../Rendering/AutomaticSeeAlsoTests.swift | 2 +- .../ConstraintsRenderSectionTests.swift | 2 +- .../DeclarationsRenderSectionTests.swift | 2 +- .../Rendering/DefaultAvailabilityTests.swift | 2 +- .../DefaultCodeListingSyntaxTests.swift | 2 +- .../Rendering/HeadingAnchorTests.swift | 2 +- .../Rendering/PlistSymbolTests.swift | 4 ++-- ...ropertyListDetailsRenderSectionTests.swift | 2 +- .../Rendering/RESTSymbolsTests.swift | 2 +- .../RenderContentCompilerTests.swift | 2 +- ...derNodeTranslatorSymbolVariantsTests.swift | 2 +- .../Rendering/RenderNodeTranslatorTests.swift | 2 +- .../Rendering/SymbolAvailabilityTests.swift | 2 +- .../Rendering/TermListTests.swift | 2 +- .../ArticleSymbolMentionsTests.swift | 2 +- .../Semantics/ChoiceTests.swift | 2 +- .../Semantics/DoxygenTests.swift | 2 +- .../MarkupReferenceResolverTests.swift | 2 +- .../Semantics/MultipleChoiceTests.swift | 2 +- .../SwiftDocCTests/Semantics/StackTests.swift | 2 +- .../SwiftDocCTests/Semantics/StepTests.swift | 2 +- .../Semantics/SymbolTests.swift | 2 +- .../Semantics/TutorialArticleTests.swift | 2 +- .../Semantics/TutorialTests.swift | 2 +- .../Semantics/VideoMediaTests.swift | 2 +- .../Semantics/VolumeTests.swift | 2 +- Tests/SwiftDocCTests/Utility/LMDBTests.swift | 2 +- .../Utility/ListItemExtractorTests.swift | 2 +- .../Utility/XCTestCase+MentionedIn.swift | 2 +- .../XCTestCase+LoadingTestData.swift | 2 +- Tests/signal-test-app/main.swift | 6 ++--- bin/check-source | 2 +- bin/preview-docs | 22 +++++++++--------- bin/update-gh-pages-documentation-site | 18 +++++++------- 237 files changed, 191 insertions(+), 191 deletions(-) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Action.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/ActionResult.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Action+MoveOutput.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Convert/ConvertAction.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Convert/ConvertFileWritingConsumer.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Convert/Indexer.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/CoverageAction.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/EmitGeneratedCurationAction.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/IndexAction.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Init/CatalogTemplate.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Init/CatalogTemplateKind.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Init/InitAction.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/Merge/MergeAction.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/PreviewAction.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Action/Actions/TransformForStaticHostingAction.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Options/DirectoryPathOption.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Options/DocumentationArchiveOption.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Options/DocumentationBundleOption.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift (97%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Options/InitOptions.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Options/PreviewOptions.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Options/TemplateOption.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Subcommands/Convert.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Subcommands/Index.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Subcommands/Init.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Subcommands/Merge.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Subcommands/Preview.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Subcommands/ProcessArchive.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Subcommands/ProcessCatalog.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/ArgumentParsing/Subcommands/TransformForStaticHosting.swift (100%) rename Sources/{SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md => CommandLine/CommandLine.docc/CommandLine.md} (54%) rename Sources/{SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities => CommandLine/CommandLine.docc/CommandLine}/Actions/InitAction.md (98%) rename Sources/{SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities => CommandLine/CommandLine.docc/CommandLine}/Extensions/Docc.md (90%) rename Sources/{SwiftDocCUtilities/SwiftDocCUtilities.docc => CommandLine/CommandLine.docc}/footer.html (100%) rename Sources/{SwiftDocCUtilities/SwiftDocCUtilities.docc => CommandLine/CommandLine.docc}/header.html (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Docc.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/PreviewServer/PreviewHTTPHandler.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/PreviewServer/PreviewServer.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/PreviewServer/RequestHandler/DefaultRequestHandler.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/PreviewServer/RequestHandler/ErrorRequestHandler.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/PreviewServer/RequestHandler/FileRequestHandler.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/PreviewServer/RequestHandler/RequestHandlerFactory.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Transformers/StaticHostableTransformer.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Utility/DirectoryMonitor.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Utility/FoundationExtensions/Sequence+Unique.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Utility/FoundationExtensions/String+Path.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Utility/FoundationExtensions/URL+Relative.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Utility/PlatformArgumentParser.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Utility/Signal.swift (100%) rename Sources/{SwiftDocCUtilities => CommandLine}/Utility/Throttle.swift (100%) rename Sources/{SwiftDocCTestUtilities => TestUtilities}/FilesAndFolders.swift (100%) rename Sources/{SwiftDocCTestUtilities => TestUtilities}/SymbolGraphCreation.swift (100%) rename Sources/{SwiftDocCTestUtilities => TestUtilities}/TestFileSystem.swift (100%) rename Sources/{SwiftDocCTestUtilities => TestUtilities}/XCTestCase+TemporaryDirectory.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ArgumentParsing/ConvertSubcommandTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ArgumentParsing/ErrorMessageTests.swift (93%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ArgumentParsing/MergeSubcommandTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ArgumentParsing/PreviewSubcommandTests.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/C+Extensions.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ConvertActionIndexerTests.swift (98%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ConvertActionStaticHostableTests.swift (96%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ConvertActionTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/DirectoryMonitorTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/EmitGeneratedCurationsActionTests.swift (96%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/FolderStructure.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/FolderStructureTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/HTMLTemplateDirectory.swift (98%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/IndexActionTests.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Init/InitActionTests.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/JSONEncodingRenderNodeWriterTests.swift (95%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/MergeActionTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/PlatformArgumentParserTests.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/PreviewActionIntegrationTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/PreviewServer/PreviewHTTPHandlerTests.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift (96%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift (98%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/PreviewServer/RequestHandler/FileRequestHandlerTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/PreviewServer/ServerTestUtils.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ProblemTests.swift (89%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/SemanticAnalyzerTests.swift (98%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ShadowFileManagerTemporaryDirectory.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/SignalTests.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/StaticHostableTransformerTests.swift (98%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/StaticHostingBaseTest.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/MixedLanguageFramework.docc/Info.plist (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/OverloadedSymbols.docc/Info.plist (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/SingleArticleTestBundle.docc/Info.plist (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Bundles/SingleArticleTestBundle.docc/article.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Resources/DeckKit-Objective-C.symbols.json (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Resources/Overview.tutorial (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Resources/Test Template/index.html (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Resources/TopLevelCuration.symbols.json (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Resources/UncuratedArticle.md (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Test Resources/image.png (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/ThrottleTests.swift (92%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/TransformForStaticHostingActionTests.swift (98%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Utility/DirectedGraphTests.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Utility/FileTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Utility/LogHandleTests.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Utility/Sequence+UniqueTests.swift (94%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Utility/TestFileSystemTests.swift (99%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Utility/URL+IsAbsoluteWebURLTests.swift (90%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Utility/URL+RelativeTests.swift (97%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/Utility/XCTestCase+enableFeatureFlag.swift (100%) rename Tests/{SwiftDocCUtilitiesTests => CommandLineTests}/XCTestCase+LoadingData.swift (98%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da6d76fb8f..71eff08cab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -520,7 +520,7 @@ For more in-depth technical information about Swift-DocC, please refer to the project's technical documentation: - [`SwiftDocC` framework documentation](https://swiftlang.github.io/swift-docc/documentation/swiftdocc/) -- [`SwiftDocCUtilities` framework documentation](https://swiftlang.github.io/swift-docc/documentation/swiftdoccutilities/) +- [`CommandLine` framework documentation](https://swiftlang.github.io/swift-docc/documentation/commandline/) ### Related Projects @@ -545,4 +545,4 @@ project's technical documentation: with support for building and viewing documentation for your framework and its dependencies. - + diff --git a/Package.swift b/Package.swift index 9ad6fa6d7a..544c13b596 100644 --- a/Package.swift +++ b/Package.swift @@ -2,7 +2,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 @@ -55,7 +55,7 @@ let package = Package( name: "SwiftDocCTests", dependencies: [ .target(name: "SwiftDocC"), - .target(name: "SwiftDocCTestUtilities"), + .target(name: "TestUtilities"), ], resources: [ .copy("Test Resources"), @@ -67,7 +67,7 @@ let package = Package( ), // Command-line tool library .target( - name: "SwiftDocCUtilities", + name: "CommandLine", dependencies: [ .target(name: "SwiftDocC"), .product(name: "NIOHTTP1", package: "swift-nio", condition: .when(platforms: [.macOS, .iOS, .linux, .android])), @@ -77,11 +77,11 @@ let package = Package( swiftSettings: swiftSettings ), .testTarget( - name: "SwiftDocCUtilitiesTests", + name: "CommandLineTests", dependencies: [ - .target(name: "SwiftDocCUtilities"), + .target(name: "CommandLine"), .target(name: "SwiftDocC"), - .target(name: "SwiftDocCTestUtilities"), + .target(name: "TestUtilities"), ], resources: [ .copy("Test Resources"), @@ -92,7 +92,7 @@ let package = Package( // Test utility library .target( - name: "SwiftDocCTestUtilities", + name: "TestUtilities", dependencies: [ .target(name: "SwiftDocC"), .product(name: "SymbolKit", package: "swift-docc-symbolkit"), @@ -104,17 +104,17 @@ let package = Package( .executableTarget( name: "docc", dependencies: [ - .target(name: "SwiftDocCUtilities"), + .target(name: "CommandLine"), ], exclude: ["CMakeLists.txt"], swiftSettings: swiftSettings ), - // Test app for SwiftDocCUtilities + // Test app for CommandLine .executableTarget( name: "signal-test-app", dependencies: [ - .target(name: "SwiftDocCUtilities"), + .target(name: "CommandLine"), ], path: "Tests/signal-test-app", swiftSettings: swiftSettings diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 8f8e806655..4362f918d0 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -8,5 +8,5 @@ See https://swift.org/LICENSE.txt for license information #]] add_subdirectory(SwiftDocC) -add_subdirectory(SwiftDocCUtilities) +add_subdirectory(CommandLine) add_subdirectory(docc) diff --git a/Sources/SwiftDocCUtilities/Action/Action.swift b/Sources/CommandLine/Action/Action.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Action.swift rename to Sources/CommandLine/Action/Action.swift diff --git a/Sources/SwiftDocCUtilities/Action/ActionResult.swift b/Sources/CommandLine/Action/ActionResult.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/ActionResult.swift rename to Sources/CommandLine/Action/ActionResult.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift b/Sources/CommandLine/Action/Actions/Action+MoveOutput.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift rename to Sources/CommandLine/Action/Actions/Action+MoveOutput.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift b/Sources/CommandLine/Action/Actions/Convert/ConvertAction.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift rename to Sources/CommandLine/Action/Actions/Convert/ConvertAction.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift b/Sources/CommandLine/Action/Actions/Convert/ConvertFileWritingConsumer.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift rename to Sources/CommandLine/Action/Actions/Convert/ConvertFileWritingConsumer.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift b/Sources/CommandLine/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift rename to Sources/CommandLine/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/Indexer.swift b/Sources/CommandLine/Action/Actions/Convert/Indexer.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Convert/Indexer.swift rename to Sources/CommandLine/Action/Actions/Convert/Indexer.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift b/Sources/CommandLine/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift rename to Sources/CommandLine/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/CoverageAction.swift b/Sources/CommandLine/Action/Actions/CoverageAction.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/CoverageAction.swift rename to Sources/CommandLine/Action/Actions/CoverageAction.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift b/Sources/CommandLine/Action/Actions/EmitGeneratedCurationAction.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift rename to Sources/CommandLine/Action/Actions/EmitGeneratedCurationAction.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/IndexAction.swift b/Sources/CommandLine/Action/Actions/IndexAction.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/IndexAction.swift rename to Sources/CommandLine/Action/Actions/IndexAction.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplate.swift b/Sources/CommandLine/Action/Actions/Init/CatalogTemplate.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplate.swift rename to Sources/CommandLine/Action/Actions/Init/CatalogTemplate.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift b/Sources/CommandLine/Action/Actions/Init/CatalogTemplateKind.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift rename to Sources/CommandLine/Action/Actions/Init/CatalogTemplateKind.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Init/InitAction.swift b/Sources/CommandLine/Action/Actions/Init/InitAction.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Init/InitAction.swift rename to Sources/CommandLine/Action/Actions/Init/InitAction.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift b/Sources/CommandLine/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift rename to Sources/CommandLine/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift b/Sources/CommandLine/Action/Actions/Merge/MergeAction.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift rename to Sources/CommandLine/Action/Actions/Merge/MergeAction.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift b/Sources/CommandLine/Action/Actions/PreviewAction.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift rename to Sources/CommandLine/Action/Actions/PreviewAction.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/TransformForStaticHostingAction.swift b/Sources/CommandLine/Action/Actions/TransformForStaticHostingAction.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Action/Actions/TransformForStaticHostingAction.swift rename to Sources/CommandLine/Action/Actions/TransformForStaticHostingAction.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift b/Sources/CommandLine/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift rename to Sources/CommandLine/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift b/Sources/CommandLine/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift rename to Sources/CommandLine/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift b/Sources/CommandLine/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift rename to Sources/CommandLine/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift b/Sources/CommandLine/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift rename to Sources/CommandLine/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift b/Sources/CommandLine/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift rename to Sources/CommandLine/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift b/Sources/CommandLine/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift rename to Sources/CommandLine/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift b/Sources/CommandLine/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift rename to Sources/CommandLine/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift b/Sources/CommandLine/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift rename to Sources/CommandLine/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DirectoryPathOption.swift b/Sources/CommandLine/ArgumentParsing/Options/DirectoryPathOption.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Options/DirectoryPathOption.swift rename to Sources/CommandLine/ArgumentParsing/Options/DirectoryPathOption.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationArchiveOption.swift b/Sources/CommandLine/ArgumentParsing/Options/DocumentationArchiveOption.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationArchiveOption.swift rename to Sources/CommandLine/ArgumentParsing/Options/DocumentationArchiveOption.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationBundleOption.swift b/Sources/CommandLine/ArgumentParsing/Options/DocumentationBundleOption.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationBundleOption.swift rename to Sources/CommandLine/ArgumentParsing/Options/DocumentationBundleOption.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift b/Sources/CommandLine/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift similarity index 97% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift rename to Sources/CommandLine/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift index f1812bbfc5..25b5aabbd3 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift +++ b/Sources/CommandLine/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift @@ -76,7 +76,7 @@ public struct DocumentationCoverageOptionsArgument: ParsableArguments { // // It is safe to add a retroactively conformance here because the other module (SwiftDocC) is in the same package. // -// These conforming types are defined in SwiftDocC and extended in SwiftDocCUtilities, because SwiftDocC doesn't link against ArgumentParse (since it isn't about CLI). +// These conforming types are defined in SwiftDocC and extended in CommandLine, because SwiftDocC doesn't link against ArgumentParser (since it isn't about CLI). // We conform here because this is the first place that we can add the conformance. The implementation is in SwiftDocC. extension SwiftDocC.DocumentationCoverageLevel: ArgumentParser.ExpressibleByArgument {} extension SwiftDocC.DocumentationCoverageOptions.KindFilterOptions.BitFlagRepresentation: ArgumentParser.ExpressibleByArgument {} diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/InitOptions.swift b/Sources/CommandLine/ArgumentParsing/Options/InitOptions.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Options/InitOptions.swift rename to Sources/CommandLine/ArgumentParsing/Options/InitOptions.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift b/Sources/CommandLine/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift rename to Sources/CommandLine/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift b/Sources/CommandLine/ArgumentParsing/Options/PreviewOptions.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift rename to Sources/CommandLine/ArgumentParsing/Options/PreviewOptions.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift b/Sources/CommandLine/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift rename to Sources/CommandLine/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/TemplateOption.swift b/Sources/CommandLine/ArgumentParsing/Options/TemplateOption.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Options/TemplateOption.swift rename to Sources/CommandLine/ArgumentParsing/Options/TemplateOption.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift b/Sources/CommandLine/ArgumentParsing/Subcommands/Convert.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift rename to Sources/CommandLine/ArgumentParsing/Subcommands/Convert.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift b/Sources/CommandLine/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift rename to Sources/CommandLine/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Index.swift b/Sources/CommandLine/ArgumentParsing/Subcommands/Index.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Index.swift rename to Sources/CommandLine/ArgumentParsing/Subcommands/Index.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Init.swift b/Sources/CommandLine/ArgumentParsing/Subcommands/Init.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Init.swift rename to Sources/CommandLine/ArgumentParsing/Subcommands/Init.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Merge.swift b/Sources/CommandLine/ArgumentParsing/Subcommands/Merge.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Merge.swift rename to Sources/CommandLine/ArgumentParsing/Subcommands/Merge.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift b/Sources/CommandLine/ArgumentParsing/Subcommands/Preview.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift rename to Sources/CommandLine/ArgumentParsing/Subcommands/Preview.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift b/Sources/CommandLine/ArgumentParsing/Subcommands/ProcessArchive.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift rename to Sources/CommandLine/ArgumentParsing/Subcommands/ProcessArchive.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessCatalog.swift b/Sources/CommandLine/ArgumentParsing/Subcommands/ProcessCatalog.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessCatalog.swift rename to Sources/CommandLine/ArgumentParsing/Subcommands/ProcessCatalog.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/TransformForStaticHosting.swift b/Sources/CommandLine/ArgumentParsing/Subcommands/TransformForStaticHosting.swift similarity index 100% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/TransformForStaticHosting.swift rename to Sources/CommandLine/ArgumentParsing/Subcommands/TransformForStaticHosting.swift diff --git a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md b/Sources/CommandLine/CommandLine.docc/CommandLine.md similarity index 54% rename from Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md rename to Sources/CommandLine/CommandLine.docc/CommandLine.md index 0e3a8b4f47..defdb873b6 100644 --- a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md +++ b/Sources/CommandLine/CommandLine.docc/CommandLine.md @@ -1,12 +1,12 @@ -# ``SwiftDocCUtilities`` +# ``CommandLine`` Build custom documentation workflows by leveraging the DocC compiler pipeline. ## Overview -SwiftDocCUtilities provides a default, command-line workflow for DocC, powered by Swift [Argument Parser](https://apple.github.io/swift-argument-parser/documentation/argumentparser/). `docc` commands, such as `convert` and `preview`, are conformant ``Action`` types that use DocC to perform documentation tasks. +CommandLine provides a default, command-line workflow for DocC, powered by Swift [Argument Parser](https://apple.github.io/swift-argument-parser/documentation/argumentparser/). `docc` commands, such as `convert` and `preview`, are conformant ``Action`` types that use DocC to perform documentation tasks. -Use SwiftDocCUtilities to build a custom, command-line interface and extend it with additional commands. To add a new sub-command called `example`, create a conformant ``Action`` type, `ExampleAction`, that performs the desired work, and add it as a sub-command. Optionally, you can also reuse any of the provided actions like ``ConvertAction``. +Use CommandLine to build a custom, command-line interface and extend it with additional commands. To add a new sub-command called `example`, create a conformant ``Action`` type, `ExampleAction`, that performs the desired work, and add it as a sub-command. Optionally, you can also reuse any of the provided actions like ``ConvertAction``. ```swift public import ArgumentParser @@ -44,4 +44,4 @@ Adding a new sub-command automatically adds routing and execution of its code, a - ``Throttle`` - ``Signal`` - + diff --git a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Actions/InitAction.md b/Sources/CommandLine/CommandLine.docc/CommandLine/Actions/InitAction.md similarity index 98% rename from Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Actions/InitAction.md rename to Sources/CommandLine/CommandLine.docc/CommandLine/Actions/InitAction.md index be46e30b13..70faf36635 100644 --- a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Actions/InitAction.md +++ b/Sources/CommandLine/CommandLine.docc/CommandLine/Actions/InitAction.md @@ -1,4 +1,4 @@ -# ``SwiftDocCUtilities/InitAction`` +# ``InitAction`` @Metadata { @DocumentationExtension(mergeBehavior: override) diff --git a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Extensions/Docc.md b/Sources/CommandLine/CommandLine.docc/CommandLine/Extensions/Docc.md similarity index 90% rename from Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Extensions/Docc.md rename to Sources/CommandLine/CommandLine.docc/CommandLine/Extensions/Docc.md index f7e904530e..70d767f9d3 100644 --- a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Extensions/Docc.md +++ b/Sources/CommandLine/CommandLine.docc/CommandLine/Extensions/Docc.md @@ -1,4 +1,4 @@ -# ``SwiftDocCUtilities/Docc`` +# ``Docc`` ## Topics diff --git a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/footer.html b/Sources/CommandLine/CommandLine.docc/footer.html similarity index 100% rename from Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/footer.html rename to Sources/CommandLine/CommandLine.docc/footer.html diff --git a/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/header.html b/Sources/CommandLine/CommandLine.docc/header.html similarity index 100% rename from Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/header.html rename to Sources/CommandLine/CommandLine.docc/header.html diff --git a/Sources/SwiftDocCUtilities/Docc.swift b/Sources/CommandLine/Docc.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Docc.swift rename to Sources/CommandLine/Docc.swift diff --git a/Sources/SwiftDocCUtilities/PreviewServer/PreviewHTTPHandler.swift b/Sources/CommandLine/PreviewServer/PreviewHTTPHandler.swift similarity index 100% rename from Sources/SwiftDocCUtilities/PreviewServer/PreviewHTTPHandler.swift rename to Sources/CommandLine/PreviewServer/PreviewHTTPHandler.swift diff --git a/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift b/Sources/CommandLine/PreviewServer/PreviewServer.swift similarity index 100% rename from Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift rename to Sources/CommandLine/PreviewServer/PreviewServer.swift diff --git a/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/DefaultRequestHandler.swift b/Sources/CommandLine/PreviewServer/RequestHandler/DefaultRequestHandler.swift similarity index 100% rename from Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/DefaultRequestHandler.swift rename to Sources/CommandLine/PreviewServer/RequestHandler/DefaultRequestHandler.swift diff --git a/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/ErrorRequestHandler.swift b/Sources/CommandLine/PreviewServer/RequestHandler/ErrorRequestHandler.swift similarity index 100% rename from Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/ErrorRequestHandler.swift rename to Sources/CommandLine/PreviewServer/RequestHandler/ErrorRequestHandler.swift diff --git a/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/FileRequestHandler.swift b/Sources/CommandLine/PreviewServer/RequestHandler/FileRequestHandler.swift similarity index 100% rename from Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/FileRequestHandler.swift rename to Sources/CommandLine/PreviewServer/RequestHandler/FileRequestHandler.swift diff --git a/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift b/Sources/CommandLine/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift similarity index 100% rename from Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift rename to Sources/CommandLine/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift diff --git a/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/RequestHandlerFactory.swift b/Sources/CommandLine/PreviewServer/RequestHandler/RequestHandlerFactory.swift similarity index 100% rename from Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/RequestHandlerFactory.swift rename to Sources/CommandLine/PreviewServer/RequestHandler/RequestHandlerFactory.swift diff --git a/Sources/SwiftDocCUtilities/Transformers/StaticHostableTransformer.swift b/Sources/CommandLine/Transformers/StaticHostableTransformer.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Transformers/StaticHostableTransformer.swift rename to Sources/CommandLine/Transformers/StaticHostableTransformer.swift diff --git a/Sources/SwiftDocCUtilities/Utility/DirectoryMonitor.swift b/Sources/CommandLine/Utility/DirectoryMonitor.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Utility/DirectoryMonitor.swift rename to Sources/CommandLine/Utility/DirectoryMonitor.swift diff --git a/Sources/SwiftDocCUtilities/Utility/FoundationExtensions/Sequence+Unique.swift b/Sources/CommandLine/Utility/FoundationExtensions/Sequence+Unique.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Utility/FoundationExtensions/Sequence+Unique.swift rename to Sources/CommandLine/Utility/FoundationExtensions/Sequence+Unique.swift diff --git a/Sources/SwiftDocCUtilities/Utility/FoundationExtensions/String+Path.swift b/Sources/CommandLine/Utility/FoundationExtensions/String+Path.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Utility/FoundationExtensions/String+Path.swift rename to Sources/CommandLine/Utility/FoundationExtensions/String+Path.swift diff --git a/Sources/SwiftDocCUtilities/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift b/Sources/CommandLine/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift rename to Sources/CommandLine/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift diff --git a/Sources/SwiftDocCUtilities/Utility/FoundationExtensions/URL+Relative.swift b/Sources/CommandLine/Utility/FoundationExtensions/URL+Relative.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Utility/FoundationExtensions/URL+Relative.swift rename to Sources/CommandLine/Utility/FoundationExtensions/URL+Relative.swift diff --git a/Sources/SwiftDocCUtilities/Utility/PlatformArgumentParser.swift b/Sources/CommandLine/Utility/PlatformArgumentParser.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Utility/PlatformArgumentParser.swift rename to Sources/CommandLine/Utility/PlatformArgumentParser.swift diff --git a/Sources/SwiftDocCUtilities/Utility/Signal.swift b/Sources/CommandLine/Utility/Signal.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Utility/Signal.swift rename to Sources/CommandLine/Utility/Signal.swift diff --git a/Sources/SwiftDocCUtilities/Utility/Throttle.swift b/Sources/CommandLine/Utility/Throttle.swift similarity index 100% rename from Sources/SwiftDocCUtilities/Utility/Throttle.swift rename to Sources/CommandLine/Utility/Throttle.swift diff --git a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/AddingFeatureFlags.md b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/AddingFeatureFlags.md index 3712ca2aa0..52d7220981 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/AddingFeatureFlags.md +++ b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/AddingFeatureFlags.md @@ -17,12 +17,12 @@ value so that the default initializer can be used. ### Feature flags on the command line -Command-line feature flags live in the `Docc.Convert.FeatureFlagOptions` in `SwiftDocCUtilities`. +Command-line feature flags live in the `Docc.Convert.FeatureFlagOptions` in `CommandLine`. This type implements the `ParsableArguments` protocol from Swift Argument Parser to create an option group for the `convert` and `preview` commands. These options are then handled in `ConvertAction.init(fromConvertCommand:)`, still in -`SwiftDocCUtilities`, where they are written into the global feature flags ``FeatureFlags/current`` +`CommandLine`, where they are written into the global feature flags ``FeatureFlags/current`` instance, which can then be used during the compilation process. ### Feature flags in Info.plist @@ -37,4 +37,4 @@ Feature flags that are loaded from an Info.plist file are saved into the global the bundle is being registered. To ensure that your new feature flag is properly loaded, update the ``FeatureFlags/loadFlagsFromBundle(_:)`` method to load your new field into the global flags. - + diff --git a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/CompilerPipeline.md b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/CompilerPipeline.md index eb63941f1b..afd5e55ed7 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/CompilerPipeline.md +++ b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/CompilerPipeline.md @@ -33,7 +33,7 @@ SwiftDocC.docc ├ Essentials │ ├ ActionManager.md │ ├ Action.md -│ ╰ Getting Started with SwiftDocCUtilities.md +│ ╰ Getting Started with SwiftDocC.md ├ Migration to DocC │ ├ DocumentationContext.md │ ╰ ... @@ -104,4 +104,4 @@ The file hierarchy under the output path represents the complete, compiled docum ╰ videos ``` - + diff --git a/Sources/SwiftDocCUtilities/CMakeLists.txt b/Sources/SwiftDocCUtilities/CMakeLists.txt index c419ba4807..a0552f96dc 100644 --- a/Sources/SwiftDocCUtilities/CMakeLists.txt +++ b/Sources/SwiftDocCUtilities/CMakeLists.txt @@ -7,7 +7,7 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information #]] -add_library(SwiftDocCUtilities STATIC +add_library(CommandLine STATIC Action/Action.swift Action/ActionResult.swift Action/Actions/Action+MoveOutput.swift @@ -69,6 +69,6 @@ add_library(SwiftDocCUtilities STATIC Utility/PlatformArgumentParser.swift Utility/Signal.swift Utility/Throttle.swift) -target_link_libraries(SwiftDocCUtilities PUBLIC +target_link_libraries(CommandLine PUBLIC ArgumentParser SwiftDocC) diff --git a/Sources/SwiftDocCTestUtilities/FilesAndFolders.swift b/Sources/TestUtilities/FilesAndFolders.swift similarity index 100% rename from Sources/SwiftDocCTestUtilities/FilesAndFolders.swift rename to Sources/TestUtilities/FilesAndFolders.swift diff --git a/Sources/SwiftDocCTestUtilities/SymbolGraphCreation.swift b/Sources/TestUtilities/SymbolGraphCreation.swift similarity index 100% rename from Sources/SwiftDocCTestUtilities/SymbolGraphCreation.swift rename to Sources/TestUtilities/SymbolGraphCreation.swift diff --git a/Sources/SwiftDocCTestUtilities/TestFileSystem.swift b/Sources/TestUtilities/TestFileSystem.swift similarity index 100% rename from Sources/SwiftDocCTestUtilities/TestFileSystem.swift rename to Sources/TestUtilities/TestFileSystem.swift diff --git a/Sources/SwiftDocCTestUtilities/XCTestCase+TemporaryDirectory.swift b/Sources/TestUtilities/XCTestCase+TemporaryDirectory.swift similarity index 100% rename from Sources/SwiftDocCTestUtilities/XCTestCase+TemporaryDirectory.swift rename to Sources/TestUtilities/XCTestCase+TemporaryDirectory.swift diff --git a/Sources/docc/CMakeLists.txt b/Sources/docc/CMakeLists.txt index 4289856b76..722df3a849 100644 --- a/Sources/docc/CMakeLists.txt +++ b/Sources/docc/CMakeLists.txt @@ -10,7 +10,7 @@ See https://swift.org/LICENSE.txt for license information add_executable(docc main.swift) target_link_libraries(docc PRIVATE - SwiftDocCUtilities) + CommandLine) install(TARGETS docc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/Sources/docc/main.swift b/Sources/docc/main.swift index 1715eed8e1..51180b0335 100644 --- a/Sources/docc/main.swift +++ b/Sources/docc/main.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors + Copyright (c) 2021-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 @@ -9,7 +9,7 @@ */ #if os(macOS) || os(Linux) || os(Android) || os(Windows) || os(FreeBSD) -import SwiftDocCUtilities +import CommandLine await Task { await Docc.main() diff --git a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift b/Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift rename to Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift index 654006839c..e77b39a535 100644 --- a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift +++ b/Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 @@ -9,9 +9,9 @@ */ import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities import ArgumentParser class ConvertSubcommandSourceRepositoryTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift b/Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift rename to Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandTests.swift index 41027fbba1..69f4c7373b 100644 --- a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift +++ b/Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandTests.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 @@ -9,9 +9,9 @@ */ import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class ConvertSubcommandTests: XCTestCase { private let testBundleURL = Bundle.module.url( diff --git a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift b/Tests/CommandLineTests/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift rename to Tests/CommandLineTests/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift diff --git a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ErrorMessageTests.swift b/Tests/CommandLineTests/ArgumentParsing/ErrorMessageTests.swift similarity index 93% rename from Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ErrorMessageTests.swift rename to Tests/CommandLineTests/ArgumentParsing/ErrorMessageTests.swift index 335ac9f3d5..2a331cb9e7 100644 --- a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ErrorMessageTests.swift +++ b/Tests/CommandLineTests/ArgumentParsing/ErrorMessageTests.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 @@ -10,7 +10,7 @@ import XCTest import Foundation -@testable import SwiftDocCUtilities +@testable import CommandLine class ErrorMessageTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.swift b/Tests/CommandLineTests/ArgumentParsing/MergeSubcommandTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.swift rename to Tests/CommandLineTests/ArgumentParsing/MergeSubcommandTests.swift index 85731b8e9c..d073d6b34e 100644 --- a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.swift +++ b/Tests/CommandLineTests/ArgumentParsing/MergeSubcommandTests.swift @@ -10,8 +10,8 @@ import XCTest import ArgumentParser -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities class MergeSubcommandTests: XCTestCase { func testCommandLineArgumentValidation() throws { diff --git a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift b/Tests/CommandLineTests/ArgumentParsing/PreviewSubcommandTests.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift rename to Tests/CommandLineTests/ArgumentParsing/PreviewSubcommandTests.swift index 1f586324d3..d475294290 100644 --- a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift +++ b/Tests/CommandLineTests/ArgumentParsing/PreviewSubcommandTests.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 @@ -10,7 +10,7 @@ #if canImport(NIOHTTP1) import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine class PreviewSubcommandTests: XCTestCase { func testOptionsValidation() throws { diff --git a/Tests/SwiftDocCUtilitiesTests/C+Extensions.swift b/Tests/CommandLineTests/C+Extensions.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/C+Extensions.swift rename to Tests/CommandLineTests/C+Extensions.swift diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift b/Tests/CommandLineTests/ConvertActionIndexerTests.swift similarity index 98% rename from Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift rename to Tests/CommandLineTests/ConvertActionIndexerTests.swift index 4d3db2bdbf..e28af46ab5 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift +++ b/Tests/CommandLineTests/ConvertActionIndexerTests.swift @@ -11,7 +11,7 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import SwiftDocCUtilities +@testable import CommandLine class ConvertActionIndexerTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift b/Tests/CommandLineTests/ConvertActionStaticHostableTests.swift similarity index 96% rename from Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift rename to Tests/CommandLineTests/ConvertActionStaticHostableTests.swift index 0c2c0498b1..be300dc869 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift +++ b/Tests/CommandLineTests/ConvertActionStaticHostableTests.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 @@ -11,8 +11,8 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities class ConvertActionStaticHostableTests: StaticHostingBaseTests { /// Creates a DocC archive and then archives it with options to produce static content which is then validated. diff --git a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift b/Tests/CommandLineTests/ConvertActionTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift rename to Tests/CommandLineTests/ConvertActionTests.swift index a4f76bcd53..ea32665a99 100644 --- a/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift +++ b/Tests/CommandLineTests/ConvertActionTests.swift @@ -11,10 +11,10 @@ import XCTest import Foundation @testable @_spi(ExternalLinks) import SwiftDocC -@testable import SwiftDocCUtilities +@testable import CommandLine import SymbolKit import Markdown -@testable import SwiftDocCTestUtilities +@testable import TestUtilities class ConvertActionTests: XCTestCase { #if !os(iOS) diff --git a/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift b/Tests/CommandLineTests/DirectoryMonitorTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift rename to Tests/CommandLineTests/DirectoryMonitorTests.swift index 555d258222..e7b5813765 100644 --- a/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift +++ b/Tests/CommandLineTests/DirectoryMonitorTests.swift @@ -9,7 +9,7 @@ */ import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine #if !os(Linux) && !os(Android) && !os(Windows) && !os(FreeBSD) fileprivate extension NSNotification.Name { diff --git a/Tests/SwiftDocCUtilitiesTests/EmitGeneratedCurationsActionTests.swift b/Tests/CommandLineTests/EmitGeneratedCurationsActionTests.swift similarity index 96% rename from Tests/SwiftDocCUtilitiesTests/EmitGeneratedCurationsActionTests.swift rename to Tests/CommandLineTests/EmitGeneratedCurationsActionTests.swift index 4a07a85ae4..60fc7d3339 100644 --- a/Tests/SwiftDocCUtilitiesTests/EmitGeneratedCurationsActionTests.swift +++ b/Tests/CommandLineTests/EmitGeneratedCurationsActionTests.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 @@ -10,8 +10,8 @@ import XCTest import Foundation -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities class EmitGeneratedCurationsActionTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/FolderStructure.swift b/Tests/CommandLineTests/FolderStructure.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/FolderStructure.swift rename to Tests/CommandLineTests/FolderStructure.swift index 95a36997ae..4d8f649b5b 100644 --- a/Tests/SwiftDocCUtilitiesTests/FolderStructure.swift +++ b/Tests/CommandLineTests/FolderStructure.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 @@ -9,9 +9,9 @@ */ @testable import SwiftDocC -@testable import SwiftDocCUtilities +@testable import CommandLine import XCTest -public import SwiftDocCTestUtilities +public import TestUtilities /* This file contains a test helper API for working with folder hierarchies, with the ability to: diff --git a/Tests/SwiftDocCUtilitiesTests/FolderStructureTests.swift b/Tests/CommandLineTests/FolderStructureTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/FolderStructureTests.swift rename to Tests/CommandLineTests/FolderStructureTests.swift index 148da86252..e809a9cd71 100644 --- a/Tests/SwiftDocCUtilitiesTests/FolderStructureTests.swift +++ b/Tests/CommandLineTests/FolderStructureTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import SwiftDocCTestUtilities +import TestUtilities class FolderStructureTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.swift b/Tests/CommandLineTests/HTMLTemplateDirectory.swift similarity index 98% rename from Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.swift rename to Tests/CommandLineTests/HTMLTemplateDirectory.swift index e3f25527c0..f378ac0fa2 100644 --- a/Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.swift +++ b/Tests/CommandLineTests/HTMLTemplateDirectory.swift @@ -9,7 +9,7 @@ */ import Foundation -import SwiftDocCTestUtilities +import TestUtilities /// A folder that represents a fake html-build directory for testing. extension Folder { diff --git a/Tests/SwiftDocCUtilitiesTests/IndexActionTests.swift b/Tests/CommandLineTests/IndexActionTests.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/IndexActionTests.swift rename to Tests/CommandLineTests/IndexActionTests.swift index d118aa6593..76414e35d4 100644 --- a/Tests/SwiftDocCUtilitiesTests/IndexActionTests.swift +++ b/Tests/CommandLineTests/IndexActionTests.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 @@ -11,9 +11,9 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import SwiftDocCUtilities +@testable import CommandLine import Markdown -import SwiftDocCTestUtilities +import TestUtilities class IndexActionTests: XCTestCase { #if !os(iOS) diff --git a/Tests/SwiftDocCUtilitiesTests/Init/InitActionTests.swift b/Tests/CommandLineTests/Init/InitActionTests.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/Init/InitActionTests.swift rename to Tests/CommandLineTests/Init/InitActionTests.swift index b0cb85e9cc..acb5a22333 100644 --- a/Tests/SwiftDocCUtilitiesTests/Init/InitActionTests.swift +++ b/Tests/CommandLineTests/Init/InitActionTests.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 @@ -10,8 +10,8 @@ import XCTest import Foundation -import SwiftDocCTestUtilities -@testable import SwiftDocCUtilities +import TestUtilities +@testable import CommandLine final class InitActionTests: XCTestCase { private let documentationTitle = "MyTestDocumentation" diff --git a/Tests/SwiftDocCUtilitiesTests/JSONEncodingRenderNodeWriterTests.swift b/Tests/CommandLineTests/JSONEncodingRenderNodeWriterTests.swift similarity index 95% rename from Tests/SwiftDocCUtilitiesTests/JSONEncodingRenderNodeWriterTests.swift rename to Tests/CommandLineTests/JSONEncodingRenderNodeWriterTests.swift index 4f31b23adf..156c1ae626 100644 --- a/Tests/SwiftDocCUtilitiesTests/JSONEncodingRenderNodeWriterTests.swift +++ b/Tests/CommandLineTests/JSONEncodingRenderNodeWriterTests.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 @@ -10,7 +10,7 @@ import XCTest import SwiftDocC -@testable import SwiftDocCUtilities +@testable import CommandLine class JSONEncodingRenderNodeWriterTests: XCTestCase { /// Verifies that if we fail during writing a JSON file the execution diff --git a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift b/Tests/CommandLineTests/MergeActionTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift rename to Tests/CommandLineTests/MergeActionTests.swift index 88acc2a5c2..6dd906bd3f 100644 --- a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift +++ b/Tests/CommandLineTests/MergeActionTests.swift @@ -10,8 +10,8 @@ import XCTest @testable import SwiftDocC -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities class MergeActionTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/PlatformArgumentParserTests.swift b/Tests/CommandLineTests/PlatformArgumentParserTests.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/PlatformArgumentParserTests.swift rename to Tests/CommandLineTests/PlatformArgumentParserTests.swift index 5a2386ef89..f3b54898a9 100644 --- a/Tests/SwiftDocCUtilitiesTests/PlatformArgumentParserTests.swift +++ b/Tests/CommandLineTests/PlatformArgumentParserTests.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 @@ -12,7 +12,7 @@ import Foundation import SwiftDocC import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine class PlatformArgumentParserTests: XCTestCase { let correctInputs: [[String]] = [ diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift b/Tests/CommandLineTests/PreviewActionIntegrationTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift rename to Tests/CommandLineTests/PreviewActionIntegrationTests.swift index bc23ac7575..1493a3cb29 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift +++ b/Tests/CommandLineTests/PreviewActionIntegrationTests.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 @@ -11,8 +11,8 @@ #if canImport(NIOHTTP1) import XCTest @testable import SwiftDocC -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities class PreviewActionIntegrationTests: XCTestCase { private func createMinimalDocsBundle() -> Folder { diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift b/Tests/CommandLineTests/PreviewServer/PreviewHTTPHandlerTests.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift rename to Tests/CommandLineTests/PreviewServer/PreviewHTTPHandlerTests.swift index 95878b0e7f..ac2267f053 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift +++ b/Tests/CommandLineTests/PreviewServer/PreviewHTTPHandlerTests.swift @@ -11,8 +11,8 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities import NIO import NIOHTTP1 diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift b/Tests/CommandLineTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift similarity index 96% rename from Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift rename to Tests/CommandLineTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift index 634ba02b9e..3bbbbd973f 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift +++ b/Tests/CommandLineTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift @@ -11,8 +11,8 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities import NIO import NIOHTTP1 diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift b/Tests/CommandLineTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift similarity index 98% rename from Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift rename to Tests/CommandLineTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift index 552b51e715..7a601f26d1 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift +++ b/Tests/CommandLineTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift @@ -11,7 +11,7 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine import NIO import NIOHTTP1 diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift b/Tests/CommandLineTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift rename to Tests/CommandLineTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift index 9d4fefa8e2..c08e0d2887 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift +++ b/Tests/CommandLineTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift @@ -11,8 +11,8 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities import NIO import NIOHTTP1 diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/ServerTestUtils.swift b/Tests/CommandLineTests/PreviewServer/ServerTestUtils.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/PreviewServer/ServerTestUtils.swift rename to Tests/CommandLineTests/PreviewServer/ServerTestUtils.swift index 14f60ed6a4..9f48813df5 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/ServerTestUtils.swift +++ b/Tests/CommandLineTests/PreviewServer/ServerTestUtils.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 @@ -13,7 +13,7 @@ import Foundation import NIO import NIOHTTP1 import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine /// Makes a request head part with the given URI and headers. func makeRequestHead(uri: String, headers: [(String, String)]? = nil) -> HTTPRequestHead { diff --git a/Tests/SwiftDocCUtilitiesTests/ProblemTests.swift b/Tests/CommandLineTests/ProblemTests.swift similarity index 89% rename from Tests/SwiftDocCUtilitiesTests/ProblemTests.swift rename to Tests/CommandLineTests/ProblemTests.swift index f5f18a41be..196fe92d20 100644 --- a/Tests/SwiftDocCUtilitiesTests/ProblemTests.swift +++ b/Tests/CommandLineTests/ProblemTests.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 @@ -9,7 +9,7 @@ */ import XCTest -import SwiftDocCUtilities +import CommandLine @testable import SwiftDocC import Markdown diff --git a/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift b/Tests/CommandLineTests/SemanticAnalyzerTests.swift similarity index 98% rename from Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift rename to Tests/CommandLineTests/SemanticAnalyzerTests.swift index 034b935152..bdff305818 100644 --- a/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift +++ b/Tests/CommandLineTests/SemanticAnalyzerTests.swift @@ -11,8 +11,8 @@ import XCTest import Markdown @testable import SwiftDocC -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities class SemanticAnalyzerTests: XCTestCase { private let catalogHierarchy = Folder(name: "SemanticAnalyzerTests.docc", content: [ diff --git a/Tests/SwiftDocCUtilitiesTests/ShadowFileManagerTemporaryDirectory.swift b/Tests/CommandLineTests/ShadowFileManagerTemporaryDirectory.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/ShadowFileManagerTemporaryDirectory.swift rename to Tests/CommandLineTests/ShadowFileManagerTemporaryDirectory.swift diff --git a/Tests/SwiftDocCUtilitiesTests/SignalTests.swift b/Tests/CommandLineTests/SignalTests.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/SignalTests.swift rename to Tests/CommandLineTests/SignalTests.swift diff --git a/Tests/SwiftDocCUtilitiesTests/StaticHostableTransformerTests.swift b/Tests/CommandLineTests/StaticHostableTransformerTests.swift similarity index 98% rename from Tests/SwiftDocCUtilitiesTests/StaticHostableTransformerTests.swift rename to Tests/CommandLineTests/StaticHostableTransformerTests.swift index 0fa8c102e6..a1d17aec07 100644 --- a/Tests/SwiftDocCUtilitiesTests/StaticHostableTransformerTests.swift +++ b/Tests/CommandLineTests/StaticHostableTransformerTests.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 @@ -11,8 +11,8 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities class StaticHostableTransformerTests: StaticHostingBaseTests { diff --git a/Tests/SwiftDocCUtilitiesTests/StaticHostingBaseTest.swift b/Tests/CommandLineTests/StaticHostingBaseTest.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/StaticHostingBaseTest.swift rename to Tests/CommandLineTests/StaticHostingBaseTest.swift diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png b/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png rename to Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/Info.plist b/Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/Info.plist similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/Info.plist rename to Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/Info.plist diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json b/Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json rename to Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json b/Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json rename to Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/OverloadedSymbols.docc/Info.plist b/Tests/CommandLineTests/Test Bundles/OverloadedSymbols.docc/Info.plist similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/OverloadedSymbols.docc/Info.plist rename to Tests/CommandLineTests/Test Bundles/OverloadedSymbols.docc/Info.plist diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json b/Tests/CommandLineTests/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json rename to Tests/CommandLineTests/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/SingleArticleTestBundle.docc/Info.plist b/Tests/CommandLineTests/Test Bundles/SingleArticleTestBundle.docc/Info.plist similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/SingleArticleTestBundle.docc/Info.plist rename to Tests/CommandLineTests/Test Bundles/SingleArticleTestBundle.docc/Info.plist diff --git a/Tests/SwiftDocCUtilitiesTests/Test Bundles/SingleArticleTestBundle.docc/article.md b/Tests/CommandLineTests/Test Bundles/SingleArticleTestBundle.docc/article.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Bundles/SingleArticleTestBundle.docc/article.md rename to Tests/CommandLineTests/Test Bundles/SingleArticleTestBundle.docc/article.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Resources/DeckKit-Objective-C.symbols.json b/Tests/CommandLineTests/Test Resources/DeckKit-Objective-C.symbols.json similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Resources/DeckKit-Objective-C.symbols.json rename to Tests/CommandLineTests/Test Resources/DeckKit-Objective-C.symbols.json diff --git a/Tests/SwiftDocCUtilitiesTests/Test Resources/Overview.tutorial b/Tests/CommandLineTests/Test Resources/Overview.tutorial similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Resources/Overview.tutorial rename to Tests/CommandLineTests/Test Resources/Overview.tutorial diff --git a/Tests/SwiftDocCUtilitiesTests/Test Resources/Test Template/index.html b/Tests/CommandLineTests/Test Resources/Test Template/index.html similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Resources/Test Template/index.html rename to Tests/CommandLineTests/Test Resources/Test Template/index.html diff --git a/Tests/SwiftDocCUtilitiesTests/Test Resources/TopLevelCuration.symbols.json b/Tests/CommandLineTests/Test Resources/TopLevelCuration.symbols.json similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Resources/TopLevelCuration.symbols.json rename to Tests/CommandLineTests/Test Resources/TopLevelCuration.symbols.json diff --git a/Tests/SwiftDocCUtilitiesTests/Test Resources/UncuratedArticle.md b/Tests/CommandLineTests/Test Resources/UncuratedArticle.md similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Resources/UncuratedArticle.md rename to Tests/CommandLineTests/Test Resources/UncuratedArticle.md diff --git a/Tests/SwiftDocCUtilitiesTests/Test Resources/image.png b/Tests/CommandLineTests/Test Resources/image.png similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Test Resources/image.png rename to Tests/CommandLineTests/Test Resources/image.png diff --git a/Tests/SwiftDocCUtilitiesTests/ThrottleTests.swift b/Tests/CommandLineTests/ThrottleTests.swift similarity index 92% rename from Tests/SwiftDocCUtilitiesTests/ThrottleTests.swift rename to Tests/CommandLineTests/ThrottleTests.swift index 699822810d..5a1fb7cdfa 100644 --- a/Tests/SwiftDocCUtilitiesTests/ThrottleTests.swift +++ b/Tests/CommandLineTests/ThrottleTests.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 @@ -9,7 +9,7 @@ */ import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine class ThrottleTests: XCTestCase { func testThrottlingSingleCall() throws { diff --git a/Tests/SwiftDocCUtilitiesTests/TransformForStaticHostingActionTests.swift b/Tests/CommandLineTests/TransformForStaticHostingActionTests.swift similarity index 98% rename from Tests/SwiftDocCUtilitiesTests/TransformForStaticHostingActionTests.swift rename to Tests/CommandLineTests/TransformForStaticHostingActionTests.swift index 912e5824a7..7b1e3b59eb 100644 --- a/Tests/SwiftDocCUtilitiesTests/TransformForStaticHostingActionTests.swift +++ b/Tests/CommandLineTests/TransformForStaticHostingActionTests.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 @@ -11,8 +11,8 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import SwiftDocCUtilities -import SwiftDocCTestUtilities +@testable import CommandLine +import TestUtilities class TransformForStaticHostingActionTests: StaticHostingBaseTests { diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/DirectedGraphTests.swift b/Tests/CommandLineTests/Utility/DirectedGraphTests.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Utility/DirectedGraphTests.swift rename to Tests/CommandLineTests/Utility/DirectedGraphTests.swift diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/FileTests.swift b/Tests/CommandLineTests/Utility/FileTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/Utility/FileTests.swift rename to Tests/CommandLineTests/Utility/FileTests.swift index cfabe527a4..0165dcf716 100644 --- a/Tests/SwiftDocCUtilitiesTests/Utility/FileTests.swift +++ b/Tests/CommandLineTests/Utility/FileTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import SwiftDocCTestUtilities +import TestUtilities class FileTests: XCTestCase { func testAbsoluteURL() { diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/LogHandleTests.swift b/Tests/CommandLineTests/Utility/LogHandleTests.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/Utility/LogHandleTests.swift rename to Tests/CommandLineTests/Utility/LogHandleTests.swift index c7bdb5afb9..489f0f91b5 100644 --- a/Tests/SwiftDocCUtilitiesTests/Utility/LogHandleTests.swift +++ b/Tests/CommandLineTests/Utility/LogHandleTests.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 @@ -9,7 +9,7 @@ */ import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine @testable import SwiftDocC class LogHandleTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/Sequence+UniqueTests.swift b/Tests/CommandLineTests/Utility/Sequence+UniqueTests.swift similarity index 94% rename from Tests/SwiftDocCUtilitiesTests/Utility/Sequence+UniqueTests.swift rename to Tests/CommandLineTests/Utility/Sequence+UniqueTests.swift index 865ffca714..4de534089d 100644 --- a/Tests/SwiftDocCUtilitiesTests/Utility/Sequence+UniqueTests.swift +++ b/Tests/CommandLineTests/Utility/Sequence+UniqueTests.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 @@ -10,7 +10,7 @@ import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine class Sequence_UniqueTests: XCTestCase { func testEmpty() { diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift b/Tests/CommandLineTests/Utility/TestFileSystemTests.swift similarity index 99% rename from Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift rename to Tests/CommandLineTests/Utility/TestFileSystemTests.swift index fd3eb71489..85eec4dc38 100644 --- a/Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift +++ b/Tests/CommandLineTests/Utility/TestFileSystemTests.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 @@ -10,7 +10,7 @@ import XCTest import SwiftDocC -@testable import SwiftDocCTestUtilities +@testable import TestUtilities class TestFileSystemTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/URL+IsAbsoluteWebURLTests.swift b/Tests/CommandLineTests/Utility/URL+IsAbsoluteWebURLTests.swift similarity index 90% rename from Tests/SwiftDocCUtilitiesTests/Utility/URL+IsAbsoluteWebURLTests.swift rename to Tests/CommandLineTests/Utility/URL+IsAbsoluteWebURLTests.swift index d693826f6b..75c20a70ae 100644 --- a/Tests/SwiftDocCUtilitiesTests/Utility/URL+IsAbsoluteWebURLTests.swift +++ b/Tests/CommandLineTests/Utility/URL+IsAbsoluteWebURLTests.swift @@ -1,14 +1,14 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-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 */ -@testable import SwiftDocCUtilities +@testable import CommandLine import XCTest class URL_IsAbsoluteWebURLTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/URL+RelativeTests.swift b/Tests/CommandLineTests/Utility/URL+RelativeTests.swift similarity index 97% rename from Tests/SwiftDocCUtilitiesTests/Utility/URL+RelativeTests.swift rename to Tests/CommandLineTests/Utility/URL+RelativeTests.swift index 9aea0848a8..d832546cbd 100644 --- a/Tests/SwiftDocCUtilitiesTests/Utility/URL+RelativeTests.swift +++ b/Tests/CommandLineTests/Utility/URL+RelativeTests.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 @@ -9,7 +9,7 @@ */ import XCTest -@testable import SwiftDocCUtilities +@testable import CommandLine class URL_RelativeTests: XCTestCase { diff --git a/Tests/SwiftDocCUtilitiesTests/Utility/XCTestCase+enableFeatureFlag.swift b/Tests/CommandLineTests/Utility/XCTestCase+enableFeatureFlag.swift similarity index 100% rename from Tests/SwiftDocCUtilitiesTests/Utility/XCTestCase+enableFeatureFlag.swift rename to Tests/CommandLineTests/Utility/XCTestCase+enableFeatureFlag.swift diff --git a/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift b/Tests/CommandLineTests/XCTestCase+LoadingData.swift similarity index 98% rename from Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift rename to Tests/CommandLineTests/XCTestCase+LoadingData.swift index cc50e4ca1f..2a6c26b7a1 100644 --- a/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift +++ b/Tests/CommandLineTests/XCTestCase+LoadingData.swift @@ -11,7 +11,7 @@ import Foundation import XCTest import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities extension XCTestCase { /// Loads a documentation catalog from an in-memory test file system. diff --git a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift index 1073bc693e..20f219b6f2 100644 --- a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift +++ b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift @@ -11,7 +11,7 @@ import XCTest import Markdown @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class NonInclusiveLanguageCheckerTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift index 8cfbad65db..b92fdff6c4 100644 --- a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift +++ b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift @@ -10,7 +10,7 @@ import Foundation import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities import XCTest // THIS SHOULD BE REMOVED, RIGHT?! diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift index 6943f58bc6..e5042b0536 100644 --- a/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.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 @@ -11,7 +11,7 @@ import XCTest import Markdown @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class DiagnosticConsoleWriterDefaultFormattingTest: XCTestCase { diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift index f22e888977..fd94e38c82 100644 --- a/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift @@ -12,7 +12,7 @@ import XCTest @testable import SwiftDocC import Markdown @testable import SymbolKit -import SwiftDocCTestUtilities +import TestUtilities class DiagnosticTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift index a46afb677e..9df8b1e100 100644 --- a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift @@ -12,7 +12,7 @@ import XCTest import Foundation @testable import SwiftDocC import SymbolKit -import SwiftDocCTestUtilities +import TestUtilities class ConvertServiceTests: XCTestCase { private let testBundleInfo = DocumentationBundle.Info( diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index 1d5ed2b1cc..9335fe2bd4 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @_spi(ExternalLinks) @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class ExternalRenderNodeTests: XCTestCase { private func generateExternalResolver() -> TestMultiResultExternalReferenceResolver { diff --git a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift index 16c63bede5..6f62a77450 100644 --- a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities typealias Node = NavigatorTree.Node typealias PageType = NavigatorIndex.PageType diff --git a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift index d2682c360d..8f748d2cdf 100644 --- a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import SwiftDocCTestUtilities +import TestUtilities @testable import SwiftDocC diff --git a/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift b/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift index f45a2d7119..e60f677da9 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class AutoCapitalizationTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift index d214b77338..2f8df44a62 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class AutomaticCurationTests: XCTestCase { private let (availableExtensionSymbolKinds, availableNonExtensionSymbolKinds) = Set(AutomaticCuration.groupKindOrder).union(SymbolGraph.Symbol.KindIdentifier.allCases) diff --git a/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift b/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift index 323fe5a2c5..b0493e744c 100644 --- a/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class BundleDiscoveryTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift index 9523dc0966..b068787d18 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift @@ -11,7 +11,7 @@ import XCTest import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class DocumentationContext_RootPageTests: XCTestCase { func testArticleOnlyCatalogWithExplicitTechnologyRoot() async throws { diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index e788f1b192..c037afb9a4 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -12,7 +12,7 @@ import XCTest import SymbolKit @testable @_spi(ExternalLinks) import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities func diffDescription(lhs: String, rhs: String) -> String { let leftLines = lhs.components(separatedBy: .newlines) diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift index cab832ec10..ce1ab9ce2b 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift @@ -13,7 +13,7 @@ import Foundation import XCTest import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities import Markdown class DocumentationCuratorTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift index 0d0bab01b5..adc94f2251 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift @@ -12,7 +12,7 @@ import XCTest import Markdown import SymbolKit @testable @_spi(ExternalLinks) import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class ExternalPathHierarchyResolverTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 803868d2ad..3101ec444c 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -12,7 +12,7 @@ import XCTest @_spi(ExternalLinks) @testable import SwiftDocC import Markdown import SymbolKit -import SwiftDocCTestUtilities +import TestUtilities class ExternalReferenceResolverTests: XCTestCase { class TestExternalReferenceResolver: ExternalDocumentationSource { diff --git a/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift b/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift index 2d6edd7e57..2bd1f21ceb 100644 --- a/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import SwiftDocCTestUtilities +import TestUtilities @testable import SwiftDocC class DocumentationInputsProviderTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift b/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift index 4a34e693ca..28bc0caa6f 100644 --- a/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class NodeTagsTests: XCTestCase { func testSPIMetadata() async throws { diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index c7c54f5495..305bc6dc9a 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -11,7 +11,7 @@ import XCTest import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities import Markdown class PathHierarchyTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift index 84e39551d5..5ee3c9ff1d 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import SymbolKit -import SwiftDocCTestUtilities +import TestUtilities class SnippetResolverTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift index 39ad7e8482..6d235652bf 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class SymbolGraphLoaderTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift index 49d4985b93..6384fda46e 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class SymbolReferenceTests: XCTestCase { func testUsesIdentifierForUnresolvedSymbols() { diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 5ac4655d92..029fe68c33 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -11,7 +11,7 @@ import XCTest import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class LinkDestinationSummaryTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift b/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift index c63d951a60..93f560e259 100644 --- a/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift +++ b/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class LineHighlighterTests: XCTestCase { static let bundleID: DocumentationBundle.Identifier = "org.swift.docc.LineHighlighterTests" diff --git a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift index 912ac6fdb3..eb9b2d5fd4 100644 --- a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift +++ b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift @@ -13,7 +13,7 @@ import XCTest import Markdown @testable import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class ParametersAndReturnValidatorTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift b/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift index 2a5ddab8bf..2e9869da7a 100644 --- a/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift +++ b/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift @@ -12,7 +12,7 @@ import XCTest import SymbolKit import Foundation @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class PropertyListPossibleValuesSectionTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift index d3a3fc2446..225e5cccc0 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift @@ -11,7 +11,7 @@ import Foundation @testable import SwiftDocC import SymbolKit -import SwiftDocCTestUtilities +import TestUtilities import XCTest class SemaToRenderNodeMixedLanguageTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 73b4a508ed..f6a309beed 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -12,7 +12,7 @@ import Markdown import XCTest import SymbolKit -import SwiftDocCTestUtilities +import TestUtilities class SemaToRenderNodeTests: XCTestCase { func testCompileTutorial() async throws { diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift index a4640f8b2f..4140da55ed 100644 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift @@ -12,7 +12,7 @@ import XCTest import Foundation import SymbolKit @_spi(ExternalLinks) @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities // This tests the deprecated V1 implementation of `OutOfProcessReferenceResolver`. // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift index e4e42b500a..d6ce70ddb7 100644 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift @@ -12,7 +12,7 @@ import XCTest import Foundation import SymbolKit @_spi(ExternalLinks) @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities #if os(macOS) class OutOfProcessReferenceResolverV2Tests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift b/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift index 4c264ec3e7..dd44559f63 100644 --- a/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift +++ b/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class AutomaticSeeAlsoTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift index 19bd1673ac..0350f4cad2 100644 --- a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities import SymbolKit fileprivate let jsonDecoder = JSONDecoder() diff --git a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift index 24e21475fa..847872ed85 100644 --- a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities import SymbolKit class DeclarationsRenderSectionTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift index 200fd3073b..37a9ab94f9 100644 --- a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class DefaultAvailabilityTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift b/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift index 002f93d25a..176f776c4e 100644 --- a/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class DefaultCodeBlockSyntaxTests: XCTestCase { func testCodeBlockWithoutAnyLanguageOrDefault() async throws { diff --git a/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift b/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift index 778e190577..35f3515ac4 100644 --- a/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class HeadingAnchorTests: XCTestCase { func testEncodeHeadingAnchor() async throws { diff --git a/Tests/SwiftDocCTests/Rendering/PlistSymbolTests.swift b/Tests/SwiftDocCTests/Rendering/PlistSymbolTests.swift index 15b8c44978..1c13a18678 100644 --- a/Tests/SwiftDocCTests/Rendering/PlistSymbolTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PlistSymbolTests.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 @@ -11,7 +11,7 @@ import Foundation import XCTest @testable public import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class PlistSymbolTests: XCTestCase { private let plistSymbolURL = Bundle.module.url( diff --git a/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift index a4790d4d28..90137262f7 100644 --- a/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SwiftDocC import SymbolKit -import SwiftDocCTestUtilities +import TestUtilities class PropertyListDetailsRenderSectionTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift b/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift index 9de46ea5c3..ca982b3f6b 100644 --- a/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities import SymbolKit fileprivate extension [RenderBlockContent] { diff --git a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift index 29b3728990..0349b25ec7 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift @@ -11,7 +11,7 @@ import Foundation import Markdown @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities import XCTest typealias Position = RenderBlockContent.CodeBlockOptions.Position diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift index 1cb095f54a..0aa591d6fa 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift @@ -13,7 +13,7 @@ import XCTest import SymbolKit import Markdown @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift index be2a95ea56..e9b1bdaff1 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities import Markdown import SymbolKit diff --git a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift index c3ea5e5ab5..d70241cba9 100644 --- a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest import SymbolKit @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class SymbolAvailabilityTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/TermListTests.swift b/Tests/SwiftDocCTests/Rendering/TermListTests.swift index d5e496456c..c2c1696b82 100644 --- a/Tests/SwiftDocCTests/Rendering/TermListTests.swift +++ b/Tests/SwiftDocCTests/Rendering/TermListTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest import Markdown @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities class TermListTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift b/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift index 856a3908be..4a489dc12a 100644 --- a/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift @@ -11,7 +11,7 @@ import XCTest @testable @preconcurrency import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities import SymbolKit class ArticleSymbolMentionsTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift b/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift index 7be45b6176..1587ad1c54 100644 --- a/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class ChoiceTests: XCTestCase { func testInvalidEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift b/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift index 9832537c9e..9aa4cca0ad 100644 --- a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift +++ b/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities @testable import SymbolKit class DoxygenTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift index 97f3b48ab2..16cfa20232 100644 --- a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class MarkupReferenceResolverTests: XCTestCase { func testArbitraryReferenceInComment() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift b/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift index 042cad6bd8..4664829965 100644 --- a/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class MultipleChoiceTests: XCTestCase { func testInvalidEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/StackTests.swift b/Tests/SwiftDocCTests/Semantics/StackTests.swift index 721db9aa3f..6f7275889a 100644 --- a/Tests/SwiftDocCTests/Semantics/StackTests.swift +++ b/Tests/SwiftDocCTests/Semantics/StackTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class StackTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/StepTests.swift b/Tests/SwiftDocCTests/Semantics/StepTests.swift index d8a1b576df..3f66fbbfcb 100644 --- a/Tests/SwiftDocCTests/Semantics/StepTests.swift +++ b/Tests/SwiftDocCTests/Semantics/StepTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class StepTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift index 063c8c997d..4798c6e78d 100644 --- a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift @@ -12,7 +12,7 @@ import XCTest @testable import SymbolKit @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class SymbolTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift index 086f0a7488..de064d8895 100644 --- a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class TutorialArticleTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/TutorialTests.swift b/Tests/SwiftDocCTests/Semantics/TutorialTests.swift index 33683991a4..767ed5f706 100644 --- a/Tests/SwiftDocCTests/Semantics/TutorialTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TutorialTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class TutorialTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift b/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift index 3fb78b1444..99708fd1c1 100644 --- a/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift +++ b/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class VideoMediaTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/VolumeTests.swift b/Tests/SwiftDocCTests/Semantics/VolumeTests.swift index 827ab38f7d..2b5c258899 100644 --- a/Tests/SwiftDocCTests/Semantics/VolumeTests.swift +++ b/Tests/SwiftDocCTests/Semantics/VolumeTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities class VolumeTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Utility/LMDBTests.swift b/Tests/SwiftDocCTests/Utility/LMDBTests.swift index dd50b29604..06a6c514b5 100644 --- a/Tests/SwiftDocCTests/Utility/LMDBTests.swift +++ b/Tests/SwiftDocCTests/Utility/LMDBTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC -import SwiftDocCTestUtilities +import TestUtilities final class SwiftLMDBTests: XCTestCase { var environment: LMDB.Environment! diff --git a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift index 8639fd62a8..ceb47c7213 100644 --- a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift +++ b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import SwiftDocCTestUtilities +import TestUtilities @testable import SwiftDocC import Markdown diff --git a/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift b/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift index a7306396b9..939e79c82a 100644 --- a/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift +++ b/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift @@ -10,7 +10,7 @@ @testable import SwiftDocC import XCTest -import SwiftDocCTestUtilities +import TestUtilities import SymbolKit extension XCTestCase { diff --git a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift index 4103f4f92b..203311214a 100644 --- a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift +++ b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SwiftDocC import Markdown -import SwiftDocCTestUtilities +import TestUtilities extension XCTestCase { diff --git a/Tests/signal-test-app/main.swift b/Tests/signal-test-app/main.swift index 2973ef3d69..4a96e6eb9f 100644 --- a/Tests/signal-test-app/main.swift +++ b/Tests/signal-test-app/main.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 @@ -10,13 +10,13 @@ // // Trap signals and exit with a predefined error code. -// Check Tests/SwiftDocCUtilitiesTests/SignalTests.swift for more details. +// Check Tests/CommandLineTests/SignalTests.swift for more details. // #if os(macOS) || os(Linux) || os(Android) import Foundation -import SwiftDocCUtilities +import CommandLine Signal.on(Signal.all) { _ in print("Signal test app exiting.") diff --git a/bin/check-source b/bin/check-source index 7ab7fa1de5..b72b7a4c41 100755 --- a/bin/check-source +++ b/bin/check-source @@ -48,7 +48,7 @@ for language in swift-or-c bash md-or-tutorial html docker; do reader=head case "$language" in swift-or-c) - exceptions=( -name Package.swift -o -path "./Tests/SwiftDocCTests/Test Bundles/*" -o -path "./Tests/SwiftDocCUtilitiesTests/Test Bundles/*") + exceptions=( -name Package.swift -o -path "./Tests/SwiftDocCTests/Test Bundles/*" -o -path "./Tests/CommandLineTests/Test Bundles/*") matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) cat > "$tmp" <<"EOF" /* diff --git a/bin/preview-docs b/bin/preview-docs index f988b5548f..1faa752bdf 100755 --- a/bin/preview-docs +++ b/bin/preview-docs @@ -2,7 +2,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 @@ -28,7 +28,7 @@ while test $# -gt 0; do case "$1" in --help) echo - echo "Usage: $(basename $0) [SwiftDocC|SwiftDocCUtilities|DocC] [-h] [--convert-only]" + echo "Usage: $(basename $0) [SwiftDocC|CommandLine|DocC] [-h] [--convert-only]" echo "Builds the given framework and converts or previews the documentation" echo "Note: To preview you must set the \`DOCC_HTML_DIR\` with a path to a documentation template. @@ -80,27 +80,27 @@ case $FRAMEWORK in --output-path "$DOCS_BUILD_DIR" ;; - "SwiftDocCUtilities") + "CommandLine") - # Generate symbol graph files for SwiftDocCUtilities + # Generate symbol graph files for CommandLine swift build --package-path "$DOCC_ROOT" \ - --target SwiftDocCUtilities \ + --target CommandLine \ -Xswiftc -emit-symbol-graph \ -Xswiftc -emit-symbol-graph-dir -Xswiftc "$SGFS_DIR" echo # Delete the symbol graph files from dependences by looking for - # those without a 'SwiftDocCUtilities' prefix. - find "$SGFS_DIR" -type f ! -name 'SwiftDocCUtilities*' -delete + # those without a 'CommandLine' prefix. + find "$SGFS_DIR" -type f ! -name 'CommandLine*' -delete echo # Compile the documentation and the symbol graph data. - swift run docc $DOCC_CMD "$DOCC_ROOT/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc" \ + swift run docc $DOCC_CMD "$DOCC_ROOT/Sources/CommandLine/CommandLine.docc" \ --experimental-enable-custom-templates \ - --fallback-display-name SwiftDocCUtilities \ - --fallback-bundle-identifier org.swift.SwiftDocCUtilities \ + --fallback-display-name CommandLine \ + --fallback-bundle-identifier org.swift.CommandLine \ --fallback-bundle-version 1.0.0 \ --additional-symbol-graph-dir "$SGFS_DIR" \ --output-path $DOCS_BUILD_DIR @@ -116,7 +116,7 @@ case $FRAMEWORK in *) echo - echo "Error: Unknown module '$FRAMEWORK'. Preview is supported only for SwiftDocC, SwiftDocCUtilities, or DocC." + echo "Error: Unknown module '$FRAMEWORK'. Preview is supported only for SwiftDocC, CommandLine, or DocC." exit 0 ;; esac diff --git a/bin/update-gh-pages-documentation-site b/bin/update-gh-pages-documentation-site index c1469ed8ae..41b8e41341 100755 --- a/bin/update-gh-pages-documentation-site +++ b/bin/update-gh-pages-documentation-site @@ -2,7 +2,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 @@ -27,9 +27,9 @@ SWIFT_DOCC_ROOT="$(dirname $(dirname $(filepath $0)))" DOCC_BUILD_DIR="$SWIFT_DOCC_ROOT"/.build/docc-gh-pages-build DOCC_OUTPUT_DIR="$DOCC_BUILD_DIR"/SwiftDocC.doccarchive -DOCC_UTILITIES_OUTPUT_DIR="$DOCC_BUILD_DIR"/SwiftDocCUtilities.doccarchive +COMMAND_LINE_OUTPUT_DIR="$DOCC_BUILD_DIR"/CommandLine.doccarchive -mkdir -p "$DOCC_UTILITIES_OUTPUT_DIR" +mkdir -p "$COMMAND_LINE_OUTPUT_DIR" # Set current directory to the repository root cd "$SWIFT_DOCC_ROOT" @@ -58,31 +58,31 @@ swift package \ --hosting-base-path swift-docc \ --output-path "$DOCC_OUTPUT_DIR" -echo -e "\033[34;1m Building SwiftDocC Utilities docs at $DOCC_UTILITIES_OUTPUT_DIR \033[0m" +echo -e "\033[34;1m Building SwiftDocC Utilities docs at $COMMAND_LINE_OUTPUT_DIR \033[0m" -# Generate documentation for the 'SwiftDocCUtilities' target and output it +# Generate documentation for the 'CommandLine' target and output it # to a temporary output directory in the .build directory. swift package \ --allow-writing-to-directory "$DOCC_BUILD_DIR" \ generate-documentation \ - --target SwiftDocCUtilities \ + --target CommandLine \ --disable-indexing \ --source-service github \ --source-service-base-url https://github.com/swiftlang/swift-docc/blob/main \ --checkout-path "$SWIFT_DOCC_ROOT" \ --transform-for-static-hosting \ --hosting-base-path swift-docc \ - --output-path "$DOCC_UTILITIES_OUTPUT_DIR" + --output-path "$COMMAND_LINE_OUTPUT_DIR" echo -e "\033[34;1m Merging docs \033q[0m" # Remove the output directory so that the merge command can output there rm -rf "$SWIFT_DOCC_ROOT/gh-pages/docs" -# Merge the SwiftDocCUtilities docs into the primary SwiftDocC docs +# Merge the CommandLine docs into the primary SwiftDocC docs swift run docc merge \ "$DOCC_OUTPUT_DIR" \ - "$DOCC_UTILITIES_OUTPUT_DIR" \ + "$COMMAND_LINE_OUTPUT_DIR" \ --output-path "$SWIFT_DOCC_ROOT/gh-pages/docs" # Save the current commit we've just built documentation from in a variable From f2a841ee8351ca6e902465c6e161dbe6ae89d122 Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Mon, 10 Nov 2025 09:26:25 -0800 Subject: [PATCH 68/90] Revert "Rename two internal targets for clarity (#1330)" This reverts commit d616aa5e089533820aedfbce4a5979dffac98698. --- CONTRIBUTING.md | 4 ++-- Package.swift | 20 ++++++++-------- Sources/CMakeLists.txt | 2 +- .../SwiftDocC/AddingFeatureFlags.md | 6 ++--- .../SwiftDocC/CompilerPipeline.md | 4 ++-- .../FilesAndFolders.swift | 0 .../SymbolGraphCreation.swift | 0 .../TestFileSystem.swift | 0 .../XCTestCase+TemporaryDirectory.swift | 0 .../Action/Action.swift | 0 .../Action/ActionResult.swift | 0 .../Action/Actions/Action+MoveOutput.swift | 0 .../Actions/Convert/ConvertAction.swift | 0 .../Convert/ConvertFileWritingConsumer.swift | 0 .../CoverageDataEntry+generateSummary.swift | 0 .../Action/Actions/Convert/Indexer.swift | 0 .../JSONEncodingRenderNodeWriter.swift | 0 .../Action/Actions/CoverageAction.swift | 0 .../Actions/EmitGeneratedCurationAction.swift | 0 .../Action/Actions/IndexAction.swift | 0 .../Action/Actions/Init/CatalogTemplate.swift | 0 .../Actions/Init/CatalogTemplateKind.swift | 0 .../Action/Actions/Init/InitAction.swift | 0 .../MergeAction+SynthesizedLandingPage.swift | 0 .../Action/Actions/Merge/MergeAction.swift | 0 .../Action/Actions/PreviewAction.swift | 0 .../TransformForStaticHostingAction.swift | 0 .../Action+performAndHandleResult.swift | 0 .../ConvertAction+CommandInitialization.swift | 0 ...CurationAction+CommandInitialization.swift | 0 .../IndexAction+CommandInitialization.swift | 0 .../InitAction+CommandInitialization.swift | 0 .../PreviewAction+CommandInitialization.swift | 0 ...cHostingAction+CommandInitialization.swift | 0 .../URLArgumentValidator.swift | 0 .../Options/DirectoryPathOption.swift | 0 .../Options/DocumentationArchiveOption.swift | 0 .../Options/DocumentationBundleOption.swift | 0 ...DocumentationCoverageOptionsArgument.swift | 2 +- .../ArgumentParsing/Options/InitOptions.swift | 0 .../OutOfProcessLinkResolverOption.swift | 0 .../Options/PreviewOptions.swift | 0 .../SourceRepositoryArguments.swift | 0 .../Options/TemplateOption.swift | 0 .../ArgumentParsing/Subcommands/Convert.swift | 0 .../Subcommands/EmitGeneratedCuration.swift | 0 .../ArgumentParsing/Subcommands/Index.swift | 0 .../ArgumentParsing/Subcommands/Init.swift | 0 .../ArgumentParsing/Subcommands/Merge.swift | 0 .../ArgumentParsing/Subcommands/Preview.swift | 0 .../Subcommands/ProcessArchive.swift | 0 .../Subcommands/ProcessCatalog.swift | 0 .../TransformForStaticHosting.swift | 0 Sources/SwiftDocCUtilities/CMakeLists.txt | 4 ++-- .../Docc.swift | 0 .../PreviewServer/PreviewHTTPHandler.swift | 0 .../PreviewServer/PreviewServer.swift | 0 .../DefaultRequestHandler.swift | 0 .../RequestHandler/ErrorRequestHandler.swift | 0 .../RequestHandler/FileRequestHandler.swift | 0 .../HTTPResponseHead+FromRequest.swift | 0 .../RequestHandlerFactory.swift | 0 .../SwiftDocCUtilities.md} | 8 +++---- .../SwiftDocCUtilities}/Actions/InitAction.md | 2 +- .../SwiftDocCUtilities}/Extensions/Docc.md | 2 +- .../SwiftDocCUtilities.docc}/footer.html | 0 .../SwiftDocCUtilities.docc}/header.html | 0 .../StaticHostableTransformer.swift | 0 .../Utility/DirectoryMonitor.swift | 0 .../Sequence+Unique.swift | 0 .../FoundationExtensions/String+Path.swift | 0 .../URL+IsAbsoluteWebURL.swift | 0 .../FoundationExtensions/URL+Relative.swift | 0 .../Utility/PlatformArgumentParser.swift | 0 .../Utility/Signal.swift | 0 .../Utility/Throttle.swift | 0 Sources/docc/CMakeLists.txt | 2 +- Sources/docc/main.swift | 4 ++-- .../NonInclusiveLanguageCheckerTests.swift | 2 +- ...recatedDiagnosticsDigestWarningTests.swift | 2 +- ...icConsoleWriterDefaultFormattingTest.swift | 4 ++-- .../Diagnostics/DiagnosticTests.swift | 2 +- .../ConvertService/ConvertServiceTests.swift | 2 +- .../Indexing/ExternalRenderNodeTests.swift | 2 +- .../Indexing/NavigatorIndexTests.swift | 2 +- .../Indexing/RenderIndexTests.swift | 2 +- .../AutoCapitalizationTests.swift | 2 +- .../AutomaticCurationTests.swift | 2 +- .../Infrastructure/BundleDiscoveryTests.swift | 2 +- .../DocumentationContext+RootPageTests.swift | 2 +- .../DocumentationContextTests.swift | 2 +- .../DocumentationCuratorTests.swift | 2 +- .../ExternalPathHierarchyResolverTests.swift | 2 +- .../ExternalReferenceResolverTests.swift | 2 +- .../DocumentationInputsProviderTests.swift | 2 +- .../Infrastructure/NodeTagsTests.swift | 2 +- .../Infrastructure/PathHierarchyTests.swift | 2 +- .../Infrastructure/SnippetResolverTests.swift | 2 +- .../SymbolGraph/SymbolGraphLoaderTests.swift | 2 +- .../Infrastructure/SymbolReferenceTests.swift | 2 +- .../LinkDestinationSummaryTests.swift | 2 +- .../Model/LineHighlighterTests.swift | 2 +- .../ParametersAndReturnValidatorTests.swift | 2 +- ...opertyListPossibleValuesSectionTests.swift | 2 +- .../SemaToRenderNodeMultiLanguageTests.swift | 2 +- .../Model/SemaToRenderNodeTests.swift | 2 +- ...OutOfProcessReferenceResolverV1Tests.swift | 2 +- ...OutOfProcessReferenceResolverV2Tests.swift | 2 +- .../Rendering/AutomaticSeeAlsoTests.swift | 2 +- .../ConstraintsRenderSectionTests.swift | 2 +- .../DeclarationsRenderSectionTests.swift | 2 +- .../Rendering/DefaultAvailabilityTests.swift | 2 +- .../DefaultCodeListingSyntaxTests.swift | 2 +- .../Rendering/HeadingAnchorTests.swift | 2 +- .../Rendering/PlistSymbolTests.swift | 4 ++-- ...ropertyListDetailsRenderSectionTests.swift | 2 +- .../Rendering/RESTSymbolsTests.swift | 2 +- .../RenderContentCompilerTests.swift | 2 +- ...derNodeTranslatorSymbolVariantsTests.swift | 2 +- .../Rendering/RenderNodeTranslatorTests.swift | 2 +- .../Rendering/SymbolAvailabilityTests.swift | 2 +- .../Rendering/TermListTests.swift | 2 +- .../ArticleSymbolMentionsTests.swift | 2 +- .../Semantics/ChoiceTests.swift | 2 +- .../Semantics/DoxygenTests.swift | 2 +- .../MarkupReferenceResolverTests.swift | 2 +- .../Semantics/MultipleChoiceTests.swift | 2 +- .../SwiftDocCTests/Semantics/StackTests.swift | 2 +- .../SwiftDocCTests/Semantics/StepTests.swift | 2 +- .../Semantics/SymbolTests.swift | 2 +- .../Semantics/TutorialArticleTests.swift | 2 +- .../Semantics/TutorialTests.swift | 2 +- .../Semantics/VideoMediaTests.swift | 2 +- .../Semantics/VolumeTests.swift | 2 +- Tests/SwiftDocCTests/Utility/LMDBTests.swift | 2 +- .../Utility/ListItemExtractorTests.swift | 2 +- .../Utility/XCTestCase+MentionedIn.swift | 2 +- .../XCTestCase+LoadingTestData.swift | 2 +- ...nvertSubcommandSourceRepositoryTests.swift | 6 ++--- .../ConvertSubcommandTests.swift | 6 ++--- ...tationCoverageKindFilterOptionsTests.swift | 0 .../ArgumentParsing/ErrorMessageTests.swift | 4 ++-- .../MergeSubcommandTests.swift | 4 ++-- .../PreviewSubcommandTests.swift | 4 ++-- .../C+Extensions.swift | 0 .../ConvertActionIndexerTests.swift | 2 +- .../ConvertActionStaticHostableTests.swift | 6 ++--- .../ConvertActionTests.swift | 4 ++-- .../DirectoryMonitorTests.swift | 2 +- .../EmitGeneratedCurationsActionTests.swift | 6 ++--- .../FolderStructure.swift | 6 ++--- .../FolderStructureTests.swift | 2 +- .../HTMLTemplateDirectory.swift | 2 +- .../IndexActionTests.swift | 6 ++--- .../Init/InitActionTests.swift | 6 ++--- .../JSONEncodingRenderNodeWriterTests.swift | 4 ++-- .../MergeActionTests.swift | 4 ++-- .../PlatformArgumentParserTests.swift | 4 ++-- .../PreviewActionIntegrationTests.swift | 6 ++--- .../PreviewHTTPHandlerTests.swift | 4 ++-- .../DefaultRequestHandlerTests.swift | 4 ++-- .../ErrorRequestHandlerTests.swift | 2 +- .../FileRequestHandlerTests.swift | 4 ++-- .../PreviewServer/ServerTestUtils.swift | 4 ++-- .../ProblemTests.swift | 4 ++-- .../SemanticAnalyzerTests.swift | 4 ++-- .../ShadowFileManagerTemporaryDirectory.swift | 0 .../SignalTests.swift | 0 .../StaticHostableTransformerTests.swift | 6 ++--- .../StaticHostingBaseTest.swift | 0 .../Default Code Listing Syntax.md | 0 .../FillIntroduced.symbols.json | 0 .../Info.plist | 0 .../MyKit@SideKit.symbols.json | 0 .../TestOverview.tutorial | 0 .../TestTutorial.tutorial | 0 .../TestTutorial2.tutorial | 0 .../TestTutorialArticle.tutorial | 0 .../TutorialMediaWithSpaces.tutorial | 0 .../article.md | 0 .../article2.md | 0 .../article3.md | 0 .../documentation/myclass.md | 0 .../documentation/mykit.md | 0 .../documentation/myprotocol.md | 0 .../documentation/sideclass-init.md | 0 .../documentation/sidekit.md | 0 .../figure1.png | Bin .../figure1~dark.png | Bin .../helloworld.swift | 0 .../helloworld1.swift | 0 .../helloworld2.swift | 0 .../helloworld3.swift | 0 .../helloworld4.swift | 0 .../intro.png | Bin .../introposter.png | Bin .../introposter2.png | Bin .../introvideo.mp4 | Bin .../introvideo~dark.mp4 | Bin .../mykit-iOS.symbols.json | 0 .../project.zip | Bin .../sidekit.symbols.json | 0 .../something@2x.png | Bin .../step.png | Bin .../titled2up.png | Bin .../titled2upCapital.PNG | Bin .../with spaces.mp4 | Bin .../with spaces.png | Bin .../with spaces@2x.png | Bin .../MixedLanguageFramework.docc/Info.plist | 0 .../clang/MixedLanguageFramework.symbols.json | 0 .../swift/MixedLanguageFramework.symbols.json | 0 .../OverloadedSymbols.docc/Info.plist | 0 .../ShapeKit.symbols.json | 0 .../SingleArticleTestBundle.docc/Info.plist | 0 .../SingleArticleTestBundle.docc/article.md | 0 .../DeckKit-Objective-C.symbols.json | 0 .../Test Resources/Overview.tutorial | 0 .../Test Resources/Test Template/index.html | 0 .../TopLevelCuration.symbols.json | 0 .../Test Resources/UncuratedArticle.md | 0 .../Test Resources/image.png | Bin .../ThrottleTests.swift | 4 ++-- ...TransformForStaticHostingActionTests.swift | 6 ++--- .../Utility/DirectedGraphTests.swift | 0 .../Utility/FileTests.swift | 2 +- .../Utility/LogHandleTests.swift | 4 ++-- .../Utility/Sequence+UniqueTests.swift | 4 ++-- .../Utility/TestFileSystemTests.swift | 4 ++-- .../Utility/URL+IsAbsoluteWebURLTests.swift | 4 ++-- .../Utility/URL+RelativeTests.swift | 4 ++-- .../XCTestCase+enableFeatureFlag.swift | 0 .../XCTestCase+LoadingData.swift | 2 +- Tests/signal-test-app/main.swift | 6 ++--- bin/check-source | 2 +- bin/preview-docs | 22 +++++++++--------- bin/update-gh-pages-documentation-site | 18 +++++++------- 237 files changed, 191 insertions(+), 191 deletions(-) rename Sources/{TestUtilities => SwiftDocCTestUtilities}/FilesAndFolders.swift (100%) rename Sources/{TestUtilities => SwiftDocCTestUtilities}/SymbolGraphCreation.swift (100%) rename Sources/{TestUtilities => SwiftDocCTestUtilities}/TestFileSystem.swift (100%) rename Sources/{TestUtilities => SwiftDocCTestUtilities}/XCTestCase+TemporaryDirectory.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Action.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/ActionResult.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Action+MoveOutput.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Convert/ConvertAction.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Convert/ConvertFileWritingConsumer.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Convert/Indexer.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/CoverageAction.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/EmitGeneratedCurationAction.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/IndexAction.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Init/CatalogTemplate.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Init/CatalogTemplateKind.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Init/InitAction.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/Merge/MergeAction.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/PreviewAction.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Action/Actions/TransformForStaticHostingAction.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Options/DirectoryPathOption.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Options/DocumentationArchiveOption.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Options/DocumentationBundleOption.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift (97%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Options/InitOptions.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Options/PreviewOptions.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Options/TemplateOption.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Subcommands/Convert.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Subcommands/Index.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Subcommands/Init.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Subcommands/Merge.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Subcommands/Preview.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Subcommands/ProcessArchive.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Subcommands/ProcessCatalog.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/ArgumentParsing/Subcommands/TransformForStaticHosting.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Docc.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/PreviewServer/PreviewHTTPHandler.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/PreviewServer/PreviewServer.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/PreviewServer/RequestHandler/DefaultRequestHandler.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/PreviewServer/RequestHandler/ErrorRequestHandler.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/PreviewServer/RequestHandler/FileRequestHandler.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/PreviewServer/RequestHandler/RequestHandlerFactory.swift (100%) rename Sources/{CommandLine/CommandLine.docc/CommandLine.md => SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md} (54%) rename Sources/{CommandLine/CommandLine.docc/CommandLine => SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities}/Actions/InitAction.md (98%) rename Sources/{CommandLine/CommandLine.docc/CommandLine => SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities}/Extensions/Docc.md (90%) rename Sources/{CommandLine/CommandLine.docc => SwiftDocCUtilities/SwiftDocCUtilities.docc}/footer.html (100%) rename Sources/{CommandLine/CommandLine.docc => SwiftDocCUtilities/SwiftDocCUtilities.docc}/header.html (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Transformers/StaticHostableTransformer.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Utility/DirectoryMonitor.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Utility/FoundationExtensions/Sequence+Unique.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Utility/FoundationExtensions/String+Path.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Utility/FoundationExtensions/URL+Relative.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Utility/PlatformArgumentParser.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Utility/Signal.swift (100%) rename Sources/{CommandLine => SwiftDocCUtilities}/Utility/Throttle.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ArgumentParsing/ConvertSubcommandTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ArgumentParsing/ErrorMessageTests.swift (93%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ArgumentParsing/MergeSubcommandTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ArgumentParsing/PreviewSubcommandTests.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/C+Extensions.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ConvertActionIndexerTests.swift (98%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ConvertActionStaticHostableTests.swift (96%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ConvertActionTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/DirectoryMonitorTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/EmitGeneratedCurationsActionTests.swift (96%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/FolderStructure.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/FolderStructureTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/HTMLTemplateDirectory.swift (98%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/IndexActionTests.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Init/InitActionTests.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/JSONEncodingRenderNodeWriterTests.swift (95%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/MergeActionTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/PlatformArgumentParserTests.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/PreviewActionIntegrationTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/PreviewServer/PreviewHTTPHandlerTests.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift (96%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift (98%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/PreviewServer/RequestHandler/FileRequestHandlerTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/PreviewServer/ServerTestUtils.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ProblemTests.swift (89%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/SemanticAnalyzerTests.swift (98%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ShadowFileManagerTemporaryDirectory.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/SignalTests.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/StaticHostableTransformerTests.swift (98%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/StaticHostingBaseTest.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/MixedLanguageFramework.docc/Info.plist (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/OverloadedSymbols.docc/Info.plist (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/SingleArticleTestBundle.docc/Info.plist (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Bundles/SingleArticleTestBundle.docc/article.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Resources/DeckKit-Objective-C.symbols.json (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Resources/Overview.tutorial (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Resources/Test Template/index.html (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Resources/TopLevelCuration.symbols.json (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Resources/UncuratedArticle.md (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Test Resources/image.png (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/ThrottleTests.swift (92%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/TransformForStaticHostingActionTests.swift (98%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Utility/DirectedGraphTests.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Utility/FileTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Utility/LogHandleTests.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Utility/Sequence+UniqueTests.swift (94%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Utility/TestFileSystemTests.swift (99%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Utility/URL+IsAbsoluteWebURLTests.swift (90%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Utility/URL+RelativeTests.swift (97%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/Utility/XCTestCase+enableFeatureFlag.swift (100%) rename Tests/{CommandLineTests => SwiftDocCUtilitiesTests}/XCTestCase+LoadingData.swift (98%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71eff08cab..da6d76fb8f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -520,7 +520,7 @@ For more in-depth technical information about Swift-DocC, please refer to the project's technical documentation: - [`SwiftDocC` framework documentation](https://swiftlang.github.io/swift-docc/documentation/swiftdocc/) -- [`CommandLine` framework documentation](https://swiftlang.github.io/swift-docc/documentation/commandline/) +- [`SwiftDocCUtilities` framework documentation](https://swiftlang.github.io/swift-docc/documentation/swiftdoccutilities/) ### Related Projects @@ -545,4 +545,4 @@ project's technical documentation: with support for building and viewing documentation for your framework and its dependencies. - + diff --git a/Package.swift b/Package.swift index 544c13b596..9ad6fa6d7a 100644 --- a/Package.swift +++ b/Package.swift @@ -2,7 +2,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -55,7 +55,7 @@ let package = Package( name: "SwiftDocCTests", dependencies: [ .target(name: "SwiftDocC"), - .target(name: "TestUtilities"), + .target(name: "SwiftDocCTestUtilities"), ], resources: [ .copy("Test Resources"), @@ -67,7 +67,7 @@ let package = Package( ), // Command-line tool library .target( - name: "CommandLine", + name: "SwiftDocCUtilities", dependencies: [ .target(name: "SwiftDocC"), .product(name: "NIOHTTP1", package: "swift-nio", condition: .when(platforms: [.macOS, .iOS, .linux, .android])), @@ -77,11 +77,11 @@ let package = Package( swiftSettings: swiftSettings ), .testTarget( - name: "CommandLineTests", + name: "SwiftDocCUtilitiesTests", dependencies: [ - .target(name: "CommandLine"), + .target(name: "SwiftDocCUtilities"), .target(name: "SwiftDocC"), - .target(name: "TestUtilities"), + .target(name: "SwiftDocCTestUtilities"), ], resources: [ .copy("Test Resources"), @@ -92,7 +92,7 @@ let package = Package( // Test utility library .target( - name: "TestUtilities", + name: "SwiftDocCTestUtilities", dependencies: [ .target(name: "SwiftDocC"), .product(name: "SymbolKit", package: "swift-docc-symbolkit"), @@ -104,17 +104,17 @@ let package = Package( .executableTarget( name: "docc", dependencies: [ - .target(name: "CommandLine"), + .target(name: "SwiftDocCUtilities"), ], exclude: ["CMakeLists.txt"], swiftSettings: swiftSettings ), - // Test app for CommandLine + // Test app for SwiftDocCUtilities .executableTarget( name: "signal-test-app", dependencies: [ - .target(name: "CommandLine"), + .target(name: "SwiftDocCUtilities"), ], path: "Tests/signal-test-app", swiftSettings: swiftSettings diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 4362f918d0..8f8e806655 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -8,5 +8,5 @@ See https://swift.org/LICENSE.txt for license information #]] add_subdirectory(SwiftDocC) -add_subdirectory(CommandLine) +add_subdirectory(SwiftDocCUtilities) add_subdirectory(docc) diff --git a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/AddingFeatureFlags.md b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/AddingFeatureFlags.md index 52d7220981..3712ca2aa0 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/AddingFeatureFlags.md +++ b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/AddingFeatureFlags.md @@ -17,12 +17,12 @@ value so that the default initializer can be used. ### Feature flags on the command line -Command-line feature flags live in the `Docc.Convert.FeatureFlagOptions` in `CommandLine`. +Command-line feature flags live in the `Docc.Convert.FeatureFlagOptions` in `SwiftDocCUtilities`. This type implements the `ParsableArguments` protocol from Swift Argument Parser to create an option group for the `convert` and `preview` commands. These options are then handled in `ConvertAction.init(fromConvertCommand:)`, still in -`CommandLine`, where they are written into the global feature flags ``FeatureFlags/current`` +`SwiftDocCUtilities`, where they are written into the global feature flags ``FeatureFlags/current`` instance, which can then be used during the compilation process. ### Feature flags in Info.plist @@ -37,4 +37,4 @@ Feature flags that are loaded from an Info.plist file are saved into the global the bundle is being registered. To ensure that your new feature flag is properly loaded, update the ``FeatureFlags/loadFlagsFromBundle(_:)`` method to load your new field into the global flags. - + diff --git a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/CompilerPipeline.md b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/CompilerPipeline.md index afd5e55ed7..eb63941f1b 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/CompilerPipeline.md +++ b/Sources/SwiftDocC/SwiftDocC.docc/SwiftDocC/CompilerPipeline.md @@ -33,7 +33,7 @@ SwiftDocC.docc ├ Essentials │ ├ ActionManager.md │ ├ Action.md -│ ╰ Getting Started with SwiftDocC.md +│ ╰ Getting Started with SwiftDocCUtilities.md ├ Migration to DocC │ ├ DocumentationContext.md │ ╰ ... @@ -104,4 +104,4 @@ The file hierarchy under the output path represents the complete, compiled docum ╰ videos ``` - + diff --git a/Sources/TestUtilities/FilesAndFolders.swift b/Sources/SwiftDocCTestUtilities/FilesAndFolders.swift similarity index 100% rename from Sources/TestUtilities/FilesAndFolders.swift rename to Sources/SwiftDocCTestUtilities/FilesAndFolders.swift diff --git a/Sources/TestUtilities/SymbolGraphCreation.swift b/Sources/SwiftDocCTestUtilities/SymbolGraphCreation.swift similarity index 100% rename from Sources/TestUtilities/SymbolGraphCreation.swift rename to Sources/SwiftDocCTestUtilities/SymbolGraphCreation.swift diff --git a/Sources/TestUtilities/TestFileSystem.swift b/Sources/SwiftDocCTestUtilities/TestFileSystem.swift similarity index 100% rename from Sources/TestUtilities/TestFileSystem.swift rename to Sources/SwiftDocCTestUtilities/TestFileSystem.swift diff --git a/Sources/TestUtilities/XCTestCase+TemporaryDirectory.swift b/Sources/SwiftDocCTestUtilities/XCTestCase+TemporaryDirectory.swift similarity index 100% rename from Sources/TestUtilities/XCTestCase+TemporaryDirectory.swift rename to Sources/SwiftDocCTestUtilities/XCTestCase+TemporaryDirectory.swift diff --git a/Sources/CommandLine/Action/Action.swift b/Sources/SwiftDocCUtilities/Action/Action.swift similarity index 100% rename from Sources/CommandLine/Action/Action.swift rename to Sources/SwiftDocCUtilities/Action/Action.swift diff --git a/Sources/CommandLine/Action/ActionResult.swift b/Sources/SwiftDocCUtilities/Action/ActionResult.swift similarity index 100% rename from Sources/CommandLine/Action/ActionResult.swift rename to Sources/SwiftDocCUtilities/Action/ActionResult.swift diff --git a/Sources/CommandLine/Action/Actions/Action+MoveOutput.swift b/Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Action+MoveOutput.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift diff --git a/Sources/CommandLine/Action/Actions/Convert/ConvertAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Convert/ConvertAction.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift diff --git a/Sources/CommandLine/Action/Actions/Convert/ConvertFileWritingConsumer.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Convert/ConvertFileWritingConsumer.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift diff --git a/Sources/CommandLine/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Convert/CoverageDataEntry+generateSummary.swift diff --git a/Sources/CommandLine/Action/Actions/Convert/Indexer.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/Indexer.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Convert/Indexer.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Convert/Indexer.swift diff --git a/Sources/CommandLine/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift b/Sources/SwiftDocCUtilities/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift diff --git a/Sources/CommandLine/Action/Actions/CoverageAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/CoverageAction.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/CoverageAction.swift rename to Sources/SwiftDocCUtilities/Action/Actions/CoverageAction.swift diff --git a/Sources/CommandLine/Action/Actions/EmitGeneratedCurationAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/EmitGeneratedCurationAction.swift rename to Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift diff --git a/Sources/CommandLine/Action/Actions/IndexAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/IndexAction.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/IndexAction.swift rename to Sources/SwiftDocCUtilities/Action/Actions/IndexAction.swift diff --git a/Sources/CommandLine/Action/Actions/Init/CatalogTemplate.swift b/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplate.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Init/CatalogTemplate.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplate.swift diff --git a/Sources/CommandLine/Action/Actions/Init/CatalogTemplateKind.swift b/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Init/CatalogTemplateKind.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift diff --git a/Sources/CommandLine/Action/Actions/Init/InitAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Init/InitAction.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Init/InitAction.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Init/InitAction.swift diff --git a/Sources/CommandLine/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift diff --git a/Sources/CommandLine/Action/Actions/Merge/MergeAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/Merge/MergeAction.swift rename to Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift diff --git a/Sources/CommandLine/Action/Actions/PreviewAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/PreviewAction.swift rename to Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift diff --git a/Sources/CommandLine/Action/Actions/TransformForStaticHostingAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/TransformForStaticHostingAction.swift similarity index 100% rename from Sources/CommandLine/Action/Actions/TransformForStaticHostingAction.swift rename to Sources/SwiftDocCUtilities/Action/Actions/TransformForStaticHostingAction.swift diff --git a/Sources/CommandLine/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/Action+performAndHandleResult.swift diff --git a/Sources/CommandLine/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift diff --git a/Sources/CommandLine/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/EmitGeneratedCurationAction+CommandInitialization.swift diff --git a/Sources/CommandLine/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/IndexAction+CommandInitialization.swift diff --git a/Sources/CommandLine/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/InitAction+CommandInitialization.swift diff --git a/Sources/CommandLine/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift diff --git a/Sources/CommandLine/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/TransformForStaticHostingAction+CommandInitialization.swift diff --git a/Sources/CommandLine/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/ArgumentValidation/URLArgumentValidator.swift diff --git a/Sources/CommandLine/ArgumentParsing/Options/DirectoryPathOption.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DirectoryPathOption.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Options/DirectoryPathOption.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Options/DirectoryPathOption.swift diff --git a/Sources/CommandLine/ArgumentParsing/Options/DocumentationArchiveOption.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationArchiveOption.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Options/DocumentationArchiveOption.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationArchiveOption.swift diff --git a/Sources/CommandLine/ArgumentParsing/Options/DocumentationBundleOption.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationBundleOption.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Options/DocumentationBundleOption.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationBundleOption.swift diff --git a/Sources/CommandLine/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift similarity index 97% rename from Sources/CommandLine/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift index 25b5aabbd3..f1812bbfc5 100644 --- a/Sources/CommandLine/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/DocumentationCoverageOptionsArgument.swift @@ -76,7 +76,7 @@ public struct DocumentationCoverageOptionsArgument: ParsableArguments { // // It is safe to add a retroactively conformance here because the other module (SwiftDocC) is in the same package. // -// These conforming types are defined in SwiftDocC and extended in CommandLine, because SwiftDocC doesn't link against ArgumentParser (since it isn't about CLI). +// These conforming types are defined in SwiftDocC and extended in SwiftDocCUtilities, because SwiftDocC doesn't link against ArgumentParse (since it isn't about CLI). // We conform here because this is the first place that we can add the conformance. The implementation is in SwiftDocC. extension SwiftDocC.DocumentationCoverageLevel: ArgumentParser.ExpressibleByArgument {} extension SwiftDocC.DocumentationCoverageOptions.KindFilterOptions.BitFlagRepresentation: ArgumentParser.ExpressibleByArgument {} diff --git a/Sources/CommandLine/ArgumentParsing/Options/InitOptions.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/InitOptions.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Options/InitOptions.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Options/InitOptions.swift diff --git a/Sources/CommandLine/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Options/OutOfProcessLinkResolverOption.swift diff --git a/Sources/CommandLine/ArgumentParsing/Options/PreviewOptions.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Options/PreviewOptions.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift diff --git a/Sources/CommandLine/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Options/Source Repository/SourceRepositoryArguments.swift diff --git a/Sources/CommandLine/ArgumentParsing/Options/TemplateOption.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/TemplateOption.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Options/TemplateOption.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Options/TemplateOption.swift diff --git a/Sources/CommandLine/ArgumentParsing/Subcommands/Convert.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Subcommands/Convert.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift diff --git a/Sources/CommandLine/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/EmitGeneratedCuration.swift diff --git a/Sources/CommandLine/ArgumentParsing/Subcommands/Index.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Index.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Subcommands/Index.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Index.swift diff --git a/Sources/CommandLine/ArgumentParsing/Subcommands/Init.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Init.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Subcommands/Init.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Init.swift diff --git a/Sources/CommandLine/ArgumentParsing/Subcommands/Merge.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Merge.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Subcommands/Merge.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Merge.swift diff --git a/Sources/CommandLine/ArgumentParsing/Subcommands/Preview.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Subcommands/Preview.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift diff --git a/Sources/CommandLine/ArgumentParsing/Subcommands/ProcessArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Subcommands/ProcessArchive.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift diff --git a/Sources/CommandLine/ArgumentParsing/Subcommands/ProcessCatalog.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessCatalog.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Subcommands/ProcessCatalog.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessCatalog.swift diff --git a/Sources/CommandLine/ArgumentParsing/Subcommands/TransformForStaticHosting.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/TransformForStaticHosting.swift similarity index 100% rename from Sources/CommandLine/ArgumentParsing/Subcommands/TransformForStaticHosting.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/TransformForStaticHosting.swift diff --git a/Sources/SwiftDocCUtilities/CMakeLists.txt b/Sources/SwiftDocCUtilities/CMakeLists.txt index a0552f96dc..c419ba4807 100644 --- a/Sources/SwiftDocCUtilities/CMakeLists.txt +++ b/Sources/SwiftDocCUtilities/CMakeLists.txt @@ -7,7 +7,7 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information #]] -add_library(CommandLine STATIC +add_library(SwiftDocCUtilities STATIC Action/Action.swift Action/ActionResult.swift Action/Actions/Action+MoveOutput.swift @@ -69,6 +69,6 @@ add_library(CommandLine STATIC Utility/PlatformArgumentParser.swift Utility/Signal.swift Utility/Throttle.swift) -target_link_libraries(CommandLine PUBLIC +target_link_libraries(SwiftDocCUtilities PUBLIC ArgumentParser SwiftDocC) diff --git a/Sources/CommandLine/Docc.swift b/Sources/SwiftDocCUtilities/Docc.swift similarity index 100% rename from Sources/CommandLine/Docc.swift rename to Sources/SwiftDocCUtilities/Docc.swift diff --git a/Sources/CommandLine/PreviewServer/PreviewHTTPHandler.swift b/Sources/SwiftDocCUtilities/PreviewServer/PreviewHTTPHandler.swift similarity index 100% rename from Sources/CommandLine/PreviewServer/PreviewHTTPHandler.swift rename to Sources/SwiftDocCUtilities/PreviewServer/PreviewHTTPHandler.swift diff --git a/Sources/CommandLine/PreviewServer/PreviewServer.swift b/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift similarity index 100% rename from Sources/CommandLine/PreviewServer/PreviewServer.swift rename to Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift diff --git a/Sources/CommandLine/PreviewServer/RequestHandler/DefaultRequestHandler.swift b/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/DefaultRequestHandler.swift similarity index 100% rename from Sources/CommandLine/PreviewServer/RequestHandler/DefaultRequestHandler.swift rename to Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/DefaultRequestHandler.swift diff --git a/Sources/CommandLine/PreviewServer/RequestHandler/ErrorRequestHandler.swift b/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/ErrorRequestHandler.swift similarity index 100% rename from Sources/CommandLine/PreviewServer/RequestHandler/ErrorRequestHandler.swift rename to Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/ErrorRequestHandler.swift diff --git a/Sources/CommandLine/PreviewServer/RequestHandler/FileRequestHandler.swift b/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/FileRequestHandler.swift similarity index 100% rename from Sources/CommandLine/PreviewServer/RequestHandler/FileRequestHandler.swift rename to Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/FileRequestHandler.swift diff --git a/Sources/CommandLine/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift b/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift similarity index 100% rename from Sources/CommandLine/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift rename to Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/HTTPResponseHead+FromRequest.swift diff --git a/Sources/CommandLine/PreviewServer/RequestHandler/RequestHandlerFactory.swift b/Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/RequestHandlerFactory.swift similarity index 100% rename from Sources/CommandLine/PreviewServer/RequestHandler/RequestHandlerFactory.swift rename to Sources/SwiftDocCUtilities/PreviewServer/RequestHandler/RequestHandlerFactory.swift diff --git a/Sources/CommandLine/CommandLine.docc/CommandLine.md b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md similarity index 54% rename from Sources/CommandLine/CommandLine.docc/CommandLine.md rename to Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md index defdb873b6..0e3a8b4f47 100644 --- a/Sources/CommandLine/CommandLine.docc/CommandLine.md +++ b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities.md @@ -1,12 +1,12 @@ -# ``CommandLine`` +# ``SwiftDocCUtilities`` Build custom documentation workflows by leveraging the DocC compiler pipeline. ## Overview -CommandLine provides a default, command-line workflow for DocC, powered by Swift [Argument Parser](https://apple.github.io/swift-argument-parser/documentation/argumentparser/). `docc` commands, such as `convert` and `preview`, are conformant ``Action`` types that use DocC to perform documentation tasks. +SwiftDocCUtilities provides a default, command-line workflow for DocC, powered by Swift [Argument Parser](https://apple.github.io/swift-argument-parser/documentation/argumentparser/). `docc` commands, such as `convert` and `preview`, are conformant ``Action`` types that use DocC to perform documentation tasks. -Use CommandLine to build a custom, command-line interface and extend it with additional commands. To add a new sub-command called `example`, create a conformant ``Action`` type, `ExampleAction`, that performs the desired work, and add it as a sub-command. Optionally, you can also reuse any of the provided actions like ``ConvertAction``. +Use SwiftDocCUtilities to build a custom, command-line interface and extend it with additional commands. To add a new sub-command called `example`, create a conformant ``Action`` type, `ExampleAction`, that performs the desired work, and add it as a sub-command. Optionally, you can also reuse any of the provided actions like ``ConvertAction``. ```swift public import ArgumentParser @@ -44,4 +44,4 @@ Adding a new sub-command automatically adds routing and execution of its code, a - ``Throttle`` - ``Signal`` - + diff --git a/Sources/CommandLine/CommandLine.docc/CommandLine/Actions/InitAction.md b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Actions/InitAction.md similarity index 98% rename from Sources/CommandLine/CommandLine.docc/CommandLine/Actions/InitAction.md rename to Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Actions/InitAction.md index 70faf36635..be46e30b13 100644 --- a/Sources/CommandLine/CommandLine.docc/CommandLine/Actions/InitAction.md +++ b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Actions/InitAction.md @@ -1,4 +1,4 @@ -# ``InitAction`` +# ``SwiftDocCUtilities/InitAction`` @Metadata { @DocumentationExtension(mergeBehavior: override) diff --git a/Sources/CommandLine/CommandLine.docc/CommandLine/Extensions/Docc.md b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Extensions/Docc.md similarity index 90% rename from Sources/CommandLine/CommandLine.docc/CommandLine/Extensions/Docc.md rename to Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Extensions/Docc.md index 70d767f9d3..f7e904530e 100644 --- a/Sources/CommandLine/CommandLine.docc/CommandLine/Extensions/Docc.md +++ b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/SwiftDocCUtilities/Extensions/Docc.md @@ -1,4 +1,4 @@ -# ``Docc`` +# ``SwiftDocCUtilities/Docc`` ## Topics diff --git a/Sources/CommandLine/CommandLine.docc/footer.html b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/footer.html similarity index 100% rename from Sources/CommandLine/CommandLine.docc/footer.html rename to Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/footer.html diff --git a/Sources/CommandLine/CommandLine.docc/header.html b/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/header.html similarity index 100% rename from Sources/CommandLine/CommandLine.docc/header.html rename to Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc/header.html diff --git a/Sources/CommandLine/Transformers/StaticHostableTransformer.swift b/Sources/SwiftDocCUtilities/Transformers/StaticHostableTransformer.swift similarity index 100% rename from Sources/CommandLine/Transformers/StaticHostableTransformer.swift rename to Sources/SwiftDocCUtilities/Transformers/StaticHostableTransformer.swift diff --git a/Sources/CommandLine/Utility/DirectoryMonitor.swift b/Sources/SwiftDocCUtilities/Utility/DirectoryMonitor.swift similarity index 100% rename from Sources/CommandLine/Utility/DirectoryMonitor.swift rename to Sources/SwiftDocCUtilities/Utility/DirectoryMonitor.swift diff --git a/Sources/CommandLine/Utility/FoundationExtensions/Sequence+Unique.swift b/Sources/SwiftDocCUtilities/Utility/FoundationExtensions/Sequence+Unique.swift similarity index 100% rename from Sources/CommandLine/Utility/FoundationExtensions/Sequence+Unique.swift rename to Sources/SwiftDocCUtilities/Utility/FoundationExtensions/Sequence+Unique.swift diff --git a/Sources/CommandLine/Utility/FoundationExtensions/String+Path.swift b/Sources/SwiftDocCUtilities/Utility/FoundationExtensions/String+Path.swift similarity index 100% rename from Sources/CommandLine/Utility/FoundationExtensions/String+Path.swift rename to Sources/SwiftDocCUtilities/Utility/FoundationExtensions/String+Path.swift diff --git a/Sources/CommandLine/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift b/Sources/SwiftDocCUtilities/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift similarity index 100% rename from Sources/CommandLine/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift rename to Sources/SwiftDocCUtilities/Utility/FoundationExtensions/URL+IsAbsoluteWebURL.swift diff --git a/Sources/CommandLine/Utility/FoundationExtensions/URL+Relative.swift b/Sources/SwiftDocCUtilities/Utility/FoundationExtensions/URL+Relative.swift similarity index 100% rename from Sources/CommandLine/Utility/FoundationExtensions/URL+Relative.swift rename to Sources/SwiftDocCUtilities/Utility/FoundationExtensions/URL+Relative.swift diff --git a/Sources/CommandLine/Utility/PlatformArgumentParser.swift b/Sources/SwiftDocCUtilities/Utility/PlatformArgumentParser.swift similarity index 100% rename from Sources/CommandLine/Utility/PlatformArgumentParser.swift rename to Sources/SwiftDocCUtilities/Utility/PlatformArgumentParser.swift diff --git a/Sources/CommandLine/Utility/Signal.swift b/Sources/SwiftDocCUtilities/Utility/Signal.swift similarity index 100% rename from Sources/CommandLine/Utility/Signal.swift rename to Sources/SwiftDocCUtilities/Utility/Signal.swift diff --git a/Sources/CommandLine/Utility/Throttle.swift b/Sources/SwiftDocCUtilities/Utility/Throttle.swift similarity index 100% rename from Sources/CommandLine/Utility/Throttle.swift rename to Sources/SwiftDocCUtilities/Utility/Throttle.swift diff --git a/Sources/docc/CMakeLists.txt b/Sources/docc/CMakeLists.txt index 722df3a849..4289856b76 100644 --- a/Sources/docc/CMakeLists.txt +++ b/Sources/docc/CMakeLists.txt @@ -10,7 +10,7 @@ See https://swift.org/LICENSE.txt for license information add_executable(docc main.swift) target_link_libraries(docc PRIVATE - CommandLine) + SwiftDocCUtilities) install(TARGETS docc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/Sources/docc/main.swift b/Sources/docc/main.swift index 51180b0335..1715eed8e1 100644 --- a/Sources/docc/main.swift +++ b/Sources/docc/main.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -9,7 +9,7 @@ */ #if os(macOS) || os(Linux) || os(Android) || os(Windows) || os(FreeBSD) -import CommandLine +import SwiftDocCUtilities await Task { await Docc.main() diff --git a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift index 20f219b6f2..1073bc693e 100644 --- a/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift +++ b/Tests/SwiftDocCTests/Checker/Checkers/NonInclusiveLanguageCheckerTests.swift @@ -11,7 +11,7 @@ import XCTest import Markdown @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class NonInclusiveLanguageCheckerTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift index b92fdff6c4..8cfbad65db 100644 --- a/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift +++ b/Tests/SwiftDocCTests/DeprecatedDiagnosticsDigestWarningTests.swift @@ -10,7 +10,7 @@ import Foundation import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities import XCTest // THIS SHOULD BE REMOVED, RIGHT?! diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift index e5042b0536..6943f58bc6 100644 --- a/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticConsoleWriterDefaultFormattingTest.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2024-2025 Apple Inc. and the Swift project authors + Copyright (c) 2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -11,7 +11,7 @@ import XCTest import Markdown @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class DiagnosticConsoleWriterDefaultFormattingTest: XCTestCase { diff --git a/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift b/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift index fd94e38c82..f22e888977 100644 --- a/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift +++ b/Tests/SwiftDocCTests/Diagnostics/DiagnosticTests.swift @@ -12,7 +12,7 @@ import XCTest @testable import SwiftDocC import Markdown @testable import SymbolKit -import TestUtilities +import SwiftDocCTestUtilities class DiagnosticTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift index 9df8b1e100..a46afb677e 100644 --- a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift @@ -12,7 +12,7 @@ import XCTest import Foundation @testable import SwiftDocC import SymbolKit -import TestUtilities +import SwiftDocCTestUtilities class ConvertServiceTests: XCTestCase { private let testBundleInfo = DocumentationBundle.Info( diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index 9335fe2bd4..1d5ed2b1cc 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @_spi(ExternalLinks) @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class ExternalRenderNodeTests: XCTestCase { private func generateExternalResolver() -> TestMultiResultExternalReferenceResolver { diff --git a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift index 6f62a77450..16c63bede5 100644 --- a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities typealias Node = NavigatorTree.Node typealias PageType = NavigatorIndex.PageType diff --git a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift index 8f748d2cdf..d2682c360d 100644 --- a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import TestUtilities +import SwiftDocCTestUtilities @testable import SwiftDocC diff --git a/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift b/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift index e60f677da9..f45a2d7119 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AutoCapitalizationTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class AutoCapitalizationTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift index 2f8df44a62..d214b77338 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class AutomaticCurationTests: XCTestCase { private let (availableExtensionSymbolKinds, availableNonExtensionSymbolKinds) = Set(AutomaticCuration.groupKindOrder).union(SymbolGraph.Symbol.KindIdentifier.allCases) diff --git a/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift b/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift index b0493e744c..323fe5a2c5 100644 --- a/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class BundleDiscoveryTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift index b068787d18..9523dc0966 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContext+RootPageTests.swift @@ -11,7 +11,7 @@ import XCTest import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class DocumentationContext_RootPageTests: XCTestCase { func testArticleOnlyCatalogWithExplicitTechnologyRoot() async throws { diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index c037afb9a4..e788f1b192 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -12,7 +12,7 @@ import XCTest import SymbolKit @testable @_spi(ExternalLinks) import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities func diffDescription(lhs: String, rhs: String) -> String { let leftLines = lhs.components(separatedBy: .newlines) diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift index ce1ab9ce2b..cab832ec10 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift @@ -13,7 +13,7 @@ import Foundation import XCTest import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities import Markdown class DocumentationCuratorTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift index adc94f2251..0d0bab01b5 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift @@ -12,7 +12,7 @@ import XCTest import Markdown import SymbolKit @testable @_spi(ExternalLinks) import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class ExternalPathHierarchyResolverTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 3101ec444c..803868d2ad 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -12,7 +12,7 @@ import XCTest @_spi(ExternalLinks) @testable import SwiftDocC import Markdown import SymbolKit -import TestUtilities +import SwiftDocCTestUtilities class ExternalReferenceResolverTests: XCTestCase { class TestExternalReferenceResolver: ExternalDocumentationSource { diff --git a/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift b/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift index 2bd1f21ceb..2d6edd7e57 100644 --- a/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import TestUtilities +import SwiftDocCTestUtilities @testable import SwiftDocC class DocumentationInputsProviderTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift b/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift index 28bc0caa6f..4a34e693ca 100644 --- a/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/NodeTagsTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class NodeTagsTests: XCTestCase { func testSPIMetadata() async throws { diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index 305bc6dc9a..c7c54f5495 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -11,7 +11,7 @@ import XCTest import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities import Markdown class PathHierarchyTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift index 5ee3c9ff1d..84e39551d5 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SnippetResolverTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import SymbolKit -import TestUtilities +import SwiftDocCTestUtilities class SnippetResolverTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift index 6d235652bf..39ad7e8482 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class SymbolGraphLoaderTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift index 6384fda46e..49d4985b93 100644 --- a/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/SymbolReferenceTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class SymbolReferenceTests: XCTestCase { func testUsesIdentifierForUnresolvedSymbols() { diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 029fe68c33..5ac4655d92 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -11,7 +11,7 @@ import XCTest import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class LinkDestinationSummaryTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift b/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift index 93f560e259..c63d951a60 100644 --- a/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift +++ b/Tests/SwiftDocCTests/Model/LineHighlighterTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class LineHighlighterTests: XCTestCase { static let bundleID: DocumentationBundle.Identifier = "org.swift.docc.LineHighlighterTests" diff --git a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift index eb9b2d5fd4..912ac6fdb3 100644 --- a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift +++ b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift @@ -13,7 +13,7 @@ import XCTest import Markdown @testable import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class ParametersAndReturnValidatorTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift b/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift index 2e9869da7a..2a5ddab8bf 100644 --- a/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift +++ b/Tests/SwiftDocCTests/Model/PropertyListPossibleValuesSectionTests.swift @@ -12,7 +12,7 @@ import XCTest import SymbolKit import Foundation @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class PropertyListPossibleValuesSectionTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift index 225e5cccc0..d3a3fc2446 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift @@ -11,7 +11,7 @@ import Foundation @testable import SwiftDocC import SymbolKit -import TestUtilities +import SwiftDocCTestUtilities import XCTest class SemaToRenderNodeMixedLanguageTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index f6a309beed..73b4a508ed 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -12,7 +12,7 @@ import Markdown import XCTest import SymbolKit -import TestUtilities +import SwiftDocCTestUtilities class SemaToRenderNodeTests: XCTestCase { func testCompileTutorial() async throws { diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift index 4140da55ed..a4640f8b2f 100644 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV1Tests.swift @@ -12,7 +12,7 @@ import XCTest import Foundation import SymbolKit @_spi(ExternalLinks) @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities // This tests the deprecated V1 implementation of `OutOfProcessReferenceResolver`. // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. diff --git a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift index d6ce70ddb7..e4e42b500a 100644 --- a/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift +++ b/Tests/SwiftDocCTests/OutOfProcessReferenceResolverV2Tests.swift @@ -12,7 +12,7 @@ import XCTest import Foundation import SymbolKit @_spi(ExternalLinks) @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities #if os(macOS) class OutOfProcessReferenceResolverV2Tests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift b/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift index dd44559f63..4c264ec3e7 100644 --- a/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift +++ b/Tests/SwiftDocCTests/Rendering/AutomaticSeeAlsoTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class AutomaticSeeAlsoTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift index 0350f4cad2..19bd1673ac 100644 --- a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities import SymbolKit fileprivate let jsonDecoder = JSONDecoder() diff --git a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift index 847872ed85..24e21475fa 100644 --- a/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities import SymbolKit class DeclarationsRenderSectionTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift index 37a9ab94f9..200fd3073b 100644 --- a/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DefaultAvailabilityTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class DefaultAvailabilityTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift b/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift index 176f776c4e..002f93d25a 100644 --- a/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DefaultCodeListingSyntaxTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class DefaultCodeBlockSyntaxTests: XCTestCase { func testCodeBlockWithoutAnyLanguageOrDefault() async throws { diff --git a/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift b/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift index 35f3515ac4..778e190577 100644 --- a/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/HeadingAnchorTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class HeadingAnchorTests: XCTestCase { func testEncodeHeadingAnchor() async throws { diff --git a/Tests/SwiftDocCTests/Rendering/PlistSymbolTests.swift b/Tests/SwiftDocCTests/Rendering/PlistSymbolTests.swift index 1c13a18678..15b8c44978 100644 --- a/Tests/SwiftDocCTests/Rendering/PlistSymbolTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PlistSymbolTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -11,7 +11,7 @@ import Foundation import XCTest @testable public import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class PlistSymbolTests: XCTestCase { private let plistSymbolURL = Bundle.module.url( diff --git a/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift index 90137262f7..a4790d4d28 100644 --- a/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/PropertyListDetailsRenderSectionTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SwiftDocC import SymbolKit -import TestUtilities +import SwiftDocCTestUtilities class PropertyListDetailsRenderSectionTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift b/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift index ca982b3f6b..9de46ea5c3 100644 --- a/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RESTSymbolsTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities import SymbolKit fileprivate extension [RenderBlockContent] { diff --git a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift index 0349b25ec7..29b3728990 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderContentCompilerTests.swift @@ -11,7 +11,7 @@ import Foundation import Markdown @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities import XCTest typealias Position = RenderBlockContent.CodeBlockOptions.Position diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift index 0aa591d6fa..1cb095f54a 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift @@ -13,7 +13,7 @@ import XCTest import SymbolKit import Markdown @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift index e9b1bdaff1..be2a95ea56 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift @@ -11,7 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities import Markdown import SymbolKit diff --git a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift index d70241cba9..c3ea5e5ab5 100644 --- a/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift +++ b/Tests/SwiftDocCTests/Rendering/SymbolAvailabilityTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest import SymbolKit @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class SymbolAvailabilityTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Rendering/TermListTests.swift b/Tests/SwiftDocCTests/Rendering/TermListTests.swift index c2c1696b82..d5e496456c 100644 --- a/Tests/SwiftDocCTests/Rendering/TermListTests.swift +++ b/Tests/SwiftDocCTests/Rendering/TermListTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest import Markdown @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class TermListTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift b/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift index 4a489dc12a..856a3908be 100644 --- a/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ArticleSymbolMentionsTests.swift @@ -11,7 +11,7 @@ import XCTest @testable @preconcurrency import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities import SymbolKit class ArticleSymbolMentionsTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift b/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift index 1587ad1c54..7be45b6176 100644 --- a/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift +++ b/Tests/SwiftDocCTests/Semantics/ChoiceTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class ChoiceTests: XCTestCase { func testInvalidEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift b/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift index 9aa4cca0ad..9832537c9e 100644 --- a/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift +++ b/Tests/SwiftDocCTests/Semantics/DoxygenTests.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities @testable import SymbolKit class DoxygenTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift index 16cfa20232..97f3b48ab2 100644 --- a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class MarkupReferenceResolverTests: XCTestCase { func testArbitraryReferenceInComment() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift b/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift index 4664829965..042cad6bd8 100644 --- a/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MultipleChoiceTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class MultipleChoiceTests: XCTestCase { func testInvalidEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/StackTests.swift b/Tests/SwiftDocCTests/Semantics/StackTests.swift index 6f7275889a..721db9aa3f 100644 --- a/Tests/SwiftDocCTests/Semantics/StackTests.swift +++ b/Tests/SwiftDocCTests/Semantics/StackTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class StackTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/StepTests.swift b/Tests/SwiftDocCTests/Semantics/StepTests.swift index 3f66fbbfcb..d8a1b576df 100644 --- a/Tests/SwiftDocCTests/Semantics/StepTests.swift +++ b/Tests/SwiftDocCTests/Semantics/StepTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class StepTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift index 4798c6e78d..063c8c997d 100644 --- a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift @@ -12,7 +12,7 @@ import XCTest @testable import SymbolKit @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class SymbolTests: XCTestCase { diff --git a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift index de064d8895..086f0a7488 100644 --- a/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TutorialArticleTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class TutorialArticleTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/TutorialTests.swift b/Tests/SwiftDocCTests/Semantics/TutorialTests.swift index 767ed5f706..33683991a4 100644 --- a/Tests/SwiftDocCTests/Semantics/TutorialTests.swift +++ b/Tests/SwiftDocCTests/Semantics/TutorialTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class TutorialTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift b/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift index 99708fd1c1..3fb78b1444 100644 --- a/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift +++ b/Tests/SwiftDocCTests/Semantics/VideoMediaTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class VideoMediaTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Semantics/VolumeTests.swift b/Tests/SwiftDocCTests/Semantics/VolumeTests.swift index 2b5c258899..827ab38f7d 100644 --- a/Tests/SwiftDocCTests/Semantics/VolumeTests.swift +++ b/Tests/SwiftDocCTests/Semantics/VolumeTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities class VolumeTests: XCTestCase { func testEmpty() async throws { diff --git a/Tests/SwiftDocCTests/Utility/LMDBTests.swift b/Tests/SwiftDocCTests/Utility/LMDBTests.swift index 06a6c514b5..dd50b29604 100644 --- a/Tests/SwiftDocCTests/Utility/LMDBTests.swift +++ b/Tests/SwiftDocCTests/Utility/LMDBTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities final class SwiftLMDBTests: XCTestCase { var environment: LMDB.Environment! diff --git a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift index ceb47c7213..8639fd62a8 100644 --- a/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift +++ b/Tests/SwiftDocCTests/Utility/ListItemExtractorTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import TestUtilities +import SwiftDocCTestUtilities @testable import SwiftDocC import Markdown diff --git a/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift b/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift index 939e79c82a..a7306396b9 100644 --- a/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift +++ b/Tests/SwiftDocCTests/Utility/XCTestCase+MentionedIn.swift @@ -10,7 +10,7 @@ @testable import SwiftDocC import XCTest -import TestUtilities +import SwiftDocCTestUtilities import SymbolKit extension XCTestCase { diff --git a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift index 203311214a..4103f4f92b 100644 --- a/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift +++ b/Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift @@ -12,7 +12,7 @@ import Foundation import XCTest @testable import SwiftDocC import Markdown -import TestUtilities +import SwiftDocCTestUtilities extension XCTestCase { diff --git a/Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift similarity index 97% rename from Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift rename to Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift index e77b39a535..654006839c 100644 --- a/Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandSourceRepositoryTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022-2025 Apple Inc. and the Swift project authors + Copyright (c) 2022 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 @@ -9,9 +9,9 @@ */ import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities import ArgumentParser class ConvertSubcommandSourceRepositoryTests: XCTestCase { diff --git a/Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandTests.swift b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift similarity index 99% rename from Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandTests.swift rename to Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift index 69f4c7373b..41027fbba1 100644 --- a/Tests/CommandLineTests/ArgumentParsing/ConvertSubcommandTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ConvertSubcommandTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -9,9 +9,9 @@ */ import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities @testable import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities class ConvertSubcommandTests: XCTestCase { private let testBundleURL = Bundle.module.url( diff --git a/Tests/CommandLineTests/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift similarity index 100% rename from Tests/CommandLineTests/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift rename to Tests/SwiftDocCUtilitiesTests/ArgumentParsing/DocumentationCoverageKindFilterOptionsTests.swift diff --git a/Tests/CommandLineTests/ArgumentParsing/ErrorMessageTests.swift b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ErrorMessageTests.swift similarity index 93% rename from Tests/CommandLineTests/ArgumentParsing/ErrorMessageTests.swift rename to Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ErrorMessageTests.swift index 2a331cb9e7..335ac9f3d5 100644 --- a/Tests/CommandLineTests/ArgumentParsing/ErrorMessageTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/ErrorMessageTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -10,7 +10,7 @@ import XCTest import Foundation -@testable import CommandLine +@testable import SwiftDocCUtilities class ErrorMessageTests: XCTestCase { diff --git a/Tests/CommandLineTests/ArgumentParsing/MergeSubcommandTests.swift b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.swift similarity index 99% rename from Tests/CommandLineTests/ArgumentParsing/MergeSubcommandTests.swift rename to Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.swift index d073d6b34e..85731b8e9c 100644 --- a/Tests/CommandLineTests/ArgumentParsing/MergeSubcommandTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/MergeSubcommandTests.swift @@ -10,8 +10,8 @@ import XCTest import ArgumentParser -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities class MergeSubcommandTests: XCTestCase { func testCommandLineArgumentValidation() throws { diff --git a/Tests/CommandLineTests/ArgumentParsing/PreviewSubcommandTests.swift b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift similarity index 97% rename from Tests/CommandLineTests/ArgumentParsing/PreviewSubcommandTests.swift rename to Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift index d475294290..1f586324d3 100644 --- a/Tests/CommandLineTests/ArgumentParsing/PreviewSubcommandTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -10,7 +10,7 @@ #if canImport(NIOHTTP1) import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities class PreviewSubcommandTests: XCTestCase { func testOptionsValidation() throws { diff --git a/Tests/CommandLineTests/C+Extensions.swift b/Tests/SwiftDocCUtilitiesTests/C+Extensions.swift similarity index 100% rename from Tests/CommandLineTests/C+Extensions.swift rename to Tests/SwiftDocCUtilitiesTests/C+Extensions.swift diff --git a/Tests/CommandLineTests/ConvertActionIndexerTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift similarity index 98% rename from Tests/CommandLineTests/ConvertActionIndexerTests.swift rename to Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift index e28af46ab5..4d3db2bdbf 100644 --- a/Tests/CommandLineTests/ConvertActionIndexerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionIndexerTests.swift @@ -11,7 +11,7 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import CommandLine +@testable import SwiftDocCUtilities class ConvertActionIndexerTests: XCTestCase { diff --git a/Tests/CommandLineTests/ConvertActionStaticHostableTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift similarity index 96% rename from Tests/CommandLineTests/ConvertActionStaticHostableTests.swift rename to Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift index be300dc869..0c2c0498b1 100644 --- a/Tests/CommandLineTests/ConvertActionStaticHostableTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionStaticHostableTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -11,8 +11,8 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities class ConvertActionStaticHostableTests: StaticHostingBaseTests { /// Creates a DocC archive and then archives it with options to produce static content which is then validated. diff --git a/Tests/CommandLineTests/ConvertActionTests.swift b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift similarity index 99% rename from Tests/CommandLineTests/ConvertActionTests.swift rename to Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift index ea32665a99..a4f76bcd53 100644 --- a/Tests/CommandLineTests/ConvertActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift @@ -11,10 +11,10 @@ import XCTest import Foundation @testable @_spi(ExternalLinks) import SwiftDocC -@testable import CommandLine +@testable import SwiftDocCUtilities import SymbolKit import Markdown -@testable import TestUtilities +@testable import SwiftDocCTestUtilities class ConvertActionTests: XCTestCase { #if !os(iOS) diff --git a/Tests/CommandLineTests/DirectoryMonitorTests.swift b/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift similarity index 99% rename from Tests/CommandLineTests/DirectoryMonitorTests.swift rename to Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift index e7b5813765..555d258222 100644 --- a/Tests/CommandLineTests/DirectoryMonitorTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift @@ -9,7 +9,7 @@ */ import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities #if !os(Linux) && !os(Android) && !os(Windows) && !os(FreeBSD) fileprivate extension NSNotification.Name { diff --git a/Tests/CommandLineTests/EmitGeneratedCurationsActionTests.swift b/Tests/SwiftDocCUtilitiesTests/EmitGeneratedCurationsActionTests.swift similarity index 96% rename from Tests/CommandLineTests/EmitGeneratedCurationsActionTests.swift rename to Tests/SwiftDocCUtilitiesTests/EmitGeneratedCurationsActionTests.swift index 60fc7d3339..4a07a85ae4 100644 --- a/Tests/CommandLineTests/EmitGeneratedCurationsActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/EmitGeneratedCurationsActionTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2024-2025 Apple Inc. and the Swift project authors + Copyright (c) 2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -10,8 +10,8 @@ import XCTest import Foundation -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities class EmitGeneratedCurationsActionTests: XCTestCase { diff --git a/Tests/CommandLineTests/FolderStructure.swift b/Tests/SwiftDocCUtilitiesTests/FolderStructure.swift similarity index 97% rename from Tests/CommandLineTests/FolderStructure.swift rename to Tests/SwiftDocCUtilitiesTests/FolderStructure.swift index 4d8f649b5b..95a36997ae 100644 --- a/Tests/CommandLineTests/FolderStructure.swift +++ b/Tests/SwiftDocCUtilitiesTests/FolderStructure.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -9,9 +9,9 @@ */ @testable import SwiftDocC -@testable import CommandLine +@testable import SwiftDocCUtilities import XCTest -public import TestUtilities +public import SwiftDocCTestUtilities /* This file contains a test helper API for working with folder hierarchies, with the ability to: diff --git a/Tests/CommandLineTests/FolderStructureTests.swift b/Tests/SwiftDocCUtilitiesTests/FolderStructureTests.swift similarity index 99% rename from Tests/CommandLineTests/FolderStructureTests.swift rename to Tests/SwiftDocCUtilitiesTests/FolderStructureTests.swift index e809a9cd71..148da86252 100644 --- a/Tests/CommandLineTests/FolderStructureTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/FolderStructureTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import TestUtilities +import SwiftDocCTestUtilities class FolderStructureTests: XCTestCase { diff --git a/Tests/CommandLineTests/HTMLTemplateDirectory.swift b/Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.swift similarity index 98% rename from Tests/CommandLineTests/HTMLTemplateDirectory.swift rename to Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.swift index f378ac0fa2..e3f25527c0 100644 --- a/Tests/CommandLineTests/HTMLTemplateDirectory.swift +++ b/Tests/SwiftDocCUtilitiesTests/HTMLTemplateDirectory.swift @@ -9,7 +9,7 @@ */ import Foundation -import TestUtilities +import SwiftDocCTestUtilities /// A folder that represents a fake html-build directory for testing. extension Folder { diff --git a/Tests/CommandLineTests/IndexActionTests.swift b/Tests/SwiftDocCUtilitiesTests/IndexActionTests.swift similarity index 97% rename from Tests/CommandLineTests/IndexActionTests.swift rename to Tests/SwiftDocCUtilitiesTests/IndexActionTests.swift index 76414e35d4..d118aa6593 100644 --- a/Tests/CommandLineTests/IndexActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/IndexActionTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -11,9 +11,9 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import CommandLine +@testable import SwiftDocCUtilities import Markdown -import TestUtilities +import SwiftDocCTestUtilities class IndexActionTests: XCTestCase { #if !os(iOS) diff --git a/Tests/CommandLineTests/Init/InitActionTests.swift b/Tests/SwiftDocCUtilitiesTests/Init/InitActionTests.swift similarity index 97% rename from Tests/CommandLineTests/Init/InitActionTests.swift rename to Tests/SwiftDocCUtilitiesTests/Init/InitActionTests.swift index acb5a22333..b0cb85e9cc 100644 --- a/Tests/CommandLineTests/Init/InitActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/Init/InitActionTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2024-2025 Apple Inc. and the Swift project authors + Copyright (c) 2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -10,8 +10,8 @@ import XCTest import Foundation -import TestUtilities -@testable import CommandLine +import SwiftDocCTestUtilities +@testable import SwiftDocCUtilities final class InitActionTests: XCTestCase { private let documentationTitle = "MyTestDocumentation" diff --git a/Tests/CommandLineTests/JSONEncodingRenderNodeWriterTests.swift b/Tests/SwiftDocCUtilitiesTests/JSONEncodingRenderNodeWriterTests.swift similarity index 95% rename from Tests/CommandLineTests/JSONEncodingRenderNodeWriterTests.swift rename to Tests/SwiftDocCUtilitiesTests/JSONEncodingRenderNodeWriterTests.swift index 156c1ae626..4f31b23adf 100644 --- a/Tests/CommandLineTests/JSONEncodingRenderNodeWriterTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/JSONEncodingRenderNodeWriterTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -10,7 +10,7 @@ import XCTest import SwiftDocC -@testable import CommandLine +@testable import SwiftDocCUtilities class JSONEncodingRenderNodeWriterTests: XCTestCase { /// Verifies that if we fail during writing a JSON file the execution diff --git a/Tests/CommandLineTests/MergeActionTests.swift b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift similarity index 99% rename from Tests/CommandLineTests/MergeActionTests.swift rename to Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift index 6dd906bd3f..88acc2a5c2 100644 --- a/Tests/CommandLineTests/MergeActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift @@ -10,8 +10,8 @@ import XCTest @testable import SwiftDocC -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities class MergeActionTests: XCTestCase { diff --git a/Tests/CommandLineTests/PlatformArgumentParserTests.swift b/Tests/SwiftDocCUtilitiesTests/PlatformArgumentParserTests.swift similarity index 97% rename from Tests/CommandLineTests/PlatformArgumentParserTests.swift rename to Tests/SwiftDocCUtilitiesTests/PlatformArgumentParserTests.swift index f3b54898a9..5a2386ef89 100644 --- a/Tests/CommandLineTests/PlatformArgumentParserTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PlatformArgumentParserTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -12,7 +12,7 @@ import Foundation import SwiftDocC import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities class PlatformArgumentParserTests: XCTestCase { let correctInputs: [[String]] = [ diff --git a/Tests/CommandLineTests/PreviewActionIntegrationTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift similarity index 99% rename from Tests/CommandLineTests/PreviewActionIntegrationTests.swift rename to Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift index 1493a3cb29..bc23ac7575 100644 --- a/Tests/CommandLineTests/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-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -11,8 +11,8 @@ #if canImport(NIOHTTP1) import XCTest @testable import SwiftDocC -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities class PreviewActionIntegrationTests: XCTestCase { private func createMinimalDocsBundle() -> Folder { diff --git a/Tests/CommandLineTests/PreviewServer/PreviewHTTPHandlerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift similarity index 97% rename from Tests/CommandLineTests/PreviewServer/PreviewHTTPHandlerTests.swift rename to Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift index ac2267f053..95878b0e7f 100644 --- a/Tests/CommandLineTests/PreviewServer/PreviewHTTPHandlerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift @@ -11,8 +11,8 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities import NIO import NIOHTTP1 diff --git a/Tests/CommandLineTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift similarity index 96% rename from Tests/CommandLineTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift rename to Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift index 3bbbbd973f..634ba02b9e 100644 --- a/Tests/CommandLineTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/DefaultRequestHandlerTests.swift @@ -11,8 +11,8 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities import NIO import NIOHTTP1 diff --git a/Tests/CommandLineTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift similarity index 98% rename from Tests/CommandLineTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift rename to Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift index 7a601f26d1..552b51e715 100644 --- a/Tests/CommandLineTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/ErrorRequestHandlerTests.swift @@ -11,7 +11,7 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities import NIO import NIOHTTP1 diff --git a/Tests/CommandLineTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift similarity index 99% rename from Tests/CommandLineTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift rename to Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift index c08e0d2887..9d4fefa8e2 100644 --- a/Tests/CommandLineTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/RequestHandler/FileRequestHandlerTests.swift @@ -11,8 +11,8 @@ #if canImport(NIOHTTP1) import Foundation import XCTest -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities import NIO import NIOHTTP1 diff --git a/Tests/CommandLineTests/PreviewServer/ServerTestUtils.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/ServerTestUtils.swift similarity index 97% rename from Tests/CommandLineTests/PreviewServer/ServerTestUtils.swift rename to Tests/SwiftDocCUtilitiesTests/PreviewServer/ServerTestUtils.swift index 9f48813df5..14f60ed6a4 100644 --- a/Tests/CommandLineTests/PreviewServer/ServerTestUtils.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/ServerTestUtils.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -13,7 +13,7 @@ import Foundation import NIO import NIOHTTP1 import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities /// Makes a request head part with the given URI and headers. func makeRequestHead(uri: String, headers: [(String, String)]? = nil) -> HTTPRequestHead { diff --git a/Tests/CommandLineTests/ProblemTests.swift b/Tests/SwiftDocCUtilitiesTests/ProblemTests.swift similarity index 89% rename from Tests/CommandLineTests/ProblemTests.swift rename to Tests/SwiftDocCUtilitiesTests/ProblemTests.swift index 196fe92d20..f5f18a41be 100644 --- a/Tests/CommandLineTests/ProblemTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ProblemTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -9,7 +9,7 @@ */ import XCTest -import CommandLine +import SwiftDocCUtilities @testable import SwiftDocC import Markdown diff --git a/Tests/CommandLineTests/SemanticAnalyzerTests.swift b/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift similarity index 98% rename from Tests/CommandLineTests/SemanticAnalyzerTests.swift rename to Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift index bdff305818..034b935152 100644 --- a/Tests/CommandLineTests/SemanticAnalyzerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/SemanticAnalyzerTests.swift @@ -11,8 +11,8 @@ import XCTest import Markdown @testable import SwiftDocC -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities class SemanticAnalyzerTests: XCTestCase { private let catalogHierarchy = Folder(name: "SemanticAnalyzerTests.docc", content: [ diff --git a/Tests/CommandLineTests/ShadowFileManagerTemporaryDirectory.swift b/Tests/SwiftDocCUtilitiesTests/ShadowFileManagerTemporaryDirectory.swift similarity index 100% rename from Tests/CommandLineTests/ShadowFileManagerTemporaryDirectory.swift rename to Tests/SwiftDocCUtilitiesTests/ShadowFileManagerTemporaryDirectory.swift diff --git a/Tests/CommandLineTests/SignalTests.swift b/Tests/SwiftDocCUtilitiesTests/SignalTests.swift similarity index 100% rename from Tests/CommandLineTests/SignalTests.swift rename to Tests/SwiftDocCUtilitiesTests/SignalTests.swift diff --git a/Tests/CommandLineTests/StaticHostableTransformerTests.swift b/Tests/SwiftDocCUtilitiesTests/StaticHostableTransformerTests.swift similarity index 98% rename from Tests/CommandLineTests/StaticHostableTransformerTests.swift rename to Tests/SwiftDocCUtilitiesTests/StaticHostableTransformerTests.swift index a1d17aec07..0fa8c102e6 100644 --- a/Tests/CommandLineTests/StaticHostableTransformerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/StaticHostableTransformerTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -11,8 +11,8 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities class StaticHostableTransformerTests: StaticHostingBaseTests { diff --git a/Tests/CommandLineTests/StaticHostingBaseTest.swift b/Tests/SwiftDocCUtilitiesTests/StaticHostingBaseTest.swift similarity index 100% rename from Tests/CommandLineTests/StaticHostingBaseTest.swift rename to Tests/SwiftDocCUtilitiesTests/StaticHostingBaseTest.swift diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Default Code Listing Syntax.md diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/FillIntroduced.symbols.json diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/Info.plist diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/MyKit@SideKit.symbols.json diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestOverview.tutorial diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial.tutorial diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorial2.tutorial diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TestTutorialArticle.tutorial diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/TutorialMediaWithSpaces.tutorial diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article.md diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article2.md diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/article3.md diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myclass.md diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/mykit.md diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/myprotocol.md diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sideclass-init.md diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/documentation/sidekit.md diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1.png diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/figure1~dark.png diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld.swift diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld1.swift diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld2.swift diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld3.swift diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/helloworld4.swift diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/intro.png diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter.png diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introposter2.png diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo.mp4 diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/introvideo~dark.mp4 diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/mykit-iOS.symbols.json diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/project.zip diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/sidekit.symbols.json diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/something@2x.png diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/step.png diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2up.png diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/titled2upCapital.PNG diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.mp4 diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces.png diff --git a/Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png b/Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png similarity index 100% rename from Tests/CommandLineTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/LegacyBundle_DoNotUseInNewTests.docc/with spaces@2x.png diff --git a/Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/Info.plist b/Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/Info.plist similarity index 100% rename from Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/Info.plist rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/Info.plist diff --git a/Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json b/Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json similarity index 100% rename from Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/clang/MixedLanguageFramework.symbols.json diff --git a/Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json b/Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json similarity index 100% rename from Tests/CommandLineTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json diff --git a/Tests/CommandLineTests/Test Bundles/OverloadedSymbols.docc/Info.plist b/Tests/SwiftDocCUtilitiesTests/Test Bundles/OverloadedSymbols.docc/Info.plist similarity index 100% rename from Tests/CommandLineTests/Test Bundles/OverloadedSymbols.docc/Info.plist rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/OverloadedSymbols.docc/Info.plist diff --git a/Tests/CommandLineTests/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json b/Tests/SwiftDocCUtilitiesTests/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json similarity index 100% rename from Tests/CommandLineTests/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/OverloadedSymbols.docc/ShapeKit.symbols.json diff --git a/Tests/CommandLineTests/Test Bundles/SingleArticleTestBundle.docc/Info.plist b/Tests/SwiftDocCUtilitiesTests/Test Bundles/SingleArticleTestBundle.docc/Info.plist similarity index 100% rename from Tests/CommandLineTests/Test Bundles/SingleArticleTestBundle.docc/Info.plist rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/SingleArticleTestBundle.docc/Info.plist diff --git a/Tests/CommandLineTests/Test Bundles/SingleArticleTestBundle.docc/article.md b/Tests/SwiftDocCUtilitiesTests/Test Bundles/SingleArticleTestBundle.docc/article.md similarity index 100% rename from Tests/CommandLineTests/Test Bundles/SingleArticleTestBundle.docc/article.md rename to Tests/SwiftDocCUtilitiesTests/Test Bundles/SingleArticleTestBundle.docc/article.md diff --git a/Tests/CommandLineTests/Test Resources/DeckKit-Objective-C.symbols.json b/Tests/SwiftDocCUtilitiesTests/Test Resources/DeckKit-Objective-C.symbols.json similarity index 100% rename from Tests/CommandLineTests/Test Resources/DeckKit-Objective-C.symbols.json rename to Tests/SwiftDocCUtilitiesTests/Test Resources/DeckKit-Objective-C.symbols.json diff --git a/Tests/CommandLineTests/Test Resources/Overview.tutorial b/Tests/SwiftDocCUtilitiesTests/Test Resources/Overview.tutorial similarity index 100% rename from Tests/CommandLineTests/Test Resources/Overview.tutorial rename to Tests/SwiftDocCUtilitiesTests/Test Resources/Overview.tutorial diff --git a/Tests/CommandLineTests/Test Resources/Test Template/index.html b/Tests/SwiftDocCUtilitiesTests/Test Resources/Test Template/index.html similarity index 100% rename from Tests/CommandLineTests/Test Resources/Test Template/index.html rename to Tests/SwiftDocCUtilitiesTests/Test Resources/Test Template/index.html diff --git a/Tests/CommandLineTests/Test Resources/TopLevelCuration.symbols.json b/Tests/SwiftDocCUtilitiesTests/Test Resources/TopLevelCuration.symbols.json similarity index 100% rename from Tests/CommandLineTests/Test Resources/TopLevelCuration.symbols.json rename to Tests/SwiftDocCUtilitiesTests/Test Resources/TopLevelCuration.symbols.json diff --git a/Tests/CommandLineTests/Test Resources/UncuratedArticle.md b/Tests/SwiftDocCUtilitiesTests/Test Resources/UncuratedArticle.md similarity index 100% rename from Tests/CommandLineTests/Test Resources/UncuratedArticle.md rename to Tests/SwiftDocCUtilitiesTests/Test Resources/UncuratedArticle.md diff --git a/Tests/CommandLineTests/Test Resources/image.png b/Tests/SwiftDocCUtilitiesTests/Test Resources/image.png similarity index 100% rename from Tests/CommandLineTests/Test Resources/image.png rename to Tests/SwiftDocCUtilitiesTests/Test Resources/image.png diff --git a/Tests/CommandLineTests/ThrottleTests.swift b/Tests/SwiftDocCUtilitiesTests/ThrottleTests.swift similarity index 92% rename from Tests/CommandLineTests/ThrottleTests.swift rename to Tests/SwiftDocCUtilitiesTests/ThrottleTests.swift index 5a1fb7cdfa..699822810d 100644 --- a/Tests/CommandLineTests/ThrottleTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ThrottleTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -9,7 +9,7 @@ */ import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities class ThrottleTests: XCTestCase { func testThrottlingSingleCall() throws { diff --git a/Tests/CommandLineTests/TransformForStaticHostingActionTests.swift b/Tests/SwiftDocCUtilitiesTests/TransformForStaticHostingActionTests.swift similarity index 98% rename from Tests/CommandLineTests/TransformForStaticHostingActionTests.swift rename to Tests/SwiftDocCUtilitiesTests/TransformForStaticHostingActionTests.swift index 7b1e3b59eb..912e5824a7 100644 --- a/Tests/CommandLineTests/TransformForStaticHostingActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/TransformForStaticHostingActionTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -11,8 +11,8 @@ import XCTest import Foundation @testable import SwiftDocC -@testable import CommandLine -import TestUtilities +@testable import SwiftDocCUtilities +import SwiftDocCTestUtilities class TransformForStaticHostingActionTests: StaticHostingBaseTests { diff --git a/Tests/CommandLineTests/Utility/DirectedGraphTests.swift b/Tests/SwiftDocCUtilitiesTests/Utility/DirectedGraphTests.swift similarity index 100% rename from Tests/CommandLineTests/Utility/DirectedGraphTests.swift rename to Tests/SwiftDocCUtilitiesTests/Utility/DirectedGraphTests.swift diff --git a/Tests/CommandLineTests/Utility/FileTests.swift b/Tests/SwiftDocCUtilitiesTests/Utility/FileTests.swift similarity index 99% rename from Tests/CommandLineTests/Utility/FileTests.swift rename to Tests/SwiftDocCUtilitiesTests/Utility/FileTests.swift index 0165dcf716..cfabe527a4 100644 --- a/Tests/CommandLineTests/Utility/FileTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/Utility/FileTests.swift @@ -9,7 +9,7 @@ */ import XCTest -import TestUtilities +import SwiftDocCTestUtilities class FileTests: XCTestCase { func testAbsoluteURL() { diff --git a/Tests/CommandLineTests/Utility/LogHandleTests.swift b/Tests/SwiftDocCUtilitiesTests/Utility/LogHandleTests.swift similarity index 97% rename from Tests/CommandLineTests/Utility/LogHandleTests.swift rename to Tests/SwiftDocCUtilitiesTests/Utility/LogHandleTests.swift index 489f0f91b5..c7bdb5afb9 100644 --- a/Tests/CommandLineTests/Utility/LogHandleTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/Utility/LogHandleTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -9,7 +9,7 @@ */ import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities @testable import SwiftDocC class LogHandleTests: XCTestCase { diff --git a/Tests/CommandLineTests/Utility/Sequence+UniqueTests.swift b/Tests/SwiftDocCUtilitiesTests/Utility/Sequence+UniqueTests.swift similarity index 94% rename from Tests/CommandLineTests/Utility/Sequence+UniqueTests.swift rename to Tests/SwiftDocCUtilitiesTests/Utility/Sequence+UniqueTests.swift index 4de534089d..865ffca714 100644 --- a/Tests/CommandLineTests/Utility/Sequence+UniqueTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/Utility/Sequence+UniqueTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -10,7 +10,7 @@ import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities class Sequence_UniqueTests: XCTestCase { func testEmpty() { diff --git a/Tests/CommandLineTests/Utility/TestFileSystemTests.swift b/Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift similarity index 99% rename from Tests/CommandLineTests/Utility/TestFileSystemTests.swift rename to Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift index 85eec4dc38..fd3eb71489 100644 --- a/Tests/CommandLineTests/Utility/TestFileSystemTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -10,7 +10,7 @@ import XCTest import SwiftDocC -@testable import TestUtilities +@testable import SwiftDocCTestUtilities class TestFileSystemTests: XCTestCase { diff --git a/Tests/CommandLineTests/Utility/URL+IsAbsoluteWebURLTests.swift b/Tests/SwiftDocCUtilitiesTests/Utility/URL+IsAbsoluteWebURLTests.swift similarity index 90% rename from Tests/CommandLineTests/Utility/URL+IsAbsoluteWebURLTests.swift rename to Tests/SwiftDocCUtilitiesTests/Utility/URL+IsAbsoluteWebURLTests.swift index 75c20a70ae..d693826f6b 100644 --- a/Tests/CommandLineTests/Utility/URL+IsAbsoluteWebURLTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/Utility/URL+IsAbsoluteWebURLTests.swift @@ -1,14 +1,14 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022-2025 Apple Inc. and the Swift project authors + Copyright (c) 2022 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 */ -@testable import CommandLine +@testable import SwiftDocCUtilities import XCTest class URL_IsAbsoluteWebURLTests: XCTestCase { diff --git a/Tests/CommandLineTests/Utility/URL+RelativeTests.swift b/Tests/SwiftDocCUtilitiesTests/Utility/URL+RelativeTests.swift similarity index 97% rename from Tests/CommandLineTests/Utility/URL+RelativeTests.swift rename to Tests/SwiftDocCUtilitiesTests/Utility/URL+RelativeTests.swift index d832546cbd..9aea0848a8 100644 --- a/Tests/CommandLineTests/Utility/URL+RelativeTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/Utility/URL+RelativeTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -9,7 +9,7 @@ */ import XCTest -@testable import CommandLine +@testable import SwiftDocCUtilities class URL_RelativeTests: XCTestCase { diff --git a/Tests/CommandLineTests/Utility/XCTestCase+enableFeatureFlag.swift b/Tests/SwiftDocCUtilitiesTests/Utility/XCTestCase+enableFeatureFlag.swift similarity index 100% rename from Tests/CommandLineTests/Utility/XCTestCase+enableFeatureFlag.swift rename to Tests/SwiftDocCUtilitiesTests/Utility/XCTestCase+enableFeatureFlag.swift diff --git a/Tests/CommandLineTests/XCTestCase+LoadingData.swift b/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift similarity index 98% rename from Tests/CommandLineTests/XCTestCase+LoadingData.swift rename to Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift index 2a6c26b7a1..cc50e4ca1f 100644 --- a/Tests/CommandLineTests/XCTestCase+LoadingData.swift +++ b/Tests/SwiftDocCUtilitiesTests/XCTestCase+LoadingData.swift @@ -11,7 +11,7 @@ import Foundation import XCTest import SwiftDocC -import TestUtilities +import SwiftDocCTestUtilities extension XCTestCase { /// Loads a documentation catalog from an in-memory test file system. diff --git a/Tests/signal-test-app/main.swift b/Tests/signal-test-app/main.swift index 4a96e6eb9f..2973ef3d69 100644 --- a/Tests/signal-test-app/main.swift +++ b/Tests/signal-test-app/main.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2025 Apple Inc. and the Swift project authors + Copyright (c) 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -10,13 +10,13 @@ // // Trap signals and exit with a predefined error code. -// Check Tests/CommandLineTests/SignalTests.swift for more details. +// Check Tests/SwiftDocCUtilitiesTests/SignalTests.swift for more details. // #if os(macOS) || os(Linux) || os(Android) import Foundation -import CommandLine +import SwiftDocCUtilities Signal.on(Signal.all) { _ in print("Signal test app exiting.") diff --git a/bin/check-source b/bin/check-source index b72b7a4c41..7ab7fa1de5 100755 --- a/bin/check-source +++ b/bin/check-source @@ -48,7 +48,7 @@ for language in swift-or-c bash md-or-tutorial html docker; do reader=head case "$language" in swift-or-c) - exceptions=( -name Package.swift -o -path "./Tests/SwiftDocCTests/Test Bundles/*" -o -path "./Tests/CommandLineTests/Test Bundles/*") + exceptions=( -name Package.swift -o -path "./Tests/SwiftDocCTests/Test Bundles/*" -o -path "./Tests/SwiftDocCUtilitiesTests/Test Bundles/*") matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) cat > "$tmp" <<"EOF" /* diff --git a/bin/preview-docs b/bin/preview-docs index 1faa752bdf..f988b5548f 100755 --- a/bin/preview-docs +++ b/bin/preview-docs @@ -2,7 +2,7 @@ # # This source file is part of the Swift.org open source project # -# Copyright (c) 2021-2025 Apple Inc. and the Swift project authors +# Copyright (c) 2021 Apple Inc. and the Swift project authors # Licensed under Apache License v2.0 with Runtime Library Exception # # See https://swift.org/LICENSE.txt for license information @@ -28,7 +28,7 @@ while test $# -gt 0; do case "$1" in --help) echo - echo "Usage: $(basename $0) [SwiftDocC|CommandLine|DocC] [-h] [--convert-only]" + echo "Usage: $(basename $0) [SwiftDocC|SwiftDocCUtilities|DocC] [-h] [--convert-only]" echo "Builds the given framework and converts or previews the documentation" echo "Note: To preview you must set the \`DOCC_HTML_DIR\` with a path to a documentation template. @@ -80,27 +80,27 @@ case $FRAMEWORK in --output-path "$DOCS_BUILD_DIR" ;; - "CommandLine") + "SwiftDocCUtilities") - # Generate symbol graph files for CommandLine + # Generate symbol graph files for SwiftDocCUtilities swift build --package-path "$DOCC_ROOT" \ - --target CommandLine \ + --target SwiftDocCUtilities \ -Xswiftc -emit-symbol-graph \ -Xswiftc -emit-symbol-graph-dir -Xswiftc "$SGFS_DIR" echo # Delete the symbol graph files from dependences by looking for - # those without a 'CommandLine' prefix. - find "$SGFS_DIR" -type f ! -name 'CommandLine*' -delete + # those without a 'SwiftDocCUtilities' prefix. + find "$SGFS_DIR" -type f ! -name 'SwiftDocCUtilities*' -delete echo # Compile the documentation and the symbol graph data. - swift run docc $DOCC_CMD "$DOCC_ROOT/Sources/CommandLine/CommandLine.docc" \ + swift run docc $DOCC_CMD "$DOCC_ROOT/Sources/SwiftDocCUtilities/SwiftDocCUtilities.docc" \ --experimental-enable-custom-templates \ - --fallback-display-name CommandLine \ - --fallback-bundle-identifier org.swift.CommandLine \ + --fallback-display-name SwiftDocCUtilities \ + --fallback-bundle-identifier org.swift.SwiftDocCUtilities \ --fallback-bundle-version 1.0.0 \ --additional-symbol-graph-dir "$SGFS_DIR" \ --output-path $DOCS_BUILD_DIR @@ -116,7 +116,7 @@ case $FRAMEWORK in *) echo - echo "Error: Unknown module '$FRAMEWORK'. Preview is supported only for SwiftDocC, CommandLine, or DocC." + echo "Error: Unknown module '$FRAMEWORK'. Preview is supported only for SwiftDocC, SwiftDocCUtilities, or DocC." exit 0 ;; esac diff --git a/bin/update-gh-pages-documentation-site b/bin/update-gh-pages-documentation-site index 41b8e41341..c1469ed8ae 100755 --- a/bin/update-gh-pages-documentation-site +++ b/bin/update-gh-pages-documentation-site @@ -2,7 +2,7 @@ # # This source file is part of the Swift.org open source project # -# Copyright (c) 2022-2025 Apple Inc. and the Swift project authors +# Copyright (c) 2022-2024 Apple Inc. and the Swift project authors # Licensed under Apache License v2.0 with Runtime Library Exception # # See https://swift.org/LICENSE.txt for license information @@ -27,9 +27,9 @@ SWIFT_DOCC_ROOT="$(dirname $(dirname $(filepath $0)))" DOCC_BUILD_DIR="$SWIFT_DOCC_ROOT"/.build/docc-gh-pages-build DOCC_OUTPUT_DIR="$DOCC_BUILD_DIR"/SwiftDocC.doccarchive -COMMAND_LINE_OUTPUT_DIR="$DOCC_BUILD_DIR"/CommandLine.doccarchive +DOCC_UTILITIES_OUTPUT_DIR="$DOCC_BUILD_DIR"/SwiftDocCUtilities.doccarchive -mkdir -p "$COMMAND_LINE_OUTPUT_DIR" +mkdir -p "$DOCC_UTILITIES_OUTPUT_DIR" # Set current directory to the repository root cd "$SWIFT_DOCC_ROOT" @@ -58,31 +58,31 @@ swift package \ --hosting-base-path swift-docc \ --output-path "$DOCC_OUTPUT_DIR" -echo -e "\033[34;1m Building SwiftDocC Utilities docs at $COMMAND_LINE_OUTPUT_DIR \033[0m" +echo -e "\033[34;1m Building SwiftDocC Utilities docs at $DOCC_UTILITIES_OUTPUT_DIR \033[0m" -# Generate documentation for the 'CommandLine' target and output it +# Generate documentation for the 'SwiftDocCUtilities' target and output it # to a temporary output directory in the .build directory. swift package \ --allow-writing-to-directory "$DOCC_BUILD_DIR" \ generate-documentation \ - --target CommandLine \ + --target SwiftDocCUtilities \ --disable-indexing \ --source-service github \ --source-service-base-url https://github.com/swiftlang/swift-docc/blob/main \ --checkout-path "$SWIFT_DOCC_ROOT" \ --transform-for-static-hosting \ --hosting-base-path swift-docc \ - --output-path "$COMMAND_LINE_OUTPUT_DIR" + --output-path "$DOCC_UTILITIES_OUTPUT_DIR" echo -e "\033[34;1m Merging docs \033q[0m" # Remove the output directory so that the merge command can output there rm -rf "$SWIFT_DOCC_ROOT/gh-pages/docs" -# Merge the CommandLine docs into the primary SwiftDocC docs +# Merge the SwiftDocCUtilities docs into the primary SwiftDocC docs swift run docc merge \ "$DOCC_OUTPUT_DIR" \ - "$COMMAND_LINE_OUTPUT_DIR" \ + "$DOCC_UTILITIES_OUTPUT_DIR" \ --output-path "$SWIFT_DOCC_ROOT/gh-pages/docs" # Save the current commit we've just built documentation from in a variable From ae6a083a559989eb839ee1cf1dcb76a534d65b4b Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sun, 9 Nov 2025 23:15:33 -0800 Subject: [PATCH 69/90] build: repair linker detection with CMake Add a workaround to repair the linker detection in CMake to allow testing properly in GHA based CI. --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cab897c1f..fa036ee10c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,11 @@ See https://swift.org/LICENSE.txt for license information cmake_minimum_required(VERSION 3.24) +# FIXME: The C language is enabled as `GNUInstallDirs` requires the language to +# function properly. We must further enable the language in the initial set to +# ensure that the correct linker is detected. project(SwiftDocC - LANGUAGES Swift) + LANGUAGES C Swift) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) @@ -20,7 +23,6 @@ set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY MultiThreadedDLL) set(CMAKE_Swift_LANGUAGE_VERSION 5) -enable_language(C) include(GNUInstallDirs) # NOTE(compnerd) workaround CMake issues From 8beb703d7d2b7c93b6a981e9f1ef6b51cc9e94ba Mon Sep 17 00:00:00 2001 From: Andrea Fernandez Buitrago <15234535+anferbui@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:14:32 +0000 Subject: [PATCH 70/90] Index external entities after local entities (#1328) The navigation indexing code skips all further nodes after it has already indexed a node with a given identifier [1]. This can result in an odd interaction when there is an external link which is resolved to a path that the bundle serves itself (or more colloquially, a catalog has an external link to itself). Since external entities are indexed before local entities, they were always be preferred over local entities, resulting in local entities mistakenly being dropped from the navigator altogether. The resulting navigator is incorrect and missing nodes, due to external entities not having hierarchy information. This issue was introduced when support for external links in the navigator was introduced. This commit updates `ConvertActionConverter` to always index local entities first. If any external entities clash with local entities, they will be resolved as the local entity (including its hierarchy) in the navigator. Fixes rdar://163018922. [1]: https://github.com/swiftlang/swift-docc/blob/b27288dd99b0e2715ed1a2d5720cd0f23118c030/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift#L711-L713 [2]: https://github.com/swiftlang/swift-docc/commit/65aaf926ec079ddbd40f29540d4180a70af99e5e --- .../ConvertActionConverter.swift | 33 ++-- .../Indexing/ExternalRenderNodeTests.swift | 146 ++++++++++++++++++ 2 files changed, 168 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift index 1673f7b33d..c18d64004b 100644 --- a/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift +++ b/Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift @@ -100,16 +100,7 @@ package enum ConvertActionConverter { let resultsSyncQueue = DispatchQueue(label: "Convert Serial Queue", qos: .unspecified, attributes: []) let resultsGroup = DispatchGroup() - - // Consume external links and add them into the sidebar. - for externalLink in context.externalCache { - // Here we're associating the external node with the **current** bundle's bundle ID. - // This is needed because nodes are only considered children if the parent and child's bundle ID match. - // Otherwise, the node will be considered as a separate root node and displayed separately. - let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: context.inputs.id) - try outputConsumer.consume(externalRenderNode: externalRenderNode) - } - + let renderSignpostHandle = signposter.beginInterval("Render", id: signposter.makeSignpostID(), "Render \(context.knownPages.count) pages") var conversionProblems: [Problem] = context.knownPages.concurrentPerform { identifier, results in @@ -172,7 +163,27 @@ package enum ConvertActionConverter { signposter.endInterval("Render", renderSignpostHandle) guard !Task.isCancelled else { return [] } - + + // Consumes all external links and adds them into the sidebar. + // This consumes all external links referenced across all content, and indexes them so they're available for reference in the navigator. + // This is not ideal as it means that links outside of the Topics section can impact the content of the navigator. + // TODO: It would be more correct to only index external links which have been curated as part of the Topics section. + // + // This has to run after all local nodes have been indexed because we're associating the external node with the **local** documentation's identifier, + // which makes it possible for there be clashes between local and external render nodes. + // When there are duplicate nodes, only the first one will be indexed, + // so in order to prefer local entities whenever there are any clashes, we have to index external nodes second. + // TODO: External render nodes should be associated with the correct documentation identifier. + try signposter.withIntervalSignpost("Index external links", id: signposter.makeSignpostID()) { + for externalLink in context.externalCache { + // Here we're associating the external node with the **local** documentation's identifier. + // This is needed because nodes are only considered children if the parent and child's identifier match. + // Otherwise, the node will be considered as a separate root node and displayed separately. + let externalRenderNode = ExternalRenderNode(externalEntity: externalLink.value, bundleIdentifier: context.inputs.id) + try outputConsumer.consume(externalRenderNode: externalRenderNode) + } + } + // Write various metadata if emitDigest { signposter.withIntervalSignpost("Emit digest", id: signposter.makeSignpostID()) { diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index 1d5ed2b1cc..588c979ebd 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -507,4 +507,150 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, objcTitle) XCTAssertTrue(objcNavigatorExternalRenderNode.metadata.isBeta) } + + func testExternalLinksInContentDontAffectNavigatorIndex() async throws { + let externalResolver = ExternalReferenceResolverTests.TestExternalReferenceResolver() + externalResolver.expectedReferencePath = "/documentation/testbundle/sampleclass" + + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Article.md", utf8Content: """ + # Article + + This is an internal article with an external link which clashes with the curated local link. + + External links in content should not affect the navigator. + + ## Topics + + - ``SampleClass`` + """), + TextFile(name: "SampleClass.md", utf8Content: """ + # ``SampleClass`` + + This extends the documentation for this symbol. + + ## Topics + + - + - + """), + TextFile(name: "ChildArticleA.md", utf8Content: """ + # ChildArticleA + + A child article. + """), + TextFile(name: "ChildArticleB.md", utf8Content: """ + # ChildArticleB + + A child article. + """), + // Symbol graph with a class that matches an external link path + JSONFile(name: "TestBundle.symbols.json", content: makeSymbolGraph(moduleName: "TestBundle", symbols: [ + makeSymbol(id: "some-symbol-id", language: .swift, kind: .class, pathComponents: ["SampleClass"]) + ])), + ]) + + var configuration = DocumentationContext.Configuration() + configuration.externalDocumentationConfiguration.sources[externalResolver.bundleID] = externalResolver + let (bundle, context) = try await loadBundle(catalog: catalog, configuration: configuration) + XCTAssert(context.problems.isEmpty, "Unexpectedly found problems: \(context.problems.map(\.diagnostic.summary))") + + let renderIndexFolder = try createTemporaryDirectory() + let indexBuilder = NavigatorIndex.Builder(outputURL: renderIndexFolder, bundleIdentifier: bundle.id.rawValue, sortRootChildrenByName: true, groupByLanguage: true) + indexBuilder.setup() + let outputConsumer = TestExternalRenderNodeOutputConsumer(indexBuilder: indexBuilder) + + let problems = try ConvertActionConverter.convert( + context: context, + outputConsumer: outputConsumer, + sourceRepository: nil, + emitDigest: false, + documentationCoverageOptions: .noCoverage + ) + XCTAssert(problems.isEmpty, "Unexpectedly found problems: \(DiagnosticConsoleWriter.formattedDescription(for: problems))") + indexBuilder.finalize(emitJSONRepresentation: true, emitLMDBRepresentation: false) + + XCTAssertEqual( + try RenderIndex.fromURL(renderIndexFolder.appendingPathComponent("index.json", isDirectory: false)), + try RenderIndex.fromString( + """ + { + "includedArchiveIdentifiers": [ + "unit-test" + ], + "interfaceLanguages": { + "swift": [ + { + "children": [ + { + "title": "Articles", + "type": "groupMarker" + }, + { + "children": [ + { + "children": [ + { + "path": "/documentation/unit-test/childarticlea", + "title": "ChildArticleA", + "type": "article" + }, + { + "path": "/documentation/unit-test/childarticleb", + "title": "ChildArticleB", + "type": "article" + } + ], + "path": "/documentation/testbundle/sampleclass", + "title": "SampleClass", + "type": "class" + } + ], + "path": "/documentation/unit-test/article", + "title": "Article", + "type": "symbol" + } + ], + "path": "/documentation/testbundle", + "title": "TestBundle", + "type": "module" + } + ] + }, + "schemaVersion": { + "major": 0, + "minor": 1, + "patch": 2 + } + } + """ + ) + ) + } +} + +private class TestExternalRenderNodeOutputConsumer: ConvertOutputConsumer, ExternalNodeConsumer { + let indexBuilder: Synchronized! + + init(indexBuilder: NavigatorIndex.Builder) { + self.indexBuilder = Synchronized(indexBuilder) + } + + func consume(externalRenderNode: ExternalRenderNode) throws { + try self.indexBuilder.sync { try $0.index(renderNode: externalRenderNode) } + } + + func consume(renderNode: RenderNode) throws { + try self.indexBuilder.sync { try $0.index(renderNode: renderNode) } + } + + func consume(assetsInBundle bundle: DocumentationBundle) throws { } + func consume(linkableElementSummaries: [LinkDestinationSummary]) throws { } + func consume(indexingRecords: [IndexingRecord]) throws { } + func consume(assets: [RenderReferenceType: [any 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 { } } From 7db36ad21303f2b85c094b7add3023b4c9e1111e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 11 Nov 2025 15:25:16 +0100 Subject: [PATCH 71/90] Support decoding link summaries with absolute paths to external pages (#1334) rdar://149470919 --- .../ExternalPathHierarchyResolver.swift | 2 +- .../LinkTargets/LinkDestinationSummary.swift | 32 +++++++++- .../ExternalReferenceResolverTests.swift | 58 +++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift index 7efffba881..44477ca526 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift @@ -192,7 +192,7 @@ extension LinkDestinationSummary { identifier: .init(referenceURL.absoluteString), titleVariants: titleVariants, abstractVariants: abstractVariants, - url: relativePresentationURL.absoluteString, + url: absolutePresentationURL?.absoluteString ?? relativePresentationURL.absoluteString, kind: kind, required: false, role: role, diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index e6407e9057..5c3af1ff6b 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -83,6 +83,11 @@ public struct LinkDestinationSummary: Codable, Equatable { /// The relative presentation URL for this element. public let relativePresentationURL: URL + /// The absolute presentation URL for this element, or `nil` if only the _relative_ presentation URL is known. + /// + /// - Note: The absolute presentation URL (if one exists) and the relative presentation URL will always have the same path and fragment components. + let absolutePresentationURL: URL? + /// The resolved topic reference URL to this element. public var referenceURL: URL @@ -359,6 +364,7 @@ public struct LinkDestinationSummary: Codable, Equatable { self.kind = kind self.language = language self.relativePresentationURL = relativePresentationURL + self.absolutePresentationURL = nil self.referenceURL = referenceURL self.title = title self.abstract = abstract @@ -763,7 +769,9 @@ extension LinkDestinationSummary { } catch { kind = try container.decode(DocumentationNode.Kind.self, forKey: .kind) } - relativePresentationURL = try container.decode(URL.self, forKey: .relativePresentationURL) + let decodedURL = try container.decode(URL.self, forKey: .relativePresentationURL) + (relativePresentationURL, absolutePresentationURL) = Self.checkIfDecodedURLWasAbsolute(decodedURL) + referenceURL = try container.decode(URL.self, forKey: .referenceURL) title = try container.decode(String.self, forKey: .title) abstract = try container.decodeIfPresent(Abstract.self, forKey: .abstract) @@ -808,6 +816,28 @@ extension LinkDestinationSummary { variants = try container.decodeIfPresent([Variant].self, forKey: .variants) ?? [] } + + private static func checkIfDecodedURLWasAbsolute(_ decodedURL: URL) -> (relative: URL, absolute: URL?) { + guard decodedURL.isAbsoluteWebURL, + var components = URLComponents(url: decodedURL, resolvingAgainstBaseURL: false) + else { + // If the decoded URL isn't an absolute web URL that's valid according to RFC 3986, then treat it as relative. + return (relative: decodedURL, absolute: nil) + } + + // Remove the scheme, user, port, and host to create a relative URL. + components.scheme = nil + components.user = nil + components.host = nil + components.port = nil + + guard let relativeURL = components.url else { + // If we can't create a relative URL that's valid according to RFC 3986, then treat the original as relative. + return (relative: decodedURL, absolute: nil) + } + + return (relative: relativeURL, absolute: decodedURL) + } } extension LinkDestinationSummary.Variant { diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index 803868d2ad..a5aae23123 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -1379,4 +1379,62 @@ class ExternalReferenceResolverTests: XCTestCase { XCTAssertEqual(externalLinkCount, 2, "Did not resolve the 2 expected external links.") } + func testExternalReferenceWithAbsolutePresentationURL() async throws { + class Resolver: ExternalDocumentationSource { + let bundleID: DocumentationBundle.Identifier = "com.example.test" + + func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult { + .success(ResolvedTopicReference(bundleID: bundleID, path: "/path/to/something", sourceLanguage: .swift)) + } + + var entityToReturn: LinkDestinationSummary + init(entityToReturn: LinkDestinationSummary) { + self.entityToReturn = entityToReturn + } + + func entity(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity { + entityToReturn + } + } + + let catalog = Folder(name: "unit-test.docc", content: [ + TextFile(name: "Root.md", utf8Content: """ + # Root + + Link to an external page: + """), + ]) + + // Only decoded link summaries support absolute presentation URLs. + let externalEntity = try JSONDecoder().decode(LinkDestinationSummary.self, from: Data(""" + { + "path": "https://com.example/path/to/something", + "title": "Something", + "kind": "org.swift.docc.kind.article", + "referenceURL": "doc://com.example.test/path/to/something", + "language": "swift", + "availableLanguages": [ + "swift" + ] + } + """.utf8)) + XCTAssertEqual(externalEntity.relativePresentationURL.absoluteString, "/path/to/something") + XCTAssertEqual(externalEntity.absolutePresentationURL?.absoluteString, "https://com.example/path/to/something") + + let resolver = Resolver(entityToReturn: externalEntity) + + var configuration = DocumentationContext.Configuration() + configuration.externalDocumentationConfiguration.sources = [resolver.bundleID: resolver] + let (_, context) = try await loadBundle(catalog: catalog, configuration: configuration) + + XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))") + + // Check the curation on the root page + let rootNode = try context.entity(with: XCTUnwrap(context.soleRootModuleReference)) + let converter = DocumentationNodeConverter(context: context) + + let renderNode = converter.convert(rootNode) + let externalTopicReference = try XCTUnwrap(renderNode.references.values.first as? TopicRenderReference) + XCTAssertEqual(externalTopicReference.url, "https://com.example/path/to/something") + } } From 304804b2d869af292619f5fdb8a3e9843daa2b0f Mon Sep 17 00:00:00 2001 From: Prajwal Nadig Date: Wed, 12 Nov 2025 10:19:38 +0000 Subject: [PATCH 72/90] Speed up directory monitoring tests (#1325) The directory monitoring tests check for whether the documentation is rebuilt if the documentation catalog is edited when running a live server with `docc preview`. These tests assumed an extremely generous timeout for receiving the directory change signal, and ran much slower than the rest of the test suite. The timeouts have been reduced to a more reasonable threshold that succeeds even with throttled resources on a single-core machine. --- .../DirectoryMonitorTests.swift | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift b/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift index 555d258222..efd9b23b15 100644 --- a/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/DirectoryMonitorTests.swift @@ -88,10 +88,13 @@ class DirectoryMonitorTests: XCTestCase { } } - /// - Warning: Please do not overuse this method as it takes 10s of wait time and can potentially slow down running the test suite. private func monitorNoUpdates(url: URL, testBlock: @escaping () throws -> Void, file: StaticString = #filePath, line: UInt = #line) throws { + let fileUpdateEvent = expectation(description: "Unexpectedly triggered an update event") + // This test does not expect any file change events + fileUpdateEvent.isInverted = true + let monitor = try DirectoryMonitor(root: url) { rootURL, url in - XCTFail("Did produce file update event for a hidden file", file: file, line: line) + fileUpdateEvent.fulfill() } try monitor.start() @@ -99,17 +102,11 @@ class DirectoryMonitorTests: XCTestCase { monitor.stop() } - let didNotTriggerUpdateForHiddenFile = expectation(description: "Doesn't trigger update") - DispatchQueue.global().async { - try? testBlock() - } - - // For the test purposes we assume a file change event will be delivered within generous 10 seconds. - DispatchQueue.global().asyncAfter(deadline: .now() + 10) { - didNotTriggerUpdateForHiddenFile.fulfill() - } - - wait(for: [didNotTriggerUpdateForHiddenFile], timeout: 20) + // For test purposes, we assume a file change event will be delivered within 1.5 seconds. + // This also aligns with the `monitor()` method above, that ensures that file change events + // in tests are received within 1.5 seconds. If this works too eagerly, then the other tests + // in this suite will fail. + waitForExpectations(timeout: 1.5) } #endif From 758f91ea00c390bb8ca0bdb9f85b83c76600d80a Mon Sep 17 00:00:00 2001 From: Pat Shaughnessy Date: Wed, 12 Nov 2025 02:31:05 -0800 Subject: [PATCH 73/90] Add a public initializer to RenderContentMetadata (#1337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a public initializer to RenderContentMetadata * Update Sources/SwiftDocC/Model/Rendering/Content/RenderContentMetadata.swift Co-authored-by: David Rönnqvist * Introduce PublicAPITests for verifying certain API are actually public. * Remove unneeded unit test in PublicAPITests.swift. --------- Co-authored-by: David Rönnqvist --- .../Rendering/Content/RenderContentMetadata.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/SwiftDocC/Model/Rendering/Content/RenderContentMetadata.swift b/Sources/SwiftDocC/Model/Rendering/Content/RenderContentMetadata.swift index 90da0dbd36..657276a0a1 100644 --- a/Sources/SwiftDocC/Model/Rendering/Content/RenderContentMetadata.swift +++ b/Sources/SwiftDocC/Model/Rendering/Content/RenderContentMetadata.swift @@ -20,6 +20,19 @@ public struct RenderContentMetadata: Equatable, Codable { public var abstract: [RenderInlineContent]? /// An optional identifier for the device frame that should wrap this element. public var deviceFrame: String? + + /// Creates a new metadata value for a content element. + /// - Parameters: + /// - anchor: The named anchor of the content element. + /// - title: The customized title for the content element. + /// - abstract: The customized abstract for the content element. + /// - deviceFrame: The identifier for the device frame that should wrap the content element. + public init(anchor: String? = nil, title: String? = nil, abstract: [RenderInlineContent]? = nil, deviceFrame: String? = nil) { + self.anchor = anchor + self.title = title + self.abstract = abstract + self.deviceFrame = deviceFrame + } } extension RenderContentMetadata { From 9aabe366a1829964f1cb24102417d7d2ac4821bd Mon Sep 17 00:00:00 2001 From: Maya Epps <53411851+mayaepps@users.noreply.github.com> Date: Wed, 12 Nov 2025 02:41:49 -0800 Subject: [PATCH 74/90] Make `CodableContentSection.section` property public (#1344) * Make CodableContentSection.section public This allows other applications linking to Swift DocC to access this property. * Modernize how section property initializes its stored property using an init accessor --- .../Rendering/RenderNode/CodableContentSection.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNode/CodableContentSection.swift b/Sources/SwiftDocC/Model/Rendering/RenderNode/CodableContentSection.swift index 6358e9b309..44b11c4f57 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNode/CodableContentSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNode/CodableContentSection.swift @@ -15,7 +15,11 @@ import Foundation /// This allows decoding a ``RenderSection`` into its appropriate concrete type, based on the section's /// ``RenderSection/kind``. public struct CodableContentSection: Codable, Equatable { - var section: any RenderSection { + public var section: any RenderSection { + @storageRestrictions(initializes: typeErasedSection) + init(initialValue) { + typeErasedSection = AnyRenderSection(initialValue) + } get { typeErasedSection.value } @@ -27,7 +31,6 @@ public struct CodableContentSection: Codable, Equatable { /// Creates a codable content section from the given section. public init(_ section: any RenderSection) { - self.typeErasedSection = AnyRenderSection(section) self.section = section } @@ -35,7 +38,6 @@ public struct CodableContentSection: Codable, Equatable { let container = try decoder.container(keyedBy: CodingKeys.self) let kind = try container.decode(RenderSectionKind.self, forKey: .kind) - self.typeErasedSection = AnyRenderSection(ContentRenderSection(kind: .content, content: [])) switch kind { case .discussion: section = try ContentRenderSection(from: decoder) From a9569b4fa53eced96e5a3e75e0816b50a7b23a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Wed, 12 Nov 2025 14:55:31 +0100 Subject: [PATCH 75/90] Improve parsing of links with type disambiguation that include generics (#1338) * Improve parsing of links with type disambiguation that include generics rdar://160232871 * Add documentation for the private type signature string scanner API --- .../PathHierarchy+TypeSignature.swift | 168 ++++++++++++++++-- .../Infrastructure/PathHierarchyTests.swift | 85 +++++++++ 2 files changed, 238 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift index 06d33cdf38..1644116036 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2023-2024 Apple Inc. and the Swift project authors + Copyright (c) 2023-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 @@ -522,6 +522,20 @@ extension PathHierarchy.PathParser { // MARK: Scanning a substring +/// A file-private, low-level string scanner type that's only designed for parsing type signature based disambiguation suffixes in authored links. +/// +/// ## Correct usage +/// +/// The higher level methods like ``scanReturnTypes()``, ``scanArguments()``, ``scanTuple()``, or ``scanValue()`` makes assumptions about the scanners content and current state. +/// For example: +/// - ``scanReturnTypes()`` knows that return types are specified after any parameter types and requires that the caller has already scanned the parameter types and advanced past the `"->"` separator. +/// It's the caller's (`parseTypeSignatureDisambiguation(pathComponent:)` above) responsibility to do these things correctly. +/// Similarly, it's the caller's responsibility to advance past the `"-"` prefix verify that the scanner points to an open parenthesis character (`(`) that before calling ``scanArguments()`` to scan the parameter types. +/// Failing to do either of these things will result in unexpected parsed disambiguation that DocC will fail to find a match for. +/// - Both ``scanArguments()``, or ``scanTuple()`` expects that the disambiguation portion of the authored link has a balanced number of open and closer parenthesis (`(` and `)`). +/// If the authored link contains unbalanced parenthesis then disambiguation isn't valid and the scanner will return a parsed value that DocC will fail to find a match for. +/// - ``scanValue()`` expects that the disambiguation portion of the authored link has a balanced number of open and closer angle brackets (`<` and `>`). +/// If the authored link contains unbalanced angle brackets then disambiguation isn't valid and the scanner will return a parsed value that DocC will fail to find a match for. private struct StringScanner: ~Copyable { private var remaining: Substring @@ -529,25 +543,45 @@ private struct StringScanner: ~Copyable { remaining = original } - func peek() -> Character? { + /// Returns the next character _without_ advancing the scanner + private func peek() -> Character? { remaining.first } - mutating func take() -> Character { + /// Advances the scanner and returns the scanned character. + private mutating func take() -> Character { remaining.removeFirst() } + /// Advances the scanner by `count` elements and returns the scanned substring. mutating func take(_ count: Int) -> Substring { defer { remaining = remaining.dropFirst(count) } return remaining.prefix(count) } - mutating func takeAll() -> Substring { + /// Advances the scanner to the end and returns the scanned substring. + private mutating func takeAll() -> Substring { defer { remaining.removeAll() } return remaining } - mutating func scan(until predicate: (Character) -> Bool) -> Substring? { + /// Advances the scanner up to the first character that satisfies the given `predicate` and returns the scanned substring. + /// + /// If the scanner doesn't contain any characters that satisfy the given `predicate`, then this method returns `nil` _without_ advancing the scanner. + /// + /// For example, consider a scanner that has already advanced 4 characters into the string `"One,Two,Three"` + /// ``` + /// One,Two,Three + /// ^ + /// ``` + /// Calling `scanner.scan(until: \.isNumber)` returns `nil` without advancing the scanner because none of the (remaining) characters is a number. + /// + /// Calling `scanner.scan(until: { $0 == "," })` advances the scanner by 3 additional characters, returning the scanned `"Two"` substring. + /// ``` + /// One,Two,Three + /// ^ + /// ``` + private mutating func scan(until predicate: (Character) -> Bool) -> Substring? { guard let index = remaining.firstIndex(where: predicate) else { return nil } @@ -555,16 +589,54 @@ private struct StringScanner: ~Copyable { return remaining[.. Bool) -> Substring? { + guard let beforeIndex = remaining.firstIndex(where: predicate) else { + return nil + } + let index = remaining.index(after: beforeIndex) + defer { remaining = remaining[index...] } + return remaining[.. Bool { remaining.hasPrefix(prefix) } // MARK: Parsing argument types by scanning + /// Scans the remainder of the scanner's contents as the individual elements of a tuple return type, + /// or as a single return type if the scanners current position isn't an open parenthesis (`(`) + /// + /// For example, consider a scanner that has already advanced 8 characters into the string `"-(One)->(Two,Three)"` + /// ``` + /// -(One)->(Two, Three) + /// ^ + /// ``` + /// Because the scanner's current position is an open parenthesis (`(`), the scanner advances all the way to the end and returns `["Two", "Three"]` representing two elements in the tuple return value. + /// + /// - Note: The scanner expects that the caller has already scanned any parameter types and advanced past the `"->"` separator. mutating func scanReturnTypes() -> [Substring] { if peek() == "(" { _ = take() // the leading parenthesis @@ -573,7 +645,20 @@ private struct StringScanner: ~Copyable { return [takeAll()] } } - + + /// Scans the list of individual parameter type names as if the scanner's current position was 1 past the open parenthesis (`(`) or a tuple. + /// + /// For example, consider a scanner that has already advanced 2 characters into the string `"-(One,(A,B))->(Two)"` + /// ``` + /// -(One,(A,B))->(Two) + /// ^ + /// ``` + /// The scanner parses two parameter return types---`"One"` and `"(A,B)"`---before the parenthesis balance out, advancing its position to one after the arguments list's closing parenthesis (`)`). + /// ``` + /// -(One,(A,B))->(Two) + /// ^ + /// ``` + /// - Note: The scanner expects that the caller has already advanced past the open parenthesis (`(`) that begins the list of parameter types. mutating func scanArguments() -> [Substring] { guard peek() != ")" else { _ = take() // drop the ")" @@ -591,11 +676,23 @@ private struct StringScanner: ~Copyable { return arguments } - mutating func scanArgument() -> Substring? { + /// Scans a single type name, representing either a scalar value (such as `One`) or a nested tuple (such as `(A,B)`). + /// + /// For example, consider a scanner that has already advanced 6 characters into the string `"-(One,(A,B))->(Two)"` + /// ``` + /// -(One,(A,B))->(Two) + /// ^ + /// ``` + /// Because the value starts with an opening parenthesis (`(`), the scanner advances until the parenthesis balance out, returning `"(A,B)"`. + /// ``` + /// -(One,(A,B))->(Two) + /// ^ + /// ``` + private mutating func scanArgument() -> Substring? { guard peek() == "(" else { // If the argument doesn't start with "(" it can't be neither a tuple nor a closure type. // In this case, scan until the next argument (",") or the end of the arguments (")") - return scan(until: { $0 == "," || $0 == ")" }) ?? takeAll() + return scanValue() ?? takeAll() } guard var argumentString = scanTuple() else { @@ -611,7 +708,7 @@ private struct StringScanner: ~Copyable { guard peek() == "(" else { // This closure type has a simple return type. - guard let returnValue = scan(until: { $0 == "," || $0 == ")" }) else { + guard let returnValue = scanValue() else { return nil } return argumentString + returnValue @@ -622,7 +719,20 @@ private struct StringScanner: ~Copyable { return argumentString + returnValue } - mutating func scanTuple() -> Substring? { + /// Scans a nested tuple as a single substring. + /// + /// For example, consider a scanner that has already advanced 6 character into the string `"-(One,(A,B))->(Two)"` + /// ``` + /// -(One,(A,B))->(Two) + /// ^ + /// ``` + /// Because the value starts with an opening parenthesis (`(`), the scanner advances until the parenthesis balance out, returning `"(A,B)"`. + /// ``` + /// -(One,(A,B))->(Two) + /// ^ + /// ``` + /// - Note: The scanner expects that the caller has already advanced to the open parenthesis (`(`) that's the start of the nested tuple. + private mutating func scanTuple() -> Substring? { assert(peek() == "(", "The caller should have checked that this is a tuple") // The tuple may contain any number of nested tuples. Keep track of the open and close parenthesis while scanning. @@ -632,13 +742,41 @@ private struct StringScanner: ~Copyable { depth += 1 return false // keep scanning } - if depth > 0 { - if $0 == ")" { - depth -= 1 - } + else if $0 == ")" { + depth -= 1 + return depth == 0 // stop only if we've reached a balanced number of parenthesis + } + return false // keep scanning + } + + return scan(past: predicate) + } + + /// Scans a single type name. + /// + /// For example, consider a scanner that has already advanced 2 character into the string `"-(One,Two)"` + /// ``` + /// -(One,Two) + /// ^ + /// ``` + /// Because the value contains generics (``), the scanner advances until the angle brackets balance out, returning `"One"`. + /// ``` + /// -(One,Two) + /// ^ + /// ``` + private mutating func scanValue() -> Substring? { + // The value may contain any number of nested generics. Keep track of the open and close angle brackets while scanning. + var depth = 0 + let predicate: (Character) -> Bool = { + if $0 == "<" { + depth += 1 + return false // keep scanning + } + else if $0 == ">" { + depth -= 1 return false // keep scanning } - return $0 == "," || $0 == ")" + return depth == 0 && ($0 == "," || $0 == ")") } return scan(until: predicate) } diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index c7c54f5495..57627d5086 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -2037,6 +2037,71 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("doSomething()-9kd0v", in: tree, asSymbolID: "some-function-id-AnyObject") } + func testParameterDisambiguationWithKeyPathType() async throws { + // Create two overloads with different key path parameter types + let parameterTypes: [SymbolGraph.Symbol.DeclarationFragments.Fragment] = [ + // Swift.Int + .init(kind: .typeIdentifier, spelling: "Int", preciseIdentifier: "s:Si"), + // Swift.Bool + .init(kind: .typeIdentifier, spelling: "Bool", preciseIdentifier: "s:Sb"), + ] + + let catalog = Folder(name: "CatalogName.docc", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: parameterTypes.map { parameterTypeFragment in + makeSymbol(id: "some-function-id-\(parameterTypeFragment.spelling)-KeyPath", kind: .func, pathComponents: ["doSomething(keyPath:)"], signature: .init( + parameters: [ + // "keyPath: KeyPath" or "keyPath: KeyPath" + .init(name: "keyPath", externalName: nil, declarationFragments: [ + .init(kind: .identifier, spelling: "keyPath", preciseIdentifier: nil), + .init(kind: .text, spelling: ": ", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "KeyPath", preciseIdentifier: "s:s7KeyPathC"), + .init(kind: .text, spelling: "<", preciseIdentifier: nil), + .init(kind: .typeIdentifier, spelling: "String", preciseIdentifier: "s:SS"), + .init(kind: .text, spelling: ", ", preciseIdentifier: nil), + parameterTypeFragment, + .init(kind: .text, spelling: ">", preciseIdentifier: nil) + ], children: []) + ], + returns: [ + .init(kind: .text, spelling: "()", preciseIdentifier: nil) // 'Void' in text representation + ] + )) + })), + ]) + let (_, context) = try await loadBundle(catalog: catalog) + let tree = context.linkResolver.localResolver.pathHierarchy + + XCTAssert(context.problems.isEmpty, "Unexpected problems \(context.problems.map(\.diagnostic.summary))") + + let paths = tree.caseInsensitiveDisambiguatedPaths() + + XCTAssertEqual(paths["some-function-id-Int-KeyPath"], "/ModuleName/doSomething(keyPath:)-(KeyPath)") + XCTAssertEqual(paths["some-function-id-Bool-KeyPath"], "/ModuleName/doSomething(keyPath:)-(KeyPath)") + + try assertPathCollision("doSomething(keyPath:)", in: tree, collisions: [ + ("some-function-id-Int-KeyPath", "-(KeyPath)"), + ("some-function-id-Bool-KeyPath", "-(KeyPath)"), + ]) + + try assertPathRaisesErrorMessage("doSomething(keyPath:)", in: tree, context: context, expectedErrorMessage: "'doSomething(keyPath:)' is ambiguous at '/ModuleName'") { error in + XCTAssertEqual(error.solutions.count, 2) + + // These test symbols don't have full declarations. A real solution would display enough information to distinguish these. + XCTAssertEqual(error.solutions.dropFirst(0).first, .init(summary: "Insert '-(KeyPath)' for \n'doSomething(keyPath:)'" , replacements: [("-(KeyPath)", 21, 21)])) + XCTAssertEqual(error.solutions.dropFirst(1).first, .init(summary: "Insert '-(KeyPath)' for \n'doSomething(keyPath:)'" /* the test symbols don't have full declarations */, replacements: [("-(KeyPath)", 21, 21)])) + } + + assertParsedPathComponents("doSomething(keyPath:)-(KeyPath)", [("doSomething(keyPath:)", .typeSignature(parameterTypes: ["KeyPath"], returnTypes: nil))]) + try assertFindsPath("doSomething(keyPath:)-(KeyPath)", in: tree, asSymbolID: "some-function-id-Int-KeyPath") + try assertFindsPath("doSomething(keyPath:)-(KeyPath)->()", in: tree, asSymbolID: "some-function-id-Int-KeyPath") + try assertFindsPath("doSomething(keyPath:)-2zg7h", in: tree, asSymbolID: "some-function-id-Int-KeyPath") + + assertParsedPathComponents("doSomething(keyPath:)-(KeyPath)", [("doSomething(keyPath:)", .typeSignature(parameterTypes: ["KeyPath"], returnTypes: nil))]) + try assertFindsPath("doSomething(keyPath:)-(KeyPath)", in: tree, asSymbolID: "some-function-id-Bool-KeyPath") + try assertFindsPath("doSomething(keyPath:)-(KeyPath)->()", in: tree, asSymbolID: "some-function-id-Bool-KeyPath") + try assertFindsPath("doSomething(keyPath:)-2frrn", in: tree, asSymbolID: "some-function-id-Bool-KeyPath") + } + func testOverloadGroupSymbolsResolveLinksWithoutHash() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) @@ -4404,6 +4469,26 @@ class PathHierarchyTests: XCTestCase { } assertParsedPathComponents("operator[]-(std::string&)->std::string&", [("operator[]", .typeSignature(parameterTypes: ["std::string&"], returnTypes: ["std::string&"]))]) + + // Nested generic types + assertParsedPathComponents("functionName-(KeyPath)", [("functionName", .typeSignature(parameterTypes: ["KeyPath"], returnTypes: nil))]) + assertParsedPathComponents("functionName->KeyPath", [("functionName", .typeSignature(parameterTypes: nil, returnTypes: ["KeyPath"]))]) + + assertParsedPathComponents("functionName-(KeyPath,Dictionary)", [("functionName", .typeSignature(parameterTypes: ["KeyPath", "Dictionary"], returnTypes: nil))]) + assertParsedPathComponents("functionName->(KeyPath,Dictionary)", [("functionName", .typeSignature(parameterTypes: nil, returnTypes: ["KeyPath", "Dictionary"]))]) + + assertParsedPathComponents("functionName-(KeyPath>)", [("functionName", .typeSignature(parameterTypes: ["KeyPath>"], returnTypes: nil))]) + assertParsedPathComponents("functionName->KeyPath>", [("functionName", .typeSignature(parameterTypes: nil, returnTypes: ["KeyPath>"]))]) + + assertParsedPathComponents("functionName-(KeyPath,Dictionary>)", [("functionName", .typeSignature(parameterTypes: ["KeyPath,Dictionary>"], returnTypes: nil))]) + assertParsedPathComponents("functionName->KeyPath,Dictionary>", [("functionName", .typeSignature(parameterTypes: nil, returnTypes: ["KeyPath,Dictionary>"]))]) + + // Nested generics and tuple types + assertParsedPathComponents( "functionName-(A,(D,H<(I,J),(K,L)>),M,R),S>)", [("functionName", .typeSignature(parameterTypes: ["A", "(D,H<(I,J),(K,L)>)", "M,R),S>"], returnTypes: nil))]) + assertParsedPathComponents("functionName->(A,(D,H<(I,J),(K,L)>),M,R),S>)", [("functionName", .typeSignature(parameterTypes: nil, returnTypes: ["A", "(D,H<(I,J),(K,L)>)", "M,R),S>"]))]) + // With special characters + assertParsedPathComponents( "functionName-(Å<𝔹,©>,(Δ<∃,⨍,𝄞>,ℌ<(𝓲,ⅉ),(🄺,ƛ)>),𝔐<𝚗,(Ω<π,Ⓠ>,℟),𝔖>)", [("functionName", .typeSignature(parameterTypes: ["Å<𝔹,©>", "(Δ<∃,⨍,𝄞>,ℌ<(𝓲,ⅉ),(🄺,ƛ)>)", "𝔐<𝚗,(Ω<π,Ⓠ>,℟),𝔖>"], returnTypes: nil))]) + assertParsedPathComponents("functionName->(Å<𝔹,©>,(Δ<∃,⨍,𝄞>,ℌ<(𝓲,ⅉ),(🄺,ƛ)>),𝔐<𝚗,(Ω<π,Ⓠ>,℟),𝔖>)", [("functionName", .typeSignature(parameterTypes: nil, returnTypes: ["Å<𝔹,©>", "(Δ<∃,⨍,𝄞>,ℌ<(𝓲,ⅉ),(🄺,ƛ)>)", "𝔐<𝚗,(Ω<π,Ⓠ>,℟),𝔖>"]))]) } func testResolveExternalLinkFromTechnologyRoot() async throws { From 0b356e876bf104129ace54cfc954f2bc2bb305ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Wed, 12 Nov 2025 15:23:18 +0100 Subject: [PATCH 76/90] Warn when an article would override a symbol page (#1339) rdar://109177620 --- .../Infrastructure/DocumentationContext.swift | 21 +++++++++- .../DocumentationContextTests.swift | 40 ++++++++++++++++++- .../Infrastructure/PathHierarchyTests.swift | 13 ++++-- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 9d4d83216b..b40d8dd7f6 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -1726,7 +1726,26 @@ public class DocumentationContext { } let reference = documentation.reference - documentationCache[reference] = documentation + if let existing = documentationCache[reference], existing.kind.isSymbol { + // By the time we get here it's already to late to fix the collision. All we can do is make the author aware of it and handle the collision deterministically. + // rdar://79745455 and https://github.com/swiftlang/swift-docc/issues/593 tracks fixing the root cause of this issue, avoiding the collision and allowing the article and symbol to both exist. + diagnosticEngine.emit( + Problem( + diagnostic: Diagnostic(source: article.source, severity: .warning, identifier: "org.swift.docc.articleCollisionProblem", summary: """ + Article '\(article.source.lastPathComponent)' (\(title)) would override \(existing.kind.name.lowercased()) '\(existing.name.description)'. + """, explanation: """ + DocC computes unique URLs for symbols, even if they have the same name, but doesn't account for article filenames that collide with symbols because of a bug. + Until rdar://79745455 (issue #593) is fixed, DocC favors the symbol in this collision and drops the article to have deterministic behavior. + """), + possibleSolutions: [ + Solution(summary: "Rename '\(article.source.lastPathComponent)'", replacements: [ /* Renaming a file isn't something that we can represent with a replacement */ ]) + ] + ) + ) + return article // Don't continue processing this article + } else { + documentationCache[reference] = documentation + } documentLocationMap[article.source] = reference let graphNode = TopicGraph.Node(reference: reference, kind: .article, source: .file(url: article.source), title: title) diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift index e788f1b192..7b48aa7794 100644 --- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift @@ -2458,7 +2458,10 @@ let expected = """ let curatedTopic = try XCTUnwrap(renderNode.topicSections.first?.identifiers.first) let topicReference = try XCTUnwrap(renderNode.references[curatedTopic] as? TopicRenderReference) - XCTAssertEqual(topicReference.title, "An article") + + // FIXME: Verify that article matches are preferred for general (non-symbol) links once rdar://79745455 https://github.com/swiftlang/swift-docc/issues/593 is fixed + XCTAssertEqual(topicReference.title, "Wrapper") +// XCTAssertEqual(topicReference.title, "An article") // This test also reproduce https://github.com/swiftlang/swift-docc/issues/593 // When that's fixed this test should also use a symbol link to curate the top-level symbol and verify that @@ -4806,6 +4809,41 @@ let expected = """ XCTAssertEqual(problem.diagnostic.summary, "\'NonExistingDoc\' doesn\'t exist at \'/BestBook/MyArticle\'") } + func testArticleCollidingWithSymbol() async throws { + let catalog = Folder(name: "ModuleName.docc", content: [ + JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [ + makeSymbol(id: "some-symbol-id", kind: .class, pathComponents: ["SomeClass"]), // Collision + ])), + + TextFile(name: "SomeClass.md", utf8Content: """ + # Some article + + This article has the same reference as the symbol. One will override the other. + We should at least warn about that. + """), + ]) + + let (_, context) = try await loadBundle(catalog: catalog) + let moduleReference = try XCTUnwrap(context.soleRootModuleReference) + let collidingPageReference = moduleReference.appendingPath("SomeClass") + + XCTAssertEqual( + Set(context.knownPages), [moduleReference, collidingPageReference], + "Ideally there should be 3 pages here but because of rdar://79745455 (issue #593) there isn't" // https://github.com/swiftlang/swift-docc/issues/593 + ) + + let node = try context.entity(with: collidingPageReference) + XCTAssert(node.kind.isSymbol, "Given #593 / rdar://79745455 we should deterministically prioritize the symbol over the article") + + XCTAssertEqual(context.problems.map(\.diagnostic.summary), [ + "Article 'SomeClass.md' (Some article) would override class 'SomeClass'." + ]) + + let problem = try XCTUnwrap(context.problems.first) + let solution = try XCTUnwrap(problem.possibleSolutions.first) + XCTAssertEqual(solution.summary, "Rename 'SomeClass.md'") + } + func testContextRecognizesOverloads() async throws { enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled) diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index 57627d5086..a658bf4045 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -1239,8 +1239,11 @@ class PathHierarchyTests: XCTestCase { // The added article above has the same path as an existing symbol in the this module. let symbolNode = try tree.findNode(path: "/MixedLanguageFramework/Bar", onlyFindSymbols: true) XCTAssertNotNil(symbolNode.symbol, "Symbol link finds the symbol") + let articleNode = try tree.findNode(path: "/MixedLanguageFramework/Bar", onlyFindSymbols: false) - XCTAssertNil(articleNode.symbol, "General documentation link find the article") + XCTAssertNotNil(articleNode.symbol, "This should be an article but can't be because of rdar://79745455") + // FIXME: Verify that article matches are preferred for general (non-symbol) links once https://github.com/swiftlang/swift-docc/issues/593 is fixed +// XCTAssertNil(articleNode.symbol, "General documentation link find the article") } func testArticleSelfAnchorLinks() async throws { @@ -2556,13 +2559,17 @@ class PathHierarchyTests: XCTestCase { // Links to non-symbols can use only the file name, without specifying the module or catalog name. let articleID = try tree.find(path: "Wrapper", onlyFindSymbols: false) let articleMatch = try XCTUnwrap(tree.lookup[articleID]) - XCTAssertNil(articleMatch.symbol, "Should have found the article") + XCTAssertNotNil(articleMatch.symbol, "This should be an article but can't be because of rdar://79745455") + // FIXME: Verify that article matches are preferred for general (non-symbol) links once rdar://79745455 https://github.com/swiftlang/swift-docc/issues/593 is fixed +// XCTAssertNil(articleMatch.symbol, "Should have found the article") } do { // Links to non-symbols can also use module-relative links. let articleID = try tree.find(path: "/Something/Wrapper", onlyFindSymbols: false) let articleMatch = try XCTUnwrap(tree.lookup[articleID]) - XCTAssertNil(articleMatch.symbol, "Should have found the article") + XCTAssertNotNil(articleMatch.symbol, "This should be an article but can't be because of rdar://79745455") + // FIXME: Verify that article matches are preferred for general (non-symbol) links once rdar://79745455 https://github.com/swiftlang/swift-docc/issues/593 is fixed +// XCTAssertNil(articleMatch.symbol, "Should have found the article") } // Symbols can only use absolute links or be found relative to another page. let symbolID = try tree.find(path: "/Something/Wrapper", onlyFindSymbols: true) From cfcd96f0e992af2287661f6a26e8398096f6bc1e Mon Sep 17 00:00:00 2001 From: Maksym Grebenets Date: Thu, 13 Nov 2025 03:12:54 +1100 Subject: [PATCH 77/90] (#1257) Combined module link issue - public extension of dependent module causes resolution failure (#1327) * (#1257) Combined module link issue - public extension of dependent module causes resolution failure * Apply suggestions from code review * Address feedback comments - Cleanup unrelated assertions - Use readable names for IDs - Cleanup unused IDs - Make sure the tests clearly show that relative paths resolve and absolute paths thoe 'no module' error - Use ExtendedModule name --------- Co-authored-by: Max Grebenets --- .../Link Resolution/PathHierarchy+Find.swift | 4 +- .../Infrastructure/PathHierarchyTests.swift | 86 ++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift index a4ea9ad67c..7117fcabaa 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift @@ -84,7 +84,9 @@ extension PathHierarchy { if let moduleMatch = modules.first(where: { $0.matches(firstComponent) }) { return try searchForNode(descendingFrom: moduleMatch, pathComponents: remaining.dropFirst(), onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPath) } - if modules.count == 1 { + // For absolute links, only use the single-module fallback if the first component doesn't match + // any module name + if modules.count == 1 && !isAbsolute { do { return try searchForNode(descendingFrom: modules.first!, pathComponents: remaining, onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPath) } catch { diff --git a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift index a658bf4045..661198700a 100644 --- a/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift @@ -3198,7 +3198,91 @@ class PathHierarchyTests: XCTestCase { try assertFindsPath("/MainModule/TopLevelProtocol/extensionMember(_:)", in: tree, asSymbolID: "extensionMember1") try assertFindsPath("/MainModule/TopLevelProtocol/InnerStruct/extensionMember(_:)", in: tree, asSymbolID: "extensionMember2") } - + + func testAbsoluteLinksToOtherModuleWithExtensions() async throws { + enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled) + + let extendedTypeID = "extended-type-id" + let extensionID = "extension-id" + let extensionMethodID = "extension-method-id" + + let extensionMixin = SymbolGraph.Symbol.Swift.Extension( + extendedModule: "ExtendedModule", + typeKind: .struct, + constraints: [] + ) + + let catalog = Folder(name: "TestCatalog.docc", content: [ + JSONFile(name: "MainModule.symbols.json", content: makeSymbolGraph(moduleName: "MainModule", symbols: [])), + JSONFile(name: "MainModule@ExtendedModule.symbols.json", content: makeSymbolGraph( + moduleName: "MainModule", + symbols: [ + makeSymbol( + id: extensionID, + kind: .extension, + pathComponents: ["ExtendedType"], + otherMixins: [extensionMixin] + ), + makeSymbol( + id: extensionMethodID, + kind: .method, + pathComponents: ["ExtendedType", "extensionMethod()"], + otherMixins: [extensionMixin] + ) + ], + relationships: [ + .init( + source: extensionMethodID, + target: extensionID, + kind: .memberOf, + targetFallback: "ExtendedModule.ExtendedType" + ), + .init( + source: extensionID, + target: extendedTypeID, + kind: .extensionTo, + targetFallback: "ExtendedModule.ExtendedType" + ) + ] + )) + ]) + + let (_, context) = try await loadBundle(catalog: catalog) + let tree = context.linkResolver.localResolver.pathHierarchy + + try assertFindsPath( + "/MainModule/ExtendedModule/ExtendedType/extensionMethod()", + in: tree, + asSymbolID: extensionMethodID + ) + + try assertFindsPath( + "ExtendedModule/ExtendedType", + in: tree, + asSymbolID: extensionID + ) + try assertFindsPath( + "ExtendedModule/ExtendedType/extensionMethod()", + in: tree, + asSymbolID: extensionMethodID + ) + + // Verify that a link that resolves relative to the module + // fails to resolve as an absolute link, with a moduleNotFound error. + try assertPathRaisesErrorMessage( + "/ExtendedModule/ExtendedType", + in: tree, + context: context, + expectedErrorMessage: "No module named 'ExtendedModule'" + ) + try assertPathRaisesErrorMessage( + "/ExtendedModule/ExtendedType/extensionMethod()", + in: tree, + context: context, + expectedErrorMessage: "No module named 'ExtendedModule'" + ) + } + func testMissingRequiredMemberOfSymbolGraphRelationshipInOneLanguageAcrossManyPlatforms() async throws { // We make a best-effort attempt to create a valid path hierarchy, even if the symbol graph inputs are not valid. From 20a0c8bb7158f6cfa30cf821c6a07abf90b33aa5 Mon Sep 17 00:00:00 2001 From: Andrea Fernandez Buitrago <15234535+anferbui@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:11:56 +0000 Subject: [PATCH 78/90] Encode absolute presentation URL if present (#1346) * Add missing fields in Equatable implementation The `Equatable` implementation of `LinkDestinationSummary` was missing some of the new fields which were added recently. This commit adds them. * Encode absolute presentation URL if present Support for absolute URLs in link summaries was added in 7db36ad21303f2b85c094b7add3023b4c9e1111e. It added support for absolute URLs when decoding a link summary, but not when encoding it: https://github.com/swiftlang/swift-docc/blob/cfcd96f0e992af2287661f6a26e8398096f6bc1e/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift#L772-L773 https://github.com/swiftlang/swift-docc/blob/cfcd96f0e992af2287661f6a26e8398096f6bc1e/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift#L728 Both are needed when using an external link resolver, as the external link resolver needs to encode the entity to send to DocC. Fixes rdar://164628218. --- .../SwiftDocC/LinkTargets/LinkDestinationSummary.swift | 8 ++++++-- .../Infrastructure/ExternalReferenceResolverTests.swift | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 5c3af1ff6b..993e8fb315 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -112,7 +112,8 @@ public struct LinkDestinationSummary: Codable, Equatable { // so that external documentation sources don't need to provide that data. // Adding new required properties is considered breaking change since existing external documentation sources // wouldn't necessarily meet these new requirements. - + // Make sure to update the encoding, decoding and Equatable implementations when adding new properties. + /// A collection of identifiers that all relate to some common task, as described by the title. public struct TaskGroup: Codable, Equatable { /// The title of this task group @@ -725,7 +726,7 @@ extension LinkDestinationSummary { } else { try container.encode(kind, forKey: .kind) } - try container.encode(relativePresentationURL, forKey: .relativePresentationURL) + try container.encode(absolutePresentationURL ?? relativePresentationURL, forKey: .relativePresentationURL) try container.encode(referenceURL, forKey: .referenceURL) try container.encode(title, forKey: .title) try container.encodeIfPresent(abstract, forKey: .abstract) @@ -935,12 +936,15 @@ extension LinkDestinationSummary { guard lhs.kind == rhs.kind else { return false } guard lhs.language == rhs.language else { return false } guard lhs.relativePresentationURL == rhs.relativePresentationURL else { return false } + guard lhs.absolutePresentationURL == rhs.absolutePresentationURL else { return false } guard lhs.title == rhs.title else { return false } guard lhs.abstract == rhs.abstract else { return false } guard lhs.availableLanguages == rhs.availableLanguages else { return false } guard lhs.platforms == rhs.platforms else { return false } guard lhs.taskGroups == rhs.taskGroups else { return false } + guard lhs.plainTextDeclaration == rhs.plainTextDeclaration else { return false } guard lhs.subheadingDeclarationFragments == rhs.subheadingDeclarationFragments else { return false } + guard lhs.navigatorDeclarationFragments == rhs.navigatorDeclarationFragments else { return false } guard lhs.redirects == rhs.redirects else { return false } guard lhs.topicImages == rhs.topicImages else { return false } guard lhs.variants == rhs.variants else { return false } diff --git a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift index a5aae23123..d4a10d31d5 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ExternalReferenceResolverTests.swift @@ -1420,7 +1420,10 @@ class ExternalReferenceResolverTests: XCTestCase { """.utf8)) XCTAssertEqual(externalEntity.relativePresentationURL.absoluteString, "/path/to/something") XCTAssertEqual(externalEntity.absolutePresentationURL?.absoluteString, "https://com.example/path/to/something") - + + // Test that encoding the link summary preserves the absolute URL + try assertRoundTripCoding(externalEntity) + let resolver = Resolver(entityToReturn: externalEntity) var configuration = DocumentationContext.Configuration() From 4864758a4a92bb90a2165b622a818fae2491f0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 14 Nov 2025 13:14:49 +0100 Subject: [PATCH 79/90] Reset unsupported metadata configuration like the doc comment says (#1312) rdar://161219604 --- .../Semantics/Metadata/Metadata.swift | 102 ++++++++++-------- .../Semantics/SymbolTests.swift | 2 - 2 files changed, 55 insertions(+), 49 deletions(-) diff --git a/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift b/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift index 4f172ea2a1..ca3ae0c296 100644 --- a/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift +++ b/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift @@ -200,56 +200,64 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible { /// Validates the use of this Metadata directive in a documentation comment. /// - /// Some configuration options of Metadata are invalid in documentation comments. This function - /// emits warnings for illegal uses and sets their values to `nil`. - func validateForUseInDocumentationComment( - symbolSource: URL?, - problems: inout [Problem] - ) { - let invalidDirectives: [(any AutomaticDirectiveConvertible)?] = [ - documentationOptions, - technologyRoot, - displayName, - callToAction, - pageKind, - _pageColor, - titleHeading, - ] + (redirects ?? []) - + supportedLanguages - + pageImages - - let namesAndRanges = invalidDirectives - .compactMap { $0 } - .map { (type(of: $0).directiveName, $0.originalMarkup.range) } + /// Some configuration options of Metadata are not supported in documentation comments. + /// This function emits warnings for unsupported uses and resets their values (to `nil` or `[]`) . + func validateForUseInDocumentationComment(symbolSource: URL?, problems: inout [Problem]) { + func validateUnsupportedMetadataDirective(for directives: [Directive]?) { + for directive in directives ?? [] { + validateUnsupportedMetadataDirective(for: directive) + } + } - problems.append( - contentsOf: namesAndRanges.map { (name, range) in - let diagnostic = Diagnostic( - source: symbolSource, - severity: .warning, - range: range, - identifier: "org.swift.docc.\(Metadata.directiveName).Invalid\(name)InDocumentationComment", - summary: "Invalid use of \(name.singleQuoted) directive in documentation comment; configuration will be ignored", - explanation: "Specify this configuration in a documentation extension file" - ) - - let solutions: [Solution] = range.map { range in - [Solution( - summary: "Remove invalid \(name.singleQuoted) directive", - replacements: [ - Replacement(range: range, replacement: "") - ] - )] - } ?? [] - - return Problem(diagnostic: diagnostic, possibleSolutions: solutions) + func validateUnsupportedMetadataDirective(for directive: Directive?) { + guard let directive else { + return } - ) + + let name = Directive.directiveName + let range = directive.originalMarkup.range + + let diagnostic = Diagnostic( + source: symbolSource, + severity: .warning, + range: range, + identifier: "org.swift.docc.\(Metadata.directiveName).Invalid\(name)InDocumentationComment", + summary: "Invalid use of \(name.singleQuoted) directive in documentation comment; configuration will be ignored", + explanation: "Specify this configuration in a documentation extension file" + ) + + let solutions: [Solution] = range.map { range in + [Solution( + summary: "Remove invalid \(name.singleQuoted) directive", + replacements: [ + Replacement(range: range, replacement: "") + ] + )] + } ?? [] + + problems.append(Problem(diagnostic: diagnostic, possibleSolutions: solutions)) + } + + validateUnsupportedMetadataDirective(for: documentationOptions) + validateUnsupportedMetadataDirective(for: technologyRoot) + validateUnsupportedMetadataDirective(for: displayName) + validateUnsupportedMetadataDirective(for: callToAction) + validateUnsupportedMetadataDirective(for: pageKind) + validateUnsupportedMetadataDirective(for: _pageColor) + validateUnsupportedMetadataDirective(for: titleHeading) + validateUnsupportedMetadataDirective(for: redirects) + validateUnsupportedMetadataDirective(for: supportedLanguages) + validateUnsupportedMetadataDirective(for: pageImages) documentationOptions = nil - technologyRoot = nil - displayName = nil - pageKind = nil - _pageColor = nil + technologyRoot = nil + displayName = nil + callToAction = nil + pageKind = nil + _pageColor = nil + titleHeading = nil + redirects = nil + supportedLanguages = [] + pageImages = [] } } diff --git a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift index 063c8c997d..a2ea0fdf21 100644 --- a/Tests/SwiftDocCTests/Semantics/SymbolTests.swift +++ b/Tests/SwiftDocCTests/Semantics/SymbolTests.swift @@ -1315,8 +1315,6 @@ class SymbolTests: XCTestCase { "org.swift.docc.Metadata.InvalidPageColorInDocumentationComment", "org.swift.docc.Metadata.InvalidTitleHeadingInDocumentationComment", "org.swift.docc.Metadata.InvalidRedirectedInDocumentationComment", - - "org.swift.docc.unresolvedResource", // For the "test" asset that doesn't exist. ] ) From 70a8a0d32d57903a545c06b5cbac0dfdac9b5e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 14 Nov 2025 17:52:20 +0100 Subject: [PATCH 80/90] Create new target for common code (#1331) * Extract the SourceLanguage type to a new "Common" target * Keep public type alias to moved SourceLanguage type * Use Swift 6 language mode in new target * Add "DocC" prefix to new common target --- Package.swift | 24 +++++++++++++++++++ .../CMakeLists.txt | 0 .../Model => DocCCommon}/SourceLanguage.swift | 10 ++++---- .../SwiftDocC/Utility/CommonTypeExports.swift | 13 ++++++++++ .../SourceLanguageTests.swift | 2 +- 5 files changed, 44 insertions(+), 5 deletions(-) rename Sources/{SwiftDocCUtilities => CommandLine}/CMakeLists.txt (100%) rename Sources/{SwiftDocC/Model => DocCCommon}/SourceLanguage.swift (95%) create mode 100644 Sources/SwiftDocC/Utility/CommonTypeExports.swift rename Tests/{SwiftDocCTests/Model => DocCCommonTests}/SourceLanguageTests.swift (96%) diff --git a/Package.swift b/Package.swift index 9ad6fa6d7a..3ceb324be8 100644 --- a/Package.swift +++ b/Package.swift @@ -43,6 +43,7 @@ let package = Package( .target( name: "SwiftDocC", dependencies: [ + .target(name: "DocCCommon"), .product(name: "Markdown", package: "swift-markdown"), .product(name: "SymbolKit", package: "swift-docc-symbolkit"), .product(name: "CLMDB", package: "swift-lmdb"), @@ -55,6 +56,7 @@ let package = Package( name: "SwiftDocCTests", dependencies: [ .target(name: "SwiftDocC"), + .target(name: "DocCCommon"), .target(name: "SwiftDocCTestUtilities"), ], resources: [ @@ -70,6 +72,7 @@ let package = Package( name: "SwiftDocCUtilities", dependencies: [ .target(name: "SwiftDocC"), + .target(name: "DocCCommon"), .product(name: "NIOHTTP1", package: "swift-nio", condition: .when(platforms: [.macOS, .iOS, .linux, .android])), .product(name: "ArgumentParser", package: "swift-argument-parser") ], @@ -81,6 +84,7 @@ let package = Package( dependencies: [ .target(name: "SwiftDocCUtilities"), .target(name: "SwiftDocC"), + .target(name: "DocCCommon"), .target(name: "SwiftDocCTestUtilities"), ], resources: [ @@ -95,6 +99,7 @@ let package = Package( name: "SwiftDocCTestUtilities", dependencies: [ .target(name: "SwiftDocC"), + .target(name: "DocCCommon"), .product(name: "SymbolKit", package: "swift-docc-symbolkit"), ], swiftSettings: swiftSettings @@ -109,6 +114,25 @@ let package = Package( exclude: ["CMakeLists.txt"], swiftSettings: swiftSettings ), + + // A few common types and core functionality that's useable by all other targets. + .target( + name: "DocCCommon", + dependencies: [ + // This target shouldn't have any local dependencies so that all other targets can depend on it. + // We can add dependencies on SymbolKit and Markdown here but they're not needed yet. + ], + swiftSettings: [.swiftLanguageMode(.v6)] + ), + + .testTarget( + name: "DocCCommonTests", + dependencies: [ + .target(name: "DocCCommon"), + .target(name: "SwiftDocCTestUtilities"), + ], + swiftSettings: [.swiftLanguageMode(.v6)] + ), // Test app for SwiftDocCUtilities .executableTarget( diff --git a/Sources/SwiftDocCUtilities/CMakeLists.txt b/Sources/CommandLine/CMakeLists.txt similarity index 100% rename from Sources/SwiftDocCUtilities/CMakeLists.txt rename to Sources/CommandLine/CMakeLists.txt diff --git a/Sources/SwiftDocC/Model/SourceLanguage.swift b/Sources/DocCCommon/SourceLanguage.swift similarity index 95% rename from Sources/SwiftDocC/Model/SourceLanguage.swift rename to Sources/DocCCommon/SourceLanguage.swift index 2866213456..4483219757 100644 --- a/Sources/SwiftDocC/Model/SourceLanguage.swift +++ b/Sources/DocCCommon/SourceLanguage.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2023 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 @@ -9,7 +9,7 @@ */ /// A programming language. -public struct SourceLanguage: Hashable, Codable, Comparable { +public struct SourceLanguage: Hashable, Codable, Comparable, Sendable { /// The display name of the programming language. public var name: String /// A globally unique identifier for the language. @@ -132,7 +132,7 @@ public struct SourceLanguage: Hashable, Codable, Comparable { public static let metal = SourceLanguage(name: "Metal", id: "metal") /// The list of programming languages that are known to DocC. - public static var knownLanguages: [SourceLanguage] = [.swift, .objectiveC, .javaScript, .data, .metal] + public static let knownLanguages: [SourceLanguage] = [.swift, .objectiveC, .javaScript, .data, .metal] enum CodingKeys: CodingKey { case name @@ -157,7 +157,9 @@ public struct SourceLanguage: Hashable, Codable, Comparable { try container.encode(self.name, forKey: SourceLanguage.CodingKeys.name) try container.encode(self.id, forKey: SourceLanguage.CodingKeys.id) - try container.encodeIfNotEmpty(self.idAliases, forKey: SourceLanguage.CodingKeys.idAliases) + if !self.idAliases.isEmpty { + try container.encode(self.idAliases, forKey: SourceLanguage.CodingKeys.idAliases) + } try container.encode(self.linkDisambiguationID, forKey: SourceLanguage.CodingKeys.linkDisambiguationID) } diff --git a/Sources/SwiftDocC/Utility/CommonTypeExports.swift b/Sources/SwiftDocC/Utility/CommonTypeExports.swift new file mode 100644 index 0000000000..7194a5c125 --- /dev/null +++ b/Sources/SwiftDocC/Utility/CommonTypeExports.swift @@ -0,0 +1,13 @@ +/* + 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 +*/ + +public import DocCCommon + +public typealias SourceLanguage = DocCCommon.SourceLanguage diff --git a/Tests/SwiftDocCTests/Model/SourceLanguageTests.swift b/Tests/DocCCommonTests/SourceLanguageTests.swift similarity index 96% rename from Tests/SwiftDocCTests/Model/SourceLanguageTests.swift rename to Tests/DocCCommonTests/SourceLanguageTests.swift index 734c5c7a1d..d63b6dcaac 100644 --- a/Tests/SwiftDocCTests/Model/SourceLanguageTests.swift +++ b/Tests/DocCCommonTests/SourceLanguageTests.swift @@ -8,7 +8,7 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ -@testable import SwiftDocC +import DocCCommon import XCTest class SourceLanguageTests: XCTestCase { From 537037751e277d23fadff7a62b4ce542dd09d4b1 Mon Sep 17 00:00:00 2001 From: Joseph Heck Date: Fri, 14 Nov 2025 09:20:25 -1000 Subject: [PATCH 81/90] resolves mismatched in OpenAPI resource specs for type generation (#1239) * fixes OpenAPI spec mismatches between require properties and available properties * updating specs with feedback --- .../SwiftDocC.docc/Resources/LinkableEntities.json | 2 +- .../SwiftDocC.docc/Resources/RenderNode.spec.json | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftDocC/SwiftDocC.docc/Resources/LinkableEntities.json b/Sources/SwiftDocC/SwiftDocC.docc/Resources/LinkableEntities.json index a07d1c18a2..4b5e0a3b34 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/Resources/LinkableEntities.json +++ b/Sources/SwiftDocC/SwiftDocC.docc/Resources/LinkableEntities.json @@ -570,7 +570,7 @@ "type": "string", "enum": ["icon", "card"] }, - "reference": { + "identifier": { "type": "string", "format": "reference(ImageRenderReference)" } diff --git a/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json b/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json index 4f25303ac7..6cb6924e51 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json +++ b/Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json @@ -360,8 +360,7 @@ "type": "object", "required": [ "kind", - "tiles", - "content" + "tiles" ], "properties": { "kind": { @@ -526,8 +525,7 @@ "kind", "content", "media", - "mediaPosition", - "layout" + "mediaPosition" ], "properties": { "kind": { @@ -1850,7 +1848,7 @@ "type": "string", "enum": ["icon", "card"] }, - "reference": { + "identifier": { "type": "string", "format": "reference(ImageRenderReference)" } @@ -2011,7 +2009,7 @@ "type": "string", "format": "reference(RenderReference)" }, - "sections": { + "kind": { "type": "string", "enum": ["task", "assessment", "heading"] } @@ -2547,7 +2545,6 @@ }, "MentionsRenderSection": { "required": [ - "kind", "mentions" ], "type": "object", From a3f9fdac87737c0f57982b36da09ac9d6e7dccee Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 17 Nov 2025 12:45:50 -0800 Subject: [PATCH 82/90] build: move incorrect file movement (#1356) The SwiftDocCUtilities directory was renamed to CommandLine, and then renamed again. When The second rename occurred, the file was not restored. --- Sources/CMakeLists.txt | 1 + Sources/DocCCommon/CMakeLists.txt | 11 +++++++ Sources/SwiftDocC/CMakeLists.txt | 30 ++++++++++--------- .../CMakeLists.txt | 0 4 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 Sources/DocCCommon/CMakeLists.txt rename Sources/{CommandLine => SwiftDocCUtilities}/CMakeLists.txt (100%) diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 8f8e806655..840812426a 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -7,6 +7,7 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information #]] +add_subdirectory(DocCCommon) add_subdirectory(SwiftDocC) add_subdirectory(SwiftDocCUtilities) add_subdirectory(docc) diff --git a/Sources/DocCCommon/CMakeLists.txt b/Sources/DocCCommon/CMakeLists.txt new file mode 100644 index 0000000000..a393257167 --- /dev/null +++ b/Sources/DocCCommon/CMakeLists.txt @@ -0,0 +1,11 @@ +#[[ +This source file is part of the Swift open source project + +Copyright © 2014 - 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 +#]] + +add_library(DocCCommon STATIC + SourceLanguage.swift) diff --git a/Sources/SwiftDocC/CMakeLists.txt b/Sources/SwiftDocC/CMakeLists.txt index 6a54c8c802..c5e6ef0e26 100644 --- a/Sources/SwiftDocC/CMakeLists.txt +++ b/Sources/SwiftDocC/CMakeLists.txt @@ -10,13 +10,13 @@ See https://swift.org/LICENSE.txt for license information add_library(SwiftDocC Benchmark/Benchmark.swift Benchmark/BenchmarkResults.swift + Benchmark/Metrics.swift Benchmark/Metrics/Duration.swift Benchmark/Metrics/ExternalTopicsHash.swift Benchmark/Metrics/OutputSize.swift Benchmark/Metrics/PeakMemory.swift Benchmark/Metrics/TopicAnchorHash.swift Benchmark/Metrics/TopicGraphHash.swift - Benchmark/Metrics.swift "Catalog Processing/GeneratedCurationWriter.swift" Checker/Checker.swift Checker/Checkers/AbstractContainsFormattedTextOnly.swift @@ -198,16 +198,16 @@ add_library(SwiftDocC Model/Rendering/RenderContentCompiler.swift Model/Rendering/RenderContentConvertible.swift Model/Rendering/RenderContext.swift + Model/Rendering/RenderNode.Tag.swift + Model/Rendering/RenderNode.swift + Model/Rendering/RenderNodeTranslator.swift + Model/Rendering/RenderNodeVariant.swift Model/Rendering/RenderNode/AnyMetadata.swift Model/Rendering/RenderNode/CodableContentSection.swift Model/Rendering/RenderNode/CodableRenderReference.swift Model/Rendering/RenderNode/CodableRenderSection.swift Model/Rendering/RenderNode/RenderMetadata.swift Model/Rendering/RenderNode/RenderNode+Codable.swift - Model/Rendering/RenderNode.swift - Model/Rendering/RenderNode.Tag.swift - Model/Rendering/RenderNodeTranslator.swift - Model/Rendering/RenderNodeVariant.swift Model/Rendering/RenderReferenceStore.swift Model/Rendering/RenderSection.swift Model/Rendering/RenderSectionTranslator/AttributesSectionTranslator.swift @@ -238,22 +238,22 @@ add_library(SwiftDocC Model/Rendering/Symbol/PossibleValuesRenderSection.swift Model/Rendering/Symbol/PropertiesRenderSection.swift Model/Rendering/Symbol/PropertyListDetailsRenderSection.swift - Model/Rendering/Symbol/RelationshipsRenderSection.swift Model/Rendering/Symbol/RESTBodyRenderSection.swift Model/Rendering/Symbol/RESTEndpointRenderSection.swift Model/Rendering/Symbol/RESTExampleRenderSection.swift Model/Rendering/Symbol/RESTParametersRenderSection.swift Model/Rendering/Symbol/RESTResponseRenderSection.swift + Model/Rendering/Symbol/RelationshipsRenderSection.swift Model/Rendering/Symbol/SampleDownloadSection.swift Model/Rendering/Symbol/TaskGroupRenderSection.swift Model/Rendering/TopicsSectionStyle.swift + "Model/Rendering/Tutorial Article/TutorialArticleSection.swift" Model/Rendering/Tutorial/LineHighlighter.swift Model/Rendering/Tutorial/References/DownloadReference.swift Model/Rendering/Tutorial/References/XcodeRequirementReference.swift Model/Rendering/Tutorial/Sections/IntroRenderSection.swift Model/Rendering/Tutorial/Sections/TutorialAssessmentsRenderSection.swift Model/Rendering/Tutorial/Sections/TutorialSectionsRenderSection.swift - "Model/Rendering/Tutorial Article/TutorialArticleSection.swift" "Model/Rendering/Tutorials Overview/Resources/RenderTile.swift" "Model/Rendering/Tutorials Overview/Sections/CallToActionSection.swift" "Model/Rendering/Tutorials Overview/Sections/ContentAndMediaGroupSection.swift" @@ -299,7 +299,6 @@ add_library(SwiftDocC Model/Semantics/Parameter.swift Model/Semantics/Return.swift Model/Semantics/Throw.swift - Model/SourceLanguage.swift Model/TaskGroup.swift Semantics/Abstracted.swift Semantics/Article/Article.swift @@ -358,11 +357,11 @@ add_library(SwiftDocC Semantics/Options/TopicsVisualStyle.swift Semantics/Redirect.swift Semantics/Redirected.swift + Semantics/ReferenceResolver.swift Semantics/Reference/Links.swift Semantics/Reference/Row.swift Semantics/Reference/Small.swift Semantics/Reference/TabNavigator.swift - Semantics/ReferenceResolver.swift Semantics/Semantic.swift Semantics/SemanticAnalyzer.swift Semantics/Snippets/Snippet.swift @@ -381,6 +380,8 @@ add_library(SwiftDocC Semantics/Technology/Volume/Volume.swift Semantics/Timed.swift Semantics/Titled.swift + Semantics/TutorialArticle/Stack.swift + Semantics/TutorialArticle/TutorialArticle.swift Semantics/Tutorial/Assessments/Assessments.swift "Semantics/Tutorial/Assessments/Multiple Choice/Choice/Choice.swift" "Semantics/Tutorial/Assessments/Multiple Choice/Choice/Justification.swift" @@ -391,8 +392,6 @@ add_library(SwiftDocC Semantics/Tutorial/Tasks/TutorialSection.swift Semantics/Tutorial/Tutorial.swift Semantics/Tutorial/XcodeRequirement.swift - Semantics/TutorialArticle/Stack.swift - Semantics/TutorialArticle/TutorialArticle.swift Semantics/Visitor/SemanticVisitor.swift Semantics/Walker/SemanticWalker.swift Semantics/Walker/Walkers/SemanticTreeDumper.swift @@ -402,6 +401,7 @@ add_library(SwiftDocC Utility/Checksum.swift Utility/Collection+ConcurrentPerform.swift Utility/CollectionChanges.swift + Utility/CommonTypeExports.swift Utility/DataStructures/BidirectionalMap.swift Utility/DataStructures/GroupedSequence.swift Utility/DispatchGroup+Async.swift @@ -440,14 +440,14 @@ add_library(SwiftDocC Utility/Graphs/DirectedGraph+Paths.swift Utility/Graphs/DirectedGraph+Traversal.swift Utility/Graphs/DirectedGraph.swift - Utility/Language/EnglishLanguage.swift - Utility/Language/NativeLanguage.swift - Utility/ListItemUpdatable.swift Utility/LMDB/LMDB+Database.swift Utility/LMDB/LMDB+Environment.swift Utility/LMDB/LMDB+Error.swift Utility/LMDB/LMDB+Transaction.swift Utility/LMDB/LMDB.swift + Utility/Language/EnglishLanguage.swift + Utility/Language/NativeLanguage.swift + Utility/ListItemUpdatable.swift Utility/LogHandle.swift Utility/MarkupExtensions/AnyLink.swift Utility/MarkupExtensions/BlockDirectiveExtensions.swift @@ -463,6 +463,8 @@ add_library(SwiftDocC Utility/Synchronization.swift Utility/ValidatedURL.swift Utility/Version.swift) +target_link_libraries(SwiftDocC PRIVATE + DocCCommon) target_link_libraries(SwiftDocC PUBLIC SwiftMarkdown::Markdown DocC::SymbolKit diff --git a/Sources/CommandLine/CMakeLists.txt b/Sources/SwiftDocCUtilities/CMakeLists.txt similarity index 100% rename from Sources/CommandLine/CMakeLists.txt rename to Sources/SwiftDocCUtilities/CMakeLists.txt From 2ee8148753ffccf543481c59f46992a7d8438331 Mon Sep 17 00:00:00 2001 From: Victor Manuel Puga Ruiz <39507381+VictorPuga@users.noreply.github.com> Date: Tue, 18 Nov 2025 04:36:42 -0600 Subject: [PATCH 83/90] fix: fixed MultipleChoice symbol in Assessments docs (#1357) --- .../Top-Level Directives/Tutorial/Assessments/Assessments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/Top-Level Directives/Tutorial/Assessments/Assessments.md b/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/Top-Level Directives/Tutorial/Assessments/Assessments.md index 4fed90616c..e0e9a76699 100644 --- a/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/Top-Level Directives/Tutorial/Assessments/Assessments.md +++ b/Sources/docc/DocCDocumentation.docc/Reference Syntax/Tutorials Syntax/Top-Level Directives/Tutorial/Assessments/Assessments.md @@ -8,7 +8,7 @@ Tests the reader's knowledge at the end of a tutorial page. ## Overview -Use the `Assessment` directive to display an assessments section that helps the reader check their knowledge of your Swift framework or package APIs at the end of a tutorial page. An assessment includes a set of multiple-choice questions that you create using the`MultipleChoice`` directive. If the reader gets a question wrong, you can provide a hint that points them toward the correct answer so they can try again. +Use the `Assessment` directive to display an assessments section that helps the reader check their knowledge of your Swift framework or package APIs at the end of a tutorial page. An assessment includes a set of multiple-choice questions that you create using the ``MultipleChoice`` directive. If the reader gets a question wrong, you can provide a hint that points them toward the correct answer so they can try again. ``` @Tutorial(time: 30) { From a42ebbf8ec80e5409783a9eabce9540ffa136e17 Mon Sep 17 00:00:00 2001 From: Maya Epps <53411851+mayaepps@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:44:49 -0800 Subject: [PATCH 84/90] RenderIndex.Node accepts an optional type (#1360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since RenderIndex.Node’s type property is optional, the public initializer should accept an optional. Co-authored-by: Pat Shaughnessy --- Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift b/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift index 7b228244bb..19e4d476c8 100644 --- a/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift +++ b/Sources/SwiftDocC/Indexing/RenderIndexJSON/RenderIndex.swift @@ -223,7 +223,7 @@ extension RenderIndex { public init( title: String, path: String?, - type: String, + type: String?, children: [Node]?, isDeprecated: Bool, isExternal: Bool, From 5915e66d1eb071a4410bcf6aea0d9219a21a1f8e Mon Sep 17 00:00:00 2001 From: Maya Epps <53411851+mayaepps@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:04:57 -0800 Subject: [PATCH 85/90] Make a number of RenderNode constants vars (#1350) This updates some of the properties in the RenderNode tree to be mutable. A number of the top-level properties in RenderNode are already mutable, and making these additional properties mutable as well allows callers to modify these values. rdar://164748009 --- .../Content/RenderBlockContent.swift | 32 +++++++++---------- .../Symbol/AttributesRenderSection.swift | 4 +-- .../Symbol/ParameterRenderSection.swift | 2 +- .../Symbol/PossibleValuesRenderSection.swift | 4 +-- .../Symbol/PropertiesRenderSection.swift | 8 ++--- .../Symbol/RESTBodyRenderSection.swift | 6 ++-- .../Symbol/RESTEndpointRenderSection.swift | 2 +- .../Symbol/RESTParametersRenderSection.swift | 4 +-- .../Symbol/RESTResponseRenderSection.swift | 6 ++-- .../Symbol/TaskGroupRenderSection.swift | 4 +-- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift index 2b74a95e30..ad1827d012 100644 --- a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift +++ b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift @@ -630,7 +630,7 @@ public enum RenderBlockContent: Equatable { /// A list of rendering block elements. public typealias Cell = [RenderBlockContent] /// The list of row cells. - public let cells: [Cell] + public var cells: [Cell] /// Creates a new table row. /// - Parameter cells: The list of row cells to use. @@ -692,18 +692,18 @@ public enum RenderBlockContent: Equatable { /// A term rendered as content. public struct Term: Codable, Equatable { /// The term content. - public let inlineContent: [RenderInlineContent] + public var inlineContent: [RenderInlineContent] } /// A definition rendered as a list of block-content elements. public struct Definition: Codable, Equatable { /// The definition content. - public let content: [RenderBlockContent] + public var content: [RenderBlockContent] } /// The term in the term-list item. - public let term: Term + public var term: Term /// The definition in the term-list item. - public let definition: Definition + public var definition: Definition } /// A row in a grid-based layout system that describes a collection of columns. @@ -713,18 +713,18 @@ public enum RenderBlockContent: Equatable { /// This may be different then the count of ``columns`` array. For example, there may be /// individual columns that span multiple columns (specified with the column's /// ``Column/size`` property) or the row could be not fully filled with columns. - public let numberOfColumns: Int + public var numberOfColumns: Int /// The columns that should be rendered in this row. - public let columns: [Column] + public var columns: [Column] /// A column with a row in a grid-based layout system. public struct Column: Codable, Equatable { /// The number of columns in the parent row this column should span. - public let size: Int + public var size: Int /// The content that should be rendered in this column. - public let content: [RenderBlockContent] + public var content: [RenderBlockContent] } } @@ -734,21 +734,21 @@ public enum RenderBlockContent: Equatable { /// license, or copyright text. public struct Small: Codable, Equatable { /// The inline content that should be rendered. - public let inlineContent: [RenderInlineContent] + public var inlineContent: [RenderInlineContent] } /// A collection of content that should be rendered in a tab-based layout. public struct TabNavigator: Codable, Equatable { /// The tabs that make up this tab navigator. - public let tabs: [Tab] + public var tabs: [Tab] /// A titled tab inside a tab-based layout container. public struct Tab: Codable, Equatable { /// The title that should be used to identify this tab. - public let title: String + public var title: String /// The content that should be rendered in this tab. - public let content: [RenderBlockContent] + public var content: [RenderBlockContent] } } @@ -770,10 +770,10 @@ public enum RenderBlockContent: Equatable { } /// The style that should be used when rendering the link items. - public let style: Style + public var style: Style /// The topic render references for the pages that should be rendered in this links block. - public let items: [String] + public var items: [String] /// Create a new links block with the given style and topic render references. public init(style: RenderBlockContent.Links.Style, items: [String]) { @@ -788,7 +788,7 @@ public enum RenderBlockContent: Equatable { public let identifier: RenderReferenceIdentifier /// Any metadata associated with this video, like a caption. - public let metadata: RenderContentMetadata? + public var metadata: RenderContentMetadata? /// Create a new video with the given identifier and metadata. public init(identifier: RenderReferenceIdentifier, metadata: RenderContentMetadata? = nil) { diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/AttributesRenderSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/AttributesRenderSection.swift index d3f84bd3d6..18faf1e46c 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/AttributesRenderSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/AttributesRenderSection.swift @@ -14,9 +14,9 @@ import Foundation public struct AttributesRenderSection: RenderSection, Equatable { public var kind: RenderSectionKind = .attributes /// The section title. - public let title: String + public var title: String /// The list of attributes in this section. - public let attributes: [RenderAttribute]? + public var attributes: [RenderAttribute]? /// Creates a new attributes section. /// - Parameter title: The section title. diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/ParameterRenderSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/ParameterRenderSection.swift index 572e27c7bc..7a7f93eaa5 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/ParameterRenderSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/ParameterRenderSection.swift @@ -12,7 +12,7 @@ public struct ParametersRenderSection: RenderSection, Equatable { public var kind: RenderSectionKind = .parameters /// The list of parameter sub-sections. - public let parameters: [ParameterRenderSection] + public var parameters: [ParameterRenderSection] /// Creates a new parameters section with the given list. public init(parameters: [ParameterRenderSection]) { diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/PossibleValuesRenderSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/PossibleValuesRenderSection.swift index c5e0938448..5bedd1d523 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/PossibleValuesRenderSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/PossibleValuesRenderSection.swift @@ -29,9 +29,9 @@ public struct PossibleValuesRenderSection: RenderSection, Equatable { public var kind: RenderSectionKind = .possibleValues /// The title for the section, `nil` by default. - public let title: String? + public var title: String? /// The list of named values. - public let values: [NamedValue] + public var values: [NamedValue] /// Creates a new possible values section. /// - Parameter title: The section title. diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/PropertiesRenderSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/PropertiesRenderSection.swift index 527d042289..2be88815da 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/PropertiesRenderSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/PropertiesRenderSection.swift @@ -16,9 +16,9 @@ import Foundation public struct PropertiesRenderSection: RenderSection { public var kind: RenderSectionKind = .properties /// The title for this section. - public let title: String + public var title: String /// The list of properties. - public let items: [RenderProperty] + public var items: [RenderProperty] /// Creates a new property-list section. /// - Parameters: @@ -60,9 +60,9 @@ public struct RenderProperty: Codable, TextIndexing, Equatable { /// The list of possible type declarations for the property's value including additional details, if available. public let typeDetails: [TypeDetails]? /// Additional details about the property, if available. - public let content: [RenderBlockContent]? + public var content: [RenderBlockContent]? /// Additional list of attributes, if any. - public let attributes: [RenderAttribute]? + public var attributes: [RenderAttribute]? /// A mime-type associated with the property, if applicable. public let mimeType: String? /// If true, the property is required in its containing context. diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/RESTBodyRenderSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/RESTBodyRenderSection.swift index 5d62c5e98e..173ef737e3 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/RESTBodyRenderSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/RESTBodyRenderSection.swift @@ -14,7 +14,7 @@ import Foundation public struct RESTBodyRenderSection: RenderSection, Equatable { public var kind: RenderSectionKind = .restBody /// A title for the section. - public let title: String + public var title: String /// Content encoding MIME type for the request body. public let mimeType: String @@ -23,14 +23,14 @@ public struct RESTBodyRenderSection: RenderSection, Equatable { public let bodyContentType: [DeclarationRenderSection.Token] /// Details about the request body, if available. - public let content: [RenderBlockContent]? + public var content: [RenderBlockContent]? /// A list of request parameters, if applicable. /// /// If the body content is `multipart/form-data` encoded, it contains a list /// of parameters. Each of these parameters is a ``RESTParameter`` /// and it has its own value-content encoding, name, type, and description. - public let parameters: [RenderProperty]? + public var parameters: [RenderProperty]? /// Creates a new REST body section. /// - Parameters: diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/RESTEndpointRenderSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/RESTEndpointRenderSection.swift index b6e72e4a20..f727375d7c 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/RESTEndpointRenderSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/RESTEndpointRenderSection.swift @@ -50,7 +50,7 @@ public struct RESTEndpointRenderSection: RenderSection, Equatable { } /// The title for the section. - public let title: String + public var title: String /// The list of tokens. public let tokens: [Token] diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/RESTParametersRenderSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/RESTParametersRenderSection.swift index d2102b257f..ea7aefbadd 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/RESTParametersRenderSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/RESTParametersRenderSection.swift @@ -29,9 +29,9 @@ public enum RESTParameterSource: String, Codable { public struct RESTParametersRenderSection: RenderSection, Equatable { public var kind: RenderSectionKind = .restParameters /// The title for the section. - public let title: String + public var title: String /// The list of REST parameters. - public let parameters: [RenderProperty] + public var parameters: [RenderProperty] /// The kind of listed parameters. public let source: RESTParameterSource diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/RESTResponseRenderSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/RESTResponseRenderSection.swift index 332a9d81bf..826fef56d7 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/RESTResponseRenderSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/RESTResponseRenderSection.swift @@ -14,9 +14,9 @@ import Foundation public struct RESTResponseRenderSection: RenderSection, Equatable { public var kind: RenderSectionKind = .restResponses /// The title for the section. - public let title: String + public var title: String /// The list of possible REST responses. - public let responses: [RESTResponse] + public var responses: [RESTResponse] enum CodingKeys: String, CodingKey { case kind @@ -70,7 +70,7 @@ public struct RESTResponse: Codable, TextIndexing, Equatable { /// A type declaration of the response's content. public let type: [DeclarationRenderSection.Token] /// Response details, if any. - public let content: [RenderBlockContent]? + public var content: [RenderBlockContent]? /// Creates a new REST response section. /// - Parameters: diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/TaskGroupRenderSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/TaskGroupRenderSection.swift index 0c56f378b5..cb944da1a8 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/TaskGroupRenderSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/TaskGroupRenderSection.swift @@ -13,9 +13,9 @@ public struct TaskGroupRenderSection: RenderSection, Equatable { public let kind: RenderSectionKind = .taskGroup /// An optional title for the section. - public let title: String? + public var title: String? /// An optional abstract summary for the section. - public let abstract: [RenderInlineContent]? + public var abstract: [RenderInlineContent]? /// An optional discussion for the section. public var discussion: (any RenderSection)? { get { From d3d19c3a2f857e8cf68c557a9fd8909de407d50d Mon Sep 17 00:00:00 2001 From: Raluca Coutinho <38136087+RalucaP@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:34:41 +0100 Subject: [PATCH 86/90] Fix api collection icon rendering (#1343) * Add test for API Collection icon rendering in external references Adds tests to verify that API Collections (articles with Topics sections) are correctly identified as .collectionGroup kind in linkable entities, ensuring cross-framework references display the correct icon. Includes regression test for explicit @PageKind(article) metadata override behavior to ensure the fix doesn't break existing functionality. The first test is temporarily skipped until the fix is implemented in the next commit. rdar://135837611 * Fix API Collection icon rendering for external references Ensures API Collections are correctly identified as collectionGroup kind in linkable entities by using DocumentationContentRenderer.roleForArticle logic. This fixes cross-framework references where API Collections were incorrectly showing Article icons instead of Collection Group icons. rdar://135837611 --- .../LinkTargets/LinkDestinationSummary.swift | 13 ++- .../LinkDestinationSummaryTests.swift | 90 +++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 993e8fb315..911163c327 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -1017,7 +1017,18 @@ private extension DocumentationNode { // specialized articles, like sample code pages, that benefit from being treated as articles in // some parts of the compilation process (like curation) but not others (like link destination // summary creation and render node translation). - return metadata?.pageKind?.kind.documentationNodeKind ?? kind + let baseKind = metadata?.pageKind?.kind.documentationNodeKind ?? kind + + // For articles, check if they should be treated as API Collections (collectionGroup). + // This ensures that linkable entities have the same kind detection logic as the rendering system, + // fixing cross-framework references where API Collections were incorrectly showing as articles. + if baseKind == .article, + let article = semantic as? Article, + DocumentationContentRenderer.roleForArticle(article, nodeKind: kind) == .collectionGroup { + return .collectionGroup + } + + return baseKind } } diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 5ac4655d92..43bb6f0739 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -829,4 +829,94 @@ class LinkDestinationSummaryTests: XCTestCase { "doc://com.example.mymodule/documentation/MyModule/MyClass/myFunc()-9a7po", ]) } + + /// Tests that API Collections (articles with Topics sections) are correctly identified as `.collectionGroup` + /// kind in linkable entities, ensuring cross-framework references display the correct icon. + func testAPICollectionKindForLinkDestinationSummary() async throws { + let symbolGraph = makeSymbolGraph( + moduleName: "TestModule", + symbols: [makeSymbol(id: "test-class", kind: .class, pathComponents: ["TestClass"])] + ) + + let catalogHierarchy = Folder(name: "unit-test.docc", content: [ + TextFile(name: "APICollection.md", utf8Content: """ + # API Collection + + This is an API Collection that curates symbols. + + ## Topics + + - ``TestModule/TestClass`` + """), + JSONFile(name: "TestModule.symbols.json", content: symbolGraph) + ]) + + let (_, context) = try await loadBundle(catalog: catalogHierarchy) + let converter = DocumentationNodeConverter(context: context) + + let apiCollectionReference = ResolvedTopicReference( + bundleID: context.inputs.id, + path: "/documentation/unit-test/APICollection", + sourceLanguage: .swift + ) + let node = try context.entity(with: apiCollectionReference) + let renderNode = converter.convert(node) + + let summaries = node.externallyLinkableElementSummaries(context: context, renderNode: renderNode) + let pageSummary = try XCTUnwrap(summaries.first) + + XCTAssertEqual(pageSummary.kind, .collectionGroup) + XCTAssertEqual(pageSummary.title, "API Collection") + XCTAssertEqual(pageSummary.abstract, [.text("This is an API Collection that curates symbols.")]) + + // Verify round-trip encoding preserves the correct kind + try assertRoundTripCoding(pageSummary) + } + + /// Tests that explicit `@PageKind(article)` metadata overrides API Collection detection, + /// ensuring that explicit page kind directives take precedence over automatic detection. + func testExplicitPageKindOverridesAPICollectionDetection() async throws { + let symbolGraph = makeSymbolGraph( + moduleName: "TestModule", + symbols: [makeSymbol(id: "test-class", kind: .class, pathComponents: ["TestClass"])] + ) + + let catalogHierarchy = Folder(name: "unit-test.docc", content: [ + TextFile(name: "ExplicitArticle.md", utf8Content: """ + # Explicit Article + + This looks like an API Collection but is explicitly marked as an article. + + @Metadata { + @PageKind(article) + } + + ## Topics + + - ``TestModule/TestClass`` + """), + JSONFile(name: "TestModule.symbols.json", content: symbolGraph) + ]) + + let (_, context) = try await loadBundle(catalog: catalogHierarchy) + let converter = DocumentationNodeConverter(context: context) + + let explicitArticleReference = ResolvedTopicReference( + bundleID: context.inputs.id, + path: "/documentation/unit-test/ExplicitArticle", + sourceLanguage: .swift + ) + let node = try context.entity(with: explicitArticleReference) + let renderNode = converter.convert(node) + + let summaries = node.externallyLinkableElementSummaries(context: context, renderNode: renderNode) + let pageSummary = try XCTUnwrap(summaries.first) + + // Should be .article because of explicit @PageKind(article), not .collectionGroup + XCTAssertEqual(pageSummary.kind, .article) + XCTAssertEqual(pageSummary.title, "Explicit Article") + + // Verify round-trip encoding preserves the correct kind + try assertRoundTripCoding(pageSummary) + } } From 8089d301cd23dc625a5439547d069b9fc5422bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 1 Dec 2025 09:40:58 +0100 Subject: [PATCH 87/90] [Low level] Store SourceLanguage properties outside of the main structure use a new bit-set type for "sets" of languages (#1355) * Implement SourceLanguage as a tiny identifier with properties stored separately This idea came from after realizing that in practice accesses are _almost_ exclusively `id`, `==`, or `<`. * Deprecate mutating SourceLanguage properties * Remove unnecessary custom language sorting * Avoid accessing the language ID where it's not necessary * Move "_TinySmallValueIntSet" to DocCCommon as "_FixedSizeBitSet" * Support different sizes of _FixedSizeBitSet * Add Collection conformance to _FixedSizeBitSet * Specialize a few common collection methods * Use masking shifts and overflow adds in other _FixedSizeBitSet implementations * Add a SmallSourceLanguageSet type * Rely only in `_id` comparison for known language sorting * Use new small language set type in link resolution code * Use new small language set type inside ResolvedTopicReference * Use new small language set type inside DocumentationDataVariantsTrait * Fix implementation code comments about direction for layout of values/bits * Add new source files to CMakeLists for Windows CI * User simpler parameter name for ResolvedTopicReference initializer * Update additional callers to prefer passing source languages rather than their string IDs * Misc minor code comment fixes and clarifications --- Sources/DocCCommon/CMakeLists.txt | 2 + Sources/DocCCommon/FixedSizeBitSet.swift | 294 +++++++++ Sources/DocCCommon/Mutex.swift | 46 ++ Sources/DocCCommon/SourceLanguage.swift | 564 +++++++++++++++--- .../GeneratedCurationWriter.swift | 2 +- .../Infrastructure/DocumentationContext.swift | 13 +- .../Infrastructure/DocumentationCurator.swift | 2 +- .../Link Resolution/LinkResolver.swift | 2 +- .../Link Resolution/PathHierarchy+Find.swift | 2 +- .../PathHierarchy+TypeSignature.swift | 2 +- ...ierarchy+TypeSignatureDisambiguation.swift | 169 +----- .../Link Resolution/PathHierarchy.swift | 8 +- .../PathHierarchyBasedLinkResolver.swift | 5 +- .../GeneratedDocumentationTopics.swift | 13 +- .../Topic Graph/AutomaticCuration.swift | 10 +- .../LinkTargets/LinkDestinationSummary.swift | 8 +- .../SwiftDocC/Model/DocumentationNode.swift | 6 +- Sources/SwiftDocC/Model/Identifier.swift | 53 +- .../Model/ParametersAndReturnValidator.swift | 2 +- .../DocumentationContentRenderer.swift | 2 +- .../RenderHierarchyTranslator.swift | 2 +- .../Model/Rendering/RenderContext.swift | 2 +- .../Rendering/RenderNodeTranslator.swift | 24 +- .../DeclarationsSectionTranslator.swift | 6 +- .../Symbol/DocumentationDataVariants.swift | 27 +- .../FixedSizeBitSetTests.swift | 263 ++++++++ .../SmallSourceLanguageSetTests.swift | 154 +++++ .../DocCCommonTests/SourceLanguageTests.swift | 149 ++++- .../TinySmallValueIntSetTests.swift | 149 ----- .../ParametersAndReturnValidatorTests.swift | 6 +- .../DocumentationContentRendererTests.swift | 2 +- 31 files changed, 1480 insertions(+), 509 deletions(-) create mode 100644 Sources/DocCCommon/FixedSizeBitSet.swift create mode 100644 Sources/DocCCommon/Mutex.swift create mode 100644 Tests/DocCCommonTests/FixedSizeBitSetTests.swift create mode 100644 Tests/DocCCommonTests/SmallSourceLanguageSetTests.swift delete mode 100644 Tests/SwiftDocCTests/Infrastructure/TinySmallValueIntSetTests.swift diff --git a/Sources/DocCCommon/CMakeLists.txt b/Sources/DocCCommon/CMakeLists.txt index a393257167..9ea7088e8a 100644 --- a/Sources/DocCCommon/CMakeLists.txt +++ b/Sources/DocCCommon/CMakeLists.txt @@ -8,4 +8,6 @@ See https://swift.org/LICENSE.txt for license information #]] add_library(DocCCommon STATIC + FixedSizeBitSet.swift + Mutex.swift SourceLanguage.swift) diff --git a/Sources/DocCCommon/FixedSizeBitSet.swift b/Sources/DocCCommon/FixedSizeBitSet.swift new file mode 100644 index 0000000000..9ef13a9bb6 --- /dev/null +++ b/Sources/DocCCommon/FixedSizeBitSet.swift @@ -0,0 +1,294 @@ +/* + This source file is part of the Swift.org open source project + + 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 + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +/// A fixed size bit set, used for storing very small amounts of small integer values. +/// +/// This type can only store values that are `0 ..< Storage.bitWidth` which makes it _unsuitable_ as a general purpose set-algebra type. +/// However, in specialized cases where the caller can guarantee that all values are in bounds, this type can offer a memory and performance improvement. +package struct _FixedSizeBitSet: Sendable { + package typealias Element = Int + + package init() {} + + @usableFromInline + private(set) var storage: Storage = 0 + + @inlinable + init(storage: Storage) { + self.storage = storage + } +} + +// MARK: Set Algebra + +extension _FixedSizeBitSet: SetAlgebra { + private static func mask(_ number: Int) -> Storage { + precondition(number < Storage.bitWidth, "Number \(number) is out of bounds (0..<\(Storage.bitWidth))") + return 1 &<< number + } + + @inlinable + @discardableResult + mutating package func insert(_ member: Int) -> (inserted: Bool, memberAfterInsert: Int) { + let newStorage = storage | _FixedSizeBitSet.mask(member) + defer { + storage = newStorage + } + return (newStorage != storage, member) + } + + @inlinable + @discardableResult + mutating package func remove(_ member: Int) -> Int? { + let newStorage = storage & ~_FixedSizeBitSet.mask(member) + defer { + storage = newStorage + } + return newStorage != storage ? member : nil + } + + @inlinable + @discardableResult + mutating package func update(with member: Int) -> Int? { + let (inserted, _) = insert(member) + return inserted ? nil : member + } + + @inlinable + package func contains(_ member: Int) -> Bool { + storage & _FixedSizeBitSet.mask(member) != 0 + } + + @inlinable + package func isSuperset(of other: Self) -> Bool { + (storage & other.storage) == other.storage + } + + @inlinable + package func union(_ other: Self) -> Self { + .init(storage: storage | other.storage) + } + + @inlinable + package func intersection(_ other: Self) -> Self { + .init(storage: storage & other.storage) + } + + @inlinable + package func symmetricDifference(_ other: Self) -> Self { + .init(storage: storage ^ other.storage) + } + + @inlinable + mutating package func formUnion(_ other: Self) { + storage |= other.storage + } + + @inlinable + mutating package func formIntersection(_ other: Self) { + storage &= other.storage + } + + @inlinable + mutating package func formSymmetricDifference(_ other: Self) { + storage ^= other.storage + } + + @inlinable + package var isEmpty: Bool { + storage == 0 + } +} + +// MARK: Sequence + +extension _FixedSizeBitSet: Sequence { + @inlinable + package func makeIterator() -> some IteratorProtocol { + _Iterator(set: self) + } + + private struct _Iterator: IteratorProtocol { + typealias Element = Int + + private var storage: Storage + private var current: Int = -1 + + @inlinable + init(set: _FixedSizeBitSet) { + self.storage = set.storage + } + + @inlinable + mutating func next() -> Int? { + guard storage != 0 else { + return nil + } + // If the set is somewhat sparse, we can find the next element faster by shifting to the next value. + // This saves needing to do `contains()` checks for all the numbers since the previous element. + let amountToShift = storage.trailingZeroBitCount &+ 1 + storage &>>= amountToShift + + current &+= amountToShift + return current + } + } +} + +// MARK: Collection + +extension _FixedSizeBitSet: Collection { + // Collection conformance requires an `Index` type, that the collection can advance, and `startIndex` and `endIndex` accessors that follow certain requirements. + // + // For this design, as a hidden implementation detail, the `Index` holds the bit offset to the element. + + @inlinable + package subscript(position: Index) -> Int { + precondition(position.bit < Storage.bitWidth, "Index \(position.bit) out of bounds") + // Because the index stores the bit offset, which is also the value, we can simply return the value without accessing the storage. + return Int(position.bit) + } + + package struct Index: Comparable { + // The bit offset into the storage to the value + fileprivate var bit: UInt8 + + package static func < (lhs: Self, rhs: Self) -> Bool { + lhs.bit < rhs.bit + } + } + + @inlinable + package var startIndex: Index { + // This is the index (bit offset) to the smallest value in the bit set. + Index(bit: UInt8(storage.trailingZeroBitCount)) + } + + @inlinable + package var endIndex: Index { + // For a valid collection, the end index is required to be _exactly_ one past the last in-bounds index, meaning; `index(after: LAST_IN-BOUNDS_INDEX)` + // If the collection implementation doesn't satisfy this requirement, it will have an infinitely long `indices` collection. + // This either results in infinite implementations or hits internal preconditions in other Swift types that that collection has more elements than its `count`. + + // See `index(after:)` below for explanation of how the index after is calculated. + let lastInBoundsBit = UInt8(Storage.bitWidth &- storage.leadingZeroBitCount) + return Index(bit: lastInBoundsBit &+ UInt8((storage &>> lastInBoundsBit).trailingZeroBitCount)) + } + + @inlinable + package func index(after currentIndex: Index) -> Index { + // To advance the index we have to find the next 1 bit _after_ the current bit. + // For example, consider the following 16 bits, where values are represented from right to left: + // 0110 0010 0110 0010 + // + // To go from the first index to the second index, we need to count the number of 0 bits between it and the next 1 bit. + // We get this value by shifting the bits by one past the current index: + // 0110 0010 0110 0010 + // ╰╴current index + // 0001 1000 1001 1000 + // ~~~ 3 trailing zero bits + // + // The second index's absolute value is the one past the first index's value plus the number of trailing zero bits in the shifted value. + // + // For the third index we repeat the same process, starting by shifting the bits by one past second index: + // 0110 0010 0110 0010 + // ╰╴current index + // 0000 0001 1000 1001 + // 0 trailing zero bits + // + // This time there are no trailing zero bits in the shifted value, so the third index's absolute value is just one past the second index. + let shift = currentIndex.bit &+ 1 + return Index(bit: shift &+ UInt8((storage &>> shift).trailingZeroBitCount)) + } + + @inlinable + package func formIndex(after index: inout Index) { + // See `index(after:)` above for explanation. + index.bit &+= 1 + index.bit &+= UInt8((storage &>> index.bit).trailingZeroBitCount) + } + + @inlinable + package func distance(from start: Index, to end: Index) -> Int { + // To compute the distance between two indices we have to find the number of 1 bits from the start index to (but excluding) the end index. + // For example, consider the following 16 bits, where values are represented from right to left: + // 0110 0010 0110 0010 + // end╶╯ ╰╴start + // + // To find the distance between the second index and the fourth index, we need to count the number of 0 bits between it and the next 1 bit. + // We limit the calculation to this range in two steps. + // + // First, we mask out all the bits above the end index: + // end╶╮ ╭╴start + // 0110 0010 0110 0010 + // 0000 0011 1111 1111 mask + // + // Because collections can have end indices that extend out-of-bounds we need to clamp the mask from a larger integer type to avoid it wrapping around to 0. + let mask = Storage(clamping: (1 &<< UInt(end.bit)) &- 1) + var distance = storage & mask + + // Then, we shift away all the bits below the start index: + // end╶╮ ╭╴start + // 0000 0010 0110 0010 + // 0000 0000 0000 1001 + distance &>>= start.bit + + // The distance from start to end is the number of 1 bits in this number. + return distance.nonzeroBitCount + } + + @inlinable + package var first: Element? { + isEmpty ? nil : storage.trailingZeroBitCount + } + + @inlinable + package func min() -> Element? { + first // The elements are already sorted + } + + @inlinable + package func sorted() -> [Element] { + Array(self) // The elements are already sorted + } + + @inlinable + package var count: Int { + storage.nonzeroBitCount + } +} + +// MARK: Hashable + +extension _FixedSizeBitSet: Hashable {} + +// MARK: Combinations + +extension _FixedSizeBitSet { + /// Returns a list of all possible combinations of the elements in the set, in order of increasing number of elements. + package func allCombinationsOfValues() -> [Self] { + // Leverage the fact that bits of an Int represent the possible combinations. + let smallest = storage.trailingZeroBitCount + + var combinations: [Self] = [] + combinations.reserveCapacity((1 &<< count /*known to be less than Storage.bitWidth */) - 1) + + for raw in 1 ... storage &>> smallest { + let combination = Self(storage: Storage(raw &<< smallest)) + + // Filter out any combinations that include columns that are the same for all overloads + guard self.isSuperset(of: combination) else { continue } + + combinations.append(combination) + } + // The bits of larger and larger Int values won't be in order of number of bits set, so we sort them. + return combinations.sorted(by: { $0.count < $1.count }) + } +} diff --git a/Sources/DocCCommon/Mutex.swift b/Sources/DocCCommon/Mutex.swift new file mode 100644 index 0000000000..27453ce1db --- /dev/null +++ b/Sources/DocCCommon/Mutex.swift @@ -0,0 +1,46 @@ +/* + 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 +*/ + +#if os(macOS) || os(iOS) +import Darwin + +// This type is designed to have the same API surface as 'Synchronization.Mutex'. +// It's different from 'SwiftDocC.Synchronized' which requires that the wrapped value is `Copyable`. +// +// When we can require macOS 15.0 we can remove this custom type and use 'Synchronization.Mutex' directly on all platforms. +struct Mutex: ~Copyable, @unchecked Sendable { + private var value: UnsafeMutablePointer + private var lock: UnsafeMutablePointer + + init(_ initialValue: consuming sending Value) { + value = UnsafeMutablePointer.allocate(capacity: 1) + value.initialize(to: initialValue) + + lock = UnsafeMutablePointer.allocate(capacity: 1) + lock.initialize(to: os_unfair_lock()) + } + + deinit { + value.deallocate() + lock.deallocate() + } + + borrowing func withLock(_ body: (inout sending Value) throws(E) -> sending Result) throws(E) -> sending Result { + os_unfair_lock_lock(lock) + defer { os_unfair_lock_unlock(lock) } + + return try body(&value.pointee) + } +} +#else +import Synchronization + +typealias Mutex = Synchronization.Mutex +#endif diff --git a/Sources/DocCCommon/SourceLanguage.swift b/Sources/DocCCommon/SourceLanguage.swift index 4483219757..3de76d214a 100644 --- a/Sources/DocCCommon/SourceLanguage.swift +++ b/Sources/DocCCommon/SourceLanguage.swift @@ -8,60 +8,262 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ -/// A programming language. +/// A programming language, for example "Swift" or "Objective-C". public struct SourceLanguage: Hashable, Codable, Comparable, Sendable { + /// Using only an 8-bit value as an identifier technically limits a single DocC execution to 256 different languages. + /// This may sound like a significant limitation. However, in practice almost all content deals with either 1 or 2 languages. + /// There is some known content with 3 languages but beyond that 4 or 5 or more languages is increasingly less common/realistic. + /// + /// Thus, in practice it's deemed unrealistic that any content would ever represent symbols from 256 different programming languages. + /// Note that this limitation only applies to the languages within a single DocC execution and not globally. + /// Two different DocC executions can each represent 200+ different languages, resulting in a total of 400+ languages together. + /// + /// When DocC works with programming languages it's very common to work with a set of languages. + /// For example, a set can represent a page's list of supported language or it can represent a filter of common languages between to pages. + /// Because each DocC execution only involves very few unique languages in practice, + /// having a very small private identifier type allows DocC to pack all the languages it realistically needs into a small (inlineable) value. + fileprivate var _id: UInt8 // this is fileprivate so that SmallSourceLanguageSet (below) can access it +} + +/// The private type that holds the information for each source language +private struct _SourceLanguageInformation: Equatable { + var name: String + var id: String + var idAliases: [String] = [] + var linkDisambiguationID: String + + init(name: String, id: String, idAliases: [String] = [], linkDisambiguationID: String? = nil) { + self.name = name + self.id = id + self.idAliases = idAliases + self.linkDisambiguationID = linkDisambiguationID ?? id + } +} + +// MARK: Known Languages + +private let _knownLanguages = [ + // NOTE: The known languages have identifiers that is also their sort order when there are no unknown languages + + // Swift + _SourceLanguageInformation(name: "Swift", id: "swift"), + + // Miscellaneous data, that's not a programming language. + _SourceLanguageInformation(name: "Data", id: "data"), + + // JavaScript or another language that conforms to the ECMAScript specification. + _SourceLanguageInformation(name: "JavaScript", id: "javascript"), + + // The Metal programming language. + _SourceLanguageInformation(name: "Metal", id: "metal"), + + // Objective-C, C, and C++ + _SourceLanguageInformation( + name: "Objective-C", + id: "occ", + idAliases: [ + "objective-c", + "objc", + "c", // FIXME: DocC should display C as its own language (https://github.com/swiftlang/swift-docc/issues/169). + "c++", // FIXME: DocC should display C++ and Objective-C++ as their own languages (https://github.com/swiftlang/swift-docc/issues/767) + "objective-c++", + "objc++", + "occ++", + ], + linkDisambiguationID: "c" + ), +] + +private extension SourceLanguage { + private var _isKnownLanguage: Bool { + Self._isKnownLanguageID(_id) + } + private static func _isKnownLanguageID(_ id: UInt8) -> Bool { + id < _numberOfKnownLanguages + } + + private static let _numberOfKnownLanguages = UInt8(_knownLanguages.count) + private static let _maximumNumberOfUnknownLanguages: UInt8 = .max - _numberOfKnownLanguages +} + +// Public accessors for known languages +public extension SourceLanguage { + // NOTE: The known languages have identifiers that is also their sort order when there are no unknown languages + + /// The Swift programming language. + static let swift = SourceLanguage(_id: 0) + + /// Miscellaneous data, that's not a programming language. + /// + /// For example, use this to represent JSON or XML content. + static let data = SourceLanguage(_id: 1) + /// The JavaScript programming language or another language that conforms to the ECMAScript specification. + static let javaScript = SourceLanguage(_id: 2) + /// The Metal programming language. + static let metal = SourceLanguage(_id: 3) + + /// The Objective-C programming language. + static let objectiveC = SourceLanguage(_id: 4) + + /// The list of programming languages that are known to DocC. + static let knownLanguages: [SourceLanguage] = [.swift, .objectiveC, .javaScript, .data, .metal] +} + +private let _unknownLanguages = Mutex([_SourceLanguageInformation]()) + +// MARK: Language properties + +private extension SourceLanguage { + private func _accessInfo() -> _SourceLanguageInformation { + Self._accessInfo(id: _id) + } + + private static func _accessInfo(id: UInt8) -> _SourceLanguageInformation { + let (unknownIndex, isKnownLanguage) = id.subtractingReportingOverflow(SourceLanguage._numberOfKnownLanguages) + return if isKnownLanguage { + _knownLanguages[Int(id)] + } else { + _unknownLanguages.withLock { $0[Int(unknownIndex)] } + } + } + + private func _accessInfo(withUnlockedUnknownLanguages unknownLanguages: borrowing [_SourceLanguageInformation]) -> _SourceLanguageInformation { + let (unknownIndex, isKnownLanguage) = _id.subtractingReportingOverflow(SourceLanguage._numberOfKnownLanguages) + return if isKnownLanguage { + _knownLanguages[Int(_id)] + } else { + unknownLanguages[Int(unknownIndex)] + } + } + + private mutating func _addOrFindExisting(unknownLanguage: _SourceLanguageInformation, withUnlockedUnknownLanguages unknownLanguages: inout [_SourceLanguageInformation]) { + self._id = Self._addingOrFindingExisting(unknownLanguageInfo: unknownLanguage, withUnlockedUnknownLanguages: &unknownLanguages) + } + + private static func _addingOrFindingExisting(unknownLanguageInfo: _SourceLanguageInformation, withUnlockedUnknownLanguages unknownLanguages: inout [_SourceLanguageInformation]) -> UInt8 { + if let existingIndex = unknownLanguages.firstIndex(of: unknownLanguageInfo) { + return _languageID(unknownLanguageIndex: existingIndex) + } else { + unknownLanguages.append(unknownLanguageInfo) + return _languageID(unknownLanguageIndex: unknownLanguages.count - 1) + } + } + + private static func _languageID(unknownLanguageIndex: Int) -> UInt8 { + precondition(unknownLanguageIndex < _maximumNumberOfUnknownLanguages, """ + Unexpectedly created more than 256 different programming languages in a single DocC execution. \ + This is considered highly unlikely in real content and is possibly caused by some programming bug that is frequently modifying existing source languages. + """) + return _numberOfKnownLanguages + UInt8(clamping: unknownLanguageIndex) + } +} + +// Public accessors for each language property +public extension SourceLanguage { /// The display name of the programming language. - public var name: String + var name: String { + get { _accessInfo().name } + @available(*, deprecated, message: "Create a new source language using 'init(name:id:idAliases:linkDisambiguationID:)' instead. This deprecated API will be removed after 6.4 is released.") + set { + // Modifying a language in any way create a new entry. This is generally discouraged because it easily creates a situation where language ID strings aren't globally unique anymore + _unknownLanguages.withLock { unknownLanguages in + var copy = _accessInfo(withUnlockedUnknownLanguages: unknownLanguages) + copy.name = newValue + _addOrFindExisting(unknownLanguage: copy, withUnlockedUnknownLanguages: &unknownLanguages) + } + } + } /// A globally unique identifier for the language. - public var id: String + var id: String { + get { _accessInfo().id } + @available(*, deprecated, message: "Create a new source language using 'init(name:id:idAliases:linkDisambiguationID:)' instead. This deprecated API will be removed after 6.4 is released.") + set { + // Modifying a language in any way create a new entry. This is generally discouraged because it easily creates a situation where language ID strings aren't globally unique anymore + _unknownLanguages.withLock { unknownLanguages in + var copy = _accessInfo(withUnlockedUnknownLanguages: unknownLanguages) + copy.id = newValue + _addOrFindExisting(unknownLanguage: copy, withUnlockedUnknownLanguages: &unknownLanguages) + } + } + } /// Aliases for the language's identifier. - public var idAliases: [String] = [] + var idAliases: [String] { + get { _accessInfo().idAliases } + @available(*, deprecated, message: "Create a new source language using 'init(name:id:idAliases:linkDisambiguationID:)' instead. This deprecated API will be removed after 6.4 is released.") + set { + // Modifying a language in any way create a new entry. This is generally discouraged because it easily creates a situation where language ID strings aren't globally unique anymore + _unknownLanguages.withLock { unknownLanguages in + var copy = _accessInfo(withUnlockedUnknownLanguages: unknownLanguages) + copy.idAliases = newValue + _addOrFindExisting(unknownLanguage: copy, withUnlockedUnknownLanguages: &unknownLanguages) + } + } + } /// The identifier to use for link disambiguation purposes. - public var linkDisambiguationID: String + var linkDisambiguationID: String { + get { _accessInfo().linkDisambiguationID } + @available(*, deprecated, message: "Create a new source language using 'init(name:id:idAliases:linkDisambiguationID:)' instead. This deprecated API will be removed after 6.4 is released.") + set { + // Modifying a language in any way create a new entry. This is generally discouraged because it easily creates a situation where language ID strings aren't globally unique anymore + _unknownLanguages.withLock { unknownLanguages in + var copy = _accessInfo(withUnlockedUnknownLanguages: unknownLanguages) + copy.linkDisambiguationID = newValue + _addOrFindExisting(unknownLanguage: copy, withUnlockedUnknownLanguages: &unknownLanguages) + } + } + } +} +// MARK: Creating languages + +// Public initializers +public extension SourceLanguage { /// Creates a new language with a given name and identifier. /// - Parameters: /// - name: The display name of the programming language. /// - id: A globally unique identifier for the language. /// - idAliases: Aliases for the language's identifier. /// - linkDisambiguationID: The identifier to use for link disambiguation purposes. - public init(name: String, id: String, idAliases: [String] = [], linkDisambiguationID: String? = nil) { - self.name = name - self.id = id - self.idAliases = idAliases - self.linkDisambiguationID = linkDisambiguationID ?? id + init(name: String, id: String, idAliases: [String] = [], linkDisambiguationID: String? = nil) { + let newInfo = _SourceLanguageInformation(name: name, id: id, idAliases: idAliases, linkDisambiguationID: linkDisambiguationID) + + // Before creating a new language, check if there is one that matches all the information + if let existing = Self._knownLanguage(withIdentifier: id), newInfo == Self._accessInfo(id: existing._id) { + self = existing + return + } + + self._id = _unknownLanguages.withLock { unknownLanguages in + Self._addingOrFindingExisting( + unknownLanguageInfo: .init(name: name, id: id, idAliases: idAliases, linkDisambiguationID: linkDisambiguationID), + withUnlockedUnknownLanguages: &unknownLanguages + ) + } } - + /// Finds the programming language that matches a given identifier, or creates a new one if it finds no existing language. /// - Parameter id: The identifier of the programming language. - public init(id: String) { - switch id { - case "swift": self = .swift - case "occ", "objc", "objective-c", "c": self = .objectiveC - // FIXME: DocC should display C++ and Objective-C++ as their own languages (https://github.com/swiftlang/swift-docc/issues/767) - case "occ++", "objc++", "objective-c++", "c++": self = .objectiveC - case "javascript": self = .javaScript - case "data": self = .data - case "metal": self = .metal - default: - self.name = id - self.id = id - self.linkDisambiguationID = id + init(id: String) { + if let known = Self._knownLanguage(withIdentifier: id) { + self = known + } else { + self._id = _unknownLanguages.withLock { unknownLanguages in + Self._addingOrFindingExisting(unknownLanguageInfo: .init(name: id, id: id), withUnlockedUnknownLanguages: &unknownLanguages) + } } } - + /// Finds the programming language that matches a given display name, or creates a new one if it finds no existing language. /// /// - Parameter name: The display name of the programming language. - public init(name: String) { - if let knownLanguage = SourceLanguage.firstKnownLanguage(withName: name) { + init(name: String) { + let id = name.lowercased() + if let knownLanguage = Self.knownLanguage(withName: name) ?? Self._knownLanguage(withIdentifier: id) { self = knownLanguage } else { - self.name = name - - let id = name.lowercased() - self.id = id - self.linkDisambiguationID = id + self._id = _unknownLanguages.withLock { unknownLanguages in + Self._addingOrFindingExisting(unknownLanguageInfo: .init(name: name, id: id), withUnlockedUnknownLanguages: &unknownLanguages) + } } } @@ -70,8 +272,8 @@ public struct SourceLanguage: Hashable, Codable, Comparable, Sendable { /// If the language name doesn't match any known language, this initializer returns `nil`. /// /// - Parameter knownLanguageName: The display name of the programming language. - public init?(knownLanguageName: String) { - if let knownLanguage = SourceLanguage.firstKnownLanguage(withName: knownLanguageName) { + init?(knownLanguageName: String) { + if let knownLanguage = Self.knownLanguage(withName: knownLanguageName) { self = knownLanguage } else { return nil @@ -83,94 +285,262 @@ public struct SourceLanguage: Hashable, Codable, Comparable, Sendable { /// If the language identifier doesn't match any known language, this initializer returns `nil`. /// /// - Parameter knownLanguageIdentifier: The identifier name of the programming language. - public init?(knownLanguageIdentifier: String) { - if let knownLanguage = SourceLanguage.firstKnownLanguage(withIdentifier: knownLanguageIdentifier) { + init?(knownLanguageIdentifier: String) { + if let knownLanguage = Self._knownLanguage(withIdentifier: knownLanguageIdentifier) { self = knownLanguage } else { return nil } } - - private static func firstKnownLanguage(withName name: String) -> SourceLanguage? { - SourceLanguage.knownLanguages.first { $0.name.lowercased() == name.lowercased() } - } - private static func firstKnownLanguage(withIdentifier id: String) -> SourceLanguage? { - SourceLanguage.knownLanguages.first { knownLanguage in - ([knownLanguage.id] + knownLanguage.idAliases) - .map { $0.lowercased() } - .contains(id) + private static func knownLanguage(withName name: String) -> SourceLanguage? { + switch name.lowercased() { + case "swift": .swift + case "objective-c": .objectiveC + case "javascript": .javaScript + case "data": .data + case "metal": .metal + default: nil } } - /// The Swift programming language. - public static let swift = SourceLanguage(name: "Swift", id: "swift") + private static func _knownLanguage(withIdentifier id: String) -> SourceLanguage? { + switch id.lowercased() { + case "swift": .swift + case "occ", "objc", "objective-c", + "c", // FIXME: DocC should display C as its own language (https://github.com/swiftlang/swift-docc/issues/169). + "occ++", "objc++", "objective-c++", "c++": // FIXME: DocC should display C++ and Objective-C++ as their own languages (https://github.com/swiftlang/swift-docc/issues/767) + .objectiveC + case "javascript": .javaScript + case "data": .data + case "metal": .metal + default: nil + } + } +} - /// The Objective-C programming language. - public static let objectiveC = SourceLanguage( - name: "Objective-C", - id: "occ", - idAliases: [ - "objective-c", - "objc", - "c", // FIXME: DocC should display C as its own language (github.com/swiftlang/swift-docc/issues/169). - "c++", // FIXME: DocC should display C++ and Objective-C++ as their own languages (https://github.com/swiftlang/swift-docc/issues/767) - "objective-c++", - "objc++", - "occ++", - ], - linkDisambiguationID: "c" - ) +// MARK: Conformances - /// The JavaScript programming language or another language that conforms to the ECMAScript specification. - public static let javaScript = SourceLanguage(name: "JavaScript", id: "javascript") - /// Miscellaneous data, that's not a programming language. - /// - /// For example, use this to represent JSON or XML content. - public static let data = SourceLanguage(name: "Data", id: "data") - /// The Metal programming language. - public static let metal = SourceLanguage(name: "Metal", id: "metal") - - /// The list of programming languages that are known to DocC. - public static let knownLanguages: [SourceLanguage] = [.swift, .objectiveC, .javaScript, .data, .metal] - - enum CodingKeys: CodingKey { - case name - case id - case idAliases - case linkDisambiguationID +extension SourceLanguage { + private enum CodingKeys: CodingKey { + case name, id, idAliases, linkDisambiguationID } public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: SourceLanguage.CodingKeys.self) + let container = try decoder.container(keyedBy: CodingKeys.self) - let name = try container.decode(String.self, forKey: SourceLanguage.CodingKeys.name) - let id = try container.decode(String.self, forKey: SourceLanguage.CodingKeys.id) - let idAliases = try container.decodeIfPresent([String].self, forKey: SourceLanguage.CodingKeys.idAliases) ?? [] - let linkDisambiguationID = try container.decodeIfPresent(String.self, forKey: SourceLanguage.CodingKeys.linkDisambiguationID) + let name = try container.decode(String.self, forKey: .name) + let id = try container.decode(String.self, forKey: .id) + let idAliases = try container.decodeIfPresent([String].self, forKey: .idAliases) ?? [] + let linkDisambiguationID = try container.decodeIfPresent(String.self, forKey: .linkDisambiguationID) self.init(name: name, id: id, idAliases: idAliases, linkDisambiguationID: linkDisambiguationID) } public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: SourceLanguage.CodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) + let info = _accessInfo() - try container.encode(self.name, forKey: SourceLanguage.CodingKeys.name) - try container.encode(self.id, forKey: SourceLanguage.CodingKeys.id) - if !self.idAliases.isEmpty { - try container.encode(self.idAliases, forKey: SourceLanguage.CodingKeys.idAliases) + try container.encode(info.name, forKey: .name) + try container.encode(info.id, forKey: .id) + if !info.idAliases.isEmpty { + try container.encode(info.idAliases, forKey: .idAliases) } - try container.encode(self.linkDisambiguationID, forKey: SourceLanguage.CodingKeys.linkDisambiguationID) + try container.encode(info.linkDisambiguationID, forKey: .linkDisambiguationID) } - - public static func < (lhs: SourceLanguage, rhs: SourceLanguage) -> Bool { +} + +public extension SourceLanguage { + static func == (lhs: SourceLanguage, rhs: SourceLanguage) -> Bool { + lhs._id == rhs._id || lhs._accessInfo() == rhs._accessInfo() + } +} + +public extension SourceLanguage { + static func < (lhs: SourceLanguage, rhs: SourceLanguage) -> Bool { + if lhs._isKnownLanguage, rhs._isKnownLanguage { + // If both languages are known, their `_id` is also their sort order + lhs._id < rhs._id + } + // Sort Swift before other languages. - if lhs == .swift { - return true + else if lhs == .swift { + true } else if rhs == .swift { - return false + false + } else { + // Otherwise, sort by ID (a string) for a stable order. + lhs.id < rhs.id + } + } +} + +// MARK: SourceLanguage Set + +package struct SmallSourceLanguageSet: Sendable, Hashable, SetAlgebra, ExpressibleByArrayLiteral, Sequence, Collection { + // There are a few different valid ways that we could implement this, each with their own tradeoffs. + // + // The current implementation uses a single fixed size 64-value bit set to store the private `SourceLanguage._id` values. + // The primary benefit of this design is that it's easy to implement, very fast, and uses the same logic for both known and unknown source languages. + // The tradeoff is that it "only" supports 64 different programming languages at once and that it "only" supports the first 59 unknown/custom source languages that a single DocC build creates. + // This may sound like a significant limitation. However, in practice almost all content deals with either 1 or 2 languages. + // There is some known content with 3 languages but beyond that; as little as 4 or 5 or more languages is increasingly less common/realistic. + // A single project with >64 languages is considered so _extremely_ unlikely that it's considered an unrealistic hypothetical. + // + // Another way that we could implement this within 64 bits could be to store 8 separate UInt8 values. + // This would limit the numbers of source languages in a single set to 8 but would enable a project to use create 251 different unknown/custom source languages. + // This would make it a harder to implement the SetAlgebra, Sequence, and Collection conformances. + // Because `InlineArray` requires Swift 6.2, this design would need to use 8 separate properties or an 8 element tuple, making most operations _O(n)_ (where _n_ is <= 8). + // + // We could also combine the two designs above to use a smaller bit set for some values, and a series of separate UInt8 values. + // This would enable a project to use create 251 different unknown/custom source languages at the cost of supporting fewer simultaneous values in the set. + // This would also have a _greatly_ increased implementation complexity; because we would need both the bit-set-implementation and the separate-UInt8-values-implementation and we would need to dynamically switch between them throughout the entire implementation. + // Depending on the size of the bit set and the number of additional UInt8 values, we could achieve different balances between total number of supported values in the set and number of unknown/custom languages. + // For example, an 8-value bit set for known languages and 7 UInt8 properties for unknown languages would allow the set to contains 12 languages (the 5 known and 7 unknown). + // Alternatively, a 32-value bit set for both known and unknown languages and 4 additional UInt8 properties for unknown languages with a high `_id` could support up to 36 different values. + // That said, storing unknown languages in both the bit set and the additional UInt8 properties would have an _even_ greater implementation complexity. + // Because the very high implementation complexity of these various mixed-implementation designs, we shouldn't try to implement any of them until we know for certain that it's necessary. + // + // We _could_ use an enum to switch between an inline fixed size value and a dynamic resizable value. + // However, the 1 bit for the two enum cases would double the `stride` of the memory layout, resulting in 63 unused "wasted" bits. + + private var bitSet: _FixedSizeBitSet + private init(storage: _FixedSizeBitSet) { + self.bitSet = storage + } + + @inlinable + package init() { + bitSet = .init() + } + + // SetAlgebra + + package typealias Element = SourceLanguage + + @inlinable + package func contains(_ member: SourceLanguage) -> Bool { + bitSet.contains(Int(member._id)) + } + @inlinable + package func union(_ other: SmallSourceLanguageSet) -> SmallSourceLanguageSet { + Self(storage: bitSet.union(other.bitSet)) + } + @inlinable + package func intersection(_ other: SmallSourceLanguageSet) -> SmallSourceLanguageSet { + Self(storage: bitSet.intersection(other.bitSet)) + } + @inlinable + package func symmetricDifference(_ other: SmallSourceLanguageSet) -> SmallSourceLanguageSet { + Self(storage: bitSet.symmetricDifference(other.bitSet)) + } + @inlinable + @discardableResult + package mutating func insert(_ newMember: SourceLanguage) -> (inserted: Bool, memberAfterInsert: SourceLanguage) { + (bitSet.insert(Int(newMember._id)).inserted, newMember) + } + @inlinable + @discardableResult + package mutating func remove(_ member: SourceLanguage) -> SourceLanguage? { + bitSet.remove(Int(member._id)).map { SourceLanguage(_id: UInt8($0)) } + } + @inlinable + @discardableResult + package mutating func update(with newMember: SourceLanguage) -> SourceLanguage? { + bitSet.update(with: Int(newMember._id)).map { SourceLanguage(_id: UInt8($0)) } + } + @inlinable + package mutating func formUnion(_ other: SmallSourceLanguageSet) { + bitSet.formUnion(other.bitSet) + } + @inlinable + package mutating func formIntersection(_ other: SmallSourceLanguageSet) { + bitSet.formIntersection(other.bitSet) + } + @inlinable + package mutating func formSymmetricDifference(_ other: SmallSourceLanguageSet) { + bitSet.formSymmetricDifference(other.bitSet) + } + + // ExpressibleByArrayLiteral + + @inlinable + package init(arrayLiteral elements: SourceLanguage...) { + bitSet = .init() + for language in elements { + bitSet.insert(Int(language._id)) } - // Otherwise, sort by ID for a stable order. - return lhs.id < rhs.id + } + + // Sequence + + @inlinable + package func makeIterator() -> some IteratorProtocol { + _Iterator(wrapped: bitSet.makeIterator()) + } + + private struct _Iterator>: IteratorProtocol { + typealias Element = SourceLanguage + + fileprivate var wrapped: Wrapped + + @inlinable + mutating func next() -> SourceLanguage? { + wrapped.next().map { SourceLanguage(_id: UInt8($0) )} + } + } + + // Collection + + package typealias Index = _FixedSizeBitSet.Index + @inlinable + package var startIndex: Index { + bitSet.startIndex + } + @inlinable + package var endIndex: Index { + bitSet.endIndex + } + @inlinable + package subscript(position: Index) -> SourceLanguage { + SourceLanguage(_id: UInt8(bitSet[position])) + } + @inlinable + package func index(after currentIndex: Index) -> Index { + bitSet.index(after: currentIndex) + } + + private var containsUnknownLanguages: Bool { + // There are 5 known languages, representing the trailing 5 bits of the bit set + let unknownLanguagesMask: UInt64 = 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11100000 + return (bitSet.storage & unknownLanguagesMask) != 0 + } + + package func min() -> SourceLanguage? { + guard containsUnknownLanguages else { + // Known languages are trivially sortable by their `_id` + return bitSet.min().map { SourceLanguage(_id: UInt8($0) )} + } + + return Array(bitSet).map { SourceLanguage(_id: UInt8($0) )}.min() + } + + package func sorted() -> [SourceLanguage] { + guard containsUnknownLanguages else { + // Known languages are trivially sortable by their `_id` + return bitSet.sorted().map { SourceLanguage(_id: UInt8($0) )} + } + + return Array(bitSet).map { SourceLanguage(_id: UInt8($0) )}.sorted() + } + + @inlinable + package var isEmpty: Bool { + bitSet.isEmpty + } + + @inlinable + package var count: Int { + bitSet.count } } diff --git a/Sources/SwiftDocC/Catalog Processing/GeneratedCurationWriter.swift b/Sources/SwiftDocC/Catalog Processing/GeneratedCurationWriter.swift index a700bde21a..fd8e7f4ef1 100644 --- a/Sources/SwiftDocC/Catalog Processing/GeneratedCurationWriter.swift +++ b/Sources/SwiftDocC/Catalog Processing/GeneratedCurationWriter.swift @@ -185,7 +185,7 @@ public struct GeneratedCurationWriter { let languagesToCurate = node.availableSourceLanguages.sorted() var topicsByLanguage = [SourceLanguage: [AutomaticCuration.TaskGroup]]() for language in languagesToCurate { - topicsByLanguage[language] = try? AutomaticCuration.topics(for: node, withTraits: [.init(interfaceLanguage: language.id)], context: context) + topicsByLanguage[language] = try? AutomaticCuration.topics(for: node, withTraits: [.init(sourceLanguage: language)], context: context) } guard topicsByLanguage.count > 1 else { diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index b40d8dd7f6..dd0c3dc5c7 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -1194,7 +1194,7 @@ public class DocumentationContext { bundleID: reference.bundleID, path: symbolPath, fragment: nil, - sourceLanguages: reference.sourceLanguages + sourceLanguages: reference._sourceLanguages ) if let existing = uncuratedDocumentationExtensions[symbolReference] { @@ -1938,7 +1938,7 @@ public class DocumentationContext { // for each language it's available in. if let symbol = node.semantic as? Symbol { for sourceLanguage in node.availableSourceLanguages { - symbol.automaticTaskGroupsVariants[.init(interfaceLanguage: sourceLanguage.id)] = [automaticTaskGroup] + symbol.automaticTaskGroupsVariants[.init(sourceLanguage: sourceLanguage)] = [automaticTaskGroup] } } else if var taskGroupProviding = node.semantic as? (any AutomaticTaskGroupsProviding) { taskGroupProviding.automaticTaskGroups = [automaticTaskGroup] @@ -2950,14 +2950,7 @@ extension DocumentationContext { var problems = [Problem]() func listSourceLanguages(_ sourceLanguages: Set) -> String { - sourceLanguages.sorted(by: { language1, language2 in - // Emit Swift first, then alphabetically. - switch (language1, language2) { - case (.swift, _): return true - case (_, .swift): return false - default: return language1.id < language2.id - } - }).map(\.name).list(finalConjunction: .and) + sourceLanguages.sorted().map(\.name).list(finalConjunction: .and) } func removeAlternateRepresentationSolution(_ alternateRepresentation: AlternateRepresentation) -> [Solution] { [Solution( diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift b/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift index b470886fdc..f430ab01eb 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift @@ -105,7 +105,7 @@ struct DocumentationCurator { let reference = ResolvedTopicReference( bundleID: resolved.bundleID, path: sourceArticlePath, - sourceLanguages: resolved.sourceLanguages) + sourceLanguages: resolved._sourceLanguages) guard let currentArticle = self.context.uncuratedArticles[reference], let documentationNode = try? DocumentationNode(reference: reference, article: currentArticle.value) else { return nil } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift index d7a5f331cf..10b3fc5057 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver.swift @@ -151,7 +151,7 @@ private final class FallbackResolverBasedLinkResolver { bundleID: referenceBundleID, path: unresolvedReference.path.prependingLeadingSlash, fragment: unresolvedReference.topicURL.components.fragment, - sourceLanguages: parent.sourceLanguages + sourceLanguages: parent._sourceLanguages ) allCandidateURLs.append(alreadyResolved.url) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift index 7117fcabaa..3f7351b44f 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift @@ -197,7 +197,7 @@ extension PathHierarchy { startingPoint = counterpoint default: // Only symbols have counterpoints which means that each node should always have at least one language - if counterpoint.languages.map(\.id).min()! < startingPoint.languages.map(\.id).min()! { + if counterpoint.languages.min()! < startingPoint.languages.min()! { startingPoint = counterpoint } } diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift index 1644116036..860df9ebde 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift @@ -20,7 +20,7 @@ extension PathHierarchy { return nil } - let isSwift = symbol.identifier.interfaceLanguage == SourceLanguage.swift.id + let isSwift = symbol.identifier.interfaceLanguage == "swift" return ( signature.parameters.map { parameterTypeSpelling(for: $0.declarationFragments, isSwift: isSwift) }, returnTypeSpellings(for: signature.returns, isSwift: isSwift) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignatureDisambiguation.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignatureDisambiguation.swift index a05904d76e..2871130a10 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignatureDisambiguation.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignatureDisambiguation.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 @@ -9,6 +9,7 @@ */ import Foundation +import DocCCommon extension PathHierarchy.DisambiguationContainer { @@ -64,7 +65,13 @@ extension PathHierarchy.DisambiguationContainer { } private static func _minimalSuggestedDisambiguationForFewParameters(typeNames: consuming Table) -> [[String]?] { - typealias IntSet = _TinySmallValueIntSet + /// A specialized set-algebra type that only stores the possible values `0 ..< 64`. + /// + /// This specialized implementation is _not_ suitable as a general purpose set-algebra type. + /// However, because the code in this file only works with consecutive sequences of very small integers (most likely `0 ..< 16` and increasingly less likely the higher the number), + /// and because the the sets of those integers is frequently accessed in loops, a specialized implementation addresses bottlenecks in `_minimalSuggestedDisambiguation(...)`. + typealias IntSet = _FixedSizeBitSet + // We find the minimal suggested type-signature disambiguation in two steps. // // First, we compute which type names occur in which overloads. @@ -136,7 +143,7 @@ extension PathHierarchy.DisambiguationContainer { } // Create a sequence of type name combinations with increasing number of type names in each combination. - let typeNameCombinationsToCheck = typeNameIndicesToCheck.combinationsToCheck() + let typeNameCombinationsToCheck = typeNameIndicesToCheck.allCombinationsOfValues() return typeNames.rowIndices.map { row in var shortestDisambiguationSoFar: (indicesToInclude: IntSet, length: Int)? = nil @@ -259,162 +266,6 @@ extension PathHierarchy.DisambiguationContainer { } } -// MARK: Int Set - -/// A specialized set-algebra type that only stores the possible values `0 ..< 64`. -/// -/// This specialized implementation is _not_ suitable as a general purpose set-algebra type. -/// However, because the code in this file only works with consecutive sequences of very small integers (most likely `0 ..< 16` and increasingly less likely the higher the number), -/// and because the the sets of those integers is frequently accessed in loops, a specialized implementation addresses bottlenecks in `_minimalSuggestedDisambiguation(...)`. -/// -/// > Important: -/// > This type is thought of as file private but it made internal so that it can be tested. -struct _TinySmallValueIntSet: SetAlgebra { - typealias Element = Int - - init() {} - - @usableFromInline - private(set) var storage: UInt64 = 0 - - @inlinable - init(storage: UInt64) { - self.storage = storage - } - - private static func mask(_ number: Int) -> UInt64 { - precondition(number < 64, "Number \(number) is out of bounds (0..<64)") - return 1 << number - } - - @inlinable - @discardableResult - mutating func insert(_ member: Int) -> (inserted: Bool, memberAfterInsert: Int) { - let newStorage = storage | Self.mask(member) - defer { - storage = newStorage - } - return (newStorage != storage, member) - } - - @inlinable - @discardableResult - mutating func remove(_ member: Int) -> Int? { - let newStorage = storage & ~Self.mask(member) - defer { - storage = newStorage - } - return newStorage != storage ? member : nil - } - - @inlinable - @discardableResult - mutating func update(with member: Int) -> Int? { - let (inserted, _) = insert(member) - return inserted ? nil : member - } - - @inlinable - func contains(_ member: Int) -> Bool { - storage & Self.mask(member) != 0 - } - - @inlinable - var count: Int { - storage.nonzeroBitCount - } - - @inlinable - func isSuperset(of other: Self) -> Bool { - // Provide a custom implementation since this is called frequently in `combinationsToCheck()` - (storage & other.storage) == other.storage - } - - @inlinable - func union(_ other: Self) -> Self { - .init(storage: storage | other.storage) - } - - @inlinable - func intersection(_ other: Self) -> Self { - .init(storage: storage & other.storage) - } - - @inlinable - func symmetricDifference(_ other: Self) -> Self { - .init(storage: storage ^ other.storage) - } - - @inlinable - mutating func formUnion(_ other: Self) { - storage |= other.storage - } - - @inlinable - mutating func formIntersection(_ other: Self) { - storage &= other.storage - } - - @inlinable - mutating func formSymmetricDifference(_ other: Self) { - storage ^= other.storage - } -} - -extension _TinySmallValueIntSet: Sequence { - func makeIterator() -> Iterator { - Iterator(set: self) - } - - struct Iterator: IteratorProtocol { - typealias Element = Int - - private var storage: UInt64 - private var current: Int = -1 - - @inlinable - init(set: _TinySmallValueIntSet) { - self.storage = set.storage - } - - @inlinable - mutating func next() -> Int? { - guard storage != 0 else { - return nil - } - // If the set is somewhat sparse, we can find the next element faster by shifting to the next value. - // This saves needing to do `contains()` checks for all the numbers since the previous element. - let amountToShift = storage.trailingZeroBitCount + 1 - storage >>= amountToShift - - current += amountToShift - return current - } - } -} - -extension _TinySmallValueIntSet { - /// All possible combinations of values to check in order of increasing number of values. - func combinationsToCheck() -> [Self] { - // For `_TinySmallValueIntSet`, leverage the fact that bits of an Int represent the possible combinations. - let smallest = storage.trailingZeroBitCount - - var combinations: [Self] = [] - combinations.reserveCapacity((1 << count /*known to be <64 */) - 1) - - for raw in 1 ... storage >> smallest { - let combination = Self(storage: UInt64(raw << smallest)) - - // Filter out any combinations that include columns that are the same for all overloads - guard self.isSuperset(of: combination) else { continue } - - combinations.append(combination) - } - // The bits of larger and larger Int values won't be in order of number of bits set, so we sort them. - return combinations.sorted(by: { $0.count < $1.count }) - } -} - // MARK: Table /// A fixed-size grid of elements. diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy.swift index d4891efab4..36d5db5372 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy.swift @@ -10,6 +10,7 @@ import Foundation import SymbolKit +import DocCCommon /// An opaque identifier that uniquely identifies a resolved entry in the path hierarchy, /// @@ -149,6 +150,7 @@ struct PathHierarchy { // If there are multiple symbol graphs (for example for different source languages or platforms) then the nodes may have already been added to the hierarchy. var topLevelCandidates = nodes.filter { _, node in node.parent == nil } + let graphLanguageID = language?.id for relationship in graph.relationships where relationship.kind.formsHierarchy { guard let sourceNode = nodes[relationship.source], let expectedContainerName = sourceNode.symbol?.pathComponents.dropLast().last else { continue @@ -188,7 +190,7 @@ struct PathHierarchy { } // Prefer the symbol that matches the relationship's language. - if let targetNode = targetNodes.first(where: { $0.symbol!.identifier.interfaceLanguage == language?.id }) { + if let targetNode = targetNodes.first(where: { $0.symbol!.identifier.interfaceLanguage == graphLanguageID }) { targetNode.add(symbolChild: sourceNode) } else { // It's not clear which target to add the source to, so we add it to all of them. @@ -516,7 +518,7 @@ extension PathHierarchy { /// The symbol, if a node has one. fileprivate(set) var symbol: SymbolGraph.Symbol? /// The languages where this node's symbol is represented. - fileprivate(set) var languages: Set = [] + fileprivate(set) var languages = SmallSourceLanguageSet() /// The other language representation of this symbol. /// /// > Note: Swift currently only supports one other language representation (either Objective-C or C++ but not both). @@ -574,7 +576,7 @@ extension PathHierarchy { fileprivate func deepClone( separating separatedLanguage: SourceLanguage, - keeping otherLanguages: Set, + keeping otherLanguages: SmallSourceLanguageSet, symbolsByUSR: borrowing [String: SymbolGraph.Symbol], didCloneNode: (Node, SymbolGraph.Symbol) -> Void ) -> Node { diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift index 652580be28..1bac256aab 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver.swift @@ -10,6 +10,7 @@ import Foundation import SymbolKit +import DocCCommon /// A type that encapsulates resolving links by searching a hierarchy of path components. final class PathHierarchyBasedLinkResolver { @@ -58,7 +59,7 @@ final class PathHierarchyBasedLinkResolver { /// - reference: The identifier of the page whose descendants to return. /// - languagesFilter: A set of source languages to filter descendants against. /// - Returns: The references of each direct descendant that has a language representation in at least one of the given languages. - func directDescendants(of reference: ResolvedTopicReference, languagesFilter: Set) -> Set { + func directDescendants(of reference: ResolvedTopicReference, languagesFilter: SmallSourceLanguageSet) -> Set { guard let id = resolvedReferenceMap[reference] else { return [] } let node = pathHierarchy.lookup[id]! @@ -339,7 +340,7 @@ private let whitespaceAndDashes = CharacterSet.whitespaces .union(CharacterSet(charactersIn: "-–—")) // hyphen, en dash, em dash private extension PathHierarchy.Node { - func matches(languagesFilter: Set) -> Bool { + func matches(languagesFilter: SmallSourceLanguageSet) -> Bool { languagesFilter.isEmpty || !self.languages.isDisjoint(with: languagesFilter) } } diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift index cf461d3286..d90a10aa73 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift @@ -11,6 +11,7 @@ import Foundation import SymbolKit import Markdown +import DocCCommon /// A collection of APIs to generate documentation topics. enum GeneratedDocumentationTopics { @@ -97,10 +98,8 @@ enum GeneratedDocumentationTopics { private static let defaultImplementationGroupTitle = "Default Implementations" private static func createCollectionNode(parent: ResolvedTopicReference, title: String, identifiers: [ResolvedTopicReference], context: DocumentationContext) throws { - let automaticCurationSourceLanguage: SourceLanguage - let automaticCurationSourceLanguages: Set - automaticCurationSourceLanguage = identifiers.first?.sourceLanguage ?? .swift - automaticCurationSourceLanguages = Set(identifiers.flatMap { identifier in context.sourceLanguages(for: identifier) }) + let automaticCurationSourceLanguage = identifiers.first?.sourceLanguage ?? .swift + let automaticCurationSourceLanguages = SmallSourceLanguageSet(identifiers.flatMap { identifier in context.sourceLanguages(for: identifier) }) // Create the collection topic reference let collectionReference = ResolvedTopicReference( @@ -121,8 +120,8 @@ enum GeneratedDocumentationTopics { let node = try context.entity(with: parent) if let symbol = node.semantic as? Symbol { for trait in node.availableVariantTraits { - guard let language = trait.interfaceLanguage, - automaticCurationSourceLanguages.lazy.map(\.id).contains(language) + guard let language = trait.sourceLanguage, + automaticCurationSourceLanguages.contains(language) else { // If the collection is not available in this trait, don't curate it in this symbol's variant. continue @@ -190,7 +189,7 @@ enum GeneratedDocumentationTopics { reference: collectionReference, kind: .collectionGroup, sourceLanguage: automaticCurationSourceLanguage, - availableSourceLanguages: automaticCurationSourceLanguages, + availableSourceLanguages: Set(automaticCurationSourceLanguages), name: DocumentationNode.Name.conceptual(title: title), markup: Document(parsing: ""), semantic: collectionArticle diff --git a/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift b/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift index d0deba7971..f15c4323ff 100644 --- a/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift +++ b/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift @@ -11,7 +11,7 @@ import Foundation import Markdown import SymbolKit - +import DocCCommon private let automaticSeeAlsoLimit: Int = { ProcessInfo.processInfo.environment["DOCC_AUTOMATIC_SEE_ALSO_LIMIT"].flatMap { Int($0) } ?? 15 @@ -56,9 +56,7 @@ public struct AutomaticCuration { withTraits variantsTraits: Set, context: DocumentationContext ) throws -> [TaskGroup] { - let languagesFilter = Set(variantsTraits.compactMap { - $0.interfaceLanguage.map { SourceLanguage(id: $0) } - }) + let languagesFilter = SmallSourceLanguageSet(variantsTraits.compactMap(\.sourceLanguage)) // Because the `TopicGraph` uses the same nodes for both language representations and doesn't have awareness of language specific edges, // it can't correctly determine language specific automatic curation. Instead we ask the `PathHierarchy` which is source-language-aware. @@ -171,9 +169,7 @@ public struct AutomaticCuration { return nil } - let variantLanguages = Set(variantsTraits.compactMap { traits in - traits.interfaceLanguage.map { SourceLanguage(id: $0) } - }) + let variantLanguages = SmallSourceLanguageSet(variantsTraits.compactMap(\.sourceLanguage)) func isRelevant(_ filteredGroup: DocumentationContentRenderer.ReferenceGroup) -> Bool { // Check if the task group is filtered to a subset of languages diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 911163c327..e0f64d733f 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -527,7 +527,7 @@ extension LinkDestinationSummary { let topicImages = renderNode.metadata.images let referenceIdentifiers = topicImages.map(\.identifier) - guard let symbol = documentationNode.semantic as? Symbol, let summaryTrait = documentationNode.availableVariantTraits.first(where: { $0.interfaceLanguage == documentationNode.sourceLanguage.id }) else { + guard let symbol = documentationNode.semantic as? Symbol, let summaryTrait = documentationNode.availableVariantTraits.first(where: { $0.sourceLanguage == documentationNode.sourceLanguage }) else { // Only symbol documentation currently support multi-language variants (rdar://86580915) let references = referenceIdentifiers .compactMap { renderNode.references[$0.identifier] } @@ -577,7 +577,7 @@ extension LinkDestinationSummary { let variants: [Variant] = documentationNode.availableVariantTraits.compactMap { trait in // Skip the variant for the summarized elements source language. - guard let interfaceLanguage = trait.interfaceLanguage, interfaceLanguage != documentationNode.sourceLanguage.id else { + guard let sourceLanguage = trait.sourceLanguage, sourceLanguage != documentationNode.sourceLanguage else { return nil } @@ -588,7 +588,7 @@ extension LinkDestinationSummary { } let plainTextDeclarationVariant = symbol.plainTextDeclaration(for: trait) - let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage(interfaceLanguage)] + let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage(sourceLanguage.id)] // Use the abbreviated declaration fragments instead of the full declaration fragments. // These have been derived from the symbol's subheading declaration fragments as part of rendering. @@ -602,7 +602,7 @@ extension LinkDestinationSummary { return Variant( traits: variantTraits, kind: nilIfEqual(main: kind, variant: symbol.kindVariants[trait].map { DocumentationNode.kind(forKind: $0.identifier) }), - language: nilIfEqual(main: language, variant: SourceLanguage(knownLanguageIdentifier: interfaceLanguage)), + language: nilIfEqual(main: language, variant: sourceLanguage), relativePresentationURL: nil, // The symbol variant uses the same relative path title: nilIfEqual(main: title, variant: symbol.titleVariants[trait]), abstract: nilIfEqual(main: abstract, variant: abstractVariant), diff --git a/Sources/SwiftDocC/Model/DocumentationNode.swift b/Sources/SwiftDocC/Model/DocumentationNode.swift index d0fe3e811f..b84d5474ef 100644 --- a/Sources/SwiftDocC/Model/DocumentationNode.swift +++ b/Sources/SwiftDocC/Model/DocumentationNode.swift @@ -31,11 +31,7 @@ public struct DocumentationNode { /// All of the traits that make up the different variants of this node. public var availableVariantTraits: Set { - return Set( - availableSourceLanguages - .map(\.id) - .map(DocumentationDataVariantsTrait.init(interfaceLanguage:)) - ) + Set(availableSourceLanguages.map(DocumentationDataVariantsTrait.init(sourceLanguage:))) } /// The names of the platforms for which the node is available. diff --git a/Sources/SwiftDocC/Model/Identifier.swift b/Sources/SwiftDocC/Model/Identifier.swift index 469af2f4ca..b6f66561f2 100644 --- a/Sources/SwiftDocC/Model/Identifier.swift +++ b/Sources/SwiftDocC/Model/Identifier.swift @@ -11,6 +11,7 @@ public import Foundation import SymbolKit public import Markdown +public import DocCCommon /// A resolved or unresolved reference to a piece of documentation. /// @@ -144,7 +145,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString private struct ReferenceKey: Hashable { var path: String var fragment: String? - var sourceLanguages: Set + var sourceLanguages: SmallSourceLanguageSet } /// A synchronized reference cache to store resolved references. @@ -194,7 +195,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString /// The source language for which this topic is relevant. public var sourceLanguage: SourceLanguage { // Return Swift by default to maintain backwards-compatibility. - return sourceLanguages.contains(.swift) ? .swift : sourceLanguages.first! + _sourceLanguages.min()! } /// The source languages for which this topic is relevant. @@ -203,7 +204,11 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString /// corresponding ``DocumentationNode``. If you need to query the source languages associated with a documentation node, use /// ``DocumentationContext/sourceLanguages(for:)`` instead. public var sourceLanguages: Set { - return _storage.sourceLanguages + Set(_sourceLanguages) + } + + var _sourceLanguages: SmallSourceLanguageSet { + _storage.sourceLanguages } /// - Note: The `path` parameter is escaped to a path readable string. @@ -211,7 +216,12 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString self.init(bundleID: bundleID, path: path, fragment: fragment, sourceLanguages: [sourceLanguage]) } + @_disfavoredOverload public init(bundleID: DocumentationBundle.Identifier, path: String, fragment: String? = nil, sourceLanguages: Set) { + self.init(bundleID: bundleID, path: path, fragment: fragment, sourceLanguages: .init(sourceLanguages)) + } + + init(bundleID: DocumentationBundle.Identifier, path: String, fragment: String? = nil, sourceLanguages: SmallSourceLanguageSet) { self.init( bundleID: bundleID, urlReadablePath: urlReadablePath(path), @@ -220,7 +230,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString ) } - private init(bundleID: DocumentationBundle.Identifier, urlReadablePath: String, urlReadableFragment: String? = nil, sourceLanguages: Set) { + private init(bundleID: DocumentationBundle.Identifier, urlReadablePath: String, urlReadableFragment: String? = nil, sourceLanguages: SmallSourceLanguageSet) { precondition(!sourceLanguages.isEmpty, "ResolvedTopicReference.sourceLanguages cannot be empty") // Check for a cached instance of the reference let key = ReferenceKey(path: urlReadablePath, fragment: urlReadableFragment, sourceLanguages: sourceLanguages) @@ -306,8 +316,8 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString let newReference = ResolvedTopicReference( bundleID: bundleID, path: path, - fragment: fragment.map(urlReadableFragment), - sourceLanguages: sourceLanguages + fragment: fragment, // The internal initializer implementation ensures that the fragment is URL readable + sourceLanguages: _sourceLanguages ) return newReference @@ -323,7 +333,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString let newReference = ResolvedTopicReference( bundleID: bundleID, urlReadablePath: url.appendingPathComponent(urlReadablePath(path), isDirectory: false).path, - sourceLanguages: sourceLanguages + sourceLanguages: _sourceLanguages ) return newReference } @@ -345,7 +355,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString bundleID: bundleID, urlReadablePath: newPath, urlReadableFragment: reference.fragment.map(urlReadableFragment), - sourceLanguages: sourceLanguages + sourceLanguages: _sourceLanguages ) return newReference } @@ -357,7 +367,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString bundleID: bundleID, urlReadablePath: newPath, urlReadableFragment: fragment, - sourceLanguages: sourceLanguages + sourceLanguages: _sourceLanguages ) return newReference } @@ -366,10 +376,12 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString /// /// If the current topic reference already includes the given source languages, this returns /// the original topic reference. - public func addingSourceLanguages(_ sourceLanguages: Set) -> ResolvedTopicReference { - let combinedSourceLanguages = self.sourceLanguages.union(sourceLanguages) - - guard combinedSourceLanguages != self.sourceLanguages else { + public func addingSourceLanguages(_ sourceLanguages: some Sequence) -> ResolvedTopicReference { + var updatedLanguages = _sourceLanguages + for language in sourceLanguages { + updatedLanguages.insert(language) + } + guard updatedLanguages != _sourceLanguages else { return self } @@ -377,7 +389,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString bundleID: bundleID, urlReadablePath: path, urlReadableFragment: fragment, - sourceLanguages: combinedSourceLanguages + sourceLanguages: updatedLanguages ) } @@ -386,7 +398,8 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString /// If the current topic reference's source languages equal the given source languages, /// this returns the original topic reference. public func withSourceLanguages(_ sourceLanguages: Set) -> ResolvedTopicReference { - guard sourceLanguages != self.sourceLanguages else { + let newSourceLanguages = SmallSourceLanguageSet(sourceLanguages) + guard newSourceLanguages != _sourceLanguages else { return self } @@ -394,7 +407,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString bundleID: bundleID, urlReadablePath: path, urlReadableFragment: fragment, - sourceLanguages: sourceLanguages + sourceLanguages: newSourceLanguages ) } @@ -410,8 +423,8 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString let sourceLanguageIDVariants = DocumentationDataVariants( values: [DocumentationDataVariantsTrait: String]( - uniqueKeysWithValues: sourceLanguages.map { language in - (DocumentationDataVariantsTrait(interfaceLanguage: language.id), language.id) + uniqueKeysWithValues: _sourceLanguages.map { language in + (DocumentationDataVariantsTrait(sourceLanguage: language), language.id) } ) ) @@ -443,7 +456,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString let bundleID: DocumentationBundle.Identifier let path: String let fragment: String? - let sourceLanguages: Set + let sourceLanguages: SmallSourceLanguageSet let url: URL @@ -455,7 +468,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString bundleID: DocumentationBundle.Identifier, path: String, fragment: String? = nil, - sourceLanguages: Set + sourceLanguages: SmallSourceLanguageSet ) { self.bundleID = bundleID self.path = path diff --git a/Sources/SwiftDocC/Model/ParametersAndReturnValidator.swift b/Sources/SwiftDocC/Model/ParametersAndReturnValidator.swift index efa43e189b..4df4d1c174 100644 --- a/Sources/SwiftDocC/Model/ParametersAndReturnValidator.swift +++ b/Sources/SwiftDocC/Model/ParametersAndReturnValidator.swift @@ -296,7 +296,7 @@ struct ParametersAndReturnValidator { var traitsWithNonVoidReturnValues = Set(signatures.keys) for (trait, signature) in signatures { - let language = trait.interfaceLanguage.flatMap(SourceLanguage.init(knownLanguageIdentifier:)) + let language = trait.sourceLanguage // The function signature for Swift initializers indicate a Void return type. // However, initializers have a _conceptual_ return value that's sometimes worth documenting (rdar://131913065). diff --git a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift index 0d6ee8dd40..b386b3dbd9 100644 --- a/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift +++ b/Sources/SwiftDocC/Model/Rendering/DocumentationContentRenderer.swift @@ -337,7 +337,7 @@ public class DocumentationContentRenderer { let containerReference = ResolvedTopicReference( bundleID: reference.bundleID, path: reference.path, - sourceLanguages: reference.sourceLanguages + sourceLanguages: reference._sourceLanguages ) abstractedNode = try? context.entity(with: containerReference) } diff --git a/Sources/SwiftDocC/Model/Rendering/Navigation Tree/RenderHierarchyTranslator.swift b/Sources/SwiftDocC/Model/Rendering/Navigation Tree/RenderHierarchyTranslator.swift index c62907e757..1dd1e512cd 100644 --- a/Sources/SwiftDocC/Model/Rendering/Navigation Tree/RenderHierarchyTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/Navigation Tree/RenderHierarchyTranslator.swift @@ -203,7 +203,7 @@ struct RenderHierarchyTranslator { defaultValue: mainPathReferences.map(makeHierarchy) // It's possible that the symbol only has a language representation in a variant language ) - for language in symbolReference.sourceLanguages where language != symbolReference.sourceLanguage { + for language in symbolReference._sourceLanguages where language != symbolReference.sourceLanguage { guard let variantPathReferences = context.linkResolver.localResolver.breadcrumbs(of: symbolReference, in: language), variantPathReferences != mainPathReferences else { diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift b/Sources/SwiftDocC/Model/Rendering/RenderContext.swift index 367845ad35..d6e60d1cec 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContext.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContext.swift @@ -104,7 +104,7 @@ public struct RenderContext { path: url.path, fragment: url.fragment, // TopicRenderReference doesn't have language information. Also, the reference's languages _doesn't_ specify the languages of the linked entity. - sourceLanguages: reference.sourceLanguages + sourceLanguages: reference._sourceLanguages ) topics[dependencyReference] = .init(renderReference: dependency, canonicalPath: nil, taskGroups: nil, source: nil, isDocumentationExtensionContent: false) } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index b01c820978..443eaf0b3d 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -11,6 +11,7 @@ public import Foundation public import Markdown import SymbolKit +import DocCCommon /// A visitor which converts a semantic model into a render node. /// @@ -1062,10 +1063,10 @@ public struct RenderNodeTranslator: SemanticVisitor { return true } - let referenceSourceLanguageIDs = Set(context.sourceLanguages(for: reference).map(\.id)) + let referenceSourceLanguages = SmallSourceLanguageSet(context.sourceLanguages(for: reference)) - let availableSourceLanguageTraits = Set(availableTraits.compactMap(\.interfaceLanguage)) - if availableSourceLanguageTraits.isDisjoint(with: referenceSourceLanguageIDs) { + let availableSourceLanguageTraits = SmallSourceLanguageSet(availableTraits.compactMap(\.sourceLanguage)) + if availableSourceLanguageTraits.isDisjoint(with: referenceSourceLanguages) { // The set of available source language traits has no members in common with the // set of source languages the given reference is available in. // @@ -1074,10 +1075,8 @@ public struct RenderNodeTranslator: SemanticVisitor { return true } - return referenceSourceLanguageIDs.contains { sourceLanguageID in - allowedTraits.contains { trait in - trait.interfaceLanguage == sourceLanguageID - } + return allowedTraits.contains { trait in + trait.sourceLanguage.map { referenceSourceLanguages.contains($0) } ?? false } } @@ -1869,7 +1868,7 @@ public struct RenderNodeTranslator: SemanticVisitor { // Symbols can only specify custom alternate language representations for languages that the documented symbol doesn't already have a representation for. // If the current symbol and its custom alternate representation share language representations, the custom language representation is ignored. allVariants.merge( - alternateRepresentationReference.sourceLanguages.map { ($0, alternateRepresentationReference) } + alternateRepresentationReference._sourceLanguages.map { ($0, alternateRepresentationReference) } ) { existing, _ in existing } } } @@ -2053,13 +2052,8 @@ extension ContentRenderSection: RenderTree {} private extension Sequence { func matchesOneOf(traits: Set) -> Bool { - traits.contains(where: { - guard let languageID = $0.interfaceLanguage, - let traitLanguage = SourceLanguage(knownLanguageIdentifier: languageID) - else { - return false - } - return self.contains(traitLanguage) + traits.contains(where: { trait in + trait.sourceLanguage.map { self.contains($0) } ?? false }) } } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift index 7e255a2883..5cacc05eab 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift @@ -219,7 +219,7 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { } var declarations: [DeclarationRenderSection] = [] - let languages = [ + let renderLanguageIDs = [ trait.interfaceLanguage ?? renderNodeTranslator.identifier.sourceLanguage.id ] for pair in declaration { @@ -263,7 +263,7 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { declarations.append( DeclarationRenderSection( - languages: languages, + languages: renderLanguageIDs, platforms: platformNames, tokens: renderedTokens, otherDeclarations: otherDeclarations @@ -281,7 +281,7 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator { declarations.append( DeclarationRenderSection( - languages: languages, + languages: renderLanguageIDs, platforms: platformNames, tokens: renderedTokens ) diff --git a/Sources/SwiftDocC/Semantics/Symbol/DocumentationDataVariants.swift b/Sources/SwiftDocC/Semantics/Symbol/DocumentationDataVariants.swift index 5452d0c6f9..cd9e3d3c77 100644 --- a/Sources/SwiftDocC/Semantics/Symbol/DocumentationDataVariants.swift +++ b/Sources/SwiftDocC/Semantics/Symbol/DocumentationDataVariants.swift @@ -128,13 +128,23 @@ extension DocumentationDataVariants: Equatable where Variant: Equatable {} /// The trait associated with a variant of some piece of information about a documentation node. public struct DocumentationDataVariantsTrait: Hashable { /// The Swift programming language. - public static var swift = DocumentationDataVariantsTrait(interfaceLanguage: SourceLanguage.swift.id) + public static var swift = DocumentationDataVariantsTrait(sourceLanguage: .swift) /// The Objective-C programming language. - public static var objectiveC = DocumentationDataVariantsTrait(interfaceLanguage: SourceLanguage.objectiveC.id) + public static var objectiveC = DocumentationDataVariantsTrait(sourceLanguage: .objectiveC) /// The language in which the documentation node is relevant. - public var interfaceLanguage: String? + public var interfaceLanguage: String? { + get { + sourceLanguage?.id + } + @available(*, deprecated, message: "Create a new DocumentationDataVariantsTrait instead. This deprecated API will be removed after 6.4 is released.") + set { + sourceLanguage = newValue.map { SourceLanguage(id: $0) } + } + } + + private(set) var sourceLanguage: SourceLanguage? /// A special trait that represents the fallback trait, which internal clients can use to access the default value of a collection of variants. static var fallback = DocumentationDataVariantsTrait() @@ -143,17 +153,18 @@ public struct DocumentationDataVariantsTrait: Hashable { /// /// - Parameter interfaceLanguage: The language in which a documentation node is relevant. public init(interfaceLanguage: String? = nil) { - self.interfaceLanguage = interfaceLanguage + self.init(sourceLanguage: interfaceLanguage.map { SourceLanguage(id: $0) }) + } + + init(sourceLanguage: SourceLanguage?) { + self.sourceLanguage = sourceLanguage } /// Creates a new trait given a symbol graph selector. /// /// - Parameter selector: The symbol graph selector to use when creating the trait. public init(for selector: UnifiedSymbolGraph.Selector) { - self.init( - interfaceLanguage: SourceLanguage(knownLanguageIdentifier: selector.interfaceLanguage)?.id - ?? selector.interfaceLanguage - ) + self.init(interfaceLanguage: selector.interfaceLanguage) } } diff --git a/Tests/DocCCommonTests/FixedSizeBitSetTests.swift b/Tests/DocCCommonTests/FixedSizeBitSetTests.swift new file mode 100644 index 0000000000..22a1d91de0 --- /dev/null +++ b/Tests/DocCCommonTests/FixedSizeBitSetTests.swift @@ -0,0 +1,263 @@ +/* + This source file is part of the Swift.org open source project + + 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 + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import DocCCommon +import Testing + +struct FixedSizeBitSetTests { + @Test + func testBehavesSameAsSet() { + var tiny = _FixedSizeBitSet() + var real = Set() + + #expect(tiny.contains(4) == real.contains(4)) + #expect(tiny.insert(4) == real.insert(4)) + #expect(tiny.contains(4) == real.contains(4)) + #expect(tiny.count == real.count) + + #expect(tiny.insert(4) == real.insert(4)) + #expect(tiny.contains(4) == real.contains(4)) + #expect(tiny.count == real.count) + + #expect(tiny.insert(7) == real.insert(7)) + #expect(tiny.contains(7) == real.contains(7)) + #expect(tiny.count == real.count) + + #expect(tiny.update(with: 2) == real.update(with: 2)) + #expect(tiny.contains(2) == real.contains(2)) + #expect(tiny.count == real.count) + + #expect(tiny.remove(9) == real.remove(9)) + #expect(tiny.contains(9) == real.contains(9)) + #expect(tiny.count == real.count) + + #expect(tiny.remove(4) == real.remove(4)) + #expect(tiny.contains(4) == real.contains(4)) + #expect(tiny.count == real.count) + + tiny.formUnion([19]) + real.formUnion([19]) + #expect(tiny.contains(19) == real.contains(19)) + #expect(tiny.count == real.count) + + tiny.formSymmetricDifference([9]) + real.formSymmetricDifference([9]) + #expect(tiny.contains(7) == real.contains(7)) + #expect(tiny.contains(9) == real.contains(9)) + #expect(tiny.count == real.count) + + tiny.formIntersection([5,6,7]) + real.formIntersection([5,6,7]) + #expect(tiny.contains(4) == real.contains(4)) + #expect(tiny.contains(5) == real.contains(5)) + #expect(tiny.contains(6) == real.contains(6)) + #expect(tiny.contains(7) == real.contains(7)) + #expect(tiny.contains(8) == real.contains(8)) + #expect(tiny.contains(9) == real.contains(9)) + #expect(tiny.count == real.count) + + tiny.formUnion([11,29]) + real.formUnion([11,29]) + #expect(tiny.contains(11) == real.contains(11)) + #expect(tiny.contains(29) == real.contains(29)) + #expect(tiny.count == real.count) + + #expect(tiny.isSuperset(of: tiny) == real.isSuperset(of: real)) + #expect(tiny.isSuperset(of: []) == real.isSuperset(of: [])) + #expect(tiny.isSuperset(of: .init(tiny.dropFirst())) == real.isSuperset(of: .init(real.dropFirst()))) + #expect(tiny.isSuperset(of: .init(tiny.dropLast())) == real.isSuperset(of: .init(real.dropLast()))) + } + + @Test(arguments: [ + [], + [ 2], + [0,1, 4, 6, 10], + [0, 3, 5,6, 10, 13, 15], + [ 7,8, 11, 14], + [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] + ]) + func testBehavesSameAsArray(_ real: [Int]) throws { + let tiny = _FixedSizeBitSet(real) + + #expect(tiny.elementsEqual(real)) + + // Sorting + #expect(tiny.min() == real.min()) + #expect(tiny.max() == real.max()) + #expect(tiny.sorted() == real.sorted()) + + // Indexes + #expect(tiny.distance(from: tiny.startIndex, to: tiny.endIndex) + == real.distance(from: real.startIndex, to: real.endIndex)) + + // Index distances + #expect(real.count == tiny.count) + for offset in 0 ..< tiny.count { + let tinyIndex = try #require(tiny.index(tiny.startIndex, offsetBy: offset, limitedBy: tiny.endIndex)) + let realIndex = try #require(real.index(real.startIndex, offsetBy: offset, limitedBy: real.endIndex)) + + #expect(tiny.distance(from: tiny.startIndex, to: tinyIndex) + == real.distance(from: real.startIndex, to: realIndex), "Distances from start to index @\(offset) is the same") + + #expect(tiny.distance(from: tinyIndex, to: tiny.endIndex) + == real.distance(from: realIndex, to: real.endIndex), "Distances from index @\(offset) to end is the same") + } + // Limited index offset by count is not-nil + #expect(tiny.index(tiny.startIndex, offsetBy: tiny.count, limitedBy: tiny.endIndex) != nil) + #expect(real.index(real.startIndex, offsetBy: real.count, limitedBy: real.endIndex) != nil) + + // Limited index offset beyond the count is nil + #expect(tiny.index(tiny.startIndex, offsetBy: tiny.count + 1, limitedBy: tiny.endIndex) == nil) + #expect(real.index(real.startIndex, offsetBy: real.count + 1, limitedBy: real.endIndex) == nil) + + // Index advancements + do { + var currentIndex = tiny.startIndex + + for _ in 0 ..< tiny.count { + let before = currentIndex + let after = tiny.index(after: currentIndex) + + #expect(before < after) + #expect(tiny.distance(from: before, to: after) == 1) + + #expect(currentIndex == before) + tiny.formIndex(after: ¤tIndex) + #expect(currentIndex == after) + } + } + + // Index subscripts + #expect(tiny.indices.count == real.indices.count) + for (tinyIndex, realIndex) in zip(tiny.indices, real.indices) { + #expect(tiny[tinyIndex] == real[realIndex]) + } + + // Subsequences + + // Dropping prefixes + for elementsToDrop in 0 ..< tiny.count { + #expect(real.dropFirst(elementsToDrop).elementsEqual(tiny.dropFirst(elementsToDrop)), "Dropping \(elementsToDrop) from the start should be the same") + #expect(real.dropLast(elementsToDrop).elementsEqual(tiny.dropLast(elementsToDrop)), "Dropping \(elementsToDrop) from the end should be the same") + } + + for elementsToKeep in 0 ..< tiny.count { + #expect(real.prefix(elementsToKeep).elementsEqual(tiny.prefix(elementsToKeep)), "A \(elementsToKeep) prefix should be the same") + #expect(real.suffix(elementsToKeep).elementsEqual(tiny.suffix(elementsToKeep)), "A \(elementsToKeep) suffix should be the same") + } + + // Iteration + for (tinyNumber, realNumber) in zip(tiny, real) { + #expect(tinyNumber == realNumber) + } + } + + @Test() + func testCombinations() { + do { + let tiny: _FixedSizeBitSet = [0,1,2] + #expect(tiny.allCombinationsOfValues().map { $0.sorted() } == [ + [0], [1], [2], + [0,1], [0,2], [1,2], + [0,1,2] + ]) + } + + do { + let tiny: _FixedSizeBitSet = [2,5,9] + #expect(tiny.allCombinationsOfValues().map { $0.sorted() } == [ + [2], [5], [9], + [2,5], [2,9], [5,9], + [2,5,9] + ]) + } + + do { + let tiny: _FixedSizeBitSet = [3,4,7,11,15,16] + + let expected: [[Int]] = [ + // 1 elements + [3], [4], [7], [11], [15], [16], + // 2 elements + [3,4], [3,7], [3,11], [3,15], [3,16], + [4,7], [4,11], [4,15], [4,16], + [7,11], [7,15], [7,16], + [11,15], [11,16], + [15,16], + // 3 elements + [3,4,7], [3,4,11], [3,4,15], [3,4,16], [3,7,11], [3,7,15], [3,7,16], [3,11,15], [3,11,16], [3,15,16], + [4,7,11], [4,7,15], [4,7,16], [4,11,15], [4,11,16], [4,15,16], + [7,11,15], [7,11,16], [7,15,16], + [11,15,16], + // 4 elements + [3,4,7,11], [3,4,7,15], [3,4,7,16], [3,4,11,15], [3,4,11,16], [3,4,15,16], [3,7,11,15], [3,7,11,16], [3,7,15,16], [3,11,15,16], + [4,7,11,15], [4,7,11,16], [4,7,15,16], [4,11,15,16], + [7,11,15,16], + // 5 elements + [3,4,7,11,15], [3,4,7,11,16], [3,4,7,15,16], [3,4,11,15,16], [3,7,11,15,16], + [4,7,11,15,16], + // 6 elements + [3,4,7,11,15,16], + ] + let actual = tiny.allCombinationsOfValues().map { Array($0) } + + #expect(expected.count == actual.count) + + // The order of combinations within a given size doesn't matter. + // It's only important that all combinations of a given size exist and that the sizes are in order. + let expectedBySize = [Int: [[Int]]](grouping: expected, by: \.count).sorted(by: { $0.key < $1.key }).map(\.value) + let actualBySize = [Int: [[Int]]](grouping: actual, by: \.count).sorted(by: { $0.key < $1.key }).map(\.value) + + for (expectedForSize, actualForSize) in zip(expectedBySize, actualBySize) { + #expect(expectedForSize.count == actualForSize.count) + + // Comparing [Int] descriptions to allow each same-size combination list to have different orders. + // For example, these two lists of combinations (with the last 2 elements swapped) are considered equivalent: + // [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4] + // [1, 2, 3], [1, 2, 4], [2, 3, 4], [1, 3, 4] + #expect(expectedForSize.map(\.description).sorted() + == actualForSize.map(\.description).sorted()) + } + } + } + + @Test + func testIsSameSizeAsWrappedStorageType() async { + // Size + #expect(MemoryLayout<_FixedSizeBitSet< Int8 >>.size == MemoryLayout< Int8 >.size) + #expect(MemoryLayout<_FixedSizeBitSet< UInt8 >>.size == MemoryLayout< UInt8 >.size) + #expect(MemoryLayout<_FixedSizeBitSet< Int16 >>.size == MemoryLayout< Int16 >.size) + #expect(MemoryLayout<_FixedSizeBitSet< UInt16 >>.size == MemoryLayout< UInt16 >.size) + #expect(MemoryLayout<_FixedSizeBitSet< Int32 >>.size == MemoryLayout< Int32 >.size) + #expect(MemoryLayout<_FixedSizeBitSet< UInt32 >>.size == MemoryLayout< UInt32 >.size) + #expect(MemoryLayout<_FixedSizeBitSet< Int64 >>.size == MemoryLayout< Int64 >.size) + #expect(MemoryLayout<_FixedSizeBitSet< UInt64 >>.size == MemoryLayout< UInt64 >.size) + + // Stride + #expect(MemoryLayout<_FixedSizeBitSet< Int8 >>.stride == MemoryLayout< Int8 >.stride) + #expect(MemoryLayout<_FixedSizeBitSet< UInt8 >>.stride == MemoryLayout< UInt8 >.stride) + #expect(MemoryLayout<_FixedSizeBitSet< Int16 >>.stride == MemoryLayout< Int16 >.stride) + #expect(MemoryLayout<_FixedSizeBitSet< UInt16 >>.stride == MemoryLayout< UInt16 >.stride) + #expect(MemoryLayout<_FixedSizeBitSet< Int32 >>.stride == MemoryLayout< Int32 >.stride) + #expect(MemoryLayout<_FixedSizeBitSet< UInt32 >>.stride == MemoryLayout< UInt32 >.stride) + #expect(MemoryLayout<_FixedSizeBitSet< Int64 >>.stride == MemoryLayout< Int64 >.stride) + #expect(MemoryLayout<_FixedSizeBitSet< UInt64 >>.stride == MemoryLayout< UInt64 >.stride) + + // Alignment + #expect(MemoryLayout<_FixedSizeBitSet< Int8 >>.alignment == MemoryLayout< Int8 >.alignment) + #expect(MemoryLayout<_FixedSizeBitSet< UInt8 >>.alignment == MemoryLayout< UInt8 >.alignment) + #expect(MemoryLayout<_FixedSizeBitSet< Int16 >>.alignment == MemoryLayout< Int16 >.alignment) + #expect(MemoryLayout<_FixedSizeBitSet< UInt16 >>.alignment == MemoryLayout< UInt16 >.alignment) + #expect(MemoryLayout<_FixedSizeBitSet< Int32 >>.alignment == MemoryLayout< Int32 >.alignment) + #expect(MemoryLayout<_FixedSizeBitSet< UInt32 >>.alignment == MemoryLayout< UInt32 >.alignment) + #expect(MemoryLayout<_FixedSizeBitSet< Int64 >>.alignment == MemoryLayout< Int64 >.alignment) + #expect(MemoryLayout<_FixedSizeBitSet< UInt64 >>.alignment == MemoryLayout< UInt64 >.alignment) + } +} diff --git a/Tests/DocCCommonTests/SmallSourceLanguageSetTests.swift b/Tests/DocCCommonTests/SmallSourceLanguageSetTests.swift new file mode 100644 index 0000000000..6270ce6225 --- /dev/null +++ b/Tests/DocCCommonTests/SmallSourceLanguageSetTests.swift @@ -0,0 +1,154 @@ +/* + 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 DocCCommon +import Testing +import Foundation + +struct SmallSourceLanguageSetTests { + @Test + func testBehavesSameAsSet() { + var tiny = SmallSourceLanguageSet() + var real = Set() + + #expect(tiny.isEmpty == real.isEmpty) + #expect(tiny.count == real.count) + #expect(tiny.min() == real.min()) + #expect(tiny.first == real.first) + for language in SourceLanguage.knownLanguages { + #expect(tiny.contains(language) == real.contains(language)) + } + + // Add known languages + #expect(tiny.insert(.swift) == real.insert(.swift)) + #expect(tiny.insert(.swift) == real.insert(.swift)) + #expect(tiny.insert(.objectiveC) == real.insert(.objectiveC)) + #expect(tiny.remove(.swift) == real.remove(.swift)) + #expect(tiny.remove(.swift) == real.remove(.swift)) + #expect(tiny.update(with: .swift) == real.update(with: .swift)) + + #expect(tiny.update(with: .swift) == real.update(with: .swift)) + #expect(tiny.update(with: .objectiveC) == real.update(with: .objectiveC)) + #expect(tiny.update(with: .data) == real.update(with: .data)) + + #expect(tiny.isEmpty == real.isEmpty) + #expect(tiny.count == real.count) + #expect(tiny.min() == real.min()) + for language in SourceLanguage.knownLanguages { + #expect(tiny.contains(language) == real.contains(language)) + } + + // Add unknown languages + for language in [ + SourceLanguage(name: "Custom"), + SourceLanguage(name: "AAA", id: "zzz" /* will sort last */), + SourceLanguage(name: "ZZZ", id: "aaa" /* will sort first (after Swift) */), + ] { + #expect(tiny.update(with: language) == real.update(with: language)) + #expect(tiny.contains(language) == real.contains(language)) + #expect(tiny.remove(language) == real.remove(language)) + #expect(tiny.remove(language) == real.remove(language)) + #expect(tiny.contains(language) == real.contains(language)) + #expect(tiny.insert(language) == real.insert(language)) + #expect(tiny.contains(language) == real.contains(language)) + #expect(tiny.update(with: language) == real.update(with: language)) + #expect(tiny.contains(language) == real.contains(language)) + } + + #expect(tiny.isEmpty == real.isEmpty) + #expect(tiny.count == real.count) + #expect(tiny.min() == real.min()) + for language in SourceLanguage.knownLanguages { + #expect(tiny.contains(language) == real.contains(language)) + } + + // Set operations + #expect(real.intersection([]) == []) + #expect(tiny.intersection([]) == []) + #expect(real.union([]) == real) + #expect(tiny.union([]) == tiny) + #expect(real.symmetricDifference([]) == real) + #expect(tiny.symmetricDifference([]) == tiny) + + #expect( real.intersection(Set( SourceLanguage.knownLanguages)) + == Set(tiny.intersection(SmallSourceLanguageSet(SourceLanguage.knownLanguages)) )) + #expect( real.union(Set( SourceLanguage.knownLanguages)) + == Set(tiny.union(SmallSourceLanguageSet(SourceLanguage.knownLanguages)) )) + #expect( real.symmetricDifference(Set( SourceLanguage.knownLanguages)) + == Set(tiny.symmetricDifference(SmallSourceLanguageSet(SourceLanguage.knownLanguages)) )) + } + + @Test + func testSortsSwiftFirstAndThenByID() { + var languages = SmallSourceLanguageSet(SourceLanguage.knownLanguages) + #expect(languages.min()?.name == "Swift") + #expect(languages.count == 5) + #expect(languages.sorted().map(\.name) == [ + "Swift", // swift (always first) + "Data", // data + "JavaScript", // javascript + "Metal", // metal + "Objective-C", // occ + ]) + + for language in SourceLanguage.knownLanguages { + #expect(languages.insert(language).inserted == false) + } + + // Add unknown languages + #expect(languages.insert(SourceLanguage(name: "Custom")).inserted == true) + #expect(languages.insert(SourceLanguage(name: "AAA", id: "zzz" /* will sort last */)).inserted == true) + #expect(languages.insert(SourceLanguage(name: "ZZZ", id: "aaa" /* will sort first (after Swift) */)).inserted == true) + + #expect(languages.min()?.name == "Swift") + #expect(languages.count == 8) + #expect(languages.sorted().map(\.name) == [ + "Swift", // swift (always first) + "ZZZ", // aaa (the AAA/zzz and ZZZ/aaa languages have their names and ids flipped to verify that sorting happens by id) + "Custom", // custom + "Data", // data + "JavaScript", // javascript + "Metal", // metal + "Objective-C", // occ + "AAA", // zzz (the AAA/zzz and ZZZ/aaa languages have their names and ids flipped to verify that sorting happens by id) + ]) + + for language in SourceLanguage.knownLanguages { + #expect(languages.remove(language) != nil) + #expect(languages.remove(language) == nil) + } + + #expect(languages.min()?.name == "ZZZ") + #expect(languages.count == 3) + #expect(languages.sorted().map(\.name) == [ + "ZZZ", // aaa (the AAA/zzz and ZZZ/aaa languages have their names and ids flipped to verify that sorting happens by id) + "Custom", // custom + "AAA", // zzz (the AAA/zzz and ZZZ/aaa languages have their names and ids flipped to verify that sorting happens by id) + ]) + + languages.insert(.swift) + + #expect(languages.min()?.name == "Swift") + #expect(languages.count == 4) + #expect(languages.sorted().map(\.name) == [ + "Swift", // swift (always first) + "ZZZ", // aaa (the AAA/zzz and ZZZ/aaa languages have their names and ids flipped to verify that sorting happens by id) + "Custom", // custom + "AAA", // zzz (the AAA/zzz and ZZZ/aaa languages have their names and ids flipped to verify that sorting happens by id) + ]) + } + + @Test + func testIsSameSizeAsUInt64() { + #expect(MemoryLayout.size == MemoryLayout.size) + #expect(MemoryLayout.stride == MemoryLayout.stride) + #expect(MemoryLayout.alignment == MemoryLayout.alignment) + } +} diff --git a/Tests/DocCCommonTests/SourceLanguageTests.swift b/Tests/DocCCommonTests/SourceLanguageTests.swift index d63b6dcaac..111c79e6e5 100644 --- a/Tests/DocCCommonTests/SourceLanguageTests.swift +++ b/Tests/DocCCommonTests/SourceLanguageTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022-2023 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 @@ -9,12 +9,147 @@ */ import DocCCommon -import XCTest +import Testing +import Foundation -class SourceLanguageTests: XCTestCase { - func testUsesIDAliasesWhenQueryingFirstKnownLanguage() { - XCTAssertEqual(SourceLanguage(knownLanguageIdentifier: "objective-c"), .objectiveC) - XCTAssertEqual(SourceLanguage(knownLanguageIdentifier: "objc"), .objectiveC) - XCTAssertEqual(SourceLanguage(knownLanguageIdentifier: "c"), .objectiveC) +struct SourceLanguageTests { + @Test(arguments: SourceLanguage.knownLanguages) + func testUsesIDAliasesWhenQueryingFirstKnownLanguage(_ language: SourceLanguage) { + #expect(SourceLanguage(id: language.id) == language) + for alias in language.idAliases { + #expect(SourceLanguage(id: alias) == language, "Unexpectedly found different language for id alias '\(alias)'") + } + } + + // This test uses mutating SourceLanguage properties which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) + @Test + func testHasValueSemanticsForBothKnownAndUnknownLanguages() throws { + var original = SourceLanguage.swift + var copy = original + copy.name = "First" + #expect(copy.name == "First", "The copy has a modified value") + #expect(original.name == "Swift", "Modifying one value doesn't change the original") + + try assertRoundTripCoding(original) + try assertRoundTripCoding(copy) + + original = .init(name: "Custom", id: "custom") + copy = original + copy.name = "Second" + #expect(copy.name == "Second", "The copy has a modified value") + #expect(original.name == "Custom", "Modifying one value doesn't change the original") + + try assertRoundTripCoding(original) + try assertRoundTripCoding(copy) + } + + @Test + func testReusesExistingValuesWhenCreatingLanguages() throws { + // Creating more than 256 languages would fail if SourceLanguage initializer didn't reuse existing values + let numberOfIterations = 300 // anything more than `UInt8.max` + + for _ in 0...numberOfIterations { + let knownLanguageByID = SourceLanguage(id: "swift") + try assertRoundTripCoding(knownLanguageByID) + #expect(knownLanguageByID.id == "swift") + } + + for _ in 0...numberOfIterations { + let knownLanguageWithAllInfo = SourceLanguage(name: "Swift", id: "swift", idAliases: [], linkDisambiguationID: nil) + try assertRoundTripCoding(knownLanguageWithAllInfo) + #expect(knownLanguageWithAllInfo.id == "swift") + } + + for _ in 0...numberOfIterations { + let knownLanguageByName = SourceLanguage(name: "Swift") + try assertRoundTripCoding(knownLanguageByName) + #expect(knownLanguageByName.id == "swift") + } + + for _ in 0...numberOfIterations { + let unknownLanguage = SourceLanguage(name: "Custom") + try assertRoundTripCoding(unknownLanguage) + #expect(unknownLanguage.id == "custom") + } + + for _ in 0...numberOfIterations { + let unknownLanguageWithAllInfo = SourceLanguage(name: "Custom", id: "custom", idAliases: ["other", "preferred"], linkDisambiguationID: "preferred") + try assertRoundTripCoding(unknownLanguageWithAllInfo) + #expect(unknownLanguageWithAllInfo.name == "Custom") + #expect(unknownLanguageWithAllInfo.id == "custom") + #expect(unknownLanguageWithAllInfo.idAliases == ["other", "preferred"]) + #expect(unknownLanguageWithAllInfo.linkDisambiguationID == "preferred") + } + } + + // This test uses mutating SourceLanguage properties which is deprecated. + // Deprecating the test silences the deprecation warning when running the tests. It doesn't skip the test. + @available(*, deprecated) + @Test + func testReusesExistingValuesModifyingProperties() { + // Creating more than 256 languages would fail if SourceLanguage initializer didn't reuse existing values + let numberOfIterations = 300 // anything more than `UInt8.max` + + var language = SourceLanguage.swift + for iteration in 0...numberOfIterations { + language.name = iteration.isMultiple(of: 2) ? "Even" : "Odd" + } + } + + @Test(arguments: [ + (SourceLanguage.swift, "Swift"), + (SourceLanguage.objectiveC, "Objective-C"), + (SourceLanguage.data, "Data"), + (SourceLanguage.javaScript, "JavaScript"), + (SourceLanguage.metal, "Metal"), + ]) + func testNameOfKnownLanguage(language: SourceLanguage, matches expectedName: String) { + // Known languages have their own dedicated implementation that requires two implementation detail values to be consistent. + #expect(language.name == expectedName) + } + + @Test + func testSortsSwiftFirstAndThenByID() throws { + var languages = SourceLanguage.knownLanguages + #expect(languages.min()?.name == "Swift") + #expect(languages.sorted().map(\.name) == [ + "Swift", // swift (always first) + "Data", // data + "JavaScript", // javascript + "Metal", // metal + "Objective-C", // occ + ]) + + languages.append(contentsOf: [ + SourceLanguage(name: "Custom"), + SourceLanguage(name: "AAA", id: "zzz"), // will sort last + SourceLanguage(name: "ZZZ", id: "aaa"), // will sort first (after Swift) + ]) + #expect(languages.min()?.name == "Swift") + #expect(languages.sorted().map(\.name) == [ + "Swift", // swift (always first) + "ZZZ", // aaa (the AAA/zzz and ZZZ/aaa languages have their names and ids flipped to verify that sorting happens by id) + "Custom", // custom + "Data", // data + "JavaScript", // javascript + "Metal", // metal + "Objective-C", // occ + "AAA", // zzz (the AAA/zzz and ZZZ/aaa languages have their names and ids flipped to verify that sorting happens by id) + ]) + } + + private func assertRoundTripCoding(_ original: SourceLanguage, sourceLocation: SourceLocation = #_sourceLocation) throws { + let encoded = try JSONEncoder().encode(original) + let decoded = try JSONDecoder().decode(SourceLanguage.self, from: encoded) + // Check that both values are equal + #expect(original == decoded, sourceLocation: sourceLocation) + + // Also check that all their properties are equal + #expect(original.id == decoded.id, sourceLocation: sourceLocation) + #expect(original.name == decoded.name, sourceLocation: sourceLocation) + #expect(original.idAliases == decoded.idAliases, sourceLocation: sourceLocation) + #expect(original.linkDisambiguationID == decoded.linkDisambiguationID, sourceLocation: sourceLocation) } } diff --git a/Tests/SwiftDocCTests/Infrastructure/TinySmallValueIntSetTests.swift b/Tests/SwiftDocCTests/Infrastructure/TinySmallValueIntSetTests.swift deleted file mode 100644 index 8f72b8b467..0000000000 --- a/Tests/SwiftDocCTests/Infrastructure/TinySmallValueIntSetTests.swift +++ /dev/null @@ -1,149 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import XCTest -@testable import SwiftDocC - -class TinySmallValueIntSetTests: XCTestCase { - func testBehavesSameAsSet() { - var tiny = _TinySmallValueIntSet() - var real = Set() - - func AssertEqual(_ lhs: (inserted: Bool, memberAfterInsert: Int), _ rhs: (inserted: Bool, memberAfterInsert: Int), file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(lhs.inserted, rhs.inserted, file: file, line: line) - XCTAssertEqual(lhs.memberAfterInsert, rhs.memberAfterInsert, file: file, line: line) - } - - XCTAssertEqual(tiny.contains(4), real.contains(4)) - AssertEqual(tiny.insert(4), real.insert(4)) - XCTAssertEqual(tiny.contains(4), real.contains(4)) - XCTAssertEqual(tiny.count, real.count) - - AssertEqual(tiny.insert(4), real.insert(4)) - XCTAssertEqual(tiny.contains(4), real.contains(4)) - XCTAssertEqual(tiny.count, real.count) - - AssertEqual(tiny.insert(7), real.insert(7)) - XCTAssertEqual(tiny.contains(7), real.contains(7)) - XCTAssertEqual(tiny.count, real.count) - - XCTAssertEqual(tiny.update(with: 2), real.update(with: 2)) - XCTAssertEqual(tiny.contains(2), real.contains(2)) - XCTAssertEqual(tiny.count, real.count) - - XCTAssertEqual(tiny.remove(9), real.remove(9)) - XCTAssertEqual(tiny.contains(9), real.contains(9)) - XCTAssertEqual(tiny.count, real.count) - - XCTAssertEqual(tiny.remove(4), real.remove(4)) - XCTAssertEqual(tiny.contains(4), real.contains(4)) - XCTAssertEqual(tiny.count, real.count) - - tiny.formUnion([19]) - real.formUnion([19]) - XCTAssertEqual(tiny.contains(19), real.contains(19)) - XCTAssertEqual(tiny.count, real.count) - - tiny.formSymmetricDifference([9]) - real.formSymmetricDifference([9]) - XCTAssertEqual(tiny.contains(7), real.contains(7)) - XCTAssertEqual(tiny.contains(9), real.contains(9)) - XCTAssertEqual(tiny.count, real.count) - - tiny.formIntersection([5,6,7]) - real.formIntersection([5,6,7]) - XCTAssertEqual(tiny.contains(4), real.contains(4)) - XCTAssertEqual(tiny.contains(5), real.contains(5)) - XCTAssertEqual(tiny.contains(6), real.contains(6)) - XCTAssertEqual(tiny.contains(7), real.contains(7)) - XCTAssertEqual(tiny.contains(8), real.contains(8)) - XCTAssertEqual(tiny.contains(9), real.contains(9)) - XCTAssertEqual(tiny.count, real.count) - - tiny.formUnion([11,29]) - real.formUnion([11,29]) - XCTAssertEqual(tiny.contains(11), real.contains(11)) - XCTAssertEqual(tiny.contains(29), real.contains(29)) - XCTAssertEqual(tiny.count, real.count) - - XCTAssertEqual(tiny.isSuperset(of: tiny), real.isSuperset(of: real)) - XCTAssertEqual(tiny.isSuperset(of: []), real.isSuperset(of: [])) - XCTAssertEqual(tiny.isSuperset(of: .init(tiny.dropFirst())), real.isSuperset(of: .init(real.dropFirst()))) - XCTAssertEqual(tiny.isSuperset(of: .init(tiny.dropLast())), real.isSuperset(of: .init(real.dropLast()))) - } - - func testCombinations() { - do { - let tiny: _TinySmallValueIntSet = [0,1,2] - XCTAssertEqual(tiny.combinationsToCheck().map { $0.sorted() }, [ - [0], [1], [2], - [0,1], [0,2], [1,2], - [0,1,2] - ]) - } - - do { - let tiny: _TinySmallValueIntSet = [2,5,9] - XCTAssertEqual(tiny.combinationsToCheck().map { $0.sorted() }, [ - [2], [5], [9], - [2,5], [2,9], [5,9], - [2,5,9] - ]) - } - - do { - let tiny: _TinySmallValueIntSet = [3,4,7,11,15,16] - - let expected: [[Int]] = [ - // 1 elements - [3], [4], [7], [11], [15], [16], - // 2 elements - [3,4], [3,7], [3,11], [3,15], [3,16], - [4,7], [4,11], [4,15], [4,16], - [7,11], [7,15], [7,16], - [11,15], [11,16], - [15,16], - // 3 elements - [3,4,7], [3,4,11], [3,4,15], [3,4,16], [3,7,11], [3,7,15], [3,7,16], [3,11,15], [3,11,16], [3,15,16], - [4,7,11], [4,7,15], [4,7,16], [4,11,15], [4,11,16], [4,15,16], - [7,11,15], [7,11,16], [7,15,16], - [11,15,16], - // 4 elements - [3,4,7,11], [3,4,7,15], [3,4,7,16], [3,4,11,15], [3,4,11,16], [3,4,15,16], [3,7,11,15], [3,7,11,16], [3,7,15,16], [3,11,15,16], - [4,7,11,15], [4,7,11,16], [4,7,15,16], [4,11,15,16], - [7,11,15,16], - // 5 elements - [3,4,7,11,15], [3,4,7,11,16], [3,4,7,15,16], [3,4,11,15,16], [3,7,11,15,16], - [4,7,11,15,16], - // 6 elements - [3,4,7,11,15,16], - ] - let actual = tiny.combinationsToCheck().map { Array($0) } - - XCTAssertEqual(expected.count, actual.count) - - // The order of combinations within a given size doesn't matter. - // It's only important that all combinations of a given size exist and that the sizes are in order. - let expectedBySize = [Int: [[Int]]](grouping: expected, by: \.count).sorted(by: \.key).map(\.value) - let actualBySize = [Int: [[Int]]](grouping: actual, by: \.count).sorted(by: \.key).map(\.value) - - for (expectedForSize, actualForSize) in zip(expectedBySize, actualBySize) { - XCTAssertEqual(expectedForSize.count, actualForSize.count) - - // Comparing [Int] descriptions to allow each same-size combination list to have different orders. - // For example, these two lists of combinations (with the last 2 elements swapped) are considered equivalent: - // [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4] - // [1, 2, 3], [1, 2, 4], [2, 3, 4], [1, 3, 4] - XCTAssertEqual(expectedForSize.map(\.description).sorted(), - actualForSize .map(\.description).sorted()) - } - } - } -} diff --git a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift index 912ac6fdb3..df4061c5d4 100644 --- a/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift +++ b/Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift @@ -302,7 +302,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { let symbolSemantic = try XCTUnwrap(node.semantic as? Symbol) let swiftParameterNames = symbolSemantic.parametersSectionVariants.firstValue?.parameters let objcParameterNames = symbolSemantic.parametersSectionVariants.allValues.mapFirst(where: { (trait, variant) -> [Parameter]? in - guard trait.interfaceLanguage == SourceLanguage.objectiveC.id else { return nil } + guard trait.sourceLanguage == .objectiveC else { return nil } return variant.parameters }) @@ -312,7 +312,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { let swiftReturnsContent = symbolSemantic.returnsSection.map { _format($0.content) } let objcReturnsContent = symbolSemantic.returnsSectionVariants.allValues.mapFirst(where: { (trait, variant) -> String? in - guard trait.interfaceLanguage == SourceLanguage.objectiveC.id else { return nil } + guard trait.sourceLanguage == .objectiveC else { return nil } return variant.content.map { $0.format() }.joined() }) @@ -344,7 +344,7 @@ class ParametersAndReturnValidatorTests: XCTestCase { let symbolSemantic = try XCTUnwrap(node.semantic as? Symbol) let swiftReturnsSection = try XCTUnwrap( - symbolSemantic.returnsSectionVariants.allValues.first(where: { trait, _ in trait.interfaceLanguage == "swift" }) + symbolSemantic.returnsSectionVariants.allValues.first(where: { trait, _ in trait.sourceLanguage == .swift }) ).variant XCTAssertEqual(swiftReturnsSection.content.map { $0.format() }, [ "Return value documentation for an initializer." diff --git a/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift b/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift index daf92c4e17..aba604d1f7 100644 --- a/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift +++ b/Tests/SwiftDocCTests/Rendering/DocumentationContentRendererTests.swift @@ -155,7 +155,7 @@ private extension DocumentationContentRendererTests { sourceLanguage: .swift, availableSourceLanguages: [ .swift, - .init(id: DocumentationDataVariantsTrait.otherLanguage.interfaceLanguage!) + DocumentationDataVariantsTrait.otherLanguage.sourceLanguage! ], name: .symbol(name: ""), markup: Document(parsing: ""), From f24b0d3626088d664239838f62cc40dfb4c7821a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 1 Dec 2025 10:16:13 +0100 Subject: [PATCH 88/90] Re-enable test (#1363) rdar://159615046 --- Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift b/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift index 9eea805846..bbe65e52b4 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift @@ -117,8 +117,6 @@ class RenderMetadataTests: XCTestCase { /// Test that when a bystanders symbol graph is loaded that extends a different module, that /// those symbols correctly report the modules when rendered. func testRendersBystanderExtensionsFromSymbolGraph() async throws { - throw XCTSkip("Fails in CI. rdar://159615046") - let (_, _, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", externalResolvers: [:]) { url in let baseSymbolGraphURL = Bundle.module.url( forResource: "BaseKit.symbols", withExtension: "json", subdirectory: "Test Resources")! From b2975d86729ab6b69c6b982ee7c702cc1f6bb549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Mon, 1 Dec 2025 10:25:33 +0100 Subject: [PATCH 89/90] Add '.nojekyll' file in the 'docs' output so that the prebuilt documentation gets published (#1365) --- bin/update-gh-pages-documentation-site | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/update-gh-pages-documentation-site b/bin/update-gh-pages-documentation-site index c1469ed8ae..149911c7d4 100755 --- a/bin/update-gh-pages-documentation-site +++ b/bin/update-gh-pages-documentation-site @@ -74,7 +74,7 @@ swift package \ --hosting-base-path swift-docc \ --output-path "$DOCC_UTILITIES_OUTPUT_DIR" -echo -e "\033[34;1m Merging docs \033q[0m" +echo -e "\033[34;1m Merging docs \033[0m" # Remove the output directory so that the merge command can output there rm -rf "$SWIFT_DOCC_ROOT/gh-pages/docs" @@ -90,6 +90,7 @@ CURRENT_COMMIT_HASH=`git rev-parse --short HEAD` # Commit and push our changes to the gh-pages branch cd gh-pages +touch docs/.nojekyll # We need this (empty) file so that GitHub Pages will publish our prebuilt documentation. git add docs if [ -n "$(git status --porcelain)" ]; then From 4fa4af91049cd0e3217d09f93b584b2281573dba Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Mon, 1 Dec 2025 10:12:40 -0800 Subject: [PATCH 90/90] fixing metadata name --- Sources/SwiftDocC/SwiftDocC.docc/Resources/Metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDocC/SwiftDocC.docc/Resources/Metadata.json b/Sources/SwiftDocC/SwiftDocC.docc/Resources/Metadata.json index 043a45977a..5fc325993a 100644 --- a/Sources/SwiftDocC/SwiftDocC.docc/Resources/Metadata.json +++ b/Sources/SwiftDocC/SwiftDocC.docc/Resources/Metadata.json @@ -12,7 +12,7 @@ "type": "object", "required": [ "bundleDisplayName", - "bundleIdentifier", + "bundleID", "schemaVersion" ], "properties": {