Skip to content

Commit bd254c7

Browse files
authored
fix(parse/css): fix parsing --*: initial; with tailwindDirectives enabled (#7852)
1 parent 8919e02 commit bd254c7

File tree

12 files changed

+316
-29
lines changed

12 files changed

+316
-29
lines changed

.changeset/itchy-aliens-ask.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed #7843: The CSS parser, when `tailwindDirectives` is enabled, correctly parses `--*: initial;`.

crates/biome_css_factory/src/generated/node_factory.rs

Lines changed: 27 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_css_parser/src/lexer/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,12 @@ impl<'src> CssLexer<'src> {
10111011
&& current == b'-'
10121012
&& self.peek_byte() == Some(b'*')
10131013
{
1014+
// HACK: handle `--*`
1015+
if self.prev_byte() == Some(b'-') {
1016+
self.advance(1);
1017+
return Some(current as char);
1018+
}
1019+
// otherwise, handle cases like `--color-*`
10141020
return None;
10151021
}
10161022

@@ -1305,6 +1311,7 @@ impl<'src> CssLexer<'src> {
13051311
// or the third and fourth code points are a valid escape
13061312
// return true.
13071313
BSL => self.is_valid_escape_at(3),
1314+
MUL => true,
13081315
_ => false,
13091316
}
13101317
}

crates/biome_css_parser/src/syntax/property/mod.rs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@ use crate::parser::CssParser;
66
use crate::syntax::css_modules::{
77
composes_not_allowed, expected_classes_list, expected_composes_import_source,
88
};
9-
use crate::syntax::parse_error::{expected_component_value, expected_identifier};
9+
use crate::syntax::parse_error::{
10+
expected_component_value, expected_identifier, tailwind_disabled,
11+
};
1012
use crate::syntax::{
11-
is_at_any_value, is_at_dashed_identifier, is_at_identifier, is_at_string, parse_any_value,
12-
parse_custom_identifier_with_keywords, parse_dashed_identifier, parse_regular_identifier,
13-
parse_string,
13+
CssSyntaxFeatures, is_at_any_value, is_at_dashed_identifier, is_at_identifier, is_at_string,
14+
parse_any_value, parse_custom_identifier_with_keywords, parse_dashed_identifier,
15+
parse_regular_identifier, parse_string,
1416
};
1517
use biome_css_syntax::CssSyntaxKind::*;
1618
use biome_css_syntax::{CssSyntaxKind, T};
1719
use biome_parser::parse_lists::ParseNodeList;
1820
use biome_parser::parse_recovery::{ParseRecovery, ParseRecoveryTokenSet, RecoveryResult};
1921
use biome_parser::prelude::ParsedSyntax;
2022
use biome_parser::prelude::ParsedSyntax::{Absent, Present};
21-
use biome_parser::{Parser, TokenSet, token_set};
23+
use biome_parser::{Parser, SyntaxFeature, TokenSet, token_set};
2224

2325
#[inline]
2426
pub(crate) fn is_at_any_property(p: &mut CssParser) -> bool {
@@ -160,7 +162,11 @@ const END_OF_COMPOSES_CLASS_TOKEN_SET: TokenSet<CssSyntaxKind> =
160162
#[inline]
161163
fn is_at_generic_property(p: &mut CssParser) -> bool {
162164
is_at_identifier(p)
163-
&& (p.nth_at(1, T![:]) || (p.nth_at(1, T![-]) && p.nth_at(2, T![*]) && p.nth_at(3, T![:])))
165+
&& (p.nth_at(1, T![:])
166+
// handle --*:
167+
|| (p.nth_at(1, T![*]) && p.nth_at(2, T![:]))
168+
// handle --color-*:
169+
|| (p.nth_at(1, T![-]) && p.nth_at(2, T![*]) && p.nth_at(3, T![:])))
164170
}
165171

166172
#[inline]
@@ -174,13 +180,22 @@ fn parse_generic_property(p: &mut CssParser) -> ParsedSyntax {
174180
if is_at_dashed_identifier(p) {
175181
let ident = parse_dashed_identifier(p).ok();
176182
if let Some(ident) = ident
177-
&& p.options().is_tailwind_directives_enabled()
178-
&& p.at(T![-])
183+
&& p.at_ts(token_set![T![-], T![*]])
179184
{
180-
let m = ident.precede(p);
181-
p.expect(T![-]);
182-
p.expect(T![*]);
183-
m.complete(p, TW_VALUE_THEME_REFERENCE);
185+
CssSyntaxFeatures::Tailwind
186+
.parse_exclusive_syntax(
187+
p,
188+
|p| {
189+
let m = ident.precede(p);
190+
if p.at(T![-]) {
191+
p.expect(T![-]);
192+
}
193+
p.expect(T![*]);
194+
Present(m.complete(p, TW_VALUE_THEME_REFERENCE))
195+
},
196+
|p, m| tailwind_disabled(p, m.range(p)),
197+
)
198+
.ok();
184199
}
185200
} else {
186201
parse_regular_identifier(p).ok();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* Negative test for crates/biome_css_parser/tests/css_test_suite/ok/tailwind/theme/custom-theme.css */
2+
3+
.reset {
4+
--*: initial;
5+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
source: crates/biome_css_parser/tests/spec_test.rs
3+
expression: snapshot
4+
---
5+
## Input
6+
7+
```css
8+
/* Negative test for crates/biome_css_parser/tests/css_test_suite/ok/tailwind/theme/custom-theme.css */
9+
10+
.reset {
11+
--*: initial;
12+
}
13+
14+
```
15+
16+
17+
## AST
18+
19+
```
20+
CssRoot {
21+
bom_token: missing (optional),
22+
rules: CssRuleList [
23+
CssQualifiedRule {
24+
prelude: CssSelectorList [
25+
CssCompoundSelector {
26+
nesting_selectors: CssNestedSelectorList [],
27+
simple_selector: missing (optional),
28+
sub_selectors: CssSubSelectorList [
29+
CssClassSelector {
30+
dot_token: DOT@0..106 "." [Comments("/* Negative test for ..."), Newline("\n"), Newline("\n")] [],
31+
name: CssCustomIdentifier {
32+
value_token: IDENT@106..112 "reset" [] [Whitespace(" ")],
33+
},
34+
},
35+
],
36+
},
37+
],
38+
block: CssDeclarationOrRuleBlock {
39+
l_curly_token: L_CURLY@112..113 "{" [] [],
40+
items: CssDeclarationOrRuleList [
41+
CssDeclarationWithSemicolon {
42+
declaration: CssDeclaration {
43+
property: CssBogusProperty {
44+
items: [
45+
CssBogusSupportsCondition {
46+
items: [
47+
CssDashedIdentifier {
48+
value_token: IDENT@113..117 "--" [Newline("\n"), Whitespace("\t")] [],
49+
},
50+
STAR@117..118 "*" [] [],
51+
],
52+
},
53+
COLON@118..120 ":" [] [Whitespace(" ")],
54+
CssGenericComponentValueList [
55+
CssIdentifier {
56+
value_token: IDENT@120..127 "initial" [] [],
57+
},
58+
],
59+
],
60+
},
61+
important: missing (optional),
62+
},
63+
semicolon_token: SEMICOLON@127..128 ";" [] [],
64+
},
65+
],
66+
r_curly_token: R_CURLY@128..130 "}" [Newline("\n")] [],
67+
},
68+
},
69+
],
70+
eof_token: EOF@130..131 "" [Newline("\n")] [],
71+
}
72+
```
73+
74+
## CST
75+
76+
```
77+
78+
0: (empty)
79+
80+
81+
82+
83+
84+
1: (empty)
85+
86+
87+
0: [email protected] "." [Comments("/* Negative test for ..."), Newline("\n"), Newline("\n")] []
88+
89+
0: [email protected] "reset" [] [Whitespace(" ")]
90+
91+
0: [email protected] "{" [] []
92+
1: CSS_DECLARATION_OR_RULE_LIST@113..128
93+
0: CSS_DECLARATION_WITH_SEMICOLON@113..128
94+
0: CSS_DECLARATION@113..127
95+
0: CSS_BOGUS_PROPERTY@113..127
96+
0: CSS_BOGUS_SUPPORTS_CONDITION@113..118
97+
0: CSS_DASHED_IDENTIFIER@113..117
98+
0: IDENT@113..117 "--" [Newline("\n"), Whitespace("\t")] []
99+
1: STAR@117..118 "*" [] []
100+
1: COLON@118..120 ":" [] [Whitespace(" ")]
101+
2: CSS_GENERIC_COMPONENT_VALUE_LIST@120..127
102+
0: CSS_IDENTIFIER@120..127
103+
0: IDENT@120..127 "initial" [] []
104+
1: (empty)
105+
1: SEMICOLON@127..128 ";" [] []
106+
2: R_CURLY@128..130 "}" [Newline("\n")] []
107+
2: EOF@130..131 "" [Newline("\n")] []
108+
109+
```
110+
111+
## Diagnostics
112+
113+
```
114+
custom-theme.css:4:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
115+
116+
× Tailwind-specific syntax is disabled.
117+
118+
3 │ .reset {
119+
> 4 │ --*: initial;
120+
│ ^^^
121+
5 │ }
122+
6
123+
124+
i Enable `tailwindDirectives` in the css parser options, or remove this if you are not using Tailwind CSS.
125+
126+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* From tailwind docs: https://tailwindcss.com/docs/theme#using-a-custom-theme */
2+
3+
@theme {
4+
--*: initial;
5+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
source: crates/biome_css_parser/tests/spec_test.rs
3+
expression: snapshot
4+
---
5+
## Input
6+
7+
```css
8+
/* From tailwind docs: https://tailwindcss.com/docs/theme#using-a-custom-theme */
9+
10+
@theme {
11+
--*: initial;
12+
}
13+
14+
```
15+
16+
17+
## AST
18+
19+
```
20+
CssRoot {
21+
bom_token: missing (optional),
22+
rules: CssRuleList [
23+
CssAtRule {
24+
at_token: AT@0..84 "@" [Comments("/* From tailwind docs ..."), Newline("\n"), Newline("\n")] [],
25+
rule: TwThemeAtRule {
26+
theme_token: THEME_KW@84..90 "theme" [] [Whitespace(" ")],
27+
name: missing (optional),
28+
block: CssDeclarationOrRuleBlock {
29+
l_curly_token: L_CURLY@90..91 "{" [] [],
30+
items: CssDeclarationOrRuleList [
31+
CssDeclarationWithSemicolon {
32+
declaration: CssDeclaration {
33+
property: CssGenericProperty {
34+
name: TwValueThemeReference {
35+
reference: CssDashedIdentifier {
36+
value_token: IDENT@91..95 "--" [Newline("\n"), Whitespace("\t")] [],
37+
},
38+
minus_token: missing (optional),
39+
star_token: STAR@95..96 "*" [] [],
40+
},
41+
colon_token: COLON@96..98 ":" [] [Whitespace(" ")],
42+
value: CssGenericComponentValueList [
43+
CssIdentifier {
44+
value_token: IDENT@98..105 "initial" [] [],
45+
},
46+
],
47+
},
48+
important: missing (optional),
49+
},
50+
semicolon_token: SEMICOLON@105..106 ";" [] [],
51+
},
52+
],
53+
r_curly_token: R_CURLY@106..108 "}" [Newline("\n")] [],
54+
},
55+
},
56+
},
57+
],
58+
eof_token: EOF@108..109 "" [Newline("\n")] [],
59+
}
60+
```
61+
62+
## CST
63+
64+
```
65+
66+
0: (empty)
67+
68+
69+
0: [email protected] "@" [Comments("/* From tailwind docs ..."), Newline("\n"), Newline("\n")] []
70+
71+
0: [email protected] "theme" [] [Whitespace(" ")]
72+
1: (empty)
73+
74+
0: [email protected] "{" [] []
75+
1: CSS_DECLARATION_OR_RULE_LIST@91..106
76+
0: CSS_DECLARATION_WITH_SEMICOLON@91..106
77+
0: CSS_DECLARATION@91..105
78+
0: CSS_GENERIC_PROPERTY@91..105
79+
0: TW_VALUE_THEME_REFERENCE@91..96
80+
0: CSS_DASHED_IDENTIFIER@91..95
81+
0: IDENT@91..95 "--" [Newline("\n"), Whitespace("\t")] []
82+
1: (empty)
83+
2: STAR@95..96 "*" [] []
84+
1: COLON@96..98 ":" [] [Whitespace(" ")]
85+
2: CSS_GENERIC_COMPONENT_VALUE_LIST@98..105
86+
0: CSS_IDENTIFIER@98..105
87+
0: IDENT@98..105 "initial" [] []
88+
1: (empty)
89+
1: SEMICOLON@105..106 ";" [] []
90+
2: R_CURLY@106..108 "}" [Newline("\n")] []
91+
2: EOF@108..109 "" [Newline("\n")] []
92+
93+
```

0 commit comments

Comments
 (0)