feat(schema): locate structure-pass schema diagnostics
Convert the seven structure-pass compile checkers to located findings so each error attaches to its own declaring node instead of the schema root (#169): a facet inapplicable to a list/union variety, a restriction of xs:anySimpleType and a constraining facet on anySimpleType-based simple content, a top-level definition missing its name or carrying a local-only attribute, a nested named definition, an empty targetNamespace or import namespace, and an all-group reference with an illegal minOccurs/maxOccurs. The rewire in structureFindings preserves the prior finding order and count; each error predicate and reason string is unchanged, only the location each finding carries differs. Adds a located-diagnostics test per checker family in a new suite kept within the type-body budget.
This commit is contained in:
parent
d53cdaad1c
commit
3a56d9f400
@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Schema compile diagnostics are now located: `SchemaError.inconsistent` carries `[ValidationError]` with coding paths that resolve to `TreeNode.sourceRange` on a ranged schema tree (#169). Parse-time facet and structure findings attach to their declaring nodes where possible; `SchemaError.inconsistentFindings` exposes the full list. Schematron reuses the shared `validationCodingPath()` helper. Value-constraint errors now locate to their declaring element or attribute: a `default`/`fixed` on an ID-derived type, a value that is invalid for its built-in, named user, or inline type, and an illegal combination of value constraints each underline the offending declaration rather than the schema root.
|
||||
- Schema compile diagnostics are now located: `SchemaError.inconsistent` carries `[ValidationError]` with coding paths that resolve to `TreeNode.sourceRange` on a ranged schema tree (#169). Parse-time facet and structure findings attach to their declaring nodes where possible; `SchemaError.inconsistentFindings` exposes the full list. Schematron reuses the shared `validationCodingPath()` helper. Value-constraint errors now locate to their declaring element or attribute: a `default`/`fixed` on an ID-derived type, a value that is invalid for its built-in, named user, or inline type, and an illegal combination of value constraints each underline the offending declaration rather than the schema root. The structure-pass checkers are likewise located: a facet inapplicable to a list or union variety, a derivation by restriction of `xs:anySimpleType` and a constraining facet on `anySimpleType`-based simple content, a top-level definition missing its name or carrying a local-only attribute, a nested named definition, an empty `targetNamespace` or `import` namespace, and an all-group reference with an illegal `minOccurs`/`maxOccurs` each underline their own node instead of the schema root.
|
||||
|
||||
- DTD parse-time validity findings are warnings until strict validation promotes them; `validateAgainstInternalDTD` returns full findings (errors and warnings).
|
||||
|
||||
|
||||
@ -121,14 +121,14 @@ extension PureXML.Schema.XSDParser {
|
||||
/// reference). The per-component applicability table is the permissive
|
||||
/// global/local union and cannot make this distinction; this scan supplies the
|
||||
/// global-only constraint.
|
||||
static func topLevelDeclarationErrors(_ schema: XSDTree) -> [String] {
|
||||
var errors: [String] = []
|
||||
collectTopLevelDeclarationErrors(in: schema, into: &errors)
|
||||
static func topLevelDeclarationFindings(_ schema: XSDTree) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
collectTopLevelDeclarationFindings(in: schema, into: &findings)
|
||||
for redefine in PureXML.Schema.XSDNode.elementChildren(schema) {
|
||||
guard PureXML.Schema.XSDNode.localName(redefine) == "redefine" else { continue }
|
||||
collectTopLevelDeclarationErrors(in: redefine, into: &errors)
|
||||
collectTopLevelDeclarationFindings(in: redefine, into: &findings)
|
||||
}
|
||||
return errors
|
||||
return findings
|
||||
}
|
||||
|
||||
private static let topLevelForbidden: [String: [String]] = [
|
||||
@ -142,17 +142,23 @@ extension PureXML.Schema.XSDParser {
|
||||
"simpleType", "complexType", "group", "attributeGroup", "element", "attribute", "notation",
|
||||
]
|
||||
|
||||
private static func collectTopLevelDeclarationErrors(in container: XSDTree, into errors: inout [String]) {
|
||||
private static func collectTopLevelDeclarationFindings(in container: XSDTree, into findings: inout [PureXML.Schema.SchemaLocatedFinding]) {
|
||||
for child in PureXML.Schema.XSDNode.elementChildren(container) {
|
||||
guard child.name?.namespaceURI == xsdNamespace,
|
||||
let kind = PureXML.Schema.XSDNode.localName(child)
|
||||
else { continue }
|
||||
if namedTopLevelKinds.contains(kind), !hasUnprefixed(child, "name") {
|
||||
errors.append("a top-level '\(kind)' definition must have a 'name' attribute")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "a top-level '\(kind)' definition must have a 'name' attribute",
|
||||
node: child,
|
||||
))
|
||||
}
|
||||
if let forbidden = topLevelForbidden[kind] {
|
||||
for attribute in forbidden where hasUnprefixed(child, attribute) {
|
||||
errors.append("a top-level '\(kind)' declaration may not specify '\(attribute)'")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "a top-level '\(kind)' declaration may not specify '\(attribute)'",
|
||||
node: child,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,10 +9,10 @@ extension PureXML.Schema.XSDParser {
|
||||
/// The base's variety is resolved from the raw tree, since this runs before the
|
||||
/// types are compiled; an atomic, unresolvable, or external base is skipped, so
|
||||
/// no valid restriction is rejected.
|
||||
static func simpleTypeVarietyFacetErrors(_ schema: XSDTree) -> [String] {
|
||||
static func simpleTypeVarietyFacetFindings(_ schema: XSDTree) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
let bindings = namespaceBindings(schema)
|
||||
let target = PureXML.Schema.XSDNode.attribute(schema, "targetNamespace")
|
||||
var errors: [String] = []
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
forEachSimpleType(schema) { simpleType in
|
||||
guard let restriction = PureXML.Schema.XSDNode.firstChild(simpleType, named: "restriction"),
|
||||
let variety = restrictionBaseVariety(restriction, schema, bindings, target, [])
|
||||
@ -22,10 +22,13 @@ extension PureXML.Schema.XSDParser {
|
||||
.compactMap(PureXML.Schema.XSDNode.localName)
|
||||
.filter { facetNames.contains($0) }
|
||||
for facet in used where !allowed.contains(facet) {
|
||||
errors.append("the facet '\(facet)' does not apply to a \(variety) type")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "the facet '\(facet)' does not apply to a \(variety) type",
|
||||
node: simpleType,
|
||||
))
|
||||
}
|
||||
}
|
||||
return errors
|
||||
return findings
|
||||
}
|
||||
|
||||
/// A `default`/`fixed` value on an `element` or `attribute` must be a valid value
|
||||
@ -289,22 +292,25 @@ extension PureXML.Schema.XSDParser {
|
||||
|
||||
/// Reject any restriction (in the XSD namespace) whose `base`
|
||||
/// attribute resolves to `xs:anySimpleType` in the XSD namespace when defined directly under a simpleType.
|
||||
static func anySimpleTypeRestrictionErrors(_ schema: XSDTree) -> [String] {
|
||||
static func anySimpleTypeRestrictionFindings(_ schema: XSDTree) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
let targetNamespace = schema.attributes.first { $0.name.prefix == nil && $0.name.localName == "targetNamespace" }?.value ?? ""
|
||||
if targetNamespace == xsdNamespace {
|
||||
return []
|
||||
}
|
||||
let bindings = namespaceBindings(schema)
|
||||
var errors: [String] = []
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
forEachRestrictionInSimpleType(schema) { node in
|
||||
guard let base = PureXML.Schema.XSDNode.attribute(node, "base") else { return }
|
||||
let (prefix, local) = splitQName(base)
|
||||
let uri = prefix.map { bindings[$0] } ?? bindings[""]
|
||||
if uri == xsdNamespace, local == "anySimpleType" {
|
||||
errors.append("derivation by restriction of 'xs:anySimpleType' is not allowed")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "derivation by restriction of 'xs:anySimpleType' is not allowed",
|
||||
node: node,
|
||||
))
|
||||
}
|
||||
}
|
||||
return errors
|
||||
return findings
|
||||
}
|
||||
|
||||
private static func forEachRestrictionInSimpleType(_ node: XSDTree, _ visit: (XSDTree) -> Void) {
|
||||
@ -330,19 +336,22 @@ extension PureXML.Schema.XSDParser {
|
||||
/// built-in other than anySimpleType, a simple-type, or external/unresolved is left
|
||||
/// alone, so no valid facet is rejected. A restriction supplying its own nested
|
||||
/// `simpleType` base is also skipped (the facet applies to that, not anySimpleType).
|
||||
static func anySimpleTypeFacetErrors(_ schema: XSDTree) -> [String] {
|
||||
static func anySimpleTypeFacetFindings(_ schema: XSDTree) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
let bindings = namespaceBindings(schema)
|
||||
let target = PureXML.Schema.XSDNode.attribute(schema, "targetNamespace")
|
||||
var errors: [String] = []
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
forEachSimpleContentRestriction(schema) { restriction in
|
||||
guard PureXML.Schema.XSDNode.firstChild(restriction, named: "simpleType") == nil,
|
||||
let base = PureXML.Schema.XSDNode.attribute(restriction, "base"),
|
||||
hasConstrainingFacet(restriction),
|
||||
simpleContentBaseIsAnySimpleType(base, schema, bindings, target, [])
|
||||
else { return }
|
||||
errors.append("a constraining facet may not be applied to anySimpleType-based simpleContent")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "a constraining facet may not be applied to anySimpleType-based simpleContent",
|
||||
node: restriction,
|
||||
))
|
||||
}
|
||||
return errors
|
||||
return findings
|
||||
}
|
||||
|
||||
private static func hasConstrainingFacet(_ restriction: XSDTree) -> Bool {
|
||||
|
||||
@ -11,23 +11,26 @@ extension PureXML.Schema.XSDParser {
|
||||
///
|
||||
/// "Top-level" means a direct child of `schema` or of a `redefine`; everything
|
||||
/// deeper is nested.
|
||||
static func nestedNamedDefinitionErrors(_ schema: XSDTree) -> [String] {
|
||||
var errors: [String] = []
|
||||
collectNestedNamed(schema, parentIsTopLevel: false, into: &errors)
|
||||
return errors
|
||||
static func nestedNamedDefinitionFindings(_ schema: XSDTree) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
collectNestedNamed(schema, parentIsTopLevel: false, into: &findings)
|
||||
return findings
|
||||
}
|
||||
|
||||
private static func collectNestedNamed(_ node: XSDTree, parentIsTopLevel: Bool, into errors: inout [String]) {
|
||||
private static func collectNestedNamed(_ node: XSDTree, parentIsTopLevel: Bool, into findings: inout [PureXML.Schema.SchemaLocatedFinding]) {
|
||||
let kind = NestedNode.localName(node)
|
||||
// Foreign content inside an annotation is not the schema's own and is not
|
||||
// structurally checked.
|
||||
if kind == "appinfo" || kind == "documentation" { return }
|
||||
if isNestedNamedDefinition(node, kind, parentIsTopLevel: parentIsTopLevel) {
|
||||
errors.append("a nested '\(kind ?? "")' definition may not have a 'name'; only a top-level definition is named")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "a nested '\(kind ?? "")' definition may not have a 'name'; only a top-level definition is named",
|
||||
node: node,
|
||||
))
|
||||
}
|
||||
let childrenAreTopLevel = kind == "schema" || kind == "redefine"
|
||||
for child in NestedNode.elementChildren(node) {
|
||||
collectNestedNamed(child, parentIsTopLevel: childrenAreTopLevel, into: &errors)
|
||||
collectNestedNamed(child, parentIsTopLevel: childrenAreTopLevel, into: &findings)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -61,12 +61,12 @@ extension PureXML.Schema.XSDParser {
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
let bindings = PureXML.Schema.XSDNode.namespaceBindings(of: schema)
|
||||
collectStructure(schema, bindings: bindings, into: &findings)
|
||||
append(simpleTypeVarietyFacetErrors(schema), at: schema, into: &findings)
|
||||
findings += simpleTypeVarietyFacetFindings(schema)
|
||||
findings += valueConstraintFindings(schema)
|
||||
append(topLevelDeclarationErrors(schema), at: schema, into: &findings)
|
||||
append(nestedNamedDefinitionErrors(schema), at: schema, into: &findings)
|
||||
append(anySimpleTypeRestrictionErrors(schema) + anySimpleTypeFacetErrors(schema), at: schema, into: &findings)
|
||||
append(emptyNamespaceErrors(schema) + allGroupReferenceMaxOccursErrors(schema), at: schema, into: &findings)
|
||||
findings += topLevelDeclarationFindings(schema)
|
||||
findings += nestedNamedDefinitionFindings(schema)
|
||||
findings += anySimpleTypeRestrictionFindings(schema) + anySimpleTypeFacetFindings(schema)
|
||||
findings += emptyNamespaceFindings(schema) + allGroupReferenceMaxOccursFindings(schema)
|
||||
return findings
|
||||
}
|
||||
|
||||
|
||||
@ -80,7 +80,7 @@ extension PureXML.Schema.XSDParser {
|
||||
/// is itself an all-group particle, so its `maxOccurs` must be 1 and its
|
||||
/// `minOccurs` 0 or 1. ``allGroupLimitedErrors`` covers the direct `<all>` element;
|
||||
/// this covers a `<group ref>` resolving to an all group (corpus particlesEa025).
|
||||
static func allGroupReferenceMaxOccursErrors(_ schema: XSDTree) -> [String] {
|
||||
static func allGroupReferenceMaxOccursFindings(_ schema: XSDTree) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
let xsd = PureXML.Schema.XSDParser.xsdNamespace
|
||||
var allGroupNames: Set<String> = []
|
||||
for group in descendants(schema, named: "group") where group.name?.namespaceURI == xsd {
|
||||
@ -91,21 +91,27 @@ extension PureXML.Schema.XSDParser {
|
||||
if isAll { allGroupNames.insert(name) }
|
||||
}
|
||||
guard !allGroupNames.isEmpty else { return [] }
|
||||
var errors: [String] = []
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
for group in descendants(schema, named: "group") where group.name?.namespaceURI == xsd {
|
||||
guard let ref = PureXML.Schema.XSDNode.attribute(group, "ref"),
|
||||
allGroupNames.contains(PureXML.Schema.XSDNode.stripPrefix(ref))
|
||||
else { continue }
|
||||
let maxOccurs = PureXML.Schema.XSDNode.attribute(group, "maxOccurs")?.trimmingXMLWhitespace()
|
||||
if let maxOccurs, maxOccurs == "unbounded" || (isNonNegativeInteger(maxOccurs) && canonicalMagnitude(maxOccurs) != "1") {
|
||||
errors.append("the maxOccurs of a reference to an all group must be 1")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "the maxOccurs of a reference to an all group must be 1",
|
||||
node: group,
|
||||
))
|
||||
}
|
||||
let minOccurs = PureXML.Schema.XSDNode.attribute(group, "minOccurs")?.trimmingXMLWhitespace()
|
||||
if let minOccurs, isNonNegativeInteger(minOccurs), !["0", "1"].contains(canonicalMagnitude(minOccurs)) {
|
||||
errors.append("the minOccurs of a reference to an all group must be 0 or 1")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "the minOccurs of a reference to an all group must be 0 or 1",
|
||||
node: group,
|
||||
))
|
||||
}
|
||||
}
|
||||
return errors
|
||||
return findings
|
||||
}
|
||||
|
||||
static func exceeds(_ lhs: String, _ rhs: String) -> Bool {
|
||||
@ -315,14 +321,20 @@ extension PureXML.Schema.XSDParser {
|
||||
/// is not a legal namespace name. So `<schema targetNamespace="">` and
|
||||
/// `<import namespace="">` are invalid, while omitting the attribute (no target
|
||||
/// namespace, a no-namespace import) stays valid.
|
||||
static func emptyNamespaceErrors(_ schema: XSDTree) -> [String] {
|
||||
var errors: [String] = []
|
||||
static func emptyNamespaceFindings(_ schema: XSDTree) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
if PureXML.Schema.XSDNode.attribute(schema, "targetNamespace") == "" {
|
||||
errors.append("the 'targetNamespace' attribute may not be the empty string")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "the 'targetNamespace' attribute may not be the empty string",
|
||||
node: schema,
|
||||
))
|
||||
}
|
||||
for importNode in descendants(schema, named: "import") where PureXML.Schema.XSDNode.attribute(importNode, "namespace") == "" {
|
||||
errors.append("an 'import' namespace may not be the empty string")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "an 'import' namespace may not be the empty string",
|
||||
node: importNode,
|
||||
))
|
||||
}
|
||||
return errors
|
||||
return findings
|
||||
}
|
||||
}
|
||||
|
||||
146
Tests/SchemaStructureLocatedDiagnosticsTests.swift
Normal file
146
Tests/SchemaStructureLocatedDiagnosticsTests.swift
Normal file
@ -0,0 +1,146 @@
|
||||
import Testing
|
||||
@testable import PureXML
|
||||
|
||||
/// Structure-pass schema compile findings (facet applicability, top-level and
|
||||
/// nested declaration rules, empty namespaces, all-group references) carry
|
||||
/// validation coding paths that resolve to source spans, the IDE underline flow
|
||||
/// for #169. Split from `SchemaLocatedDiagnosticsTests` to keep each suite within
|
||||
/// the type-body length budget.
|
||||
@Suite("Schema structure-pass located compile diagnostics")
|
||||
struct SchemaStructureLocatedDiagnosticsTests {
|
||||
private func inconsistentFindings(in xsd: String) throws -> [PureXML.Validation.ValidationError] {
|
||||
do {
|
||||
_ = try PureXML.Schema.Document(xsd)
|
||||
Issue.record("expected schema compilation to fail")
|
||||
return []
|
||||
} catch let error as PureXML.Schema.SchemaError {
|
||||
guard case let .inconsistent(findings) = error else {
|
||||
Issue.record("expected .inconsistent, got \(error)")
|
||||
return []
|
||||
}
|
||||
return findings
|
||||
}
|
||||
}
|
||||
|
||||
@Test("A facet that does not apply to a list variety is located on its declaring simpleType")
|
||||
func test_varietyFacetLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:simpleType name="ints"><xs:list itemType="xs:integer"/></xs:simpleType>
|
||||
<xs:simpleType name="t">
|
||||
<xs:restriction base="ints"><xs:maxInclusive value="5"/></xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("'maxInclusive' does not apply to a list type") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema", "xs:simpleType"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
let range = tree.sourceRange(at: located.codingPath)
|
||||
#expect(range?.start.line == 3)
|
||||
}
|
||||
|
||||
@Test("A top-level declaration without a name is located on the declaration")
|
||||
func test_topLevelMissingNameLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element type="xs:string"/>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("top-level 'element' definition must have a 'name'") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema", "xs:element"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
let range = tree.sourceRange(at: located.codingPath)
|
||||
#expect(range?.start.line == 2)
|
||||
#expect(range?.start.column == 3)
|
||||
}
|
||||
|
||||
@Test("A nested named definition is located on the offending nested component")
|
||||
func test_nestedNamedDefinitionLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="a">
|
||||
<xs:simpleType name="bad"><xs:restriction base="xs:string"/></xs:simpleType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("nested 'simpleType' definition may not have a 'name'") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema", "xs:element", "xs:simpleType"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
let range = tree.sourceRange(at: located.codingPath)
|
||||
#expect(range?.start.line == 3)
|
||||
}
|
||||
|
||||
@Test("A restriction of xs:anySimpleType is located on the restriction")
|
||||
func test_anySimpleTypeRestrictionLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t">
|
||||
<xs:simpleType name="t"><xs:restriction base="xs:anySimpleType"/></xs:simpleType>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("restriction of 'xs:anySimpleType' is not allowed") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema", "xs:simpleType", "xs:restriction"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
#expect(tree.sourceRange(at: located.codingPath)?.start.line == 2)
|
||||
}
|
||||
|
||||
@Test("A facet on anySimpleType-based simpleContent is located on the restriction")
|
||||
func test_anySimpleTypeFacetLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:complexType name="base">
|
||||
<xs:simpleContent><xs:extension base="xs:anySimpleType"/></xs:simpleContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="t">
|
||||
<xs:simpleContent>
|
||||
<xs:restriction base="base"><xs:minLength value="1"/></xs:restriction>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("constraining facet may not be applied to anySimpleType") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema", "xs:complexType", "xs:simpleContent", "xs:restriction"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
#expect(tree.sourceRange(at: located.codingPath)?.start.line == 7)
|
||||
}
|
||||
|
||||
@Test("An empty targetNamespace is located on the schema element")
|
||||
func test_emptyTargetNamespaceLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="">
|
||||
<xs:element name="a" type="xs:string"/>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("'targetNamespace' attribute may not be the empty string") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
#expect(tree.sourceRange(at: located.codingPath)?.start.line == 1)
|
||||
}
|
||||
|
||||
@Test("An all-group reference with an illegal maxOccurs is located on the reference")
|
||||
func test_allGroupReferenceMaxOccursLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:group name="g"><xs:all><xs:element name="a" type="xs:string"/></xs:all></xs:group>
|
||||
<xs:complexType name="t"><xs:group ref="g" maxOccurs="2"/></xs:complexType>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("maxOccurs of a reference to an all group must be 1") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema", "xs:complexType", "xs:group"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
#expect(tree.sourceRange(at: located.codingPath)?.start.line == 3)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user