fix(parsing): reject a block entry after a flow collection on a line
A block sequence entry cannot follow a flow collection that closed earlier
on the same line ('- { y: z }- invalid', '- [a] - b'): the entry would
begin a sequence inline after a complete node. The scanner accepted it,
producing extra sequence items.
The scanner now records when a flow collection closes at the top level on
the current line and rejects a following block entry. The flag resets at
each line break, so a flow collection on its own line is unaffected, and a
nested block entry ('- -') has no preceding flow close, so it stays valid.
Measured against the official YAML test-suite, parse-level conformance
rises from 276/333 (82.9%) to 277/333 (83.2%) with no regression.
This commit is contained in:
parent
ecf634fdf7
commit
d79df8c4bc
@ -23,6 +23,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A block sequence entry can no longer follow a flow collection that closed earlier on the same
|
||||
line (`- { y: z }- invalid`, `- [a] - b`); the entry would begin a sequence inline after a
|
||||
complete node. The scanner now tracks a top-level flow close on the current line and rejects a
|
||||
block entry after it, while a nested block entry (`- -`, which has no preceding flow close), a
|
||||
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
|
||||
|
||||
@ -13,6 +13,7 @@ extension PureYAML.Parsing {
|
||||
state.isAtLineStart = true
|
||||
state.sawContentOnLine = false
|
||||
state.sawMappingValueOnLine = false
|
||||
state.sawFlowCloseOnLine = false
|
||||
continue
|
||||
}
|
||||
|
||||
@ -83,6 +84,10 @@ extension PureYAML.Parsing.Scanner {
|
||||
// (`a: b: c`). Reset at each line break. Flow `:` do not set it, since flow allows several
|
||||
// pairs on a line (`{a: b, c: d}`).
|
||||
var sawMappingValueOnLine = false
|
||||
// Whether a flow collection has closed on the current line at the top level. A block
|
||||
// sequence entry cannot follow a flow node on the same line (`{a: b}- x`), unlike a nested
|
||||
// block entry (`- -`), which is not preceded by a flow close. Reset at each line break.
|
||||
var sawFlowCloseOnLine = false
|
||||
var tagHandles = [
|
||||
"!": "!",
|
||||
"!!": "tag:yaml.org,2002:",
|
||||
@ -193,6 +198,16 @@ extension PureYAML.Parsing.Scanner {
|
||||
if state.sawMappingValueOnLine, state.flowDepth == 0 {
|
||||
throw PureYAML.Parsing.ParseError.blockSequenceAsInlineMappingValue(line: state.reader.mark.line)
|
||||
}
|
||||
// A block sequence entry cannot follow a flow collection that closed earlier on the
|
||||
// line (`{a: b}- x`); the entry would begin a sequence inline after a complete node.
|
||||
if state.sawFlowCloseOnLine {
|
||||
throw PureYAML.Parsing.ParseError.unexpectedToken(
|
||||
expected: "a line break or flow indicator",
|
||||
actual: "-",
|
||||
line: state.reader.mark.line,
|
||||
column: state.reader.mark.column,
|
||||
)
|
||||
}
|
||||
scanFixedToken(.blockEntry, state: &state)
|
||||
return true
|
||||
case "?" where isIndicatorBoundary(state.reader.peek(offset: 1)):
|
||||
@ -220,6 +235,9 @@ extension PureYAML.Parsing.Scanner {
|
||||
state.flowDepth = max(0, state.flowDepth - 1)
|
||||
scanFixedToken(.flowSequenceEnd, state: &state)
|
||||
_ = state.flowContexts.popLast()
|
||||
if state.flowDepth == 0 {
|
||||
state.sawFlowCloseOnLine = true
|
||||
}
|
||||
return true
|
||||
case "{":
|
||||
state.flowDepth += 1
|
||||
@ -230,6 +248,9 @@ extension PureYAML.Parsing.Scanner {
|
||||
state.flowDepth = max(0, state.flowDepth - 1)
|
||||
scanFixedToken(.flowMappingEnd, state: &state)
|
||||
_ = state.flowContexts.popLast()
|
||||
if state.flowDepth == 0 {
|
||||
state.sawFlowCloseOnLine = true
|
||||
}
|
||||
return true
|
||||
case ",":
|
||||
scanFixedToken(.flowEntry, state: &state)
|
||||
|
||||
@ -93,6 +93,7 @@ extension PureYAML.Parsing {
|
||||
scannerBox.state.isAtLineStart = true
|
||||
scannerBox.state.sawContentOnLine = false
|
||||
scannerBox.state.sawMappingValueOnLine = false
|
||||
scannerBox.state.sawFlowCloseOnLine = false
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
25
Tests/BlockEntryAfterFlowTests.swift
Normal file
25
Tests/BlockEntryAfterFlowTests.swift
Normal file
@ -0,0 +1,25 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("A block entry after a flow collection")
|
||||
struct BlockEntryAfterFlowTests {
|
||||
@Test("A block entry on the same line as a closed flow collection is rejected", arguments: [
|
||||
"---\n- { y: z }- invalid\n", // a block entry after a flow mapping
|
||||
"- [a] - b\n", // a block entry after a flow sequence
|
||||
])
|
||||
func test_blockEntryAfterFlowRejected(_ yaml: String) throws {
|
||||
#expect(throws: PureYAML.Parsing.ParseError.self) {
|
||||
try PureYAML.parse(yaml)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Nested entries and flow collections on their own line stay valid", arguments: [
|
||||
"- - a\n- - b\n", // a nested block sequence
|
||||
"- [a]\n- [b]\n", // flow sequences, each on its own line
|
||||
"- {a: b}\n- c\n", // a flow mapping item then a sibling entry
|
||||
"[[a], [b]]\n", // nested flow sequences
|
||||
])
|
||||
func test_validBlockAndFlowEntries(_ yaml: String) throws {
|
||||
_ = try PureYAML.parse(yaml)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user