Skip to content

Conversation

@jrturton
Copy link

Issue #1301

Summary

This PR addresses the changes proposed in #1301. It involves a new translator / semantic visitor / markup walker to create the new outputs from DocumentationNode. This is done for each node, after the render JSON is generated.

Dependencies

None

Testing

There are extensive unit tests in MarkdownOutputTests.swift. Testers can also run the convert command against any catalog with the --enable-experimental-markdown-output and --enable-experimental-markdown-output-manifest flags set.

Checklist

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

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

@jrturton jrturton linked an issue Sep 26, 2025 that may be closed by this pull request
Copy link
Contributor

@d-ronnqvist d-ronnqvist left a comment

Choose a reason for hiding this comment

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

There's a lot to unpack here and I've only had the time to skim thought the code but these are a few things that stand out to me:

  • If this is intended as experimental feature, then it can't add actual public API because consumers of SwiftDocC would have no way of knowing what API is stable and what is experimental and could still have breaking changes or be removed.

    If a goal of this API is that clients can use it while it's still experimental then it needs to be marked as SPI (using @_spi(SomeName) so that those clients make a deliberate decision to use it and acknowledge that it may have breaking changes.

  • The added tests follow a pattern not seen elsewhere in the repo. I left some more specific comments about that.

@jrturton
Copy link
Author

jrturton commented Oct 2, 2025

I have removed or @_spi-decorated all new public API, with the exception of the feature flags themselves, I assumed that would be OK

@d-ronnqvist d-ronnqvist added the needs forum discussion Needs to be discussed in the Swift Forums label Oct 10, 2025
Copy link
Contributor

@d-ronnqvist d-ronnqvist left a comment

Choose a reason for hiding this comment

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

There's quite a lot going on here. To name a few, there's:

  • logic bugs or bugs where the values are based of incorrect data
  • "public" properties that are very loosely typed (either String or Data) without accompanying documentation that describe how the caller can decode and use that information.
  • several symbol sections that's silently excluded from the output
  • a fair amount of supported markdown content that's never tested against (and AFAICT isn't defined what the expected output would be)
  • some supported tutorial content that's silently excluded from the output
  • some terminology that's not seen elsewhere in the project
  • a few design choices that could be worth revisiting
  • quite a lot of formatting that's different from existing code in this projected
  • some unclear "public" SPI where it's not clear to me what the use-case is and there's also no documentation to give the caller/client context to how and when they should use this "public" SPI.

Comment on lines +130 to +161
let fileSafePath = NodeURLGenerator.fileSafeReferencePath(
markdownNode.identifier,
lowercased: true
)

// The path on disk to write the markdown file at.
let markdownNodeTargetFileURL = renderNodeURLGenerator
.urlForReference(
markdownNode.identifier,
fileSafePath: fileSafePath
)
.appendingPathExtension("md")

let markdownNodeTargetFolderURL = markdownNodeTargetFileURL.deletingLastPathComponent()

// On Linux sometimes it takes a moment for the directory to be created and that leads to
// errors when trying to write files concurrently in the same target location.
// We keep an index in `directoryIndex` and create new sub-directories as needed.
// When the symbol's directory already exists no code is executed during the lock below
// besides the set lookup.
try directoryIndex.sync { directoryIndex in
let (insertedMarkdownNodeTargetFolderURL, _) = directoryIndex.insert(markdownNodeTargetFolderURL)
if insertedMarkdownNodeTargetFolderURL {
try fileManager.createDirectory(
at: markdownNodeTargetFolderURL,
withIntermediateDirectories: true,
attributes: nil
)
}
}

try fileManager.createFile(at: markdownNodeTargetFileURL, contents: markdownNode.nodeData, options: nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: This is duplicating some non-trivial code with only a few minor differences. Can we extract the common code into a private helper function that both write(_:) methods can call?

extension MarkdownOutputNode {
public struct Metadata: Codable, Sendable {

static let version = "0.1.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use SemanticVersion rather than strings for version information. See for example DiagnosticFile, SerializableLinkResolutionInformation, and RenderIndex.

Comment on lines +51 to +52
// If no introduced, we are unavailable
self.unavailable = unavailable || introduced == nil
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this logic is correct. A framework can have availability information configured in its Info.plist that doesn't include an introduced version. That configuration specifies that the framework is available for that platform.

Comment on lines +48 to +49
// Can't have deprecated without an introduced
self.introduced = introduced ?? deprecated
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if this is correct. It's possible to specify @available(macOS, deprecated: 26, message: "This symbol is deprecated") as an availability attribute in Swift.

Generally, no introduced version means that a symbol is available as far back as the framework itself supports.

}

public struct Symbol: Codable, Sendable {
public let kind: String
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Is this representing the tooling facing kind identifier (for example "struct" or "enum.case") or is this representing a display name (for example "Structure" or "Enumeration Case")?

If it's the former; is there a more precise type—for example SymbolKit/SymbolGraph/Symbol/KindIdentifier —that we could use here?

If it's the latter; please rename this to kindDisplayName or something similar that clarifies what this value represents.

}

var markdown = ""
var outgoingReferences: Set<MarkdownOutputManifest.Relationship> = []
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: what does this represent? Based on the implementation it looks like it's "topic curation" (documentation organization) but this property name sounds broader than that. It would be good to clarify this name and maybe add a documentation comment if the name alone isn't clear.

@@ -1,4 +1,5 @@
{
"originHash" : "6a2fba12d9d8cf682094fa377600d56c196ce947a70bf82d12a7faff1b81c530",
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: what does this hash represent?


}

// Merge into ConvertOutputMarkdownConsumer when no longer SPI
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this comment referring to? This is the ConvertOutputMarkdownConsumer protocol.


// Merge into ConvertOutputMarkdownConsumer when no longer SPI
@_spi(MarkdownOutput)
public protocol ConvertOutputMarkdownConsumer {
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Is there a use case for callers/clients to consume the in-memory representations of this information as opposed to decoding the output files? If not, can we make this internal only?


public let sourceURI: String
public let relationshipType: RelationshipType
public let subtype: String?
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: what does this represent? Is there a more precise type—either existing or new—that this could use rather than a string value?

Another way to look at it; what is the caller expected to do with this information? It's public but just a string so it could represent anything. With an enumeration there would be a known list of cases the the caller could check against and make decisions based on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs forum discussion Needs to be discussed in the Swift Forums

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LLM-accessible output

2 participants