PureYAML/Tests/RootBlockScalarIndentationTests.swift
Mihaela M a5b8986d5e fix(parsing): fold root block scalars with column-0 content
A block scalar that is the document's own root node (`>`, `|`, or after
`---` on the same line) may begin its content at column 0: the document node's
indentation is -1, so column-0 content is still more indented than its parent.
The scanner read the base level as 0 and required content strictly deeper than
it, so the lines fell through indentation handling and were mis-scanned as a
stray plain scalar after the document, raising "expected document boundary".

A root block scalar and an under-indented nested one (`- |` then unindented
content) present identical indentation state at the first content line, so the
two cannot be told apart there. Capture the distinction at header-scan time
instead: a root block scalar has the base indentation and no mapping value,
explicit key, block-entry implicit indent, or flow context introducing it on
its line. Only then is its parent the document; a sequence item or mapping
value keeps its parent's indent as the floor, so its under-indented content is
still rejected, unchanged.

Raises yaml-test-suite parse-level to 291/333 (87.4%) and value-level to
173/191 (90.6%). A regression suite covers the root cases (including a leading
'#' as literal content) and the nested under-indented cases that must error.
410 tests pass.
2026-06-28 17:42:38 +02:00

47 lines
2.3 KiB
Swift

@testable import PureYAML
import Testing
/// A block scalar that is the document's own root node has the document as its parent, whose
/// indentation is -1, so its content may begin at column 0. A block scalar nested in a collection
/// keeps its parent's indentation as the floor, so column-0 content there is under-indented and stays
/// an error. These two sit on the same scanner state at the first content line, distinguished only by
/// whether a mapping value, explicit key, block entry, or flow context introduced the scalar.
@Suite("Root block scalar indentation")
struct RootBlockScalarIndentationTests {
@Test("A root block scalar folds content that begins at column 0")
func test_rootFoldedAtColumnZero() throws {
#expect(try PureYAML.parse(">\nline1\nline2\n") == .string("line1 line2\n"))
#expect(try PureYAML.parse("--- >\nline1\nline2\n") == .string("line1 line2\n"))
}
@Test("A root literal block keeps line breaks for content at column 0")
func test_rootLiteralAtColumnZero() throws {
#expect(try PureYAML.parse("|\nline1\nline2\n") == .string("line1\nline2\n"))
#expect(try PureYAML.parse("--- |\nline1\nline2\n") == .string("line1\nline2\n"))
}
@Test("In a block scalar at column 0, a leading '#' is literal content, not a comment")
func test_rootBlockScalarHashIsLiteral() throws {
#expect(try PureYAML.parse(">\nline1\n# no comment\nline3\n") == .string("line1 # no comment line3\n"))
}
@Test("A non-root block scalar with content at column 0 is still under-indented and rejected")
func test_nestedUnderIndentedStillRejected() {
// A sequence item value: its parent is the sequence at indent 0, so content must be indented.
#expect(throws: PureYAML.Parsing.ParseError.self) {
try PureYAML.parse("- |\ncontent\n")
}
// A mapping value: same floor, so unindented content is not the block scalar's body.
#expect(throws: PureYAML.Parsing.ParseError.self) {
try PureYAML.parse("key: |\ncontent\n")
}
}
@Test("A nested block scalar with properly indented content is unaffected")
func test_nestedIndentedStillWorks() throws {
#expect(try PureYAML.parse("key: |\n content\n") == .mapping(.init([
.init(key: "key", value: .string("content\n")),
])))
}
}