From 6cb814237817e5d5832ec537b1b79b145c744332 Mon Sep 17 00:00:00 2001 From: Franklin Schrans Date: Mon, 11 Apr 2022 14:33:10 +0100 Subject: [PATCH] Register references resolved by fallback resolvers When resolving a link via a fallback resolver (i.e., a resolver that resolves additional content for the bundle's identifier), register the reference in the documentation context so that it can be looked up via its absolute URL string. This is important for subsequent link resolution requests, in order for the reference to be looked up in the context rather than getting resolved again. rdar://91545038 --- .../Infrastructure/DocumentationContext.swift | 11 +- .../ConvertService/ConvertServiceTests.swift | 106 +++++++++ .../one-symbol-top-level.symbols.json | 213 ++++++++++++++++++ 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 Tests/SwiftDocCTests/Test Resources/one-symbol-top-level.symbols.json diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift index 5f47ddc60d..569a25c7eb 100644 --- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift +++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift @@ -1378,12 +1378,16 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { referencesIndex.removeAll() referencesIndex.reserveCapacity(knownIdentifiers.count) for reference in knownIdentifiers { - referencesIndex[reference.absoluteString] = reference + registerReference(reference) } return (moduleReferences: Set(moduleReferences.values), urlHierarchy: symbolsURLHierarchy) } } + + private func registerReference(_ resolvedReference: ResolvedTopicReference) { + referencesIndex[resolvedReference.absoluteString] = resolvedReference + } private func shouldContinueRegistration() throws { guard isRegistrationEnabled.sync({ $0 }) else { @@ -2592,6 +2596,11 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { if case .success(let resolvedReference) = reference { cacheReference(resolvedReference, withKey: ResolvedTopicReference.cacheIdentifier(unresolvedReference, fromSymbolLink: isCurrentlyResolvingSymbolLink, in: parent)) + + // Register the resolved reference in the context so that it can be looked up via its absolute + // path. We only do this for in-bundle content, and since we've just resolved an in-bundle link, + // we register the reference. + registerReference(resolvedReference) return .success(resolvedReference) } } diff --git a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift index 87808e3568..a4dcca1cab 100644 --- a/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift +++ b/Tests/SwiftDocCTests/DocumentationService/ConvertService/ConvertServiceTests.swift @@ -1187,6 +1187,112 @@ class ConvertServiceTests: XCTestCase { } } + func testConvertTopLevelSymbolWithLinkResolving() throws { + let symbolGraphFile = Bundle.module.url( + forResource: "one-symbol-top-level", + withExtension: "symbols.json", + subdirectory: "Test Resources" + )! + + let symbolGraph = try Data(contentsOf: symbolGraphFile) + + let request = ConvertRequest( + bundleInfo: DocumentationBundle.Info( + displayName: "TestBundle", + identifier: "org.swift.example", + version: "1.0.0" + ), + externalIDsToConvert: ["s:32MyKit3FooV"], + documentPathsToConvert: [], + symbolGraphs: [symbolGraph], + markupFiles: [], + miscResourceURLs: [] + ) + + let server = DocumentationServer() + + let mockLinkResolvingService = LinkResolvingService { message in + do { + let payload = try XCTUnwrap(message.payload) + let request = try JSONDecoder() + .decode( + ConvertRequestContextWrapper.self, + from: payload + ) + + let errorResponse = DocumentationServer.Message( + type: "resolve-reference-response", + payload: try JSONEncoder().encode( + OutOfProcessReferenceResolver.Response + .errorMessage("Unable to resolve reference.") + ) + ) + + switch request.payload { + case .topic(let url): + let resolvableBarURL = URL( + string: "doc://org.swift.example/documentation/MyKit/Foo/bar()" + )! + + if url == resolvableBarURL { + let testSymbolInformationResponse = OutOfProcessReferenceResolver + .ResolvedInformation( + kind: .init( + name: "bar()", + id: "org.swift.docc.kind.method", + isSymbol: true + ), + url: resolvableBarURL, + title: "bar()", + abstract: "", + language: .init(name: "Swift", id: "swift"), + availableLanguages: [], + platforms: [], + declarationFragments: nil + ) + + let payloadData = OutOfProcessReferenceResolver.Response + .resolvedInformation(testSymbolInformationResponse) + + return DocumentationServer.Message( + type: "resolve-reference-response", + payload: try JSONEncoder().encode(payloadData) + ) + } else { + return errorResponse + } + default: + return errorResponse + } + } catch { + XCTFail(error.localizedDescription) + return nil + } + } + + server.register(service: mockLinkResolvingService) + + try processAndAssert(request: request, linkResolvingServer: server) { message in + let renderNodes = try JSONDecoder().decode( + ConvertResponse.self, + from: XCTUnwrap(message.payload) + ).renderNodes + + XCTAssertEqual(renderNodes.count, 1) + let data = try XCTUnwrap(renderNodes.first) + let renderNode = try JSONDecoder().decode(RenderNode.self, from: data) + + XCTAssertEqual( + Set(renderNode.references.keys), + [ + "doc://org.swift.example/documentation/MyKit", + "doc://org.swift.example/documentation/MyKit/Foo", + "doc://org.swift.example/documentation/MyKit/Foo/bar()", + ] + ) + } + } + func testOrderOfLinkResolutionRequestsForDocLink() throws { let symbolGraphFile = try XCTUnwrap( Bundle.module.url( diff --git a/Tests/SwiftDocCTests/Test Resources/one-symbol-top-level.symbols.json b/Tests/SwiftDocCTests/Test Resources/one-symbol-top-level.symbols.json new file mode 100644 index 0000000000..36bbe5df57 --- /dev/null +++ b/Tests/SwiftDocCTests/Test Resources/one-symbol-top-level.symbols.json @@ -0,0 +1,213 @@ +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 5, + "patch": 3 + }, + "generator": "Apple Swift version 5.7 (swiftlang-5.7.0.101.10 clang-1400.0.10.4.2)" + }, + "module": { + "name": "MyKit", + "platform": { + "architecture": "x86_64", + "vendor": "apple", + "operatingSystem": { + "name": "macosx", + "minimumVersion": { + "major": 10, + "minor": 10, + "patch": 0 + } + } + } + }, + "symbols": [ + { + "kind": { + "identifier": "swift.struct", + "displayName": "Structure" + }, + "identifier": { + "precise": "s:32MyKit3FooV", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Foo" + ], + "names": { + "title": "Foo" + }, + "docComment": { + "lines": [ + { + "range": { + "start": { + "line": 0, + "character": 4 + }, + "end": { + "line": 0, + "character": 15 + } + }, + "text": "Foo" + }, + { + "range": { + "start": { + "line": 1, + "character": 3 + }, + "end": { + "line": 1, + "character": 3 + } + }, + "text": "" + }, + { + "range": { + "start": { + "line": 2, + "character": 4 + }, + "end": { + "line": 2, + "character": 39 + } + }, + "text": "All of these links should resolve:" + }, + { + "range": { + "start": { + "line": 3, + "character": 3 + }, + "end": { + "line": 3, + "character": 3 + } + }, + "text": "" + }, + { + "range": { + "start": { + "line": 4, + "character": 4 + }, + "end": { + "line": 4, + "character": 13 + } + }, + "text": "``bar()``" + }, + { + "range": { + "start": { + "line": 5, + "character": 3 + }, + "end": { + "line": 5, + "character": 3 + } + }, + "text": "" + }, + { + "range": { + "start": { + "line": 6, + "character": 4 + }, + "end": { + "line": 6, + "character": 17 + } + }, + "text": "``Foo/bar()``" + }, + { + "range": { + "start": { + "line": 7, + "character": 3 + }, + "end": { + "line": 7, + "character": 3 + } + }, + "text": "" + }, + { + "range": { + "start": { + "line": 8, + "character": 4 + }, + "end": { + "line": 8, + "character": 22 + } + }, + "text": "``Foo/otherBar()``" + }, + { + "range": { + "start": { + "line": 9, + "character": 3 + }, + "end": { + "line": 9, + "character": 3 + } + }, + "text": "" + }, + { + "range": { + "start": { + "line": 10, + "character": 4 + }, + "end": { + "line": 10, + "character": 50 + } + }, + "text": "``MyKit/Foo/bar()``" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "struct" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Foo" + } + ], + "accessLevel": "public", + "location": { + "uri": "file:///tmp/Downloads/MyKit/MyKit.swift", + "position": { + "line": 11, + "character": 14 + } + } + } + ], + "relationships": [] +}