PureYAML/Tests/ParsingEventTests.swift
Mihaela Mihaljevic f7500400ee feat(parsing): exact scalar end positions in the located parse
The scalar event now carries the scanner token's end mark, so a located
scalar's range spans the whole token (end exclusive, one past the last
character) instead of being start-anchored. This gives consumers exact
highlight widths and is the prerequisite for surgical, position-based
text edits. Only the scalar event case gained a field; the value tree,
its equality, and the event dump (which compares value and style, not
marks) are unchanged, so the canonical-event and whole-suite gates stay
green (343 tests).
2026-06-26 02:25:01 +02:00

224 lines
8.6 KiB
Swift

@testable import PureYAML
import Testing
@Suite("Parsing Events")
struct ParsingEventTests {
@Test("Event model represents aliases anchors tags and flow style")
func test_eventModelRepresentsAliasesAnchorsTagsAndFlowStyle() {
let mark = PureYAML.Parsing.Mark(line: 2, column: 3, index: 12)
let events: [PureYAML.Parsing.Event] = [
.scalar(value: "value", anchor: "root", tag: "!Thing", style: .singleQuoted, mark: mark, endMark: mark),
.sequenceStart(anchor: "items", tag: "!seq", style: .flow, mark: mark),
.alias(anchor: "root", mark: mark),
]
#expect(events.map(\.description) == [
"scalar value=\"value\" anchor=root tag=!Thing style=singleQuoted @2:3@12",
"sequenceStart anchor=items tag=!seq style=flow @2:3@12",
"alias anchor=root @2:3@12",
])
}
@Test("Emits golden events for mapping scalars and scalar styles")
func test_mappingScalarsAndScalarStyles() throws {
let events = try PureYAML.Parsing.Parser().parseEvents(
"""
name: "Example"
single: 'it''s'
missing:
""",
)
#expect(events.map(\.description) == [
"streamStart @1:1@0",
"documentStart explicit=false @1:1@0",
"mappingStart anchor=- tag=- style=block @1:1@0",
"scalar value=\"name\" anchor=- tag=- style=plain @1:1@0",
"scalar value=\"Example\" anchor=- tag=- style=doubleQuoted @1:7@6",
"scalar value=\"single\" anchor=- tag=- style=plain @2:1@16",
"scalar value=\"it's\" anchor=- tag=- style=singleQuoted @2:9@24",
"scalar value=\"missing\" anchor=- tag=- style=plain @3:1@32",
"scalar value=\"\" anchor=- tag=- style=plain @3:9@40",
"mappingEnd @4:1@40",
"documentEnd explicit=false @4:1@40",
"streamEnd @4:1@40",
])
}
@Test("Emits golden events for nested sequences and mappings")
func test_nestedSequencesAndMappings() throws {
let events = try PureYAML.Parsing.Parser().parseEvents(
"""
servers:
- url: /
description: Default
""",
)
#expect(events.map(\.description) == [
"streamStart @1:1@0",
"documentStart explicit=false @1:1@0",
"mappingStart anchor=- tag=- style=block @1:1@0",
"scalar value=\"servers\" anchor=- tag=- style=plain @1:1@0",
"sequenceStart anchor=- tag=- style=block @2:3@11",
"mappingStart anchor=- tag=- style=block @2:5@13",
"scalar value=\"url\" anchor=- tag=- style=plain @2:5@13",
"scalar value=\"/\" anchor=- tag=- style=plain @2:10@18",
"scalar value=\"description\" anchor=- tag=- style=plain @3:5@24",
"scalar value=\"Default\" anchor=- tag=- style=plain @3:18@37",
"mappingEnd @4:1@44",
"sequenceEnd @4:1@44",
"mappingEnd @4:1@44",
"documentEnd explicit=false @4:1@44",
"streamEnd @4:1@44",
])
#expect(!events.map(\.description).contains("scalar value=\"\" anchor=- tag=- style=plain @1:1@0"))
}
@Test("Emits golden events for flow collections aliases anchors and tags")
func test_flowCollectionsAliasesAnchorsAndTags() throws {
let events = try PureYAML.Parsing.Parser().parseEvents(
"""
flow: [!Thing &item "value", *item, {tagged: !<tag:example.com,2026:thing> 'it''s'}]
""",
)
let descriptions = events.map(\.description)
#expect(descriptions == [
"streamStart @1:1@0",
"documentStart explicit=false @1:1@0",
"mappingStart anchor=- tag=- style=block @1:1@0",
"scalar value=\"flow\" anchor=- tag=- style=plain @1:1@0",
"sequenceStart anchor=- tag=- style=flow @1:7@6",
"scalar value=\"value\" anchor=item tag=!Thing style=doubleQuoted @1:8@7",
"alias anchor=item @1:30@29",
"mappingStart anchor=- tag=- style=flow @1:37@36",
"scalar value=\"tagged\" anchor=- tag=- style=plain @1:38@37",
"scalar value=\"it's\" anchor=- tag=tag:example.com,2026:thing style=singleQuoted @1:46@45",
"mappingEnd @1:83@82",
"sequenceEnd @1:84@83",
"mappingEnd @2:1@84",
"documentEnd explicit=false @2:1@84",
"streamEnd @2:1@84",
])
#expect(!descriptions.contains("sequenceStart anchor=- tag=- style=block @1:7@6"))
#expect(!descriptions.contains { $0.contains("it''s") })
}
@Test("Parses JSON-style flow mapping pairs")
func test_jsonStyleFlowMappingPairs() throws {
let root = try requireMapping(PureYAML.parse(
"""
value:
{
"version" : "7",
"fields" : [
{
"key" : "productType",
"required" : true
}
]
}
""",
))
let value = root?.mapping("value")
let field = value?.sequence("fields")?.first?.mapping
#expect(value?["version"] == .string("7"))
#expect(field?["key"] == .string("productType"))
#expect(field?["required"] == .bool(true))
#expect(value?["missing"] == nil)
}
@Test("Emits golden events for block scalar headers")
func test_blockScalarHeaders() throws {
let events = try PureYAML.Parsing.Parser().parseEvents(
"""
text: |
one
two
stripped: |-
one
two
folded: >
one
two
""",
)
let descriptions = events.map(\.description)
#expect(descriptions == [
"streamStart @1:1@0",
"documentStart explicit=false @1:1@0",
"mappingStart anchor=- tag=- style=block @1:1@0",
"scalar value=\"text\" anchor=- tag=- style=plain @1:1@0",
"scalar value=\"one\\ntwo\\n\" anchor=- tag=- style=literal @1:7@6",
"scalar value=\"stripped\" anchor=- tag=- style=plain @4:1@20",
"scalar value=\"one\\ntwo\" anchor=- tag=- style=literal @4:11@30",
"scalar value=\"folded\" anchor=- tag=- style=plain @7:1@45",
"scalar value=\"one two\\n\" anchor=- tag=- style=folded @7:9@53",
"mappingEnd @10:1@66",
"documentEnd explicit=false @10:1@66",
"streamEnd @10:1@66",
])
#expect(!descriptions.contains { $0.contains("one\\ntwo\\n") && $0.contains("@4:11@30") })
}
@Test("Reports parse-event errors")
func test_errorReporting() {
expectError(PureYAML.Parsing.ParseError.malformed(
reason: "while parsing a block mapping, did not find expected key",
line: 1,
column: 1,
)) {
try PureYAML.Parsing.Parser().parseEvents("root:\n child: yes\n wrong: no")
}
expectError(PureYAML.Parsing.ParseError.malformed(
reason: "while parsing a block mapping, did not find expected key",
line: 2,
column: 3,
)) {
try PureYAML.Parsing.Parser().parseEvents("root:\n child: value\n dangling")
}
expectParseEventError(
"items: [one, two",
PureYAML.Parsing.ParseError.malformed(
reason: "while parsing a flow sequence, did not find expected ',' or ']'",
line: 1,
column: 8,
),
description: "while parsing a flow sequence, did not find expected ',' or ']' at line 1, column 8",
)
}
@Test("Emits no document for empty input")
func test_emptyInputEmitsNoDocument() throws {
// YAML 1.2: a stream with no content (empty or comment-only) has zero
// documents, matching libyaml and the test suite. The value API maps this
// empty stream to a single null value separately.
let events = try PureYAML.Parsing.Parser().parseEvents("")
#expect(events.map(\.description) == [
"streamStart @1:1@0",
"streamEnd @1:1@0",
])
}
}
private func expectParseEventError(
_ yaml: String,
_ expected: PureYAML.Parsing.ParseError,
description: String,
) {
do {
_ = try PureYAML.Parsing.Parser().parseEvents(yaml)
recordIssue("expected error \(expected)")
} catch let error as PureYAML.Parsing.ParseError {
#expect(error == expected)
#expect(error.description == description)
} catch {
recordIssue("unexpected error \(error)")
}
}