Skip to content

Spelling never flags text under an xsd:any-matched (schema-undeclared) element, e.g. Maven POM <properties> children #28

Description

@t-gergely

Summary

XMLSpellcheckDelegate.shouldSpellcheck(int, IStructuredModel) (in org.eclipse.wst.xml.ui.internal.spelling.SpellcheckDelegateAdapterFactory)
silently suppresses spell checking for any text node whose parent element has no CMElementDeclaration in the resolved grammar — which includes every element that only matches an xsd:any wildcard. For a schema that uses wildcards to allow open-ended content (the Maven POM 4.0.0 XSD's <properties> element being a very common real-world example), this means none of the text inside that element is ever spell checked, even though the element is perfectly valid per the schema and its content is exactly the kind of free-form text spell checking exists for.

Where

public boolean shouldSpellcheck(int offset, IStructuredModel model) {
    boolean shouldSpellcheck = true;
    IndexedRegion region = model.getIndexedRegion(offset);
    if (region != null) {
        shouldSpellcheck = region instanceof Comment;
        if (!shouldSpellcheck && region instanceof Text) {
            final Node parent = ((Text) (region)).getParentNode();
            if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
                final ModelQuery mq = ModelQueryUtil.getModelQuery(parent.getOwnerDocument());
                if (mq != null) {
                    final CMElementDeclaration decl = mq.getCMElementDeclaration((Element) parent);
                    if (decl != null) {
                        final CMDataType type = decl.getDataType();
                        shouldSpellcheck = type == null || type.getEnumeratedValues() == null || type.getEnumeratedValues().length == 0;
                    }
                    // decl == null (e.g. only matched via xsd:any) -> shouldSpellcheck stays false, silently
                }
                // mq == null -> shouldSpellcheck also stays false
            }
        }
    }
    return shouldSpellcheck;
}

(xml/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/internal/spelling/SpellcheckDelegateAdapterFactory.java)

When mq.getCMElementDeclaration((Element) parent) returns null — which is exactly what happens for an element that's only reachable through an xsd:any wildcard, since there's no dedicated declaration for it — the method falls through without ever setting shouldSpellcheck back to true. The same happens if ModelQueryUtil.getModelQuery(...) itself returns null. Both cases are indistinguishable from "this text genuinely shouldn't be spell-checked" even though neither implies that.

This is invoked from SpellcheckStrategy.isInterestingProblem(SpellingProblem) (org.eclipse.wst.sse.ui), which is the last filter a SpellingProblem passes through before becoming a visible annotation — the underlying ISpellingEngine (any of them, built-in or a custom-contributed one) still computes and reports the problem correctly; it's discarded here, silently, with no logging and no way to see it just from watching the editor.

Reproduction

  1. Open this pom.xml in the XML editor:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>example</groupId>
      <artifactId>example</artifactId>
      <version>1.0</version>
      <properties>
        <my.custom.property>wombatz</my.custom.property>
      </properties>
      <name>wombatz</name>
    </project>
  2. Enable spell checking (Preferences > General > Editors > Text Editors > Spelling) with a dictionary that will flag "wombatz" as misspelled.

  3. Observe: wombatz inside <name> gets underlined (name has an explicit CMElementDeclaration with a non-enumerated string type in the POM 4.0.0 XSD). wombatz inside <my.custom.property> does not — even though it's the identical word, in the same file, checked by the same engine.

  4. Confirm the engine really did compute+report it: open the same file a second time via Open With > Plain Text Editor. Since it shares the same IAnnotationModel with the already-open XML editor, both editors immediately show the <my.custom.property> annotation too — proving it was always being calculated correctly and only withheld by XMLSpellcheckDelegate.

Expected

Text under an element that's valid per the schema (even if only via a wildcard) should be spell-checked the same as any other element with a non enumerated string content type — or, if suppressing wildcard-matched content is intentional, that should be a distinguishable, documented case (and ideally configurable), not indistinguishable from "no grammar at all" or logged nowhere.

Suggested fix

  • Default shouldSpellcheck to true when decl == null (unknown/wildcard element) instead of leaving it false, mirroring how a document with no grammar at all is presumably still spell-checked for its Text nodes elsewhere.

  • Or, more precisely: the content model does expose this distinction, just not through the method this code calls. getCMElementDeclaration(Element) can only ever return a CMElementDeclaration, so it comes back null for anything only reachable through an xsd:any wildcard — that wildcard is represented separately as CMAnyElement (CMNode.getNodeType() == CMNode.ANY_ELEMENT), which getCMElementDeclaration has no way to surface. ModelQuery.getCMNode(Node) returns the broader CMNode type and would let this code tell the two cases apart:

    final CMNode node = mq.getCMNode((Element) parent);
    if (node != null) {
        if (node.getNodeType() == CMNode.ANY_ELEMENT) {
            // Matched only via an xsd:any (or DTD ANY) wildcard — there's no
            // dedicated declaration, but the element is still valid content
            // here, so its text is exactly the free-form case spell checking
            // is for.
            shouldSpellcheck = true;
        } else if (node instanceof CMElementDeclaration) {
            final CMDataType type = ((CMElementDeclaration) node).getDataType();
            shouldSpellcheck = type == null || type.getEnumeratedValues() == null || type.getEnumeratedValues().length == 0;
        }
    }

in place of the current getCMElementDeclaration call, keeping the existing enumerated-type check for genuinely declared elements and only changing the outcome for the ANY_ELEMENT case.

Environment

  • Eclipse 4.40 (2026-06)
  • org.eclipse.wst.xml.ui 1.2.900.v202510300114
  • org.eclipse.wst.sse.ui 1.7.1200.v202510300114

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions