Parser: a line starting with # that is not a comment is prose !10

merged merged by cmc on 2026-09-30 05:08 UTC · krz/org-swift:hash-paragraph into main

2 files changed, +28 −2

Layout: unified · split

Sources/OrgSwift/AST/OrgParser.swift +6 −2
@@ -138,7 +138,10 @@ public enum OrgParser {
138138 while index < lines.count {
139139 let candidate = lines[index]
140140 let candidateTrimmed = candidate.trimmingCharacters(in: .whitespaces)
141 if candidateTrimmed.isEmpty || startsNewConstruct(candidateTrimmed, raw: candidate) { break }
141 // The first line is always taken: nothing above claimed it, and
142 // leaving it would parse the same line forever.
143 if !paragraph.isEmpty,
144 candidateTrimmed.isEmpty || startsNewConstruct(candidateTrimmed, raw: candidate) { break }
142145 paragraph.append(candidateTrimmed)
143146 index += 1
144147 }
@@ -153,7 +156,8 @@ public enum OrgParser {
153156
154157 /// Would this line begin a construct other than the paragraph currently being consumed?
155158 private static func startsNewConstruct(_ trimmed: String, raw: String) -> Bool {
156 if trimmed.hasPrefix("#+") || trimmed.hasPrefix("#") { return true }
159 // A keyword or a comment; `#12 fixed this` is prose.
160 if trimmed.hasPrefix("#+") || trimmed == "#" || trimmed.hasPrefix("# ") { return true }
157161 if trimmed.firstMatch(of: /^\*{1,6}\s+/) != nil { return true }
158162 if isOrgHorizontalRule(trimmed) { return true }
159163 if isTableLine(trimmed) { return true }
Tests/OrgSwiftTests/OrgTreeTests.swift +22
@@ -9,6 +9,28 @@ import Testing
99/// than a second parser. Corpus conformance is gated separately, in `ConformanceTests`.
1010struct ASTParseTests {
1111
12 @Test(.timeLimit(.minutes(1)))
13 func aHashLineThatIsNotACommentIsAParagraph() {
14 let doc = OrgParser.parse("#12 fixed this\nsee #1")
15 guard case .paragraph(let objects) = doc.elements.first else {
16 Issue.record("expected a paragraph, got \(doc.elements)"); return
17 }
18 #expect(doc.elements.count == 1)
19 #expect(!objects.isEmpty)
20 }
21
22 @Test(.timeLimit(.minutes(1)))
23 func aHashLineEndsNoParagraph() {
24 let doc = OrgParser.parse("see\n#12 for context")
25 #expect(doc.elements.count == 1)
26 }
27
28 @Test(.timeLimit(.minutes(1)))
29 func aCommentStillEndsAParagraph() {
30 let doc = OrgParser.parse("one\n# a comment\ntwo")
31 #expect(doc.elements.count == 2)
32 }
33
1234 @Test
1335 func headingCarriesTodoPriorityAndTags() {
1436 let doc = OrgParser.parse("* TODO [#A] Write the parser :work:rust:")