Skip to content

Commit 1a7a4ae

Browse files
authored
Merge pull request #49 from bschlenk/master
feat: Add support for parsing & nesting selector
2 parents 3225295 + 300f177 commit 1a7a4ae

File tree

14 files changed

+333
-5
lines changed

14 files changed

+333
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ const parse = createParser({
179179
| `css-scoping-1` | Shadow DOM selectors (`:host`, `:host-context()`, `::slotted()`) |
180180
| `css-pseudo-4` | Modern pseudo-elements (`::selection`, `::backdrop`, etc.) |
181181
| `css-shadow-parts-1` | `::part()` for styling shadow DOM components |
182+
| `css-nesting-1` | CSS Nesting selector (`&`) |
182183

183184
The `latest` syntax automatically includes all modules marked as current specifications.
184185

docs/interfaces/AstFactory.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ console.log(ast.isRule(selector)); // prints false
5252
- [isFormulaOfSelector](AstFactory.md#isformulaofselector)
5353
- [isId](AstFactory.md#isid)
5454
- [isNamespaceName](AstFactory.md#isnamespacename)
55+
- [isNestingSelector](AstFactory.md#isnestingselector)
5556
- [isNoNamespace](AstFactory.md#isnonamespace)
5657
- [isPseudoClass](AstFactory.md#ispseudoclass)
5758
- [isPseudoElement](AstFactory.md#ispseudoelement)
@@ -63,6 +64,7 @@ console.log(ast.isRule(selector)); // prints false
6364
- [isWildcardNamespace](AstFactory.md#iswildcardnamespace)
6465
- [isWildcardTag](AstFactory.md#iswildcardtag)
6566
- [namespaceName](AstFactory.md#namespacename)
67+
- [nestingSelector](AstFactory.md#nestingselector)
6668
- [noNamespace](AstFactory.md#nonamespace)
6769
- [pseudoClass](AstFactory.md#pseudoclass)
6870
- [pseudoElement](AstFactory.md#pseudoelement)
@@ -317,6 +319,27 @@ ___
317319
entity is AstNamespaceName
318320

319321

322+
___
323+
324+
### isNestingSelector
325+
326+
**isNestingSelector**: (`entity`: `unknown`) => entity is AstNestingSelector
327+
328+
#### Type declaration
329+
330+
▸ (`entity`): entity is AstNestingSelector
331+
332+
##### Parameters
333+
334+
| Name | Type |
335+
| :------ | :------ |
336+
| `entity` | `unknown` |
337+
338+
##### Returns
339+
340+
entity is AstNestingSelector
341+
342+
320343
___
321344

322345
### isNoNamespace
@@ -549,6 +572,27 @@ ___
549572
[`AstNamespaceName`](AstNamespaceName.md)
550573

551574

575+
___
576+
577+
### nestingSelector
578+
579+
**nestingSelector**: (`props?`: {}) => [`AstNestingSelector`](AstNestingSelector.md)
580+
581+
#### Type declaration
582+
583+
▸ (`props?`): [`AstNestingSelector`](AstNestingSelector.md)
584+
585+
##### Parameters
586+
587+
| Name | Type |
588+
| :------ | :------ |
589+
| `props?` | `Object` |
590+
591+
##### Returns
592+
593+
[`AstNestingSelector`](AstNestingSelector.md)
594+
595+
552596
___
553597

554598
### noNamespace
@@ -620,7 +664,7 @@ ___
620664

621665
### rule
622666

623-
**rule**: (`props`: { `combinator?`: `string` ; `items`: ([`AstTagName`](AstTagName.md) \| [`AstWildcardTag`](AstWildcardTag.md) \| [`AstId`](AstId.md) \| [`AstClassName`](AstClassName.md) \| [`AstPseudoClass`](AstPseudoClass.md) \| [`AstAttribute`](AstAttribute.md) \| [`AstPseudoElement`](AstPseudoElement.md))[] ; `nestedRule?`: [`AstRule`](AstRule.md) }) => [`AstRule`](AstRule.md)
667+
**rule**: (`props`: { `combinator?`: `string` ; `items`: ([`AstTagName`](AstTagName.md) \| [`AstWildcardTag`](AstWildcardTag.md) \| [`AstId`](AstId.md) \| [`AstClassName`](AstClassName.md) \| [`AstNestingSelector`](AstNestingSelector.md) \| [`AstPseudoClass`](AstPseudoClass.md) \| [`AstAttribute`](AstAttribute.md) \| [`AstPseudoElement`](AstPseudoElement.md))[] ; `nestedRule?`: [`AstRule`](AstRule.md) }) => [`AstRule`](AstRule.md)
624668

625669
#### Type declaration
626670

@@ -632,7 +676,7 @@ ___
632676
| :------ | :------ | :------ |
633677
| `props` | `Object` | - |
634678
| `props.combinator?` | `string` | Rule combinator which was used to nest this rule (i.e. `">"` in case of `"div > span"` if the current rule is `"span"`). |
635-
| `props.items` | ([`AstTagName`](AstTagName.md) \| [`AstWildcardTag`](AstWildcardTag.md) \| [`AstId`](AstId.md) \| [`AstClassName`](AstClassName.md) \| [`AstPseudoClass`](AstPseudoClass.md) \| [`AstAttribute`](AstAttribute.md) \| [`AstPseudoElement`](AstPseudoElement.md))[] | Items of a CSS rule. Can be tag, ids, class names, pseudo-classes and pseudo-elements. |
679+
| `props.items` | ([`AstTagName`](AstTagName.md) \| [`AstWildcardTag`](AstWildcardTag.md) \| [`AstId`](AstId.md) \| [`AstClassName`](AstClassName.md) \| [`AstNestingSelector`](AstNestingSelector.md) \| [`AstPseudoClass`](AstPseudoClass.md) \| [`AstAttribute`](AstAttribute.md) \| [`AstPseudoElement`](AstPseudoElement.md))[] | Items of a CSS rule. Can be tag, ids, class names, pseudo-classes and pseudo-elements. |
636680
| `props.nestedRule?` | [`AstRule`](AstRule.md) | Nested rule if specified (i.e. `"div > span"`). |
637681

638682
##### Returns
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[css-selector-parser](../../README.md) / [Exports](../modules.md) / AstNestingSelector
2+
3+
# Interface: AstNestingSelector
4+
5+
CSS Nesting Selector: `&`.
6+
Represents the parent selector in nested CSS.
7+
Generated by [ast.nestingSelector](AstFactory.md#nestingselector).
8+
9+
**`See`**
10+
11+
https://www.w3.org/TR/css-nesting-1/#nest-selector
12+
13+
**`Example`**
14+
15+
```ts
16+
"&:hover"
17+
```
18+
19+
## Table of contents
20+
21+
### Properties
22+
23+
- [type](AstNestingSelector.md#type)
24+
25+
## Properties
26+
27+
### type
28+
29+
**type**: ``"NestingSelector"``

docs/interfaces/AstRule.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ ___
2727

2828
### items
2929

30-
**items**: ([`AstTagName`](AstTagName.md) \| [`AstWildcardTag`](AstWildcardTag.md) \| [`AstId`](AstId.md) \| [`AstClassName`](AstClassName.md) \| [`AstPseudoClass`](AstPseudoClass.md) \| [`AstAttribute`](AstAttribute.md) \| [`AstPseudoElement`](AstPseudoElement.md))[]
30+
**items**: ([`AstTagName`](AstTagName.md) \| [`AstWildcardTag`](AstWildcardTag.md) \| [`AstId`](AstId.md) \| [`AstClassName`](AstClassName.md) \| [`AstNestingSelector`](AstNestingSelector.md) \| [`AstPseudoClass`](AstPseudoClass.md) \| [`AstAttribute`](AstAttribute.md) \| [`AstPseudoElement`](AstPseudoElement.md))[]
3131

3232
Items of a CSS rule. Can be tag, ids, class names, pseudo-classes and pseudo-elements.
3333

docs/interfaces/SyntaxDefinition.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ CSS Selector Syntax Definition can be used to define custom CSS selector parsing
1313
- [classNames](SyntaxDefinition.md#classnames)
1414
- [combinators](SyntaxDefinition.md#combinators)
1515
- [ids](SyntaxDefinition.md#ids)
16+
- [modules](SyntaxDefinition.md#modules)
1617
- [namespace](SyntaxDefinition.md#namespace)
18+
- [nestingSelector](SyntaxDefinition.md#nestingselector)
1719
- [pseudoClasses](SyntaxDefinition.md#pseudoclasses)
1820
- [pseudoElements](SyntaxDefinition.md#pseudoelements)
1921
- [tag](SyntaxDefinition.md#tag)
@@ -101,6 +103,21 @@ https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors/Typ
101103

102104
___
103105

106+
### modules
107+
108+
`Optional` **modules**: (``"css-position-1"`` \| ``"css-position-2"`` \| ``"css-position-3"`` \| ``"css-position-4"`` \| ``"css-scoping-1"`` \| ``"css-pseudo-4"`` \| ``"css-shadow-parts-1"`` \| ``"css-nesting-1"``)[]
109+
110+
Additional CSS modules to include in the syntax definition.
111+
These are specific CSS modules that add new selectors or modify existing ones.
112+
113+
**`Example`**
114+
115+
```ts
116+
['css-position-4', 'css-scoping-1']
117+
```
118+
119+
___
120+
104121
### namespace
105122

106123
`Optional` **namespace**: `boolean` \| { `wildcard?`: `boolean` }
@@ -119,6 +136,25 @@ https://www.w3.org/TR/css3-namespace/
119136

120137
___
121138

139+
### nestingSelector
140+
141+
`Optional` **nestingSelector**: `boolean`
142+
143+
CSS Nesting Selector (&).
144+
Represents the parent selector in nested CSS.
145+
146+
**`Example`**
147+
148+
```ts
149+
&:hover
150+
```
151+
152+
**`See`**
153+
154+
https://www.w3.org/TR/css-nesting-1/#nest-selector
155+
156+
___
157+
122158
### pseudoClasses
123159

124160
`Optional` **pseudoClasses**: ``false`` \| { `definitions?`: { `Formula`: `undefined` \| `string`[] ; `FormulaOfSelector`: `undefined` \| `string`[] ; `NoArgument`: `undefined` \| `string`[] ; `Selector`: `undefined` \| `string`[] ; `String`: `undefined` \| `string`[] } ; `unknown?`: ``"accept"`` \| ``"reject"`` }

docs/modules.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- [AstFormulaOfSelector](interfaces/AstFormulaOfSelector.md)
1414
- [AstId](interfaces/AstId.md)
1515
- [AstNamespaceName](interfaces/AstNamespaceName.md)
16+
- [AstNestingSelector](interfaces/AstNestingSelector.md)
1617
- [AstNoNamespace](interfaces/AstNoNamespace.md)
1718
- [AstPseudoClass](interfaces/AstPseudoClass.md)
1819
- [AstPseudoElement](interfaces/AstPseudoElement.md)
@@ -30,6 +31,7 @@
3031

3132
- [AstEntity](modules.md#astentity)
3233
- [CssLevel](modules.md#csslevel)
34+
- [CssModule](modules.md#cssmodule)
3335
- [Parser](modules.md#parser)
3436

3537
### Variables
@@ -45,7 +47,7 @@
4547

4648
### AstEntity
4749

48-
Ƭ **AstEntity**: [`AstSelector`](interfaces/AstSelector.md) \| [`AstRule`](interfaces/AstRule.md) \| [`AstTagName`](interfaces/AstTagName.md) \| [`AstWildcardTag`](interfaces/AstWildcardTag.md) \| [`AstId`](interfaces/AstId.md) \| [`AstClassName`](interfaces/AstClassName.md) \| [`AstNamespaceName`](interfaces/AstNamespaceName.md) \| [`AstWildcardNamespace`](interfaces/AstWildcardNamespace.md) \| [`AstNoNamespace`](interfaces/AstNoNamespace.md) \| [`AstSubstitution`](interfaces/AstSubstitution.md) \| [`AstString`](interfaces/AstString.md) \| [`AstFormula`](interfaces/AstFormula.md) \| [`AstFormulaOfSelector`](interfaces/AstFormulaOfSelector.md) \| [`AstPseudoClass`](interfaces/AstPseudoClass.md) \| [`AstAttribute`](interfaces/AstAttribute.md) \| [`AstPseudoElement`](interfaces/AstPseudoElement.md)
50+
Ƭ **AstEntity**: [`AstSelector`](interfaces/AstSelector.md) \| [`AstRule`](interfaces/AstRule.md) \| [`AstTagName`](interfaces/AstTagName.md) \| [`AstWildcardTag`](interfaces/AstWildcardTag.md) \| [`AstId`](interfaces/AstId.md) \| [`AstClassName`](interfaces/AstClassName.md) \| [`AstNamespaceName`](interfaces/AstNamespaceName.md) \| [`AstWildcardNamespace`](interfaces/AstWildcardNamespace.md) \| [`AstNoNamespace`](interfaces/AstNoNamespace.md) \| [`AstNestingSelector`](interfaces/AstNestingSelector.md) \| [`AstSubstitution`](interfaces/AstSubstitution.md) \| [`AstString`](interfaces/AstString.md) \| [`AstFormula`](interfaces/AstFormula.md) \| [`AstFormulaOfSelector`](interfaces/AstFormulaOfSelector.md) \| [`AstPseudoClass`](interfaces/AstPseudoClass.md) \| [`AstAttribute`](interfaces/AstAttribute.md) \| [`AstPseudoElement`](interfaces/AstPseudoElement.md)
4951

5052
One of CSS AST entity types.
5153

@@ -57,6 +59,26 @@ ___
5759

5860
___
5961

62+
### CssModule
63+
64+
Ƭ **CssModule**: keyof typeof `cssModules`
65+
66+
CSS Module name.
67+
68+
**`Example`**
69+
70+
```ts
71+
'css-position-3'
72+
```
73+
74+
**`Example`**
75+
76+
```ts
77+
'css-scoping-1'
78+
```
79+
80+
___
81+
6082
### Parser
6183

6284
Ƭ **Parser**: (`input`: `string`) => [`AstSelector`](interfaces/AstSelector.md)
@@ -133,6 +155,7 @@ Creates a parse function to be used later to parse CSS selectors.
133155
| Name | Type | Description |
134156
| :------ | :------ | :------ |
135157
| `options` | `Object` | - |
158+
| `options.modules?` | (``"css-position-1"`` \| ``"css-position-2"`` \| ``"css-position-3"`` \| ``"css-position-4"`` \| ``"css-scoping-1"`` \| ``"css-pseudo-4"`` \| ``"css-shadow-parts-1"`` \| ``"css-nesting-1"``)[] | Additional CSS modules to include in the syntax definition. These are specific CSS modules that add new selectors or modify existing ones. **`Example`** ```ts ['css-position-3', 'css-scoping-1'] ``` |
136159
| `options.strict?` | `boolean` | CSS selector parser in modern browsers is very forgiving. For instance, it works fine with unclosed attribute selectors: `"[attr=value"`. Set to `false` in order to mimic browser behaviour. Default: `true` |
137160
| `options.substitutes?` | `boolean` | Flag to enable substitutes. This is not part of CSS syntax, but rather a useful feature to pass variables into CSS selectors. Default: `false` **`Example`** ```ts "[attr=$variable]" ``` |
138161
| `options.syntax?` | [`CssLevel`](modules.md#csslevel) \| [`SyntaxDefinition`](interfaces/SyntaxDefinition.md) | CSS Syntax options to be used for parsing. Can either be one of the predefined CSS levels ([CssLevel](modules.md#csslevel)) or a more detailed syntax definition ([SyntaxDefinition](interfaces/SyntaxDefinition.md)). Default: `"latest"` |

src/ast.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@ export interface AstSelector {
2020
export interface AstRule {
2121
type: 'Rule';
2222
/** Items of a CSS rule. Can be tag, ids, class names, pseudo-classes and pseudo-elements. */
23-
items: (AstTagName | AstWildcardTag | AstId | AstClassName | AstAttribute | AstPseudoClass | AstPseudoElement)[];
23+
items: (
24+
| AstTagName
25+
| AstWildcardTag
26+
| AstId
27+
| AstClassName
28+
| AstAttribute
29+
| AstPseudoClass
30+
| AstPseudoElement
31+
| AstNestingSelector
32+
)[];
2433
/** Rule combinator which was used to nest this rule (i.e. `">"` in case of `"div > span"` if the current rule is `"span"`). */
2534
combinator?: string;
2635
/** Nested rule if specified (i.e. `"div > span"`). */
@@ -194,6 +203,17 @@ export interface AstFormulaOfSelector {
194203
/** Selector that goes after formula (i.e. `"div -> span"` in case of `":nth-child(2n + 1 of div > span)"` */
195204
selector: AstRule;
196205
}
206+
/**
207+
* CSS Nesting Selector: `&`.
208+
* Represents the parent selector in nested CSS.
209+
* Generated by {@link AstFactory.nestingSelector ast.nestingSelector}.
210+
* @see https://www.w3.org/TR/css-nesting-1/#nest-selector
211+
* @example "&:hover"
212+
*/
213+
export interface AstNestingSelector {
214+
type: 'NestingSelector';
215+
}
216+
197217
/**
198218
* Substitution is not part of CSS spec, but rather a useful extension on top of CSS if you need to pass variables.
199219
* Generated by {@link AstFactory.substitution ast.substitution}.
@@ -221,6 +241,7 @@ export type AstEntity =
221241
| AstNamespaceName
222242
| AstWildcardNamespace
223243
| AstNoNamespace
244+
| AstNestingSelector
224245
| AstSubstitution
225246
| AstString
226247
| AstFormula
@@ -343,6 +364,7 @@ export const ast: AstFactory = {
343364
...astMethods<AstNamespaceName>('NamespaceName')('namespaceName', 'isNamespaceName'),
344365
...astMethods<AstWildcardNamespace>('WildcardNamespace')('wildcardNamespace', 'isWildcardNamespace'),
345366
...astMethods<AstNoNamespace>('NoNamespace')('noNamespace', 'isNoNamespace'),
367+
...astMethods<AstNestingSelector>('NestingSelector')('nestingSelector', 'isNestingSelector'),
346368
...astMethods<AstAttribute>('Attribute')('attribute', 'isAttribute'),
347369
...astMethods<AstPseudoClass>('PseudoClass')('pseudoClass', 'isPseudoClass'),
348370
...astMethods<AstPseudoElement>('PseudoElement')('pseudoElement', 'isPseudoElement'),

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export {
1010
AstFormulaOfSelector,
1111
AstId,
1212
AstNamespaceName,
13+
AstNestingSelector,
1314
AstNoNamespace,
1415
AstPseudoClass,
1516
AstPseudoElement,

src/parser.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export function createParser(
117117
: [false, false];
118118
const idEnabled = Boolean(syntaxDefinition.ids);
119119
const classNamesEnabled = Boolean(syntaxDefinition.classNames);
120+
const nestingSelectorEnabled = Boolean(syntaxDefinition.nestingSelector);
120121
const namespaceEnabled = Boolean(syntaxDefinition.namespace);
121122
const namespaceWildcardEnabled =
122123
syntaxDefinition.namespace &&
@@ -743,6 +744,10 @@ export function createParser(
743744
const idName = parseIdentifier();
744745
assert(idName, 'Expected ID name.');
745746
rule.items.push({type: 'Id', name: idName});
747+
} else if (is('&')) {
748+
assert(nestingSelectorEnabled, 'Nesting selector is not enabled.');
749+
next();
750+
rule.items.push({type: 'NestingSelector'});
746751
} else if (is('[')) {
747752
assert(attributesEnabled, 'Attributes are not enabled.');
748753
rule.items.push(parseAttribute());

src/render.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ export function render(entity: AstEntity): string {
8686
return `#${escapeIdentifier(entity.name)}`;
8787
} else if (entity.type === 'ClassName') {
8888
return `.${escapeIdentifier(entity.name)}`;
89+
} else if (entity.type === 'NestingSelector') {
90+
return '&';
8991
} else if (entity.type === 'Attribute') {
9092
const {name, namespace, operator, value, caseSensitivityModifier} = entity;
9193
let result = '[';

0 commit comments

Comments
 (0)