From ede0e15610338a52323a7f363906439c1d120d82 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sun, 27 Feb 2022 21:23:15 +0800 Subject: [PATCH 1/5] fix(eslint-plugin): [no-shadow] allow ignoreOnInitialization option --- packages/eslint-plugin/src/rules/no-shadow.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index 3f13f587de16..ef87ebc29d72 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -18,6 +18,7 @@ type Options = [ allow?: string[]; builtinGlobals?: boolean; hoist?: 'all' | 'functions' | 'never'; + ignoreOnInitialization?: boolean; ignoreTypeValueShadow?: boolean; ignoreFunctionTypeParameterNameValueShadow?: boolean; }, @@ -49,6 +50,9 @@ export default util.createRule({ type: 'string', }, }, + ignoreOnInitialization: { + type: 'boolean', + }, ignoreTypeValueShadow: { type: 'boolean', }, @@ -68,6 +72,7 @@ export default util.createRule({ allow: [], builtinGlobals: false, hoist: 'functions', + ignoreOnInitialization: false, ignoreTypeValueShadow: true, ignoreFunctionTypeParameterNameValueShadow: true, }, From 49800bb1be47d1e2c1135d6d374edb804af6f179 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Tue, 1 Mar 2022 17:45:29 +0800 Subject: [PATCH 2/5] fix(eslint-plugin): backport upstream changes --- packages/eslint-plugin/src/rules/no-shadow.ts | 125 +++++ .../tests/rules/no-shadow.test.ts | 466 ++++++++++++++++++ 2 files changed, 591 insertions(+) diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index ef87ebc29d72..75f5ffd85152 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -24,6 +24,9 @@ type Options = [ }, ]; +const SENTINEL_TYPE = + /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; + export default util.createRule({ name: 'no-shadow', meta: { @@ -319,6 +322,124 @@ export default util.createRule({ ); } + /** + * Checks whether or not a given location is inside of the range of a given node. + * @param node An node to check. + * @param location A location to check. + * @returns `true` if the location is inside of the range of the node. + */ + function isInRange( + node: TSESTree.Node | null, + location: number, + ): boolean | null { + return node && node.range[0] <= location && location <= node.range[1]; + } + + /** + * Searches from the current node through its ancestry to find a matching node. + * @param node a node to get. + * @param match a callback that checks whether or not the node verifies its condition or not. + * @returns the matching node. + */ + function findSelfOrAncestor( + node: TSESTree.Node | undefined, + match: (node: TSESTree.Node) => boolean, + ): TSESTree.Node | undefined { + let currentNode = node; + + while (currentNode && !match(currentNode)) { + currentNode = currentNode.parent; + } + return currentNode; + } + + /** + * Finds function's outer scope. + * @param scope Function's own scope. + * @returns Function's outer scope. + */ + function getOuterScope( + scope: TSESLint.Scope.Scope, + ): TSESLint.Scope.Scope | null { + 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 variable a variable to check. + * @param shadowedVariable a shadowedVariable to check. + * @returns Whether or not the variable and the shadowedVariable have the same init pattern ancestor. + */ + function isInitPatternNode( + variable: TSESLint.Scope.Variable, + shadowedVariable: TSESLint.Scope.Variable, + ): boolean { + const outerDef = shadowedVariable.defs[0]; + + if (!outerDef) { + return false; + } + + const { variableScope } = variable.scope; + + if ( + !( + (variableScope.block.type === + AST_NODE_TYPES.ArrowFunctionExpression || + variableScope.block.type === AST_NODE_TYPES.FunctionExpression) && + getOuterScope(variableScope) === shadowedVariable.scope + ) + ) { + return false; + } + + const fun = variableScope.block; + const { parent } = fun; + + const callExpression = findSelfOrAncestor( + parent, + node => node.type === AST_NODE_TYPES.CallExpression, + ); + + if (!callExpression) { + return false; + } + + let node: TSESTree.Node | undefined = outerDef.name; + const location = callExpression.range[1]; + + while (node) { + if (node.type === AST_NODE_TYPES.VariableDeclarator) { + if (isInRange(node.init, location)) { + return true; + } + if ( + (node.parent?.parent?.type === AST_NODE_TYPES.ForInStatement || + node.parent?.parent?.type === AST_NODE_TYPES.ForOfStatement) && + isInRange(node.parent.parent.right, location) + ) { + return true; + } + break; + } else if (node.type === AST_NODE_TYPES.AssignmentPattern) { + if (isInRange(node.right, location)) { + return true; + } + } else if (SENTINEL_TYPE.test(node.type)) { + break; + } + + node = node.parent; + } + + return false; + } + /** * Checks if a variable is inside the initializer of scopeVar. * @@ -460,6 +581,10 @@ export default util.createRule({ (shadowed.identifiers.length > 0 || (options.builtinGlobals && isESLintGlobal)) && !isOnInitializer(variable, shadowed) && + !( + options.ignoreOnInitialization && + isInitPatternNode(variable, shadowed) + ) && !(options.hoist !== 'all' && isInTdz(variable, shadowed)) ) { context.report({ diff --git a/packages/eslint-plugin/tests/rules/no-shadow.test.ts b/packages/eslint-plugin/tests/rules/no-shadow.test.ts index 635dff307e1f..a89c12f5c899 100644 --- a/packages/eslint-plugin/tests/rules/no-shadow.test.ts +++ b/packages/eslint-plugin/tests/rules/no-shadow.test.ts @@ -202,6 +202,156 @@ function doThing(foo: number) {} `, options: [{ ignoreTypeValueShadow: true }], }, + { + code: 'const a = [].find(a => a);', + options: [{ ignoreOnInitialization: true }], + }, + { + code: ` +const a = [].find(function (a) { + return a; +}); + `, + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'const [a = [].find(a => true)] = dummy;', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'const { a = [].find(a => true) } = dummy;', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'function func(a = [].find(a => true)) {}', + options: [{ ignoreOnInitialization: true }], + }, + { + code: ` +for (const a in [].find(a => true)) { +} + `, + options: [{ ignoreOnInitialization: true }], + }, + { + code: ` +for (const a of [].find(a => true)) { +} + `, + options: [{ ignoreOnInitialization: true }], + }, + { + code: "const a = [].map(a => true).filter(a => a === 'b');", + options: [{ ignoreOnInitialization: true }], + }, + { + code: ` +const a = [] + .map(a => true) + .filter(a => a === 'b') + .find(a => a === 'c'); + `, + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'const { a } = (({ a }) => ({ a }))();', + options: [{ ignoreOnInitialization: true }], + }, + { + code: ` +const person = people.find(item => { + const person = item.name; + return person === 'foo'; +}); + `, + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'var y = bar || foo(y => y);', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'var y = bar && foo(y => y);', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'var z = bar(foo(z => z));', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'var z = boo(bar(foo(z => z)));', + options: [{ ignoreOnInitialization: true }], + }, + { + code: ` +var match = function (person) { + return person.name === 'foo'; +}; +const person = [].find(match); + `, + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'const a = foo(x || (a => {}));', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'const { a = 1 } = foo(a => {});', + options: [{ ignoreOnInitialization: true }], + }, + { + 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 }], + }, + { + code: 'const x = (x => x)();', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'var y = bar || (y => y)();', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'var y = bar && (y => y)();', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'var x = (x => x)((y => y)());', + options: [{ ignoreOnInitialization: true }], + }, + { + code: 'const { a = 1 } = (a => {})();', + options: [{ ignoreOnInitialization: true }], + }, + { + code: ` +() => { + const y = (y => y)(); +}; + `, + options: [{ ignoreOnInitialization: true }], + }, + { code: 'const [x = y => y] = [].map(y => y);' }, ], invalid: [ { @@ -1612,5 +1762,321 @@ declare module 'bar' { }, ], }, + { + code: ` +let x = foo((x, y) => {}); +let y; + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ hoist: 'all' }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'x', + shadowedLine: 1, + shadowedColumn: 5, + }, + type: AST_NODE_TYPES.Identifier, + }, + { + messageId: 'noShadow', + data: { + name: 'y', + shadowedLine: 2, + shadowedColumn: 5, + }, + type: AST_NODE_TYPES.Identifier, + }, + ], + }, + { + code: ` +const a = fn(() => { + class C { + fn() { + const a = 42; + return a; + } + } + return new C(); +}); + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'a', + shadowedLine: 1, + shadowedColumn: 7, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 44, + }, + ], + }, + { + code: 'function a() {}\nfoo(a => {});', + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'a', + shadowedLine: 1, + shadowedColumn: 10, + }, + type: AST_NODE_TYPES.Identifier, + line: 2, + column: 5, + }, + ], + }, + { + code: ` +const a = fn(() => { + function C() { + this.fn = function () { + const a = 42; + return a; + }; + } + return new C(); +}); + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'a', + shadowedLine: 1, + shadowedColumn: 7, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 62, + }, + ], + }, + { + code: ` +const x = foo(() => { + const bar = () => { + return x => {}; + }; + return bar; +}); + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'x', + shadowedLine: 1, + shadowedColumn: 7, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 50, + }, + ], + }, + { + code: ` +const x = foo(() => { + return { bar(x) {} }; +}); + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'x', + shadowedLine: 1, + shadowedColumn: 7, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 36, + }, + ], + }, + { + code: ` +const x = () => { + foo(x => x); +}; + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'x', + shadowedLine: 1, + shadowedColumn: 7, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 23, + }, + ], + }, + { + code: ` +const foo = () => { + let x; + bar(x => x); +}; + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'x', + shadowedLine: 1, + shadowedColumn: 25, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 32, + }, + ], + }, + { + code: ` +foo(() => { + const x = x => x; +}); + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'x', + shadowedLine: 1, + shadowedColumn: 19, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 23, + }, + ], + }, + { + code: ` +const foo = x => { + bar(x => {}); +}; + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'x', + shadowedLine: 1, + shadowedColumn: 14, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 26, + }, + ], + }, + { + code: ` +let x = ((x, y) => {})(); +let y; + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ hoist: 'all' }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'x', + shadowedLine: 1, + shadowedColumn: 5, + }, + type: AST_NODE_TYPES.Identifier, + }, + { + messageId: 'noShadow', + data: { + name: 'y', + shadowedLine: 2, + shadowedColumn: 5, + }, + type: AST_NODE_TYPES.Identifier, + }, + ], + }, + { + code: ` +const a = (() => { + class C { + fn() { + const a = 42; + return a; + } + } + return new C(); +})(); + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'a', + shadowedLine: 1, + shadowedColumn: 7, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 42, + }, + ], + }, + { + code: ` +const x = () => { + (x => x)(); +}; + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ ignoreOnInitialization: true }], + errors: [ + { + messageId: 'noShadow', + data: { + name: 'x', + shadowedLine: 1, + shadowedColumn: 7, + }, + type: AST_NODE_TYPES.Identifier, + line: 1, + column: 20, + }, + ], + }, ], }); From 148d3ef07edccd67c36e3b1833e07c7b01d9f005 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Wed, 2 Mar 2022 14:56:03 +0800 Subject: [PATCH 3/5] refactor(eslint-plugin): refactor --- packages/eslint-plugin/src/rules/no-shadow.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index 75f5ffd85152..a80a1b506732 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -24,9 +24,6 @@ type Options = [ }, ]; -const SENTINEL_TYPE = - /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; - export default util.createRule({ name: 'no-shadow', meta: { @@ -430,7 +427,18 @@ export default util.createRule({ if (isInRange(node.right, location)) { return true; } - } else if (SENTINEL_TYPE.test(node.type)) { + } else if ( + [ + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.ClassDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.ClassExpression, + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.CatchClause, + AST_NODE_TYPES.ImportDeclaration, + AST_NODE_TYPES.ExportNamedDeclaration, + ].includes(node.type) + ) { break; } From 83eff7b02243d3885d4c2535a12f786330585646 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Tue, 15 Mar 2022 14:31:01 +0800 Subject: [PATCH 4/5] chore: sync eslint type def --- packages/eslint-plugin/typings/eslint-rules.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index fa7d5934b263..f8efbe03aff6 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -374,6 +374,7 @@ declare module 'eslint/lib/rules/no-shadow' { builtinGlobals?: boolean; hoist?: 'all' | 'functions' | 'never'; allow?: string[]; + ignoreOnInitialization?: boolean; }, ], { From d39e5897cbe628c5fe945fb7d0526ae4a59fc486 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Tue, 29 Mar 2022 20:46:45 +0800 Subject: [PATCH 5/5] test: fix all test failures --- .../tests/rules/no-shadow.test.ts | 124 +++++------------- 1 file changed, 34 insertions(+), 90 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/no-shadow.test.ts b/packages/eslint-plugin/tests/rules/no-shadow.test.ts index a89c12f5c899..69a69f2f70eb 100644 --- a/packages/eslint-plugin/tests/rules/no-shadow.test.ts +++ b/packages/eslint-plugin/tests/rules/no-shadow.test.ts @@ -1772,11 +1772,7 @@ let y; errors: [ { messageId: 'noShadow', - data: { - name: 'x', - shadowedLine: 1, - shadowedColumn: 5, - }, + data: { name: 'x' }, type: AST_NODE_TYPES.Identifier, }, { @@ -1807,14 +1803,10 @@ const a = fn(() => { errors: [ { messageId: 'noShadow', - data: { - name: 'a', - shadowedLine: 1, - shadowedColumn: 7, - }, + data: { name: 'a' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 44, + line: 5, + column: 13, }, ], }, @@ -1825,11 +1817,7 @@ const a = fn(() => { errors: [ { messageId: 'noShadow', - data: { - name: 'a', - shadowedLine: 1, - shadowedColumn: 10, - }, + data: { name: 'a' }, type: AST_NODE_TYPES.Identifier, line: 2, column: 5, @@ -1853,14 +1841,10 @@ const a = fn(() => { errors: [ { messageId: 'noShadow', - data: { - name: 'a', - shadowedLine: 1, - shadowedColumn: 7, - }, + data: { name: 'a' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 62, + line: 5, + column: 13, }, ], }, @@ -1878,14 +1862,10 @@ const x = foo(() => { errors: [ { messageId: 'noShadow', - data: { - name: 'x', - shadowedLine: 1, - shadowedColumn: 7, - }, + data: { name: 'x' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 50, + line: 4, + column: 12, }, ], }, @@ -1900,14 +1880,10 @@ const x = foo(() => { errors: [ { messageId: 'noShadow', - data: { - name: 'x', - shadowedLine: 1, - shadowedColumn: 7, - }, + data: { name: 'x' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 36, + line: 3, + column: 16, }, ], }, @@ -1922,14 +1898,10 @@ const x = () => { errors: [ { messageId: 'noShadow', - data: { - name: 'x', - shadowedLine: 1, - shadowedColumn: 7, - }, + data: { name: 'x' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 23, + line: 3, + column: 7, }, ], }, @@ -1945,14 +1917,10 @@ const foo = () => { errors: [ { messageId: 'noShadow', - data: { - name: 'x', - shadowedLine: 1, - shadowedColumn: 25, - }, + data: { name: 'x' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 32, + line: 4, + column: 7, }, ], }, @@ -1967,14 +1935,10 @@ foo(() => { errors: [ { messageId: 'noShadow', - data: { - name: 'x', - shadowedLine: 1, - shadowedColumn: 19, - }, + data: { name: 'x' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 23, + line: 3, + column: 13, }, ], }, @@ -1989,14 +1953,10 @@ const foo = x => { errors: [ { messageId: 'noShadow', - data: { - name: 'x', - shadowedLine: 1, - shadowedColumn: 14, - }, + data: { name: 'x' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 26, + line: 3, + column: 7, }, ], }, @@ -2010,20 +1970,12 @@ let y; errors: [ { messageId: 'noShadow', - data: { - name: 'x', - shadowedLine: 1, - shadowedColumn: 5, - }, + data: { name: 'x' }, type: AST_NODE_TYPES.Identifier, }, { messageId: 'noShadow', - data: { - name: 'y', - shadowedLine: 2, - shadowedColumn: 5, - }, + data: { name: 'y' }, type: AST_NODE_TYPES.Identifier, }, ], @@ -2045,14 +1997,10 @@ const a = (() => { errors: [ { messageId: 'noShadow', - data: { - name: 'a', - shadowedLine: 1, - shadowedColumn: 7, - }, + data: { name: 'a' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 42, + line: 5, + column: 13, }, ], }, @@ -2067,14 +2015,10 @@ const x = () => { errors: [ { messageId: 'noShadow', - data: { - name: 'x', - shadowedLine: 1, - shadowedColumn: 7, - }, + data: { name: 'x' }, type: AST_NODE_TYPES.Identifier, - line: 1, - column: 20, + line: 3, + column: 4, }, ], }, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy