Skip to content

Commit 36d5aa7

Browse files
authored
fix(useVueValidVBind): accept Vue 3.4+ same-name shorthand (#10767)
1 parent 771daa4 commit 36d5aa7

6 files changed

Lines changed: 157 additions & 55 deletions

File tree

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 [#10754](https://github.com/biomejs/biome/issues/10754): [`useVueValidVBind`](https://biomejs.dev/linter/rules/use-vue-valid-v-bind/) no longer reports the Vue 3.4+ same-name shorthand as missing a value. `:foo` and `v-bind:foo` are now accepted as equivalent to `:foo="foo"`, while `v-bind`, `v-bind:[dynamicArg]`, and `:[dynamicArg]` without a value continue to be reported.

crates/biome_html_analyze/src/lint/correctness/use_vue_valid_v_bind.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,21 @@ use biome_analyze::{
33
};
44
use biome_console::markup;
55
use biome_diagnostics::Severity;
6-
use biome_html_syntax::{AnyVueDirective, VueModifierList};
6+
use biome_html_syntax::{
7+
AnyVueDirective, AnyVueDirectiveArgument, VueDirectiveArgument, VueModifierList,
8+
};
79
use biome_rowan::{AstNode, TextRange};
810
use biome_rule_options::use_vue_valid_v_bind::UseVueValidVBindOptions;
911

1012
declare_lint_rule! {
1113
/// Forbids `v-bind` directives with missing values or invalid modifiers.
1214
///
1315
/// This rule reports v-bind directives in the following cases:
14-
/// - The directive does not have a value. E.g. `<div v-bind:aaa></div>`
16+
/// - The directive has neither a value nor a static argument from which to
17+
/// derive one. E.g. `<div v-bind></div>` or `<div v-bind:[foo]></div>`.
18+
/// `v-bind:foo` and `:foo` are accepted because they are valid Vue 3.4+
19+
/// [same-name shorthand](https://vuejs.org/guide/essentials/template-syntax.html#same-name-shorthand)
20+
/// for `:foo="foo"`.
1521
/// - The directive has invalid modifiers. E.g. `<div v-bind:aaa.bbb="ccc"></div>`
1622
///
1723
/// ## Examples
@@ -32,6 +38,10 @@ declare_lint_rule! {
3238
/// <Foo v-bind:foo="foo" />
3339
/// ```
3440
///
41+
/// ```vue
42+
/// <Foo :foo />
43+
/// ```
44+
///
3545
pub UseVueValidVBind {
3646
version: "2.3.6",
3747
name: "useVueValidVBind",
@@ -64,7 +74,9 @@ impl Rule for UseVueValidVBind {
6474
return None;
6575
}
6676

67-
if vue_directive.initializer().is_none() {
77+
if vue_directive.initializer().is_none()
78+
&& !vue_directive.arg().is_some_and(|arg| is_static_arg(&arg))
79+
{
6880
return Some(ViolationKind::MissingValue);
6981
}
7082

@@ -77,7 +89,9 @@ impl Rule for UseVueValidVBind {
7789
AnyVueDirective::VueVBindShorthandDirective(dir) => {
7890
// missing argument would be caught by the parser
7991

80-
if dir.initializer().is_none() {
92+
if dir.initializer().is_none()
93+
&& !dir.arg().is_ok_and(|arg| is_static_arg(&arg))
94+
{
8195
return Some(ViolationKind::MissingValue);
8296
}
8397

@@ -126,9 +140,22 @@ impl Rule for UseVueValidVBind {
126140

127141
fn find_invalid_modifiers(modifiers: &VueModifierList) -> Option<TextRange> {
128142
for modifier in modifiers {
129-
if !VALID_MODIFIERS.contains(&modifier.modifier_token().ok()?.text()) {
143+
if !VALID_MODIFIERS.contains(&modifier.modifier_token().ok()?.text_trimmed()) {
130144
return Some(modifier.range());
131145
}
132146
}
133147
None
134148
}
149+
150+
/// Returns `true` if the directive argument is a static identifier
151+
/// (e.g. `foo` in `:foo`), as opposed to a dynamic expression
152+
/// (e.g. `[foo]` in `:[foo]`). Only static arguments can be the
153+
/// source of a Vue 3.4+ same-name shorthand binding.
154+
///
155+
/// See <https://vuejs.org/guide/essentials/template-syntax.html#same-name-shorthand>.
156+
fn is_static_arg(arg: &VueDirectiveArgument) -> bool {
157+
matches!(
158+
arg.arg().ok(),
159+
Some(AnyVueDirectiveArgument::VueStaticArgument(_))
160+
)
161+
}

crates/biome_html_analyze/tests/specs/correctness/useVueValidVBind/invalid.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
<!-- should generate diagnostics -->
22

33
<template>
4-
<!-- Missing value -->
5-
<Foo v-bind:foo />
6-
<Foo :foo />
4+
<!-- Missing value with no argument -->
5+
<Foo v-bind />
6+
<div v-bind></div>
7+
8+
<!-- Missing value with a dynamic argument: no static name to derive a binding from -->
9+
<Foo v-bind:[dynamic] />
10+
<Foo :[dynamic] />
711

812
<!-- Missing value with modifier -->
913
<div v-bind.prop></div>

crates/biome_html_analyze/tests/specs/correctness/useVueValidVBind/invalid.vue.snap

Lines changed: 87 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ expression: invalid.vue
77
<!-- should generate diagnostics -->
88
99
<template>
10-
<!-- Missing value -->
11-
<Foo v-bind:foo />
12-
<Foo :foo />
10+
<!-- Missing value with no argument -->
11+
<Foo v-bind />
12+
<div v-bind></div>
13+
14+
<!-- Missing value with a dynamic argument: no static name to derive a binding from -->
15+
<Foo v-bind:[dynamic] />
16+
<Foo :[dynamic] />
1317
1418
<!-- Missing value with modifier -->
1519
<div v-bind.prop></div>
@@ -42,10 +46,10 @@ invalid.vue:5:8 lint/correctness/useVueValidVBind ━━━━━━━━━━
4246
× This v-bind directive is missing a value.
4347
4448
3 <template>
45-
4 <!-- Missing value -->
46-
> 5 │ <Foo v-bind:foo />
47-
│ ^^^^^^^^^^
48-
6 │ <Foo :foo />
49+
4 <!-- Missing value with no argument -->
50+
> 5 │ <Foo v-bind />
51+
│ ^^^^^^
52+
6 │ <div v-bind></div>
4953
7 │
5054
5155
i v-bind directives require a value.
@@ -60,12 +64,12 @@ invalid.vue:6:8 lint/correctness/useVueValidVBind ━━━━━━━━━━
6064
6165
× This v-bind directive is missing a value.
6266
63-
4 │ <!-- Missing value -->
64-
5 │ <Foo v-bind:foo />
65-
> 6 │ <Foo :foo />
66-
│ ^^^^
67+
4 │ <!-- Missing value with no argument -->
68+
5 │ <Foo v-bind />
69+
> 6 │ <div v-bind></div>
70+
│ ^^^^^^
6771
7 │
68-
8 │ <!-- Missing value with modifier -->
72+
8 │ <!-- Missing value with a dynamic argument: no static name to derive a binding from -->
6973
7074
i v-bind directives require a value.
7175
@@ -79,11 +83,48 @@ invalid.vue:9:8 lint/correctness/useVueValidVBind ━━━━━━━━━━
7983
8084
× This v-bind directive is missing a value.
8185
82-
8 │ <!-- Missing value with modifier -->
83-
> 9 │ <div v-bind.prop></div>
86+
8 │ <!-- Missing value with a dynamic argument: no static name to derive a binding from -->
87+
> 9 │ <Foo v-bind:[dynamic] />
88+
│ ^^^^^^^^^^^^^^^^
89+
10 │ <Foo :[dynamic] />
90+
11 │
91+
92+
i v-bind directives require a value.
93+
94+
i Add a value to the directive, e.g. v-bind:foo="bar".
95+
96+
97+
```
98+
99+
```
100+
invalid.vue:10:8 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
101+
102+
× This v-bind directive is missing a value.
103+
104+
8 │ <!-- Missing value with a dynamic argument: no static name to derive a binding from -->
105+
9 │ <Foo v-bind:[dynamic] />
106+
> 10 │ <Foo :[dynamic] />
107+
│ ^^^^^^^^^^
108+
11 │
109+
12 │ <!-- Missing value with modifier -->
110+
111+
i v-bind directives require a value.
112+
113+
i Add a value to the directive, e.g. v-bind:foo="bar".
114+
115+
116+
```
117+
118+
```
119+
invalid.vue:13:8 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
120+
121+
× This v-bind directive is missing a value.
122+
123+
12 │ <!-- Missing value with modifier -->
124+
> 13 │ <div v-bind.prop></div>
84125
^^^^^^^^^^^
85-
10
86-
11 <!-- Invalid single modifier on long-form -->
126+
14
127+
15 <!-- Invalid single modifier on long-form -->
87128
88129
i v-bind directives require a value.
89130
@@ -93,15 +134,15 @@ invalid.vue:9:8 lint/correctness/useVueValidVBind ━━━━━━━━━━
93134
```
94135
95136
```
96-
invalid.vue:12:18 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
137+
invalid.vue:16:18 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
97138
98139
× This v-bind directive has an invalid modifier.
99140
100-
11 <!-- Invalid single modifier on long-form -->
101-
> 12 │ <div v-bind:foo.invalid="bar"></div>
141+
15 <!-- Invalid single modifier on long-form -->
142+
> 16 │ <div v-bind:foo.invalid="bar"></div>
102143
^^^^^^^^
103-
13
104-
14 <!-- Invalid modifier on shorthand -->
144+
17
145+
18 <!-- Invalid modifier on shorthand -->
105146
106147
i Only the following modifiers are allowed on v-bind directives: prop, camel, sync, and attr.
107148
@@ -111,15 +152,15 @@ invalid.vue:12:18 lint/correctness/useVueValidVBind ━━━━━━━━━
111152
```
112153
113154
```
114-
invalid.vue:15:13 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
155+
invalid.vue:19:13 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
115156
116157
× This v-bind directive has an invalid modifier.
117158
118-
14 <!-- Invalid modifier on shorthand -->
119-
> 15 │ <span :bar.badModifier="baz"></span>
159+
18 <!-- Invalid modifier on shorthand -->
160+
> 19 │ <span :bar.badModifier="baz"></span>
120161
^^^^^^^^^^^^
121-
16
122-
17 <!-- Mixed valid and invalid modifiers: 'prop' is valid, 'wrong' is not -->
162+
20
163+
21 <!-- Mixed valid and invalid modifiers: 'prop' is valid, 'wrong' is not -->
123164
124165
i Only the following modifiers are allowed on v-bind directives: prop, camel, sync, and attr.
125166
@@ -129,15 +170,15 @@ invalid.vue:15:13 lint/correctness/useVueValidVBind ━━━━━━━━━
129170
```
130171
131172
```
132-
invalid.vue:18:15 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
173+
invalid.vue:22:15 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
133174
134175
× This v-bind directive has an invalid modifier.
135176
136-
17 <!-- Mixed valid and invalid modifiers: 'prop' is valid, 'wrong' is not -->
137-
> 18 │ <p :baz.prop.wrong="value"></p>
177+
21 <!-- Mixed valid and invalid modifiers: 'prop' is valid, 'wrong' is not -->
178+
> 22 │ <p :baz.prop.wrong="value"></p>
138179
^^^^^^
139-
19
140-
20 <!-- Dynamic argument is present but modifier is invalid -->
180+
23
181+
24 <!-- Dynamic argument is present but modifier is invalid -->
141182
142183
i Only the following modifiers are allowed on v-bind directives: prop, camel, sync, and attr.
143184
@@ -147,15 +188,15 @@ invalid.vue:18:15 lint/correctness/useVueValidVBind ━━━━━━━━━
147188
```
148189
149190
```
150-
invalid.vue:21:22 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
191+
invalid.vue:25:22 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
151192
152193
× This v-bind directive has an invalid modifier.
153194
154-
20 <!-- Dynamic argument is present but modifier is invalid -->
155-
> 21 │ <p v-bind:[dynamic].notAValidModifier="value"></p>
195+
24 <!-- Dynamic argument is present but modifier is invalid -->
196+
> 25 │ <p v-bind:[dynamic].notAValidModifier="value"></p>
156197
^^^^^^^^^^^^^^^^^^
157-
22
158-
23 <!-- Multiple invalid modifiers -->
198+
26
199+
27 <!-- Multiple invalid modifiers -->
159200
160201
i Only the following modifiers are allowed on v-bind directives: prop, camel, sync, and attr.
161202
@@ -165,15 +206,15 @@ invalid.vue:21:22 lint/correctness/useVueValidVBind ━━━━━━━━━
165206
```
166207
167208
```
168-
invalid.vue:24:20 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
209+
invalid.vue:28:20 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
169210
170211
× This v-bind directive has an invalid modifier.
171212
172-
23 <!-- Multiple invalid modifiers -->
173-
> 24 │ <button :disabled.once="true"></button>
213+
27 <!-- Multiple invalid modifiers -->
214+
> 28 │ <button :disabled.once="true"></button>
174215
^^^^^
175-
25
176-
26 <!-- Component binding with unknown modifier -->
216+
29
217+
30 <!-- Component binding with unknown modifier -->
177218
178219
i Only the following modifiers are allowed on v-bind directives: prop, camel, sync, and attr.
179220
@@ -183,15 +224,15 @@ invalid.vue:24:20 lint/correctness/useVueValidVBind ━━━━━━━━━
183224
```
184225
185226
```
186-
invalid.vue:27:31 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
227+
invalid.vue:31:31 lint/correctness/useVueValidVBind ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
187228
188229
× This v-bind directive has an invalid modifier.
189230
190-
26 <!-- Component binding with unknown modifier -->
191-
> 27 │ <MyComponent v-bind:propName.weird="someValue"></MyComponent>
231+
30 <!-- Component binding with unknown modifier -->
232+
> 31 │ <MyComponent v-bind:propName.weird="someValue"></MyComponent>
192233
^^^^^^
193-
28 </template>
194-
29
234+
32 </template>
235+
33
195236
196237
i Only the following modifiers are allowed on v-bind directives: prop, camel, sync, and attr.
197238

crates/biome_html_analyze/tests/specs/correctness/useVueValidVBind/valid.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@
88
<div v-bind="props"></div>
99
<Foo v-bind="props" />
1010

11+
<!-- Vue 3.4+ same-name shorthand: `:foo` is equivalent to `:foo="foo"`. -->
12+
<Foo :foo />
13+
<Foo :foo-bar />
14+
<Foo v-bind:foo />
15+
<Foo v-bind:foo-bar />
16+
17+
<!-- Same-name shorthand combined with valid modifiers. -->
18+
<Foo :foo.prop />
19+
<Foo v-bind:foo.prop />
20+
21+
<!-- Same-name shorthand referencing a v-for iteration variable. -->
22+
<Foo v-for="foo of foos" :key="foo.id" :foo />
23+
1124
<!-- Valid modifiers from the rule: prop, camel, sync, attr -->
1225
<div v-bind:foo.prop="bar"></div>
1326
<div v-bind:foo.camel="bar"></div>

crates/biome_html_analyze/tests/specs/correctness/useVueValidVBind/valid.vue.snap

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
22
source: crates/biome_html_analyze/tests/spec_tests.rs
3-
assertion_line: 77
43
expression: valid.vue
54
---
65
# Input
@@ -15,6 +14,19 @@ expression: valid.vue
1514
<div v-bind="props"></div>
1615
<Foo v-bind="props" />
1716
17+
<!-- Vue 3.4+ same-name shorthand: `:foo` is equivalent to `:foo="foo"`. -->
18+
<Foo :foo />
19+
<Foo :foo-bar />
20+
<Foo v-bind:foo />
21+
<Foo v-bind:foo-bar />
22+
23+
<!-- Same-name shorthand combined with valid modifiers. -->
24+
<Foo :foo.prop />
25+
<Foo v-bind:foo.prop />
26+
27+
<!-- Same-name shorthand referencing a v-for iteration variable. -->
28+
<Foo v-for="foo of foos" :key="foo.id" :foo />
29+
1830
<!-- Valid modifiers from the rule: prop, camel, sync, attr -->
1931
<div v-bind:foo.prop="bar"></div>
2032
<div v-bind:foo.camel="bar"></div>

0 commit comments

Comments
 (0)