fix(parsing): reject a later under-indented property on a mapping value

The value-property indentation check used only the first property's mark,
so it caught a sole under-indented property ('seq:' then '&anchor') but not
a second one after an inline first ('key: &x' then a '!!tag' in column 1),
which still parsed.

The last consumed property is cursor.previous after consumeProperties, so
the check now uses it instead of the first mark, catching an under-indented
property whether it is the only one or a later one. Inline properties, a
property indented under the key, and a properly indented anchor-and-tag pair
are unaffected.

Measured against the official YAML test-suite, parse-level conformance
rises from 277/333 (83.2%) to 278/333 (83.5%) with no regression.
This commit is contained in:
Mihaela M 2026-06-28 01:44:48 +02:00
parent d79df8c4bc
commit 25b1388dbb
3 changed files with 22 additions and 14 deletions

View File

@ -30,10 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
flow collection on its own line, and nested flow collections are unaffected. Lifts official
test-suite parse-level conformance from 276/333 (82.9%) to 277/333 (83.2%), with no regression.
- A mapping value's anchor or tag on the line below the key must now be indented past the key. A
property at the key's own indentation (`seq:` then `&anchor` in column 1) is ambiguous with a
sibling and is rejected, while an inline property, a property indented under the key, and a block
sequence at the key's indent (which carries no property) stay valid. Lifts official test-suite
parse-level conformance from 275/333 (82.6%) to 276/333 (82.9%), with no regression.
property at the key's own indentation is ambiguous with a sibling and is rejected, whether it is
the only property (`seq:` then `&anchor` in column 1) or a later one after an inline first
(`key: &x` then a `!!tag` in column 1). An inline property, a property indented under the key, and
a block sequence at the key's indent (which carries no property) stay valid. Lifts official
test-suite parse-level conformance from 275/333 (82.6%) to 278/333 (83.5%), with no regression.
- A `-` immediately before a flow indicator is no longer treated as a scalar. In a flow collection
a `-` that begins a plain scalar must be followed by a non-indicator character, so `[-]`,
`[-, -]`, and `{a: -}` are now rejected. A dash followed by ordinary content is unaffected:

View File

@ -66,20 +66,22 @@ extension PureYAML.Parsing.TokenEventParser {
return result
}
/// A value's anchor or tag on a line below the `:` must be indented past the key. Reaching the
/// value-property consumption with a property on a later line means no indent token preceded it
/// (a properly indented one is consumed inside the indented value), so it sits at the key's
/// level and is malformed (`seq:` then `&anchor` in column 1).
/// A value's anchor or tag on a line below the `:` must be indented past the key. A properly
/// indented property is preceded by an indent token and consumed inside the indented value, so a
/// property consumed here on a line below the `:` sits at the key's level and is malformed. The
/// last consumed property is `cursor.previous`; checking it catches both a single under-indented
/// property (`seq:` then `&anchor`) and a second one after an inline first (`key: &x` then a
/// `!!tag` in column 1).
func enforceValuePropertyIndented(
_ properties: PureYAML.Parsing.NodeProperties,
valueIndicator: PureYAML.Parsing.Token,
) throws {
guard properties != .none, let propertyMark = properties.mark,
propertyMark.line != valueIndicator.mark.line
guard properties != .none, let lastProperty = cursor.previous,
lastProperty.mark.line != valueIndicator.mark.line
else {
return
}
throw PureYAML.Parsing.ParseError.unexpectedIndentation(line: propertyMark.line)
throw PureYAML.Parsing.ParseError.unexpectedIndentation(line: lastProperty.mark.line)
}
/// Emit an empty (null) scalar as a mapping value and return its node result, used where a

View File

@ -3,16 +3,21 @@ import Testing
@Suite("A mapping value's property indentation")
struct ValuePropertyIndentationTests {
@Test("A value's anchor on a line below the key, at the key's indent, is rejected")
func test_underIndentedValuePropertyRejected() throws {
@Test("An under-indented value property is rejected, whether it is the first or a later one", arguments: [
"---\nseq:\n&anchor\n- a\n- b\n", // a sole anchor at the key's indent
"key: &x\n!!map\n a: b\n", // a second property (a tag) at the key's indent
])
func test_underIndentedValuePropertyRejected(_ yaml: String) throws {
#expect(throws: PureYAML.Parsing.ParseError.self) {
try PureYAML.parse("---\nseq:\n&anchor\n- a\n- b\n")
try PureYAML.parse(yaml)
}
}
@Test("Properties indented past the key, or inline, stay valid", arguments: [
"seq:\n &a\n - x\n", // an anchor indented under the key
"key: &a value\n", // an inline anchor on the value
"key: &a !!str value\n", // an inline anchor and tag
"key:\n &a !!map\n a: b\n", // properly indented anchor and tag
"seq:\n- a\n- b\n", // a block sequence at the key's indent (no property)
"key:\n &a sub: v\n", // an indented anchored key
])