Skip to content

Commit

Permalink
Process typeDetails information from symbol graphs. (swiftlang#529)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
pdwilson12 authored and Peter Wilson committed Mar 31, 2023
1 parent 06737e0 commit 33d15c9
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"repositoryURL": "https://github.com/apple/swift-docc-symbolkit",
"state": {
"branch": "main",
"revision": "ccbbd881d75d3ca503daf8cf3b5d78adbf647da9",
"revision": "53e5cb9b18222f66cb8d6fb684d7383e705e0936",
"version": null
}
},
Expand Down
49 changes: 33 additions & 16 deletions Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ class SemaToRenderNodeDictionaryDataTests: XCTestCase {
let expectedPageUSRs: Set<String> = Set(expectedPageUSRsAndLangs.keys)

let expectedNonpageUSRs: Set<String> = [
// 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",
]

Expand Down Expand Up @@ -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")
Expand All @@ -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)'."
}
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -192,7 +242,11 @@
"declarationFragments": [
{
"kind": "text",
"spelling": "string Genre"
"spelling": "string "
},
{
"kind": "identifier",
"spelling": "Genre"
}
],
"identifier": {
Expand All @@ -218,6 +272,11 @@
},
"pathComponents": [
"Genre"
],
"typeDetails": [
{
"baseType": "string",
}
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftDocCTests/XCTestCase+AssertingTestData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 33d15c9

Please sign in to comment.