A utility-first CSS framework for rapid UI development.
Add `list`, `compound`, and `complex` Selector nodes (#20088)
This PR introduces a few more nodes in the `SelectorParser`:
- A `list` node
- A `complex` node
- A `compound` node
These names are closer to the CSS Selector AST names
(https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Selectors/Selector_structure),
and are also used in other libraries.
The problem today is that there are situations where we parse a selector
like: `#a.b > .c, .d` as:
```ts
[
{ kind: 'selector', value: '#a' },
{ kind: 'selector', value: '.b' },
{ kind: 'combinator', value: ' > ' },
{ kind: 'selector', value: '.c' },
{ kind: 'separator', value: ', ' },
{ kind: 'selector', value: '.d' }
]
```
Which is a very simple structure, but this contains a flaw that is
annoying to deal with in practice: In order to determine that we are
dealing with multiple selectors, we have to loop through the nodes and
see if a separator occurs somewhere.
The other fun thing is that we already know the difference between
selectors, combinators and separators. So if we tweak this structure a
little bit during parsing, then we can answer the question from above in
a much simpler way:
With this PR, we will parse the selector as:
```ts
[
{
kind: 'list',
nodes: [
{
kind: 'complex',
nodes: [
{
kind: 'compound',
nodes: [
{ kind: 'selector', value: '#a' },
{ kind: 'selector', value: '.b' }
]
},
{ kind: 'combinator', value: '>' },
{ kind: 'selector', value: '.c' }
]
},
{ kind: 'selector', value: '.d' }
]
}
]
```
It definitely looks more complex, but now that we have a `list` node, we
already know that we are dealing with multiple selectors.
If you squint your eyes, in the inner part there is a `compound`
selector. This is essentially a node where each sub-node can be squished
together with no spaces whatsoever.
The `complex` selector is there just to group everything together. In
other tools, a complex selector is often represented as:
```ts
{
kind: 'complex',
combinator: '>',
lhs: { … },
rhs: { … },
}
```
While I want to have the concept of a `complex` node, I didn't go with
this syntax just because I want to keep the concept of `nodes` which
means that we don't need any special handling when using `walk` (which
loops over `.nodes` internally).
The reason this complex node exists is because otherwise you would end
up with this structure:
```ts
[
{
kind: 'list',
nodes: [
{
kind: 'compound',
nodes: [
{ kind: 'selector', value: '#a' },
{ kind: 'selector', value: '.b' }
]
},
{ kind: 'combinator', value: '>' },
{ kind: 'selector', value: '.c' }
{ kind: 'selector', value: '.d' }
]
}
]
```
But if you look at the `list` node now, it's not clear that we are
dealing with `2` selectors since there are 4 nodes. We could solve this
by re-introducing the separator node (`,`). The fact that the `list`
exists tells us that we're dealing with `n` selectors. But to know which
selectors we're dealing with, then we have to look for that `,` node
again, which introduces the original problem.
This is just an internal refactor to make future changes easier.
## Test plan
1. Everything still works as expected (all tests pass)
2. No public API breaking changes, this parser was never exposed R
Robin Malfait committed
fc17df05f2a5f0b907cb89c2d82ed38c907a6879
Parent: cea8c97
Committed by GitHub <noreply@github.com>
on 5/20/2026, 1:04:05 PM