diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index 37356c9c1..e808ee7f9 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -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 // ------------------------------------------------------------------------------ @@ -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}` + ) + } } - } - ] - : [] - }) + ] + : [] + }) + } } } }) diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js index 0c1b85e55..81df799bb 100644 --- a/tests/lib/rules/no-unused-vars.js +++ b/tests/lib/rules/no-unused-vars.js @@ -162,6 +162,58 @@ tester.run('no-unused-vars', rule, { code: '', options: [{ ignorePattern: '^_' }], errors: ["'a' is defined but never used."] + }, + { + code: + '', + errors: ["'a' is defined but never used."] + }, + { + code: + '', + errors: ["'i' is defined but never used."] + }, + { + code: + '', + errors: ["'a' is defined but never used."] + }, + { + code: + '', + 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: + '', + errors: [ + "'a' is defined but never used.", + "'b' is defined but never used.", + "'d' is defined but never used." + ] + }, + { + code: + '', + errors: [ + "'a' is defined but never used.", + "'b' is defined but never used.", + "'c' is defined but never used." + ] + }, + { + code: + '', + errors: [ + "'a' is defined but never used.", + "'bar' is defined but never used.", + "'d' is defined but never used." + ] } ] })