PureYAML/Sources/Parsing/ParseError.swift
Mihaela M c1dda8bbab 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.
2026-06-27 23:20:52 +02:00

233 lines
11 KiB
Swift

public extension PureYAML.Parsing {
/// Parser error with source line information when available.
enum ParseError: Swift.Error, Equatable, Sendable, CustomStringConvertible {
case unexpectedIndentation(line: Int)
case mixedCollectionStyles(line: Int)
case expectedAnchorName(line: Int)
case expectedAliasName(line: Int)
case expectedNode(line: Int, column: Int)
case expectedScalarKey(line: Int, column: Int)
case incompatibleYAMLDirective(line: Int)
case invalidTaggedScalar(tag: String, value: String, line: Int, column: Int)
case invalidMergeValue(line: Int, column: Int)
case unterminatedTag(line: Int)
case unterminatedQuotedString(line: Int)
case unsupportedDirective(name: String, line: Int)
case unsupportedMultiDocumentStream(line: Int)
case undefinedAlias(anchor: String, line: Int, column: Int)
case unexpectedEvent(expected: String, actual: String, line: Int, column: Int)
case unexpectedToken(expected: String, actual: String, line: Int, column: Int)
case nestingTooDeep(line: Int, column: Int)
case commentNotPrecededByWhitespace(line: Int, column: Int)
case invalidEscape(sequence: String, line: Int)
case malformedDirective(name: String, line: Int)
case duplicateDirective(name: String, line: Int)
case directiveWithoutDocument(line: Int)
case contentAfterDocumentEnd(line: Int)
case blockSequenceAsInlineMappingValue(line: Int)
case multilineImplicitKey(line: Int)
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 {
case let .unexpectedIndentation(line):
"unexpected indentation at line \(line)"
case let .mixedCollectionStyles(line):
"mapping and sequence entries are mixed at line \(line)"
case let .expectedAnchorName(line):
"expected an anchor name at line \(line)"
case let .expectedAliasName(line):
"expected an alias name at line \(line)"
case let .expectedNode(line, column):
"expected a YAML node at line \(line), column \(column)"
case let .expectedScalarKey(line, column):
"expected a scalar mapping key at line \(line), column \(column)"
case let .incompatibleYAMLDirective(line):
"incompatible YAML directive at line \(line)"
case let .invalidTaggedScalar(tag, value, line, column):
"invalid scalar '\(value)' for tag '\(tag)' at line \(line), column \(column)"
case let .invalidMergeValue(line, column):
"expected merge value to be a mapping or sequence of mappings at line \(line), column \(column)"
case let .unterminatedTag(line):
"unterminated tag at line \(line)"
case let .unterminatedQuotedString(line):
"unterminated quoted string at line \(line)"
case let .unsupportedDirective(name, line):
"unsupported directive '\(name)' at line \(line)"
case let .unsupportedMultiDocumentStream(line):
"multi-document streams are not supported at line \(line)"
case let .undefinedAlias(anchor, line, column):
"undefined alias '\(anchor)' at line \(line), column \(column)"
case let .unexpectedEvent(expected, actual, line, column):
"expected \(expected) at line \(line), column \(column), found \(actual)"
case let .unexpectedToken(expected, actual, line, column):
"expected \(expected) at line \(line), column \(column), found \(actual)"
case let .nestingTooDeep(line, column):
"nesting is too deep at line \(line), column \(column)"
case let .commentNotPrecededByWhitespace(line, column):
"a comment must be preceded by whitespace at line \(line), column \(column)"
case let .invalidEscape(sequence, line):
"invalid escape sequence '\(sequence)' at line \(line)"
case let .malformedDirective(name, line):
"malformed directive '\(name)' at line \(line)"
case let .duplicateDirective(name, line):
"duplicate directive '\(name)' at line \(line)"
case let .directiveWithoutDocument(line):
"a directive must be followed by a document at line \(line)"
case let .contentAfterDocumentEnd(line):
"no content may follow a document end marker at line \(line)"
case let .blockSequenceAsInlineMappingValue(line):
"a block sequence cannot start on the same line as a mapping value at line \(line)"
case let .multilineImplicitKey(line):
"an implicit mapping key must not span multiple lines at line \(line)"
case let .commentInMultilineScalar(line):
"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)"
case let .documentMarkerInScalar(line):
"a document marker cannot appear inside a multi-line scalar at line \(line)"
}
}
public var diagnosticCode: String {
switch self {
case .unexpectedIndentation:
"unexpectedIndentation"
case .mixedCollectionStyles:
"mixedCollectionStyles"
case .expectedAnchorName:
"expectedAnchorName"
case .expectedAliasName:
"expectedAliasName"
case .expectedNode:
"expectedNode"
case .expectedScalarKey:
"expectedScalarKey"
case .incompatibleYAMLDirective:
"incompatibleYAMLDirective"
case .invalidTaggedScalar:
"invalidTaggedScalar"
case .invalidMergeValue:
"invalidMergeValue"
case .unterminatedTag:
"unterminatedTag"
case .unterminatedQuotedString:
"unterminatedQuotedString"
case .unsupportedDirective:
"unsupportedDirective"
case .unsupportedMultiDocumentStream:
"unsupportedMultiDocumentStream"
case .undefinedAlias:
"undefinedAlias"
case .unexpectedEvent:
"unexpectedEvent"
case .unexpectedToken:
"unexpectedToken"
case .nestingTooDeep:
"nestingTooDeep"
case .commentNotPrecededByWhitespace:
"commentNotPrecededByWhitespace"
case .invalidEscape:
"invalidEscape"
case .malformedDirective:
"malformedDirective"
case .duplicateDirective:
"duplicateDirective"
case .directiveWithoutDocument:
"directiveWithoutDocument"
case .contentAfterDocumentEnd:
"contentAfterDocumentEnd"
case .blockSequenceAsInlineMappingValue:
"blockSequenceAsInlineMappingValue"
case .multilineImplicitKey:
"multilineImplicitKey"
case .commentInMultilineScalar:
"commentInMultilineScalar"
case .aliasWithProperties:
"aliasWithProperties"
case .inlineBlockSequenceAfterProperty:
"inlineBlockSequenceAfterProperty"
case .documentMarkerInScalar:
"documentMarkerInScalar"
}
}
public var sourceLine: Int? {
switch self {
case let .unexpectedIndentation(line),
let .mixedCollectionStyles(line),
let .expectedAnchorName(line),
let .expectedAliasName(line),
let .incompatibleYAMLDirective(line),
let .unterminatedTag(line),
let .unterminatedQuotedString(line),
let .unsupportedDirective(_, line),
let .unsupportedMultiDocumentStream(line),
let .documentMarkerInScalar(line),
let .invalidEscape(_, line),
let .malformedDirective(_, line),
let .duplicateDirective(_, line),
let .directiveWithoutDocument(line),
let .contentAfterDocumentEnd(line),
let .blockSequenceAsInlineMappingValue(line),
let .multilineImplicitKey(line),
let .commentInMultilineScalar(line):
line
case let .expectedNode(line, _),
let .expectedScalarKey(line, _),
let .invalidTaggedScalar(_, _, line, _),
let .invalidMergeValue(line, _),
let .undefinedAlias(_, line, _),
let .unexpectedEvent(_, _, line, _),
let .unexpectedToken(_, _, line, _),
let .nestingTooDeep(line, _),
let .commentNotPrecededByWhitespace(line, _),
let .aliasWithProperties(line, _),
let .inlineBlockSequenceAfterProperty(line, _):
line
}
}
public var sourceColumn: Int? {
switch self {
case .unexpectedIndentation,
.mixedCollectionStyles,
.expectedAnchorName,
.expectedAliasName,
.incompatibleYAMLDirective,
.unterminatedTag,
.unterminatedQuotedString,
.unsupportedDirective,
.unsupportedMultiDocumentStream,
.documentMarkerInScalar,
.invalidEscape,
.malformedDirective,
.duplicateDirective,
.directiveWithoutDocument,
.contentAfterDocumentEnd,
.blockSequenceAsInlineMappingValue,
.multilineImplicitKey,
.commentInMultilineScalar:
nil
case let .expectedNode(_, column),
let .expectedScalarKey(_, column),
let .invalidTaggedScalar(_, _, _, column),
let .invalidMergeValue(_, column),
let .undefinedAlias(_, _, column),
let .unexpectedEvent(_, _, _, column),
let .unexpectedToken(_, _, _, column),
let .nestingTooDeep(_, column),
let .commentNotPrecededByWhitespace(_, column),
let .aliasWithProperties(_, column),
let .inlineBlockSequenceAfterProperty(_, column):
column
}
}
}
}