Skip to content

Commit 1b6e17b

Browse files
authored
Fix a bug where article-only catalogs couldn't resolve external links (#1013)
1 parent 2be46d9 commit 1b6e17b

File tree

3 files changed

+99
-1
lines changed

3 files changed

+99
-1
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ extension PathHierarchy {
9090
// Ignore this error and raise an error about not finding the module instead.
9191
break
9292
case .unknownName(let partialResult, remaining: _, availableChildren: _):
93-
if partialResult.node.symbol?.kind.identifier == .module {
93+
if modules.contains(where: { $0.identifier == partialResult.node.identifier }) {
9494
// Failed to find the first path component. Ignore this error and raise an error about not finding the module instead.
9595
break
9696
} else {

Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5340,6 +5340,78 @@ let expected = """
53405340
}
53415341
}
53425342
}
5343+
5344+
func testResolveExternalLinkFromTechnologyRoot() throws {
5345+
enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled)
5346+
5347+
let externalModuleName = "ExternalModuleName"
5348+
5349+
func makeExternalResolver() throws -> ExternalPathHierarchyResolver {
5350+
let (bundle, context) = try loadBundle(
5351+
catalog: Folder(name: "Dependency.docc", content: [
5352+
JSONFile(name: "\(externalModuleName).symbols.json", content: makeSymbolGraph(moduleName: externalModuleName)),
5353+
TextFile(name: "Extension.md", utf8Content: """
5354+
# ``\(externalModuleName)``
5355+
5356+
Some description of this module.
5357+
""")
5358+
])
5359+
)
5360+
5361+
// Retrieve the link information from the dependency, as if '--enable-experimental-external-link-support' was passed to DocC
5362+
let converter = DocumentationNodeConverter(bundle: bundle, context: context)
5363+
let linkSummaries: [LinkDestinationSummary] = try context.knownPages.flatMap { reference in
5364+
let entity = try context.entity(with: reference)
5365+
let renderNode = try XCTUnwrap(converter.convert(entity))
5366+
5367+
return entity.externallyLinkableElementSummaries(context: context, renderNode: renderNode, includeTaskGroups: false)
5368+
}
5369+
let linkResolutionInformation = try context.linkResolver.localResolver.prepareForSerialization(bundleID: bundle.identifier)
5370+
5371+
return ExternalPathHierarchyResolver(linkInformation: linkResolutionInformation, entityInformation: linkSummaries)
5372+
}
5373+
5374+
let catalog = Folder(name: "unit-test.docc", content: [
5375+
TextFile(name: "Root.md", utf8Content: """
5376+
# Some root page
5377+
5378+
A single-file article-only catalog.
5379+
5380+
This root links to an external module ``/\(externalModuleName)``
5381+
"""),
5382+
])
5383+
5384+
let (_, bundle, context) = try loadBundle(from: createTempFolder(content: [catalog])) { context in
5385+
context.linkResolver.externalResolvers = [externalModuleName: try makeExternalResolver()]
5386+
}
5387+
5388+
XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))")
5389+
let reference = try XCTUnwrap(context.soleRootModuleReference)
5390+
let node = try context.entity(with: reference)
5391+
5392+
let converter = DocumentationNodeConverter(bundle: bundle, context: context)
5393+
let renderNode = try converter.convert(node)
5394+
5395+
let externalReference = "doc://Dependency/documentation/ExternalModuleName"
5396+
5397+
// Verify that the rendered page contains the resolved reference
5398+
let discussionSection = try XCTUnwrap(renderNode.primaryContentSections.first as? ContentRenderSection)
5399+
XCTAssertEqual(discussionSection.content, [
5400+
.heading(.init(level: 2, text: "Overview", anchor: "overview")),
5401+
5402+
.paragraph(.init(inlineContent: [
5403+
.text("This root links to an external module "),
5404+
.reference(identifier: RenderReferenceIdentifier(externalReference), isActive: true, overridingTitle: nil, overridingTitleInlineContent: nil)
5405+
]))
5406+
])
5407+
5408+
// Verify that the rendered page has the render details about the resolved reference
5409+
XCTAssertEqual(renderNode.references.keys.sorted(), [externalReference])
5410+
5411+
let externalRenderReference = try XCTUnwrap(renderNode.references[externalReference] as? TopicRenderReference)
5412+
XCTAssertEqual(externalRenderReference.title, externalModuleName)
5413+
XCTAssertEqual(externalRenderReference.abstract, [.text("Some description of this module.")])
5414+
}
53435415
}
53445416

53455417
func assertEqualDumps(_ lhs: String, _ rhs: String, file: StaticString = #file, line: UInt = #line) {

Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2476,6 +2476,32 @@ class PathHierarchyTests: XCTestCase {
24762476
}
24772477
}
24782478

2479+
func testResolveExternalLinkFromTechnologyRoot() throws {
2480+
enableFeatureFlag(\.isExperimentalLinkHierarchySerializationEnabled)
2481+
2482+
let catalog = Folder(name: "unit-test.docc", content: [
2483+
TextFile(name: "Root.md", utf8Content: """
2484+
# Some root page
2485+
2486+
A single-file article-only catalog
2487+
"""),
2488+
])
2489+
2490+
let (_, _, context) = try loadBundle(from: createTempFolder(content: [catalog]))
2491+
let tree = context.linkResolver.localResolver.pathHierarchy
2492+
2493+
let rootIdentifier = try XCTUnwrap(tree.modules.first?.identifier)
2494+
do {
2495+
_ = try tree.find(path: "/SomeModule", parent: rootIdentifier, onlyFindSymbols: true)
2496+
XCTFail("Unexpectedly found external symbol")
2497+
} catch PathHierarchy.Error.moduleNotFound(_, let remaining, _) {
2498+
// `LinkResolver` uses this error to know when to resolve a link from an external source.
2499+
XCTAssertEqual(remaining.map(\.full), ["SomeModule"])
2500+
} catch {
2501+
XCTFail("Unexpected error \(error)")
2502+
}
2503+
}
2504+
24792505
// MARK: Test helpers
24802506

24812507
private func assertFindsPath(_ path: String, in tree: PathHierarchy, asSymbolID symbolID: String, file: StaticString = #file, line: UInt = #line) throws {

0 commit comments

Comments
 (0)