Skip to content

Commit

Permalink
Add support for Doxygen discussion/note tags (#159)
Browse files Browse the repository at this point in the history
* Fix DoxygenReturns doc comments

* Add support for parsing Doxygen \discussion and \note block-level commands

* Add some tests

* Update CMakeLists.txt
  • Loading branch information
j-f1 authored Feb 1, 2024
1 parent 0b59ad6 commit cfe8c84
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 20 deletions.
4 changes: 4 additions & 0 deletions Sources/Markdown/Base/Markup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ func makeMarkup(_ data: _MarkupData) -> Markup {
return SymbolLink(data)
case .inlineAttributes:
return InlineAttributes(data)
case .doxygenDiscussion:
return DoxygenDiscussion(data)
case .doxygenNote:
return DoxygenNote(data)
case .doxygenParam:
return DoxygenParameter(data)
case .doxygenReturns:
Expand Down
10 changes: 10 additions & 0 deletions Sources/Markdown/Base/RawMarkup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ enum RawMarkupData: Equatable {
case tableRow
case tableCell(colspan: UInt, rowspan: UInt)

case doxygenDiscussion
case doxygenNote
case doxygenParam(name: String)
case doxygenReturns
}
Expand Down Expand Up @@ -334,6 +336,14 @@ final class RawMarkup: ManagedBuffer<RawMarkupHeader, RawMarkup> {
return .create(data: .tableCell(colspan: colspan, rowspan: rowspan), parsedRange: parsedRange, children: children)
}

static func doxygenDiscussion(parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup {
return .create(data: .doxygenDiscussion, parsedRange: parsedRange, children: children)
}

static func doxygenNote(parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup {
return .create(data: .doxygenNote, parsedRange: parsedRange, children: children)
}

static func doxygenParam(name: String, parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup {
return .create(data: .doxygenParam(name: name), parsedRange: parsedRange, children: children)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
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

/// A parsed Doxygen `\discussion` command.
///
/// The Doxygen support in Swift-Markdown parses `\discussion` commands of the form
/// `\discussion description`, where `description` continues until the next blank
/// line or parsed command.
///
/// ```markdown
/// \discussion This object can give other objects in your program magical powers.
/// ```
public struct DoxygenDiscussion: BlockContainer {
public var _data: _MarkupData

init(_ raw: RawMarkup) throws {
guard case .doxygenDiscussion = raw.data else {
throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenDiscussion.self)
}
let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0))
self.init(_MarkupData(absoluteRaw))
}

init(_ data: _MarkupData) {
self._data = data
}

public func accept<V: MarkupVisitor>(_ visitor: inout V) -> V.Result {
return visitor.visitDoxygenDiscussion(self)
}
}

public extension DoxygenDiscussion {
/// Create a new Doxygen discussion definition.
///
/// - Parameter children: Block child elements.
init<Children: Sequence>(children: Children) where Children.Element == BlockMarkup {
try! self.init(.doxygenDiscussion(parsedRange: nil, children.map({ $0.raw.markup })))
}

/// Create a new Doxygen discussion definition.
///
/// - Parameter children: Block child elements.
init(children: BlockMarkup...) {
self.init(children: children)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
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

/// A parsed Doxygen `\note` command.
///
/// The Doxygen support in Swift-Markdown parses `\note` commands of the form
/// `\note description`, where `description` continues until the next blank
/// line or parsed command.
///
/// ```markdown
/// \note This method is only meant to be called an odd number of times.
/// ```
public struct DoxygenNote: BlockContainer {
public var _data: _MarkupData

init(_ raw: RawMarkup) throws {
guard case .doxygenNote = raw.data else {
throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenNote.self)
}
let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0))
self.init(_MarkupData(absoluteRaw))
}

init(_ data: _MarkupData) {
self._data = data
}

public func accept<V: MarkupVisitor>(_ visitor: inout V) -> V.Result {
return visitor.visitDoxygenNote(self)
}
}

public extension DoxygenNote {
/// Create a new Doxygen note definition.
///
/// - Parameter children: Block child elements.
init<Children: Sequence>(children: Children) where Children.Element == BlockMarkup {
try! self.init(.doxygenNote(parsedRange: nil, children.map({ $0.raw.markup })))
}

/// Create a new Doxygen note definition.
///
/// - Parameter children: Block child elements.
init(children: BlockMarkup...) {
self.init(children: children)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,15 @@ public struct DoxygenReturns: BlockContainer {
}

public extension DoxygenReturns {
/// Create a new Doxygen parameter definition.
/// Create a new Doxygen returns definition.
///
/// - Parameter name: The name of the parameter being described.
/// - Parameter children: Block child elements.
init<Children: Sequence>(children: Children) where Children.Element == BlockMarkup {
try! self.init(.doxygenReturns(parsedRange: nil, children.map({ $0.raw.markup })))
}

/// Create a new Doxygen parameter definition.
/// Create a new Doxygen returns definition.
///
/// - Parameter name: The name of the parameter being described.
/// - Parameter children: Block child elements.
init(children: BlockMarkup...) {
self.init(children: children)
Expand Down
2 changes: 2 additions & 0 deletions Sources/Markdown/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ add_library(Markdown
Base/MarkupData.swift
Base/PlainTextConvertibleMarkup.swift
Base/RawMarkup.swift
"Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenDiscussion.swift"
"Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenNote.swift"
"Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenParameter.swift"
"Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenReturns.swift"
"Block Nodes/Block Container Blocks/BlockDirective.swift"
Expand Down
42 changes: 26 additions & 16 deletions Sources/Markdown/Parser/BlockDirectiveParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,17 @@ struct PendingBlockDirective {

struct PendingDoxygenCommand {
enum CommandKind {
case discussion
case note
case param(name: Substring)
case returns

var debugDescription: String {
switch self {
case .discussion:
return "'discussion'"
case .note:
return "'note'"
case .param(name: let name):
return "'param' Argument: '\(name)'"
case .returns:
Expand Down Expand Up @@ -745,6 +751,10 @@ private enum ParseContainer: CustomStringConvertible {
let children = ParseContainer.lineRun(lines, isInCodeFence: false)
.convertToRawMarkup(ranges: &ranges, parent: self, options: options)
switch pendingDoxygenCommand.kind {
case .discussion:
return [.doxygenDiscussion(parsedRange: range, children)]
case .note:
return [.doxygenNote(parsedRange: range, children)]
case .param(let name):
return [.doxygenParam(name: String(name), parsedRange: range, children)]
case .returns:
Expand Down Expand Up @@ -873,7 +883,12 @@ struct ParseContainerStack {
}) else { return nil }
remainder.lexWhitespace()

let kind: PendingDoxygenCommand.CommandKind
switch name.text.lowercased() {
case "discussion":
kind = .discussion
case "note":
kind = .note
case "param":
guard let paramName = remainder.lex(until: { ch in
if ch.isWhitespace {
Expand All @@ -883,26 +898,21 @@ struct ParseContainerStack {
}
}) else { return nil }
remainder.lexWhitespace()
var pendingCommand = PendingDoxygenCommand(
atLocation: at.range!.lowerBound,
atSignIndentation: indent?.text.count ?? 0,
nameLocation: name.range!.lowerBound,
kind: .param(name: paramName.text),
endLocation: name.range!.upperBound)
pendingCommand.addLine(remainder)
return (pendingCommand, remainder)
kind = .param(name: paramName.text)
case "return", "returns", "result":
var pendingCommand = PendingDoxygenCommand(
atLocation: at.range!.lowerBound,
atSignIndentation: indent?.text.count ?? 0,
nameLocation: name.range!.lowerBound,
kind: .returns,
endLocation: name.range!.upperBound)
pendingCommand.addLine(remainder)
return (pendingCommand, remainder)
kind = .returns
default:
return nil
}

var pendingCommand = PendingDoxygenCommand(
atLocation: at.range!.lowerBound,
atSignIndentation: indent?.text.count ?? 0,
nameLocation: name.range!.lowerBound,
kind: kind,
endLocation: name.range!.upperBound)
pendingCommand.addLine(remainder)
return (pendingCommand, remainder)
}

/// Accept a trimmed line, opening new block directives as indicated by the source,
Expand Down
22 changes: 22 additions & 0 deletions Sources/Markdown/Visitor/MarkupVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,22 @@ public protocol MarkupVisitor {
*/
mutating func visitInlineAttributes(_ attributes: InlineAttributes) -> Result

/**
Visit a `DoxygenDiscussion` element and return the result.
- parameter doxygenDiscussion: A `DoxygenDiscussion` element.
- returns: The result of the visit.
*/
mutating func visitDoxygenDiscussion(_ doxygenDiscussion: DoxygenDiscussion) -> Result

/**
Visit a `DoxygenNote` element and return the result.
- parameter doxygenNote: A `DoxygenNote` element.
- returns: The result of the visit.
*/
mutating func visitDoxygenNote(_ doxygenNote: DoxygenNote) -> Result

/**
Visit a `DoxygenParam` element and return the result.
Expand Down Expand Up @@ -389,6 +405,12 @@ extension MarkupVisitor {
public mutating func visitInlineAttributes(_ attributes: InlineAttributes) -> Result {
return defaultVisit(attributes)
}
public mutating func visitDoxygenDiscussion(_ doxygenDiscussion: DoxygenDiscussion) -> Result {
return defaultVisit(doxygenDiscussion)
}
public mutating func visitDoxygenNote(_ doxygenNote: DoxygenNote) -> Result {
return defaultVisit(doxygenNote)
}
public mutating func visitDoxygenParameter(_ doxygenParam: DoxygenParameter) -> Result {
return defaultVisit(doxygenParam)
}
Expand Down
36 changes: 36 additions & 0 deletions Tests/MarkdownTests/Parsing/DoxygenCommandParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,42 @@ import XCTest
class DoxygenCommandParserTests: XCTestCase {
let parseOptions: ParseOptions = [.parseMinimalDoxygen, .parseBlockDirectives]

func testParseDiscussion() {
func assertValidParse(source: String) {
let document = Document(parsing: source, options: parseOptions)
XCTAssert(document.child(at: 0) is DoxygenDiscussion)

let expectedDump = """
Document
└─ DoxygenDiscussion
└─ Paragraph
└─ Text "The thing."
"""
XCTAssertEqual(document.debugDescription(), expectedDump)
}

assertValidParse(source: "@discussion The thing.")
assertValidParse(source: #"\discussion The thing."#)
}

func testParseNote() {
func assertValidParse(source: String) {
let document = Document(parsing: source, options: parseOptions)
XCTAssert(document.child(at: 0) is DoxygenNote)

let expectedDump = """
Document
└─ DoxygenNote
└─ Paragraph
└─ Text "The thing."
"""
XCTAssertEqual(document.debugDescription(), expectedDump)
}

assertValidParse(source: "@note The thing.")
assertValidParse(source: #"\note The thing."#)
}

func testParseParam() throws {
let source = """
@param thing The thing.
Expand Down

0 comments on commit cfe8c84

Please sign in to comment.