feat(schema): locate extension/substitution-pass schema diagnostics
Convert the three extension/substitution-pass compile checkers to located findings so each error attaches to its declaring node instead of the schema root (#169): an xs:all group reached through an extension over a base that already has content underlines the extension (extensionAllGroupFindings); an anonymous complex-type restriction that is not a subset of its base underlines the restriction (anonymousRestrictionFindings); and a substitution-group member whose type is not validly derived from its head, including the head's final-exclusion violations, underlines the member element (substitutionTypeFindings). Every predicate, iteration order, and reason string is unchanged; only the location each finding carries differs, verified conformance-neutral by the full XSTS suite (14383 groups, pinned counts unchanged). The substitution checker's checkExclusions helper stays [String] and is wrapped onto the member element at the call site. The three checkers had exactly one caller each (their dispatch), so each is renamed in place and the .unlocated wrap dropped. Adds a located-diagnostics test per checker.
This commit is contained in:
parent
4dd1d330cb
commit
c6dd0f7d2f
@ -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. 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. The simple-type-pass checkers join them: a list whose item type is itself a list or a union containing a list, a list declaring both or neither of `itemType`/inline `simpleType`, an empty union, a simpleType restriction whose base resolves to a complex type, an `itemType`/`memberType` referencing a type that is `final` for list/union derivation, and an attribute typed by a complex type each underline the offending `list`, `union`, `restriction`, or `attribute` node rather than the schema root. The attribute-pass checkers follow: an attribute declaration that lands in the XSI namespace underlines the `attribute`; a duplicate attribute use or a second ID-typed attribute underlines its declaring `complexType` or `attributeGroup`; an attribute reference that contradicts a global `fixed` value underlines the reference; and a complex-type restriction that relaxes a base attribute use (making a required attribute optional, narrowing its type unfaithfully, dropping a fixed value, or widening the attribute wildcard) underlines the `restriction`. The derivation/cycle-pass checkers, which report a cycle by component name over a name-keyed graph, now record the declaring node alongside that graph and underline it: a type derived from itself underlines its `complexType`/`simpleType`, an element that is a member of its own substitution group underlines its `element`, a model group or attribute group that references itself underlines its `group`/`attributeGroup` declaration, and an all-group referenced inside a `sequence`/`choice`/`all` underlines the offending `group` reference. The content-derivation-pass checkers (the `cos-ct-extends`/`src-ct` base-kind family) follow: a complexContent extension adding element content over a simpleContent base, a simpleContent restriction whose base is a built-in or user simple type, an unfaithful inline type in a simpleContent restriction, a complexContent base that is a simple type, a simpleContent extension whose base is `xs:anyType`, and a mismatched `mixed` setting on a complexContent extension each underline their `extension`/`restriction` derivation node, and an element carrying a `default`/`fixed` value over element-only or empty content underlines the `element`. The reference-resolution rule is now genuinely located: `referenceFindings` previously wrapped its whole aggregate in `.unlocated`, so every undeclared-reference error collapsed onto the schema root despite the rule being dispatched as located; each of its six sub-collectors (the core type/base/itemType/memberTypes/ref/substitutionGroup walk, the `xs:`-namespace builtin check, the simpleContent base-kind check, the un-imported-namespace check, the redefine-component-existence check, and the import-namespace-agreement check) now attaches to its referencing node, `import`, or `redefine` component.
|
||||
- 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. The simple-type-pass checkers join them: a list whose item type is itself a list or a union containing a list, a list declaring both or neither of `itemType`/inline `simpleType`, an empty union, a simpleType restriction whose base resolves to a complex type, an `itemType`/`memberType` referencing a type that is `final` for list/union derivation, and an attribute typed by a complex type each underline the offending `list`, `union`, `restriction`, or `attribute` node rather than the schema root. The attribute-pass checkers follow: an attribute declaration that lands in the XSI namespace underlines the `attribute`; a duplicate attribute use or a second ID-typed attribute underlines its declaring `complexType` or `attributeGroup`; an attribute reference that contradicts a global `fixed` value underlines the reference; and a complex-type restriction that relaxes a base attribute use (making a required attribute optional, narrowing its type unfaithfully, dropping a fixed value, or widening the attribute wildcard) underlines the `restriction`. The derivation/cycle-pass checkers, which report a cycle by component name over a name-keyed graph, now record the declaring node alongside that graph and underline it: a type derived from itself underlines its `complexType`/`simpleType`, an element that is a member of its own substitution group underlines its `element`, a model group or attribute group that references itself underlines its `group`/`attributeGroup` declaration, and an all-group referenced inside a `sequence`/`choice`/`all` underlines the offending `group` reference. The content-derivation-pass checkers (the `cos-ct-extends`/`src-ct` base-kind family) follow: a complexContent extension adding element content over a simpleContent base, a simpleContent restriction whose base is a built-in or user simple type, an unfaithful inline type in a simpleContent restriction, a complexContent base that is a simple type, a simpleContent extension whose base is `xs:anyType`, and a mismatched `mixed` setting on a complexContent extension each underline their `extension`/`restriction` derivation node, and an element carrying a `default`/`fixed` value over element-only or empty content underlines the `element`. The reference-resolution rule is now genuinely located: `referenceFindings` previously wrapped its whole aggregate in `.unlocated`, so every undeclared-reference error collapsed onto the schema root despite the rule being dispatched as located; each of its six sub-collectors (the core type/base/itemType/memberTypes/ref/substitutionGroup walk, the `xs:`-namespace builtin check, the simpleContent base-kind check, the un-imported-namespace check, the redefine-component-existence check, and the import-namespace-agreement check) now attaches to its referencing node, `import`, or `redefine` component. The extension/substitution-pass checkers follow: an `all` group reached through an extension over a base with content underlines the `extension`, an anonymous complex-type restriction that is not a subset of its base underlines the `restriction`, and a substitution-group member whose type is not validly derived from its head underlines the member `element`.
|
||||
|
||||
- DTD parse-time validity findings are warnings until strict validation promotes them; `validateAgainstInternalDTD` returns full findings (errors and warnings).
|
||||
|
||||
|
||||
@ -163,14 +163,12 @@ extension PureXML.Validation {
|
||||
static var substitutionMembersDeriveCorrectly: Validation<PureXML.Schema.SchemaCompileRoot, PureXML.Schema.SchemaCompileContext> {
|
||||
compileRule("Substitution-group members derive correctly from their head") { document in
|
||||
guard let derivation = document.derivation, let namedTypes = document.namedTypes else { return [] }
|
||||
return PureXML.Schema.SchemaLocatedFinding.unlocated(
|
||||
PureXML.Schema.XSDParser.substitutionTypeErrors(
|
||||
document.schema,
|
||||
document.containers,
|
||||
derivation,
|
||||
namedTypes,
|
||||
document.context,
|
||||
),
|
||||
return PureXML.Schema.XSDParser.substitutionTypeFindings(
|
||||
document.schema,
|
||||
document.containers,
|
||||
derivation,
|
||||
namedTypes,
|
||||
document.context,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -186,9 +184,7 @@ extension PureXML.Validation {
|
||||
static var extensionAllGroupsValid: Validation<PureXML.Schema.SchemaCompileRoot, PureXML.Schema.SchemaCompileContext> {
|
||||
compileRule("Complex types extending xs:all groups satisfy XSD placement rules") { document in
|
||||
guard let namedTypes = document.namedTypes else { return [] }
|
||||
return PureXML.Schema.SchemaLocatedFinding.unlocated(
|
||||
PureXML.Schema.XSDParser.extensionAllGroupErrors(document.schema, document.context, namedTypes),
|
||||
)
|
||||
return PureXML.Schema.XSDParser.extensionAllGroupFindings(document.schema, document.context, namedTypes)
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,13 +334,11 @@ extension PureXML.Validation.SchemaCompile {
|
||||
static var anonymousRestrictionsValid: PureXML.Validation.Validation<PureXML.Schema.SchemaCompileRoot, PureXML.Schema.SchemaCompileContext> {
|
||||
compileRule("Anonymous complex-type restrictions accept a subset of their base's") { document in
|
||||
guard let namedTypes = document.namedTypes else { return [] }
|
||||
return PureXML.Schema.SchemaLocatedFinding.unlocated(
|
||||
PureXML.Schema.XSDParser.anonymousRestrictionErrors(
|
||||
document.schema,
|
||||
document.context,
|
||||
namedTypes,
|
||||
document.derivation?.typeDerivation ?? [:],
|
||||
),
|
||||
return PureXML.Schema.XSDParser.anonymousRestrictionFindings(
|
||||
document.schema,
|
||||
document.context,
|
||||
namedTypes,
|
||||
document.derivation?.typeDerivation ?? [:],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,10 +12,14 @@ extension PureXML.Schema.XSDParser {
|
||||
/// content is element-only or mixed. An empty base (extending it with an `all`
|
||||
/// makes the `all` the whole content, which is valid), a simple-content base, or
|
||||
/// an unresolved/foreign base is left alone. Checked for a self-contained schema.
|
||||
static func extensionAllGroupErrors(_ schema: XSDTree, _ context: PureXML.Schema.XSDContext, _ types: [String: PureXML.Schema.ElementType]) -> [String] {
|
||||
static func extensionAllGroupFindings(
|
||||
_ schema: XSDTree,
|
||||
_ context: PureXML.Schema.XSDContext,
|
||||
_ types: [String: PureXML.Schema.ElementType],
|
||||
) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
guard !skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) else { return [] }
|
||||
let bindings = context.namespaceBindings
|
||||
var errors: [String] = []
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
for content in descendants(schema, named: "complexContent") {
|
||||
guard let ext = ExtAllNode.firstChild(content, named: "extension"),
|
||||
let base = ExtAllNode.attribute(ext, "base"),
|
||||
@ -26,17 +30,23 @@ extension PureXML.Schema.XSDParser {
|
||||
// The extension itself adds an `all` group over a base that already has
|
||||
// element content: the all would be nested in the extension sequence.
|
||||
if extensionHasAllGroup(ext), baseHasElementContent(complex.content) {
|
||||
errors.append("an 'all' group may not extend the type '\(baseName)', which has its own content; an all group must be the whole content model")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "an 'all' group may not extend the type '\(baseName)', which has its own content; an all group must be the whole content model",
|
||||
node: ext,
|
||||
))
|
||||
continue
|
||||
}
|
||||
// The base's own content IS an `all` group and the extension adds
|
||||
// element content: the base's all is then nested in the sequence that
|
||||
// joins base and extension content, which is forbidden (cos-all-limited).
|
||||
if contentIsAllGroup(complex.content), extensionAddsElementContent(ext) {
|
||||
errors.append("the type '\(baseName)' has an 'all' group as its whole content and may not be extended with element content")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "the type '\(baseName)' has an 'all' group as its whole content and may not be extended with element content",
|
||||
node: ext,
|
||||
))
|
||||
}
|
||||
}
|
||||
return errors
|
||||
return findings
|
||||
}
|
||||
|
||||
/// Particle Valid (Restriction) for ANONYMOUS complex types: the named-type
|
||||
@ -47,15 +57,15 @@ extension PureXML.Schema.XSDParser {
|
||||
/// in this schema's own target namespace, and runs the same subset check. Only a
|
||||
/// self-contained schema is examined; the same `ParticleRestriction.violation`
|
||||
/// used for named types keeps the false-positive profile identical.
|
||||
static func anonymousRestrictionErrors(
|
||||
static func anonymousRestrictionFindings(
|
||||
_ schema: XSDTree,
|
||||
_ context: PureXML.Schema.XSDContext,
|
||||
_ types: [String: PureXML.Schema.ElementType],
|
||||
_ derivation: [String: PureXML.Schema.TypeDerivation],
|
||||
) -> [String] {
|
||||
) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
guard !skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) else { return [] }
|
||||
let bindings = context.namespaceBindings
|
||||
var errors: [String] = []
|
||||
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
||||
for complexType in descendants(schema, named: "complexType") where ExtAllNode.attribute(complexType, "name") == nil {
|
||||
guard let content = ExtAllNode.firstChild(complexType, named: "complexContent"),
|
||||
let restriction = ExtAllNode.firstChild(content, named: "restriction"),
|
||||
@ -70,10 +80,13 @@ extension PureXML.Schema.XSDParser {
|
||||
types: types,
|
||||
derivation: derivation,
|
||||
) {
|
||||
errors.append("an anonymous complex type is not a valid restriction of '\(ExtAllNode.stripPrefix(baseRef))': \(reason)")
|
||||
findings.append(PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "an anonymous complex type is not a valid restriction of '\(ExtAllNode.stripPrefix(baseRef))': \(reason)",
|
||||
node: restriction,
|
||||
))
|
||||
}
|
||||
}
|
||||
return errors
|
||||
return findings
|
||||
}
|
||||
|
||||
/// Whether a content type's whole content model is a NON-EMPTY `all` group. An
|
||||
|
||||
@ -21,13 +21,13 @@ extension PureXML.Schema.XSDParser {
|
||||
/// 2.3 and 2.4: a type derives from a union if it derives from one of the
|
||||
/// union's member types). When either type is a list or union the check stands
|
||||
/// down rather than reject a valid member, a disclosed under-rejection.
|
||||
static func substitutionTypeErrors(
|
||||
static func substitutionTypeFindings(
|
||||
_ schema: XSDTree,
|
||||
_ containers: [XSDTree],
|
||||
_ tables: DerivationTables,
|
||||
_ types: [String: PureXML.Schema.ElementType],
|
||||
_ context: PureXML.Schema.XSDContext,
|
||||
) -> [String] {
|
||||
) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
guard !skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) else { return [] }
|
||||
let sources = context.compositionLoaded ? containers : [schema]
|
||||
let namespaceMap = resolveContainerNamespaces(containers, mainTargetNamespace: context.targetNamespace)
|
||||
@ -46,7 +46,7 @@ extension PureXML.Schema.XSDParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources.indices.flatMap { index -> [String] in
|
||||
return sources.indices.flatMap { index -> [PureXML.Schema.SchemaLocatedFinding] in
|
||||
let source = sources[index]
|
||||
guard SubTypeNode.localName(source) != "redefine" else { return [] }
|
||||
let bindings = SubTypeNode.namespaceBindings(of: source)
|
||||
@ -69,7 +69,7 @@ extension PureXML.Schema.XSDParser {
|
||||
_ globalElementType: [GlobalElementKey: String],
|
||||
_ tables: DerivationTables,
|
||||
_ types: [String: PureXML.Schema.ElementType],
|
||||
) -> [String] {
|
||||
) -> [PureXML.Schema.SchemaLocatedFinding] {
|
||||
guard let member = SubTypeNode.attribute(element, "name"),
|
||||
let headReference = SubTypeNode.attribute(element, "substitutionGroup")
|
||||
else { return [] }
|
||||
@ -100,11 +100,21 @@ extension PureXML.Schema.XSDParser {
|
||||
// as a recorded restriction of the head's type, both of which `isDerived`
|
||||
// covers). Affiliating it to any other head type is not a valid derivation.
|
||||
if isListOrUnion(memberType, types), !isDerived, headType != "anySimpleType", headType != "anyType" {
|
||||
return ["element '\(member)' may not be in the substitution group of '\(head)': its list or union type is not derived from '\(headType)'"]
|
||||
return [
|
||||
PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "element '\(member)' may not be in the substitution group of '\(head)': its list or union type is not derived from '\(headType)'",
|
||||
node: element,
|
||||
),
|
||||
]
|
||||
}
|
||||
guard !isListOrUnion(memberType, types), !isListOrUnion(headType, types) else { return [] }
|
||||
if !isDerived {
|
||||
return ["element '\(member)' may not be in the substitution group of '\(head)': its type is not derived from '\(headType)'"]
|
||||
return [
|
||||
PureXML.Schema.SchemaLocatedFinding(
|
||||
reason: "element '\(member)' may not be in the substitution group of '\(head)': its type is not derived from '\(headType)'",
|
||||
node: element,
|
||||
),
|
||||
]
|
||||
}
|
||||
return checkExclusions(
|
||||
member: member,
|
||||
@ -112,7 +122,7 @@ extension PureXML.Schema.XSDParser {
|
||||
inlineDeriv: inlineDeriv,
|
||||
headInfo: (head, headType),
|
||||
tables: tables,
|
||||
)
|
||||
).map { PureXML.Schema.SchemaLocatedFinding(reason: $0, node: element) }
|
||||
}
|
||||
|
||||
private static func checkExclusions(
|
||||
|
||||
@ -16,7 +16,7 @@ extension PureXML.Schema.XSDParser {
|
||||
///
|
||||
/// The original fields (`typeDerivation`, `typeBlock`, etc.) are keyed by BARE
|
||||
/// local name and feed the schema-validity machinery (``ParticleRestriction``,
|
||||
/// ``CompiledSchemaFacts``, ``substitutionTypeErrors(_:_:_:_:_:)``), which
|
||||
/// ``CompiledSchemaFacts``, ``substitutionTypeFindings(_:_:_:_:_:)``), which
|
||||
/// resolves component references to local names and is the single-namespace
|
||||
/// consistent identity those rules compare by.
|
||||
///
|
||||
|
||||
91
Tests/SchemaExtensionSubstitutionLocatedTests.swift
Normal file
91
Tests/SchemaExtensionSubstitutionLocatedTests.swift
Normal file
@ -0,0 +1,91 @@
|
||||
import Testing
|
||||
@testable import PureXML
|
||||
|
||||
/// Extension/substitution-pass schema compile findings (an `all` group reached
|
||||
/// through extension, an anonymous complex-type restriction that is not a subset of
|
||||
/// its base, and a substitution-group member whose type is not derived from its
|
||||
/// head) carry validation coding paths that resolve to source spans, the IDE
|
||||
/// underline flow for #169. Split from the other located-diagnostics suites to keep
|
||||
/// each suite within the type-body length budget.
|
||||
@Suite("Schema extension/substitution-pass located compile diagnostics")
|
||||
struct SchemaExtensionSubstitutionLocatedTests {
|
||||
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("An all-group extending a base with content is located on the extension")
|
||||
func test_extensionAllGroupLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:complexType name="base">
|
||||
<xs:sequence><xs:element name="x" type="xs:string"/></xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="d">
|
||||
<xs:complexContent>
|
||||
<xs:extension base="base">
|
||||
<xs:all><xs:element name="y" type="xs:string"/></xs:all>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("an 'all' group may not extend the type 'base'") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema", "xs:complexType", "xs:complexContent", "xs:extension"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
#expect(tree.sourceRange(at: located.codingPath)?.start.line == 7)
|
||||
}
|
||||
|
||||
@Test("An invalid anonymous restriction is located on the restriction")
|
||||
func test_anonymousRestrictionLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:complexType name="base">
|
||||
<xs:sequence><xs:element name="a" type="xs:string"/></xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:element name="root">
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
<xs:restriction base="base">
|
||||
<xs:sequence><xs:element name="b" type="xs:string"/></xs:sequence>
|
||||
</xs:restriction>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("an anonymous complex type is not a valid restriction of 'base'") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema", "xs:element", "xs:complexType", "xs:complexContent", "xs:restriction"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
#expect(tree.sourceRange(at: located.codingPath)?.start.line == 8)
|
||||
}
|
||||
|
||||
@Test("A substitution member with an underived type is located on the member element")
|
||||
func test_substitutionTypeLocated() throws {
|
||||
let xsd = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t" xmlns:t="urn:t">
|
||||
<xs:element name="head" type="xs:string"/>
|
||||
<xs:element name="member" type="xs:int" substitutionGroup="t:head"/>
|
||||
</xs:schema>
|
||||
"""
|
||||
let findings = try inconsistentFindings(in: xsd)
|
||||
let located = try #require(findings.first { $0.reason.contains("element 'member' may not be in the substitution group of 'head'") })
|
||||
#expect(located.codingPath.map(\.stringValue) == ["xs:schema", "xs:element"])
|
||||
|
||||
let (tree, _) = PureXML.readTree(xsd)
|
||||
#expect(tree.sourceRange(at: located.codingPath)?.start.line == 3)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user