fix(parsing): accept a higher-minor %YAML version
The %YAML version check accepted only 1.1 and 1.2, rejecting anything else, so `%YAML 1.3` errored. The spec calls for processing a document whose minor version is higher than the processor's, with a warning, rather than refusing it: only a different major version is incompatible. Accept any well-formed `1.minor` version and reject only a different major (`2.0`) or a malformed version. Three tests that asserted `%YAML 1.3` was an error are corrected to use `2.0`, the genuinely incompatible case, and a test covers that 1.3 now parses. Raises yaml-test-suite parse-level conformance to 305/333 (91.6%) with the value-level (100%) and should-error rejection rates unchanged. 486 tests pass.
This commit is contained in:
parent
4b06f6e5fc
commit
7879818ce5
@ -84,6 +84,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A `%YAML` directive with a higher minor version than 1.2 is now accepted rather than rejected. The
|
||||
spec calls for processing a document with a higher minor version, with a warning, so `%YAML 1.3` is
|
||||
read as a YAML 1.x document instead of erroring. The version check now accepts any well-formed
|
||||
`1.minor` version and rejects only a different major version (`2.0`) or a malformed one. Three tests
|
||||
that asserted `%YAML 1.3` was an error were corrected to use `2.0`. Raises yaml-test-suite parse-level
|
||||
conformance to 305/333 (91.6%), with the should-error rate unchanged.
|
||||
|
||||
- A plain scalar that is an inline block sequence item value (`: - b`) now stops folding at a later
|
||||
block entry that returns to the sequence's own indent, so the dashes form the rest of the sequence
|
||||
rather than folding into the first item. A block entry only recorded its inline node's indent as a
|
||||
|
||||
@ -102,7 +102,10 @@ extension PureYAML.Parsing.Scanner {
|
||||
state.pendingDirectiveLine = start.line
|
||||
switch name {
|
||||
case "%YAML":
|
||||
guard parts.indices.contains(1), parts[1] == "1.1" || parts[1] == "1.2" else {
|
||||
// Any version with major 1 is processable: a higher minor than 1.2 (`%YAML 1.3`) is
|
||||
// accepted, since the spec calls for processing it, with a warning, rather than rejecting
|
||||
// it. Only a different major version, or a malformed one, is incompatible.
|
||||
guard parts.indices.contains(1), isProcessableYAMLVersion(parts[1]) else {
|
||||
throw PureYAML.Parsing.ParseError.incompatibleYAMLDirective(line: start.line)
|
||||
}
|
||||
// `%YAML` takes exactly one parameter, the version; only a comment may follow it.
|
||||
@ -126,4 +129,14 @@ extension PureYAML.Parsing.Scanner {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a `%YAML` version is one this processor can attempt: any `major.minor` with major 1,
|
||||
/// so 1.0 through 1.2 and a higher minor like 1.3 are all accepted; a different major (2.0) or a
|
||||
/// non-numeric version is not.
|
||||
func isProcessableYAMLVersion(_ version: String) -> Bool {
|
||||
let components = version.split(separator: ".", omittingEmptySubsequences: false)
|
||||
return components.count == 2
|
||||
&& components[0] == "1"
|
||||
&& components.allSatisfy { !$0.isEmpty && $0.allSatisfy(\.isNumber) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ struct ParsingErrorTests {
|
||||
expectParseError("name: \"open", .unterminatedQuotedString(line: 1))
|
||||
expectParseError("name: 'open", .unterminatedQuotedString(line: 1))
|
||||
expectParseError("second: *missing", .undefinedAlias(anchor: "missing", line: 1, column: 9))
|
||||
expectParseError("%YAML 1.3\n---\nvalue: yes", .incompatibleYAMLDirective(line: 1))
|
||||
expectParseError("%YAML 2.0\n---\nvalue: yes", .incompatibleYAMLDirective(line: 1))
|
||||
expectParseError("value: !!int nope", .invalidTaggedScalar(
|
||||
tag: "tag:yaml.org,2002:int",
|
||||
value: "nope",
|
||||
|
||||
@ -264,7 +264,7 @@ struct ScannerTests {
|
||||
expectScannerError("anchor: &", .expectedAnchorName(line: 1))
|
||||
expectScannerError("alias: *", .expectedAliasName(line: 1))
|
||||
expectScannerError("tag: !<tag:example.com,2026:thing", .unterminatedTag(line: 1))
|
||||
expectScannerError("%YAML 1.3\n---\nvalue: yes", .incompatibleYAMLDirective(line: 1))
|
||||
expectScannerError("%YAML 2.0\n---\nvalue: yes", .incompatibleYAMLDirective(line: 1))
|
||||
// An unrecognized directive before the document is ignored, but one after content (where a
|
||||
// directive cannot appear) is still rejected.
|
||||
expectScannerError("value: yes\n%FOO bar", .unsupportedDirective(name: "%FOO", line: 2))
|
||||
|
||||
@ -6,7 +6,7 @@ struct YAMLDirectiveTests {
|
||||
@Test("A malformed or repeated %YAML directive is rejected", arguments: [
|
||||
"%YAML 1.2 foo\n---\nx\n", // an extra parameter after the version
|
||||
"%YAML 1.2\n%YAML 1.2\n---\nx\n", // a second %YAML directive
|
||||
"%YAML 1.3\n---\nx\n", // an unsupported version
|
||||
"%YAML 2.0\n---\nx\n", // an incompatible major version
|
||||
])
|
||||
func test_invalidYAMLDirectiveIsRejected(_ yaml: String) throws {
|
||||
#expect(throws: PureYAML.Parsing.ParseError.self) {
|
||||
@ -14,6 +14,11 @@ struct YAMLDirectiveTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test("A higher minor version is accepted, since the spec calls for processing it with a warning")
|
||||
func test_higherMinorVersionAccepted() throws {
|
||||
#expect(try PureYAML.parse("%YAML 1.3\n---\nx\n") == .string("x"))
|
||||
}
|
||||
|
||||
@Test("A well-formed %YAML directive, with an optional trailing comment, is accepted")
|
||||
func test_validYAMLDirectiveAccepted() throws {
|
||||
let expected = PureYAML.Model.Value.string("x")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user