diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ad3aa9..87a69a7 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 +- Node properties that span their own indented lines above a further-indented node now attach to + that node (`key: &anchor` then ` !!map` then ` a: b`). An inline anchor with a tag on the next + indented line, above a mapping indented deeper still, left the node introduced by another indent + token, which the parser could not read and rejected as a missing node. The indented mapping value + now descends through that deeper indent, carrying the accumulated properties to the node. Fixes + yaml-test-suite BU8L; raises parse-level conformance to 312/333 (93.7%) and value-level to 205/205 + (100.0%), with the should-error rate unchanged. + - A node property written on its own indented line above a zero-indent block sequence (`seq:` then ` &anchor` then `- a` back at the key's column) now binds to that sequence. The grammar lets a block-in-block sequence sit at its parent's indentation while its property sits one level deeper, diff --git a/Sources/Parsing/TokenEventParser.Helpers.swift b/Sources/Parsing/TokenEventParser.Helpers.swift index 5de6a5d..d2555e7 100644 --- a/Sources/Parsing/TokenEventParser.Helpers.swift +++ b/Sources/Parsing/TokenEventParser.Helpers.swift @@ -76,7 +76,14 @@ extension PureYAML.Parsing.TokenEventParser { _ = try expect("dedent") { $0.isDedent } return try parseBlockSequence(properties: carried, allowMappingKeyTerminator: true) } - let node = try parseNode(properties: carried) + // The node carrying the properties may itself be indented further, when the properties + // span their own lines above it (`key: &anchor` / ` !!map` / ` a: b`): descend into that + // deeper indented value rather than expecting the node directly after the properties. + let node: PureYAML.Parsing.NodeResult = if cursor.current?.kind.isIndent == true { + try parseIndentedMappingValue(properties: carried) + } else { + try parseNode(properties: carried) + } try consumeIndentedMappingValueClose() return node } diff --git a/Tests/MultilinePropertyIndentTests.swift b/Tests/MultilinePropertyIndentTests.swift new file mode 100644 index 0000000..4609e11 --- /dev/null +++ b/Tests/MultilinePropertyIndentTests.swift @@ -0,0 +1,24 @@ +@testable import PureYAML +import Testing + +@Suite("Node properties may span indented lines above a further-indented node") +struct MultilinePropertyIndentTests { + /// yaml-test-suite BU8L: an inline anchor and a tag on the next indented line are both properties + /// of a mapping that is indented further still (`key: &anchor` / ` !!map` / ` a: b`). The parser + /// descends through the deeper indent to the node rather than rejecting it as a missing node. + @Test("An anchor then an indented tag attach to a further-indented mapping") + func test_anchorThenTagOnDeeperMapping() throws { + let root = try requireMapping(PureYAML.parse("key: &anchor\n !!map\n a: b\n")) + let value = try requireMapping(#require(root?["key"])) + #expect(value?["a"] == .string("b")) + } + + /// The same anchored mapping is reachable through an alias, confirming the anchor registered with + /// the nested mapping and not with an empty node. + @Test("The anchor on the spanned properties registers the nested mapping") + func test_spannedPropertyAnchorRegisters() throws { + let root = try requireMapping(PureYAML.parse("key: &anchor\n !!map\n a: b\nref: *anchor\n")) + let aliased = try requireMapping(#require(root?["ref"])) + #expect(aliased?["a"] == .string("b")) + } +}