A tag shorthand using a named handle (`!handle!suffix`) must be declared by a `%TAG` directive in the document that uses it; the handle does not carry across documents (YAML 1.2). The parser was permissive but silent here, so an undeclared handle passed unnoticed. - Scope tag handles per document: a `%TAG` handle now applies only to the document it precedes (merged onto the defaults at the document start, cleared at the boundary) instead of accumulating across the whole stream. A directive repeated for each document keeps working, as the suite requires. - Detect the named-handle form before the primary-handle fallback (which previously masked `!prefix!x` as a local tag) and record an undeclared handle. The parse stays permissive: it still yields a value, and `PureYAML.validationReport` surfaces the issue as an `undefinedTagHandle` error. The check reads the scanner's tokens, so it is exact: a `!x!` inside a scalar or comment is never flagged. Extracts `Scanner.State` and the tag helpers into their own files to keep each source file within the 400-line limit. Adds focused tests; the full suite and strict lint stay green.
56 lines
2.3 KiB
Swift
56 lines
2.3 KiB
Swift
@testable import PureYAML
|
|
import Testing
|
|
|
|
/// A tag shorthand using a named handle (`!handle!suffix`) must be declared by a `%TAG` directive
|
|
/// in the document that uses it; the handle does not carry across documents. The parser stays
|
|
/// permissive (it still produces a value), so the violation surfaces as a diagnostic, not a parse
|
|
/// failure. These prove the diagnostic is exact: it fires for an undeclared handle and stays silent
|
|
/// for a declared one, a local tag, the secondary handle, and a `!x!` that is only scalar content.
|
|
@Suite("Undefined tag handle")
|
|
struct UndefinedTagHandleTests {
|
|
private func codes(_ yaml: String) -> [String] {
|
|
PureYAML.validationReport(yaml).diagnostics.compactMap(\.code)
|
|
}
|
|
|
|
@Test("A handle not redeclared in a later document is flagged there, not in the declaring one")
|
|
func test_handleScopeIsPerDocument() {
|
|
let yaml = """
|
|
%TAG !prefix! tag:example.com,2011:
|
|
--- !prefix!A
|
|
a: b
|
|
--- !prefix!B
|
|
c: d
|
|
"""
|
|
let report = PureYAML.validationReport(yaml)
|
|
let undefined = report.diagnostics.filter { $0.code == "undefinedTagHandle" }
|
|
// The first document declared the handle; only the second, which did not, is flagged.
|
|
#expect(undefined.count == 1)
|
|
#expect(undefined.first?.line == 4)
|
|
}
|
|
|
|
@Test("A handle redeclared for each document is accepted with no diagnostic")
|
|
func test_redeclaredHandleIsClean() {
|
|
let yaml = """
|
|
%TAG !m! !my-
|
|
--- !m!light fluorescent
|
|
...
|
|
%TAG !m! !my-
|
|
--- !m!light green
|
|
"""
|
|
#expect(!codes(yaml).contains("undefinedTagHandle"))
|
|
}
|
|
|
|
@Test("A local tag, the secondary handle, and a verbatim tag are not handle references")
|
|
func test_nonHandleTagsAreClean() {
|
|
#expect(!codes("a: !foo bar").contains("undefinedTagHandle"))
|
|
#expect(!codes("a: !!str x").contains("undefinedTagHandle"))
|
|
#expect(!codes("a: !<tag:example.com,2011:x> y").contains("undefinedTagHandle"))
|
|
}
|
|
|
|
@Test("A bang pair inside scalar content is not a tag and is never flagged")
|
|
func test_noFalsePositiveInScalarContent() {
|
|
#expect(!codes("msg: hello !world! bye").contains("undefinedTagHandle"))
|
|
#expect(!codes("quoted: \"a !x! b\"").contains("undefinedTagHandle"))
|
|
}
|
|
}
|