Skip to content

Commit

Permalink
Add support for transforming Doxygen discussion/note tags (#798)
Browse files Browse the repository at this point in the history
* Fix typo in doc comment

* Add support for DoxygenDiscussion and DoxygenNote nodes

* Update swift-markdown dependency

* Add a basic test from @d-ronnqvist

Co-Authored-By: David Rönnqvist <[email protected]>

* Fix formatted output

* Add links to the test

* Update swift-markdown

---------

Co-authored-by: David Rönnqvist <[email protected]>
  • Loading branch information
j-f1 and d-ronnqvist authored Feb 15, 2024
1 parent f0a3d7a commit bad0aaa
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"location" : "https://github.com/apple/swift-markdown.git",
"state" : {
"branch" : "main",
"revision" : "e5ab90941a8415304b4a4e403253bd59fef00b5a"
"revision" : "75bd31951f69b9df43d433b75152d8add1692378"
}
},
{
Expand Down
23 changes: 23 additions & 0 deletions Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,29 @@ struct RenderContentCompiler: MarkupVisitor {
return renderableDirective.render(blockDirective, with: &self)
}

mutating func visitDoxygenDiscussion(_ doxygenDiscussion: DoxygenDiscussion) -> [RenderContent] {
doxygenDiscussion.children.flatMap { self.visit($0) }
}

mutating func visitDoxygenNote(_ doxygenNote: DoxygenNote) -> [RenderContent] {
let content: [RenderBlockContent] = doxygenNote.children
.flatMap { self.visit($0) }
.map {
switch $0 {
case let inlineContent as RenderInlineContent:
return .paragraph(.init(inlineContent: [inlineContent]))
case let blockContent as RenderBlockContent:
return blockContent
default:
fatalError("Unexpected content type in note: \(type(of: $0))")
}
}
return [RenderBlockContent.aside(.init(
style: .init(asideKind: .note),
content: content
))]
}

func defaultVisit(_ markup: Markup) -> [RenderContent] {
return []
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fileprivate func removedLinkDestinationProblem(reference: ResolvedTopicReference
}

/**
Rewrites a ``Markup`` tree to resolve ``UnresolvedTopicReference`s using a ``DocumentationContext``.
Rewrites a ``Markup`` tree to resolve ``UnresolvedTopicReference``s using a ``DocumentationContext``.
*/
struct MarkupReferenceResolver: MarkupRewriter {
var context: DocumentationContext
Expand Down
150 changes: 150 additions & 0 deletions Tests/SwiftDocCTests/Semantics/DoxygenTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
This source file is part of the Swift.org open source project
Copyright (c) 2024 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import Foundation

import XCTest
@testable import SwiftDocC
import SwiftDocCTestUtilities
@testable import SymbolKit

class DoxygenTests: XCTestCase {
func testDoxygenDiscussionAndNote() throws {
let documentationLines: [SymbolGraph.LineList.Line] = """
This is an abstract.
@discussion This is a discussion linking to ``AnotherClass`` and ``AnotherClass/prop``.
@note This is a note linking to ``Class3`` and ``Class3/prop2``.
"""
.splitByNewlines
.enumerated()
.map { index, line in
SymbolGraph.LineList.Line(
text: line,
range: .init(start: .init(line: 1 + index, character: 1), end: .init(line: 1 + index, character: 1 + line.utf8.count))
)
}

let tempURL = try createTempFolder(content: [
Folder(name: "unit-test.docc", content: [
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(
moduleName: "ModuleName",
symbols: [
SymbolGraph.Symbol(
identifier: .init(precise: "some-class-id", interfaceLanguage: SourceLanguage.swift.id),
names: .init(title: "SomeClass", navigator: nil, subHeading: nil, prose: nil),
pathComponents: ["SomeClass"],
docComment: .init(documentationLines),
accessLevel: .public,
kind: .init(parsedIdentifier: .class, displayName: "Kind Display Name"),
mixins: [:]
),
SymbolGraph.Symbol(
identifier: .init(precise: "another-class-id", interfaceLanguage: SourceLanguage.swift.id),
names: .init(title: "AnotherClass", navigator: nil, subHeading: nil, prose: nil),
pathComponents: ["AnotherClass"],
docComment: nil,
accessLevel: .public,
kind: .init(parsedIdentifier: .class, displayName: "Kind Display Name"),
mixins: [:]
),
SymbolGraph.Symbol(
identifier: .init(precise: "another-class-prop-id", interfaceLanguage: SourceLanguage.swift.id),
names: .init(title: "prop", navigator: nil, subHeading: nil, prose: nil),
pathComponents: ["AnotherClass", "prop"],
docComment: nil,
accessLevel: .public,
kind: .init(parsedIdentifier: .property, displayName: "Kind Display Name"),
mixins: [:]
),
SymbolGraph.Symbol(
identifier: .init(precise: "class3-id", interfaceLanguage: SourceLanguage.swift.id),
names: .init(title: "Class3", navigator: nil, subHeading: nil, prose: nil),
pathComponents: ["Class3"],
docComment: nil,
accessLevel: .public,
kind: .init(parsedIdentifier: .class, displayName: "Kind Display Name"),
mixins: [:]
),
SymbolGraph.Symbol(
identifier: .init(precise: "class3-prop-id", interfaceLanguage: SourceLanguage.swift.id),
names: .init(title: "prop", navigator: nil, subHeading: nil, prose: nil),
pathComponents: ["Class3", "prop"],
docComment: nil,
accessLevel: .public,
kind: .init(parsedIdentifier: .property, displayName: "Kind Display Name"),
mixins: [:]
),
]
)),
])
])

let (_, bundle, context) = try loadBundle(from: tempURL)
let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName/SomeClass", sourceLanguage: .swift)

// Verify the expected content in the in-memory model
let node = try context.entity(with: reference)
let symbol = try XCTUnwrap(node.semantic as? Symbol)

XCTAssertEqual(symbol.abstract?.format(), "This is an abstract.")
XCTAssertEqual(symbol.discussion?.content.map { $0.format() }, [
#"\discussion This is a discussion linking to ``doc://unit-test/documentation/ModuleName/AnotherClass`` and ``doc://unit-test/documentation/ModuleName/AnotherClass/prop``."#,
#"\note This is a note linking to ``doc://unit-test/documentation/ModuleName/Class3`` and ``Class3/prop2``."#
])

// Verify the expected content in the render model
var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference, source: nil)
let renderNode = try XCTUnwrap(translator.visit(node.semantic) as? RenderNode)

XCTAssertEqual(renderNode.abstract, [.text("This is an abstract.")])
XCTAssertEqual(renderNode.primaryContentSections.count, 1)

let overviewSection = try XCTUnwrap(renderNode.primaryContentSections.first as? ContentRenderSection)
XCTAssertEqual(overviewSection.content.count, 3)
XCTAssertEqual(overviewSection.content, [
.heading(.init(level: 2, text: "Overview", anchor: "overview")),

.paragraph(.init(inlineContent: [
.text("This is a discussion linking to "),
.reference(
identifier: .init("doc://unit-test/documentation/ModuleName/AnotherClass"),
isActive: true,
overridingTitle: nil,
overridingTitleInlineContent: nil
),
.text(" and "),
.reference(
identifier: .init("doc://unit-test/documentation/ModuleName/AnotherClass/prop"),
isActive: true,
overridingTitle: nil,
overridingTitleInlineContent: nil
),
.text(".")
])),

.aside(.init(style: .init(asideKind: .note), content: [
.paragraph(.init(inlineContent: [
.text("This is a note linking to "),
.reference(
identifier: .init("doc://unit-test/documentation/ModuleName/Class3"),
isActive: true,
overridingTitle: nil,
overridingTitleInlineContent: nil
),
.text(" and "),
.codeVoice(code: "Class3/prop2"),
.text(".")
]))
])),
])
}
}

0 comments on commit bad0aaa

Please sign in to comment.