Repository navigation
fix(lint): parse each style block once per lint instead of once per rule - #7593
Conversation
…rule Ten rules each parsed the same <style> block with postcss, so a large style block cost ten parses and ten trees for the garbage collector. The 40 000-rule linear-time test ran close to its 2 s limit on CI and failed there by chance. One shared parse per block brings that lint from about 400 ms to about 60 ms.
There was a problem hiding this comment.
Verdict: REQUEST CHANGES · 1 blocker · 0 important · 0 nits · head c00b7c4b
- blocker (lane rule): this PR fixes a test that fails on main, so the body needs a
fails without the fix:line naming a test. The Test plan describesstyleRoots.test.tsfailing on main, but not on that line. Add something likefails without the fix: packages/lint/src/styleRoots.test.ts › "parses each style block once per lint, however many rules read it"(it gets 10postcss.parsecalls on main).
I found no code blocker.
Scope
Read the full diff (6 files). Every postcss.parse(style.content) on a lint-context style block now goes through styleRoot. The parse sites still left are separate inputs: inline style="" attributes at core.ts:713 and project CSS files at project.ts:684,752. None of the rules that now share the tree modify it (I searched for remove, replaceWith, append, clone* and .value/.prop/.selector assignments). A parse error is cached, then thrown again to each caller, and every caller still catches it: the syntax rule reports it once, the others skip the block. The WeakMap is keyed by the block object, the same pattern as scriptPositions.ts. The spy test hooks the same postcss.parse the rules call, so it would really count 10 parses on main. CI, CodeQL, regression and Windows are green at this head.
— Genesis
What
The linter now parses each
<style>block with postcss once per lint, and every rule shares that one tree. Before this change, ten rule passes each parsed the same block again.Why
gsap.test.ts"scans a closed style block of many unclosed rules in linear time" lints 40 000 unclosed rules and checks a 2 s limit. On CI it took about 2 s and failed by chance, on main as well as on unrelated PRs. The cost came from ten parses and ten throwaway trees per block, which also made the garbage collector work harder. It was not slow regex scanning. Every lint of a large stylesheet paid the same extra cost.Related work
None.
How
styleRoots.tscaches each block's parsed tree, or its parse error, in aWeakMapkeyed by the block. This follows the per-block cache inscriptPositions.ts. The cache lasts as long as the lint context does.core.ts,fonts.ts,gsap.tsandtextures.tsnow read the shared tree. No rule modifies it, so sharing it is safe. A comment onstyleRootnotes that rules must not modify it.Test plan
styleRoots.test.ts. One lint callspostcss.parseonce for a style block that several rules read; on main it is called 10 times, and that is how the test fails there. A malformed block is still reported once.tsc --noEmitpasses for the lint package. oxfmt and oxlint are clean on the changed files.postcss.parsecalls for the one block instead of 1. Shown by running the test against main's rule files.Size
This is a small, standalone lint fix. The flaky timing test fails on main and on any open PR, so it lands on its own rather than inside an unrelated studio change.