From 33d15c933ac6814646e50009674cb387247236e7 Mon Sep 17 00:00:00 2001 From: pdwilson12 Date: Fri, 31 Mar 2023 16:48:48 -0700 Subject: [PATCH 1/2] Process `typeDetails` information from symbol graphs. (#529) * Process `typeDetails` information from symbol graphs. Convert data into base-type details and allowed type declarations for `RenderProperty` entities, such as dictionary keys and HTTP parameters. * Update SymbolKit dependency to pick up latest commit. --- Package.resolved | 2 +- .../Rendering/RenderNodeTranslator.swift | 49 +++++++++----- .../SemaToRenderNodeDictionaryDataTests.swift | 65 +++++++++++++++++-- .../dictionary.symbols.json | 61 ++++++++++++++++- .../XCTestCase+AssertingTestData.swift | 2 +- 5 files changed, 155 insertions(+), 24 deletions(-) diff --git a/Package.resolved b/Package.resolved index 6fc2c43930..42551b80a8 100644 --- a/Package.resolved +++ b/Package.resolved @@ -42,7 +42,7 @@ "repositoryURL": "https://github.com/apple/swift-docc-symbolkit", "state": { "branch": "main", - "revision": "ccbbd881d75d3ca503daf8cf3b5d78adbf647da9", + "revision": "53e5cb9b18222f66cb8d6fb684d7383e705e0936", "version": null } }, diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index 7e1fbf4eed..18960359d2 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -1741,6 +1741,24 @@ public struct RenderNodeTranslator: SemanticVisitor { } } + private mutating func convert_fragments(_ fragments: [SymbolGraph.Symbol.DeclarationFragments.Fragment]) -> [DeclarationRenderSection.Token] { + return fragments.map { token -> DeclarationRenderSection.Token in + + // Create a reference if one found + var reference: ResolvedTopicReference? + if let preciseIdentifier = token.preciseIdentifier, + let resolved = self.context.symbolIndex[preciseIdentifier]?.reference { + reference = resolved + + // Add relationship to render references + self.collectedTopicReferences.append(resolved) + } + + // Add the declaration token + return DeclarationRenderSection.Token(fragment: token, identifier: reference?.absoluteString) + } + } + /// Generate a RenderProperty object from markup content and symbol data. mutating func createRenderProperty(name: String, contents: [Markup], required: Bool, symbol: SymbolGraph.Symbol?) -> RenderProperty { let parameterContent = self.visitMarkupContainer( @@ -1752,25 +1770,12 @@ public struct RenderNodeTranslator: SemanticVisitor { var isReadOnly: Bool? = nil var deprecated: Bool? = nil var introducedVersion: String? = nil + var typeDetails: [TypeDetails]? = nil if let symbol = symbol { // Convert the dictionary key's declaration into section tokens if let fragments = symbol.declarationFragments { - renderedTokens = fragments.map { token -> DeclarationRenderSection.Token in - - // Create a reference if one found - var reference: ResolvedTopicReference? - if let preciseIdentifier = token.preciseIdentifier, - let resolved = self.context.symbolIndex[preciseIdentifier]?.reference { - reference = resolved - - // Add relationship to render references - self.collectedTopicReferences.append(resolved) - } - - // Add the declaration token - return DeclarationRenderSection.Token(fragment: token, identifier: reference?.absoluteString) - } + renderedTokens = convert_fragments(fragments) } // Populate attributes @@ -1801,6 +1806,18 @@ public struct RenderNodeTranslator: SemanticVisitor { if let constraint = symbol.maximumLength { attributes.append(RenderAttribute.maximumLength(String(constraint))) } + if let constraint = symbol.typeDetails, constraint.count > 0 { + // Pull out the base-type details. + typeDetails = constraint.filter { $0.baseType != nil } + .map { TypeDetails(baseType: $0.baseType, arrayMode: $0.arrayMode) } + // Pull out the allowed-type declarations. + // If there is only 1 type declaration found, it would be redundant with declaration, so skip it. + let typeDeclarations = constraint.compactMap { $0.fragments } + if typeDeclarations.count > 1 { + let allowedTypes = typeDeclarations.map { convert_fragments($0) } + attributes.append(RenderAttribute.allowedTypes(allowedTypes)) + } + } // Extract the availability information if let availabilityItems = symbol.availability, availabilityItems.count > 0 { @@ -1818,7 +1835,7 @@ public struct RenderNodeTranslator: SemanticVisitor { return RenderProperty( name: name, type: renderedTokens ?? [], - typeDetails: nil, + typeDetails: typeDetails, content: parameterContent, attributes: attributes, mimeType: symbol?.httpMediaType, diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeDictionaryDataTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeDictionaryDataTests.swift index 728db39fe3..c192a2a44f 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeDictionaryDataTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeDictionaryDataTests.swift @@ -37,13 +37,16 @@ class SemaToRenderNodeDictionaryDataTests: XCTestCase { let expectedPageUSRs: Set = Set(expectedPageUSRsAndLangs.keys) let expectedNonpageUSRs: Set = [ - // Name string type - ``Name``: + // Name string field - ``name``: "data:test:Artist@name", - // Genre string type - ``Genre``: + // Genre string field - ``genre``: "data:test:Artist@genre", - // Age integer type - ``Age``: + // Month of birth string field - ``monthOfBirth``: + "data:test:Artist@monthOfBirth", + + // Age integer field - ``age``: "data:test:Artist@age", ] @@ -179,7 +182,7 @@ class SemaToRenderNodeDictionaryDataTests: XCTestCase { } XCTAssertEqual(propertiesSection.kind, .properties) - XCTAssertEqual(propertiesSection.items.count, 3) + XCTAssertEqual(propertiesSection.items.count, 4) let ageProperty = propertiesSection.items[0] XCTAssertEqual(ageProperty.name, "age") @@ -193,9 +196,61 @@ class SemaToRenderNodeDictionaryDataTests: XCTestCase { attributeTitles = genreProperty.attributes?.map{$0.title.lowercased()}.sorted() ?? [] XCTAssertEqual(attributeTitles, ["default value", "possible values"]) - let nameProperty = propertiesSection.items[2] + let monthProperty = propertiesSection.items[2] + XCTAssertEqual(monthProperty.name, "monthOfBirth") + XCTAssertNotNil(monthProperty.typeDetails) + if let details = monthProperty.typeDetails { + XCTAssertEqual(details.count, 2) + XCTAssertEqual(details[0].baseType, "integer") + XCTAssertEqual(details[1].baseType, "string") + } + attributeTitles = monthProperty.attributes?.map{$0.title.lowercased()}.sorted() ?? [] + XCTAssertEqual(attributeTitles, ["possible types"]) + monthProperty.attributes?.forEach { attribute in + if case let .allowedTypes(decls) = attribute { + XCTAssertEqual(decls.count, 2) + XCTAssertEqual(decls[0][0].text, "integer") + XCTAssertEqual(decls[1][0].text, "string") + } + } + + let nameProperty = propertiesSection.items[3] XCTAssertEqual(nameProperty.name, "name") XCTAssertTrue(nameProperty.required ?? false) XCTAssert((nameProperty.attributes ?? []).isEmpty) } + + func testTypeRenderNodeHasExpectedContent() throws { + let outputConsumer = try renderNodeConsumer(for: "DictionaryData") + let genreRenderNode = try outputConsumer.renderNode(withIdentifier: "data:test:Genre") + + print(genreRenderNode) + assertExpectedContent( + genreRenderNode, + sourceLanguage: "data", + symbolKind: "typealias", + title: "Genre", + navigatorTitle: nil, + abstract: nil, + declarationTokens: [ + "string ", + "Genre" + ], + discussionSection: nil, + topicSectionIdentifiers: [], + referenceTitles: [ + "Artist", + "DictionaryData", + "Genre", + ], + referenceFragments: [ + "object Artist", + "string Genre", + ], + failureMessage: { fieldName in + "'Genre' symbol has unexpected content for '\(fieldName)'." + } + ) + } + } diff --git a/Tests/SwiftDocCTests/Test Bundles/DictionaryData.docc/dictionary.symbols.json b/Tests/SwiftDocCTests/Test Bundles/DictionaryData.docc/dictionary.symbols.json index ecbbf9d460..9ce66cbdc3 100644 --- a/Tests/SwiftDocCTests/Test Bundles/DictionaryData.docc/dictionary.symbols.json +++ b/Tests/SwiftDocCTests/Test Bundles/DictionaryData.docc/dictionary.symbols.json @@ -33,6 +33,12 @@ "target": "data:test:Artist", "targetFallback": null }, + { + "kind": "optionalMemberOf", + "source": "data:test:Artist@monthOfBirth", + "target": "data:test:Artist", + "targetFallback": null + }, { "kind": "memberOf", "source": "data:test:Artist@name", @@ -163,6 +169,50 @@ "genre" ] }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "text", + "spelling": "*" + } + ], + "identifier": { + "interfaceLanguage": "data", + "precise": "data:test:Artist@monthOfBirth" + }, + "kind": { + "displayName": "Dictionary Key", + "identifier": "dictionaryKey" + }, + "names": { + "title": "monthOfBirth" + }, + "pathComponents": [ + "Artist", + "monthOfBirth" + ], + "typeDetails": [ + { + "baseType": "integer", + "fragments": [ + { + "kind": "text", + "spelling": "integer" + } + ] + }, + { + "baseType": "string", + "fragments": [ + { + "kind": "text", + "spelling": "string" + } + ] + } + ] + }, { "accessLevel": "public", "declarationFragments": [ @@ -192,7 +242,11 @@ "declarationFragments": [ { "kind": "text", - "spelling": "string Genre" + "spelling": "string " + }, + { + "kind": "identifier", + "spelling": "Genre" } ], "identifier": { @@ -218,6 +272,11 @@ }, "pathComponents": [ "Genre" + ], + "typeDetails": [ + { + "baseType": "string", + } ] }, { diff --git a/Tests/SwiftDocCTests/XCTestCase+AssertingTestData.swift b/Tests/SwiftDocCTests/XCTestCase+AssertingTestData.swift index 7662837831..b4e39a289d 100644 --- a/Tests/SwiftDocCTests/XCTestCase+AssertingTestData.swift +++ b/Tests/SwiftDocCTests/XCTestCase+AssertingTestData.swift @@ -22,7 +22,7 @@ extension XCTestCase { symbolKind expectedSymbolKind: String? = nil, title expectedTitle: String, navigatorTitle expectedNavigatorTitle: String?, - abstract expectedAbstract: String, + abstract expectedAbstract: String?, declarationTokens expectedDeclarationTokens: [String]?, endpointTokens expectedEndpointTokens: [String]? = nil, httpParameters expectedHTTPParameters: [String]? = nil, From a8778a74f41b49ca6b27e3302f0324b64dbe146d Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Fri, 31 Mar 2023 16:57:58 -0700 Subject: [PATCH 2/2] Update SymbolKit dependency to point to release/5.9 branch. --- Package.resolved | 4 ++-- Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index 42551b80a8..00aafe9a4f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -41,8 +41,8 @@ "package": "SymbolKit", "repositoryURL": "https://github.com/apple/swift-docc-symbolkit", "state": { - "branch": "main", - "revision": "53e5cb9b18222f66cb8d6fb684d7383e705e0936", + "branch": "release/5.9", + "revision": "d42e3db1c0f97f4a3be9a4552f7003c2959268d5", "version": null } }, diff --git a/Package.swift b/Package.swift index a8a2a2b0e9..a79df3a617 100644 --- a/Package.swift +++ b/Package.swift @@ -118,7 +118,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(name: "swift-markdown", url: "https://github.com/apple/swift-markdown.git", .branch("main")), .package(name: "CLMDB", url: "https://github.com/apple/swift-lmdb.git", .branch("main")), .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "1.0.1")), - .package(name: "SymbolKit", url: "https://github.com/apple/swift-docc-symbolkit", .branch("main")), + .package(name: "SymbolKit", url: "https://github.com/apple/swift-docc-symbolkit", .branch("release/5.9")), .package(url: "https://github.com/apple/swift-crypto.git", .upToNextMinor(from: "1.1.2")), ]