diff --git a/CHANGELOG.md b/CHANGELOG.md index 0618f16..91a8a81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- An explicit key (`? foo`) whose plain scalar spans lines now folds a continuation line that ends at + the value `:`. `? foo` then `bar : baz` is the key `foo bar`, not an error: the `:` is the key's + value indicator, so the line stays key content and folds in, where before the scalar dropped it and + the parser stalled. An implicit key is unchanged, since there a continuation line ending at `:` is a + new mapping key and the scalar still stops before it. The block test-suite rates are unchanged (no + comparable case isolates this), and the flow form needs separate explicit-key work, but the parser + now folds block explicit keys correctly. + - An explicit block scalar indentation indicator (`|2`, `>1`) on an inline mapping value now measures from the mapping's indentation. A value like `- aaa: |2` has the inline mapping as its parent, but that mapping sits at an implicit indentation off the main stack, so the indicator was added to the diff --git a/Sources/Parsing/Scanner.Helpers.swift b/Sources/Parsing/Scanner.Helpers.swift index 29c1c0f..ace2812 100644 --- a/Sources/Parsing/Scanner.Helpers.swift +++ b/Sources/Parsing/Scanner.Helpers.swift @@ -170,7 +170,17 @@ extension PureYAML.Parsing.Scanner { var continuationState = state continuationState.reader = probe let continuation = scanPlainScalarContinuationLine(&continuationState) - guard !isAtMappingValueIndicator(state: continuationState) else { + if isAtMappingValueIndicator(state: continuationState) { + // A continuation line ending at `:` is normally a new implicit key, so the scalar + // stops before it. Under an explicit key (`? foo` then `bar :`), the `:` is the key's + // value indicator instead, so the line is still key content: fold it in and stop, and + // the `:` is scanned next as the value indicator. + guard state.pendingExplicitKey else { + return value + } + state = continuationState + value += blankLineCount == 0 ? " " : String(repeating: "\n", count: blankLineCount) + value += continuation return value } diff --git a/Tests/ExplicitKeyMultilineFoldTests.swift b/Tests/ExplicitKeyMultilineFoldTests.swift new file mode 100644 index 0000000..1e92e9f --- /dev/null +++ b/Tests/ExplicitKeyMultilineFoldTests.swift @@ -0,0 +1,33 @@ +@testable import PureYAML +import Testing + +/// An explicit key (`? foo`) may span lines: its plain scalar folds across the break, and a `:` on +/// the last key line is the value indicator, not a new entry. Under an implicit key a continuation +/// line ending at `:` is instead a new mapping key, so the scalar stops before it, unchanged. +@Suite("Explicit key multiline fold") +struct ExplicitKeyMultilineFoldTests { + @Test("An explicit key folds a continuation line that ends at the value colon") + func test_continuationEndingAtColon() throws { + #expect(try PureYAML.parse("? foo\n bar : baz\n") == .mapping(.init([ + .init(key: "foo bar", value: .string("baz")), + ]))) + } + + @Test("An explicit key with the colon on its own line still folds") + func test_colonOnOwnLine() throws { + #expect(try PureYAML.parse("? foo\n bar\n: baz\n") == .mapping(.init([ + .init(key: "foo bar", value: .string("baz")), + ]))) + } + + @Test("An implicit key is unaffected: a continuation ending at a colon stays a new key") + func test_implicitKeyUnchanged() throws { + #expect(try PureYAML.parse("foo: bar\n") == .mapping(.init([ + .init(key: "foo", value: .string("bar")), + ]))) + #expect(try PureYAML.parse("a: 1\nb: 2\n") == .mapping(.init([ + .init(key: "a", value: .int(1)), + .init(key: "b", value: .int(2)), + ]))) + } +}