Skip to content

Commit b671925

Browse files
Merge branch 'main' into r150760264/percent-encoded-character
2 parents 5b39673 + b14b0d9 commit b671925

File tree

3 files changed

+182
-4
lines changed

3 files changed

+182
-4
lines changed

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

+17-3
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,27 @@ extension PathHierarchy.Error {
223223

224224
case .lookupCollision(partialResult: let partialResult, remaining: let remaining, collisions: let collisions):
225225
let nextPathComponent = remaining.first!
226-
let (pathPrefix, _, solutions) = makeCollisionSolutions(from: collisions, nextPathComponent: nextPathComponent, partialResultPrefix: partialResult.pathPrefix)
227-
226+
let (pathPrefix, foundDisambiguation, solutions) = makeCollisionSolutions(
227+
from: collisions,
228+
nextPathComponent: nextPathComponent,
229+
partialResultPrefix: partialResult.pathPrefix)
230+
231+
let rangeAdjustment: SourceRange
232+
if !foundDisambiguation.isEmpty {
233+
rangeAdjustment = .makeRelativeRange(
234+
startColumn: pathPrefix.count - foundDisambiguation.count,
235+
length: foundDisambiguation.count)
236+
} else {
237+
rangeAdjustment = .makeRelativeRange(
238+
startColumn: pathPrefix.count - nextPathComponent.full.count,
239+
length: nextPathComponent.full.count)
240+
}
241+
228242
return TopicReferenceResolutionErrorInfo("""
229243
\(nextPathComponent.full.singleQuoted) is ambiguous at \(partialResult.node.pathWithoutDisambiguation().singleQuoted)
230244
""",
231245
solutions: solutions,
232-
rangeAdjustment: .makeRelativeRange(startColumn: pathPrefix.count - nextPathComponent.full.count, length: nextPathComponent.full.count)
246+
rangeAdjustment: rangeAdjustment
233247
)
234248
}
235249
}

