fix(parsing): reject a document marker inside a multi-line scalar
A line that begins with a document marker ('---' or '...') followed by a
boundary is forbidden inside a multi-line scalar, since it is ambiguous
with a document boundary. A multi-line quoted scalar folded such a line
into its value instead of rejecting it.
scanQuotedScalar now checks, on crossing each line break, whether the next
line begins a document marker, and rejects the scalar if so. The check
requires the marker at the line start with a following boundary, so inline
dashes ('a --- b'), '---' with no boundary ('---b'), an indented '---',
and dots not at a line start stay ordinary content.
Measured against the official YAML test-suite, parse-level conformance
rises from 268/333 (80.5%) to 270/333 (81.1%) with no regression.
This commit is contained in:
parent
28f549311f
commit
c1dda8bbab
@ -23,6 +23,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A document marker line (`---` or `...`) inside a multi-line quoted scalar is now rejected. Such
|
||||
a line is ambiguous with a document boundary, so the spec forbids it within a scalar; the scanner
|
||||
previously folded it into the value. The marker must begin a line and be followed by a boundary
|
||||
to be rejected, so inline dashes (`"a --- b"`), a `---` with no following boundary (`---b`), and
|
||||
an indented `---` remain ordinary content. Lifts official test-suite parse-level conformance from
|
||||
268/333 (80.5%) to 270/333 (81.1%), with no regression.
|
||||
- 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
|
||||
|
||||
@ -29,6 +29,7 @@ public extension PureYAML.Parsing {
|
||||
case commentInMultilineScalar(line: Int)
|
||||
case aliasWithProperties(line: Int, column: Int)
|
||||
case inlineBlockSequenceAfterProperty(line: Int, column: Int)
|
||||
case documentMarkerInScalar(line: Int)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
@ -88,6 +89,8 @@ public extension PureYAML.Parsing {
|
||||
"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)"
|
||||
case let .documentMarkerInScalar(line):
|
||||
"a document marker cannot appear inside a multi-line scalar at line \(line)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,6 +152,8 @@ public extension PureYAML.Parsing {
|
||||
"aliasWithProperties"
|
||||
case .inlineBlockSequenceAfterProperty:
|
||||
"inlineBlockSequenceAfterProperty"
|
||||
case .documentMarkerInScalar:
|
||||
"documentMarkerInScalar"
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,6 +168,7 @@ public extension PureYAML.Parsing {
|
||||
let .unterminatedQuotedString(line),
|
||||
let .unsupportedDirective(_, line),
|
||||
let .unsupportedMultiDocumentStream(line),
|
||||
let .documentMarkerInScalar(line),
|
||||
let .invalidEscape(_, line),
|
||||
let .malformedDirective(_, line),
|
||||
let .duplicateDirective(_, line),
|
||||
@ -198,6 +204,7 @@ public extension PureYAML.Parsing {
|
||||
.unterminatedQuotedString,
|
||||
.unsupportedDirective,
|
||||
.unsupportedMultiDocumentStream,
|
||||
.documentMarkerInScalar,
|
||||
.invalidEscape,
|
||||
.malformedDirective,
|
||||
.duplicateDirective,
|
||||
|
||||
@ -37,6 +37,11 @@ extension PureYAML.Parsing.Scanner {
|
||||
if character != "\\" {
|
||||
previousWasEscape = false
|
||||
}
|
||||
// A line that begins with a document marker (`---` / `...`) is forbidden inside a
|
||||
// multi-line scalar: the marker would be ambiguous with a document boundary.
|
||||
if isLineBreak(character), isAtDocumentMarker(state.reader) {
|
||||
throw PureYAML.Parsing.ParseError.documentMarkerInScalar(line: state.reader.mark.line)
|
||||
}
|
||||
}
|
||||
guard state.reader.peek() == quote else {
|
||||
throw PureYAML.Parsing.ParseError.unterminatedQuotedString(line: start.line)
|
||||
|
||||
25
Tests/DocumentMarkerInScalarTests.swift
Normal file
25
Tests/DocumentMarkerInScalarTests.swift
Normal file
@ -0,0 +1,25 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("Document markers inside multi-line scalars")
|
||||
struct DocumentMarkerInScalarTests {
|
||||
@Test("A `---` or `...` line inside a multi-line quoted scalar is rejected", arguments: [
|
||||
"---\n\"\n---\n\"\n", // a `---` line inside a double-quoted scalar
|
||||
"---\n'\n...\n'\n", // a `...` line inside a single-quoted scalar
|
||||
])
|
||||
func test_documentMarkerInScalarRejected(_ yaml: String) throws {
|
||||
#expect(throws: PureYAML.Parsing.ParseError.self) {
|
||||
try PureYAML.parse(yaml)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Dashes and dots that are not a line-start marker stay valid", arguments: [
|
||||
"\"a --- b\"\n", // dashes inline, not at a line start
|
||||
"\"a\n---b\"\n", // `---` without a following boundary is content
|
||||
"\"a\nb\"\n", // an ordinary multi-line quoted scalar
|
||||
"\"...x\"\n", // dots not at a line start
|
||||
])
|
||||
func test_nonMarkerContentStaysValid(_ yaml: String) throws {
|
||||
_ = try PureYAML.parse(yaml)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user