fix(lint): back readAttr with the HTML parser - #2802
Draft
xuanruli wants to merge 1 commit into
Draft
Conversation
`readAttr` matched only quoted attribute values. Unquoted values are valid HTML5 and the runtime honours them, so on a root written `data-composition-id=main data-start=0 data-width=1920 data-height=1080` the linter reported two errors for attributes that are present, while a file with genuinely overlapping unquoted clips and no `class="clip"` linted clean. The same rule set printed the composition id in its own message, because `readDecodedAttr` — the htmlparser2-backed reader twenty lines below — parsed that tag correctly, which isolated the defect to the regex. Widening the regex with an unquoted alternative is worse, not better: it has no notion of attribute-list position, so it captures `name=value` pairs out of other attributes' quoted values. Measured on that version, two `<video>` elements with distinct real ids sharing a `?id=9` source both read as id "9" — `duplicate_media_id` fired on an id that does not exist and swallowed two genuine `video_missing_muted` findings — and a video with no id at all but `?id=7` in its URL had `media_missing_id` masked, hiding a frozen-video render bug. A Google-Drive-style `?export=view&id=…` asset URL is enough to trigger it. Delegating to the parser fixes the blind spot without that class of mis-read, and drops the `=`-truncation and undecoded-entity divergences as well. `|| null` keeps the existing "empty value means absent" contract. Behaviour change worth surfacing in release notes: attributes that were invisible are now linted, so a project written with unquoted attributes AND a real violation will newly fail `lint` and `render --strict`. Correct unquoted-attribute projects lint clean. The repo's own 13 examples and all registry blocks produce byte-identical findings. `readJsonAttr` is left alone; the parser makes it redundant, but removing it is its own change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
readAttr(packages/lint/src/utils.ts) matched only quoted attribute values. Unquoted values are valid HTML5 and the runtime honours them, so the quoted-only pattern was simultaneously inventing errors and hiding real violations.readAttrbacks ~130 call sites across ~68 error-severity rules, and lint errors gaterender --strictand the lint section ofcheck.data-composition-id=main data-start=0 data-width=1920 data-height=1080root_missing_dimensions,root_composition_missing_data_start— for attributes that are presentdeprecated_data_layer,deprecated_data_end,timed_element_missing_clip_class) now surfaceclass="clip"overlapping_clips_same_track+ 2xtimed_element_missing_clip_classThe defect was isolated by the linter's own output: the baseline error message printed the composition id
"main", becausereadDecodedAttr— the htmlparser2-backed reader twenty lines below in the same file — parsed that very tag correctly.Why not just widen the regex
The first attempt added an unquoted alternative (
(?:["']([^"']+)["']|([^\s"'\=<>]+))). Two independent reviews rejected it with measurements, and they were right: the pattern has no notion of attribute-list position, so it capturesname=value` pairs out of other attributes' quoted values.<div data-hf-src="clip.mp4?id=5" id="root">id"5""root"<img src="x.png?class=big" class="clip">class"big""clip"<video src="v.mp4?preload=none">preload"none"idis read at 46 of the call sites, so the damage was concrete and error-severity:<video>elements with distinct real ids sharing a?id=9source both read as id"9":duplicate_media_id(error) fired on an id that does not exist, and swallowed two genuinevideo_missing_mutedfindings via same-id dedupe;<video>with no id at all but?id=7in its URL hadmedia_missing_id(error) masked — hiding the frozen-video render bug that rule exists to catch;?export=view&id=<FILEID>asset URL — a canonical pattern in generated compositions — is enough to trigger it.Delegating to the parser fixes the blind spot without introducing that class, and drops two further divergences for free: the regex silently truncated any unquoted value containing
=(href=a.html?x=1&y=2->"a.html?x"), and never decoded character references.|| nullpreserves the existing "empty value means absent" contract exactly.Verification
registry/examples/*and everyregistry/blocks/*html produce byte-identical lint findings before and after (compared viagit show HEAD:<path>file swaps, notgit stash— see note below). One reviewer independently reproduced this over 166 targets.packages/lint: 515 tests pass (505 pre-existing + 10 new).tsc --noEmitclean.readAttrbuilt an uncachednew RegExpper call).Behaviour change to surface in release notes
There is no
.changeset/in this repo — release notes are hand-curated from the commit log — so this needs to be stated rather than filed. Attributes that were invisible are now linted, so a project written with unquoted attributes and a real violation will newly faillintandrender --strict. A correct unquoted-attribute project lints clean (verified). The repo's own compositions all quote their attributes, hence the zero-delta corpus.Not in scope
readJsonAttris left alone. The parser makes it redundant (it exists specifically because the old regex truncated JSON attribute values, per its own docblock), but removing it is its own change.Four other
lintdefects confirmed in the same testing round are untouched and each needs its own fix:non_deterministic_codeandrequestanimationframe_in_compositionflag banned identifiers inside string literals (this blocks the repo's own/pr-to-videooutput — a typed code-diff animation with"Math.random()"in a data array);new Date(fires on the deterministicnew Date("2024-01-15");overlapping_clips_same_trackhas no hierarchy model (a parent clip "overlaps" its own children) and keys tracks by raw string, so"1"and"01"are different tracks;imperative_media_controlmisses?.play().🤖 Generated with Claude Code