fix: exclude the :has anchor from implicit descendants

MA
4h ago
+111-3

Fixes #1928.

:has(.a .b) must find a descendant .a containing .b; the element being tested cannot itself fill that first .a. The internal anchor is stored in an array so it can be replaced per element, but that array currently enables the inclusive descendant path intended for array query roots.

Keep a normal descendant combinator when the context is the :has() placeholder. Real array query roots retain their existing inclusive behavior. Add regressions for compile, is, and selectAll, with caching on and off, and controls for nested descendants, leading child/sibling combinators, and array roots. Update the existing cache test to expect only its outer div while retaining its cache-after-mutation check.

Validation: npm test passes (221 tests, one existing skipped test), including ESLint, TypeScript and Biome; npm run build passes. A 168-case simple-selector matrix agrees with local Chrome, and a freshly installed packed-package consumer passes the regression. The new negative/nested tests fail without the production fix.

This is separate from #1934's handling of relativeSelector: false inside nested functional selectors, and leaves #1930 and #1931 out of scope.

AI assistance was used to prepare this patch, regression tests, and description.

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes
    • Corrected :has matching so the anchor is excluded from implicit descendant matches, including when results are cached.
    • Improved matching for descendant, child, adjacent sibling, and general sibling selectors, including with array query roots.
    • Fixed nested functional selectors to preserve explicit :scope matching while keeping descendant matching behavior consistent.