Skip to content

Commit c2983f9

Browse files
dyc3ematipico
andauthored
fix(parse/html/vue): fix modifier list parser aggressively parsing tokens it shouldn't (#8084)
Co-authored-by: Emanuele Stoppa <[email protected]>
1 parent 5cd3d27 commit c2983f9

File tree

12 files changed

+1177
-1
lines changed

12 files changed

+1177
-1
lines changed

.changeset/seven-signs-sip.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+
Fixed [#8080](https://github.com/biomejs/biome/issues/8080): The HTML parser, when parsing Vue, can now properly handle Vue directives with no argument, modifiers, or initializer (e.g. `v-else`). It will no longer treat subsequent valid attributes as bogus.
6+
7+
```vue
8+
<p v-else class="flex">World</p> <!-- Fixed: class now gets parsed as it's own attribute -->
9+
```

crates/biome_html_parser/src/syntax/vue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl ParseNodeList for VueModifierList {
122122
}
123123

124124
fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool {
125-
p.at(T![=]) || p.at(T![>]) || p.at(T![/]) || p.at(T!['}'])
125+
!p.at(T![.])
126126
}
127127

128128
fn recover(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<p v-foo:bar.baz></p>
2+
<p class="flex" v-foo:bar.baz></p>
3+
<p v-foo:bar.baz class="flex"></p>
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
---
2+
source: crates/biome_html_parser/tests/spec_test.rs
3+
expression: snapshot
4+
---
5+
## Input
6+
7+
```vue
8+
<p v-foo:bar.baz></p>
9+
<p class="flex" v-foo:bar.baz></p>
10+
<p v-foo:bar.baz class="flex"></p>
11+
12+
```
13+
14+
15+
## AST
16+
17+
```
18+
HtmlRoot {
19+
bom_token: missing (optional),
20+
frontmatter: missing (optional),
21+
directive: missing (optional),
22+
html: HtmlElementList [
23+
HtmlElement {
24+
opening_element: HtmlOpeningElement {
25+
l_angle_token: L_ANGLE@0..1 "<" [] [],
26+
name: HtmlTagName {
27+
value_token: HTML_LITERAL@1..3 "p" [] [Whitespace(" ")],
28+
},
29+
attributes: HtmlAttributeList [
30+
VueDirective {
31+
name_token: VUE_IDENT@3..8 "v-foo" [] [],
32+
arg: VueDirectiveArgument {
33+
colon_token: COLON@8..9 ":" [] [],
34+
arg: VueStaticArgument {
35+
name_token: HTML_LITERAL@9..12 "bar" [] [],
36+
},
37+
},
38+
modifiers: VueModifierList [
39+
VueModifier {
40+
dot_token: DOT@12..13 "." [] [],
41+
modifier_token: HTML_LITERAL@13..16 "baz" [] [],
42+
},
43+
],
44+
initializer: missing (optional),
45+
},
46+
],
47+
r_angle_token: R_ANGLE@16..17 ">" [] [],
48+
},
49+
children: HtmlElementList [],
50+
closing_element: HtmlClosingElement {
51+
l_angle_token: L_ANGLE@17..18 "<" [] [],
52+
slash_token: SLASH@18..19 "/" [] [],
53+
name: HtmlTagName {
54+
value_token: HTML_LITERAL@19..20 "p" [] [],
55+
},
56+
r_angle_token: R_ANGLE@20..21 ">" [] [],
57+
},
58+
},
59+
HtmlElement {
60+
opening_element: HtmlOpeningElement {
61+
l_angle_token: L_ANGLE@21..23 "<" [Newline("\n")] [],
62+
name: HtmlTagName {
63+
value_token: HTML_LITERAL@23..25 "p" [] [Whitespace(" ")],
64+
},
65+
attributes: HtmlAttributeList [
66+
HtmlAttribute {
67+
name: HtmlAttributeName {
68+
value_token: HTML_LITERAL@25..30 "class" [] [],
69+
},
70+
initializer: HtmlAttributeInitializerClause {
71+
eq_token: EQ@30..31 "=" [] [],
72+
value: HtmlString {
73+
value_token: HTML_STRING_LITERAL@31..38 "\"flex\"" [] [Whitespace(" ")],
74+
},
75+
},
76+
},
77+
VueDirective {
78+
name_token: VUE_IDENT@38..43 "v-foo" [] [],
79+
arg: VueDirectiveArgument {
80+
colon_token: COLON@43..44 ":" [] [],
81+
arg: VueStaticArgument {
82+
name_token: HTML_LITERAL@44..47 "bar" [] [],
83+
},
84+
},
85+
modifiers: VueModifierList [
86+
VueModifier {
87+
dot_token: DOT@47..48 "." [] [],
88+
modifier_token: HTML_LITERAL@48..51 "baz" [] [],
89+
},
90+
],
91+
initializer: missing (optional),
92+
},
93+
],
94+
r_angle_token: R_ANGLE@51..52 ">" [] [],
95+
},
96+
children: HtmlElementList [],
97+
closing_element: HtmlClosingElement {
98+
l_angle_token: L_ANGLE@52..53 "<" [] [],
99+
slash_token: SLASH@53..54 "/" [] [],
100+
name: HtmlTagName {
101+
value_token: HTML_LITERAL@54..55 "p" [] [],
102+
},
103+
r_angle_token: R_ANGLE@55..56 ">" [] [],
104+
},
105+
},
106+
HtmlElement {
107+
opening_element: HtmlOpeningElement {
108+
l_angle_token: L_ANGLE@56..58 "<" [Newline("\n")] [],
109+
name: HtmlTagName {
110+
value_token: HTML_LITERAL@58..60 "p" [] [Whitespace(" ")],
111+
},
112+
attributes: HtmlAttributeList [
113+
VueDirective {
114+
name_token: VUE_IDENT@60..65 "v-foo" [] [],
115+
arg: VueDirectiveArgument {
116+
colon_token: COLON@65..66 ":" [] [],
117+
arg: VueStaticArgument {
118+
name_token: HTML_LITERAL@66..69 "bar" [] [],
119+
},
120+
},
121+
modifiers: VueModifierList [
122+
VueModifier {
123+
dot_token: DOT@69..70 "." [] [],
124+
modifier_token: HTML_LITERAL@70..74 "baz" [] [Whitespace(" ")],
125+
},
126+
],
127+
initializer: missing (optional),
128+
},
129+
HtmlAttribute {
130+
name: HtmlAttributeName {
131+
value_token: HTML_LITERAL@74..79 "class" [] [],
132+
},
133+
initializer: HtmlAttributeInitializerClause {
134+
eq_token: EQ@79..80 "=" [] [],
135+
value: HtmlString {
136+
value_token: HTML_STRING_LITERAL@80..86 "\"flex\"" [] [],
137+
},
138+
},
139+
},
140+
],
141+
r_angle_token: R_ANGLE@86..87 ">" [] [],
142+
},
143+
children: HtmlElementList [],
144+
closing_element: HtmlClosingElement {
145+
l_angle_token: L_ANGLE@87..88 "<" [] [],
146+
slash_token: SLASH@88..89 "/" [] [],
147+
name: HtmlTagName {
148+
value_token: HTML_LITERAL@89..90 "p" [] [],
149+
},
150+
r_angle_token: R_ANGLE@90..91 ">" [] [],
151+
},
152+
},
153+
],
154+
eof_token: EOF@91..92 "" [Newline("\n")] [],
155+
}
156+
```
157+
158+
## CST
159+
160+
```
161+
162+
0: (empty)
163+
1: (empty)
164+
2: (empty)
165+
166+
167+
168+
0: [email protected] "<" [] []
169+
170+
0: [email protected] "p" [] [Whitespace(" ")]
171+
172+
173+
0: [email protected] "v-foo" [] []
174+
175+
0: [email protected] ":" [] []
176+
177+
0: [email protected] "bar" [] []
178+
179+
180+
0: [email protected] "." [] []
181+
1: [email protected] "baz" [] []
182+
3: (empty)
183+
3: [email protected] ">" [] []
184+
185+
186+
0: [email protected] "<" [] []
187+
1: [email protected] "/" [] []
188+
189+
0: [email protected] "p" [] []
190+
3: [email protected] ">" [] []
191+
192+
193+
0: [email protected] "<" [Newline("\n")] []
194+
195+
0: [email protected] "p" [] [Whitespace(" ")]
196+
197+
198+
199+
0: [email protected] "class" [] []
200+
201+
0: [email protected] "=" [] []
202+
203+
0: [email protected] "\"flex\"" [] [Whitespace(" ")]
204+
205+
0: [email protected] "v-foo" [] []
206+
207+
0: [email protected] ":" [] []
208+
209+
0: [email protected] "bar" [] []
210+
211+
212+
0: [email protected] "." [] []
213+
1: [email protected] "baz" [] []
214+
3: (empty)
215+
3: [email protected] ">" [] []
216+
217+
218+
0: [email protected] "<" [] []
219+
1: [email protected] "/" [] []
220+
221+
0: [email protected] "p" [] []
222+
3: [email protected] ">" [] []
223+
224+
225+
0: [email protected] "<" [Newline("\n")] []
226+
227+
0: [email protected] "p" [] [Whitespace(" ")]
228+
229+
230+
0: [email protected] "v-foo" [] []
231+
232+
0: [email protected] ":" [] []
233+
234+
0: [email protected] "bar" [] []
235+
236+
237+
0: [email protected] "." [] []
238+
1: [email protected] "baz" [] [Whitespace(" ")]
239+
3: (empty)
240+
241+
242+
0: [email protected] "class" [] []
243+
244+
0: [email protected] "=" [] []
245+
246+
0: [email protected] "\"flex\"" [] []
247+
3: [email protected] ">" [] []
248+
249+
250+
0: [email protected] "<" [] []
251+
1: [email protected] "/" [] []
252+
253+
0: [email protected] "p" [] []
254+
3: [email protected] ">" [] []
255+
4: [email protected] "" [Newline("\n")] []
256+
257+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<p v-foo:bar></p>
2+
<p class="flex" v-foo:bar></p>
3+
<p v-foo:bar class="flex"></p>

0 commit comments

Comments
 (0)