Skip to content

Populate abbreviated declaration fragments in external render nodes #1255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

anferbui
Copy link
Contributor

@anferbui anferbui commented Jul 24, 2025

Bug/issue #, if applicable: rdar://156488052

Summary

Improves the quality of the navigator titles for external render nodes. Navigator titles are derived from the item's declaration fragments when at least one of the following is met 1:

  • the source language is Swift
  • there is no available navigator title
  • the page type is not part of a curated list 2

These declaration fragments are expected to be the abbreviated declaration fragments 3 of the symbol.

This PR helps capture the abbreviated declaration fragments of the symbol for external entities, so that they can be used in the navigator. The final goal is to have external entities in the navigator render no differently from local entities.

Implementation

There was no change needed to the LinkResolver.ExternalEntity type, as some declaration fragments were already being stored as part of LinkResolver.ExternalEntity.TopicRenderReference.fragmentsVariants 4.

The main issue was that while OutOfProcessReferenceResolver.ResolvedInformation relied on the fragments that it was receiving from the OutOfProcessReferenceResolver to be the abbreviated declaration fragments, LinkDestinationSummary was only encoding the full declaration fragments only as part of its summary:

let declaration = (symbol.declarationVariants[summaryTrait] ?? symbol.declaration).renderDeclarationTokens()

let declarationVariant = symbol.declarationVariants[trait]?.renderDeclarationTokens()

The abbreviated declaration fragments are different from the full declaration fragments and defined as a different field (subHeading 5) in the symbol graph (and therefore cannot be derived by DocC), but they're ultimately processed and captured as part of RenderMetadata.fragmentsVariants 6.

The main changes needed were to update the documentation of OutOfProcessReferenceResolver.ResolvedInformation and LinkDestinationSummary to have an extra property, subHeadingDeclarationFragments, which stores the abbreviated declaration fragments. These declaration fragments can then be used when initialising LinkResolver.ExternalEntity.TopicRenderReference.fragmentsVariants.

More detail on the implementation can be found in commit 832f1eb.

Alternatives considered

Considered modifying LinkDestinationSummary.declarationFragments instead of adding a new optional field, however the full declaration fragments are used to determine the full name 7 of the symbol for diagnostic reporting 8.

By introducing a new field we also ensure this is a non-breaking change.

Navigator comparison (using swift-docc-render):

This compares what it looks like to refer to those symbols within its local DocC bundle versus what it would look like to curate them as external links:

Local External
Screenshot 2025-07-24 at 15 15 25 Screenshot 2025-07-24 at 15 16 56

And if we also bring in the symbol kind changes from #1251, it becomes even closer:

Local External
Screenshot 2025-07-24 at 15 15 25 Screenshot 2025-07-24 at 15 19 01

Dependencies

N/A

Testing

Verified by using 2 bundles -- one for generating linkable entities, another for referencing an external link from the other bundle -- and verifying that the declaration fragments in the linkable entities are the abbreviated, subheading declaration fragments.

Steps:

  1. Download and extract the test bundles: Example.zip
  2. Build the linkable entities for the first bundle: swift run docc preview ExampleA.docc --emit-digest
    a. Look at http://localhost:8000/documentation/mykit
    b. Look at the Topics section and the navigator titles for both the class and the function
  3. Modify the link resolver in the second bundle to resolves the updated linkable entities for the first bundle
  4. Build the linkable entities for the second bundle: DOCC_LINK_RESOLVER_EXECUTABLE=ExampleB.docc/bin/test-data-external-resolver swift run docc preview ExampleB.docc
  5. Verify that the Topics section and the navigator titles match the ones from step 2b.

Checklist

Make sure you check off the following items. If they cannot be completed, provide a reason.

  • Added Updated tests
  • Ran the ./bin/test script and it succeeded
  • Updated documentation if necessary

@anferbui anferbui force-pushed the declaration-fragments-in-external-render-nodes branch from 8401492 to ee465d6 Compare July 24, 2025 14:31
@anferbui
Copy link
Contributor Author

anferbui commented Jul 24, 2025

Some tests are failing in ExternalPathHierarchyResolverTests which look like the error diagnostics were requiring the full declaration fragments for external links. Needs investigation.

The full declaration fragments are used to determine the full name of the symbol for diagnostic reporting:

return Solution(summary: "\(Self.replacementOperationDescription(from: foundDisambiguation, to: suggestedDisambiguation, forCollision: true)) for \n\(fullName.singleQuoted)", replacements: [
Replacement(range: replacementRange, replacement: suggestedDisambiguation)
])

I think we will need a new field in LinkDestinationSummary to contain the abbreviated declaration fragments rather than re-using the existing one.

anferbui added 3 commits July 25, 2025 14:01
LinkDestinationSummary contains a summary of an element that you can link to from outside the documentation bundle. [1]
This information is meant to be used by a server to provide information to an out-of-process resolver to resolve links to external entities, so that the partner implementation of `LinkDestinationSummary` is `OutOfProcessReferenceResolver.ResolvedInformation` [2].
As part of `LinkDestinationSummary`, we store the full declaration fragments of the symbol [3][4].

However, currently `OutOfProcessReferenceResolver` is using these full declaration fragments to populate `TopicRenderReference.fragmentsVariants` [5], which expects the abbreviated declaration fragments [6].
These abbreviated declaration fragments are meant to be used in the Topics section and the navigation index in order to reduce verbosity and improve readability by removing any declaration fragments non-essential to the core of the symbol declaration.

