diff --git a/CHANGELOG.md b/CHANGELOG.md index 2821960..c87fd02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Sources/Parsing/Scanner.Directives.swift b/Sources/Parsing/Scanner.Directives.swift index 187d23c..c752f37 100644 --- a/Sources/Parsing/Scanner.Directives.swift +++ b/Sources/Parsing/Scanner.Directives.swift @@ -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) } + } } diff --git a/Tests/ParsingErrorTests.swift b/Tests/ParsingErrorTests.swift index 8718665..6c00953 100644 --- a/Tests/ParsingErrorTests.swift +++ b/Tests/ParsingErrorTests.swift @@ -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", diff --git a/Tests/ScannerTests.swift b/Tests/ScannerTests.swift index 1033baf..2df6d5d 100644 --- a/Tests/ScannerTests.swift +++ b/Tests/ScannerTests.swift @@ -264,7 +264,7 @@ struct ScannerTests { expectScannerError("anchor: &", .expectedAnchorName(line: 1)) expectScannerError("alias: *", .expectedAliasName(line: 1)) expectScannerError("tag: !