diff --git a/CHANGELOG.md b/CHANGELOG.md index 084b6ee..c073da6 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 before a `#` now ends a plain scalar and begins a trailing comment, as a space already did, + since a comment opens after any white space. `c: d\t#X` is the value `d` with a comment, not the + literal `d\t#X`. A tab inside a plain scalar with no `#` after it stays part of the value, and a `#` + glued to content with no white space is still content. Raises yaml-test-suite value-level + conformance to 187/194 (96.4%), with the parse and should-error rates unchanged. + - A blank line next to a more-indented line in a folded block scalar (`>`) now keeps both line breaks. Between two non-more-indented lines a blank run of N folds to N breaks (the fold is absorbed into the first), but next to a more-indented line nothing folds, so the absorbed break diff --git a/Sources/Parsing/Scanner.Helpers.swift b/Sources/Parsing/Scanner.Helpers.swift index 7ccdb6b..29c1c0f 100644 --- a/Sources/Parsing/Scanner.Helpers.swift +++ b/Sources/Parsing/Scanner.Helpers.swift @@ -279,10 +279,12 @@ extension PureYAML.Parsing.Scanner { if isLineBreak(character) { return true } - if character == " ", state.reader.peek(offset: 1) == "#" { + // A comment opens after white space, a tab as well as a space, so a `#` preceded by either + // ends the plain scalar: `d\t#X` is the value `d` with a trailing comment. + if character == " " || character == "\t", state.reader.peek(offset: 1) == "#" { return true } - if character == "#", accumulated.isEmpty || accumulated.last == " " { + if character == "#", accumulated.isEmpty || accumulated.last == " " || accumulated.last == "\t" { return true } if character == ":", shouldEndPlainScalarAtMappingValue(state: state) { diff --git a/Tests/PlainScalarTabCommentTests.swift b/Tests/PlainScalarTabCommentTests.swift new file mode 100644 index 0000000..86efef2 --- /dev/null +++ b/Tests/PlainScalarTabCommentTests.swift @@ -0,0 +1,36 @@ +@testable import PureYAML +import Testing + +/// A comment opens after white space, a tab as well as a space, so a `#` preceded by a tab ends a +/// plain scalar and begins a trailing comment. A tab inside a plain scalar, with no `#` after it, +/// stays part of the value, and a `#` glued directly to content (no white space) is content. +@Suite("Plain scalar tab comment") +struct PlainScalarTabCommentTests { + @Test("A tab before a hash ends the scalar and starts a comment") + func test_tabBeforeComment() throws { + #expect(try PureYAML.parse("c: d\t#X\n") == .mapping(.init([ + .init(key: "c", value: .string("d")), + ]))) + } + + @Test("A tab inside a plain scalar value is kept") + func test_internalTabKept() throws { + #expect(try PureYAML.parse("x: a\tb\n") == .mapping(.init([ + .init(key: "x", value: .string("a\tb")), + ]))) + } + + @Test("A hash glued to content with no white space stays content") + func test_hashWithoutWhitespaceIsContent() throws { + #expect(try PureYAML.parse("c: d#X\n") == .mapping(.init([ + .init(key: "c", value: .string("d#X")), + ]))) + } + + @Test("A space before a hash still ends the scalar") + func test_spaceBeforeCommentUnchanged() throws { + #expect(try PureYAML.parse("c: d #X\n") == .mapping(.init([ + .init(key: "c", value: .string("d")), + ]))) + } +}