-
Notifications
You must be signed in to change notification settings - Fork 156
Experimental markdown output #1303
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
base: main
Are you sure you want to change the base?
Experimental markdown output #1303
Conversation
…ert feature flags
There was a problem hiding this 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.
|
||
import Foundation | ||
|
||
// Consumers of `MarkdownOutputManifest` in other packages should be able to lift this file and be able to use it standalone, without any dependencies from SwiftDocC. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's a goal of this code that clients can use it without depending on SwiftDocC, should it be moved to its own target that consumers can depend on instead?
|
||
|
||
// MARK: Visitors not used for markdown output | ||
extension MarkdownOutputSemanticVisitor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these methods that only print meant to be included? Is there more work coming that would add support for these page elements?
static var loadingTask: Task<(DocumentationBundle, DocumentationContext), any Error>? | ||
|
||
func bundleAndContext() async throws -> (bundle: DocumentationBundle, context: DocumentationContext) { | ||
|
||
if let task = Self.loadingTask { | ||
return try await task.value | ||
} else { | ||
let task = Task { | ||
try await testBundleAndContext(named: "MarkdownOutput") | ||
} | ||
Self.loadingTask = task | ||
return try await task.value | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a pattern that any other test is doing. It's also relies very heavily on opaque test content in different files which makes the tests harder to read and debug if something changes.
func testInlineDocumentLinkArticleFormatting() async throws { | ||
let node = try await generateMarkdown(path: "Links") | ||
let expected = "inline link: [Rows and Columns](doc://org.swift.MarkdownOutput/documentation/MarkdownOutput/RowsAndColumns)" | ||
XCTAssert(node.markdown.contains(expected)) | ||
} | ||
|
||
func testTopicListLinkArticleFormatting() async throws { | ||
let node = try await generateMarkdown(path: "Links") | ||
let expected = "[Rows and Columns](doc://org.swift.MarkdownOutput/documentation/MarkdownOutput/RowsAndColumns)\n\nDemonstrates how row and column directives are rendered as markdown" | ||
XCTAssert(node.markdown.contains(expected)) | ||
} | ||
|
||
func testInlineDocumentLinkSymbolFormatting() async throws { | ||
let node = try await generateMarkdown(path: "Links") | ||
let expected = "inline link: [`MarkdownSymbol`](doc://org.swift.MarkdownOutput/documentation/MarkdownOutput/MarkdownSymbol)" | ||
XCTAssert(node.markdown.contains(expected)) | ||
} | ||
|
||
func testTopicListLinkSymbolFormatting() async throws { | ||
let node = try await generateMarkdown(path: "Links") | ||
let expected = "[`MarkdownSymbol`](doc://org.swift.MarkdownOutput/documentation/MarkdownOutput/MarkdownSymbol)\n\nA basic symbol to test markdown output." | ||
XCTAssert(node.markdown.contains(expected)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two things about this that we try to avoid in new tests:
- It relies on opaque, on disk, test fixtures but only verifies a small portion of it (a single file)
There are two reasons why we try to avoid this:- People can't tell what the test is doing from looking at it. In this example, I don't know why that specific markdown content is expected without looking at content of the Links.md article in the test fixtures.
- Because multiple different validations use the same test fixture, the loaded context for each test contains more pages than necessary. This makes it harder for people to put breakpoints and debug the test if something changes in the future because they need to step through a processing of data that's not being verified in this test.
- Multiple tests load the same content to perform a single verification. This isn't a pattern that other tests are doing. In this file, the repeated disk activity has been mitigated by using unstructured concurrency in a way that's another pattern not seen in other tests.
Instead, the best practice for tests like this would be to define a the minimal amount of input for each tests, directly with the test, and perform all related verifications in the same test.
For example, after reading these test, then going to the Article.md, I can conclude that this is verifying the format of a link to a symbol and a link to an article, both in prose and in a topic section.
The way I would write this is something along the lines of:
let catalog = Folder(name: "Something.docc", content: [
TextFile(name: "First.md", utf8Content: """
# First Article
This article links to a symbol and another article in prose and in a topic section
``SomeClassName`` and <doc:Second>
## Topics
- ``SomeClassName``
- <doc:Second>
"""),
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [
makeSymbol(id: "some-symbol-id", kind: .class, pathComponents: ["SomeClassName"])
])),
TextFile(name: "Second.md", utf8Content: """
# Second Article
This empty article is only being linked to.
"""),
]))
let (_, context) = try await loadBundle(catalog: catalog)
XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))")
let markdownOutput = // ...
then, you could either do the 4 contains(...)
checks that relate to this test input or you could do an XCTAssertEqual(...)
over the entire markdown output which should be about as short as the "First.md" article input.
The main benefits with this style of testing:
- All the inputs are right there in the test. You can see that the test is about an article linking to a symbol and an article.
- The test inputs can act as documentation about the test by describing the purpose of inputs in file abstract (instead of lorem ipsum text or other placeholder content)
- Because the test inputs only contains two articles and a symbol, there's a minimal amount of pages being processed which makes it easier to perform debugging actions like, dumping the entire link hierarchy, dumping the topic graph, printing all known page identifiers, etc.
- The test uses its own in-memory file system, so it doesn't need to read from disk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To the previous point about debugging tests. I can't tell from this file how many symbols there are or what the purpose of each symbol are. I can't tell if there's a wide range of symbols that covers any edge cases or if it's mostly structs, classes, methods, and properties.
@@ -0,0 +1 @@ | |||
{"metadata":{"formatVersion":{"major":0,"minor":6,"patch":0},"generator":"Apple Swift version 6.2.1 (swiftlang-6.2.1.2.1 clang-1700.4.2.2)"},"module":{"name":"MarkdownOutput","platform":{"architecture":"arm64","vendor":"apple","operatingSystem":{"name":"macosx","minimumVersion":{"major":10,"minor":13}}}},"symbols":[{"kind":{"identifier":"swift.struct","displayName":"Structure"},"identifier":{"precise":"s:14MarkdownOutput0A6SymbolV","interfaceLanguage":"swift"},"pathComponents":["MarkdownSymbol"],"names":{"title":"MarkdownSymbol","navigator":[{"kind":"identifier","spelling":"MarkdownSymbol"}],"subHeading":[{"kind":"keyword","spelling":"struct"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"MarkdownSymbol"}]},"docComment":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","module":"MarkdownOutput","lines":[{"range":{"start":{"line":2,"character":4},"end":{"line":2,"character":43}},"text":"A basic symbol to test markdown output."},{"range":{"start":{"line":3,"character":3},"end":{"line":3,"character":3}},"text":""},{"range":{"start":{"line":4,"character":4},"end":{"line":4,"character":39}},"text":"This is the overview of the symbol."}]},"declarationFragments":[{"kind":"keyword","spelling":"struct"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"MarkdownSymbol"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":5,"character":14}}},{"kind":{"identifier":"swift.property","displayName":"Instance Property"},"identifier":{"precise":"s:14MarkdownOutput0A6SymbolV4nameSSvp","interfaceLanguage":"swift"},"pathComponents":["MarkdownSymbol","name"],"names":{"title":"name","subHeading":[{"kind":"keyword","spelling":"let"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"name"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"}]},"declarationFragments":[{"kind":"keyword","spelling":"let"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"name"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":6,"character":15}}},{"kind":{"identifier":"swift.property","displayName":"Instance Property"},"identifier":{"precise":"s:14MarkdownOutput0A6SymbolV8fullNameSSvp","interfaceLanguage":"swift"},"pathComponents":["MarkdownSymbol","fullName"],"names":{"title":"fullName","subHeading":[{"kind":"keyword","spelling":"let"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"fullName"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"}]},"declarationFragments":[{"kind":"keyword","spelling":"let"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"fullName"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"}],"accessLevel":"public","availability":[{"domain":"macOS","introduced":{"major":2,"minor":0},"deprecated":{"major":4,"minor":0},"message":"Don't be so formal"},{"domain":"iOS","introduced":{"major":1,"minor":0},"deprecated":{"major":4,"minor":0},"message":"Don't be so formal"}],"location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":10,"character":15}}},{"kind":{"identifier":"swift.property","displayName":"Instance Property"},"identifier":{"precise":"s:14MarkdownOutput0A6SymbolV9otherNameSSSgvp","interfaceLanguage":"swift"},"pathComponents":["MarkdownSymbol","otherName"],"names":{"title":"otherName","subHeading":[{"kind":"keyword","spelling":"var"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"otherName"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"},{"kind":"text","spelling":"?"}]},"declarationFragments":[{"kind":"keyword","spelling":"var"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"otherName"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"},{"kind":"text","spelling":"?"}],"accessLevel":"public","availability":[{"domain":"iOS","obsoleted":{"major":5,"minor":0}}],"location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":13,"character":15}}},{"kind":{"identifier":"swift.init","displayName":"Initializer"},"identifier":{"precise":"s:14MarkdownOutput0A6SymbolV4nameACSS_tcfc","interfaceLanguage":"swift"},"pathComponents":["MarkdownSymbol","init(name:)"],"names":{"title":"init(name:)","subHeading":[{"kind":"keyword","spelling":"init"},{"kind":"text","spelling":"("},{"kind":"externalParam","spelling":"name"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"},{"kind":"text","spelling":")"}]},"functionSignature":{"parameters":[{"name":"name","declarationFragments":[{"kind":"identifier","spelling":"name"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"}]}]},"declarationFragments":[{"kind":"keyword","spelling":"init"},{"kind":"text","spelling":"("},{"kind":"externalParam","spelling":"name"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"},{"kind":"text","spelling":")"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":15,"character":11}}},{"kind":{"identifier":"swift.struct","displayName":"Structure"},"identifier":{"precise":"s:14MarkdownOutput17ExternalConformerV","interfaceLanguage":"swift"},"pathComponents":["ExternalConformer"],"names":{"title":"ExternalConformer","navigator":[{"kind":"identifier","spelling":"ExternalConformer"}],"subHeading":[{"kind":"keyword","spelling":"struct"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"ExternalConformer"}]},"docComment":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","module":"MarkdownOutput","lines":[{"range":{"start":{"line":21,"character":4},"end":{"line":21,"character":53}},"text":"This type conforms to multiple external protocols"}]},"declarationFragments":[{"kind":"keyword","spelling":"struct"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"ExternalConformer"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":22,"character":14}}},{"kind":{"identifier":"swift.func.op","displayName":"Operator"},"identifier":{"precise":"s:SQsE2neoiySbx_xtFZ::SYNTHESIZED::s:14MarkdownOutput17ExternalConformerV","interfaceLanguage":"swift"},"pathComponents":["ExternalConformer","!=(_:_:)"],"names":{"title":"!=(_:_:)","subHeading":[{"kind":"keyword","spelling":"static"},{"kind":"text","spelling":" "},{"kind":"keyword","spelling":"func"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"!="},{"kind":"text","spelling":" "},{"kind":"text","spelling":"("},{"kind":"typeIdentifier","spelling":"Self"},{"kind":"text","spelling":", "},{"kind":"typeIdentifier","spelling":"Self"},{"kind":"text","spelling":") -> "},{"kind":"typeIdentifier","spelling":"Bool","preciseIdentifier":"s:Sb"}]},"docComment":{"module":"Swift","lines":[{"text":"Returns a Boolean value indicating whether two values are not equal."},{"text":""},{"text":"Inequality is the inverse of equality. For any values `a` and `b`, `a != b`"},{"text":"implies that `a == b` is `false`."},{"text":""},{"text":"This is the default implementation of the not-equal-to operator (`!=`)"},{"text":"for any type that conforms to `Equatable`."},{"text":""},{"text":"- Parameters:"},{"text":" - lhs: A value to compare."},{"text":" - rhs: Another value to compare."}]},"functionSignature":{"parameters":[{"name":"lhs","declarationFragments":[{"kind":"identifier","spelling":"lhs"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"Self"}]},{"name":"rhs","declarationFragments":[{"kind":"identifier","spelling":"rhs"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"Self"}]}],"returns":[{"kind":"typeIdentifier","spelling":"Bool","preciseIdentifier":"s:Sb"}]},"swiftExtension":{"extendedModule":"Swift","typeKind":"swift.protocol"},"declarationFragments":[{"kind":"keyword","spelling":"static"},{"kind":"text","spelling":" "},{"kind":"keyword","spelling":"func"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"!="},{"kind":"text","spelling":" "},{"kind":"text","spelling":"("},{"kind":"internalParam","spelling":"lhs"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"Self"},{"kind":"text","spelling":", "},{"kind":"internalParam","spelling":"rhs"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"Self"},{"kind":"text","spelling":") -> "},{"kind":"typeIdentifier","spelling":"Bool","preciseIdentifier":"s:Sb"}],"accessLevel":"public"},{"kind":{"identifier":"swift.property","displayName":"Instance Property"},"identifier":{"precise":"s:14MarkdownOutput17ExternalConformerV2idSSvp","interfaceLanguage":"swift"},"pathComponents":["ExternalConformer","id"],"names":{"title":"id","subHeading":[{"kind":"keyword","spelling":"let"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"id"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"}]},"docComment":{"module":"Swift","lines":[{"text":"The stable identity of the entity associated with this instance."}]},"declarationFragments":[{"kind":"keyword","spelling":"let"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"id"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"String","preciseIdentifier":"s:SS"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":23,"character":15}}},{"kind":{"identifier":"swift.init","displayName":"Initializer"},"identifier":{"precise":"s:14MarkdownOutput17ExternalConformerV4fromACs7Decoder_p_tKcfc","interfaceLanguage":"swift"},"pathComponents":["ExternalConformer","init(from:)"],"names":{"title":"init(from:)","subHeading":[{"kind":"keyword","spelling":"init"},{"kind":"text","spelling":"("},{"kind":"externalParam","spelling":"from"},{"kind":"text","spelling":": any "},{"kind":"typeIdentifier","spelling":"Decoder","preciseIdentifier":"s:s7DecoderP"},{"kind":"text","spelling":") "},{"kind":"keyword","spelling":"throws"}]},"docComment":{"module":"Swift","lines":[{"text":"Creates a new instance by decoding from the given decoder."},{"text":""},{"text":"This initializer throws an error if reading from the decoder fails, or"},{"text":"if the data read is corrupted or otherwise invalid."},{"text":""},{"text":"- Parameter decoder: The decoder to read data from."}]},"functionSignature":{"parameters":[{"name":"from","internalName":"decoder","declarationFragments":[{"kind":"identifier","spelling":"decoder"},{"kind":"text","spelling":": any "},{"kind":"typeIdentifier","spelling":"Decoder","preciseIdentifier":"s:s7DecoderP"}]}]},"declarationFragments":[{"kind":"keyword","spelling":"init"},{"kind":"text","spelling":"("},{"kind":"externalParam","spelling":"from"},{"kind":"text","spelling":" "},{"kind":"internalParam","spelling":"decoder"},{"kind":"text","spelling":": any "},{"kind":"typeIdentifier","spelling":"Decoder","preciseIdentifier":"s:s7DecoderP"},{"kind":"text","spelling":") "},{"kind":"keyword","spelling":"throws"}],"accessLevel":"public"},{"kind":{"identifier":"swift.enum","displayName":"Enumeration"},"identifier":{"precise":"s:14MarkdownOutput14LocalConformerO","interfaceLanguage":"swift"},"pathComponents":["LocalConformer"],"names":{"title":"LocalConformer","navigator":[{"kind":"identifier","spelling":"LocalConformer"}],"subHeading":[{"kind":"keyword","spelling":"enum"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"LocalConformer"}]},"docComment":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","module":"MarkdownOutput","lines":[{"range":{"start":{"line":26,"character":4},"end":{"line":26,"character":55}},"text":"This type demonstrates conformance in documentation"}]},"declarationFragments":[{"kind":"keyword","spelling":"enum"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"LocalConformer"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":27,"character":12}}},{"kind":{"identifier":"swift.func.op","displayName":"Operator"},"identifier":{"precise":"s:SQsE2neoiySbx_xtFZ::SYNTHESIZED::s:14MarkdownOutput14LocalConformerO","interfaceLanguage":"swift"},"pathComponents":["LocalConformer","!=(_:_:)"],"names":{"title":"!=(_:_:)","subHeading":[{"kind":"keyword","spelling":"static"},{"kind":"text","spelling":" "},{"kind":"keyword","spelling":"func"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"!="},{"kind":"text","spelling":" "},{"kind":"text","spelling":"("},{"kind":"typeIdentifier","spelling":"Self"},{"kind":"text","spelling":", "},{"kind":"typeIdentifier","spelling":"Self"},{"kind":"text","spelling":") -> "},{"kind":"typeIdentifier","spelling":"Bool","preciseIdentifier":"s:Sb"}]},"docComment":{"module":"Swift","lines":[{"text":"Returns a Boolean value indicating whether two values are not equal."},{"text":""},{"text":"Inequality is the inverse of equality. For any values `a` and `b`, `a != b`"},{"text":"implies that `a == b` is `false`."},{"text":""},{"text":"This is the default implementation of the not-equal-to operator (`!=`)"},{"text":"for any type that conforms to `Equatable`."},{"text":""},{"text":"- Parameters:"},{"text":" - lhs: A value to compare."},{"text":" - rhs: Another value to compare."}]},"functionSignature":{"parameters":[{"name":"lhs","declarationFragments":[{"kind":"identifier","spelling":"lhs"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"Self"}]},{"name":"rhs","declarationFragments":[{"kind":"identifier","spelling":"rhs"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"Self"}]}],"returns":[{"kind":"typeIdentifier","spelling":"Bool","preciseIdentifier":"s:Sb"}]},"swiftExtension":{"extendedModule":"Swift","typeKind":"swift.protocol"},"declarationFragments":[{"kind":"keyword","spelling":"static"},{"kind":"text","spelling":" "},{"kind":"keyword","spelling":"func"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"!="},{"kind":"text","spelling":" "},{"kind":"text","spelling":"("},{"kind":"internalParam","spelling":"lhs"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"Self"},{"kind":"text","spelling":", "},{"kind":"internalParam","spelling":"rhs"},{"kind":"text","spelling":": "},{"kind":"typeIdentifier","spelling":"Self"},{"kind":"text","spelling":") -> "},{"kind":"typeIdentifier","spelling":"Bool","preciseIdentifier":"s:Sb"}],"accessLevel":"public"},{"kind":{"identifier":"swift.method","displayName":"Instance Method"},"identifier":{"precise":"s:14MarkdownOutput14LocalConformerO11localMethodyyF","interfaceLanguage":"swift"},"pathComponents":["LocalConformer","localMethod()"],"names":{"title":"localMethod()","subHeading":[{"kind":"keyword","spelling":"func"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"localMethod"},{"kind":"text","spelling":"()"}]},"functionSignature":{"returns":[{"kind":"text","spelling":"()"}]},"declarationFragments":[{"kind":"keyword","spelling":"func"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"localMethod"},{"kind":"text","spelling":"()"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":28,"character":16}}},{"kind":{"identifier":"swift.enum.case","displayName":"Case"},"identifier":{"precise":"s:14MarkdownOutput14LocalConformerO3booyA2CmF","interfaceLanguage":"swift"},"pathComponents":["LocalConformer","boo"],"names":{"title":"LocalConformer.boo","subHeading":[{"kind":"keyword","spelling":"case"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"boo"}]},"declarationFragments":[{"kind":"keyword","spelling":"case"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"boo"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":32,"character":9}}},{"kind":{"identifier":"swift.protocol","displayName":"Protocol"},"identifier":{"precise":"s:14MarkdownOutput13LocalProtocolP","interfaceLanguage":"swift"},"pathComponents":["LocalProtocol"],"names":{"title":"LocalProtocol","navigator":[{"kind":"identifier","spelling":"LocalProtocol"}],"subHeading":[{"kind":"keyword","spelling":"protocol"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"LocalProtocol"}]},"docComment":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","module":"MarkdownOutput","lines":[{"range":{"start":{"line":35,"character":4},"end":{"line":35,"character":76}},"text":"This is a locally defined protocol to support the relationship test case"}]},"declarationFragments":[{"kind":"keyword","spelling":"protocol"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"LocalProtocol"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":36,"character":16}}},{"kind":{"identifier":"swift.method","displayName":"Instance Method"},"identifier":{"precise":"s:14MarkdownOutput13LocalProtocolP11localMethodyyF","interfaceLanguage":"swift"},"pathComponents":["LocalProtocol","localMethod()"],"names":{"title":"localMethod()","subHeading":[{"kind":"keyword","spelling":"func"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"localMethod"},{"kind":"text","spelling":"()"}]},"functionSignature":{"returns":[{"kind":"text","spelling":"()"}]},"declarationFragments":[{"kind":"keyword","spelling":"func"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"localMethod"},{"kind":"text","spelling":"()"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":37,"character":9}}},{"kind":{"identifier":"swift.class","displayName":"Class"},"identifier":{"precise":"s:14MarkdownOutput13LocalSubclassC","interfaceLanguage":"swift"},"pathComponents":["LocalSubclass"],"names":{"title":"LocalSubclass","navigator":[{"kind":"identifier","spelling":"LocalSubclass"}],"subHeading":[{"kind":"keyword","spelling":"class"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"LocalSubclass"}]},"docComment":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","module":"MarkdownOutput","lines":[{"range":{"start":{"line":40,"character":4},"end":{"line":40,"character":63}},"text":"This is a class to demonstrate inheritance in documentation"}]},"declarationFragments":[{"kind":"keyword","spelling":"class"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"LocalSubclass"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":41,"character":13}}},{"kind":{"identifier":"swift.class","displayName":"Class"},"identifier":{"precise":"s:14MarkdownOutput15LocalSuperclassC","interfaceLanguage":"swift"},"pathComponents":["LocalSuperclass"],"names":{"title":"LocalSuperclass","navigator":[{"kind":"identifier","spelling":"LocalSuperclass"}],"subHeading":[{"kind":"keyword","spelling":"class"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"LocalSuperclass"}]},"docComment":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","module":"MarkdownOutput","lines":[{"range":{"start":{"line":45,"character":4},"end":{"line":45,"character":70}},"text":"This is a class to demonstrate inheritance in symbol documentation"}]},"declarationFragments":[{"kind":"keyword","spelling":"class"},{"kind":"text","spelling":" "},{"kind":"identifier","spelling":"LocalSuperclass"}],"accessLevel":"public","location":{"uri":"file:///Users/richard/Documents/MarkdownOutput/Sources/MarkdownOutput/MarkdownOutput.swift","position":{"line":46,"character":13}}}],"relationships":[{"kind":"memberOf","source":"s:14MarkdownOutput0A6SymbolV4nameSSvp","target":"s:14MarkdownOutput0A6SymbolV"},{"kind":"memberOf","source":"s:14MarkdownOutput0A6SymbolV8fullNameSSvp","target":"s:14MarkdownOutput0A6SymbolV"},{"kind":"memberOf","source":"s:14MarkdownOutput0A6SymbolV9otherNameSSSgvp","target":"s:14MarkdownOutput0A6SymbolV"},{"kind":"memberOf","source":"s:14MarkdownOutput0A6SymbolV4nameACSS_tcfc","target":"s:14MarkdownOutput0A6SymbolV"},{"kind":"memberOf","source":"s:SQsE2neoiySbx_xtFZ::SYNTHESIZED::s:14MarkdownOutput17ExternalConformerV","target":"s:14MarkdownOutput17ExternalConformerV","sourceOrigin":{"identifier":"s:SQsE2neoiySbx_xtFZ","displayName":"Equatable.!=(_:_:)"}},{"kind":"conformsTo","source":"s:14MarkdownOutput17ExternalConformerV","target":"s:Se","targetFallback":"Swift.Decodable"},{"kind":"conformsTo","source":"s:14MarkdownOutput17ExternalConformerV","target":"s:SE","targetFallback":"Swift.Encodable"},{"kind":"conformsTo","source":"s:14MarkdownOutput17ExternalConformerV","target":"s:s12IdentifiableP","targetFallback":"Swift.Identifiable"},{"kind":"conformsTo","source":"s:14MarkdownOutput17ExternalConformerV","target":"s:SH","targetFallback":"Swift.Hashable"},{"kind":"conformsTo","source":"s:14MarkdownOutput17ExternalConformerV","target":"s:SQ","targetFallback":"Swift.Equatable"},{"kind":"memberOf","source":"s:14MarkdownOutput17ExternalConformerV2idSSvp","target":"s:14MarkdownOutput17ExternalConformerV","sourceOrigin":{"identifier":"s:s12IdentifiableP2id2IDQzvp","displayName":"Identifiable.id"}},{"kind":"memberOf","source":"s:14MarkdownOutput17ExternalConformerV4fromACs7Decoder_p_tKcfc","target":"s:14MarkdownOutput17ExternalConformerV","sourceOrigin":{"identifier":"s:Se4fromxs7Decoder_p_tKcfc","displayName":"Decodable.init(from:)"}},{"kind":"memberOf","source":"s:SQsE2neoiySbx_xtFZ::SYNTHESIZED::s:14MarkdownOutput14LocalConformerO","target":"s:14MarkdownOutput14LocalConformerO","sourceOrigin":{"identifier":"s:SQsE2neoiySbx_xtFZ","displayName":"Equatable.!=(_:_:)"}},{"kind":"conformsTo","source":"s:14MarkdownOutput14LocalConformerO","target":"s:SQ","targetFallback":"Swift.Equatable"},{"kind":"conformsTo","source":"s:14MarkdownOutput14LocalConformerO","target":"s:SH","targetFallback":"Swift.Hashable"},{"kind":"conformsTo","source":"s:14MarkdownOutput14LocalConformerO","target":"s:14MarkdownOutput13LocalProtocolP"},{"kind":"memberOf","source":"s:14MarkdownOutput14LocalConformerO11localMethodyyF","target":"s:14MarkdownOutput14LocalConformerO","sourceOrigin":{"identifier":"s:14MarkdownOutput13LocalProtocolP11localMethodyyF","displayName":"LocalProtocol.localMethod()"}},{"kind":"memberOf","source":"s:14MarkdownOutput14LocalConformerO3booyA2CmF","target":"s:14MarkdownOutput14LocalConformerO"},{"kind":"requirementOf","source":"s:14MarkdownOutput13LocalProtocolP11localMethodyyF","target":"s:14MarkdownOutput13LocalProtocolP"},{"kind":"inheritsFrom","source":"s:14MarkdownOutput13LocalSubclassC","target":"s:14MarkdownOutput15LocalSuperclassC"}]} No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: You have your file:///Users/richard/Documents/...
path components in this file.
If there's some symbol graph data that's really complex and hard to recreate, so that we opt to use an exported file for it, we rename that to file:///Users/username/path/to/...
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.
./bin/test
script and it succeeded