fix(parsing): accept a tab after the colon value indicator

A tab is separation white space in YAML, so it follows the `:` mapping-value
indicator exactly as a space does. isMappingValueBoundary (flow) and
isBlockMappingValueBoundary (block) accepted only a space, line break, comma,
or closing bracket, so `foo:\tbar` folded into the plain scalar `foo:\tbar`
instead of the mapping `{foo: bar}`.

Accept a tab in both. A `:` with no following white space (`foo:bar`) is still
plain scalar content, and the symmetric counterpart for a tab after a leading
indicator landed in the previous change.

Raises yaml-test-suite parse-level to 300/333 (90.1%) with the should-error
rejection rate unchanged. 433 tests pass.
This commit is contained in:
Mihaela M 2026-06-28 20:30:24 +02:00
parent 37e84b5eb4
commit 738408d458
3 changed files with 43 additions and 1 deletions

View File

@ -84,6 +84,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- A tab now follows the `:` mapping-value indicator the way a space does, in both block and flow
context, since a tab is separation white space in YAML. `foo:\tbar` is the mapping `{foo: bar}`
rather than the plain scalar `foo:\tbar`. A `:` with no following white space at all (`foo:bar`)
stays plain scalar content. Raises yaml-test-suite parse-level conformance to 300/333 (90.1%), with
the should-error rejection rate unchanged.
- A tab now ends a leading indicator the way a space does, since a tab is separation white space in
YAML (only indentation forbids a tab). A document marker, block entry, or explicit key followed by a
tab (`---\tscalar`, `-\titem`, `?\tkey`) is recognized as the indicator rather than folded into a

View File

@ -53,7 +53,9 @@ extension PureYAML.Parsing.Scanner {
guard let character else {
return true
}
// A tab is separation white space, so it follows a `:` value indicator as a space does.
return character == " "
|| character == "\t"
|| isLineBreak(character)
|| character == ","
|| character == "]"
@ -64,7 +66,7 @@ extension PureYAML.Parsing.Scanner {
guard let character else {
return true
}
return character == " " || isLineBreak(character)
return character == " " || character == "\t" || isLineBreak(character)
}
func isTokenTerminator(_ character: Character) -> Bool {

View File

@ -0,0 +1,34 @@
@testable import PureYAML
import Testing
/// A tab is separation white space in YAML, so it follows the `:` mapping-value indicator as a space
/// does, in both block and flow context. A `:` with no following white space at all stays plain
/// scalar content (`foo:bar`), unchanged.
@Suite("Mapping value tab boundary")
struct MappingValueTabBoundaryTests {
@Test("A block mapping value may follow the colon after a tab")
func test_blockValueTab() throws {
#expect(try PureYAML.parse("foo:\tbar\n") == .mapping(.init([
.init(key: "foo", value: .string("bar")),
])))
}
@Test("A block sequence item mapping accepts a tab after the colon")
func test_blockSequenceItemTab() throws {
#expect(try PureYAML.parse("- foo:\tbar\n") == .sequence([
.mapping(.init([.init(key: "foo", value: .string("bar"))])),
]))
}
@Test("A flow mapping value may follow the colon after a tab")
func test_flowValueTab() throws {
#expect(try PureYAML.parse("{a:\tb}\n") == .mapping(.init([
.init(key: "a", value: .string("b")),
])))
}
@Test("A colon with no following white space stays plain scalar content")
func test_colonWithoutWhitespaceUnchanged() throws {
#expect(try PureYAML.parse("foo:bar\n") == .string("foo:bar"))
}
}