diff --git a/docs/rules/no-template-shadow.md b/docs/rules/no-template-shadow.md new file mode 100644 index 000000000..09b079334 --- /dev/null +++ b/docs/rules/no-template-shadow.md @@ -0,0 +1,56 @@ +# Disallow variable declarations from shadowing variables declared in the outer scope. (no-template-shadow) + +`no-template-shadow` should report variable definitions of v-for directives or scope attributes if those shadows the variables in parent scopes. + +## :book: Rule Details + +This rule aims to eliminate shadowed variable declarations of v-for directives or scope attributes. + +:-1: Examples of **incorrect** code for this rule: + +```html + + ``` + + ```html + + + ``` + +:+1: Examples of **correct** code for this rule: + +```html + + +``` + +## :wrench: Options + +Nothing. diff --git a/lib/rules/no-template-shadow.js b/lib/rules/no-template-shadow.js new file mode 100644 index 000000000..6a476d67d --- /dev/null +++ b/lib/rules/no-template-shadow.js @@ -0,0 +1,76 @@ +/** + * @fileoverview Disallow variable declarations from shadowing variables declared in the outer scope. + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const GROUP_NAMES = ['props', 'computed', 'data', 'methods'] + +module.exports = { + meta: { + docs: { + description: 'disallow variable declarations from shadowing variables declared in the outer scope', + category: 'strongly-recommended', + url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.1/docs/rules/no-template-shadow.md' + }, + fixable: null, + schema: [] + }, + + create (context) { + const jsVars = new Set() + let scope = { + parent: null, + nodes: [] + } + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return utils.defineTemplateBodyVisitor(context, { + VElement (node) { + scope = { + parent: scope, + nodes: scope.nodes.slice() // make copy + } + if (node.variables) { + for (const variable of node.variables) { + const varNode = variable.id + const name = varNode.name + if (scope.nodes.some(node => node.name === name) || jsVars.has(name)) { + context.report({ + node: varNode, + loc: varNode.loc, + message: "Variable '{{name}}' is already declared in the upper scope.", + data: { + name + } + }) + } else { + scope.nodes.push(varNode) + } + } + } + }, + 'VElement:exit' (node) { + scope = scope.parent + } + }, utils.executeOnVue(context, (obj) => { + const properties = Array.from(utils.iterateProperties(obj, new Set(GROUP_NAMES))) + for (const node of properties) { + jsVars.add(node.name) + } + })) + } +} diff --git a/lib/utils/index.js b/lib/utils/index.js index f45950fcd..e0279636c 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -26,7 +26,7 @@ module.exports = { * * @param {RuleContext} context The rule context to use parser services. * @param {Object} templateBodyVisitor The visitor to traverse the template body. - * @param {Object} scriptVisitor The visitor to traverse the script. + * @param {Object} [scriptVisitor] The visitor to traverse the script. * @returns {Object} The merged visitor. */ defineTemplateBodyVisitor (context, templateBodyVisitor, scriptVisitor) { diff --git a/tests/lib/rules/no-template-shadow.js b/tests/lib/rules/no-template-shadow.js new file mode 100644 index 000000000..c52f4ddb4 --- /dev/null +++ b/tests/lib/rules/no-template-shadow.js @@ -0,0 +1,213 @@ +/** + * @fileoverview Disallow variable declarations from shadowing variables declared in the outer scope. + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/no-template-shadow') +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module' + } +}) + +ruleTester.run('no-template-shadow', rule, { + + valid: [ + '', + '', + '', + '', + '', + '', + { + filename: 'test.vue', + code: ` + ` + }, + { + filename: 'test.vue', + code: ` + ` + } + ], + + invalid: [ + { + filename: 'test.vue', + code: '', + errors: [{ + message: "Variable 'i' is already declared in the upper scope.", + type: 'Identifier' + }] + }, + { + filename: 'test.vue', + code: ` + `, + errors: [{ + message: "Variable 'i' is already declared in the upper scope.", + type: 'Identifier', + line: 2 + }] + }, + { + filename: 'test.vue', + code: ` + `, + errors: [{ + message: "Variable 'i' is already declared in the upper scope.", + type: 'Identifier', + line: 2 + }, { + message: "Variable 'i' is already declared in the upper scope.", + type: 'Identifier', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + `, + errors: [{ + message: "Variable 'i' is already declared in the upper scope.", + type: 'Identifier', + line: 2 + }, { + message: "Variable 'i' is already declared in the upper scope.", + type: 'Identifier', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + `, + errors: [{ + message: "Variable 'i' is already declared in the upper scope.", + type: 'Identifier', + line: 2 + }, { + message: "Variable 'f' is already declared in the upper scope.", + type: 'Identifier', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + `, + errors: [{ + message: "Variable 'e' is already declared in the upper scope.", + type: 'Identifier', + line: 6 + }, { + message: "Variable 'f' is already declared in the upper scope.", + type: 'Identifier', + line: 7 + }] + } + ] +})