Skip to content

fix: recognize generic HTML-attribute processors (th:content, th:for, th:open, ...) - #2

Merged
dozken merged 1 commit into
dozken:mainfrom
AlexanderWalls:fix/generic-html-attribute-processors
Jul 27, 2026
Merged

dozken merged 1 commit into
dozken:mainfrom
AlexanderWalls:fix/generic-html-attribute-processors

Conversation

@AlexanderWalls

@AlexanderWalls AlexanderWalls commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1.

The problem

Every consumer of the dialect catalog treated "not in the 42-entry ATTRS list"
as "invalid", so th:content, th:for, th:open — and ~160 other perfectly
legal attributes — produced Unknown Thymeleaf attribute warnings.

What the Standard Dialect actually accepts

I went to the tutorial
rather than adding the three reported names, because the gap is much wider than
three, and it has a documented shape:

  • §5.2 "Setting value to specific attributes" — ~140 th:<html-attr>
    processors (th:content, th:for, th:cite, th:colspan, every th:on*
    handler, …).
  • §5.5 "Fixed-value boolean attributes" — 24 boolean processors
    (th:open, th:required, th:async, th:hidden, …).

Beyond both lists, Thymeleaf 3's StandardDefaultAttributesTagProcessor handles
any remaining th:* name generically — that is how th:data-* custom
attributes work.

The change

Two-tier recognition in src/thymeleaf.rs. ATTRS keeps the curated,
hand-documented processors; the two new lists are transcribed verbatim from the
tutorial and get synthesized descriptions. classify() -> AttrKind resolves the
curated tier first, so hand-written docs always win for names that appear in
both (th:class, th:href, th:fragment, …). th:data-* / th:aria-* count
as known too.

The diagnostic is re-aimed at typos. Since the default attribute processor
accepts any name, "not in a list" was never a sound basis for a warning — but
catching th:txt genuinely is useful. So the warning now fires only when a name
is unrecognized and within MAX_SUGGESTION_DISTANCE of a real one, and it
names the intended attribute:

Unknown Thymeleaf attribute 'th:txt'. Did you mean 'th:text'?

closest_known + levenshtein moved from code_actions into thymeleaf, so
the diagnostic and the quick fix share one search. That is now an invariant:
every warning has a matching Change to ... action.

The suggestion threshold drops 3 → 2. With the candidate pool grown from 42
to ~200 names, distance 3 matches almost any short word by chance — th:bogus
would have "corrected" to th:cols. At distance 2 the separation is clean:

input nearest distance
th:txt th:text 1 warns
th:contnt th:content 1 warns
th:fragmnet th:fragment 2 warns
th:opne th:open 2 warns
th:bogus th:cols 3 silent
th:wibble th:title 3 silent

The other consumers follow the same classifier, so generic attributes are no
longer second-class: hover documents them (via a new owned AttrInfo so the
generic tier can synthesize text), semantic_tokens highlights them, and
completion offers both tiers with sort_text ranking curated names first.
The bracket-balance ERROR now applies to every th:* value rather than only
catalog ones — the value of any Thymeleaf attribute is an expression.

Verification

make check is clean (fmt, clippy -D warnings, 85 tests — 25 new).

Two existing tests asserted a warning on th:bogus, which is silent by design
now; both were retargeted to th:txt and additionally assert the message names
its fix. New coverage includes the issue's exact repro producing zero
diagnostics, th:data-*, the curated-wins-over-generic precedence, and the
threshold boundary cases above.

Also checked against the real index.html (with the three repro snippets added
to it): the only diagnostic left is the intentional th:txt typo.

Notes for review

  • The two lists are transcribed from the tutorial rather than derived from an
    HTML attribute index, so they track what Thymeleaf documents rather than what
    the HTML spec happens to contain.
  • MAX_SUGGESTION_DISTANCE is the one judgement call here. I documented the
    reasoning at the constant; happy to move it if you'd rather be more or less
    aggressive.
  • Completion now offers ~160 more items. They are ranked below the curated ones
    and clients filter as you type, but say the word if you'd rather keep the menu
    curated — it is a one-line change to drop the generic tier from completion
    only.

`th:content`, `th:for`, `th:open` and ~160 other valid Standard Dialect
attributes were flagged as "Unknown Thymeleaf attribute" because every
consumer treated absence from the 42-entry `ATTRS` catalog as invalid.

Recognition is now two-tier. `ATTRS` keeps the curated, hand-documented
processors; `SPECIFIC_ATTRS` / `BOOLEAN_ATTRS` hold the generic
`th:<html-attr>` names transcribed from the Thymeleaf tutorial (§5.2
"Setting value to specific attributes" and §5.5 "Fixed-value boolean
attributes"). `classify` resolves the curated tier first, so hand-written
docs always win, and also accepts `th:data-*` / `th:aria-*`, which
Thymeleaf 3 routes through StandardDefaultAttributesTagProcessor.

That same default processor means *any* `th:*` name is technically valid,
so "not in the catalog" was never a sound basis for a warning. The
diagnostic is re-aimed at typos: it fires only when the name is
unrecognized *and* within `MAX_SUGGESTION_DISTANCE` of a real one, and
names the intended attribute in the message. `closest_known` and
`levenshtein` move from `code_actions` into `thymeleaf` so both features
share one search — every warning now has a matching quick fix.

The threshold drops from 3 to 2: with the candidate pool grown from 42 to
~200 names, distance 3 matches almost any short word by chance
(`th:bogus` would "correct" to `th:cols`). Two edits still covers dropped
letters, transpositions and doubled characters.

Hover, semantic tokens and completion follow the same classifier, so
generic attributes get documentation, highlighting and suggestions.
Completion offers both tiers with `sort_text` ranking the curated ones
first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sSr14H6f4sgB7HAbyugMm

@dozken dozken left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for PR

@dozken
dozken merged commit 4cdcc95 into dozken:main Jul 27, 2026
1 check passed
@AlexanderWalls

Copy link
Copy Markdown
Contributor Author

thanks for PR

No worries - I'll keep using and contrib things when I see fit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Diagnostics false-positive: generic HTML-attribute processors not in ATTRS catalog (th:content, th:for, th:open, ...)

2 participants