Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed false negatives when v-for and v-slot mixed or use destructuring for vue/no-unused-var rule. #1164

Merged
merged 1 commit into from May 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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."
]
}
]
})