Skip to content

Add support for transforming Doxygen discussion/note tags #798

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

Merged
merged 10 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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(".")
]))
])),
])
}
}