Skip to content

fix: compare tag and attribute names case insensitively - #1937

Open
arpitjain099 wants to merge 1 commit into
htmlhint:mainfrom
arpitjain099:fix/case-insensitive-rules
Open

arpitjain099 wants to merge 1 commit into
htmlhint:mainfrom
arpitjain099:fix/case-insensitive-rules

Conversation

@arpitjain099

Copy link
Copy Markdown

HTML tag and attribute names are ASCII case insensitive, and the parser passes them through exactly as written. Two rules in defaultRuleset compare them raw, so the same violation in a different case goes unreported.

src-not-empty matches the tag with no i flag and the attribute by exact string:

((/^(img|script|embed|bgsound|iframe)$/.test(tagName) === true &&
  attr.name === 'src') ||
  (tagName === 'link' && attr.name === 'href') ||
  (tagName === 'object' && attr.name === 'data')) &&
attr.value === ''

attr-no-duplication keys its seen-set on attr.name directly.

Four spellings of one bug, plus the duplicate-attribute pair, through the CLI on main. The two casing rules are turned off in the config, which is routine: SVG's viewBox, Angular and Vue attribute names and generated markup all trip them legitimately.

L5  <img src="">              The attribute [ src ] of the tag [ img ] must have a value. (src-not-empty)
L9  <div id="a" id="b">       Duplicate of attribute name [ id ] was found. (attr-no-duplication)
L10 <div ID="a" id="b">       The id value [ a ] must be unique. (id-unique)

Scanned 1 files, found 3 errors

Lines 6, 7 and 8 are <img SRC="">, <IMG src=""> and <IMG SRC="">. None of them draws anything. Had line 5 not been in the file it would have exited clean.

After the change the same file reports 7, with all four src-not-empty cases and both attr-no-duplication cases:

L6  <img SRC="">   The attribute [ SRC ] of the tag [ img ] must have a value.
L7  <IMG src="">   The attribute [ src ] of the tag [ img ] must have a value.
L8  <IMG SRC="">   The attribute [ SRC ] of the tag [ img ] must have a value.
L10 <div ID="a" id="b">  Duplicate of attribute name [ id ] was found.

The message still echoes the attribute as the author spelled it, so the output points at the real text.

This is not a new convention for the project: grep -L toLowerCase src/core/rules/*.ts shows 28 of the 44 rules already normalise, and these two were among the 16 that did not.

Verification

Three cases added: the four img/src spellings, uppercase LINK HREF and OBJECT DATA, and href/HREF duplicated on one element. All three fail on main.

npm test goes from 370 to 373 passing with no failures, npm run lint reports no issues and prettier --check is clean on all four touched files.

dist/ is regenerated and included, since a clean build on main produces no churn there and the test suite imports ../../dist/htmlhint.js. If you would rather that be left out of contributions, say so and I will drop it.

HTML tag and attribute names are ASCII case insensitive, and the parser hands
them over exactly as they were written. Two default-on rules compare them raw,
so the identical violation spelled in a different case is silently unreported.

src-not-empty tests the tag with /^(img|script|embed|bgsound|iframe)$/, with no
i flag, and the attribute with attr.name === 'src'. So <img src=""> is reported
and <img SRC="">, <IMG src=""> and <IMG SRC=""> are not. The same applies to
link/href and object/data.

attr-no-duplication keys its seen-set on the raw attribute name, so
<div id="a" id="b"> is reported and <div ID="a" id="b"> is not.

28 of the 44 rules already normalise with toLowerCase; this brings these two
into line. The reported message still echoes the attribute as the author wrote
it.

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Use ASCII-only normalization and preserve the original tag spelling in diagnostics.

Pull request overview

Updates HTMLHint rules to compare tag and attribute names case-insensitively, with regression tests and regenerated distribution files.

Changes:

  • Normalizes names in src-not-empty and attr-no-duplication.
  • Adds mixed-case regression tests.
  • Regenerates compiled JavaScript.
  • Requires ASCII-only normalization and preservation of original diagnostic spelling.
File summaries
File Summary
test/rules/src-not-empty.spec.js Tests case-insensitive source checks
test/rules/attr-no-duplication.spec.js Tests case-insensitive duplicate detection
src/core/rules/src-not-empty.ts Case-insensitive tag and attribute matching
src/core/rules/attr-no-duplication.ts Case-insensitive duplicate detection
dist/core/rules/src-not-empty.js Regenerated rule output
dist/core/rules/attr-no-duplication.js Regenerated rule output
Review details

Suppressed comments (3)

src/core/rules/attr-no-duplication.ts:19

  • String.prototype.toLowerCase() performs Unicode case mappings, but HTML attribute names are only ASCII case-insensitive. For example, the distinct attribute names U+0130 (Latin capital I with dot) and i followed by U+0307 both normalize to the latter here and will be reported as duplicates. Fold only AZ (and add a regression case) so non-ASCII attribute names remain distinct.
        attrName = attr.name.toLowerCase()

src/core/rules/src-not-empty.ts:11

  • String#toLowerCase() applies Unicode case folding, but HTML name matching is ASCII case-insensitive. This can cause false positives for non-ASCII names (for example, JavaScript lowercases to k, so a <div k="a" K="b"> pair can be reported as duplicate, and Unicode characters such as ſ can make a non-HTML tag match a built-in tag). Use an ASCII-only A–Z normalization for both tagName and attrName (and add a regression case) so only ASCII case variants compare equal.
      const tagName = event.tagName.toLowerCase()

src/core/rules/src-not-empty.ts:11

  • The normalized tagName is also interpolated into the existing diagnostic, so uppercase markup now reports tag [ img ] instead of preserving the source spelling (tag [ IMG ]). Keep the normalized value for comparisons but use event.tagName when constructing the message, matching the existing preservation of attr.name.
      const tagName = event.tagName.toLowerCase()
  • Files reviewed: 4/6 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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.

2 participants