Skip to content

Commit f5238c4

Browse files
authored
More symbol info about collisions in error message when a link is ambiguous (#398)
* Use full declaration in ambiguous link error message rdar://100436859 * Remove comment about a resolved issue * Avoid percent encoding in the description of unresolved references * Refine phrasing of symbol link collision error message * Small style changes to map closure argument
1 parent 8fb7ddd commit f5238c4

File tree

4 files changed

+65
-9
lines changed

4 files changed

+65
-9
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,7 @@ extension PathHierarchy.Error {
838838
return "Symbol links can only resolve symbols."
839839

840840
case .lookupCollision(let partialResult, let collisions):
841-
let collisionDescription = collisions.map { "Add \($0.disambiguation.singleQuoted) to refer to \($0.node.fullNameOfValue(context: context).singleQuoted)"}.sorted()
841+
let collisionDescription = collisions.map { "Append '-\($0.disambiguation)' to refer to \($0.node.fullNameOfValue(context: context).singleQuoted)" }.sorted()
842842
return "Reference is ambiguous after \(partialResult.pathWithoutDisambiguation().singleQuoted): \(collisionDescription.joined(separator: ". "))."
843843
}
844844
}
@@ -864,6 +864,9 @@ private extension PathHierarchy.Node {
864864
func fullNameOfValue(context: DocumentationContext) -> String {
865865
guard let identifier = identifier else { return name }
866866
if let symbol = symbol {
867+
if let fragments = symbol[mixin: SymbolGraph.Symbol.DeclarationFragments.self]?.declarationFragments {
868+
return fragments.map(\.spelling).joined().split(whereSeparator: { $0.isWhitespace || $0.isNewline }).joined(separator: " ")
869+
}
867870
return context.symbolIndex[symbol.identifier.precise]!.name.description
868871
}
869872
// This only gets called for PathHierarchy error messages, so hierarchyBasedLinkResolver is never nil.

Sources/SwiftDocC/Model/Identifier.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,15 @@ public struct UnresolvedTopicReference: Hashable, CustomStringConvertible {
518518
}
519519

520520
public var description: String {
521-
return topicURL.components.string!
521+
var result = topicURL.components.string!
522+
// Replace that path and fragment parts of the description with the unescaped path and fragment values.
523+
if let rangeOfFragment = topicURL.components.rangeOfFragment, let fragment = topicURL.components.fragment {
524+
result.replaceSubrange(rangeOfFragment, with: fragment)
525+
}
526+
if let rangeOfPath = topicURL.components.rangeOfPath {
527+
result.replaceSubrange(rangeOfPath, with: topicURL.components.path)
528+
}
529+
return result
522530
}
523531
}
524532

Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ class PathHierarchyTests: XCTestCase {
7575
try assertFindsPath("/MixedFramework/MyObjectiveCCompatibleProtocol/myProtocolMethod", in: tree, asSymbolID: "c:@M@MixedFramework@objc(pl)MyObjectiveCCompatibleProtocol(im)myProtocolMethod")
7676
try assertFindsPath("/MixedFramework/MyObjectiveCCompatibleProtocol/myProtocolMethod()", in: tree, asSymbolID: "c:@M@MixedFramework@objc(pl)MyObjectiveCCompatibleProtocol(im)myProtocolMethod")
7777
try assertFindsPath("/MixedFramework/MyObjectiveCCompatibleProtocol/myProtocolProperty", in: tree, asSymbolID: "c:@M@MixedFramework@objc(pl)MyObjectiveCCompatibleProtocol(py)myProtocolProperty")
78-
// Objective-C class properties have a "property" kind instead of a "type.property" kind (rdar://92927788)
79-
try assertFindsPath("/MixedFramework/MyObjectiveCCompatibleProtocol/myProtocolTypeProperty-type.property", in: tree, asSymbolID: "c:@M@MixedFramework@objc(pl)MyObjectiveCCompatibleProtocol(cpy)myProtocolTypeProperty")
78+
try assertFindsPath("/MixedFramework/MyObjectiveCCompatibleProtocol/myProtocolTypeProperty", in: tree, asSymbolID: "c:@M@MixedFramework@objc(pl)MyObjectiveCCompatibleProtocol(cpy)myProtocolTypeProperty")
8079
try assertFindsPath("/MixedFramework/MyObjectiveCCompatibleProtocol/myPropertyOptionalMethod", in: tree, asSymbolID: "c:@M@MixedFramework@objc(pl)MyObjectiveCCompatibleProtocol(im)myPropertyOptionalMethod")
8180
try assertFindsPath("/MixedFramework/MyObjectiveCCompatibleProtocol/myPropertyOptionalMethod()", in: tree, asSymbolID: "c:@M@MixedFramework@objc(pl)MyObjectiveCCompatibleProtocol(im)myPropertyOptionalMethod")
8281

@@ -295,6 +294,11 @@ class PathHierarchyTests: XCTestCase {
295294
(symbolID: "s:14MixedFramework28CollisionsWithDifferentKindsO9somethingyA2CmF", disambiguation: "enum.case"),
296295
(symbolID: "s:14MixedFramework28CollisionsWithDifferentKindsO9somethingSSvp", disambiguation: "property"),
297296
])
297+
try assertPathRaisesErrorMessage("/MixedFramework/CollisionsWithDifferentKinds/something", in: tree, context: context, expectedErrorMessage: """
298+
Reference is ambiguous after '/MixedFramework/CollisionsWithDifferentKinds': \
299+
Append '-enum.case' to refer to 'case something'. \
300+
Append '-property' to refer to 'var something: String { get }'.
301+
""")
298302

299303
// public final class CollisionsWithEscapedKeywords {
300304
// public subscript() -> Int { 0 }
@@ -310,12 +314,24 @@ class PathHierarchyTests: XCTestCase {
310314
(symbolID: "s:14MixedFramework29CollisionsWithEscapedKeywordsC4inityyF", disambiguation: "method"),
311315
(symbolID: "s:14MixedFramework29CollisionsWithEscapedKeywordsC4inityyFZ", disambiguation: "type.method"),
312316
])
317+
try assertPathRaisesErrorMessage("/MixedFramework/CollisionsWithEscapedKeywords/init()", in: tree, context: context, expectedErrorMessage: """
318+
Reference is ambiguous after '/MixedFramework/CollisionsWithEscapedKeywords': \
319+
Append '-init' to refer to 'init()'. \
320+
Append '-method' to refer to 'func `init`()'. \
321+
Append '-type.method' to refer to 'static func `init`()'.
322+
""")
313323

