fix(parsing): reject an under-indented property on a mapping value
A mapping value's anchor or tag on the line below the key must be indented past the key. The parser consumed such a property regardless of its column, so 'seq:' then '&anchor' in column 1, anchoring a sequence at the document level, parsed instead of failing. When the value's properties are consumed before the indent check, a properly indented one would have been preceded by an indent token and consumed inside the indented value; reaching that point with a property on a later line therefore means it sits at the key's indentation, which is rejected. The indented-value branch and the new guard move to their own helpers so parseMappingPair stays within the complexity and length limits. An inline property, an indented property, and a block sequence at the key's indent are unaffected. Measured against the official YAML test-suite, parse-level conformance rises from 275/333 (82.6%) to 276/333 (82.9%) with no regression.
This commit is contained in:
parent
30dbd9316b
commit
ecf634fdf7
@ -23,6 +23,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- 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.
|
||||
- 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:
|
||||
|
||||
@ -19,22 +19,9 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
}
|
||||
|
||||
let properties = try consumeProperties()
|
||||
try enforceValuePropertyIndented(properties, valueIndicator: valueIndicator)
|
||||
if cursor.current?.kind.isIndent == true {
|
||||
_ = try expect("indent") { $0.isIndent }
|
||||
let result: PureYAML.Parsing.NodeResult = if cursor.current?.kind.isBlockEntry == true {
|
||||
try parseBlockSequence(
|
||||
properties: properties,
|
||||
allowMappingKeyTerminator: true,
|
||||
)
|
||||
} else {
|
||||
try parseNode(properties: properties)
|
||||
}
|
||||
if cursor.current?.kind.isDedent == true {
|
||||
_ = try expect("dedent") { $0.isDedent }
|
||||
} else if cursor.current?.kind.isMappingKey != true {
|
||||
_ = try expect("dedent") { $0.isDedent }
|
||||
}
|
||||
return result
|
||||
return try parseIndentedMappingValue(properties: properties)
|
||||
}
|
||||
if properties == .none, try isCommentInterruptedScalarValue(after: valueIndicator) {
|
||||
return try rejectCommentInterruptedScalar()
|
||||
@ -59,6 +46,42 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
return try emitNullMappingValue(at: valueIndicator.endMark)
|
||||
}
|
||||
|
||||
/// Parses a mapping value that begins on an indented line below the `:`. A leading block entry
|
||||
/// is a block sequence; anything else is a node. The closing dedent is consumed unless the value
|
||||
/// ended on a sibling mapping key, which the surrounding mapping continues from.
|
||||
mutating func parseIndentedMappingValue(
|
||||
properties: PureYAML.Parsing.NodeProperties,
|
||||
) throws -> PureYAML.Parsing.NodeResult {
|
||||
_ = try expect("indent") { $0.isIndent }
|
||||
let result: PureYAML.Parsing.NodeResult = if cursor.current?.kind.isBlockEntry == true {
|
||||
try parseBlockSequence(properties: properties, allowMappingKeyTerminator: true)
|
||||
} else {
|
||||
try parseNode(properties: properties)
|
||||
}
|
||||
if cursor.current?.kind.isDedent == true {
|
||||
_ = try expect("dedent") { $0.isDedent }
|
||||
} else if cursor.current?.kind.isMappingKey != true {
|
||||
_ = try expect("dedent") { $0.isDedent }
|
||||
}
|
||||
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).
|
||||
func enforceValuePropertyIndented(
|
||||
_ properties: PureYAML.Parsing.NodeProperties,
|
||||
valueIndicator: PureYAML.Parsing.Token,
|
||||
) throws {
|
||||
guard properties != .none, let propertyMark = properties.mark,
|
||||
propertyMark.line != valueIndicator.mark.line
|
||||
else {
|
||||
return
|
||||
}
|
||||
throw PureYAML.Parsing.ParseError.unexpectedIndentation(line: propertyMark.line)
|
||||
}
|
||||
|
||||
/// Emit an empty (null) scalar as a mapping value and return its node result, used where a
|
||||
/// mapping entry has no value: an explicit key with no `:`, or a `key:` with nothing after it.
|
||||
mutating func emitNullMappingValue(at mark: PureYAML.Parsing.Mark) throws -> PureYAML.Parsing.NodeResult {
|
||||
|
||||
22
Tests/ValuePropertyIndentationTests.swift
Normal file
22
Tests/ValuePropertyIndentationTests.swift
Normal file
@ -0,0 +1,22 @@
|
||||
@testable import PureYAML
|
||||
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 {
|
||||
#expect(throws: PureYAML.Parsing.ParseError.self) {
|
||||
try PureYAML.parse("---\nseq:\n&anchor\n- a\n- b\n")
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
"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
|
||||
])
|
||||
func test_validValuePropertyPlacement(_ yaml: String) throws {
|
||||
_ = try PureYAML.parse(yaml)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user