Sources/SwiftDocC/Semantics/ReferenceResolver.swift

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ func unresolvedReferenceProblem(source: URL?, range: SourceRange?, severity: Dia
4444
let diagnosticRange: SourceRange?
4545
if var rangeAdjustment = errorInfo.rangeAdjustment, let referenceSourceRange {
4646
rangeAdjustment.offsetWithRange(referenceSourceRange)
47+
assert(rangeAdjustment.lowerBound.column >= 0, """
48+
Unresolved topic reference range adjustment created range with negative column.
49+
Source: \(source?.absoluteString ?? "nil")
50+
Range: \(rangeAdjustment.lowerBound.description):\(rangeAdjustment.upperBound.description)
51+
Summary: \(errorInfo.message)
52+
""")
4753
diagnosticRange = rangeAdjustment
4854
} else {
4955
diagnosticRange = referenceSourceRange

Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift

+159-1
Original file line numberDiff line numberDiff line change
@@ -5266,7 +5266,165 @@ let expected = """
52665266
}
52675267
}
52685268
}
5269-
5269+
5270+
func testContextDiagnosesInsufficientDisambiguationWithCorrectRange() throws {
5271+
// This test deliberately does not turn on the overloads feature
5272+
// to ensure the symbol link below does not accidentally resolve correctly.
5273+
for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind {
5274+
// Generate a 4 symbols with the same name for every non overloadable symbol kind
5275+
let symbols: [SymbolGraph.Symbol] = [
5276+
makeSymbol(id: "first-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5277+
makeSymbol(id: "second-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5278+
makeSymbol(id: "third-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5279+
makeSymbol(id: "fourth-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5280+
]
5281+
5282+
let catalog =
5283+
Folder(name: "unit-test.docc", content: [
5284+
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(
5285+
moduleName: "ModuleName",
5286+
symbols: symbols
5287+
)),
5288+
5289+
TextFile(name: "ModuleName.md", utf8Content: """
5290+
# ``ModuleName``
5291+
5292+
This is a test file for ModuleName.
5293+
5294+
## Topics
5295+
5296+
- ``SymbolName-\(symbolKindID.identifier)``
5297+
""")
5298+
])
5299+
5300+
let (_, context) = try loadBundle(catalog: catalog)
5301+
5302+
let problems = context.problems.sorted(by: \.diagnostic.summary)
5303+
XCTAssertEqual(problems.count, 1)
5304+
5305+
let problem = try XCTUnwrap(problems.first)
5306+
5307+
XCTAssertEqual(problem.diagnostic.summary, "'SymbolName-\(symbolKindID.identifier)' is ambiguous at '/ModuleName'")
5308+
5309+
XCTAssertEqual(problem.possibleSolutions.count, 4)
5310+
5311+
for solution in problem.possibleSolutions {
5312+
XCTAssertEqual(solution.replacements.count, 1)
5313+
let replacement = try XCTUnwrap(solution.replacements.first)
5314+
5315+
XCTAssertEqual(replacement.range.lowerBound, .init(line: 7, column: 15, source: nil))
5316+
XCTAssertEqual(
5317+
replacement.range.upperBound,
5318+
.init(line: 7, column: 16 + symbolKindID.identifier.count, source: nil)
5319+
)
5320+
}
5321+
}
5322+
}
5323+
5324+
func testContextDiagnosesIncorrectDisambiguationWithCorrectRange() throws {
5325+
// This test deliberately does not turn on the overloads feature
5326+
// to ensure the symbol link below does not accidentally resolve correctly.
5327+
for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind {
5328+
// Generate a 4 symbols with the same name for every non overloadable symbol kind
5329+
let symbols: [SymbolGraph.Symbol] = [
5330+
makeSymbol(id: "first-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5331+
makeSymbol(id: "second-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5332+
makeSymbol(id: "third-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5333+
makeSymbol(id: "fourth-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5334+
]
5335+
5336+
let catalog =
5337+
Folder(name: "unit-test.docc", content: [
5338+
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(
5339+
moduleName: "ModuleName",
5340+
symbols: symbols
5341+
)),
5342+
5343+
TextFile(name: "ModuleName.md", utf8Content: """
5344+
# ``ModuleName``
5345+
5346+
This is a test file for ModuleName.
5347+
5348+
## Topics
5349+
5350+
- ``SymbolName-abc123``
5351+
""")
5352+
])
5353+
5354+
let (_, context) = try loadBundle(catalog: catalog)
5355+
5356+
let problems = context.problems.sorted(by: \.diagnostic.summary)
5357+
XCTAssertEqual(problems.count, 1)
5358+
5359+
let problem = try XCTUnwrap(problems.first)
5360+
5361+
XCTAssertEqual(problem.diagnostic.summary, "'abc123' isn't a disambiguation for 'SymbolName' at '/ModuleName'")
5362+
5363+
XCTAssertEqual(problem.possibleSolutions.count, 4)
5364+
5365+
for solution in problem.possibleSolutions {
5366+
XCTAssertEqual(solution.replacements.count, 1)
5367+
let replacement = try XCTUnwrap(solution.replacements.first)
5368+
5369+
// "Replace '-abc123' with '-(hash)'" where 'abc123' is at 7:15-7:22
5370+
XCTAssertEqual(replacement.range.lowerBound, .init(line: 7, column: 15, source: nil))
5371+
XCTAssertEqual(replacement.range.upperBound, .init(line: 7, column: 22, source: nil))
5372+
}
5373+
}
5374+
}
5375+
5376+
func testContextDiagnosesIncorrectSymbolNameWithCorrectRange() throws {
5377+
// This test deliberately does not turn on the overloads feature
5378+
// to ensure the symbol link below does not accidentally resolve correctly.
5379+
for symbolKindID in SymbolGraph.Symbol.KindIdentifier.allCases where !symbolKindID.isOverloadableKind {
5380+
// Generate a 4 symbols with the same name for every non overloadable symbol kind
5381+
let symbols: [SymbolGraph.Symbol] = [
5382+
makeSymbol(id: "first-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5383+
makeSymbol(id: "second-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5384+
makeSymbol(id: "third-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5385+
makeSymbol(id: "fourth-\(symbolKindID.identifier)-id", kind: symbolKindID, pathComponents: ["SymbolName"]),
5386+
]
5387+
5388+
let catalog =
5389+
Folder(name: "unit-test.docc", content: [
5390+
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(
5391+
moduleName: "ModuleName",
5392+
symbols: symbols
5393+
)),
5394+
5395+
TextFile(name: "ModuleName.md", utf8Content: """
5396+
# ``ModuleName``
5397+
5398+
This is a test file for ModuleName.
5399+
5400+
## Topics
5401+
5402+
- ``Symbol``
5403+
""")
5404+
])
5405+
5406+
let (_, context) = try loadBundle(catalog: catalog)
5407+
5408+
let problems = context.problems.sorted(by: \.diagnostic.summary)
5409+
XCTAssertEqual(problems.count, 1)
5410+
5411+
let problem = try XCTUnwrap(problems.first)
5412+
5413+
XCTAssertEqual(problem.diagnostic.summary, "'Symbol' doesn't exist at '/ModuleName'")
5414+
5415+
XCTAssertEqual(problem.possibleSolutions.count, 1)
5416+
let solution = try XCTUnwrap(problem.possibleSolutions.first)
5417+
5418+
XCTAssertEqual(solution.summary, "Replace 'Symbol' with 'SymbolName'")
5419+
5420+
XCTAssertEqual(solution.replacements.count, 1)
5421+
let replacement = try XCTUnwrap(solution.replacements.first)
5422+
5423+
XCTAssertEqual(replacement.range.lowerBound, .init(line: 7, column: 5, source: nil))
5424+
XCTAssertEqual(replacement.range.upperBound, .init(line: 7, column: 11, source: nil))
5425+
}
5426+
}
5427+
52705428
func testResolveExternalLinkFromTechnologyRoot() throws {
52715429
enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled)
52725430

0 commit comments

Comments
 (0)