From fadde56a05c0edb33e88441d9d040d98ce7d536c Mon Sep 17 00:00:00 2001 From: Mihaela M Date: Fri, 26 Jun 2026 07:50:07 +0200 Subject: [PATCH] fix(parsing): classify the keep block-scalar chomping indicator The scanner consumed the `+` indicator of a `|+` / `>+` block scalar but mapped it to nothing, so keep was silently treated as clip, and a scanner test baked that in. Add a `keep` case to BlockScalarChomping, recognize `+` in the header scanner, and correct the test. Full keep value semantics (preserving trailing blank lines) depend on scanner blank-line capture and are tracked in #57; this commit fixes the token classification only. --- CHANGELOG.md | 7 +++++++ Sources/Parsing/BlockScalarChomping.swift | 1 + Sources/Parsing/Scanner.swift | 2 ++ Sources/Parsing/TokenEventParser.Block.swift | 8 +++++++- Tests/ScannerTests.swift | 4 ++-- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08d595c..3263151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 win, earlier sources win, inherited-then-local ordering) now have one source of truth. No behavior change. +### Fixed + +- Classify the block scalar keep chomping indicator (`|+` / `>+`) as a distinct + `keep` chomping instead of silently treating it as clip. Full keep semantics + (preserving trailing blank lines) still depend on scanner blank-line capture, + tracked in #57. + ### Documentation - Correct `SECURITY.md`: fix the reporting contact address and replace the diff --git a/Sources/Parsing/BlockScalarChomping.swift b/Sources/Parsing/BlockScalarChomping.swift index 68298d5..6ce330e 100644 --- a/Sources/Parsing/BlockScalarChomping.swift +++ b/Sources/Parsing/BlockScalarChomping.swift @@ -2,6 +2,7 @@ extension PureYAML.Parsing { enum BlockScalarChomping: String, Equatable, CustomStringConvertible { case clip case strip + case keep var description: String { rawValue diff --git a/Sources/Parsing/Scanner.swift b/Sources/Parsing/Scanner.swift index d8c0542..cf1c01a 100644 --- a/Sources/Parsing/Scanner.swift +++ b/Sources/Parsing/Scanner.swift @@ -301,6 +301,8 @@ extension PureYAML.Parsing.Scanner { while let character = state.reader.peek(), isBlockScalarHeaderCharacter(character) { if character == "-" { chomping = .strip + } else if character == "+" { + chomping = .keep } else if character.isNumber { indentationIndicator = Int(String(character)) } diff --git a/Sources/Parsing/TokenEventParser.Block.swift b/Sources/Parsing/TokenEventParser.Block.swift index 363b3a0..821bd58 100644 --- a/Sources/Parsing/TokenEventParser.Block.swift +++ b/Sources/Parsing/TokenEventParser.Block.swift @@ -249,7 +249,13 @@ extension PureYAML.Parsing.TokenEventParser { switch chomping { case .strip: return content - case .clip: + case .clip, .keep: + // Clip and keep currently coincide: both append the single final + // line break. They diverge only over trailing blank lines, which the + // scanner does not yet capture into `lines`, so keep cannot preserve + // them today. That gap (and the related dropping of internal blank + // lines and folded-paragraph breaks) is tracked separately. Strip is + // the only branch that omits the final break. return content + "\n" } } diff --git a/Tests/ScannerTests.swift b/Tests/ScannerTests.swift index 01f10c4..7bc06cf 100644 --- a/Tests/ScannerTests.swift +++ b/Tests/ScannerTests.swift @@ -333,7 +333,7 @@ private func expectFlowAndBlockScalarKinds(_ tokens: [String]) { "mappingKey", "scalar value=\"text\" style=plain", "mappingValue", - "blockScalarHeader style=literal", + "blockScalarHeader style=literal chomping=keep", "indent width=2", "scalar value=\"line\" style=plain", "dedent width=0", @@ -357,7 +357,7 @@ private func expectFlowAndBlockScalarRequiredKinds(_ tokens: [String]) { "tag value=!Thing", "anchor name=item", "alias name=item", - "blockScalarHeader style=literal", + "blockScalarHeader style=literal chomping=keep", "blockScalarHeader style=folded chomping=strip", ]) }