Convert idValueConstraintErrors to idValueConstraintFindings so each a-props-correct.3 / e-props-correct.5 violation is attached to its declaring element or attribute node, letting an editor underline the offending declaration by line and column instead of the schema root. The error predicate and reason strings are unchanged: only where each error points differs. Adds a located-diagnostics test asserting the finding resolves to the declaring component's source range.
72 lines
3.8 KiB
Swift
72 lines
3.8 KiB
Swift
private typealias IDVNode = PureXML.Schema.XSDNode
|
|
|
|
extension PureXML.Schema.XSDParser {
|
|
/// XSD 1.0 `a-props-correct.3` / `e-props-correct.5`: an attribute or element
|
|
/// declaration whose type is or derives from `xs:ID` must not carry a `default`
|
|
/// or `fixed` value constraint. Runs after named simple types are resolved, so a
|
|
/// named user type derived from `xs:ID` is recognized, not only the built-in and
|
|
/// inline forms. Only XSD-namespace declarations are checked; foreign content is
|
|
/// skipped.
|
|
/// Located findings (#169): each error is attached to the declaring `element`/
|
|
/// `attribute` node, so an editor can underline it by line/column.
|
|
static func idValueConstraintFindings(_ schema: XSDTree, _ context: PureXML.Schema.XSDContext) -> [PureXML.Schema.SchemaLocatedFinding] {
|
|
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
|
|
collectIDValueConstraintFindings(schema, context, into: &findings)
|
|
return findings
|
|
}
|
|
|
|
private static func collectIDValueConstraintFindings(
|
|
_ node: XSDTree,
|
|
_ context: PureXML.Schema.XSDContext,
|
|
into findings: inout [PureXML.Schema.SchemaLocatedFinding],
|
|
) {
|
|
let local = IDVNode.localName(node)
|
|
if local == "appinfo" || local == "documentation" { return }
|
|
if let error = idValueConstraintError(node, context) {
|
|
findings.append(PureXML.Schema.SchemaLocatedFinding(reason: error, node: node))
|
|
}
|
|
for child in IDVNode.elementChildren(node) {
|
|
collectIDValueConstraintFindings(child, context, into: &findings)
|
|
}
|
|
}
|
|
|
|
private static func idValueConstraintError(_ node: XSDTree, _ context: PureXML.Schema.XSDContext) -> String? {
|
|
guard node.name?.namespaceURI == xsdNamespace, let kind = IDVNode.localName(node),
|
|
kind == "element" || kind == "attribute",
|
|
let name = IDVNode.attribute(node, "name"),
|
|
IDVNode.attribute(node, "default") != nil || IDVNode.attribute(node, "fixed") != nil,
|
|
isIDDerived(node, context)
|
|
else { return nil }
|
|
return "'\(kind)' '\(name)' must not have a default or fixed value because its type is derived from ID"
|
|
}
|
|
|
|
/// Whether the declaration's type is or derives from `xs:ID`. A `type` attribute
|
|
/// is resolved by reference; an inline `simpleType` is ID only when it restricts
|
|
/// an ID-derived base (a list or union is not derived from ID).
|
|
private static func isIDDerived(_ node: XSDTree, _ context: PureXML.Schema.XSDContext) -> Bool {
|
|
if let typeName = IDVNode.attribute(node, "type") {
|
|
return typeReferenceIsID(typeName, context)
|
|
}
|
|
guard let inline = IDVNode.firstChild(node, named: "simpleType"),
|
|
let restriction = IDVNode.firstChild(inline, named: "restriction"),
|
|
let base = IDVNode.attribute(restriction, "base")
|
|
else {
|
|
return false
|
|
}
|
|
return typeReferenceIsID(base, context)
|
|
}
|
|
|
|
/// Whether a type reference resolves to, or derives from, the built-in `xs:ID`.
|
|
/// The reference's namespace is resolved from its prefix: only an `ID` in the
|
|
/// XSD namespace is the built-in; a user type is ID only if it resolves to one
|
|
/// whose base is `xs:ID`; a foreign or unresolved reference is not ID. (Matching
|
|
/// by local name alone would mistake a user type named `ID`, or an imported
|
|
/// `foo:ID`, for the built-in.)
|
|
private static func typeReferenceIsID(_ typeName: String, _ context: PureXML.Schema.XSDContext) -> Bool {
|
|
if IDVNode.referenceNamespace(typeName, context.namespaceBindings) == xsdNamespace {
|
|
return IDVNode.stripPrefix(typeName) == "ID"
|
|
}
|
|
return context.simpleTypes[IDVNode.stripPrefix(typeName)]?.base == .id
|
|
}
|
|
}
|