314324
try assertPathCollision("/MixedFramework/CollisionsWithEscapedKeywords/subscript()", in: tree, collisions: [
315325
(symbolID: "s:14MixedFramework29CollisionsWithEscapedKeywordsC9subscriptyyF", disambiguation: "method"),
316326
(symbolID: "s:14MixedFramework29CollisionsWithEscapedKeywordsCSiycip", disambiguation: "subscript"),
317327
(symbolID: "s:14MixedFramework29CollisionsWithEscapedKeywordsC9subscriptyyFZ", disambiguation: "type.method"),
318328
])
329+
try assertPathRaisesErrorMessage("/MixedFramework/CollisionsWithEscapedKeywords/subscript()", in: tree, context: context, expectedErrorMessage: """
330+
Reference is ambiguous after '/MixedFramework/CollisionsWithEscapedKeywords': \
331+
Append '-method' to refer to 'func `subscript`()'. \
332+
Append '-subscript' to refer to 'subscript() -> Int { get }'. \
333+
Append '-type.method' to refer to 'static func `subscript`()'.
334+
""")
319335

320336
// public enum CollisionsWithDifferentFunctionArguments {
321337
// public func something(argument: Int) -> Int { 0 }
@@ -325,6 +341,11 @@ class PathHierarchyTests: XCTestCase {
325341
(symbolID: "s:14MixedFramework40CollisionsWithDifferentFunctionArgumentsO9something8argumentS2i_tF", disambiguation: "1cyvp"),
326342
(symbolID: "s:14MixedFramework40CollisionsWithDifferentFunctionArgumentsO9something8argumentSiSS_tF", disambiguation: "2vke2"),
327343
])
344+
try assertPathRaisesErrorMessage("/MixedFramework/CollisionsWithDifferentFunctionArguments/something(argument:)", in: tree, context: context, expectedErrorMessage: """
345+
Reference is ambiguous after '/MixedFramework/CollisionsWithDifferentFunctionArguments': \
346+
Append '-1cyvp' to refer to 'func something(argument: Int) -> Int'. \
347+
Append '-2vke2' to refer to 'func something(argument: String) -> Int'.
348+
""")
328349

329350
// public enum CollisionsWithDifferentSubscriptArguments {
330351
// public subscript(something: Int) -> Int { 0 }
@@ -334,6 +355,11 @@ class PathHierarchyTests: XCTestCase {
334355
(symbolID: "s:14MixedFramework41CollisionsWithDifferentSubscriptArgumentsOyS2icip", disambiguation: "4fd0l"),
335356
(symbolID: "s:14MixedFramework41CollisionsWithDifferentSubscriptArgumentsOySiSScip", disambiguation: "757cj"),
336357
])
358+
try assertPathRaisesErrorMessage("/MixedFramework/CollisionsWithDifferentSubscriptArguments/subscript(_:)", in: tree, context: context, expectedErrorMessage: """
359+
Reference is ambiguous after '/MixedFramework/CollisionsWithDifferentSubscriptArguments': \
360+
Append '-4fd0l' to refer to 'subscript(something: Int) -> Int { get }'. \
361+
Append '-757cj' to refer to 'subscript(somethingElse: String) -> Int { get }'.
362+
""")
337363

