fix(compiler): answer an ancestor condition that names no class - #462
Open
YevheniiKotyrlo wants to merge 2 commits into
Open
YevheniiKotyrlo wants to merge 2 commits into
YevheniiKotyrlo wants to merge 2 commits into
Conversation
`parseComponents` opens a container-query ref at a descendant combinator and every later component in that compound writes its condition onto it, but only the `class` arm attached that ref to the rule. A compound identified by a condition alone — `[data-state="on"] .x`, `:hover .x`, `:disabled .x` — therefore wrote onto an object nothing read, and the rule shipped with no condition: it applied to every element its class named, in every state. Attach the ref when a condition is written onto it, idempotently, so a compound carrying both a condition and a class attaches once. The condition then resolves against the default container, which is what the `:is()` / `:where()` spelling of the same selector already compiled to, and an absent container answers false rather than true.
…n unanswered combinator drops
Contributor
Author
This branch has not been deployed
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
A descendant combinator opens a container-query ref and every later component in that compound writes its condition onto it — but only the
classarm ever attaches that ref to the rule, so an ancestor identified by a condition alone is discarded and the rule applies unconditionally.Measured, the
cqeach rule compiles to:[data-state="on"] .x[{"a":[["d","state","=","on"]]}]:hover .x[{"p":{"h":1}}]:disabled .x[{"a":[["a","disabled"]]}].g[data-state="on"] .x[{"a":[…],"n":"g:g"}].x:where([data-state="on"] *)[{"a":[["d","state","=","on"]]}]The last two rows are what make it a defect rather than a limit.
parseIsWhereComponentsalready emits an unnamed container query for the:where(… *)spelling, and the class arm already attaches a named one — so two spellings of one selector disagree, and the one an author hand-writes is the one that fails open.Normatively the ancestor half is Selectors 4 §14.1 — a descendant combinator asks for an ancestor matching the whole compound to its left, so a condition in that compound is part of what must match rather than decoration on it.
Solution
Attach when a condition is written, not only when a class names it.
attachContainerQueryis idempotent, so a compound carrying both attaches once and the named path stays byte-identical.The condition then resolves against
DEFAULT_CONTAINER_NAMEexactly as the:where()spelling does; with no container above,testContainerQueryanswersfalse, so a selector this engine cannot answer fails closed instead of open.Tests
src/__tests__/compiler/ancestor-conditions.test.ts— 13 cases: each row above, a chain of two classless ancestors, a named and a classless one together, a multi-class compound still yielding one query, that the combinator spelling now compiles to what:where()already did, that a compound on ONE element stays the subject's, and that an unanswerable combinator still drops the rule.src/__tests__/native/ancestor-conditions.test.tsx— 7 cases through the real runtime: withheld under a container that is not hovered and applied when it is, withheld where no ancestor can answer it, withheld from a sibling's subtree, and a named ancestor still answering from its own element.Mutation-proved: restricting the attach to a named ref — the pre-fix behaviour — turns 13 of the 20 red.
Every ancestor-state test in the suite names a class, because that is what Tailwind emits:
group-hover:compiles to.group:hover .x, which was always correct. The classless form is what an author hand-writes in a plain stylesheet, and nothing exercised it.Verification
yarn test1068 passed ·yarn typecheck0 ·yarn lint0. The three failures are two babel suites that fail identically on an untouchedmainworktree (Windows-only module-specifier rewrites) — this touches no babel file.Known limits
An unnamed query resolves the nearest ancestor that established a container, not any ancestor — under-matching, and the same approximation
:where(… *)already makes. It cannot go further because the stylesheet is class-keyed:.a .xemits["a", [{c:["g:a"]}]]and makes the ancestor addressable, while[data-p] .xemits no such rule, so an element carrying no class the sheet names can register nothing. Making every element addressable is a wire-format decision.testContainerQuery's attribute arm is commented out onmain, so an emittedaquery is not yet evaluated — #442 is that half. Theparm is evaluated today, so:hover/:active/:focusancestors are fully fixed here.Independent of #463, which answers the ORDER of an ancestor chain in the runtime. They touch no common file.
Base
Branched off
f70c402.mainhas since taken #451 (a5002c5). 1 of the 3 files this changes also moved there (src/compiler/selector-builder.ts), and it still merges cleanly onto currentmain. Every measurement above was taken onf70c402. Say the word and I will re-apply it onto currentmain.