Skip to content

Commit 0045596

Browse files
authored
Fixed false negatives when v-for and v-slot mixed or use destructuring for vue/no-unused-var rule. (#1164)
1 parent d708da6 commit 0045596

File tree

2 files changed

+146
-28
lines changed

2 files changed

+146
-28
lines changed

lib/rules/no-unused-vars.js

+94-28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,58 @@
66

77
const utils = require('../utils')
88

9+
/**
10+
* @typedef {import('vue-eslint-parser').AST.Node} Node
11+
* @typedef {import('vue-eslint-parser').AST.VElement} VElement
12+
* @typedef {import('vue-eslint-parser').AST.Variable} Variable
13+
*/
14+
/**
15+
* @typedef {Variable['kind']} VariableKind
16+
*/
17+
18+
// ------------------------------------------------------------------------------
19+
// Helpers
20+
// ------------------------------------------------------------------------------
21+
22+
/**
23+
* Groups variables by directive kind.
24+
* @param {VElement} node The element node
25+
* @returns { { [kind in VariableKind]?: Variable[] } } The variables of grouped by directive kind.
26+
*/
27+
function groupingVariables(node) {
28+
/** @type { { [kind in VariableKind]?: Variable[] } } */
29+
const result = {}
30+
for (const variable of node.variables) {
31+
const vars = result[variable.kind] || (result[variable.kind] = [])
32+
vars.push(variable)
33+
}
34+
return result
35+
}
36+
37+
/**
38+
* Checks if the given variable was defined by destructuring.
39+
* @param {Variable} variable the given variable to check
40+
* @returns {boolean} `true` if the given variable was defined by destructuring.
41+
*/
42+
function isDestructuringVar(variable) {
43+
const node = variable.id
44+
/** @type {Node} */
45+
let parent = node.parent
46+
while (parent) {
47+
if (
48+
parent.type === 'VForExpression' ||
49+
parent.type === 'VSlotScopeExpression'
50+
) {
51+
return false
52+
}
53+
if (parent.type === 'Property' || parent.type === 'ArrayPattern') {
54+
return true
55+
}
56+
parent = parent.parent
57+
}
58+
return false
59+
}
60+
961
// ------------------------------------------------------------------------------
1062
// Rule Definition
1163
// ------------------------------------------------------------------------------
@@ -41,38 +93,52 @@ module.exports = {
4193
ignoreRegEx = new RegExp(ignorePattern, 'u')
4294
}
4395
return utils.defineTemplateBodyVisitor(context, {
96+
/**
97+
* @param {VElement} node
98+
*/
4499
VElement(node) {
45-
const variables = node.variables
46-
for (let i = variables.length - 1; i >= 0; i--) {
47-
const variable = variables[i]
100+
const vars = groupingVariables(node)
101+
for (const variables of Object.values(vars)) {
102+
let hasAfterUsed = false
48103

49-
if (variable.references.length) {
50-
break
51-
}
104+
for (let i = variables.length - 1; i >= 0; i--) {
105+
const variable = variables[i]
52106

53-
if (ignoreRegEx != null && ignoreRegEx.test(variable.id.name)) {
54-
continue
55-
}
56-
context.report({
57-
node: variable.id,
58-
loc: variable.id.loc,
59-
message: `'{{name}}' is defined but never used.`,
60-
data: variable.id,
61-
suggest:
62-
ignorePattern === '^_'
63-
? [
64-
{
65-
desc: `Replace the ${variable.id.name} with _${variable.id.name}`,
66-
fix(fixer) {
67-
return fixer.replaceText(
68-
variable.id,
69-
`_${variable.id.name}`
70-
)
107+
if (variable.references.length) {
108+
hasAfterUsed = true
109+
continue
110+
}
111+
112+
if (ignoreRegEx != null && ignoreRegEx.test(variable.id.name)) {
113+
continue
114+
}
115+
116+
if (hasAfterUsed && !isDestructuringVar(variable)) {
117+
// If a variable after the variable is used, it will be skipped.
118+
continue
119+
}
120+
121+
context.report({
122+
node: variable.id,
123+
loc: variable.id.loc,
124+
message: `'{{name}}' is defined but never used.`,
125+
data: variable.id,
126+
suggest:
127+
ignorePattern === '^_'
128+
? [
129+
{
130+
desc: `Replace the ${variable.id.name} with _${variable.id.name}`,
131+
fix(fixer) {
132+
return fixer.replaceText(
133+
variable.id,
134+
`_${variable.id.name}`
135+
)
136+
}
71137
}
72-
}
73-
]
74-
: []
75-
})
138+
]
139+
: []
140+
})
141+
}
76142
}
77143
}
78144
})

tests/lib/rules/no-unused-vars.js

+52
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,58 @@ tester.run('no-unused-vars', rule, {
162162
code: '<template><div v-for="(a, _i) in foo" ></div></template>',
163163
options: [{ ignorePattern: '^_' }],
164164
errors: ["'a' is defined but never used."]
165+
},
166+
{
167+
code:
168+
'<template><my-component v-slot="a" >{{d}}</my-component></template>',
169+
errors: ["'a' is defined but never used."]
170+
},
171+
{
172+
code:
173+
'<template><my-component v-for="i in foo" v-slot="a" >{{a}}</my-component></template>',
174+
errors: ["'i' is defined but never used."]
175+
},
176+
{
177+
code:
178+
'<template><my-component v-for="i in foo" v-slot="a" >{{i}}</my-component></template>',
179+
errors: ["'a' is defined but never used."]
180+
},
181+
{
182+
code:
183+
'<template><div v-for="({a, b}, [c, d], e, f) in foo" >{{f}}</div></template>',
184+
errors: [
185+
"'a' is defined but never used.",
186+
"'b' is defined but never used.",
187+
"'c' is defined but never used.",
188+
"'d' is defined but never used."
189+
]
190+
},
191+
{
192+
code:
193+
'<template><div v-for="({a, b}, c, [d], e, f) in foo" >{{f}}</div></template>',
194+
errors: [
195+
"'a' is defined but never used.",
196+
"'b' is defined but never used.",
197+
"'d' is defined but never used."
198+
]
199+
},
200+
{
201+
code:
202+
'<template><my-component v-slot="{a, b, c, d}" >{{d}}</my-component></template>',
203+
errors: [
204+
"'a' is defined but never used.",
205+
"'b' is defined but never used.",
206+
"'c' is defined but never used."
207+
]
208+
},
209+
{
210+
code:
211+
'<template><div v-for="({a, b: bar}, c = 1, [d], e, f) in foo" >{{f}}</div></template>',
212+
errors: [
213+
"'a' is defined but never used.",
214+
"'bar' is defined but never used.",
215+
"'d' is defined but never used."
216+
]
165217
}
166218
]
167219
})

0 commit comments

Comments
 (0)