fix(parsing): reject a block sequence inline after a node property
A block collection must begin on the line after its node properties, so a
block sequence whose first entry sits on the same line as its anchor or tag
('&a - x', '!!seq - x') is malformed. The parser accepted it, anchoring or
tagging the inline sequence.
parseBlockSequence now rejects a sequence whose first block entry shares a
line with the anchor or tag that introduced it, via a small helper to stay
within the complexity and brace-style limits. An anchor or tag on its own
line above the sequence, inside a sequence item ('- &a x'), before a flow
sequence ('&a [x]'), or on a scalar is unaffected; the mapping form is
left alone, since '&a key: value' is a valid anchored key.
Measured against the official YAML test-suite, parse-level conformance
rises from 267/333 (80.2%) to 268/333 (80.5%) with no regression.
This commit is contained in:
parent
779ebe596d
commit
28f549311f
@ -23,6 +23,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A block sequence can no longer start on the same line as its anchor or tag (`&a - x`). A block
|
||||
collection must begin on the line after its node properties, so an anchor or tag immediately
|
||||
before the first block entry is now rejected. An anchor or tag on its own line above the
|
||||
sequence, inside a sequence item (`- &a x`), before a flow sequence (`&a [x]`), or on a scalar
|
||||
is unaffected. Lifts official test-suite parse-level conformance from 267/333 (80.2%) to 268/333
|
||||
(80.5%), with no regression.
|
||||
- An alias node can no longer carry an anchor or tag. An alias is a bare reference, so `&b *a`
|
||||
(an anchor on an alias) and a tag on an alias are now rejected; the parser previously dropped the
|
||||
property and accepted the document. A bare alias and an anchor or tag on a real node are
|
||||
|
||||
@ -28,6 +28,7 @@ public extension PureYAML.Parsing {
|
||||
case multilineImplicitKey(line: Int)
|
||||
case commentInMultilineScalar(line: Int)
|
||||
case aliasWithProperties(line: Int, column: Int)
|
||||
case inlineBlockSequenceAfterProperty(line: Int, column: Int)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
@ -85,6 +86,8 @@ public extension PureYAML.Parsing {
|
||||
"a comment cannot interrupt a multi-line plain scalar at line \(line)"
|
||||
case let .aliasWithProperties(line, column):
|
||||
"an alias node cannot have an anchor or tag at line \(line), column \(column)"
|
||||
case let .inlineBlockSequenceAfterProperty(line, column):
|
||||
"a block sequence cannot start on the same line as its anchor or tag at line \(line), column \(column)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,6 +147,8 @@ public extension PureYAML.Parsing {
|
||||
"commentInMultilineScalar"
|
||||
case .aliasWithProperties:
|
||||
"aliasWithProperties"
|
||||
case .inlineBlockSequenceAfterProperty:
|
||||
"inlineBlockSequenceAfterProperty"
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +181,8 @@ public extension PureYAML.Parsing {
|
||||
let .unexpectedToken(_, _, line, _),
|
||||
let .nestingTooDeep(line, _),
|
||||
let .commentNotPrecededByWhitespace(line, _),
|
||||
let .aliasWithProperties(line, _):
|
||||
let .aliasWithProperties(line, _),
|
||||
let .inlineBlockSequenceAfterProperty(line, _):
|
||||
line
|
||||
}
|
||||
}
|
||||
@ -210,7 +216,8 @@ public extension PureYAML.Parsing {
|
||||
let .unexpectedToken(_, _, _, column),
|
||||
let .nestingTooDeep(_, column),
|
||||
let .commentNotPrecededByWhitespace(_, column),
|
||||
let .aliasWithProperties(_, column):
|
||||
let .aliasWithProperties(_, column),
|
||||
let .inlineBlockSequenceAfterProperty(_, column):
|
||||
column
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
allowMappingKeyTerminator: Bool = false,
|
||||
) throws -> PureYAML.Parsing.NodeResult {
|
||||
let start = try expect("block sequence entry") { $0.isBlockEntry }
|
||||
try enforceBlockSequenceNotInlineAfterProperty(properties, firstEntry: start)
|
||||
let startMark = properties.mark ?? start.mark
|
||||
var endMark = start.endMark
|
||||
try emit(.sequenceStart(
|
||||
@ -38,6 +39,22 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
return PureYAML.Parsing.NodeResult(kind: .sequence, endMark: endMark)
|
||||
}
|
||||
|
||||
/// A block collection must begin on the line after its node properties, so an anchor or tag on
|
||||
/// the same line as the sequence's first entry (`&a - x`) is malformed.
|
||||
func enforceBlockSequenceNotInlineAfterProperty(
|
||||
_ properties: PureYAML.Parsing.NodeProperties,
|
||||
firstEntry: PureYAML.Parsing.Token,
|
||||
) throws {
|
||||
guard properties.anchor != nil || properties.tag != nil,
|
||||
let propertyMark = properties.mark, propertyMark.line == firstEntry.mark.line
|
||||
else {
|
||||
return
|
||||
}
|
||||
throw PureYAML.Parsing.ParseError.inlineBlockSequenceAfterProperty(
|
||||
line: propertyMark.line, column: propertyMark.column,
|
||||
)
|
||||
}
|
||||
|
||||
mutating func consumeSequenceItemBoundaryDedent(sequenceIndentWidth: Int) throws {
|
||||
guard dedentWidth(cursor.current) == sequenceIndentWidth,
|
||||
try cursor.peek()?.kind.isBlockEntry == true
|
||||
|
||||
26
Tests/BlockSequenceAfterPropertyTests.swift
Normal file
26
Tests/BlockSequenceAfterPropertyTests.swift
Normal file
@ -0,0 +1,26 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("A block sequence after a node property")
|
||||
struct BlockSequenceAfterPropertyTests {
|
||||
@Test("A block sequence on the same line as its anchor or tag is rejected", arguments: [
|
||||
"&anchor - sequence entry\n", // an anchor immediately before a block entry
|
||||
"!!seq - x\n", // a tag immediately before a block entry
|
||||
])
|
||||
func test_inlineBlockSequenceAfterPropertyRejected(_ yaml: String) throws {
|
||||
#expect(throws: PureYAML.Parsing.ParseError.self) {
|
||||
try PureYAML.parse(yaml)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Properties positioned correctly around a sequence stay valid", arguments: [
|
||||
"&anchor\n- a\n- b\n", // the anchor on its own line, sequence below
|
||||
"- &anchor x\n- y\n", // the anchor inside a sequence item
|
||||
"&anchor [a, b]\n", // a flow sequence may follow an anchor inline
|
||||
"key: &anchor\n - a\n - b\n", // the anchor then an indented sequence
|
||||
"&anchor value\n", // a scalar may follow an anchor inline
|
||||
])
|
||||
func test_validSequencePropertyPlacement(_ yaml: String) throws {
|
||||
_ = try PureYAML.parse(yaml)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user