PureYAML/Sources/Parsing/LibYAML.Scanner.Guards.swift
Mihaela Mihaljevic 98ca76e7af fix(parsing): reject a tab used to indent flow content
A tab is never indentation, so flow-collection content reached through a
tab below the continuation indent ('- [' / '\tfoo') is malformed even
though the tab inflates the column. A tab at or past the indent is
separation, and a tab-only blank line stays blank, so both remain valid.

Track a tab in a flow continuation's leading whitespace below the
parent + 1 indent and reject content (but not a closing bracket) reached
through it. Surpasses libyaml on Y79Y/003; whole-suite conformance
400/402, canonical events 308/308.
2026-06-24 22:04:30 +02:00

60 lines
2.5 KiB
Swift

extension PureYAML.Parsing.LibYAML.Scanner {
private typealias ScannerError = PureYAML.Parsing.LibYAML.ScannerError
/// Reject a flow-collection continuation line that is not indented past the
/// enclosing block context. YAML requires `s-flow-line-prefix` to be more
/// indented than the parent block; libyaml does not enforce it, so multi-line
/// flow content in column 0 (`[a,\nb]` under a key) wrongly parses.
mutating func checkFlowContinuationIndent() throws {
guard flowLevel > 0, crossedLineBreak, let next = reader.peek() else {
return
}
// A closing `]`/`}` (or a `,` separator) may sit at any indentation; only
// flow CONTENT must be indented past the parent block.
if next == "]" || next == "}" || next == "," {
return
}
// A tab in the leading whitespace is never valid indentation, even where
// spaces of the same width would be (the tab inflates the column).
if reader.column <= indentEngine.indent || flowLeadingTab {
throw ScannerError(
context: "while parsing a flow node",
problem: "found insufficiently indented line in a flow collection",
mark: reader.mark,
)
}
}
/// Whether the last emitted token bars a block sequence from opening on the
/// same line: a mapping value indicator or a node property (anchor/tag). After
/// another block entry a compact sequence is allowed, so that is not barred.
func lastTokenForbidsInlineSequence() -> Bool {
switch indentEngine.tokens.last?.type {
case .anchor, .tag:
true
case .value:
lastValueWasInline
default:
false
}
}
/// The error raised when a tab is used where indentation is required (before a
/// block indicator or an implicit mapping key). A tab is valid separation but
/// never indentation.
func tabIndentationError() -> PureYAML.Parsing.LibYAML.ScannerError {
ScannerError(
context: "while scanning for the next token",
problem: "found a tab character where an indentation space is expected",
mark: reader.mark,
)
}
/// Whether this flow level's pending simple key is a JSON (quoted) node, which
/// lets an adjacent `:` open its value without a separating space.
var flowKeyIsJSON: Bool {
let key = simpleKeys[simpleKeys.count - 1]
return key.possible && key.isJSON
}
}