Skip to content

Commit 53ddb17

Browse files
committed
fix(parse/css/tailwind): make @custom-variant accept selector lists
1 parent c09e45c commit 53ddb17

File tree

12 files changed

+513
-243
lines changed

12 files changed

+513
-243
lines changed

.changeset/stale-windows-strive.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
The CSS parser, with `tailwindDirectives` enabled, will now accept lists of selectors in `@custom-variant` shorthand syntax.
6+
7+
```css
8+
@custom-variant cell (th:has(&), td:has(&));
9+
```

crates/biome_css_factory/src/generated/node_factory.rs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_css_factory/src/generated/syntax_factory.rs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_css_parser/src/syntax/at_rule/tailwind.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@ use crate::parser::CssParser;
33
use crate::syntax::block::{
44
parse_declaration_block, parse_declaration_or_rule_list_block, parse_rule_block,
55
};
6-
use crate::syntax::parse_error::{
7-
expected_identifier, expected_selector, expected_string, expected_tw_source,
8-
};
9-
use crate::syntax::selector::parse_selector;
6+
use crate::syntax::parse_error::{expected_identifier, expected_string, expected_tw_source};
7+
use crate::syntax::selector::SelectorList;
108
use crate::syntax::{is_at_identifier, parse_identifier, parse_regular_identifier, parse_string};
119
use biome_css_syntax::CssSyntaxKind::{self, *};
1210
use biome_css_syntax::T;
13-
use biome_parser::parse_lists::ParseNodeList;
11+
use biome_parser::parse_lists::{ParseNodeList, ParseSeparatedList};
1412
use biome_parser::parse_recovery::ParseRecoveryTokenSet;
1513
use biome_parser::parsed_syntax::ParsedSyntax;
1614
use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present};
@@ -133,7 +131,10 @@ fn parse_custom_variant_shorthand(p: &mut CssParser) -> ParsedSyntax {
133131
let m = p.start();
134132

135133
p.bump(T!['(']);
136-
parse_selector(p).or_add_diagnostic(p, expected_selector);
134+
let mut selector_list = SelectorList::default()
135+
.with_end_kind_ts(token_set![T![')']])
136+
.with_recovery_ts(token_set![T![')'], T![,], T![;]]);
137+
selector_list.parse_list(p);
137138
p.expect(T![')']);
138139
p.expect(T![;]);
139140

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/custom-variants/dark.css.snap

Lines changed: 73 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -25,51 +25,53 @@ CssRoot {
2525
},
2626
selector: TwCustomVariantShorthand {
2727
l_paren_token: L_PAREN@21..22 "(" [] [],
28-
selector: CssCompoundSelector {
29-
nesting_selectors: CssNestedSelectorList [
30-
CssNestedSelector {
31-
amp_token: AMP@22..23 "&" [] [],
32-
},
33-
],
34-
simple_selector: missing (optional),
35-
sub_selectors: CssSubSelectorList [
36-
CssPseudoClassSelector {
37-
colon_token: COLON@23..24 ":" [] [],
38-
class: CssPseudoClassFunctionSelectorList {
39-
name: CssIdentifier {
40-
value_token: IDENT@24..26 "is" [] [],
41-
},
42-
l_paren_token: L_PAREN@26..27 "(" [] [],
43-
selectors: CssSelectorList [
44-
CssComplexSelector {
45-
left: CssCompoundSelector {
46-
nesting_selectors: CssNestedSelectorList [],
47-
simple_selector: missing (optional),
48-
sub_selectors: CssSubSelectorList [
49-
CssClassSelector {
50-
dot_token: DOT@27..28 "." [] [],
51-
name: CssCustomIdentifier {
52-
value_token: IDENT@28..32 "dark" [] [],
28+
selector: CssSelectorList [
29+
CssCompoundSelector {
30+
nesting_selectors: CssNestedSelectorList [
31+
CssNestedSelector {
32+
amp_token: AMP@22..23 "&" [] [],
33+
},
34+
],
35+
simple_selector: missing (optional),
36+
sub_selectors: CssSubSelectorList [
37+
CssPseudoClassSelector {
38+
colon_token: COLON@23..24 ":" [] [],
39+
class: CssPseudoClassFunctionSelectorList {
40+
name: CssIdentifier {
41+
value_token: IDENT@24..26 "is" [] [],
42+
},
43+
l_paren_token: L_PAREN@26..27 "(" [] [],
44+
selectors: CssSelectorList [
45+
CssComplexSelector {
46+
left: CssCompoundSelector {
47+
nesting_selectors: CssNestedSelectorList [],
48+
simple_selector: missing (optional),
49+
sub_selectors: CssSubSelectorList [
50+
CssClassSelector {
51+
dot_token: DOT@27..28 "." [] [],
52+
name: CssCustomIdentifier {
53+
value_token: IDENT@28..32 "dark" [] [],
54+
},
5355
},
56+
],
57+
},
58+
combinator: CSS_SPACE_LITERAL@32..33 " " [] [],
59+
right: CssCompoundSelector {
60+
nesting_selectors: CssNestedSelectorList [],
61+
simple_selector: CssUniversalSelector {
62+
namespace: missing (optional),
63+
star_token: STAR@33..34 "*" [] [],
5464
},
55-
],
56-
},
57-
combinator: CSS_SPACE_LITERAL@32..33 " " [] [],
58-
right: CssCompoundSelector {
59-
nesting_selectors: CssNestedSelectorList [],
60-
simple_selector: CssUniversalSelector {
61-
namespace: missing (optional),
62-
star_token: STAR@33..34 "*" [] [],
65+
sub_selectors: CssSubSelectorList [],
6366
},
64-
sub_selectors: CssSubSelectorList [],
6567
},
66-
},
67-
],
68-
r_paren_token: R_PAREN@34..35 ")" [] [],
68+
],
69+
r_paren_token: R_PAREN@34..35 ")" [] [],
70+
},
6971
},
70-
},
71-
],
72-
},
72+
],
73+
},
74+
],
7375
r_paren_token: R_PAREN@35..36 ")" [] [],
7476
semicolon_token: SEMICOLON@36..37 ";" [] [],
7577
},
@@ -94,36 +96,37 @@ CssRoot {
9496
0: [email protected] "dark" [] [Whitespace(" ")]
9597
9698
0: [email protected] "(" [] []
97-
98-
99-
100-
0: [email protected] "&" [] []
101-
1: (empty)
102-
103-
104-
0: [email protected] ":" [] []
105-
106-
107-
0: [email protected] "is" [] []
108-
1: [email protected] "(" [] []
109-
110-
111-
112-
113-
1: (empty)
114-
115-
116-
0: [email protected] "." [] []
117-
118-
0: [email protected] "dark" [] []
119-
1: [email protected] " " [] []
120-
121-
122-
123-
0: (empty)
124-
1: [email protected] "*" [] []
125-
126-
3: [email protected] ")" [] []
99+
100+
101+
102+
103+
0: [email protected] "&" [] []
104+
1: (empty)
105+
106+
107+
0: [email protected] ":" [] []
108+
109+
110+
0: [email protected] "is" [] []
111+
1: [email protected] "(" [] []
112+
113+
114+
115+
116+
1: (empty)
117+
118+
119+
0: [email protected] "." [] []
120+
121+
0: [email protected] "dark" [] []
122+
1: [email protected] " " [] []
123+
124+
125+
126+
0: (empty)
127+
1: [email protected] "*" [] []
128+
129+
3: [email protected] ")" [] []
127130
2: [email protected] ")" [] []
128131
3: [email protected] ";" [] []
129132
2: [email protected] "" [Newline("\n")] []
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@custom-variant foobar (.foo, .bar);
2+
3+
@custom-variant cell (th:has(&), td:has(&));

0 commit comments

Comments
 (0)