diff --git a/CHANGELOG.md b/CHANGELOG.md index e30109f..9964333 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Sources/Parsing/Scanner.Helpers.swift b/Sources/Parsing/Scanner.Helpers.swift index 33df47b..db60423 100644 --- a/Sources/Parsing/Scanner.Helpers.swift +++ b/Sources/Parsing/Scanner.Helpers.swift @@ -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 { diff --git a/Tests/MappingValueTabBoundaryTests.swift b/Tests/MappingValueTabBoundaryTests.swift new file mode 100644 index 0000000..e5c6db2 --- /dev/null +++ b/Tests/MappingValueTabBoundaryTests.swift @@ -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")) + } +}