fix: avoid reusing descendant results across :has anchors
A compiled selector such as :has(:is(.c .b) :disabled) can return different matches depending on the order in which elements are tested. Its descendant cache reuses an ancestor's answer after :has() changes the anchor that answer depends on.
Disable descendant result caching when compiling against the mutable :has() placeholder. Descendant caches with a fixed query context and the separate text/subtree caches retain their existing behavior.
Add regressions for forward/reverse evaluation, repeated queries, functional aliases, negation, child combinators, and combinations with :contains() / :icontains(). A control verifies that fixed-context descendant caching still avoids repeated work.
Validation: npm test passes (230 tests, one existing skip, including ESLint, TypeScript and Biome); npm run build passes on Node 24.22.1. Nine new tests fail on the unchanged base. A deterministic 8,050-check matrix agrees with caching disabled, including after installing the packed npm archive.
Fixes #1929.
A separate Codex reviewer checked 6,048 generated results against an independent DOM relationship oracle for both the candidate and an installed package; all pass, while the original produces 4,719 mismatches. Another 26 checks confirm stable-context and independent text caches keep working.
OpenAI Codex implemented and tested this change; a separate Codex agent reviewed it. No human review is claimed.
Summary by CodeRabbit
- Bug Fixes
- Corrected result caching for selectors using nested pseudos inside
:has, improving match consistency across query orders and whole-document searches.
- Corrected result caching for selectors using nested pseudos inside
- Tests
- Added coverage for cached and uncached results, reversed evaluation order, and stable query contexts.