fix(lint): preserve opening-tag source bounds after multiline closes - #3917
Conversation
Capture explicit source name bounds before attributes are parsed. Preserve malformed names, Unicode lowercase expansion, and existing implied-tag ranges while retaining malformed-attribute lint failures.
e3798b5 to
84d6226
Compare
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed revision 5721b91. No blocking findings.
The opening-name callback preserves original source boundaries, including Unicode names whose lowercase representation changes length (packages/lint/src/utils.ts:76). Regression tests retain genuine malformed-tag detection alongside the valid multiline-close cases (packages/lint/src/rules/core.test.ts:965).
Validation: all 651 lint tests passed with local workspace-source aliases. An independent adversarial pass compared source ranges with tokenizer offsets across 101,512 HTML inputs without mismatches, covering implied tags, malformed names, raw-text content, and quoted attributes.
— Codex
Verdict: APPROVE
Reasoning: The change fixes the false positive while preserving malformed-input detection; no introduced correctness issues were found.
What
Preserve correct opening-tag source bounds in
parseHtmlStructure(packages/lint/src/utils.ts) so theunclosed_tag_swallowed_elementrule no longer misreports valid siblings after a multiline closing tag.Fixes #3916.
Why
Reproduced with HyperFrames v0.8.36, official source
f86aae655ae5aae7a9a2c124fa016f3bc30ebe52, and htmlparser2@10.1.0.A closing tag with whitespace before
>can leaveParser.startIndexinside that closing tag. The next opening tag then includes the preceding whitespace and>in its raw range, causing a valid sibling to triggerunclosed_tag_swallowed_element:How
Capture the original opening-name bounds in
onopentagname, before attributes are scanned. Bound the search using HTML name delimiters while retaining<inside malformed names, and use the original name end for attributes: a lowercased Unicode name can have a different UTF-16 length. Implied opening tags retain their existing parser-reported ranges. The source text and the lint rule/severity remain unchanged.Test plan
Validation executed:
packages/lint:node ../../node_modules/vitest/vitest.mjs run --config vitest.config.ts— 16 files, 651 tests passed.node node_modules/typescript/bin/tsc --project packages/lint/tsconfig.json --noEmit --types node— passed.npx --yes oxfmt@0.41.0 --check packages/lint/src/utils.ts packages/lint/src/utils.test.ts packages/lint/src/rules/core.test.tsandnpx --yes oxlint@1.64.0 packages/lint/src/utils.ts packages/lint/src/utils.test.ts packages/lint/src/rules/core.test.ts— passed.packages/lint:node ../../node_modules/tsup/dist/cli-default.js— Node and browser lint entrypoints built successfully.Tests cover adjacent and multiline closing tags, malformed names containing
<, quoted<values, ignored markup inside a malformed close, impliedp/bropens, Unicode lowercase expansion, and genuine missing->/unquoted-attribute negatives.