Compose an empty scalar wherever a node is grammatically required but the next token is a structural indicator or line/stream end, mirroring libyaml's process_empty_scalar: after a tag or anchor property with no node, as an absent mapping value (block and flow), as an absent mapping key (both the plain ': v' form and the explicit '?' form), in flow empty key/value forms ({ : }, { , : v }), and for an indented block node following properties. Also fixes a pre-existing block-sequence bug that nested a sibling '-' as a child when only properties remained on the entry line.
Empty scalars resolve through the existing resolver against the empty string, so !!str yields the empty string, bare empty or !!null yields null, and an empty key yields the empty string.
Flips 20 official YAML test suite cases to passing (270/402). CT4Q no longer false-rejects but still value-mismatches on flow plain-scalar folding (a separate gap), so it is reclassified in the manifest rather than removed. Updates the gap manifest, fixtures, counts, and roadmap.
155 lines
5.9 KiB
Swift
155 lines
5.9 KiB
Swift
@testable import PureYAML
|
|
import Testing
|
|
|
|
@Suite("Complex Mapping Key Parsing")
|
|
struct ComplexMappingKeyParsingTests {
|
|
@Test("Parses block sequence keys as first-class key nodes")
|
|
func test_parsesBlockSequenceKeysAsFirstClassKeyNodes() throws {
|
|
let value = try PureYAML.parse("""
|
|
? [Detroit Tigers, Chicago Cubs]
|
|
:
|
|
- 2001-07-23
|
|
""")
|
|
let expectedKey = PureYAML.Model.Key.sequence([
|
|
.string("Detroit Tigers"),
|
|
.string("Chicago Cubs"),
|
|
])
|
|
|
|
#expect(value == .mapping(.init([
|
|
.init(
|
|
keyNode: expectedKey,
|
|
value: .sequence([.string("2001-07-23")]),
|
|
),
|
|
])))
|
|
#expect(value.rootMapping?.pairs.first?.keyNode == expectedKey)
|
|
#expect(value.rootMapping?.pairs.first?.key == "[\"Detroit Tigers\", \"Chicago Cubs\"]")
|
|
#expect(value.rootMapping?["Detroit Tigers"] == nil)
|
|
#expect(value.rootMapping?[expectedKey] == .sequence([.string("2001-07-23")]))
|
|
}
|
|
|
|
@Test("Parses block mapping keys as first-class key nodes")
|
|
func test_parsesBlockMappingKeysAsFirstClassKeyNodes() throws {
|
|
let value = try PureYAML.parse("""
|
|
? {name: Example, version: 1}
|
|
: active
|
|
""")
|
|
let expectedKey = PureYAML.Model.Key.mapping(.init([
|
|
.init(key: "name", value: .string("Example")),
|
|
.init(key: "version", value: .int(1)),
|
|
]))
|
|
|
|
#expect(value == .mapping(.init([
|
|
.init(keyNode: expectedKey, value: .string("active")),
|
|
])))
|
|
#expect(value.rootMapping?.pairs.first?.keyNode.description == """
|
|
{"name": "Example", "version": 1}
|
|
""")
|
|
#expect(value.rootMapping?["name"] == nil)
|
|
#expect(value.rootMapping?[expectedKey] == .string("active"))
|
|
}
|
|
|
|
@Test("Keeps scalar mapping keys string-keyed even when tags look typed")
|
|
func test_keepsScalarMappingKeysStringKeyedEvenWhenTagsLookTyped() throws {
|
|
let value = try PureYAML.parse("""
|
|
? !!int 1
|
|
: integer-looking
|
|
? true
|
|
: boolean-looking
|
|
""")
|
|
|
|
#expect(value == .mapping(.init([
|
|
.init(key: "1", value: .string("integer-looking")),
|
|
.init(key: "true", value: .string("boolean-looking")),
|
|
])))
|
|
#expect(value.rootMapping?["1"] == .string("integer-looking"))
|
|
#expect(value.rootMapping?["true"] == .string("boolean-looking"))
|
|
#expect(value.rootMapping?[.sequence([.string("1")])] == nil)
|
|
}
|
|
|
|
@Test("Parses alias-backed complex keys without string coercion")
|
|
func test_parsesAliasBackedComplexKeysWithoutStringCoercion() throws {
|
|
let value = try PureYAML.parse("""
|
|
pair: &pair [a, b]
|
|
? *pair
|
|
: value
|
|
""")
|
|
let key = PureYAML.Model.Key.sequence([.string("a"), .string("b")])
|
|
|
|
#expect(value == .mapping(.init([
|
|
.init(key: "pair", value: .sequence([.string("a"), .string("b")])),
|
|
.init(keyNode: key, value: .string("value")),
|
|
])))
|
|
#expect(value.rootMapping?["pair"] == .sequence([.string("a"), .string("b")]))
|
|
#expect(value.rootMapping?[key] == .string("value"))
|
|
#expect(value.rootMapping?["[\"a\", \"b\"]"] == nil)
|
|
}
|
|
|
|
@Test("Parses explicit scalar keys with same-line mapping values")
|
|
func test_parsesExplicitScalarKeysWithSameLineMappingValues() throws {
|
|
let value = try PureYAML.parse("""
|
|
? __stringMin14PatternS3Captions
|
|
: minLength: 14
|
|
pattern: ^((s3://(.*?)\\.(srt|SRT))|(https?://(.*?)\\.(srt|SRT)))$
|
|
type: string
|
|
next: done
|
|
""")
|
|
let key = PureYAML.Model.Key.string("__stringMin14PatternS3Captions")
|
|
|
|
#expect(value == .mapping(.init([
|
|
.init(keyNode: key, value: .mapping(.init([
|
|
.init(key: "minLength", value: .int(14)),
|
|
.init(
|
|
key: "pattern",
|
|
value: .string("^((s3://(.*?)\\.(srt|SRT))|(https?://(.*?)\\.(srt|SRT)))$"),
|
|
),
|
|
.init(key: "type", value: .string("string")),
|
|
]))),
|
|
.init(key: "next", value: .string("done")),
|
|
])))
|
|
#expect(value.rootMapping?[key]?.rootMapping?["minLength"] == .int(14))
|
|
#expect(value.rootMapping?[key]?.rootMapping?["type"] == .string("string"))
|
|
#expect(value.rootMapping?["next"] == .string("done"))
|
|
#expect(value.rootMapping?["minLength"] == nil)
|
|
}
|
|
|
|
@Test("Expands merged mappings with complex-key local overrides")
|
|
func test_expandsMergedMappingsWithComplexKeyLocalOverrides() throws {
|
|
let value = try PureYAML.parse("""
|
|
defaults: &defaults
|
|
? [a, b]
|
|
: inherited
|
|
name: inherited-name
|
|
service:
|
|
<<: *defaults
|
|
? [a, b]
|
|
: local
|
|
""")
|
|
let service = value.rootMapping?["service"]
|
|
let key = PureYAML.Model.Key.sequence([.string("a"), .string("b")])
|
|
|
|
#expect(service == .mapping(.init([
|
|
.init(key: "name", value: .string("inherited-name")),
|
|
.init(keyNode: key, value: .string("local")),
|
|
])))
|
|
#expect(try PureYAML.validate(value, strict: false).isEmpty)
|
|
#expect(service?.rootMapping?[key] == .string("local"))
|
|
#expect(service?.rootMapping?["[\"a\", \"b\"]"] == nil)
|
|
}
|
|
|
|
@Test("Rejects a sequence dangling after a complex key mapping")
|
|
func test_rejectsSequenceDanglingAfterComplexKeyMapping() {
|
|
// `? [a, b]` is a valid explicit key with an implicit empty (null) value,
|
|
// so the mapping closes cleanly. The same-column `- invalid` then dangles
|
|
// outside any node and is rejected at the document boundary.
|
|
expectParseError("""
|
|
? [a, b]
|
|
- invalid
|
|
""", .unexpectedToken(
|
|
expected: "document boundary or stream end",
|
|
actual: "blockEntry",
|
|
line: 2,
|
|
column: 1,
|
|
))
|
|
}
|
|
}
|