Skip to content

Fix specificity of Selectors Level 4 functional pseudo-classes#79

Open
gaoflow wants to merge 2 commits into
jaraco:mainfrom
gaoflow:fix-functional-pseudo-specificity
Open

Fix specificity of Selectors Level 4 functional pseudo-classes#79
gaoflow wants to merge 2 commits into
jaraco:mainfrom
gaoflow:fix-functional-pseudo-specificity

Conversation

@gaoflow

@gaoflow gaoflow commented Jul 7, 2026

Copy link
Copy Markdown

Problem

Following #75 (parsing of the Level 4 functional pseudo-classes) and #76
(specificity of simple pseudo-classes), the functional selector-list
pseudo-classes are still scored incorrectly. :is(), :has(), :matches()
and :any() were each counted as a single class, and the selector passed as
their argument was not counted at all — so their specificity was always
(0, 0, 1, 0) regardless of the argument. :where() was scored the same way.

Per the CSS Selectors spec:

  • :is(), :has(), :matches(), :any() take the specificity of the most
    specific complex selector in their argument.
  • :where() always contributes zero.
  • the pseudo-class function itself does not add a class.
:is(#a)          was (0,0,1,0)  should be (0,1,0,0)
:where(#a)       was (0,0,1,0)  should be (0,0,0,0)
:is(.x .y #z)    was (0,0,1,0)  should be (0,1,2,0)

No test locked in the old values, so this is a latent scoring bug rather than
intended behavior.

Fix

Track the currently-open selector-list pseudo-classes on a stack. While inside
one whose arguments count toward specificity, fold its argument's tokens into
the running specificity as usual; a token is skipped if it is nested inside any
enclosing :where() (which zeroes everything it contains, including a nested
:is()). The functional pseudo token itself is no longer counted as a class.

Tests

Extended test_specificity with the functional pseudo-classes and their
nesting, e.g. :where(#a)(0,0,0,0), :is(#a)(0,1,0,0),
:is(.x .y #z)(0,1,2,0), :has(option:checked)(0,0,1,1),
div:is(.a):where(#b)(0,0,1,1), and nested cases :is(:where(#a)) /
:where(:is(#a))(0,0,0,0). Full suite: 396 passed, 43 xfailed.

gaoflow added 2 commits July 7, 2026 14:05
The functional selector-list pseudo-classes :is(), :has(), :matches() and
:any() were each counted as a single class and their arguments were not
counted at all, so their specificity was always (0,0,1,0). Per the CSS
Selectors specification they contribute the specificity of their argument
instead, and :where() always contributes zero.

Track the open selector-list pseudo-classes on a stack and, while inside
one whose arguments count, fold their tokens into the specificity as usual;
skip them inside any enclosing :where(). The function token itself no longer
counts as a class.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant