PureYAML/Tests/LibYAMLSimpleKeyTests.swift
Mihaela Mihaljevic c236945323 fix(parsing): reject a multi-line implicit key in a flow sequence
A flow-sequence single-pair implicit key is single-line
(c-s-implicit-json-key uses s-separate-in-line), so its ':' may not cross
a line break: '[ "key"\n :value ]' is malformed. A flow MAPPING adjacent
value may still span lines ('{ "foo"\n :bar }' stays valid), which is why
the check is scoped to flow levels that are sequences.

Track per-flow-level whether it is a sequence ('[' vs '{') in a
flowIsSequence stack pushed/popped with the flow level, and in fetchValue
reject a pending simple key whose ':' crossed a line break when the
current flow level is a sequence.

Surpasses libyaml on ZXT5 (5MUD, the flow-mapping analogue, stays valid);
whole-suite conformance 397/402, canonical events 308/308.
2026-06-24 21:50:41 +02:00

68 lines
2.9 KiB
Swift

@testable import PureYAML
import Testing
/// Verifies the ported simple-key mechanism (`save_simple_key`,
/// `remove_simple_key`, `stale_simple_keys`, flow-level push/pop) reproduces
/// libyaml's behavior.
@Suite("LibYAML Simple Key")
struct LibYAMLSimpleKeyTests {
private func mark(line: Int, column: Int, index: Int) -> PureYAML.Parsing.Mark {
PureYAML.Parsing.Mark(line: line, column: column, index: index)
}
@Test("saves a possible key, required when it sits at the block indent")
func test_saveSimpleKeyRequiredAtIndent() throws {
var scanner = PureYAML.Parsing.LibYAML.Scanner()
scanner.indentEngine.rollIndent(column: 0, number: -1, type: .blockMappingStart, mark: .start)
scanner.mark = mark(line: 1, column: 0, index: 0)
try scanner.saveSimpleKey()
#expect(scanner.simpleKeys.last?.possible == true)
#expect(scanner.simpleKeys.last?.required == true)
}
@Test("a key not at the block indent is possible but not required")
func test_saveSimpleKeyNotRequired() throws {
var scanner = PureYAML.Parsing.LibYAML.Scanner()
scanner.indentEngine.rollIndent(column: 0, number: -1, type: .blockMappingStart, mark: .start)
scanner.mark = mark(line: 1, column: 4, index: 4)
try scanner.saveSimpleKey()
#expect(scanner.simpleKeys.last?.possible == true)
#expect(scanner.simpleKeys.last?.required == false)
}
@Test("removing a required possible key is an error")
func test_removeRequiredKeyThrows() throws {
var scanner = PureYAML.Parsing.LibYAML.Scanner()
scanner.indentEngine.rollIndent(column: 0, number: -1, type: .blockMappingStart, mark: .start)
scanner.mark = mark(line: 1, column: 0, index: 0)
try scanner.saveSimpleKey()
#expect(throws: PureYAML.Parsing.LibYAML.ScannerError.self) {
try scanner.removeSimpleKey()
}
}
@Test("a non-required key on an earlier line goes stale without error")
func test_staleNonRequiredKey() throws {
var scanner = PureYAML.Parsing.LibYAML.Scanner()
scanner.indentEngine.rollIndent(column: 0, number: -1, type: .blockMappingStart, mark: .start)
scanner.mark = mark(line: 1, column: 4, index: 4)
try scanner.saveSimpleKey()
scanner.mark = mark(line: 2, column: 0, index: 10)
try scanner.staleSimpleKeys()
#expect(scanner.simpleKeys.last?.possible == false)
}
@Test("flow level pushes and pops a simple-key slot")
func test_flowLevelStack() {
var scanner = PureYAML.Parsing.LibYAML.Scanner()
#expect(scanner.simpleKeys.count == 1)
scanner.increaseFlowLevel(isSequence: true)
#expect(scanner.flowLevel == 1)
#expect(scanner.simpleKeys.count == 2)
#expect(scanner.flowIsSequence == [true])
scanner.decreaseFlowLevel()
#expect(scanner.flowLevel == 0)
#expect(scanner.simpleKeys.count == 1)
}
}