fix(lint): stop three rules matching code a composition only displays - #2811
Draft
xuanruli wants to merge 1 commit into
Draft
fix(lint): stop three rules matching code a composition only displays#2811xuanruli wants to merge 1 commit into
xuanruli wants to merge 1 commit into
Conversation
`requestanimationframe_in_composition` scanned comment-stripped source, but
`stripJsComments` preserves string literals by design, so
`const CODE = "requestAnimationFrame(step);"` produced a gating error.
`template_literal_selector` stripped nothing at all — a JS comment warning
against the pattern tripped it. `split_data_attribute_selector` scanned raw
style and script text, so a CSS comment showing the wrong form tripped it.
Code-explainer compositions are a first-class use case here, and one lint
error makes `check` return an empty browser result, so each of these also
silently discarded the whole runtime/layout/motion/contrast audit.
Add `stripJsStringLiterals`: blanks string, template and regex literal
contents, keeping delimiters, length and newline positions so a reported
snippet still slices the real source, and keeping template `${…}`
expressions because those are code. Regex literals are tracked because
their own quotes would otherwise open a phantom string and blank the rest
of the script — `/[^a-z']/g` ships in registry/components today. Regex vs
division is decided from an incrementally carried previous token, not by
re-scanning the output, which was quadratic. If the scan ends mid-literal
the source is returned untouched, so a parse this scanner cannot model
degrades to the caller's prior behaviour instead of blanking real code.
`split_data_attribute_selector` gets comment stripping only: its genuine
defect lives inside a query string, so blanking strings would remove the
true positive. Its CSS side uses a new string-aware `stripCssComments`,
because a slide printing comment markers as content otherwise pairs two of
them and deletes the real rules in between.
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.
The defect
Three
error-severity rules matched code that a composition merely renders as on-screen text. Code-explainer videos are a first-class use case here (there is a/pr-to-videoskill), and one lint error makescheckreturnemptyBrowserResult()(packages/cli/src/utils/checkPipeline.ts:1061) — so each of these did not just fail the build, it silently discarded the entire runtime / layout / motion / WCAG-contrast audit.requestanimationframe_in_compositionstripJsCommentspreserves string literals by design →const CODE = "requestAnimationFrame(step);"gatedtemplate_literal_selectorsplit_data_attribute_selectorThe fix
New
stripJsStringLiteralsblanks string, template and regex literal contents, preserving delimiters, length and newline positions (so a reported snippet still slices the real source) and keeping template${…}expressions, which are code.Two decisions worth reviewing explicitly:
split_data_attribute_selector's genuine defect lives inside a query string (gsap.to('[data-composition-id="x" data-start="0"]', …)), so blanking strings would delete its true positive. It gets comment stripping only — JS comments for scripts, and a new string-awarestripCssCommentsfor<style>, because a slide printing comment markers as content otherwise pairs two of them and deletes the real rules in between./[^a-z']/gships inregistry/components/caption-neon-accent/caption-neon-accent.html:284today; without this, 8179 tail characters collapsed to 6 non-blank. Regex-vs-division is decided from an incrementally carried previous token — re-scanning the accumulated output per slash was quadratic (a 966 KB composition did not finishlintin 600 s). If the scan ends mid-literal the source is returned untouched, so a parse this scanner cannot model degrades to the prior behaviour rather than blanking real code on a gating rule.Verification
registry/blocks+registry/components+registry/examples, each staged outsideregistry/soisRegistrySourceFilecannot exempt it, at--verboseso info-level counts): 1277 findings, byte-identical base vs fix.packages/lint535 tests pass. Each fix is pinned separately and verified non-vacuous: reverting the word-boundary flag fails exactly 2 tests, the property-name flag exactly 5, the++/--guard exactly 1.// fallow-ignore-next-line complexitysuppressions, matching howstripJsCommentsin the same file is already handled. A decomposition attempt reduced one function CRITICAL→HIGH but added a fourth flagged function, so it did not pass the gate either; keeping the simpler shape was preferred. (I suspected the decomposition's per-character state object would cost measurable time on the hot path — measured it, and it did not.)What this does NOT fix
A composition that displays code is still hard-blocked by other routes. On a realistic "determinism PR" explainer, 5 errors remain after this patch — 4 of them
non_deterministic_code, which is the single highest-probability blocker for displayed code:non_deterministic_codepackages/lint/src/rules/core.ts:616gsap_timeline_not_registeredpackages/lint/src/rules/gsap.ts:1697script.content)imperative_media_controlpackages/lint/src/rules/media.ts:146,180scene_layer_missing_visibility_killpackages/lint/src/rules/gsap.ts:1650gsap_timeline_registered_before_async_buildpackages/lint/src/rules/gsap.ts:1694gsap_infinite_repeatand thescanScriptsForRegexMatchesfamilypackages/lint/src/rules/gsap.ts:588stripCommentsoption only — wants astripStringsonesplit_data_attribute_selectoralso still fires on a split selector inside a displayed JS string. Its true positive and that false positive are the same text in the same position, so this is not fixable by changing what the rule scans. The correct end state is to gate the script-side match on selector-argument position — deliberately left out, because it changes what the rule matches (a different risk class), and because a naive version would newly suppress a real defect:const SEL = '[data-composition-id="x" data-start="0"]'; el.querySelectorAll(SEL);is caught today.Two inputs are worse than baseline, both narrow:
const u = /https:\/\//;followed on the same line by a real split selector —stripJsCommentshas no regex awareness, so the escaped\/\/reads as a line comment and eats the rest of the line. Line-bounded, and newly inherited only bysplit_data_attribute_selector(this rule previously scanned raw text).url(x/*y.png)followed by a real split selector — blanks to the end of the block. Per the CSS spec a url-token runs to), so this is a genuine divergence; no realistic instance could be constructed (base64 has no*).Follow-up ticket, with a shipped reproducer: the regex tracking belongs in
stripJsCommentstoo, or the two passes should merge.registry/examples/play-mode/compositions/stats.html:277contains a punctuation-class regex holding a backtick, which puts regex-blindstripJsCommentsinto phantom-template mode — 9 of 9 subsequent//comments in that script go unstripped. No behaviour change today, but it is a live reproducer, not a hypothetical.Known limitations, recorded
if (…) /re/statements bracketing the target balance the phantom quote, so the mid-literal fallback cannot save it — unbounded span, judged unreachable in practice;)staying division-position is the right default.window["requestAnimation" + "Frame"]is unmatched on both trees (pre-existing)./*to EOF is more correct than baseline: browsers consume to EOF, so a selector after it is dead and baseline's finding was the false one.Found by empirical false-negative / false-positive testing of the
composition.tsrule family; full report and the ~90 fixtures are separate. Reviewed adversarially twice; this patch was blocked 8 times before approval, and every blocking finding was a case where it silently suppressed a real defect rather than over-reporting.🤖 Generated with Claude Code