These abbreviated declaration fragments are not captured as part of `LinkDestinationSummary` or `OutOfProcessReferenceResolver.ResolvedInformation`. To start capturing this information, this commit modifies `LinkDestinationSummary` to add a new optional field `subHeadingDeclarationFragments` which stores the abbreviated declaration fragments  from `renderNode.metadata.fragmentsVariants` (abbreviated declaration fragments are derived from the `subHeading` of the symbol [7], further processed during the render node transformation phase, and finally stored as `renderNode.metadata.fragmentsVariants` [8]).

This will enable us to use them in `OutOfProcessReferenceResolver.ResolvedInformation` to initialise `TopicRenderReference.fragmentsVariants` with the expected value.

The final goal is for a symbol's representation in the Topics section and the navigation index to look the same regardless of whether the symbol is a local or external one.

Fixes rdar://156488052.

Alternatives considered:
------------------------

Considered modifying `LinkDestinationSummary.declarationFragments` instead of adding a new optional field, however the full declaration fragments are used to determine the full name [9] of the symbol for diagnostic reporting [10].

By introducing a new field we also ensure this is a non-breaking change.

[1]:  https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift#L66
[2]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Infrastructure/External%20Data/OutOfProcessReferenceResolver.swift#L558-L562
[3]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift#L140-L141
[4]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift#L445
[5]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/Infrastructure/External%20Data/OutOfProcessReferenceResolver.swift#L169
[6]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/Model/Rendering/References/TopicRenderReference.swift#L50-L53
[7]: https://github.com/swiftlang/swift-docc-symbolkit/blob/ebe89c7da4cf03ded04cd708f3399087c6f2dad7/Sources/SymbolKit/SymbolGraph/Symbol/Names.swift#L28-L31
[8]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift#L1304
[9]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/Infrastructure/Link%20Resolution/ExternalPathHierarchyResolver.swift#L61-L63
[10]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/Infrastructure/Link%20Resolution/PathHierarchy%2BError.swift#L115-L117
`LinkDestinationSummary` now provides information for both the full and abbreviated declaration fragments.

This commit adds the abbreviated declaration fragments to its counterpart type `OutOfProcessReferenceResolver.ResolvedInformation` [1], and uses them to initialise the external entity's `TopicRenderReference`. This is because the `TopicRenderReference` expects the abbreviated declaration fragments [2].

This results in the abbreviated declaration fragments correctly being used in the Topics and navigation index, rather than the full declaration fragments.

Fixes rdar://156488052.

[1]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/Infrastructure/External%20Data/OutOfProcessReferenceResolver.swift#L559-L562
[2]: https://github.com/swiftlang/swift-docc/blob/1b4a1850dd2785a8ebabded139ae0af3551bb029/Sources/SwiftDocC/Model/Rendering/References/TopicRenderReference.swift#L50-L53
Now that the declaration fragments will be the abbreviated declaration fragments from `LinkDestinationSummary`, we can propagate those to the navigator metadata for them to be used to inform the title of the navigator item [1].

Fixes rdar://156488052.

[1]: https://github.com/swiftlang/swift-docc/blob/65aaf926ec079ddbd40f29540d4180a70af99e5e/Sources/SwiftDocC/Indexing/Navigator/RenderNode%2BNavigatorIndex.swift#L140
@anferbui anferbui force-pushed the declaration-fragments-in-external-render-nodes branch from ee465d6 to 79c9844 Compare July 25, 2025 15:06
@anferbui
Copy link
Contributor Author

I've updated the implementation so that all unit tests should now pass.

@anferbui
Copy link
Contributor Author

@swift-ci please test

@anferbui
Copy link
Contributor Author

@swift-ci please test

@sofiaromorales
Copy link
Contributor

Thanks for the incredible work on documenting this PR, Andrea!

The only question I have about the implementation is if TopicRenderReference only takes fragments, and not subheadingFragments [1], why do we need to add subHeadingDeclarationFragments into the ResolvedInformation [2]? Can't we simply keep the summarized version and default its value to the full declaration from the LinkDestinationSummary in case this is nil?

[1] https://github.com/swiftlang/swift-docc/pull/1255/files#diff-c195a5776f681e560e52fc959b22479573bbc847217e4ba4b66c0a0f29f31424L169
[2] https://github.com/swiftlang/swift-docc/pull/1255/files#diff-c195a5776f681e560e52fc959b22479573bbc847217e4ba4b66c0a0f29f31424R584

Comment on lines +319 to +330
XCTAssertEqual(summary.subHeadingDeclarationFragments, [
.init(text: "func", kind: .keyword, identifier: nil),
.init(text: " ", kind: .text, identifier: nil),
.init(text: "globalFunction", kind: .identifier, identifier: nil),
.init(text: "(", kind: .text, identifier: nil),
.init(text: "Data", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:10Foundation4DataV"),
.init(text: ", ", kind: .text, identifier: nil),
.init(text: "considering", kind: .identifier, identifier: nil),
.init(text: ": ", kind: .text, identifier: nil),
.init(text: "Int", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:Si"),
.init(text: ")", kind: .text, identifier: nil)
])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an example where the subheading declaration fragments and the full declaration fragments are not the same?

Comment on lines +581 to +585
/// Information about the resolved abbreviated declaration fragments, if any.
///
/// They are used for displaying in contexts where the full declaration fragments would be too verbose, like in the Topics section or the navigation index.
public let subHeadingDeclarationFragments: DeclarationFragments?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the subheading declaration fragments it's used instead of the fragments in the TopicRenderReference why do we want to keep declarationFragments here instead of only keeping the subheading one?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants