Skip to content

Commit

Permalink
Fixed false negatives when v-for and v-slot mixed or use destruct…
Browse files Browse the repository at this point in the history
…uring for `vue/no-unused-var` rule. (#1164)
  • Loading branch information
ota-meshi committed May 30, 2020
1 parent d708da6 commit 0045596
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 28 deletions.
122 changes: 94 additions & 28 deletions lib/rules/no-unused-vars.js
Expand Up @@ -6,6 +6,58 @@

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

/**
* @typedef {import('vue-eslint-parser').AST.Node} Node
* @typedef {import('vue-eslint-parser').AST.VElement} VElement
* @typedef {import('vue-eslint-parser').AST.Variable} Variable
*/
/**
* @typedef {Variable['kind']} VariableKind
*/

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

/**
* Groups variables by directive kind.
* @param {VElement} node The element node
* @returns { { [kind in VariableKind]?: Variable[] } } The variables of grouped by directive kind.
*/
function groupingVariables(node) {
/** @type { { [kind in VariableKind]?: Variable[] } } */
const result = {}
for (const variable of node.variables) {
const vars = result[variable.kind] || (result[variable.kind] = [])
vars.push(variable)
}
return result
}

/**
* Checks if the given variable was defined by destructuring.
* @param {Variable} variable the given variable to check
* @returns {boolean} `true` if the given variable was defined by destructuring.
*/
function isDestructuringVar(variable) {
const node = variable.id
/** @type {Node} */
let parent = node.parent
while (parent) {
if (
parent.type === 'VForExpression' ||
parent.type === 'VSlotScopeExpression'
) {
return false
}
if (parent.type === 'Property' || parent.type === 'ArrayPattern') {
return true
}
parent = parent.parent
}
return false
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -41,38 +93,52 @@ module.exports = {
ignoreRegEx = new RegExp(ignorePattern, 'u')
}
return utils.defineTemplateBodyVisitor(context, {
/**
* @param {VElement} node
*/
VElement(node) {
const variables = node.variables
for (let i = variables.length - 1; i >= 0; i--) {
const variable = variables[i]
const vars = groupingVariables(node)
for (const variables of Object.values(vars)) {
let hasAfterUsed = false

if (variable.references.length) {
break
}
for (let i = variables.length - 1; i >= 0; i--) {
const variable = variables[i]

if (ignoreRegEx != null && ignoreRegEx.test(variable.id.name)) {
continue
}
context.report({
node: variable.id,
loc: variable.id.loc,
message: `'{{name}}' is defined but never used.`,
data: variable.id,
suggest:
ignorePattern === '^_'
? [
{
desc: `Replace the ${variable.id.name} with _${variable.id.name}`,
fix(fixer) {
return fixer.replaceText(
variable.id,
`_${variable.id.name}`
)
if (variable.references.length) {
hasAfterUsed = true
continue
}

if (ignoreRegEx != null && ignoreRegEx.test(variable.id.name)) {
continue
}

if (hasAfterUsed && !isDestructuringVar(variable)) {
// If a variable after the variable is used, it will be skipped.
continue
}

context.report({
node: variable.id,
loc: variable.id.loc,
message: `'{{name}}' is defined but never used.`,
data: variable.id,
suggest:
ignorePattern === '^_'
? [
{
desc: `Replace the ${variable.id.name} with _${variable.id.name}`,
fix(fixer) {
return fixer.replaceText(
variable.id,
`_${variable.id.name}`
)
}
}
}
]
: []
})
]
: []
})
}
}
}
})
Expand Down
52 changes: 52 additions & 0 deletions tests/lib/rules/no-unused-vars.js
Expand Up @@ -162,6 +162,58 @@ tester.run('no-unused-vars', rule, {
code: '<template><div v-for="(a, _i) in foo" ></div></template>',
options: [{ ignorePattern: '^_' }],
errors: ["'a' is defined but never used."]
},
{
code:
'<template><my-component v-slot="a" >{{d}}</my-component></template>',
errors: ["'a' is defined but never used."]
},
{
code:
'<template><my-component v-for="i in foo" v-slot="a" >{{a}}</my-component></template>',
errors: ["'i' is defined but never used."]
},
{
code:
'<template><my-component v-for="i in foo" v-slot="a" >{{i}}</my-component></template>',
errors: ["'a' is defined but never used."]
},
{
code:
'<template><div v-for="({a, b}, [c, d], e, f) in foo" >{{f}}</div></template>',
errors: [
"'a' is defined but never used.",
"'b' is defined but never used.",
"'c' is defined but never used.",
"'d' is defined but never used."
]
},
{
code:
'<template><div v-for="({a, b}, c, [d], e, f) in foo" >{{f}}</div></template>',
errors: [
"'a' is defined but never used.",
"'b' is defined but never used.",
"'d' is defined but never used."
]
},
{
code:
'<template><my-component v-slot="{a, b, c, d}" >{{d}}</my-component></template>',
errors: [
"'a' is defined but never used.",
"'b' is defined but never used.",
"'c' is defined but never used."
]
},
{
code:
'<template><div v-for="({a, b: bar}, c = 1, [d], e, f) in foo" >{{f}}</div></template>',
errors: [
"'a' is defined but never used.",
"'bar' is defined but never used.",
"'d' is defined but never used."
]
}
]
})

0 comments on commit 0045596

Please sign in to comment.