Skip to content

Commit c065f99

Browse files
authored
fix(visitor): register references from Vue same-name bindings (#11431)
1 parent 8f7786f commit c065f99

5 files changed

Lines changed: 111 additions & 11 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 [#11429](https://github.com/biomejs/biome/issues/11429): Variables and imports used by Vue same-name bindings such as `:disabled` or `v-bind:disabled` are no longer reported as unused.

crates/biome_embeds/src/references.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,51 @@ mod tests {
261261
path
262262
}
263263

264+
fn parse_vue_template_source(db: &TestDb, html_source: &str) -> Utf8PathBuf {
265+
let path = Utf8PathBuf::from("src/App.vue");
266+
let parsed = parse_html(html_source, HtmlParserOptions::default().with_vue()).into();
267+
let parsed = ParsedSource::new(db, path.clone(), parsed, 0, vec![]);
268+
db.insert_file(path.clone(), parsed);
269+
path
270+
}
271+
272+
#[test]
273+
fn is_value_reference_used_finds_vue_same_name_binding_shorthand() {
274+
let db = TestDb::new();
275+
let path = parse_vue_template_source(&db, r#"<template><button :disabled /></template>"#);
276+
277+
assert!(is_value_reference_used(
278+
&db,
279+
InternedReference::new(&db, path, token_text("disabled")),
280+
));
281+
}
282+
283+
#[test]
284+
fn is_value_reference_used_finds_vue_v_bind_same_name_binding_shorthand() {
285+
let db = TestDb::new();
286+
let path =
287+
parse_vue_template_source(&db, r#"<template><button v-bind:disabled /></template>"#);
288+
289+
assert!(is_value_reference_used(
290+
&db,
291+
InternedReference::new(&db, path, token_text("disabled")),
292+
));
293+
}
294+
295+
#[test]
296+
fn is_value_reference_used_ignores_vue_binding_attribute_name() {
297+
let db = TestDb::new();
298+
let path = parse_vue_template_source(
299+
&db,
300+
r#"<template><button :disabled="isDisabled" /><button v-bind:disabled="isDisabled" /></template>"#,
301+
);
302+
303+
assert!(!is_value_reference_used(
304+
&db,
305+
InternedReference::new(&db, path, token_text("disabled")),
306+
));
307+
}
308+
264309
#[test]
265310
fn is_value_reference_used_finds_references_across_groups() {
266311
let db = TestDb::new();

crates/biome_embeds/src/visitor.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use biome_db::ParsedSource;
44
use biome_html_syntax::{
55
AnyHtmlComponentObjectName, AnyHtmlTagName, AnySvelteBindingAssignmentBinding,
66
AnySvelteBindingProperty, AnySvelteBlock, AnySvelteBlockItem, AnySvelteDestructuredName,
7-
AnySvelteDirective, AnySvelteEachName, AnyVueVForBinding, AnyVueVForBindingListElement,
8-
AnyVueVForDestructuredBinding, HtmlElement, HtmlRoot, HtmlSelfClosingElement,
9-
VueVForIdentifierBinding, VueVForValue,
7+
AnySvelteDirective, AnySvelteEachName, AnyVueDirective, AnyVueDirectiveArgument,
8+
AnyVueVForBinding, AnyVueVForBindingListElement, AnyVueVForDestructuredBinding, HtmlElement,
9+
HtmlRoot, HtmlSelfClosingElement, VueVForIdentifierBinding, VueVForValue,
1010
};
1111
use biome_js_syntax::{
1212
AnyJsArrayAssignmentPatternElement, AnyJsArrayBindingPatternElement, AnyJsArrayElement,
@@ -1211,6 +1211,7 @@ impl EmbeddedReferencesBuilder {
12111211

12121212
fn visit_html_root(&mut self, root: &HtmlRoot, file_source: &HtmlFileSource) {
12131213
let is_svelte = file_source.is_svelte();
1214+
let is_vue = file_source.is_vue();
12141215
for node in root.syntax().descendants() {
12151216
if let Some(element) = HtmlElement::cast_ref(&node) {
12161217
self.visit_html_element(&element);
@@ -1223,6 +1224,10 @@ impl EmbeddedReferencesBuilder {
12231224
if is_svelte && let Some(directive) = AnySvelteDirective::cast_ref(&node) {
12241225
self.register_svelte_directive_reference(&directive);
12251226
}
1227+
1228+
if is_vue && let Some(directive) = AnyVueDirective::cast_ref(&node) {
1229+
self.register_vue_directive_reference(&directive);
1230+
}
12261231
}
12271232
}
12281233

@@ -1264,6 +1269,37 @@ impl EmbeddedReferencesBuilder {
12641269
Some(())
12651270
}
12661271

1272+
fn register_vue_directive_reference(&mut self, directive: &AnyVueDirective) -> Option<()> {
1273+
let value = match directive {
1274+
AnyVueDirective::VueVBindShorthandDirective(directive)
1275+
if directive.initializer().is_none() =>
1276+
{
1277+
directive.arg().ok()?
1278+
}
1279+
AnyVueDirective::VueDirective(directive)
1280+
if directive.is_binding() && directive.initializer().is_none() =>
1281+
{
1282+
directive.arg()?
1283+
}
1284+
_ => return None,
1285+
};
1286+
1287+
self.register_vue_binding_attribute(value.arg())
1288+
}
1289+
1290+
fn register_vue_binding_attribute(
1291+
&mut self,
1292+
arg: Option<AnyVueDirectiveArgument>,
1293+
) -> Option<()> {
1294+
let token = match arg? {
1295+
AnyVueDirectiveArgument::VueStaticArgument(arg) => arg.name_token().ok()?,
1296+
_ => return None,
1297+
};
1298+
1299+
self.register_reference(token.text_trimmed_range(), token.token_text_trimmed());
1300+
Some(())
1301+
}
1302+
12671303
fn visit_html_element(&mut self, element: &HtmlElement) -> Option<()> {
12681304
if element.is_script_tag() || element.is_style_tag() {
12691305
return None;

crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/valid-vue-directives.vue

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<!-- should not generate diagnostics -->
22
<script setup>
33
const supported = ref(true);
4+
const disabled = ref(false);
45
const enabled = ref(false);
56
const count = ref(0);
67
const isActive = ref(false);
@@ -17,16 +18,22 @@ function handleClick() {
1718
<template>
1819
<!-- v-on shorthand with function call -->
1920
<button @click="toggleCaptions()">Toggle</button>
20-
21+
2122
<!-- v-on shorthand with function reference -->
2223
<button @click="handleClick">Click</button>
23-
24+
2425
<!-- v-bind shorthand with expression -->
2526
<button :disabled="!supported">Disabled</button>
26-
27+
28+
<!-- v-bind same-name shorthand -->
29+
<button :disabled>Same-name shorthand</button>
30+
31+
<!-- v-bind directive same-name shorthand -->
32+
<button v-bind:disabled>Directive same-name shorthand</button>
33+
2734
<!-- v-if directive -->
2835
<div v-if="count > 0">Count: {{ count }}</div>
29-
36+
3037
<!-- v-show directive -->
3138
<div v-show="isActive">Active</div>
3239
</template>

crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/valid-vue-directives.vue.snap

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ expression: valid-vue-directives.vue
77
<!-- should not generate diagnostics -->
88
<script setup>
99
const supported = ref(true);
10+
const disabled = ref(false);
1011
const enabled = ref(false);
1112
const count = ref(0);
1213
const isActive = ref(false);
@@ -23,16 +24,22 @@ function handleClick() {
2324
<template>
2425
<!-- v-on shorthand with function call -->
2526
<button @click="toggleCaptions()">Toggle</button>
26-
27+
2728
<!-- v-on shorthand with function reference -->
2829
<button @click="handleClick">Click</button>
29-
30+
3031
<!-- v-bind shorthand with expression -->
3132
<button :disabled="!supported">Disabled</button>
32-
33+
34+
<!-- v-bind same-name shorthand -->
35+
<button :disabled>Same-name shorthand</button>
36+
37+
<!-- v-bind directive same-name shorthand -->
38+
<button v-bind:disabled>Directive same-name shorthand</button>
39+
3340
<!-- v-if directive -->
3441
<div v-if="count > 0">Count: {{ count }}</div>
35-
42+
3643
<!-- v-show directive -->
3744
<div v-show="isActive">Active</div>
3845
</template>

0 commit comments

Comments
 (0)