From fea088562397ffc6cd273bd890cc433ff689c9cc Mon Sep 17 00:00:00 2001 From: Ahmed Mahmoud Date: Sat, 13 Sep 2025 00:35:41 +0300 Subject: [PATCH 1/2] Add preceding newlines before Doxygen commands --- .../Walker/Walkers/MarkupFormatter.swift | 20 ++++++++++++++ .../Visitors/MarkupFormatterTests.swift | 27 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift b/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift index 67db4f4f..70dc4fda 100644 --- a/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift +++ b/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift @@ -1174,27 +1174,47 @@ public struct MarkupFormatter: MarkupWalker { } public mutating func visitDoxygenDiscussion(_ doxygenDiscussion: DoxygenDiscussion) { + if doxygenDiscussion.indexInParent > 0 { + ensurePrecedingNewlineCount(atLeast: 2) + } + printDoxygenStart("discussion", for: doxygenDiscussion) descendInto(doxygenDiscussion) } public mutating func visitDoxygenAbstract(_ doxygenAbstract: DoxygenAbstract) { + if doxygenAbstract.indexInParent > 0 { + ensurePrecedingNewlineCount(atLeast: 2) + } + printDoxygenStart("abstract", for: doxygenAbstract) descendInto(doxygenAbstract) } public mutating func visitDoxygenNote(_ doxygenNote: DoxygenNote) { + if doxygenNote.indexInParent > 0 { + ensurePrecedingNewlineCount(atLeast: 2) + } + printDoxygenStart("note", for: doxygenNote) descendInto(doxygenNote) } public mutating func visitDoxygenParameter(_ doxygenParam: DoxygenParameter) { + if doxygenParam.indexInParent > 0 { + ensurePrecedingNewlineCount(atLeast: 2) + } + printDoxygenStart("param", for: doxygenParam) print("\(doxygenParam.name) ", for: doxygenParam) descendInto(doxygenParam) } public mutating func visitDoxygenReturns(_ doxygenReturns: DoxygenReturns) { + if doxygenReturns.indexInParent > 0 { + ensurePrecedingNewlineCount(atLeast: 2) + } + // FIXME: store the actual command name used in the original markup printDoxygenStart("returns", for: doxygenReturns) descendInto(doxygenReturns) diff --git a/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift b/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift index dc26ee35..257373e8 100644 --- a/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift +++ b/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift @@ -1626,4 +1626,31 @@ class MarkupFormatterMixedContentTests: XCTestCase { ] zip(expected, printed).forEach { XCTAssertEqual($0, $1) } } + + func testDoxygenCommandsWithPrecedingNewlines() { + let expected = #""" + Does something. + + \abstract abstract + + \param x first param + + \returns result + + \note note + + \discussion discussion + """# + + let printed = Document( + Paragraph(Text("Does something.")), + DoxygenAbstract(children: Paragraph(Text("abstract"))), + DoxygenParameter(name: "x", children: Paragraph(Text("first param"))), + DoxygenReturns(children: Paragraph(Text("result"))), + DoxygenNote(children: Paragraph(Text("note"))), + DoxygenDiscussion(children: Paragraph(Text("discussion"))) + ).format() + + XCTAssertEqual(expected, printed) + } } From 0ab83e3da0d1efa499d6efa686221a51316d85ba Mon Sep 17 00:00:00 2001 From: Ahmed Mahmoud Date: Fri, 3 Oct 2025 02:43:52 +0300 Subject: [PATCH 2/2] Control Doxygen commands preceding newline by formatter options --- .../Walker/Walkers/MarkupFormatter.swift | 65 +++++++++++++------ .../Visitors/MarkupFormatterTests.swift | 31 ++++++++- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift b/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift index 70dc4fda..d0e9da18 100644 --- a/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift +++ b/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift @@ -33,6 +33,21 @@ fileprivate extension Markup { } return nil } + + /// Previous sibling of this element in its parent, or `nil` if it's the first child. + var previousSibling: Markup? { + guard let parent, indexInParent > 0 else { + return nil + } + + return parent.child(at: indexInParent - 1) + } + + /// Whether this element is a Doxygen command. + var isDoxygenCommand: Bool { + return self is DoxygenDiscussion || self is DoxygenNote || self is DoxygenAbstract + || self is DoxygenParameter || self is DoxygenReturns + } } fileprivate extension String { @@ -239,6 +254,15 @@ public struct MarkupFormatter: MarkupWalker { case at = "@" } + /// The spacing to use when formatting adjacent Doxygen commands. + public enum AdjacentDoxygenCommandsSpacing: String, CaseIterable { + /// Separate adjacent Doxygen commands with a single newline. + case singleNewline = "single-newline" + + /// Keep a blank line between adjacent Doxygen commands creating separate paragraphs. + case separateParagraphs = "separate-paragraphs" + } + // MARK: Option Properties var orderedListNumerals: OrderedListNumerals @@ -253,6 +277,7 @@ public struct MarkupFormatter: MarkupWalker { var preferredLineLimit: PreferredLineLimit? var customLinePrefix: String var doxygenCommandPrefix: DoxygenCommandPrefix + var adjacentDoxygenCommandsSpacing: AdjacentDoxygenCommandsSpacing /** Create a set of formatting options to use when printing an element. @@ -270,6 +295,7 @@ public struct MarkupFormatter: MarkupWalker { - preferredLineLimit: The preferred maximum line length and method for splitting ``Text`` elements in an attempt to maintain that line length. - customLinePrefix: An addition prefix to print at the start of each line, useful for adding documentation comment markers. - doxygenCommandPrefix: The command command prefix, which defaults to ``DoxygenCommandPrefix/backslash``. + - adjacentDoxygenCommandsSpacing: The spacing to use when formatting adjacent Doxygen commands. */ public init(unorderedListMarker: UnorderedListMarker = .dash, orderedListNumerals: OrderedListNumerals = .allSame(1), @@ -282,7 +308,8 @@ public struct MarkupFormatter: MarkupWalker { preferredHeadingStyle: PreferredHeadingStyle = .atx, preferredLineLimit: PreferredLineLimit? = nil, customLinePrefix: String = "", - doxygenCommandPrefix: DoxygenCommandPrefix = .backslash) { + doxygenCommandPrefix: DoxygenCommandPrefix = .backslash, + adjacentDoxygenCommandsSpacing: AdjacentDoxygenCommandsSpacing = .singleNewline) { self.unorderedListMarker = unorderedListMarker self.orderedListNumerals = orderedListNumerals self.useCodeFence = useCodeFence @@ -297,6 +324,7 @@ public struct MarkupFormatter: MarkupWalker { self.thematicBreakLength = max(3, thematicBreakLength) self.customLinePrefix = customLinePrefix self.doxygenCommandPrefix = doxygenCommandPrefix + self.adjacentDoxygenCommandsSpacing = adjacentDoxygenCommandsSpacing } /// The default set of formatting options. @@ -1173,48 +1201,47 @@ public struct MarkupFormatter: MarkupWalker { print(formattingOptions.doxygenCommandPrefix.rawValue + name + " ", for: element) } - public mutating func visitDoxygenDiscussion(_ doxygenDiscussion: DoxygenDiscussion) { - if doxygenDiscussion.indexInParent > 0 { + private mutating func ensureDoxygenCommandPrecedingNewline(for element: Markup) { + guard let previousSibling = element.previousSibling else { + return + } + + guard formattingOptions.adjacentDoxygenCommandsSpacing == .singleNewline else { ensurePrecedingNewlineCount(atLeast: 2) + return } + let newlineCount = previousSibling.isDoxygenCommand ? 1 : 2 + ensurePrecedingNewlineCount(atLeast: newlineCount) + } + + public mutating func visitDoxygenDiscussion(_ doxygenDiscussion: DoxygenDiscussion) { + ensureDoxygenCommandPrecedingNewline(for: doxygenDiscussion) printDoxygenStart("discussion", for: doxygenDiscussion) descendInto(doxygenDiscussion) } public mutating func visitDoxygenAbstract(_ doxygenAbstract: DoxygenAbstract) { - if doxygenAbstract.indexInParent > 0 { - ensurePrecedingNewlineCount(atLeast: 2) - } - + ensureDoxygenCommandPrecedingNewline(for: doxygenAbstract) printDoxygenStart("abstract", for: doxygenAbstract) descendInto(doxygenAbstract) } public mutating func visitDoxygenNote(_ doxygenNote: DoxygenNote) { - if doxygenNote.indexInParent > 0 { - ensurePrecedingNewlineCount(atLeast: 2) - } - + ensureDoxygenCommandPrecedingNewline(for: doxygenNote) printDoxygenStart("note", for: doxygenNote) descendInto(doxygenNote) } public mutating func visitDoxygenParameter(_ doxygenParam: DoxygenParameter) { - if doxygenParam.indexInParent > 0 { - ensurePrecedingNewlineCount(atLeast: 2) - } - + ensureDoxygenCommandPrecedingNewline(for: doxygenParam) printDoxygenStart("param", for: doxygenParam) print("\(doxygenParam.name) ", for: doxygenParam) descendInto(doxygenParam) } public mutating func visitDoxygenReturns(_ doxygenReturns: DoxygenReturns) { - if doxygenReturns.indexInParent > 0 { - ensurePrecedingNewlineCount(atLeast: 2) - } - + ensureDoxygenCommandPrecedingNewline(for: doxygenReturns) // FIXME: store the actual command name used in the original markup printDoxygenStart("returns", for: doxygenReturns) descendInto(doxygenReturns) diff --git a/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift b/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift index 257373e8..210de561 100644 --- a/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift +++ b/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift @@ -1627,7 +1627,32 @@ class MarkupFormatterMixedContentTests: XCTestCase { zip(expected, printed).forEach { XCTAssertEqual($0, $1) } } - func testDoxygenCommandsWithPrecedingNewlines() { + func testDoxygenCommandsPrecedingNewlinesWithSingleNewline() { + let expected = #""" + Does something. + + \abstract abstract + \param x first param + \returns result + \note note + \discussion discussion + """# + + let formattingOptions = MarkupFormatter.Options( + adjacentDoxygenCommandsSpacing: .singleNewline) + let printed = Document( + Paragraph(Text("Does something.")), + DoxygenAbstract(children: Paragraph(Text("abstract"))), + DoxygenParameter(name: "x", children: Paragraph(Text("first param"))), + DoxygenReturns(children: Paragraph(Text("result"))), + DoxygenNote(children: Paragraph(Text("note"))), + DoxygenDiscussion(children: Paragraph(Text("discussion"))) + ).format(options: formattingOptions) + + XCTAssertEqual(expected, printed) + } + + func testDoxygenCommandsPrecedingNewlinesAsSeparateParagraphs() { let expected = #""" Does something. @@ -1642,6 +1667,8 @@ class MarkupFormatterMixedContentTests: XCTestCase { \discussion discussion """# + let formattingOptions = MarkupFormatter.Options( + adjacentDoxygenCommandsSpacing: .separateParagraphs) let printed = Document( Paragraph(Text("Does something.")), DoxygenAbstract(children: Paragraph(Text("abstract"))), @@ -1649,7 +1676,7 @@ class MarkupFormatterMixedContentTests: XCTestCase { DoxygenReturns(children: Paragraph(Text("result"))), DoxygenNote(children: Paragraph(Text("note"))), DoxygenDiscussion(children: Paragraph(Text("discussion"))) - ).format() + ).format(options: formattingOptions) XCTAssertEqual(expected, printed) }