From 4b06f6e5fc3ffe935702c325f2090e965f92faf2 Mon Sep 17 00:00:00 2001 From: Mihaela M Date: Mon, 29 Jun 2026 02:09:57 +0200 Subject: [PATCH] fix(parsing): end an inline item value at a sibling block entry A block entry with an inline node recorded only the node's indent (one past the dash) as a valid implicit level, not the dash's own indent. So a plain scalar that is an inline sequence item value could not tell a sibling entry that returns to the sequence indent from its own content, and a compact nested sequence as an explicit key's value folded into one scalar: `? a` / `: - b` / ` - - c` / ` - d` became `{a: [b - - c - d]}` instead of `{a: [b, [c, d]]}`. Record the entry's own indent as a sibling level too, so the continuation stops there. Skip it under a pending explicit key, whose own sequence key drives the implicit indentation differently (an explicit key whose key is a sequence, `? - x`, must keep parsing). A block entry more indented than its container is still plain content, so a wrapped value beginning a line with `- ` folds in. The block-entry recording moved to a helper to keep the file within its length limit. Brings yaml-test-suite value-level conformance to 197/197 (100.0%) with the parse-level and should-error rejection rates unchanged. 485 tests pass. --- CHANGELOG.md | 10 ++++++ Sources/Parsing/Scanner.Helpers.swift | 15 ++++++++ Sources/Parsing/Scanner.swift | 4 +-- .../InlineSequenceItemContinuationTests.swift | 34 +++++++++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 Tests/InlineSequenceItemContinuationTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index ca7adf8..2821960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- A plain scalar that is an inline block sequence item value (`: - b`) now stops folding at a later + block entry that returns to the sequence's own indent, so the dashes form the rest of the sequence + rather than folding into the first item. A block entry only recorded its inline node's indent as a + valid implicit level, not the dash's own indent, so a continuation could not tell a sibling entry + from content, and `? a` / `: - b` / ` - - c` / ` - d` folded into one scalar. The entry's indent + is now recorded as a sibling level too (except under an explicit key, whose sequence key drives + implicit indentation itself), while a block entry more indented than its container stays plain + content. Brings yaml-test-suite value-level conformance to 197/197 (100.0%), with the parse and + should-error rates unchanged. + - A leading blank line of a block scalar now keeps any whitespace past the content indentation as content, as a blank line within the scalar already did. The leading blank lines (those before the first content line) were only counted, so an otherwise-blank `␣\t` line under a one-space content diff --git a/Sources/Parsing/Scanner.Helpers.swift b/Sources/Parsing/Scanner.Helpers.swift index d0c5409..39802d8 100644 --- a/Sources/Parsing/Scanner.Helpers.swift +++ b/Sources/Parsing/Scanner.Helpers.swift @@ -349,6 +349,21 @@ extension PureYAML.Parsing.Scanner { /// further key would form a forbidden inline nested mapping; in flow style several pairs per /// line are allowed, so that restriction does not apply. In flow a scalar that is the content of /// an open explicit key (`?`) is the key itself, not a new implicit key. + /// Records the implicit indentation levels a block entry with an inline node introduces: the + /// node's own indent (one past the dash), and the dash's indent as a sibling level so an inline + /// item's plain value stops folding at a later entry that returns to it (`: -␣b` then ` - - c`). + /// The sibling level is skipped under a pending explicit key (`? - x`), whose sequence key drives + /// implicit indentation itself. + func recordBlockEntryImplicitIndentation(start: PureYAML.Parsing.Mark, state: inout State) { + guard hasNodeOnSameLine(afterCurrentPositionIn: state.reader) else { + return + } + state.validImplicitIndentation.insert(start.column + 1) + if !state.pendingExplicitKey { + state.validImplicitIndentation.insert(start.column - 1) + } + } + func canSynthesizeImplicitBlockKey(_ state: State) -> Bool { if state.flowDepth > 0 { return state.flowContexts.last?.hasExplicitKey != true diff --git a/Sources/Parsing/Scanner.swift b/Sources/Parsing/Scanner.swift index 6ced1ee..7438cdc 100644 --- a/Sources/Parsing/Scanner.swift +++ b/Sources/Parsing/Scanner.swift @@ -369,8 +369,8 @@ extension PureYAML.Parsing.Scanner { let start = state.reader.mark state.reader.advance() state.append(kind, mark: start, endMark: state.reader.mark) - if case .blockEntry = kind, hasNodeOnSameLine(afterCurrentPositionIn: state.reader) { - state.validImplicitIndentation.insert(start.column + 1) + if case .blockEntry = kind { + recordBlockEntryImplicitIndentation(start: start, state: &state) } if case .mappingValue = kind, state.flowContexts.last?.kind == .mapping { state.flowContexts[state.flowContexts.count - 1].isExpectingKey = false diff --git a/Tests/InlineSequenceItemContinuationTests.swift b/Tests/InlineSequenceItemContinuationTests.swift new file mode 100644 index 0000000..8b7c1a9 --- /dev/null +++ b/Tests/InlineSequenceItemContinuationTests.swift @@ -0,0 +1,34 @@ +@testable import PureYAML +import Testing + +/// A plain scalar that is an inline block sequence item value (`: - b`) stops folding when a later +/// block entry returns to the sequence's own indent: those dashes form the rest of the sequence, not +/// content of the first item. A block entry that is more indented than the sequence is still plain +/// content of the scalar, so a wrapped value that happens to begin a line with `- ` folds in. +@Suite("Inline sequence item continuation") +struct InlineSequenceItemContinuationTests { + @Test("An inline item scalar ends at a sibling block entry") + func test_siblingEntryEndsItem() throws { + // `: - b` then ` - c` is the two-item sequence `[b, c]`, not `[b - c]`. + #expect(try PureYAML.parse(": - b\n - c\n") == .mapping(.init([ + .init(keyNode: .string(""), value: .sequence([.string("b"), .string("c")])), + ]))) + } + + @Test("A nested compact sequence as an explicit-key value parses with tabs") + func test_explicitKeyNestedSequenceWithTabs() throws { + #expect(try PureYAML.parse("? a\n: -\tb\n - -\tc\n - d\n") == .mapping(.init([ + .init(key: "a", value: .sequence([ + .string("b"), + .sequence([.string("c"), .string("d")]), + ])), + ]))) + } + + @Test("A more-indented dash inside a wrapped value stays content") + func test_moreIndentedDashIsContent() throws { + #expect(try PureYAML.parse("- description: a query\n and 3\n - Other.\n") == .sequence([ + .mapping(.init([.init(key: "description", value: .string("a query and 3 - Other."))])), + ])) + } +}