fix(parsing): allow a repeated document-end marker
A `...` after a document is already closed (only comments or blank lines since the last `...`) ends nothing: a repeated document-end marker is valid YAML, so consume it as a no-op rather than rejecting the stream as an unsupported multi-document case. A second documentEnd is not emitted, so no phantom null document is composed, and real content after the marker stays an error. Fixes suite case M7A3 (parse-level conformance 313 -> 315); adds focused tests.
This commit is contained in:
parent
8d245ae335
commit
056095ca3d
@ -20,8 +20,22 @@ extension PureYAML.Parsing.Scanner {
|
||||
return true
|
||||
}
|
||||
if startsWith("...", state.reader), isIndicatorBoundary(state.reader.peek(offset: 3)) {
|
||||
guard !state.isDocumentClosed else {
|
||||
throw PureYAML.Parsing.ParseError.unsupportedMultiDocumentStream(line: state.reader.mark.line)
|
||||
// A `...` after a document is already closed (only comments or blank lines since the
|
||||
// last `...`) ends nothing: it is a repeated document-end marker, which YAML allows. Skip
|
||||
// it as a no-op rather than emitting a second documentEnd (which would compose a phantom
|
||||
// null document) or rejecting a valid stream. Content after it is still an error.
|
||||
if state.isDocumentClosed {
|
||||
state.reader.advance()
|
||||
state.reader.advance()
|
||||
state.reader.advance()
|
||||
while state.reader.peek() == " " {
|
||||
state.reader.advance()
|
||||
}
|
||||
if let next = state.reader.peek(), !isLineBreak(next), next != "#" {
|
||||
throw PureYAML.Parsing.ParseError.contentAfterDocumentEnd(line: state.reader.mark.line)
|
||||
}
|
||||
skipLine(&state)
|
||||
return true
|
||||
}
|
||||
// A `...` ends a document, so a directive still awaiting its `---` never gets one.
|
||||
try enforceDirectiveHasDocument(state)
|
||||
|
||||
30
Tests/RepeatedDocumentEndTests.swift
Normal file
30
Tests/RepeatedDocumentEndTests.swift
Normal file
@ -0,0 +1,30 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
/// A document-end marker (`...`) may appear repeatedly: once a document is closed, a further `...`
|
||||
/// (with only comments or blank lines since the last one) ends nothing and is a no-op, rather than
|
||||
/// being rejected. Content other than a comment after such a marker is still an error.
|
||||
@Suite("Repeated document end")
|
||||
struct RepeatedDocumentEndTests {
|
||||
@Test("A second `...` after a closed document, separated by a comment, is a no-op")
|
||||
func test_repeatedMarkerIsNoOp() throws {
|
||||
let documents = try PureYAML.parseStream("a\n...\n# comment\n...\nb\n").map(\.value)
|
||||
#expect(documents == [.string("a"), .string("b")])
|
||||
}
|
||||
|
||||
@Test("A comment-only region between two `...` markers contributes no document")
|
||||
func test_commentOnlyBetweenMarkers() throws {
|
||||
// Mirrors suite case M7A3: a bare document, then `...`, a comment, a repeated `...`, and a
|
||||
// root block scalar as the next document.
|
||||
let yaml = "Bare\ndocument\n...\n# No document\n...\n|\n%!PS-Adobe-2.0 # Not the first line\n"
|
||||
let documents = try PureYAML.parseStream(yaml).map(\.value)
|
||||
#expect(documents == [.string("Bare document"), .string("%!PS-Adobe-2.0 # Not the first line\n")])
|
||||
}
|
||||
|
||||
@Test("Real content after a repeated `...` marker is still rejected")
|
||||
func test_contentAfterRepeatedMarkerRejected() {
|
||||
#expect(throws: PureYAML.Parsing.ParseError.self) {
|
||||
_ = try PureYAML.parseStream("a\n...\n... x\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user