fix(parser): match block continuation parsing
This commit is contained in:
parent
09b7f52cbc
commit
37239fe613
@ -7,7 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
Nothing yet.
|
||||
### Fixed
|
||||
|
||||
- Treat more-indented plain scalar continuation lines that start with `-` as
|
||||
text unless they appear at a known sequence-entry indentation. This fixes
|
||||
OpenAPI-style `allOf` descriptions while preserving nested block sequences
|
||||
such as `- - 123`.
|
||||
- Parse explicit scalar keys whose `:` value starts a same-line mapping and
|
||||
continues with indented mapping siblings, matching large OpenAPI schema keys.
|
||||
|
||||
## [0.1.0] - 2026-06-05
|
||||
|
||||
|
||||
@ -138,14 +138,14 @@ extension PureYAML.Parsing.Scanner {
|
||||
guard !probe.isAtEnd, !isLineBreak(probe.peek()), probe.peek() != "#" else {
|
||||
return value
|
||||
}
|
||||
guard !isBlockEntry(probe) else {
|
||||
return value
|
||||
}
|
||||
|
||||
let baseIndentation = plainScalarContinuationBaseIndentation(state: state)
|
||||
guard indentationWidth > baseIndentation else {
|
||||
return value
|
||||
}
|
||||
if isBlockEntry(probe), state.validImplicitIndentation.contains(indentationWidth) {
|
||||
return value
|
||||
}
|
||||
|
||||
var continuationState = state
|
||||
continuationState.reader = probe
|
||||
|
||||
@ -30,6 +30,13 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
if properties == .none, canParsePlainScalarWithContinuation(after: valueIndicator) {
|
||||
return try parsePlainScalarWithContinuation(properties: properties)
|
||||
}
|
||||
if cursor.current?.kind.isMappingKey == true, hasNodeOnSameLine(after: valueIndicator) {
|
||||
return try parseBlockMapping(
|
||||
properties: properties,
|
||||
includeIndentedContinuation: true,
|
||||
minimumColumn: cursor.current?.mark.column,
|
||||
)
|
||||
}
|
||||
if properties != .none || hasNodeOnSameLine(after: valueIndicator) {
|
||||
return try parseNode(properties: properties)
|
||||
}
|
||||
|
||||
@ -166,6 +166,39 @@ enum CollectionCompatibilityFixtures {
|
||||
]))),
|
||||
])),
|
||||
),
|
||||
SuccessCase(
|
||||
name: "mapping value with indentless sequence of inline mappings",
|
||||
yaml: """
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ErrorCategory'
|
||||
- description: An integer value that specifies the category of a query
|
||||
failure error. Values include 1 - System, 2 - User, and 3
|
||||
- Other.
|
||||
""",
|
||||
expected: .mapping(.init([
|
||||
.init(key: "schema", value: .mapping(.init([
|
||||
.init(key: "allOf", value: .sequence([
|
||||
.mapping(.init([
|
||||
.init(
|
||||
key: "$ref",
|
||||
value: .string("#/components/schemas/ErrorCategory"),
|
||||
),
|
||||
])),
|
||||
.mapping(.init([
|
||||
.init(
|
||||
key: "description",
|
||||
value: .string(
|
||||
"An integer value that specifies the category of a query "
|
||||
+ "failure error. Values include 1 - System, 2 - User, and 3 "
|
||||
+ "- Other.",
|
||||
),
|
||||
),
|
||||
])),
|
||||
])),
|
||||
]))),
|
||||
])),
|
||||
),
|
||||
]
|
||||
|
||||
static let parseErrors: [ParseErrorCase] = [
|
||||
|
||||
@ -84,6 +84,34 @@ struct ComplexMappingKeyParsingTests {
|
||||
#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("""
|
||||
|
||||
@ -43,3 +43,24 @@ Every compatibility test must pin at least one of:
|
||||
- exact validation issue;
|
||||
- exact path-aware decoding or encoding error;
|
||||
- exact required absence check for fields that must not appear.
|
||||
|
||||
## Parser Compatibility Lessons
|
||||
|
||||
YAML compatibility is parser-state compatibility, not only value-model
|
||||
compatibility. Indicator characters such as `-`, `?`, and `:` are meaningful
|
||||
only after indentation, flow depth, and the current parser production are taken
|
||||
into account.
|
||||
|
||||
When fixing scanner or parser behavior:
|
||||
|
||||
- preserve tests for the exact production corpus shape that failed, not only a
|
||||
smaller feature in isolation;
|
||||
- combine interacting YAML features in one regression fixture when the bug
|
||||
requires their interaction;
|
||||
- keep implementation fixes grammar-level and content-independent; never branch
|
||||
on corpus file names, OpenAPI field names, vendor names, schema prefixes, or
|
||||
other production-data fingerprints;
|
||||
- for plain scalar continuation bugs, cover both sides explicitly:
|
||||
- a more-indented continuation line that starts with an indicator but remains
|
||||
text;
|
||||
- a same-context indicator line that must remain a collection item.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user