338364
// typedef NS_OPTIONS(NSInteger, MyObjectiveCOption) {
339365
// MyObjectiveCOptionNone = 0,
@@ -839,6 +865,11 @@ class PathHierarchyTests: XCTestCase {
839865
("s:5MyKit0A5MyProtocol0Afunc()DefaultImp", "2dxqn"),
840866
("s:5MyKit0A5MyProtocol0Afunc()", "6ijsi"),
841867
])
868+
try assertPathRaisesErrorMessage("/SideKit/SideProtocol/func()", in: tree, context: context, expectedErrorMessage: """
869+
Reference is ambiguous after '/SideKit/SideProtocol': \
870+
Append '-2dxqn' to refer to 'func1()'. \
871+
Append '-6ijsi' to refer to 'func1()'.
872+
""") // This test data have the same declaration for both symbols.
842873

843874
try assertFindsPath("/FillIntroduced/iOSOnlyDeprecated()", in: tree, asSymbolID: "s:14FillIntroduced17iOSOnlyDeprecatedyyF")
844875
try assertFindsPath("/FillIntroduced/macCatalystOnlyIntroduced()", in: tree, asSymbolID: "s:14FillIntroduced015macCatalystOnlyB0yyF")
@@ -879,6 +910,12 @@ class PathHierarchyTests: XCTestCase {
879910
("c:@E@Foo", "struct"),
880911
("c:MixedLanguageFramework.h@T@Foo", "typealias"),
881912
])
913+
try assertPathRaisesErrorMessage("MixedLanguageFramework/Foo", in: tree, context: context, expectedErrorMessage: """
914+
Reference is ambiguous after '/MixedLanguageFramework': \
915+
Append '-enum' to refer to 'typedef enum Foo : NSString { ... } Foo;'. \
916+
Append '-struct' to refer to 'struct Foo'. \
917+
Append '-typealias' to refer to 'typedef enum Foo : NSString { ... } Foo;'.
918+
""") // The 'enum' and 'typealias' symbols have multi-line declarations that are presented on a single line
882919

883920
try assertFindsPath("MixedLanguageFramework/Foo/first", in: tree, asSymbolID: "c:@E@Foo@first")
884921

@@ -1233,6 +1270,14 @@ class PathHierarchyTests: XCTestCase {
12331270
}
12341271
}
12351272

1273+
private func assertPathRaisesErrorMessage(_ path: String, in tree: PathHierarchy, context: DocumentationContext, expectedErrorMessage: String, file: StaticString = #file, line: UInt = #line) throws {
1274+
XCTAssertThrowsError(try tree.findSymbol(path: path), "Finding path \(path) didn't raise an error.",file: file,line: line) { untypedError in
1275+
let error = untypedError as! PathHierarchy.Error
1276+
let errorMessage = error.errorMessage(context: context)
1277+
XCTAssertEqual(errorMessage, expectedErrorMessage, file: file, line: line)
1278+
}
1279+
}
1280+
12361281
private func assertParsedPathComponents(_ path: String, _ expected: [(String, String?, String?)], file: StaticString = #file, line: UInt = #line) {
12371282
let (actual, _) = PathHierarchy.parse(path: path)
12381283
XCTAssertEqual(actual.count, expected.count, file: file, line: line)

Tests/SwiftDocCTests/Semantics/SymbolTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ class SymbolTests: XCTestCase {
525525
526526
A cool API to call.
527527
528-
This overview has an ``UnresolvableSymbolLinkInMyClassOverview``.
528+
This overview has an ``UnresolvableSymbolLinkInMyClassOverview<>(_:))``.
529529
530530
- Parameters:
531531
- name: A parameter
@@ -554,14 +554,14 @@ class SymbolTests: XCTestCase {
554554

555555
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'doc://com.test.external/ExternalPage' couldn't be resolved. No external resolver registered for 'com.test.external'." }))
556556
if LinkResolutionMigrationConfiguration.shouldUseHierarchyBasedLinkResolver {
557-
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'UnresolvableSymbolLinkInMyClassOverview'. Available children: init(), myFunction()." }))
557+
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview<>(_:))' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'UnresolvableSymbolLinkInMyClassOverview<>(_:))'. Available children: init(), myFunction()." }))
558558
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'UnresolvableClassInMyClassTopicCuration'. Available children: init(), myFunction()." }))
559559

560560
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'unresolvablePropertyInMyClassTopicCuration'. Available children: init(), myFunction()." }))
561-
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'init()' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass': Add '33vaw' to refer to 'init()'. Add '3743d' to refer to 'init()'." }))
562-
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/init()-swift.init' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass': Add '33vaw' to refer to 'init()'. Add '3743d' to refer to 'init()'." }))
561+
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'init()' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass': Append '-33vaw' to refer to 'init()'. Append '-3743d' to refer to 'init()'." }))
562+
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/init()-swift.init' couldn't be resolved. Reference is ambiguous after '/MyKit/MyClass': Append '-33vaw' to refer to 'init()'. Append '-3743d' to refer to 'init()'." }))
563563
} else {
564-
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview' couldn't be resolved. No local documentation matches this reference." }))
564+
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview<>(_:))' couldn't be resolved. No local documentation matches this reference." }))
565565
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." }))
566566
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." }))
567567
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'init()' couldn't be resolved. No local documentation matches this reference." }))

0 commit comments

Comments
 (0)