From af7186c6426df0f9e26c6e4c9c24fa54987dc115 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 22 Aug 2021 18:32:51 +0100 Subject: [PATCH 01/27] Fix: Remove warning in initialized variables (fixes #12687) --- CHANGELOG.md | 6 +-- lib/rules/no-shadow.js | 76 +++++++++++++++++++++++++++++++++++- tests/lib/rules/no-shadow.js | 11 +++++- 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5670c38ac4..cc2bac94e9b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3102,7 +3102,7 @@ v3.16.0 - February 20, 2017 * c7e64f3 Upgrade: mock-fs (#8070) (Toru Nagashima) * acc3301 Update: handle uncommon linebreaks consistently in rules (fixes #7949) (#8049) (Teddy Katz) * 591b74a Chore: enable operator-linebreak on ESLint codebase (#8064) (Teddy Katz) -* 6445d2a Docs: Add documentation for /* exported */ (fixes #7998) (#8065) (Lee Yi Min) +* 6445d2a Docs: Add documentation for /*exported*/ (fixes #7998) (#8065) (Lee Yi Min) * fcc38db Chore: simplify and improve performance for autofix (#8035) (Toru Nagashima) * b04fde7 Chore: improve performance of SourceCode constructor (#8054) (Teddy Katz) * 90fd555 Update: improve null detection in eqeqeq for ES6 regexes (fixes #8020) (#8042) (Teddy Katz) @@ -6718,7 +6718,7 @@ v0.0.9 - October 5, 2013 * Rule: no-loop-func (Ilya Volodin) * Merge branch 'master' of https://github.com/nzakas/eslint into no-underscore-dangle (Matt DuVall) * Use proper node declarations and __proto__ exception (Matt DuVall) -* Updating no-undef patch (see pull request #164) - Simplify parseBoolean() - Make knowledge of```/*jshint*/``` and ```/*global */``` internal to eslint object - Put user-declared globals in Program scope (Mark Macdonald) +* Updating no-undef patch (see pull request #164) - Simplify parseBoolean() - Make knowledge of```/*jshint*/``` and ```/*global*/``` internal to eslint object - Put user-declared globals in Program scope (Mark Macdonald) * Rule: no-eq-null (Ian Christian Myers) * fixed broken merge (Raphael Pigulla) * fixes #143 (Raphael Pigulla) @@ -6727,7 +6727,7 @@ v0.0.9 - October 5, 2013 * Update eslint.json with no-underscore-dangle rule (Matt DuVall) * Rule: no-underscore-dangle for func/var declarations (Matt DuVall) * Warn on finding the bitwise NOT operator (James Allardice) -* Updating no-undef patch (see pull request #164) 3. Move parsing of ```/*global */``` and ```/*jshint */``` to eslint.js (Mark Macdonald) +* Updating no-undef patch (see pull request #164) 3. Move parsing of ```/*global*/``` and ```/*jshint*/``` to eslint.js (Mark Macdonald) * Warn on finding a bitwise shift operator (fixes #170) (James Allardice) * Fix broken test (James Allardice) * Add support for the do-while statement to the curly rule (closes #167) (James Allardice) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index bd619235ab9c..1193e3fde453 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -11,6 +11,16 @@ const astUtils = require("./utils/ast-utils"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const INIT_NODE_TYPES = [ + "AssignmentPattern", + "ForInStatement", + "ForOfStatement" +]; + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -57,6 +67,70 @@ module.exports = { allow: (context.options[0] && context.options[0].allow) || [] }; + /** + * @callback CheckNodeCallback + * @param {ASTNode} node A node to check. + */ + + /** + * Traverses AST from a given node to find a node that verifies checkNodeCB condition. + * @param {ASTNode} node a node to get. + * @param {CheckNodeCallback} checkNodeCB a callback that checks whether or not the node verifies its condition or not. + * @returns {ASTNode} the node that verifies checkNodeCB condition. + */ + function getNode(node, checkNodeCB) { + let currentNode = node; + + while (currentNode && !checkNodeCB(currentNode)) { + currentNode = currentNode.parent; + } + return currentNode; + } + + /** + * Checks if a variable and a shadowedVariable have the same init pattern node parent. + * @param {Object} variable a variable to check. + * @param {Object} shadowedVariable a shadowedVariable to check. + * @returns {boolean} Whether or not the variable and the shadowedVariable have the same init pattern node parent. + */ + function areOnInitPatternNode(variable, shadowedVariable) { + const outerDef = shadowedVariable.defs[0]; + const innerDef = variable.defs[0]; + + const callExpressionNode = getNode( + innerDef.node, + node => node.type === "CallExpression" + ); + + if (callExpressionNode) { + const initNode1 = getNode( + outerDef && outerDef.name && outerDef.name.parent, + node => { + const { type, init, right } = node; + let nodeType; + + if (type === "VariableDeclarator" && init) { + nodeType = node.init.type; + } + if (INIT_NODE_TYPES.includes(type)) { + nodeType = right.type; + } + return nodeType === "CallExpression"; + } + ); + + const initNode2 = getNode( + callExpressionNode, + node => node === initNode1 + ); + + if (initNode2) { + return true; + } + } + return false; + } + /** * Check if variable name is allowed. * @param {ASTNode} variable The variable to check. @@ -186,7 +260,7 @@ module.exports = { if (shadowed && (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && + !isOnInitializer(variable, shadowed) && !areOnInitPatternNode(variable, shadowed) && !(options.hoist !== "all" && isInTdz(variable, shadowed)) ) { const location = getDeclaredLocation(shadowed); diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index 13c4c1100d6c..0c2521bd25b2 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -63,7 +63,16 @@ ruleTester.run("no-shadow", rule, { { code: "class C { static { let x; } static { let x; } }", parserOptions: { ecmaVersion: 2022 } }, { code: "class C { static { var x; { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, { code: "class C { static { { var x; } { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { { let x; } { let x; } } }", parserOptions: { ecmaVersion: 2022 } } + { code: "class C { static { { let x; } { let x; } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "const a = [].find(a=>a)", parserOptions: { ecmaVersion: 6 } }, + { code: "const [a = [].find(a => true)] = dummy", parserOptions: { ecmaVersion: 6 } }, + { code: "const { a = [].find(a => true) } = dummy", parserOptions: { ecmaVersion: 6 } }, + { code: "function func(a = [].find(a => true)) {}", parserOptions: { ecmaVersion: 6 } }, + { code: "for (const a in [].find(a => true)) {}", parserOptions: { ecmaVersion: 6 } }, + { code: "for (const a of [].find(a => true)) {}", parserOptions: { ecmaVersion: 6 } }, + { code: "const a = [].map(a => true).filter(a => a === 'b')", parserOptions: { ecmaVersion: 6 } }, + { code: "const { a } = (({ a }) => ({ a }))();", parserOptions: { ecmaVersion: 6 } }, + { code: "const person = people.find(item => {const person = item.name; return person === 'ahmed'})", parserOptions: { ecmaVersion: 6 } } ], invalid: [ { From 5174969d4e16d1bbea990bde3287146fce94c4e2 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Fri, 27 Aug 2021 00:34:01 +0100 Subject: [PATCH 02/27] Fix: Remove warning in initialized variables (fixes #12687) --- CHANGELOG.md | 6 +++--- lib/rules/no-shadow.js | 36 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc2bac94e9b1..ce5670c38ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3102,7 +3102,7 @@ v3.16.0 - February 20, 2017 * c7e64f3 Upgrade: mock-fs (#8070) (Toru Nagashima) * acc3301 Update: handle uncommon linebreaks consistently in rules (fixes #7949) (#8049) (Teddy Katz) * 591b74a Chore: enable operator-linebreak on ESLint codebase (#8064) (Teddy Katz) -* 6445d2a Docs: Add documentation for /*exported*/ (fixes #7998) (#8065) (Lee Yi Min) +* 6445d2a Docs: Add documentation for /* exported */ (fixes #7998) (#8065) (Lee Yi Min) * fcc38db Chore: simplify and improve performance for autofix (#8035) (Toru Nagashima) * b04fde7 Chore: improve performance of SourceCode constructor (#8054) (Teddy Katz) * 90fd555 Update: improve null detection in eqeqeq for ES6 regexes (fixes #8020) (#8042) (Teddy Katz) @@ -6718,7 +6718,7 @@ v0.0.9 - October 5, 2013 * Rule: no-loop-func (Ilya Volodin) * Merge branch 'master' of https://github.com/nzakas/eslint into no-underscore-dangle (Matt DuVall) * Use proper node declarations and __proto__ exception (Matt DuVall) -* Updating no-undef patch (see pull request #164) - Simplify parseBoolean() - Make knowledge of```/*jshint*/``` and ```/*global*/``` internal to eslint object - Put user-declared globals in Program scope (Mark Macdonald) +* Updating no-undef patch (see pull request #164) - Simplify parseBoolean() - Make knowledge of```/*jshint*/``` and ```/*global */``` internal to eslint object - Put user-declared globals in Program scope (Mark Macdonald) * Rule: no-eq-null (Ian Christian Myers) * fixed broken merge (Raphael Pigulla) * fixes #143 (Raphael Pigulla) @@ -6727,7 +6727,7 @@ v0.0.9 - October 5, 2013 * Update eslint.json with no-underscore-dangle rule (Matt DuVall) * Rule: no-underscore-dangle for func/var declarations (Matt DuVall) * Warn on finding the bitwise NOT operator (James Allardice) -* Updating no-undef patch (see pull request #164) 3. Move parsing of ```/*global*/``` and ```/*jshint*/``` to eslint.js (Mark Macdonald) +* Updating no-undef patch (see pull request #164) 3. Move parsing of ```/*global */``` and ```/*jshint */``` to eslint.js (Mark Macdonald) * Warn on finding a bitwise shift operator (fixes #170) (James Allardice) * Fix broken test (James Allardice) * Add support for the do-while statement to the curly rule (closes #167) (James Allardice) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 1193e3fde453..7a7fa1060282 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -68,42 +68,42 @@ module.exports = { }; /** - * @callback CheckNodeCallback + * @callback MatchCallback * @param {ASTNode} node A node to check. */ /** - * Traverses AST from a given node to find a node that verifies checkNodeCB condition. + * Searches from the current node through its ancestry to find a matching node. * @param {ASTNode} node a node to get. - * @param {CheckNodeCallback} checkNodeCB a callback that checks whether or not the node verifies its condition or not. - * @returns {ASTNode} the node that verifies checkNodeCB condition. + * @param {MatchCallback} match a callback that checks whether or not the node verifies its condition or not. + * @returns {ASTNode} the matching node. */ - function getNode(node, checkNodeCB) { + function findSelfOrAncestor(node, match) { let currentNode = node; - while (currentNode && !checkNodeCB(currentNode)) { + while (currentNode && !match(currentNode)) { currentNode = currentNode.parent; } return currentNode; } /** - * Checks if a variable and a shadowedVariable have the same init pattern node parent. + * Checks if a variable and a shadowedVariable have the same init pattern ancestor. * @param {Object} variable a variable to check. * @param {Object} shadowedVariable a shadowedVariable to check. - * @returns {boolean} Whether or not the variable and the shadowedVariable have the same init pattern node parent. + * @returns {boolean} Whether or not the variable and the shadowedVariable have the same init pattern ancestor. */ - function areOnInitPatternNode(variable, shadowedVariable) { + function isInitPatternNode(variable, shadowedVariable) { const outerDef = shadowedVariable.defs[0]; const innerDef = variable.defs[0]; - const callExpressionNode = getNode( + const callExpressionNode = findSelfOrAncestor( innerDef.node, node => node.type === "CallExpression" ); if (callExpressionNode) { - const initNode1 = getNode( + const initAncestor = findSelfOrAncestor( outerDef && outerDef.name && outerDef.name.parent, node => { const { type, init, right } = node; @@ -119,13 +119,13 @@ module.exports = { } ); - const initNode2 = getNode( - callExpressionNode, - node => node === initNode1 - ); + if (initAncestor) { + const isSameInitAncestor = !!findSelfOrAncestor( + callExpressionNode, + node => node === initAncestor + ); - if (initNode2) { - return true; + return isSameInitAncestor; } } return false; @@ -260,7 +260,7 @@ module.exports = { if (shadowed && (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && !areOnInitPatternNode(variable, shadowed) && + !isOnInitializer(variable, shadowed) && !isInitPatternNode(variable, shadowed) && !(options.hoist !== "all" && isInTdz(variable, shadowed)) ) { const location = getDeclaredLocation(shadowed); From 3860b984cc83bfead51c0a3d57458a598b1e346c Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 5 Sep 2021 12:14:36 +0100 Subject: [PATCH 03/27] Fix: Remove warning in initialized variables (fixes #12687) --- lib/rules/no-shadow.js | 2 +- tests/lib/rules/no-shadow.js | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 7a7fa1060282..5725561c88c6 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -260,7 +260,7 @@ module.exports = { if (shadowed && (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && !isInitPatternNode(variable, shadowed) && + !isOnInitializer(variable, shadowed) && (!isInitPatternNode(variable, shadowed) || options.hoist === "all") && !(options.hoist !== "all" && isInTdz(variable, shadowed)) ) { const location = getDeclaredLocation(shadowed); diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index 0c2521bd25b2..fe27e6716bf9 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -867,7 +867,30 @@ ruleTester.run("no-shadow", rule, { type: "Identifier", line: 1, column: 50 - }] + }], + code: "let x = foo((x,y) => {});\nlet y;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }, + { + messageId: "noShadow", + data: { + name: "y", + shadowedLine: 2, + shadowedColumn: 5 + }, + type: "Identifier" + } + ] } ] }); From 083276012a9c7c853c219e768e3b38f945254e14 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 19 Sep 2021 14:03:41 +0100 Subject: [PATCH 04/27] Docs: add invalid ambiguous example in doc (fixes #12687) --- lib/rules/no-shadow.js | 76 +- tests/lib/rules/no-shadow.js | 1748 +++++++++++++++++----------------- 2 files changed, 859 insertions(+), 965 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 5725561c88c6..bd619235ab9c 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -11,16 +11,6 @@ const astUtils = require("./utils/ast-utils"); -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -const INIT_NODE_TYPES = [ - "AssignmentPattern", - "ForInStatement", - "ForOfStatement" -]; - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -67,70 +57,6 @@ module.exports = { allow: (context.options[0] && context.options[0].allow) || [] }; - /** - * @callback MatchCallback - * @param {ASTNode} node A node to check. - */ - - /** - * Searches from the current node through its ancestry to find a matching node. - * @param {ASTNode} node a node to get. - * @param {MatchCallback} match a callback that checks whether or not the node verifies its condition or not. - * @returns {ASTNode} the matching node. - */ - function findSelfOrAncestor(node, match) { - let currentNode = node; - - while (currentNode && !match(currentNode)) { - currentNode = currentNode.parent; - } - return currentNode; - } - - /** - * Checks if a variable and a shadowedVariable have the same init pattern ancestor. - * @param {Object} variable a variable to check. - * @param {Object} shadowedVariable a shadowedVariable to check. - * @returns {boolean} Whether or not the variable and the shadowedVariable have the same init pattern ancestor. - */ - function isInitPatternNode(variable, shadowedVariable) { - const outerDef = shadowedVariable.defs[0]; - const innerDef = variable.defs[0]; - - const callExpressionNode = findSelfOrAncestor( - innerDef.node, - node => node.type === "CallExpression" - ); - - if (callExpressionNode) { - const initAncestor = findSelfOrAncestor( - outerDef && outerDef.name && outerDef.name.parent, - node => { - const { type, init, right } = node; - let nodeType; - - if (type === "VariableDeclarator" && init) { - nodeType = node.init.type; - } - if (INIT_NODE_TYPES.includes(type)) { - nodeType = right.type; - } - return nodeType === "CallExpression"; - } - ); - - if (initAncestor) { - const isSameInitAncestor = !!findSelfOrAncestor( - callExpressionNode, - node => node === initAncestor - ); - - return isSameInitAncestor; - } - } - return false; - } - /** * Check if variable name is allowed. * @param {ASTNode} variable The variable to check. @@ -260,7 +186,7 @@ module.exports = { if (shadowed && (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && (!isInitPatternNode(variable, shadowed) || options.hoist === "all") && + !isOnInitializer(variable, shadowed) && !(options.hoist !== "all" && isInTdz(variable, shadowed)) ) { const location = getDeclaredLocation(shadowed); diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index fe27e6716bf9..b8e15cc18297 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -3,894 +3,862 @@ * @author Ilya Volodin */ -"use strict"; + "use strict"; -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const rule = require("../../../lib/rules/no-shadow"), - { RuleTester } = require("../../../lib/rule-tester"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -const ruleTester = new RuleTester(); - -ruleTester.run("no-shadow", rule, { - valid: [ - "var a=3; function b(x) { a++; return x + a; }; setTimeout(function() { b(a); }, 0);", - "(function() { var doSomething = function doSomething() {}; doSomething() }())", - "var arguments;\nfunction bar() { }", - { code: "var a=3; var b = (x) => { a++; return x + a; }; setTimeout(() => { b(a); }, 0);", parserOptions: { ecmaVersion: 6 } }, - { code: "class A {}", parserOptions: { ecmaVersion: 6 } }, - { code: "class A { constructor() { var a; } }", parserOptions: { ecmaVersion: 6 } }, - { code: "(function() { var A = class A {}; })()", parserOptions: { ecmaVersion: 6 } }, - { code: "{ var a; } var a;", parserOptions: { ecmaVersion: 6 } }, // this case reports `no-redeclare`, not shadowing. - { code: "{ let a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ let a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ let a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } const a = 1;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ let a; } let a;", parserOptions: { ecmaVersion: 6 } }, - { code: "{ let a; } var a;", parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } const a = 1;", parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } var a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } let a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } var a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } let a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } var a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } let a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } var a;", parserOptions: { ecmaVersion: 6 } }, - "function foo() { var Object = 0; }", - { code: "function foo() { var top = 0; }", env: { browser: true } }, - { code: "var Object = 0;", options: [{ builtinGlobals: true }] }, - { code: "var top = 0;", options: [{ builtinGlobals: true }], env: { browser: true } }, - { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] }, - { code: "class C { foo; foo() { let foo; } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { var x; } static { var x; } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { let x; } static { let x; } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { var x; { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { { var x; } { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { { let x; } { let x; } } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "const a = [].find(a=>a)", parserOptions: { ecmaVersion: 6 } }, - { code: "const [a = [].find(a => true)] = dummy", parserOptions: { ecmaVersion: 6 } }, - { code: "const { a = [].find(a => true) } = dummy", parserOptions: { ecmaVersion: 6 } }, - { code: "function func(a = [].find(a => true)) {}", parserOptions: { ecmaVersion: 6 } }, - { code: "for (const a in [].find(a => true)) {}", parserOptions: { ecmaVersion: 6 } }, - { code: "for (const a of [].find(a => true)) {}", parserOptions: { ecmaVersion: 6 } }, - { code: "const a = [].map(a => true).filter(a => a === 'b')", parserOptions: { ecmaVersion: 6 } }, - { code: "const { a } = (({ a }) => ({ a }))();", parserOptions: { ecmaVersion: 6 } }, - { code: "const person = people.find(item => {const person = item.name; return person === 'ahmed'})", parserOptions: { ecmaVersion: 6 } } - ], - invalid: [ - { - code: "function a(x) { var b = function c() { var x = 'foo'; }; }", - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 12 - }, - type: "Identifier", - line: 1, - column: 44 - }] - }, - { - code: "var a = (x) => { var b = () => { var x = 'foo'; }; }", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 10 - }, - type: "Identifier", - line: 1, - column: 38 - }] - }, - { - code: "function a(x) { var b = function () { var x = 'foo'; }; }", - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 12 - }, - type: "Identifier", - line: 1, - column: 43 - }] - }, - { - code: "var x = 1; function a(x) { return ++x; }", - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier", - line: 1, - column: 23 - }] - }, - { - code: "var a=3; function b() { var a=10; }", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }] - }, - { - code: "var a=3; function b() { var a=10; }; setTimeout(function() { b(); }, 0);", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }] - }, - { - code: "var a=3; function b() { var a=10; var b=0; }; setTimeout(function() { b(); }, 0);", - errors: [ - { - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }, { - messageId: "noShadow", - data: { - name: "b", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - } - ] - }, - { - code: "var x = 1; { let x = 2; }", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }] - }, - { - code: "let x = 1; { const x = 2; }", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }] - }, - { - code: "{ let a; } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 21 - }, - type: "Identifier" - }] - }, - { - code: "{ const a = 0; } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 27 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { let a; } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 36 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var a; } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 36 - }, - type: "Identifier" - }] - }, - { - code: "function foo(a) { } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 30 - }, - type: "Identifier" - }] - }, - { - code: "{ let a; } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 16 - }, - type: "Identifier" - }] - }, - { - code: "{ let a; } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 16 - }, - type: "Identifier" - }] - }, - { - code: "{ let a; } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 21 - }, - type: "Identifier" - }] - }, - { - code: "{ const a = 0; } const a = 1;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 24 - }, - type: "Identifier" - }] - }, - { - code: "{ const a = 0; } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 22 - }, - type: "Identifier" - }] - }, - { - code: "{ const a = 0; } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 27 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { let a; } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 31 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { let a; } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 31 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { let a; } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 36 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var a; } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 31 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var a; } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 31 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var a; } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 36 - }, - type: "Identifier" - }] - }, - { - code: "function foo(a) { } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 25 - }, - type: "Identifier" - }] - }, - { - code: "function foo(a) { } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 25 - }, - type: "Identifier" - }] - }, - { - code: "function foo(a) { } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 30 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { function a(){} })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { class a{} })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { (function a(){}); })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { (class a{}); })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function(a) {}; })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function() { function a() {} }; })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function() { class a{} }; })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function() { (function a() {}); }; })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function() { (class a{}); }; })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = class { constructor() { class a {} } }; })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "class A { constructor() { var A; } }", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "A", - shadowedLine: 1, - shadowedColumn: 7 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { function a(){ function a(){} } })()", - errors: [ - { - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier", - line: 1, - column: 26 - }, - { - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 26 - }, - type: "Identifier", - line: 1, - column: 40 - } - ] - }, - { - code: "function foo() { var Object = 0; }", - options: [{ builtinGlobals: true }], - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "Object" - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var top = 0; }", - options: [{ builtinGlobals: true }], - env: { browser: true }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "top" - }, - type: "Identifier" - }] - }, - { - code: "var Object = 0;", - options: [{ builtinGlobals: true }], - parserOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "Object" - }, - type: "Identifier" - }] - }, - { - code: "var top = 0;", - options: [{ builtinGlobals: true }], - parserOptions: { ecmaVersion: 6, sourceType: "module" }, - env: { browser: true }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "top" - }, - type: "Identifier" - }] - }, - { - code: "var Object = 0;", - options: [{ builtinGlobals: true }], - parserOptions: { ecmaFeatures: { globalReturn: true } }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "Object" - }, - type: "Identifier" - }] - }, - { - code: "var top = 0;", - options: [{ builtinGlobals: true }], - parserOptions: { ecmaFeatures: { globalReturn: true } }, - env: { browser: true }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "top" - }, - type: "Identifier" - }] - }, - { - code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", - errors: [{ - messageId: "noShadow", - data: { - name: "cb", - shadowedLine: 1, - shadowedColumn: 14 - }, - type: "Identifier", - line: 1, - column: 31 - }] - }, - { - code: "class C { static { let a; { let a; } } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 24 - }, - type: "Identifier", - line: 1, - column: 33 - }] - }, - { - code: "class C { static { var C; } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "C", - shadowedLine: 1, - shadowedColumn: 7 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "class C { static { let C; } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "C", - shadowedLine: 1, - shadowedColumn: 7 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "var a; class C { static { var a; } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier", - line: 1, - column: 31 - }] - }, - { - code: "class C { static { var a; } } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 35 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "class C { static { let a; } } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 35 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "class C { static { var a; } } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 35 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "class C { static { var a; class D { static { var a; } } } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 24 - }, - type: "Identifier", - line: 1, - column: 50 - }] - }, - { - code: "class C { static { let a; class D { static { let a; } } } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 24 - }, - type: "Identifier", - line: 1, - column: 50 - }], - code: "let x = foo((x,y) => {});\nlet y;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }, - { - messageId: "noShadow", - data: { - name: "y", - shadowedLine: 2, - shadowedColumn: 5 - }, - type: "Identifier" - } - ] - } - ] -}); + //------------------------------------------------------------------------------ + // Requirements + //------------------------------------------------------------------------------ + + const rule = require("../../../lib/rules/no-shadow"), + { RuleTester } = require("../../../lib/rule-tester"); + + //------------------------------------------------------------------------------ + // Tests + //------------------------------------------------------------------------------ + + const ruleTester = new RuleTester(); + + ruleTester.run("no-shadow", rule, { + valid: [ + "var a=3; function b(x) { a++; return x + a; }; setTimeout(function() { b(a); }, 0);", + "(function() { var doSomething = function doSomething() {}; doSomething() }())", + "var arguments;\nfunction bar() { }", + { code: "var a=3; var b = (x) => { a++; return x + a; }; setTimeout(() => { b(a); }, 0);", parserOptions: { ecmaVersion: 6 } }, + { code: "class A {}", parserOptions: { ecmaVersion: 6 } }, + { code: "class A { constructor() { var a; } }", parserOptions: { ecmaVersion: 6 } }, + { code: "(function() { var A = class A {}; })()", parserOptions: { ecmaVersion: 6 } }, + { code: "{ var a; } var a;", parserOptions: { ecmaVersion: 6 } }, // this case reports `no-redeclare`, not shadowing. + { code: "{ let a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ let a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ let a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } const a = 1;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ let a; } let a;", parserOptions: { ecmaVersion: 6 } }, + { code: "{ let a; } var a;", parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } const a = 1;", parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } var a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } let a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } var a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } let a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } var a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } let a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } var a;", parserOptions: { ecmaVersion: 6 } }, + "function foo() { var Object = 0; }", + { code: "function foo() { var top = 0; }", env: { browser: true } }, + { code: "var Object = 0;", options: [{ builtinGlobals: true }] }, + { code: "var top = 0;", options: [{ builtinGlobals: true }], env: { browser: true } }, + { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] }, + { code: "class C { foo; foo() { let foo; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { var x; } static { var x; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { let x; } static { let x; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { var x; { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { { var x; } { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { { let x; } { let x; } } }", parserOptions: { ecmaVersion: 2022 } } + ], + invalid: [ + { + code: "function a(x) { var b = function c() { var x = 'foo'; }; }", + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 12 + }, + type: "Identifier", + line: 1, + column: 44 + }] + }, + { + code: "var a = (x) => { var b = () => { var x = 'foo'; }; }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 10 + }, + type: "Identifier", + line: 1, + column: 38 + }] + }, + { + code: "function a(x) { var b = function () { var x = 'foo'; }; }", + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 12 + }, + type: "Identifier", + line: 1, + column: 43 + }] + }, + { + code: "var x = 1; function a(x) { return ++x; }", + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier", + line: 1, + column: 23 + }] + }, + { + code: "var a=3; function b() { var a=10; }", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }] + }, + { + code: "var a=3; function b() { var a=10; }; setTimeout(function() { b(); }, 0);", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }] + }, + { + code: "var a=3; function b() { var a=10; var b=0; }; setTimeout(function() { b(); }, 0);", + errors: [ + { + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }, { + messageId: "noShadow", + data: { + name: "b", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + } + ] + }, + { + code: "var x = 1; { let x = 2; }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }] + }, + { + code: "let x = 1; { const x = 2; }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }] + }, + { + code: "{ let a; } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 21 + }, + type: "Identifier" + }] + }, + { + code: "{ const a = 0; } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 27 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { let a; } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 36 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var a; } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 36 + }, + type: "Identifier" + }] + }, + { + code: "function foo(a) { } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 30 + }, + type: "Identifier" + }] + }, + { + code: "{ let a; } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 16 + }, + type: "Identifier" + }] + }, + { + code: "{ let a; } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 16 + }, + type: "Identifier" + }] + }, + { + code: "{ let a; } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 21 + }, + type: "Identifier" + }] + }, + { + code: "{ const a = 0; } const a = 1;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier" + }] + }, + { + code: "{ const a = 0; } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 22 + }, + type: "Identifier" + }] + }, + { + code: "{ const a = 0; } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 27 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { let a; } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 31 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { let a; } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 31 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { let a; } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 36 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var a; } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 31 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var a; } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 31 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var a; } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 36 + }, + type: "Identifier" + }] + }, + { + code: "function foo(a) { } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 25 + }, + type: "Identifier" + }] + }, + { + code: "function foo(a) { } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 25 + }, + type: "Identifier" + }] + }, + { + code: "function foo(a) { } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 30 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { function a(){} })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { class a{} })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { (function a(){}); })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { (class a{}); })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function(a) {}; })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function() { function a() {} }; })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function() { class a{} }; })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function() { (function a() {}); }; })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function() { (class a{}); }; })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = class { constructor() { class a {} } }; })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "class A { constructor() { var A; } }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "A", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { function a(){ function a(){} } })()", + errors: [ + { + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier", + line: 1, + column: 26 + }, + { + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 26 + }, + type: "Identifier", + line: 1, + column: 40 + } + ] + }, + { + code: "function foo() { var Object = 0; }", + options: [{ builtinGlobals: true }], + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "Object" + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var top = 0; }", + options: [{ builtinGlobals: true }], + env: { browser: true }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "top" + }, + type: "Identifier" + }] + }, + { + code: "var Object = 0;", + options: [{ builtinGlobals: true }], + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "Object" + }, + type: "Identifier" + }] + }, + { + code: "var top = 0;", + options: [{ builtinGlobals: true }], + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + env: { browser: true }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "top" + }, + type: "Identifier" + }] + }, + { + code: "var Object = 0;", + options: [{ builtinGlobals: true }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "Object" + }, + type: "Identifier" + }] + }, + { + code: "var top = 0;", + options: [{ builtinGlobals: true }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + env: { browser: true }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "top" + }, + type: "Identifier" + }] + }, + { + code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", + errors: [{ + messageId: "noShadow", + data: { + name: "cb", + shadowedLine: 1, + shadowedColumn: 14 + }, + type: "Identifier", + line: 1, + column: 31 + }] + }, + { + code: "class C { static { let a; { let a; } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier", + line: 1, + column: 33 + }] + }, + { + code: "class C { static { var C; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "C", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { let C; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "C", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "var a; class C { static { var a; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier", + line: 1, + column: 31 + }] + }, + { + code: "class C { static { var a; } } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 35 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { let a; } } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 35 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { var a; } } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 35 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { var a; class D { static { var a; } } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier", + line: 1, + column: 50 + }] + }, + { + code: "class C { static { let a; class D { static { let a; } } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier", + line: 1, + column: 50 + }] + } + ] + }); \ No newline at end of file From 0e40f043093f14ba87e2f0f202552fc801c1673f Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 19 Sep 2021 14:04:38 +0100 Subject: [PATCH 05/27] Docs: add invalid ambiguous example in doc (fixes #12687) --- docs/rules/no-shadow.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/rules/no-shadow.md b/docs/rules/no-shadow.md index bf8400a42393..e13d48f423e3 100644 --- a/docs/rules/no-shadow.md +++ b/docs/rules/no-shadow.md @@ -40,6 +40,15 @@ if (true) { } ``` +Examples of **incorrect** code for this rule: + +```js +/*eslint no-shadow: "error"*/ +/*eslint-env es6*/ + +const person = people.find(person => person.name === 'John'); +``` + ## Options This rule takes one option, an object, with properties `"builtinGlobals"`, `"hoist"` and `"allow"`. From b314ffd0a70884db1198494ddd2c84f2901c2a34 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 26 Sep 2021 01:39:10 +0100 Subject: [PATCH 06/27] Fix: Adding option for variables on intialization (fixes #12687) --- docs/rules/no-shadow.md | 21 ++++++- lib/rules/no-shadow.js | 130 +++++++++++++++++++++++++++++++++++----- 2 files changed, 133 insertions(+), 18 deletions(-) diff --git a/docs/rules/no-shadow.md b/docs/rules/no-shadow.md index e13d48f423e3..45c9fce35cff 100644 --- a/docs/rules/no-shadow.md +++ b/docs/rules/no-shadow.md @@ -51,11 +51,11 @@ const person = people.find(person => person.name === 'John'); ## Options -This rule takes one option, an object, with properties `"builtinGlobals"`, `"hoist"` and `"allow"`. +This rule takes one option, an object, with properties `"builtinGlobals"`, `"hoist"`, `"allow"` and `"ignoreOnInitialization"`. ```json { - "no-shadow": ["error", { "builtinGlobals": false, "hoist": "functions", "allow": [] }] + "no-shadow": ["error", { "builtinGlobals": false, "hoist": "functions", "allow": [], "ignoreOnInitialization": false }] } ``` @@ -173,6 +173,23 @@ foo(function (err, result) { }); ``` +### ignoreOnInitialization + +The `ignoreOnInitialization` option is `false` by default. +If it is `true`, the rule prevents reporting variables on initialization statements, the shadowed variable must be on the left side and the shadowing variable must be on the right side. + +Examples of **correct** code for the `{ "ignoreOnInitialization": true }` option: + +```js +/*eslint no-shadow: ["error", { "ignoreOnInitialization": true }]*/ + +const person = people.find(person => person.name === "John"); +``` + +## Further Reading + +* [Variable Shadowing](https://en.wikipedia.org/wiki/Variable_shadowing) + ## Related Rules * [no-shadow-restricted-names](no-shadow-restricted-names.md) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index bd619235ab9c..beca6a7d9217 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -11,6 +11,16 @@ const astUtils = require("./utils/ast-utils"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const INIT_NODE_TYPES = [ + "AssignmentPattern", + "ForInStatement", + "ForOfStatement" +]; + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -37,7 +47,8 @@ module.exports = { items: { type: "string" } - } + }, + ignoreOnInitialization: { type: "boolean", default: false } }, additionalProperties: false } @@ -54,9 +65,95 @@ module.exports = { const options = { builtinGlobals: context.options[0] && context.options[0].builtinGlobals, hoist: (context.options[0] && context.options[0].hoist) || "functions", - allow: (context.options[0] && context.options[0].allow) || [] + allow: (context.options[0] && context.options[0].allow) || [], + ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization }; + /** + * @callback MatchCallback + * @param {ASTNode} node A node to check. + */ + + /** + * Searches from the current node through its ancestry to find a matching node. + * @param {ASTNode} node a node to get. + * @param {MatchCallback} match a callback that checks whether or not the node verifies its condition or not. + * @returns {ASTNode} the matching node. + */ + function findSelfOrAncestor(node, match) { + let currentNode = node; + + while (currentNode) { + const result = match(currentNode); + + if (result === 1) { + return currentNode; + } + if (result === -1) { + return null; + } + currentNode = currentNode.parent; + + } + return currentNode; + } + + /** + * Checks if a variable and a shadowedVariable have the same init pattern ancestor. + * @param {Object} variable a variable to check. + * @param {Object} shadowedVariable a shadowedVariable to check. + * @returns {boolean} Whether or not the variable and the shadowedVariable have the same init pattern ancestor. + */ + function isInitPatternNode(variable, shadowedVariable) { + const outerDef = shadowedVariable.defs[0]; + const innerDef = variable.defs[0]; + + const callExpressionNode = findSelfOrAncestor( + innerDef.node, + node => { + const { type } = node; + + if (type === "CallExpression") { + return 1; + } + if (type === "ClassDeclaration") { + return -1; + } + return 0; + } + ); + + let callExpressionRoot = callExpressionNode; + let currentNode = callExpressionRoot && callExpressionRoot.parent; + + while (currentNode && currentNode.type === "MemberExpression") { + callExpressionRoot = currentNode.parent; + currentNode = currentNode.parent.parent; + } + if (callExpressionRoot) { + return !!findSelfOrAncestor( + outerDef && outerDef.name && outerDef.name.parent, + node => { + const { type, init, right } = node; + let rightNode; + + if (type === "VariableDeclarator" && init) { + rightNode = node.init; + } + if (INIT_NODE_TYPES.includes(type)) { + rightNode = right; + } + if (rightNode === callExpressionRoot) { + return 1; + } + return 0; + + } + ); + } + return false; + } + /** * Check if variable name is allowed. * @param {ASTNode} variable The variable to check. @@ -99,11 +196,11 @@ module.exports = { return ( outer && - inner && - outer[0] < inner[0] && - inner[1] < outer[1] && - ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && - outerScope === innerScope.upper + inner && + outer[0] < inner[0] && + inner[1] < outer[1] && + ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && + outerScope === innerScope.upper ); } @@ -154,11 +251,11 @@ module.exports = { return ( inner && - outer && - inner[1] < outer[0] && + outer && + inner[1] < outer[0] && - // Excepts FunctionDeclaration if is {"hoist":"function"}. - (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") + // Excepts FunctionDeclaration if is {"hoist":"function"}. + (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") ); } @@ -175,8 +272,8 @@ module.exports = { // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. if (variable.identifiers.length === 0 || - isDuplicatedClassNameVariable(variable) || - isAllowed(variable) + isDuplicatedClassNameVariable(variable) || + isAllowed(variable) ) { continue; } @@ -185,9 +282,10 @@ module.exports = { const shadowed = astUtils.getVariableByName(scope.upper, variable.name); if (shadowed && - (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && - !(options.hoist !== "all" && isInTdz(variable, shadowed)) + (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && + !isOnInitializer(variable, shadowed) && + (!(options.ignoreOnInitialization && isInitPatternNode(variable, shadowed)) || options.hoist === "all") && + !(options.hoist !== "all" && isInTdz(variable, shadowed)) ) { const location = getDeclaredLocation(shadowed); const messageId = location.global ? "noShadowGlobal" : "noShadow"; From b6a2c0f24047f7066e4b8a26d6a0c260b3e3f9b1 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 7 Nov 2021 14:52:08 +0100 Subject: [PATCH 07/27] Fix: Adding option for variables on intialization (fixes #12687) --- docs/rules/no-shadow.md | 14 +++++++- lib/rules/no-shadow.js | 75 +++++++++++++++-------------------------- 2 files changed, 40 insertions(+), 49 deletions(-) diff --git a/docs/rules/no-shadow.md b/docs/rules/no-shadow.md index 45c9fce35cff..739fe71e4d51 100644 --- a/docs/rules/no-shadow.md +++ b/docs/rules/no-shadow.md @@ -178,14 +178,26 @@ foo(function (err, result) { The `ignoreOnInitialization` option is `false` by default. If it is `true`, the rule prevents reporting variables on initialization statements, the shadowed variable must be on the left side and the shadowing variable must be on the right side. +Examples of **incorrect** code for the default `{ "ignoreOnInitialization": "true" }` option: + +```js +/*eslint no-shadow: ["error", { "ignoreOnInitialization": true }]*/ + +var x = x => x; +``` + +Because the shadowing variable x will shadow an initialized shadowed variable x. + Examples of **correct** code for the `{ "ignoreOnInitialization": true }` option: ```js /*eslint no-shadow: ["error", { "ignoreOnInitialization": true }]*/ -const person = people.find(person => person.name === "John"); +var x = foo(x => x) ``` +Because the shadowing variable x will shadow an uninitialized shadowed variable x. + ## Further Reading * [Variable Shadowing](https://en.wikipedia.org/wiki/Variable_shadowing) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index beca6a7d9217..7e98c28b8226 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -70,9 +70,18 @@ module.exports = { }; /** - * @callback MatchCallback - * @param {ASTNode} node A node to check. + * Verifies if the given node is an init pattern node. + * @param {ASTNode} node a node to get. + *@returns {boolean} true if the given node is an init pattern node. */ + function isInitNode(node) { + const { type, init } = node; + + return ( + (type === "VariableDeclarator" && init) || + INIT_NODE_TYPES.includes(type) + ); + } /** * Searches from the current node through its ancestry to find a matching node. @@ -83,17 +92,8 @@ module.exports = { function findSelfOrAncestor(node, match) { let currentNode = node; - while (currentNode) { - const result = match(currentNode); - - if (result === 1) { - return currentNode; - } - if (result === -1) { - return null; - } + while (currentNode && !match(currentNode)) { currentNode = currentNode.parent; - } return currentNode; } @@ -108,49 +108,28 @@ module.exports = { const outerDef = shadowedVariable.defs[0]; const innerDef = variable.defs[0]; - const callExpressionNode = findSelfOrAncestor( + const arrowFunctionExpressionNode = findSelfOrAncestor( innerDef.node, - node => { - const { type } = node; - - if (type === "CallExpression") { - return 1; - } - if (type === "ClassDeclaration") { - return -1; - } - return 0; - } + node => node.type === "ArrowFunctionExpression" ); - let callExpressionRoot = callExpressionNode; - let currentNode = callExpressionRoot && callExpressionRoot.parent; + const callExpressionNode = + arrowFunctionExpressionNode && + arrowFunctionExpressionNode.parent; - while (currentNode && currentNode.type === "MemberExpression") { - callExpressionRoot = currentNode.parent; - currentNode = currentNode.parent.parent; - } - if (callExpressionRoot) { - return !!findSelfOrAncestor( + if (callExpressionNode.type === "CallExpression") { + const shadowingVariableAncestor = findSelfOrAncestor( + callExpressionNode.parent, + isInitNode + ); + const shadowedVariableAncestor = findSelfOrAncestor( outerDef && outerDef.name && outerDef.name.parent, - node => { - const { type, init, right } = node; - let rightNode; - - if (type === "VariableDeclarator" && init) { - rightNode = node.init; - } - if (INIT_NODE_TYPES.includes(type)) { - rightNode = right; - } - if (rightNode === callExpressionRoot) { - return 1; - } - return 0; - - } + isInitNode ); + + return shadowingVariableAncestor === shadowedVariableAncestor; } + return false; } From 1185d09f07adae66777db4afbc6f10cce0fb1741 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 7 Nov 2021 19:35:22 +0100 Subject: [PATCH 08/27] Fix: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 7e98c28b8226..529da7e5ad43 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -175,11 +175,11 @@ module.exports = { return ( outer && - inner && - outer[0] < inner[0] && - inner[1] < outer[1] && - ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && - outerScope === innerScope.upper + inner && + outer[0] < inner[0] && + inner[1] < outer[1] && + ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && + outerScope === innerScope.upper ); } @@ -230,11 +230,11 @@ module.exports = { return ( inner && - outer && - inner[1] < outer[0] && + outer && + inner[1] < outer[0] && - // Excepts FunctionDeclaration if is {"hoist":"function"}. - (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") + // Excepts FunctionDeclaration if is {"hoist":"function"}. + (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") ); } @@ -251,8 +251,8 @@ module.exports = { // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. if (variable.identifiers.length === 0 || - isDuplicatedClassNameVariable(variable) || - isAllowed(variable) + isDuplicatedClassNameVariable(variable) || + isAllowed(variable) ) { continue; } From b9297d1c74aea8d705d431b656fadc918b9b5d58 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Mon, 8 Nov 2021 22:18:51 +0100 Subject: [PATCH 09/27] Fix: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 529da7e5ad43..fde4764dfa94 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -117,7 +117,7 @@ module.exports = { arrowFunctionExpressionNode && arrowFunctionExpressionNode.parent; - if (callExpressionNode.type === "CallExpression") { + if (callExpressionNode && callExpressionNode.type === "CallExpression") { const shadowingVariableAncestor = findSelfOrAncestor( callExpressionNode.parent, isInitNode From f848418a29d1cba8cd57bd65921d1611172f44dc Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Mon, 8 Nov 2021 23:53:30 +0100 Subject: [PATCH 10/27] Fix: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index fde4764dfa94..26801eaf0880 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -113,21 +113,21 @@ module.exports = { node => node.type === "ArrowFunctionExpression" ); - const callExpressionNode = - arrowFunctionExpressionNode && - arrowFunctionExpressionNode.parent; - - if (callExpressionNode && callExpressionNode.type === "CallExpression") { - const shadowingVariableAncestor = findSelfOrAncestor( - callExpressionNode.parent, - isInitNode - ); - const shadowedVariableAncestor = findSelfOrAncestor( - outerDef && outerDef.name && outerDef.name.parent, - isInitNode - ); - - return shadowingVariableAncestor === shadowedVariableAncestor; + if (arrowFunctionExpressionNode) { + const callExpressionNode = arrowFunctionExpressionNode.parent; + + if (callExpressionNode && callExpressionNode.type === "CallExpression") { + const shadowingVariableAncestor = findSelfOrAncestor( + callExpressionNode.parent, + isInitNode + ); + const shadowedVariableAncestor = findSelfOrAncestor( + outerDef && outerDef.name && outerDef.name.parent, + isInitNode + ); + + return shadowingVariableAncestor === shadowedVariableAncestor; + } } return false; From 5053720faefe20ba015510c768b06902e2ed0ded Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Mon, 8 Nov 2021 23:59:22 +0100 Subject: [PATCH 11/27] Fix: Adding option for variables on intialization (fixes #12687) --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5670c38ac4..cc2bac94e9b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3102,7 +3102,7 @@ v3.16.0 - February 20, 2017 * c7e64f3 Upgrade: mock-fs (#8070) (Toru Nagashima) * acc3301 Update: handle uncommon linebreaks consistently in rules (fixes #7949) (#8049) (Teddy Katz) * 591b74a Chore: enable operator-linebreak on ESLint codebase (#8064) (Teddy Katz) -* 6445d2a Docs: Add documentation for /* exported */ (fixes #7998) (#8065) (Lee Yi Min) +* 6445d2a Docs: Add documentation for /*exported*/ (fixes #7998) (#8065) (Lee Yi Min) * fcc38db Chore: simplify and improve performance for autofix (#8035) (Toru Nagashima) * b04fde7 Chore: improve performance of SourceCode constructor (#8054) (Teddy Katz) * 90fd555 Update: improve null detection in eqeqeq for ES6 regexes (fixes #8020) (#8042) (Teddy Katz) @@ -6718,7 +6718,7 @@ v0.0.9 - October 5, 2013 * Rule: no-loop-func (Ilya Volodin) * Merge branch 'master' of https://github.com/nzakas/eslint into no-underscore-dangle (Matt DuVall) * Use proper node declarations and __proto__ exception (Matt DuVall) -* Updating no-undef patch (see pull request #164) - Simplify parseBoolean() - Make knowledge of```/*jshint*/``` and ```/*global */``` internal to eslint object - Put user-declared globals in Program scope (Mark Macdonald) +* Updating no-undef patch (see pull request #164) - Simplify parseBoolean() - Make knowledge of```/*jshint*/``` and ```/*global*/``` internal to eslint object - Put user-declared globals in Program scope (Mark Macdonald) * Rule: no-eq-null (Ian Christian Myers) * fixed broken merge (Raphael Pigulla) * fixes #143 (Raphael Pigulla) @@ -6727,7 +6727,7 @@ v0.0.9 - October 5, 2013 * Update eslint.json with no-underscore-dangle rule (Matt DuVall) * Rule: no-underscore-dangle for func/var declarations (Matt DuVall) * Warn on finding the bitwise NOT operator (James Allardice) -* Updating no-undef patch (see pull request #164) 3. Move parsing of ```/*global */``` and ```/*jshint */``` to eslint.js (Mark Macdonald) +* Updating no-undef patch (see pull request #164) 3. Move parsing of ```/*global*/``` and ```/*jshint*/``` to eslint.js (Mark Macdonald) * Warn on finding a bitwise shift operator (fixes #170) (James Allardice) * Fix broken test (James Allardice) * Add support for the do-while statement to the curly rule (closes #167) (James Allardice) From 9e66477902f878569538a84af069c433d7a1e27a Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Tue, 9 Nov 2021 00:02:55 +0100 Subject: [PATCH 12/27] Fix: Adding option for variables on intialization (fixes #12687) --- CHANGELOG.md | 6 +++--- docs/rules/no-shadow.md | 9 --------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc2bac94e9b1..ce5670c38ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3102,7 +3102,7 @@ v3.16.0 - February 20, 2017 * c7e64f3 Upgrade: mock-fs (#8070) (Toru Nagashima) * acc3301 Update: handle uncommon linebreaks consistently in rules (fixes #7949) (#8049) (Teddy Katz) * 591b74a Chore: enable operator-linebreak on ESLint codebase (#8064) (Teddy Katz) -* 6445d2a Docs: Add documentation for /*exported*/ (fixes #7998) (#8065) (Lee Yi Min) +* 6445d2a Docs: Add documentation for /* exported */ (fixes #7998) (#8065) (Lee Yi Min) * fcc38db Chore: simplify and improve performance for autofix (#8035) (Toru Nagashima) * b04fde7 Chore: improve performance of SourceCode constructor (#8054) (Teddy Katz) * 90fd555 Update: improve null detection in eqeqeq for ES6 regexes (fixes #8020) (#8042) (Teddy Katz) @@ -6718,7 +6718,7 @@ v0.0.9 - October 5, 2013 * Rule: no-loop-func (Ilya Volodin) * Merge branch 'master' of https://github.com/nzakas/eslint into no-underscore-dangle (Matt DuVall) * Use proper node declarations and __proto__ exception (Matt DuVall) -* Updating no-undef patch (see pull request #164) - Simplify parseBoolean() - Make knowledge of```/*jshint*/``` and ```/*global*/``` internal to eslint object - Put user-declared globals in Program scope (Mark Macdonald) +* Updating no-undef patch (see pull request #164) - Simplify parseBoolean() - Make knowledge of```/*jshint*/``` and ```/*global */``` internal to eslint object - Put user-declared globals in Program scope (Mark Macdonald) * Rule: no-eq-null (Ian Christian Myers) * fixed broken merge (Raphael Pigulla) * fixes #143 (Raphael Pigulla) @@ -6727,7 +6727,7 @@ v0.0.9 - October 5, 2013 * Update eslint.json with no-underscore-dangle rule (Matt DuVall) * Rule: no-underscore-dangle for func/var declarations (Matt DuVall) * Warn on finding the bitwise NOT operator (James Allardice) -* Updating no-undef patch (see pull request #164) 3. Move parsing of ```/*global*/``` and ```/*jshint*/``` to eslint.js (Mark Macdonald) +* Updating no-undef patch (see pull request #164) 3. Move parsing of ```/*global */``` and ```/*jshint */``` to eslint.js (Mark Macdonald) * Warn on finding a bitwise shift operator (fixes #170) (James Allardice) * Fix broken test (James Allardice) * Add support for the do-while statement to the curly rule (closes #167) (James Allardice) diff --git a/docs/rules/no-shadow.md b/docs/rules/no-shadow.md index 739fe71e4d51..360138737036 100644 --- a/docs/rules/no-shadow.md +++ b/docs/rules/no-shadow.md @@ -40,15 +40,6 @@ if (true) { } ``` -Examples of **incorrect** code for this rule: - -```js -/*eslint no-shadow: "error"*/ -/*eslint-env es6*/ - -const person = people.find(person => person.name === 'John'); -``` - ## Options This rule takes one option, an object, with properties `"builtinGlobals"`, `"hoist"`, `"allow"` and `"ignoreOnInitialization"`. From 5865277217be21fc46d3e144c5ac23388b372a92 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Tue, 9 Nov 2021 00:12:21 +0100 Subject: [PATCH 13/27] Fix: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 26801eaf0880..1c40bb422c14 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -71,7 +71,7 @@ module.exports = { /** * Verifies if the given node is an init pattern node. - * @param {ASTNode} node a node to get. + *@param {ASTNode} node a node to get. *@returns {boolean} true if the given node is an init pattern node. */ function isInitNode(node) { From aec18a32a91059f50de23c7fdc7607c1471996e3 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 28 Nov 2021 12:38:34 +0100 Subject: [PATCH 14/27] Fix: Adding option for variables on intialization (fixes #12687) --- tests/lib/rules/no-shadow.js | 1771 ++++++++++++++++++---------------- 1 file changed, 913 insertions(+), 858 deletions(-) diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index b8e15cc18297..1e075436e2ef 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -3,862 +3,917 @@ * @author Ilya Volodin */ - "use strict"; +"use strict"; - //------------------------------------------------------------------------------ - // Requirements - //------------------------------------------------------------------------------ - - const rule = require("../../../lib/rules/no-shadow"), - { RuleTester } = require("../../../lib/rule-tester"); - - //------------------------------------------------------------------------------ - // Tests - //------------------------------------------------------------------------------ - - const ruleTester = new RuleTester(); - - ruleTester.run("no-shadow", rule, { - valid: [ - "var a=3; function b(x) { a++; return x + a; }; setTimeout(function() { b(a); }, 0);", - "(function() { var doSomething = function doSomething() {}; doSomething() }())", - "var arguments;\nfunction bar() { }", - { code: "var a=3; var b = (x) => { a++; return x + a; }; setTimeout(() => { b(a); }, 0);", parserOptions: { ecmaVersion: 6 } }, - { code: "class A {}", parserOptions: { ecmaVersion: 6 } }, - { code: "class A { constructor() { var a; } }", parserOptions: { ecmaVersion: 6 } }, - { code: "(function() { var A = class A {}; })()", parserOptions: { ecmaVersion: 6 } }, - { code: "{ var a; } var a;", parserOptions: { ecmaVersion: 6 } }, // this case reports `no-redeclare`, not shadowing. - { code: "{ let a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ let a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ let a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } const a = 1;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, - { code: "{ let a; } let a;", parserOptions: { ecmaVersion: 6 } }, - { code: "{ let a; } var a;", parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } const a = 1;", parserOptions: { ecmaVersion: 6 } }, - { code: "{ const a = 0; } var a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } let a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { let a; } var a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } let a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo() { var a; } var a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } let a;", parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(a) { } var a;", parserOptions: { ecmaVersion: 6 } }, - "function foo() { var Object = 0; }", - { code: "function foo() { var top = 0; }", env: { browser: true } }, - { code: "var Object = 0;", options: [{ builtinGlobals: true }] }, - { code: "var top = 0;", options: [{ builtinGlobals: true }], env: { browser: true } }, - { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] }, - { code: "class C { foo; foo() { let foo; } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { var x; } static { var x; } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { let x; } static { let x; } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { var x; { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { { var x; } { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { { let x; } { let x; } } }", parserOptions: { ecmaVersion: 2022 } } - ], - invalid: [ - { - code: "function a(x) { var b = function c() { var x = 'foo'; }; }", - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 12 - }, - type: "Identifier", - line: 1, - column: 44 - }] - }, - { - code: "var a = (x) => { var b = () => { var x = 'foo'; }; }", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 10 - }, - type: "Identifier", - line: 1, - column: 38 - }] - }, - { - code: "function a(x) { var b = function () { var x = 'foo'; }; }", - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 12 - }, - type: "Identifier", - line: 1, - column: 43 - }] - }, - { - code: "var x = 1; function a(x) { return ++x; }", - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier", - line: 1, - column: 23 - }] - }, - { - code: "var a=3; function b() { var a=10; }", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }] - }, - { - code: "var a=3; function b() { var a=10; }; setTimeout(function() { b(); }, 0);", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }] - }, - { - code: "var a=3; function b() { var a=10; var b=0; }; setTimeout(function() { b(); }, 0);", - errors: [ - { - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }, { - messageId: "noShadow", - data: { - name: "b", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - } - ] - }, - { - code: "var x = 1; { let x = 2; }", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }] - }, - { - code: "let x = 1; { const x = 2; }", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "x", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier" - }] - }, - { - code: "{ let a; } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 21 - }, - type: "Identifier" - }] - }, - { - code: "{ const a = 0; } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 27 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { let a; } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 36 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var a; } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 36 - }, - type: "Identifier" - }] - }, - { - code: "function foo(a) { } function a() {}", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 30 - }, - type: "Identifier" - }] - }, - { - code: "{ let a; } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 16 - }, - type: "Identifier" - }] - }, - { - code: "{ let a; } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 16 - }, - type: "Identifier" - }] - }, - { - code: "{ let a; } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 21 - }, - type: "Identifier" - }] - }, - { - code: "{ const a = 0; } const a = 1;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 24 - }, - type: "Identifier" - }] - }, - { - code: "{ const a = 0; } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 22 - }, - type: "Identifier" - }] - }, - { - code: "{ const a = 0; } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 27 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { let a; } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 31 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { let a; } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 31 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { let a; } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 36 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var a; } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 31 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var a; } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 31 - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var a; } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 36 - }, - type: "Identifier" - }] - }, - { - code: "function foo(a) { } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 25 - }, - type: "Identifier" - }] - }, - { - code: "function foo(a) { } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 25 - }, - type: "Identifier" - }] - }, - { - code: "function foo(a) { } function a() {}", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 30 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { function a(){} })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { class a{} })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { (function a(){}); })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { (class a{}); })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function(a) {}; })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function() { function a() {} }; })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function() { class a{} }; })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function() { (function a() {}); }; })()", - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = function() { (class a{}); }; })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "(function() { var a = class { constructor() { class a {} } }; })()", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 19 - }, - type: "Identifier" - }] - }, - { - code: "class A { constructor() { var A; } }", - parserOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "noShadow", - data: { - name: "A", - shadowedLine: 1, - shadowedColumn: 7 - }, - type: "Identifier" - }] - }, - { - code: "(function a() { function a(){ function a(){} } })()", - errors: [ - { - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 11 - }, - type: "Identifier", - line: 1, - column: 26 - }, - { - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 26 - }, - type: "Identifier", - line: 1, - column: 40 - } - ] - }, - { - code: "function foo() { var Object = 0; }", - options: [{ builtinGlobals: true }], - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "Object" - }, - type: "Identifier" - }] - }, - { - code: "function foo() { var top = 0; }", - options: [{ builtinGlobals: true }], - env: { browser: true }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "top" - }, - type: "Identifier" - }] - }, - { - code: "var Object = 0;", - options: [{ builtinGlobals: true }], - parserOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "Object" - }, - type: "Identifier" - }] - }, - { - code: "var top = 0;", - options: [{ builtinGlobals: true }], - parserOptions: { ecmaVersion: 6, sourceType: "module" }, - env: { browser: true }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "top" - }, - type: "Identifier" - }] - }, - { - code: "var Object = 0;", - options: [{ builtinGlobals: true }], - parserOptions: { ecmaFeatures: { globalReturn: true } }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "Object" - }, - type: "Identifier" - }] - }, - { - code: "var top = 0;", - options: [{ builtinGlobals: true }], - parserOptions: { ecmaFeatures: { globalReturn: true } }, - env: { browser: true }, - errors: [{ - messageId: "noShadowGlobal", - data: { - name: "top" - }, - type: "Identifier" - }] - }, - { - code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", - errors: [{ - messageId: "noShadow", - data: { - name: "cb", - shadowedLine: 1, - shadowedColumn: 14 - }, - type: "Identifier", - line: 1, - column: 31 - }] - }, - { - code: "class C { static { let a; { let a; } } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 24 - }, - type: "Identifier", - line: 1, - column: 33 - }] - }, - { - code: "class C { static { var C; } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "C", - shadowedLine: 1, - shadowedColumn: 7 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "class C { static { let C; } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "C", - shadowedLine: 1, - shadowedColumn: 7 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "var a; class C { static { var a; } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 5 - }, - type: "Identifier", - line: 1, - column: 31 - }] - }, - { - code: "class C { static { var a; } } var a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 35 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "class C { static { let a; } } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 35 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "class C { static { var a; } } let a;", - options: [{ hoist: "all" }], - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 35 - }, - type: "Identifier", - line: 1, - column: 24 - }] - }, - { - code: "class C { static { var a; class D { static { var a; } } } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 24 - }, - type: "Identifier", - line: 1, - column: 50 - }] - }, - { - code: "class C { static { let a; class D { static { let a; } } } }", - parserOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "noShadow", - data: { - name: "a", - shadowedLine: 1, - shadowedColumn: 24 - }, - type: "Identifier", - line: 1, - column: 50 - }] - } - ] - }); \ No newline at end of file +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/no-shadow"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester(); + +ruleTester.run("no-shadow", rule, { + valid: [ + "var a=3; function b(x) { a++; return x + a; }; setTimeout(function() { b(a); }, 0);", + "(function() { var doSomething = function doSomething() {}; doSomething() }())", + "var arguments;\nfunction bar() { }", + { code: "var a=3; var b = (x) => { a++; return x + a; }; setTimeout(() => { b(a); }, 0);", parserOptions: { ecmaVersion: 6 } }, + { code: "class A {}", parserOptions: { ecmaVersion: 6 } }, + { code: "class A { constructor() { var a; } }", parserOptions: { ecmaVersion: 6 } }, + { code: "(function() { var A = class A {}; })()", parserOptions: { ecmaVersion: 6 } }, + { code: "{ var a; } var a;", parserOptions: { ecmaVersion: 6 } }, // this case reports `no-redeclare`, not shadowing. + { code: "{ let a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ let a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ let a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } const a = 1;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } let a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } var a;", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } function a() {}", options: [{ hoist: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "{ let a; } let a;", parserOptions: { ecmaVersion: 6 } }, + { code: "{ let a; } var a;", parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } const a = 1;", parserOptions: { ecmaVersion: 6 } }, + { code: "{ const a = 0; } var a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } let a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { let a; } var a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } let a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo() { var a; } var a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } let a;", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo(a) { } var a;", parserOptions: { ecmaVersion: 6 } }, + "function foo() { var Object = 0; }", + { code: "function foo() { var top = 0; }", env: { browser: true } }, + { code: "var Object = 0;", options: [{ builtinGlobals: true }] }, + { code: "var top = 0;", options: [{ builtinGlobals: true }], env: { browser: true } }, + { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] }, + { code: "class C { foo; foo() { let foo; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { var x; } static { var x; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { let x; } static { let x; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { var x; { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { { var x; } { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { { let x; } { let x; } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "const a = [].find(a=>a)", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const [a = [].find(a => true)] = dummy", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const { a = [].find(a => true) } = dummy", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "function func(a = [].find(a => true)) {}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "for (const a in [].find(a => true)) {}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "for (const a of [].find(a => true)) {}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const a = [].map(a => true).filter(a => a === 'b')", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const a = [].map(a => true).filter(a => a === 'b').find(a => a === 'c')", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const { a } = (({ a }) => ({ a }))();", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const person = people.find(item => {const person = item.name; return person === 'foo'})", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = bar || foo(y => y);", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = bar && foo(y => y);", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var z = bar(foo(z => z));", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var z = boo(bar(foo(z => z)));", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var match = function (person) { return person.name === 'foo'; };\nconst person = [].find(match);", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } } + ], + invalid: [ + { + code: "function a(x) { var b = function c() { var x = 'foo'; }; }", + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 12 + }, + type: "Identifier", + line: 1, + column: 44 + }] + }, + { + code: "var a = (x) => { var b = () => { var x = 'foo'; }; }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 10 + }, + type: "Identifier", + line: 1, + column: 38 + }] + }, + { + code: "function a(x) { var b = function () { var x = 'foo'; }; }", + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 12 + }, + type: "Identifier", + line: 1, + column: 43 + }] + }, + { + code: "var x = 1; function a(x) { return ++x; }", + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier", + line: 1, + column: 23 + }] + }, + { + code: "var a=3; function b() { var a=10; }", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }] + }, + { + code: "var a=3; function b() { var a=10; }; setTimeout(function() { b(); }, 0);", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }] + }, + { + code: "var a=3; function b() { var a=10; var b=0; }; setTimeout(function() { b(); }, 0);", + errors: [ + { + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }, { + messageId: "noShadow", + data: { + name: "b", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + } + ] + }, + { + code: "var x = 1; { let x = 2; }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }] + }, + { + code: "let x = 1; { const x = 2; }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }] + }, + { + code: "{ let a; } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 21 + }, + type: "Identifier" + }] + }, + { + code: "{ const a = 0; } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 27 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { let a; } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 36 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var a; } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 36 + }, + type: "Identifier" + }] + }, + { + code: "function foo(a) { } function a() {}", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 30 + }, + type: "Identifier" + }] + }, + { + code: "{ let a; } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 16 + }, + type: "Identifier" + }] + }, + { + code: "{ let a; } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 16 + }, + type: "Identifier" + }] + }, + { + code: "{ let a; } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 21 + }, + type: "Identifier" + }] + }, + { + code: "{ const a = 0; } const a = 1;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier" + }] + }, + { + code: "{ const a = 0; } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 22 + }, + type: "Identifier" + }] + }, + { + code: "{ const a = 0; } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 27 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { let a; } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 31 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { let a; } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 31 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { let a; } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 36 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var a; } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 31 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var a; } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 31 + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var a; } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 36 + }, + type: "Identifier" + }] + }, + { + code: "function foo(a) { } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 25 + }, + type: "Identifier" + }] + }, + { + code: "function foo(a) { } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 25 + }, + type: "Identifier" + }] + }, + { + code: "function foo(a) { } function a() {}", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 30 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { function a(){} })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { class a{} })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { (function a(){}); })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { (class a{}); })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function(a) {}; })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function() { function a() {} }; })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function() { class a{} }; })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function() { (function a() {}); }; })()", + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = function() { (class a{}); }; })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "(function() { var a = class { constructor() { class a {} } }; })()", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier" + }] + }, + { + code: "class A { constructor() { var A; } }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "A", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier" + }] + }, + { + code: "(function a() { function a(){ function a(){} } })()", + errors: [ + { + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 11 + }, + type: "Identifier", + line: 1, + column: 26 + }, + { + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 26 + }, + type: "Identifier", + line: 1, + column: 40 + } + ] + }, + { + code: "function foo() { var Object = 0; }", + options: [{ builtinGlobals: true }], + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "Object" + }, + type: "Identifier" + }] + }, + { + code: "function foo() { var top = 0; }", + options: [{ builtinGlobals: true }], + env: { browser: true }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "top" + }, + type: "Identifier" + }] + }, + { + code: "var Object = 0;", + options: [{ builtinGlobals: true }], + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "Object" + }, + type: "Identifier" + }] + }, + { + code: "var top = 0;", + options: [{ builtinGlobals: true }], + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + env: { browser: true }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "top" + }, + type: "Identifier" + }] + }, + { + code: "var Object = 0;", + options: [{ builtinGlobals: true }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "Object" + }, + type: "Identifier" + }] + }, + { + code: "var top = 0;", + options: [{ builtinGlobals: true }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + env: { browser: true }, + errors: [{ + messageId: "noShadowGlobal", + data: { + name: "top" + }, + type: "Identifier" + }] + }, + { + code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", + errors: [{ + messageId: "noShadow", + data: { + name: "cb", + shadowedLine: 1, + shadowedColumn: 14 + }, + type: "Identifier", + line: 1, + column: 31 + }] + }, + { + code: "class C { static { let a; { let a; } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier", + line: 1, + column: 33 + }] + }, + { + code: "class C { static { var C; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "C", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { let C; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "C", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "var a; class C { static { var a; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier", + line: 1, + column: 31 + }] + }, + { + code: "class C { static { var a; } } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 35 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { let a; } } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 35 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { var a; } } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 35 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { var a; class D { static { var a; } } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier", + line: 1, + column: 50 + }] + }, + { + code: "class C { static { let a; class D { static { let a; } } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier", + line: 1, + column: 50 + }] + }, + { + code: "let x = foo((x,y) => {});\nlet y;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }, + { + messageId: "noShadow", + data: { + name: "y", + shadowedLine: 2, + shadowedColumn: 5 + }, + type: "Identifier" + } + ] + }, + { + code: "const a = fn(()=>{ class C { fn () { const a = 42; return a } } return new C() })", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 44 + }] + } + ] +}); From 60ccbf6865b385a4ff797b23b1b7903152197300 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 19 Dec 2021 14:42:40 +0100 Subject: [PATCH 15/27] Fix: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 67 +++++++++++++++++++++--------------- tests/lib/rules/no-shadow.js | 37 +++++++++++++++++++- 2 files changed, 76 insertions(+), 28 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 1c40bb422c14..949bf5500036 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -15,12 +15,25 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const INIT_NODE_TYPES = [ +const INIT_NODES_TYPES = [ + "VariableDeclarator", "AssignmentPattern", "ForInStatement", "ForOfStatement" ]; +const FUNCTION_EXPRESSION_NODES_TYPES = [ + "ArrowFunctionExpression", + "FunctionExpression" +]; + +const INSTANTIATION_NODES_TYPES = [ + "ClassDeclaration", + "FunctionDeclaration" +]; + +const CALL_EXPRESSION_NODE_TYPE = ["CallExpression"]; + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -70,32 +83,30 @@ module.exports = { }; /** - * Verifies if the given node is an init pattern node. - *@param {ASTNode} node a node to get. - *@returns {boolean} true if the given node is an init pattern node. + * Checks whether or not the nodes types include the node type. + * @param {ASTNode} node a node to get. + * @param {[string]} types an array of nodes types. + * @returns {boolean} True if nodes types include the node type. */ - function isInitNode(node) { - const { type, init } = node; - - return ( - (type === "VariableDeclarator" && init) || - INIT_NODE_TYPES.includes(type) - ); + function isNodeMatchesTypes(node, types) { + return types.includes(node.type); } /** * Searches from the current node through its ancestry to find a matching node. * @param {ASTNode} node a node to get. * @param {MatchCallback} match a callback that checks whether or not the node verifies its condition or not. - * @returns {ASTNode} the matching node. + * @returns {[ASTNode, number]} an array that contains the node and its height. */ function findSelfOrAncestor(node, match) { let currentNode = node; + let height = 0; while (currentNode && !match(currentNode)) { currentNode = currentNode.parent; + height++; } - return currentNode; + return [currentNode, height]; } /** @@ -108,28 +119,30 @@ module.exports = { const outerDef = shadowedVariable.defs[0]; const innerDef = variable.defs[0]; - const arrowFunctionExpressionNode = findSelfOrAncestor( + const [functionExpressionNode] = findSelfOrAncestor( innerDef.node, - node => node.type === "ArrowFunctionExpression" + node => isNodeMatchesTypes(node, FUNCTION_EXPRESSION_NODES_TYPES) ); - if (arrowFunctionExpressionNode) { - const callExpressionNode = arrowFunctionExpressionNode.parent; + if (functionExpressionNode) { + const { parent } = functionExpressionNode; - if (callExpressionNode && callExpressionNode.type === "CallExpression") { - const shadowingVariableAncestor = findSelfOrAncestor( - callExpressionNode.parent, - isInitNode - ); - const shadowedVariableAncestor = findSelfOrAncestor( - outerDef && outerDef.name && outerDef.name.parent, - isInitNode + const [, instantiationNodeHeight] = findSelfOrAncestor(parent, node => isNodeMatchesTypes(node, INSTANTIATION_NODES_TYPES)); + + const [callExpressionNode, callExpressionNodeHeight] = findSelfOrAncestor(parent, node => isNodeMatchesTypes(node, CALL_EXPRESSION_NODE_TYPE)); + + if ((callExpressionNode && (callExpressionNodeHeight < instantiationNodeHeight))) { + const [shadowingVariableAncestor] = findSelfOrAncestor( + callExpressionNode.parent, node => isNodeMatchesTypes(node, INIT_NODES_TYPES) ); - return shadowingVariableAncestor === shadowedVariableAncestor; + const shadowingVariableNode = outerDef && outerDef.name; + + const [shadowedVariableAncestor] = findSelfOrAncestor(shadowingVariableNode.parent, node => (node === shadowingVariableAncestor)); + + return !!shadowedVariableAncestor; } } - return false; } diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index 1e075436e2ef..a16381b79367 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -78,7 +78,9 @@ ruleTester.run("no-shadow", rule, { { code: "var y = bar && foo(y => y);", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "var z = bar(foo(z => z));", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "var z = boo(bar(foo(z => z)));", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, - { code: "var match = function (person) { return person.name === 'foo'; };\nconst person = [].find(match);", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } } + { code: "var match = function (person) { return person.name === 'foo'; };\nconst person = [].find(match);", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const a = foo(x || (a => {}))", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const { a = 1 } = foo(a => {})", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } } ], invalid: [ { @@ -902,6 +904,7 @@ ruleTester.run("no-shadow", rule, { }, { code: "const a = fn(()=>{ class C { fn () { const a = 42; return a } } return new C() })", + options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "noShadow", @@ -914,6 +917,38 @@ ruleTester.run("no-shadow", rule, { line: 1, column: 44 }] + }, + { + code: "function a() {}\nfoo(a => {});", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 10 + }, + type: "Identifier", + line: 2, + column: 5 + }] + }, + { + code: "const a = fn(()=>{ function C() { this.fn=function() { const a = 42; return a } } return new C() });", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 62 + }] } ] }); From ec734a35cd0e14ac4016404f0d83eb06d22185c2 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 19 Dec 2021 18:26:45 +0100 Subject: [PATCH 16/27] feat: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 949bf5500036..7971984c4fd4 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -138,7 +138,7 @@ module.exports = { const shadowingVariableNode = outerDef && outerDef.name; - const [shadowedVariableAncestor] = findSelfOrAncestor(shadowingVariableNode.parent, node => (node === shadowingVariableAncestor)); + const [shadowedVariableAncestor] = findSelfOrAncestor(shadowingVariableNode, node => (node === shadowingVariableAncestor)); return !!shadowedVariableAncestor; } From 0e74b6c4ad79fa01e3ceb89218e37b45d01048d0 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 9 Jan 2022 02:04:28 +0100 Subject: [PATCH 17/27] feat: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 125 +++++++++++++++++++++-------------- tests/lib/rules/no-shadow.js | 39 ++++++++++- 2 files changed, 114 insertions(+), 50 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 7971984c4fd4..14190771f1e3 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -22,17 +22,11 @@ const INIT_NODES_TYPES = [ "ForOfStatement" ]; -const FUNCTION_EXPRESSION_NODES_TYPES = [ - "ArrowFunctionExpression", - "FunctionExpression" -]; +const FUN_NODES_TYPES = ["ArrowFunctionExpression", "FunctionExpression"]; -const INSTANTIATION_NODES_TYPES = [ - "ClassDeclaration", - "FunctionDeclaration" -]; +const FUN_DEC_NODES_TYPES = ["MethodDefinition", "FunctionDeclaration"]; -const CALL_EXPRESSION_NODE_TYPE = ["CallExpression"]; +const CALL_EXP_NODE_TYPE = ["CallExpression"]; //------------------------------------------------------------------------------ // Rule Definition @@ -44,7 +38,8 @@ module.exports = { type: "suggestion", docs: { - description: "disallow variable declarations from shadowing variables declared in the outer scope", + description: + "disallow variable declarations from shadowing variables declared in the outer scope", recommended: false, url: "https://eslint.org/docs/rules/no-shadow" }, @@ -54,7 +49,10 @@ module.exports = { type: "object", properties: { builtinGlobals: { type: "boolean", default: false }, - hoist: { enum: ["all", "functions", "never"], default: "functions" }, + hoist: { + enum: ["all", "functions", "never"], + default: "functions" + }, allow: { type: "array", items: { @@ -68,18 +66,21 @@ module.exports = { ], messages: { - noShadow: "'{{name}}' is already declared in the upper scope on line {{shadowedLine}} column {{shadowedColumn}}.", + noShadow: + "'{{name}}' is already declared in the upper scope on line {{shadowedLine}} column {{shadowedColumn}}.", noShadowGlobal: "'{{name}}' is already a global variable." } }, create(context) { - const options = { - builtinGlobals: context.options[0] && context.options[0].builtinGlobals, - hoist: (context.options[0] && context.options[0].hoist) || "functions", + builtinGlobals: + context.options[0] && context.options[0].builtinGlobals, + hoist: + (context.options[0] && context.options[0].hoist) || "functions", allow: (context.options[0] && context.options[0].allow) || [], - ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization + ignoreOnInitialization: + context.options[0] && context.options[0].ignoreOnInitialization }; /** @@ -96,17 +97,15 @@ module.exports = { * Searches from the current node through its ancestry to find a matching node. * @param {ASTNode} node a node to get. * @param {MatchCallback} match a callback that checks whether or not the node verifies its condition or not. - * @returns {[ASTNode, number]} an array that contains the node and its height. + * @returns {ASTNode} the matching node. */ function findSelfOrAncestor(node, match) { let currentNode = node; - let height = 0; while (currentNode && !match(currentNode)) { currentNode = currentNode.parent; - height++; } - return [currentNode, height]; + return currentNode; } /** @@ -118,31 +117,43 @@ module.exports = { function isInitPatternNode(variable, shadowedVariable) { const outerDef = shadowedVariable.defs[0]; const innerDef = variable.defs[0]; + let fun = innerDef.node; - const [functionExpressionNode] = findSelfOrAncestor( - innerDef.node, - node => isNodeMatchesTypes(node, FUNCTION_EXPRESSION_NODES_TYPES) - ); + while (fun && !isNodeMatchesTypes(fun, FUN_NODES_TYPES)) { + if (isNodeMatchesTypes(fun, FUN_DEC_NODES_TYPES)) { + fun = null; + } + fun = fun.parent; + } - if (functionExpressionNode) { - const { parent } = functionExpressionNode; + if (fun) { + const { parent } = fun; - const [, instantiationNodeHeight] = findSelfOrAncestor(parent, node => isNodeMatchesTypes(node, INSTANTIATION_NODES_TYPES)); + const nestedFun = findSelfOrAncestor(parent, node => + isNodeMatchesTypes(node, FUN_NODES_TYPES)); - const [callExpressionNode, callExpressionNodeHeight] = findSelfOrAncestor(parent, node => isNodeMatchesTypes(node, CALL_EXPRESSION_NODE_TYPE)); + if (!nestedFun) { + const callExpression = findSelfOrAncestor(parent, node => + isNodeMatchesTypes(node, CALL_EXP_NODE_TYPE)); - if ((callExpressionNode && (callExpressionNodeHeight < instantiationNodeHeight))) { - const [shadowingVariableAncestor] = findSelfOrAncestor( - callExpressionNode.parent, node => isNodeMatchesTypes(node, INIT_NODES_TYPES) - ); + if (callExpression) { + const shadowingVariableAncestor = findSelfOrAncestor( + callExpression.parent, + node => isNodeMatchesTypes(node, INIT_NODES_TYPES) + ); - const shadowingVariableNode = outerDef && outerDef.name; + const shadowingVariable = outerDef && outerDef.name; - const [shadowedVariableAncestor] = findSelfOrAncestor(shadowingVariableNode, node => (node === shadowingVariableAncestor)); + const shadowedVariableAncestor = findSelfOrAncestor( + shadowingVariable, + node => node === shadowingVariableAncestor + ); - return !!shadowedVariableAncestor; + return !!shadowedVariableAncestor; + } } } + return false; } @@ -166,7 +177,10 @@ module.exports = { function isDuplicatedClassNameVariable(variable) { const block = variable.scope.block; - return block.type === "ClassDeclaration" && block.id === variable.identifiers[0]; + return ( + block.type === "ClassDeclaration" && + block.id === variable.identifiers[0] + ); } /** @@ -191,7 +205,9 @@ module.exports = { inner && outer[0] < inner[0] && inner[1] < outer[1] && - ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && + ((innerDef.type === "FunctionName" && + innerDef.node.type === "FunctionExpression") || + innerDef.node.type === "ClassExpression") && outerScope === innerScope.upper ); } @@ -247,7 +263,9 @@ module.exports = { inner[1] < outer[0] && // Excepts FunctionDeclaration if is {"hoist":"function"}. - (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") + (options.hoist !== "functions" || + !outerDef || + outerDef.node.type !== "FunctionDeclaration") ); } @@ -263,7 +281,8 @@ module.exports = { const variable = variables[i]; // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. - if (variable.identifiers.length === 0 || + if ( + variable.identifiers.length === 0 || isDuplicatedClassNameVariable(variable) || isAllowed(variable) ) { @@ -271,16 +290,27 @@ module.exports = { } // Gets shadowed variable. - const shadowed = astUtils.getVariableByName(scope.upper, variable.name); - - if (shadowed && - (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && - (!(options.ignoreOnInitialization && isInitPatternNode(variable, shadowed)) || options.hoist === "all") && - !(options.hoist !== "all" && isInTdz(variable, shadowed)) + const shadowed = astUtils.getVariableByName( + scope.upper, + variable.name + ); + + if ( + shadowed && + (shadowed.identifiers.length > 0 || + (options.builtinGlobals && "writeable" in shadowed)) && + !isOnInitializer(variable, shadowed) && + (!( + options.ignoreOnInitialization && + isInitPatternNode(variable, shadowed) + ) || + options.hoist === "all") && + !(options.hoist !== "all" && isInTdz(variable, shadowed)) ) { const location = getDeclaredLocation(shadowed); - const messageId = location.global ? "noShadowGlobal" : "noShadow"; + const messageId = location.global + ? "noShadowGlobal" + : "noShadow"; const data = { name: variable.name }; if (!location.global) { @@ -309,6 +339,5 @@ module.exports = { } } }; - } }; diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index a16381b79367..0696018270ef 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -64,7 +64,8 @@ ruleTester.run("no-shadow", rule, { { code: "class C { static { var x; { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, { code: "class C { static { { var x; } { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, { code: "class C { static { { let x; } { let x; } } }", parserOptions: { ecmaVersion: 2022 } }, - { code: "const a = [].find(a=>a)", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const a = [].find(a => a)", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const a = [].find(function(a) { return a; })", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const [a = [].find(a => true)] = dummy", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const { a = [].find(a => true) } = dummy", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "function func(a = [].find(a => true)) {}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, @@ -80,7 +81,9 @@ ruleTester.run("no-shadow", rule, { { code: "var z = boo(bar(foo(z => z)));", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "var match = function (person) { return person.name === 'foo'; };\nconst person = [].find(match);", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const a = foo(x || (a => {}))", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, - { code: "const { a = 1 } = foo(a => {})", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } } + { code: "const { a = 1 } = foo(a => {})", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const person = {...people.find((person) => person.firstName.startsWith('s'))}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 2021 } }, + { code: "const person = { firstName: people.filter((person) => person.firstName.startsWith('s')).map((person) => person.firstName)[0]}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 2021 } } ], invalid: [ { @@ -949,6 +952,38 @@ ruleTester.run("no-shadow", rule, { line: 1, column: 62 }] + }, + { + code: "const x = foo(() => { const bar = () => { return x => {}; }; return bar; });", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 50 + }] + }, + { + code: "const x = foo(() => { return { bar(x) {} }; });", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 36 + }] } ] }); From 7875170aaef385cd387225173df7905821be90a2 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 9 Jan 2022 02:25:19 +0100 Subject: [PATCH 18/27] feat: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 14190771f1e3..27ffd1fc32d8 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -117,13 +117,14 @@ module.exports = { function isInitPatternNode(variable, shadowedVariable) { const outerDef = shadowedVariable.defs[0]; const innerDef = variable.defs[0]; - let fun = innerDef.node; + let fun = innerDef && innerDef.node; while (fun && !isNodeMatchesTypes(fun, FUN_NODES_TYPES)) { if (isNodeMatchesTypes(fun, FUN_DEC_NODES_TYPES)) { fun = null; + } else { + fun = fun.parent; } - fun = fun.parent; } if (fun) { From 78fe1a0cbea13a6fc880a226a8f394407976a543 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 9 Jan 2022 02:44:12 +0100 Subject: [PATCH 19/27] feat: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 83 +++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 54 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 27ffd1fc32d8..b244518c2b7d 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -38,8 +38,7 @@ module.exports = { type: "suggestion", docs: { - description: - "disallow variable declarations from shadowing variables declared in the outer scope", + description: "disallow variable declarations from shadowing variables declared in the outer scope", recommended: false, url: "https://eslint.org/docs/rules/no-shadow" }, @@ -49,10 +48,7 @@ module.exports = { type: "object", properties: { builtinGlobals: { type: "boolean", default: false }, - hoist: { - enum: ["all", "functions", "never"], - default: "functions" - }, + hoist: { enum: ["all", "functions", "never"], default: "functions" }, allow: { type: "array", items: { @@ -66,21 +62,18 @@ module.exports = { ], messages: { - noShadow: - "'{{name}}' is already declared in the upper scope on line {{shadowedLine}} column {{shadowedColumn}}.", + noShadow: "'{{name}}' is already declared in the upper scope on line {{shadowedLine}} column {{shadowedColumn}}.", noShadowGlobal: "'{{name}}' is already a global variable." } }, create(context) { + const options = { - builtinGlobals: - context.options[0] && context.options[0].builtinGlobals, - hoist: - (context.options[0] && context.options[0].hoist) || "functions", + builtinGlobals: context.options[0] && context.options[0].builtinGlobals, + hoist: (context.options[0] && context.options[0].hoist) || "functions", allow: (context.options[0] && context.options[0].allow) || [], - ignoreOnInitialization: - context.options[0] && context.options[0].ignoreOnInitialization + ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization }; /** @@ -178,10 +171,7 @@ module.exports = { function isDuplicatedClassNameVariable(variable) { const block = variable.scope.block; - return ( - block.type === "ClassDeclaration" && - block.id === variable.identifiers[0] - ); + return block.type === "ClassDeclaration" && block.id === variable.identifiers[0]; } /** @@ -203,13 +193,11 @@ module.exports = { return ( outer && - inner && - outer[0] < inner[0] && - inner[1] < outer[1] && - ((innerDef.type === "FunctionName" && - innerDef.node.type === "FunctionExpression") || - innerDef.node.type === "ClassExpression") && - outerScope === innerScope.upper + inner && + outer[0] < inner[0] && + inner[1] < outer[1] && + ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && + outerScope === innerScope.upper ); } @@ -260,13 +248,11 @@ module.exports = { return ( inner && - outer && - inner[1] < outer[0] && + outer && + inner[1] < outer[0] && - // Excepts FunctionDeclaration if is {"hoist":"function"}. - (options.hoist !== "functions" || - !outerDef || - outerDef.node.type !== "FunctionDeclaration") + // Excepts FunctionDeclaration if is {"hoist":"function"}. + (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") ); } @@ -282,36 +268,24 @@ module.exports = { const variable = variables[i]; // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. - if ( - variable.identifiers.length === 0 || - isDuplicatedClassNameVariable(variable) || - isAllowed(variable) + if (variable.identifiers.length === 0 || + isDuplicatedClassNameVariable(variable) || + isAllowed(variable) ) { continue; } // Gets shadowed variable. - const shadowed = astUtils.getVariableByName( - scope.upper, - variable.name - ); - - if ( - shadowed && - (shadowed.identifiers.length > 0 || - (options.builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && - (!( - options.ignoreOnInitialization && - isInitPatternNode(variable, shadowed) - ) || - options.hoist === "all") && - !(options.hoist !== "all" && isInTdz(variable, shadowed)) + const shadowed = astUtils.getVariableByName(scope.upper, variable.name); + + if (shadowed && + (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && + !isOnInitializer(variable, shadowed) && + (!(options.ignoreOnInitialization && isInitPatternNode(variable, shadowed)) || options.hoist === "all") && + !(options.hoist !== "all" && isInTdz(variable, shadowed)) ) { const location = getDeclaredLocation(shadowed); - const messageId = location.global - ? "noShadowGlobal" - : "noShadow"; + const messageId = location.global ? "noShadowGlobal" : "noShadow"; const data = { name: variable.name }; if (!location.global) { @@ -340,5 +314,6 @@ module.exports = { } } }; + } }; From d854d9a3a39f1ae10af9385eb24ab41432174848 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 9 Jan 2022 02:51:43 +0100 Subject: [PATCH 20/27] feat: Adding option for variables on intialization (fixes #12687) --- docs/rules/no-shadow.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/rules/no-shadow.md b/docs/rules/no-shadow.md index 360138737036..a1b1456881c2 100644 --- a/docs/rules/no-shadow.md +++ b/docs/rules/no-shadow.md @@ -189,10 +189,6 @@ var x = foo(x => x) Because the shadowing variable x will shadow an uninitialized shadowed variable x. -## Further Reading - -* [Variable Shadowing](https://en.wikipedia.org/wiki/Variable_shadowing) - ## Related Rules * [no-shadow-restricted-names](no-shadow-restricted-names.md) From bb584358deefcb92c0d40388c2cbbdc5c0b8b47d Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sat, 29 Jan 2022 01:07:16 +0100 Subject: [PATCH 21/27] feat: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 53 ++++++++++++++++++------------------ tests/lib/rules/no-shadow.js | 3 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index b244518c2b7d..b139e8527cb9 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -90,12 +90,16 @@ module.exports = { * Searches from the current node through its ancestry to find a matching node. * @param {ASTNode} node a node to get. * @param {MatchCallback} match a callback that checks whether or not the node verifies its condition or not. + * @param {StopSearchingCallback} stopSearching a callback that checks whether or not the searching should be stopped or not. * @returns {ASTNode} the matching node. */ - function findSelfOrAncestor(node, match) { + function findSelfOrAncestor(node, match, stopSearching) { let currentNode = node; while (currentNode && !match(currentNode)) { + if (stopSearching && stopSearching(currentNode)) { + return null; + } currentNode = currentNode.parent; } return currentNode; @@ -110,41 +114,36 @@ module.exports = { function isInitPatternNode(variable, shadowedVariable) { const outerDef = shadowedVariable.defs[0]; const innerDef = variable.defs[0]; - let fun = innerDef && innerDef.node; - while (fun && !isNodeMatchesTypes(fun, FUN_NODES_TYPES)) { - if (isNodeMatchesTypes(fun, FUN_DEC_NODES_TYPES)) { - fun = null; - } else { - fun = fun.parent; - } - } + const fun = findSelfOrAncestor( + innerDef && innerDef.node, + node => isNodeMatchesTypes(node, FUN_NODES_TYPES), + node => isNodeMatchesTypes(node, FUN_DEC_NODES_TYPES) + ); if (fun) { const { parent } = fun; - const nestedFun = findSelfOrAncestor(parent, node => - isNodeMatchesTypes(node, FUN_NODES_TYPES)); - - if (!nestedFun) { - const callExpression = findSelfOrAncestor(parent, node => - isNodeMatchesTypes(node, CALL_EXP_NODE_TYPE)); + const callExpression = findSelfOrAncestor( + parent, + node => isNodeMatchesTypes(node, CALL_EXP_NODE_TYPE), + node => isNodeMatchesTypes(node, FUN_NODES_TYPES) + ); - if (callExpression) { - const shadowingVariableAncestor = findSelfOrAncestor( - callExpression.parent, - node => isNodeMatchesTypes(node, INIT_NODES_TYPES) - ); + if (callExpression) { + const shadowingVariableAncestor = findSelfOrAncestor( + callExpression.parent, + node => isNodeMatchesTypes(node, INIT_NODES_TYPES) + ); - const shadowingVariable = outerDef && outerDef.name; + const shadowingVariable = outerDef && outerDef.name; - const shadowedVariableAncestor = findSelfOrAncestor( - shadowingVariable, - node => node === shadowingVariableAncestor - ); + const shadowedVariableAncestor = findSelfOrAncestor( + shadowingVariable, + node => node === shadowingVariableAncestor + ); - return !!shadowedVariableAncestor; - } + return !!shadowedVariableAncestor; } } diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index 0696018270ef..ad959df324b0 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -83,7 +83,8 @@ ruleTester.run("no-shadow", rule, { { code: "const a = foo(x || (a => {}))", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const { a = 1 } = foo(a => {})", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const person = {...people.find((person) => person.firstName.startsWith('s'))}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 2021 } }, - { code: "const person = { firstName: people.filter((person) => person.firstName.startsWith('s')).map((person) => person.firstName)[0]}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 2021 } } + { code: "const person = { firstName: people.filter((person) => person.firstName.startsWith('s')).map((person) => person.firstName)[0]}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 2021 } }, + { code: "() => { const y = foo(y => y); }", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } } ], invalid: [ { From 89fef643d7230b50da78d945313aa803c5c56de2 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 6 Feb 2022 00:36:46 +0100 Subject: [PATCH 22/27] feat: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 68 ++++++++++++++++++++---------------- tests/lib/rules/no-shadow.js | 16 +++++++++ 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index b139e8527cb9..35d17e304591 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -23,9 +23,6 @@ const INIT_NODES_TYPES = [ ]; const FUN_NODES_TYPES = ["ArrowFunctionExpression", "FunctionExpression"]; - -const FUN_DEC_NODES_TYPES = ["MethodDefinition", "FunctionDeclaration"]; - const CALL_EXP_NODE_TYPE = ["CallExpression"]; //------------------------------------------------------------------------------ @@ -105,6 +102,20 @@ module.exports = { return currentNode; } + /** + * Finds function's outer scope. + * @param {Scope} scope Function's own scope. + * @returns {Scope} Function's outer scope. + */ + function getOuterScope(scope) { + const upper = scope.upper; + + if (upper.type === "function-expression-name") { + return upper.upper; + } + return upper; + } + /** * Checks if a variable and a shadowedVariable have the same init pattern ancestor. * @param {Object} variable a variable to check. @@ -113,41 +124,38 @@ module.exports = { */ function isInitPatternNode(variable, shadowedVariable) { const outerDef = shadowedVariable.defs[0]; - const innerDef = variable.defs[0]; + const { variableScope } = variable.scope; - const fun = findSelfOrAncestor( - innerDef && innerDef.node, - node => isNodeMatchesTypes(node, FUN_NODES_TYPES), - node => isNodeMatchesTypes(node, FUN_DEC_NODES_TYPES) - ); + if (!(FUN_NODES_TYPES.includes(variableScope.block.type) && getOuterScope(variableScope) === shadowedVariable.scope)) { + return false; + } - if (fun) { - const { parent } = fun; + const fun = variableScope.block; + const { parent } = fun; - const callExpression = findSelfOrAncestor( - parent, - node => isNodeMatchesTypes(node, CALL_EXP_NODE_TYPE), - node => isNodeMatchesTypes(node, FUN_NODES_TYPES) - ); + const callExpression = findSelfOrAncestor( + parent, + node => isNodeMatchesTypes(node, CALL_EXP_NODE_TYPE), + node => isNodeMatchesTypes(node, FUN_NODES_TYPES) + ); - if (callExpression) { - const shadowingVariableAncestor = findSelfOrAncestor( - callExpression.parent, - node => isNodeMatchesTypes(node, INIT_NODES_TYPES) - ); + if (!callExpression) { + return false; + } - const shadowingVariable = outerDef && outerDef.name; + const shadowingVariableAncestor = findSelfOrAncestor( + callExpression.parent, + node => isNodeMatchesTypes(node, INIT_NODES_TYPES) + ); - const shadowedVariableAncestor = findSelfOrAncestor( - shadowingVariable, - node => node === shadowingVariableAncestor - ); + const shadowingVariable = outerDef && outerDef.name; - return !!shadowedVariableAncestor; - } - } + const shadowedVariableAncestor = findSelfOrAncestor( + shadowingVariable, + node => node === shadowingVariableAncestor + ); - return false; + return !!shadowedVariableAncestor; } /** diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index ad959df324b0..8e53480f5f51 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -985,6 +985,22 @@ ruleTester.run("no-shadow", rule, { line: 1, column: 36 }] + }, + { + code: "const x = () => { foo(x => x); }", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 23 + }] } ] }); From 5d0292b9d4457c3eb13070c2c5e47d29188665a3 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Tue, 8 Feb 2022 23:35:57 +0100 Subject: [PATCH 23/27] feat: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 54 ++++++++++++++++++++++++------------ tests/lib/rules/no-shadow.js | 16 +++++++++++ 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 35d17e304591..ee7ac4e7b0de 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -15,15 +15,10 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const INIT_NODES_TYPES = [ - "VariableDeclarator", - "AssignmentPattern", - "ForInStatement", - "ForOfStatement" -]; - const FUN_NODES_TYPES = ["ArrowFunctionExpression", "FunctionExpression"]; const CALL_EXP_NODE_TYPE = ["CallExpression"]; +const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; +const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; //------------------------------------------------------------------------------ // Rule Definition @@ -73,6 +68,16 @@ module.exports = { ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization }; + /** + * Checks whether or not a given location is inside of the range of a given node. + * @param {ASTNode} node An node to check. + * @param {number} location A location to check. + * @returns {boolean} `true` if the location is inside of the range of the node. + */ + function isInRange(node, location) { + return node && node.range[0] <= location && location <= node.range[1]; + } + /** * Checks whether or not the nodes types include the node type. * @param {ASTNode} node a node to get. @@ -126,6 +131,7 @@ module.exports = { const outerDef = shadowedVariable.defs[0]; const { variableScope } = variable.scope; + if (!(FUN_NODES_TYPES.includes(variableScope.block.type) && getOuterScope(variableScope) === shadowedVariable.scope)) { return false; } @@ -143,19 +149,33 @@ module.exports = { return false; } - const shadowingVariableAncestor = findSelfOrAncestor( - callExpression.parent, - node => isNodeMatchesTypes(node, INIT_NODES_TYPES) - ); + let node = outerDef && outerDef.name; + const innerDef = variable.defs[0]; + const location = innerDef && innerDef.name.range[1]; - const shadowingVariable = outerDef && outerDef.name; + while (node) { + if (node.type === "VariableDeclarator") { + if (isInRange(node.init, location)) { + return true; + } + if (FOR_IN_OF_TYPE.test(node.parent.parent.type) && + isInRange(node.parent.parent.right, location) + ) { + return true; + } + break; + } else if (node.type === "AssignmentPattern") { + if (isInRange(node.right, location)) { + return true; + } + } else if (SENTINEL_TYPE.test(node.type)) { + break; + } - const shadowedVariableAncestor = findSelfOrAncestor( - shadowingVariable, - node => node === shadowingVariableAncestor - ); + node = node.parent; + } - return !!shadowedVariableAncestor; + return false; } /** diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index 8e53480f5f51..346c6cb47305 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -1001,6 +1001,22 @@ ruleTester.run("no-shadow", rule, { line: 1, column: 23 }] + }, + { + code: "const foo = () => { let x; bar(x => x); }", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 25 + }, + type: "Identifier", + line: 1, + column: 32 + }] } ] }); From 97931bfb40cc0e26409e6c2ffd0ced7f12011fbc Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Sun, 13 Feb 2022 23:59:22 +0100 Subject: [PATCH 24/27] feat: Adding option for variables on intialization (fixes #12687) --- docs/rules/no-shadow.md | 3 ++ lib/rules/no-shadow.js | 32 ++++++------------ tests/lib/rules/no-shadow.js | 65 +++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 22 deletions(-) diff --git a/docs/rules/no-shadow.md b/docs/rules/no-shadow.md index a1b1456881c2..1f9ddbfafc36 100644 --- a/docs/rules/no-shadow.md +++ b/docs/rules/no-shadow.md @@ -168,6 +168,7 @@ foo(function (err, result) { The `ignoreOnInitialization` option is `false` by default. If it is `true`, the rule prevents reporting variables on initialization statements, the shadowed variable must be on the left side and the shadowing variable must be on the right side. +The option reports also the shadowing variables that are inside IIFE. Examples of **incorrect** code for the default `{ "ignoreOnInitialization": "true" }` option: @@ -185,6 +186,8 @@ Examples of **correct** code for the `{ "ignoreOnInitialization": true }` option /*eslint no-shadow: ["error", { "ignoreOnInitialization": true }]*/ var x = foo(x => x) + +var x = (x => x)() ``` Because the shadowing variable x will shadow an uninitialized shadowed variable x. diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index ee7ac4e7b0de..0a4a74edd632 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -15,8 +15,8 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const FUN_NODES_TYPES = ["ArrowFunctionExpression", "FunctionExpression"]; -const CALL_EXP_NODE_TYPE = ["CallExpression"]; +const FUNC_EXPR_NODE_TYPES = ["ArrowFunctionExpression", "FunctionExpression"]; +const CALL_EXPR_NODE_TYPE = ["CallExpression"]; const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; @@ -78,30 +78,16 @@ module.exports = { return node && node.range[0] <= location && location <= node.range[1]; } - /** - * Checks whether or not the nodes types include the node type. - * @param {ASTNode} node a node to get. - * @param {[string]} types an array of nodes types. - * @returns {boolean} True if nodes types include the node type. - */ - function isNodeMatchesTypes(node, types) { - return types.includes(node.type); - } - /** * Searches from the current node through its ancestry to find a matching node. * @param {ASTNode} node a node to get. * @param {MatchCallback} match a callback that checks whether or not the node verifies its condition or not. - * @param {StopSearchingCallback} stopSearching a callback that checks whether or not the searching should be stopped or not. * @returns {ASTNode} the matching node. */ - function findSelfOrAncestor(node, match, stopSearching) { + function findSelfOrAncestor(node, match) { let currentNode = node; while (currentNode && !match(currentNode)) { - if (stopSearching && stopSearching(currentNode)) { - return null; - } currentNode = currentNode.parent; } return currentNode; @@ -129,10 +115,15 @@ module.exports = { */ function isInitPatternNode(variable, shadowedVariable) { const outerDef = shadowedVariable.defs[0]; + + if (!outerDef) { + return false; + } + const { variableScope } = variable.scope; - if (!(FUN_NODES_TYPES.includes(variableScope.block.type) && getOuterScope(variableScope) === shadowedVariable.scope)) { + if (!(FUNC_EXPR_NODE_TYPES.includes(variableScope.block.type) && getOuterScope(variableScope) === shadowedVariable.scope)) { return false; } @@ -141,8 +132,7 @@ module.exports = { const callExpression = findSelfOrAncestor( parent, - node => isNodeMatchesTypes(node, CALL_EXP_NODE_TYPE), - node => isNodeMatchesTypes(node, FUN_NODES_TYPES) + node => CALL_EXPR_NODE_TYPE.includes(node.type) ); if (!callExpression) { @@ -308,7 +298,7 @@ module.exports = { if (shadowed && (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && !isOnInitializer(variable, shadowed) && - (!(options.ignoreOnInitialization && isInitPatternNode(variable, shadowed)) || options.hoist === "all") && + !(options.ignoreOnInitialization && isInitPatternNode(variable, shadowed)) && !(options.hoist !== "all" && isInTdz(variable, shadowed)) ) { const location = getDeclaredLocation(shadowed); diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index 346c6cb47305..fdf022a67c7d 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -84,7 +84,13 @@ ruleTester.run("no-shadow", rule, { { code: "const { a = 1 } = foo(a => {})", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const person = {...people.find((person) => person.firstName.startsWith('s'))}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 2021 } }, { code: "const person = { firstName: people.filter((person) => person.firstName.startsWith('s')).map((person) => person.firstName)[0]}", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 2021 } }, - { code: "() => { const y = foo(y => y); }", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } } + { code: "() => { const y = foo(y => y); }", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const x = (x => x)()", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = bar || (y => y)();", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = bar && (y => y)();", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var x = (x => x)((y => y)());", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const { a = 1 } = (a => {})()", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "() => { const y = (y => y)(); }", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } } ], invalid: [ { @@ -1017,6 +1023,63 @@ ruleTester.run("no-shadow", rule, { line: 1, column: 32 }] + }, + { + code: "let x = ((x,y) => {})();\nlet y;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier" + }, + { + messageId: "noShadow", + data: { + name: "y", + shadowedLine: 2, + shadowedColumn: 5 + }, + type: "Identifier" + } + ] + }, + { + code: "const a = (()=>{ class C { fn () { const a = 42; return a } } return new C() })()", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 42 + }] + }, + { + code: "const x = () => { (x => x)(); }", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 20 + }] } ] }); From b7a94283f7afc72beee412cf3106eee7b957c4ec Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Mon, 14 Feb 2022 23:01:27 +0100 Subject: [PATCH 25/27] feat: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 3 --- tests/lib/rules/no-shadow.js | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 0a4a74edd632..1c3dc5678964 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -17,7 +17,6 @@ const astUtils = require("./utils/ast-utils"); const FUNC_EXPR_NODE_TYPES = ["ArrowFunctionExpression", "FunctionExpression"]; const CALL_EXPR_NODE_TYPE = ["CallExpression"]; -const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; //------------------------------------------------------------------------------ @@ -158,8 +157,6 @@ module.exports = { if (isInRange(node.right, location)) { return true; } - } else if (SENTINEL_TYPE.test(node.type)) { - break; } node = node.parent; diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index fdf022a67c7d..02838aff3e2e 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -90,7 +90,8 @@ ruleTester.run("no-shadow", rule, { { code: "var y = bar && (y => y)();", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "var x = (x => x)((y => y)());", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const { a = 1 } = (a => {})()", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, - { code: "() => { const y = (y => y)(); }", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } } + { code: "() => { const y = (y => y)(); }", options: [{ ignoreOnInitialization: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const [x = y => y] = [].map(y => y)", parserOptions: { ecmaVersion: 6 } } ], invalid: [ { From c9b30ec93b62fbabbf8760d51e5e78d3de6071a4 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Mon, 21 Feb 2022 22:45:54 +0100 Subject: [PATCH 26/27] feat: Adding option for variables on intialization (fixes #12687) --- docs/rules/no-shadow.md | 14 +++++++------- lib/rules/no-shadow.js | 9 ++++----- tests/lib/rules/no-shadow.js | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/docs/rules/no-shadow.md b/docs/rules/no-shadow.md index 1f9ddbfafc36..b2c5feec5d78 100644 --- a/docs/rules/no-shadow.md +++ b/docs/rules/no-shadow.md @@ -166,11 +166,11 @@ foo(function (err, result) { ### ignoreOnInitialization -The `ignoreOnInitialization` option is `false` by default. -If it is `true`, the rule prevents reporting variables on initialization statements, the shadowed variable must be on the left side and the shadowing variable must be on the right side. -The option reports also the shadowing variables that are inside IIFE. +The `ignoreOnInitialization` option is `false` by default. If it is `true`, it prevents reporting shadowing of variables in their initializers when the shadowed variable is presumably still uninitialized. -Examples of **incorrect** code for the default `{ "ignoreOnInitialization": "true" }` option: +The shadowed variable must be on the left side. The shadowing variable must be on the right side and declared in a callback function or in an IIFE. + +Examples of **incorrect** code for the `{ "ignoreOnInitialization": "true" }` option: ```js /*eslint no-shadow: ["error", { "ignoreOnInitialization": true }]*/ @@ -178,7 +178,7 @@ Examples of **incorrect** code for the default `{ "ignoreOnInitialization": "tru var x = x => x; ``` -Because the shadowing variable x will shadow an initialized shadowed variable x. +Because the shadowing variable `x` will shadow the already initialized shadowed variable `x`. Examples of **correct** code for the `{ "ignoreOnInitialization": true }` option: @@ -187,10 +187,10 @@ Examples of **correct** code for the `{ "ignoreOnInitialization": true }` option var x = foo(x => x) -var x = (x => x)() +var y = (y => y)() ``` -Because the shadowing variable x will shadow an uninitialized shadowed variable x. +The rationale for callback functions is the assumption that they will be called during the initialization, so that at the time when the shadowing variable will be used, the shadowed variable has not yet been initialized. ## Related Rules diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 1c3dc5678964..6744bcb97f2f 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -80,8 +80,8 @@ module.exports = { /** * Searches from the current node through its ancestry to find a matching node. * @param {ASTNode} node a node to get. - * @param {MatchCallback} match a callback that checks whether or not the node verifies its condition or not. - * @returns {ASTNode} the matching node. + * @param {(node: ASTNode) => boolean} match a callback that checks whether or not the node verifies its condition or not. + * @returns {ASTNode|null} the matching node. */ function findSelfOrAncestor(node, match) { let currentNode = node; @@ -138,9 +138,8 @@ module.exports = { return false; } - let node = outerDef && outerDef.name; - const innerDef = variable.defs[0]; - const location = innerDef && innerDef.name.range[1]; + let node = outerDef.name; + const location = callExpression.range[1]; while (node) { if (node.type === "VariableDeclarator") { diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index 02838aff3e2e..4083a2e509dc 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -1025,6 +1025,22 @@ ruleTester.run("no-shadow", rule, { column: 32 }] }, + { + code: "foo(() => { const x = x => x; });", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 19 + }, + type: "Identifier", + line: 1, + column: 23 + }] + }, { code: "let x = ((x,y) => {})();\nlet y;", options: [{ hoist: "all" }], From c5555857c7b9b54f6610027c8796fedad443ba39 Mon Sep 17 00:00:00 2001 From: soufianeboutahlil Date: Wed, 23 Feb 2022 23:40:43 +0100 Subject: [PATCH 27/27] feat: Adding option for variables on intialization (fixes #12687) --- lib/rules/no-shadow.js | 3 +++ tests/lib/rules/no-shadow.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 6744bcb97f2f..43d7d738e295 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -18,6 +18,7 @@ const astUtils = require("./utils/ast-utils"); const FUNC_EXPR_NODE_TYPES = ["ArrowFunctionExpression", "FunctionExpression"]; const CALL_EXPR_NODE_TYPE = ["CallExpression"]; const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; +const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; //------------------------------------------------------------------------------ // Rule Definition @@ -156,6 +157,8 @@ module.exports = { if (isInRange(node.right, location)) { return true; } + } else if (SENTINEL_TYPE.test(node.type)) { + break; } node = node.parent; diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index 4083a2e509dc..0afcc3ff31bb 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -1041,6 +1041,22 @@ ruleTester.run("no-shadow", rule, { column: 23 }] }, + { + code: "const foo = (x) => { bar(x => {}) }", + options: [{ ignoreOnInitialization: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noShadow", + data: { + name: "x", + shadowedLine: 1, + shadowedColumn: 14 + }, + type: "Identifier", + line: 1, + column: 26 + }] + }, { code: "let x = ((x,y) => {})();\nlet y;", options: [{ hoist: "all" }],