diff --git a/CHANGELOG.md b/CHANGELOG.md index 316bc3c..8739de8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- A root node that begins on the document-marker line (`--- >`, `--- |`, `--- foo`, + `--- {a: 1}`) is no longer discarded. The marker scanner skipped the rest of the + `---` line unconditionally, so a block-scalar header was dropped and its body was + mis-read as a plain scalar (folded to spaces, no trailing newline) and a same-line + plain or flow node was lost entirely. The document start now keeps any real + same-line content for the parser, skipping only trailing spaces or a comment. + Lifts official test-suite value-level conformance from 130/170 (76.5%) to + 135/169 (79.9%), with no regression; parse-level is unchanged because these + documents already parsed, just to the wrong tree, which only the value-level + metric could see. - A multi-line plain scalar whose value begins on its own line under a mapping key now folds its continuation lines instead of stopping after the first. The continuation check compared the next line's indent against the value's own pushed diff --git a/Sources/Parsing/Scanner.Directives.swift b/Sources/Parsing/Scanner.Directives.swift index c4516da..c0ab35a 100644 --- a/Sources/Parsing/Scanner.Directives.swift +++ b/Sources/Parsing/Scanner.Directives.swift @@ -38,6 +38,19 @@ extension PureYAML.Parsing.Scanner { state.reader.advance() state.reader.advance() state.append(kind, mark: start, endMark: state.reader.mark) + // The document's root node may begin on the marker's own line (e.g. `--- >`, `--- foo`, + // `--- {a: 1}`, `--- !!str x`). After a document start, skip only the spaces and leave any + // real content for the main loop to scan; an empty rest-of-line or a comment is consumed. + // A document end (`...`) takes no same-line node, so its line is always skipped. + if case .documentStart = kind { + while state.reader.peek() == " " { + state.reader.advance() + } + if let next = state.reader.peek(), !isLineBreak(next), next != "#" { + state.isAtLineStart = false + return + } + } skipLine(&state) } diff --git a/Tests/DocumentMarkerNodeTests.swift b/Tests/DocumentMarkerNodeTests.swift new file mode 100644 index 0000000..a296ee4 --- /dev/null +++ b/Tests/DocumentMarkerNodeTests.swift @@ -0,0 +1,20 @@ +@testable import PureYAML +import Testing + +@Suite("Document marker root nodes") +struct DocumentMarkerNodeTests { + @Test("A block scalar header on the document-marker line is recognized, not discarded") + func test_blockScalarHeaderOnDocumentMarkerLine() throws { + // `--- >` / `--- |`: the marker scanner used to skip the rest of the line, dropping the + // block header so the body was mis-read as a plain scalar (folded, no trailing newline). + #expect(try PureYAML.parse("--- >\n a\n b\n") == .string("a b\n")) + #expect(try PureYAML.parse("--- |\n a\n b\n") == .string("a\nb\n")) + } + + @Test("A scalar root node may begin on the document-marker line") + func test_scalarRootNodeOnDocumentMarkerLine() throws { + #expect(try PureYAML.parse("--- folded text\n") == .string("folded text")) + // A node still on its own line below the marker keeps working. + #expect(try PureYAML.parse("---\nplain text\n") == .string("plain text")) + } +}