Skip to content

Commit 0963fb7

Browse files
committed
Merge branch 'main' into fix-closure-parameter-disambiguation-suggestion
# Conflicts: # Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift
2 parents 3a7691a + 9c91d10 commit 0963fb7

File tree

9 files changed

+163
-12
lines changed

9 files changed

+163
-12
lines changed

Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SwiftDocC/DocumentationService/Convert/Symbol Link Resolution/LinkCompletionTools.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ public enum LinkCompletionTools {
131131
node,
132132
kind: symbol.kind,
133133
hash: symbol.symbolIDHash,
134-
parameterTypes: symbol.parameterTypes,
135-
returnTypes: symbol.returnTypes
134+
parameterTypes: symbol.parameterTypes?.map { $0.withoutWhitespace() },
135+
returnTypes: symbol.returnTypes?.map { $0.withoutWhitespace() }
136136
)
137137
}
138138

@@ -236,3 +236,9 @@ private extension PathHierarchy.PathComponent.Disambiguation {
236236
}
237237
}
238238
}
239+
240+
private extension String {
241+
func withoutWhitespace() -> String {
242+
filter { !$0.isWhitespace }
243+
}
244+
}

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2428,18 +2428,17 @@ public class DocumentationContext {
24282428

24292429
// Crawl the rest of the symbols that haven't been crawled so far in hierarchy pre-order.
24302430
allCuratedReferences = try crawlSymbolCuration(in: automaticallyCurated.map(\.symbol), bundle: bundle, initial: allCuratedReferences)
2431-
2432-
// Remove curation paths that have been created automatically above
2433-
// but we've found manual curation for in the second crawl pass.
2434-
removeUnneededAutomaticCuration(automaticallyCurated)
24352431

24362432
// Automatically curate articles that haven't been manually curated
24372433
// Article curation is only done automatically if there is only one root module
24382434
if let rootNode = rootNodeForAutomaticCuration {
24392435
let articleReferences = try autoCurateArticles(otherArticles, startingFrom: rootNode)
2440-
preResolveExternalLinks(references: articleReferences, localBundleID: bundle.id)
2441-
resolveLinks(curatedReferences: Set(articleReferences), bundle: bundle)
2436+
allCuratedReferences = try crawlSymbolCuration(in: articleReferences, bundle: bundle, initial: allCuratedReferences)
24422437
}
2438+
2439+
// Remove curation paths that have been created automatically above
2440+
// but we've found manual curation for in the second crawl pass.
2441+
removeUnneededAutomaticCuration(automaticallyCurated)
24432442

24442443
// Remove any empty "Extended Symbol" pages whose children have been curated elsewhere.
24452444
for module in rootModules {

Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+TypeSignature.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ extension PathHierarchy {
136136
// For example: "[", "?", "<", "...", ",", "(", "->" etc. contribute to the type spellings like
137137
// `[Name]`, `Name?`, "Name<T>", "Name...", "()", "(Name, Name)", "(Name)->Name" and more.
138138
let utf8Spelling = fragment.spelling.utf8
139+
guard !utf8Spelling.elementsEqual(".Type".utf8) else {
140+
// Once exception to that is "Name.Type" which is different from just "Name" (and we don't want a trailing ".")
141+
accumulated.append(contentsOf: utf8Spelling)
142+
continue
143+
}
139144
for index in utf8Spelling.indices {
140145
let char = utf8Spelling[index]
141146
switch char {

Sources/SwiftDocC/Infrastructure/Topic Graph/TopicGraph.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ struct TopicGraph {
330330
}
331331

332332
var result = ""
333-
result.append("\(decorator) \(node[keyPath: keyPath])\r\n")
333+
result.append("\(decorator) \(node[keyPath: keyPath])\n")
334334
if let childEdges = edges[node.reference]?.sorted(by: { $0.path < $1.path }) {
335335
for (index, childRef) in childEdges.enumerated() {
336336
var decorator = decorator

Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderIndex.spec.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"interfaceLanguages"
1616
],
1717
"properties": {
18-
"identifier": {
18+
"schemaVersion": {
1919
"$ref": "#/components/schemas/SchemaVersion"
2020
},
2121
"interfaceLanguages": {

Tests/SwiftDocCTests/Infrastructure/DocumentationCuratorTests.swift

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,90 @@ class DocumentationCuratorTests: XCTestCase {
201201
XCTAssertEqual(curationProblem.possibleSolutions.map(\.summary), ["Remove '- <doc:First>'"])
202202
}
203203

204+
func testCurationInUncuratedAPICollection() throws {
205+
// Everything should behave the same when an API Collection is automatically curated as when it is explicitly curated
206+
for shouldCurateAPICollection in [true, false] {
207+
let assertionMessageDescription = "when the API collection is \(shouldCurateAPICollection ? "explicitly curated" : "auto-curated as an article under the module")."
208+
209+
let catalog = Folder(name: "unit-test.docc", content: [
210+
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [
211+
makeSymbol(id: "some-symbol-id", kind: .class, pathComponents: ["SomeClass"])
212+
])),
213+
214+
TextFile(name: "ModuleName.md", utf8Content: """
215+
# ``ModuleName``
216+
217+
\(shouldCurateAPICollection ? "## Topics\n\n### Explicit curation\n\n- <doc:API-Collection>" : "")
218+
"""),
219+
220+
TextFile(name: "API-Collection.md", utf8Content: """
221+
# Some API collection
222+
223+
Curate the only symbol
224+
225+
## Topics
226+
227+
- ``SomeClass``
228+
- ``NotFound``
229+
"""),
230+
])
231+
let (bundle, context) = try loadBundle(catalog: catalog)
232+
XCTAssertEqual(
233+
context.problems.map(\.diagnostic.summary),
234+
[
235+
// There should only be a single problem about the unresolvable link in the API collection.
236+
"'NotFound' doesn't exist at '/unit-test/API-Collection'"
237+
],
238+
"Unexpected problems: \(context.problems.map(\.diagnostic.summary).joined(separator: "\n")) \(assertionMessageDescription)"
239+
)
240+
241+
// Verify that the topic graph paths to the symbol (although not used for its breadcrumbs) doesn't have the automatic edge anymore.
242+
let symbolReference = try XCTUnwrap(context.knownPages.first(where: { $0.lastPathComponent == "SomeClass" }))
243+
XCTAssertEqual(
244+
context.finitePaths(to: symbolReference).map { $0.map(\.path) },
245+
[
246+
// The automatic default `["/documentation/ModuleName"]` curation _shouldn't_ be here.
247+
248+
// The authored curation in the uncurated API collection
249+
["/documentation/ModuleName", "/documentation/unit-test/API-Collection"],
250+
],
251+
"Unexpected 'paths' to the symbol page \(assertionMessageDescription)"
252+
)
253+
254+
// Verify that the symbol page shouldn't auto-curate in its canonical location.
255+
let symbolTopicNode = try XCTUnwrap(context.topicGraph.nodeWithReference(symbolReference))
256+
XCTAssertFalse(symbolTopicNode.shouldAutoCurateInCanonicalLocation, "Symbol node is unexpectedly configured to auto-curate \(assertionMessageDescription)")
257+
258+
// Verify that the topic graph doesn't have the automatic edge anymore.
259+
XCTAssertEqual(context.dumpGraph(), """
260+
doc://unit-test/documentation/ModuleName
261+
╰ doc://unit-test/documentation/unit-test/API-Collection
262+
╰ doc://unit-test/documentation/ModuleName/SomeClass
263+
264+
""",
265+
"Unexpected topic graph \(assertionMessageDescription)"
266+
)
267+
268+
// Verify that the rendered top-level page doesn't have an automatic "Classes" topic section anymore.
269+
let converter = DocumentationNodeConverter(bundle: bundle, context: context)
270+
let moduleReference = try XCTUnwrap(context.soleRootModuleReference)
271+
let rootRenderNode = converter.convert(try context.entity(with: moduleReference))
272+
273+
XCTAssertEqual(
274+
rootRenderNode.topicSections.map(\.title),
275+
[shouldCurateAPICollection ? "Explicit curation" : "Articles"],
276+
"Unexpected rendered topic sections on the module page \(assertionMessageDescription)"
277+
)
278+
XCTAssertEqual(
279+
rootRenderNode.topicSections.map(\.identifiers),
280+
[
281+
["doc://unit-test/documentation/unit-test/API-Collection"],
282+
],
283+
"Unexpected rendered topic sections on the module page \(assertionMessageDescription)"
284+
)
285+
}
286+
}
287+
204288
func testModuleUnderTechnologyRoot() throws {
205289
let (_, bundle, context) = try testBundleAndContext(copying: "SourceLocations") { url in
206290
try """

Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3889,6 +3889,42 @@ class PathHierarchyTests: XCTestCase {
38893889
])
38903890
}
38913891

3892+
// The second overload refers to the metatype of the parameter
3893+
do {
3894+
func makeSignature(first: DeclToken...) -> SymbolGraph.Symbol.FunctionSignature {
3895+
.init(
3896+
parameters: [.init(name: "first", externalName: "with", declarationFragments: makeFragments(first), children: []),],
3897+
returns: makeFragments([voidType])
3898+
)
3899+
}
3900+
3901+
let someGenericTypeID = "some-generic-type-id"
3902+
let catalog = Folder(name: "unit-test.docc", content: [
3903+
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(
3904+
moduleName: "ModuleName",
3905+
symbols: [
3906+
makeSymbol(id: "function-overload-1", kind: .func, pathComponents: ["doSomething(with:)"], signature: makeSignature(
3907+
// GenericName
3908+
first: .typeIdentifier("GenericName", precise: someGenericTypeID)
3909+
)),
3910+
3911+
makeSymbol(id: "function-overload-2", kind: .func, pathComponents: ["doSomething(with:)"], signature: makeSignature(
3912+
// GenericName.Type
3913+
first: .typeIdentifier("GenericName", precise: someGenericTypeID), ".Type"
3914+
)),
3915+
]
3916+
))
3917+
])
3918+
3919+
let (_, context) = try loadBundle(catalog: catalog)
3920+
let tree = context.linkResolver.localResolver.pathHierarchy
3921+
3922+
try assertPathCollision("ModuleName/doSomething(with:)", in: tree, collisions: [
3923+
(symbolID: "function-overload-1", disambiguation: "-(GenericName)"), // GenericName
3924+
(symbolID: "function-overload-2", disambiguation: "-(GenericName.Type)"), // GenericName.Type
3925+
])
3926+
}
3927+
38923928
// Second overload requires combination of two non-unique types to disambiguate
38933929
do {
38943930
// String Set<Int> (Double)->Void

Tests/SwiftDocCTests/Infrastructure/Symbol Link Resolution/LinkCompletionToolsTests.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,25 @@ class LinkCompletionToolsTests: XCTestCase {
237237
"->_", // The only overload that returns something
238238
])
239239
}
240+
241+
func testRemovesWhitespaceFromTypeSignatureDisambiguation() {
242+
let overloads = [
243+
// The caller included whitespace in these closure type spellings but the DocC disambiguation won't include this whitespace.
244+
(parameters: ["(Int) -> Int"], returns: []), // ((Int) -> Int) -> Void
245+
(parameters: ["(Bool) -> ()"], returns: []), // ((Bool) -> () ) -> Void
246+
].map {
247+
LinkCompletionTools.SymbolInformation(
248+
kind: "func",
249+
symbolIDHash: "\($0)".stableHashString,
250+
parameterTypes: $0.parameters,
251+
returnTypes: $0.returns
252+
)
253+
}
254+
255+
XCTAssertEqual(LinkCompletionTools.suggestedDisambiguation(forCollidingSymbols: overloads), [
256+
// Both parameters require the only parameter type as disambiguation. The suggested disambiguation shouldn't contain extra whitespace.
257+
"-((Int)->Int)",
258+
"-((Bool)->())",
259+
])
260+
}
240261
}

0 commit comments

Comments
 (0)