From f9f48f612b9afbcb2095014464d7b372d1ed9e40 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Tue, 24 Dec 2024 15:29:36 +0200 Subject: [PATCH 1/5] initial implementation --- packages/eslint-plugin/src/rules/no-shadow.ts | 45 ++++++++---- .../tests/rules/no-shadow/no-shadow.test.ts | 72 +++++++++++++++++++ 2 files changed, 105 insertions(+), 12 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index 0e1f87149608..ae168c6480a9 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -11,7 +11,7 @@ type Options = [ { allow?: string[]; builtinGlobals?: boolean; - hoist?: 'all' | 'functions' | 'never'; + hoist?: 'all' | 'functions' | 'functions-and-types' | 'never' | 'types'; ignoreFunctionTypeParameterNameValueShadow?: boolean; ignoreOnInitialization?: boolean; ignoreTypeValueShadow?: boolean; @@ -28,6 +28,13 @@ const allowedFunctionVariableDefTypes = new Set([ AST_NODE_TYPES.TSConstructorType, ]); +const functionsHoistedNodes = new Set([AST_NODE_TYPES.FunctionDeclaration]); + +const typesHoistedNodes = new Set([ + AST_NODE_TYPES.TSInterfaceDeclaration, + AST_NODE_TYPES.TSTypeAliasDeclaration, +]); + export default createRule({ name: 'no-shadow', meta: { @@ -63,7 +70,7 @@ export default createRule({ type: 'string', description: 'Whether to report shadowing before outer functions or variables are defined.', - enum: ['all', 'functions', 'never'], + enum: ['all', 'functions', 'functions-and-types', 'never', 'types'], }, ignoreFunctionTypeParameterNameValueShadow: { type: 'boolean', @@ -88,7 +95,7 @@ export default createRule({ { allow: [], builtinGlobals: false, - hoist: 'functions', + hoist: 'functions-and-types', ignoreFunctionTypeParameterNameValueShadow: true, ignoreOnInitialization: false, ignoreTypeValueShadow: true, @@ -513,15 +520,29 @@ export default createRule({ const inner = getNameRange(variable); const outer = getNameRange(scopeVar); - return !!( - inner && - outer && - inner[1] < outer[0] && - // Excepts FunctionDeclaration if is {"hoist":"function"}. - (options.hoist !== 'functions' || - !outerDef || - outerDef.node.type !== AST_NODE_TYPES.FunctionDeclaration) - ); + if (!inner || !outer || inner[1] >= outer[0] || !outerDef) { + return false; + } + + // Excepts function declaration nodes if is {"hoist":"function"}. + if (options.hoist === 'functions') { + return !functionsHoistedNodes.has(outerDef.node.type); + } + + // Excepts type declaration nodes if is {"hoist":"types"}. + if (options.hoist === 'types') { + return !typesHoistedNodes.has(outerDef.node.type); + } + + // Except both if is {"hoist":"functions-and-types"} + if (options.hoist === 'functions-and-types') { + return ( + !functionsHoistedNodes.has(outerDef.node.type) && + !typesHoistedNodes.has(outerDef.node.type) + ); + } + + return true; } /** diff --git a/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts b/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts index 6dd82ef35747..5746214f7aa9 100644 --- a/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts +++ b/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts @@ -549,6 +549,78 @@ let y; }, { code: ` +type Foo = 1; +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'types' }], + }, + { + code: ` +interface Foo {} +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'types' }], + }, + { + code: ` +interface Foo {} +interface A {} + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 11, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'types' }], + }, + { + code: ` +type Foo = 1; +interface A {} + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 11, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'types' }], + }, + { + code: ` function foo any>(fn: T, args: any[]) {} `, errors: [ From 0eb600bb557369847f877677417c493bfd4bb4fe Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Thu, 26 Dec 2024 14:43:13 +0200 Subject: [PATCH 2/5] add relevant tests --- packages/eslint-plugin/src/rules/no-shadow.ts | 3 - .../tests/rules/no-shadow/no-shadow.test.ts | 380 ++++++++++++++++++ 2 files changed, 380 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index ae168c6480a9..ebc3cc88f20c 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -524,17 +524,14 @@ export default createRule({ return false; } - // Excepts function declaration nodes if is {"hoist":"function"}. if (options.hoist === 'functions') { return !functionsHoistedNodes.has(outerDef.node.type); } - // Excepts type declaration nodes if is {"hoist":"types"}. if (options.hoist === 'types') { return !typesHoistedNodes.has(outerDef.node.type); } - // Except both if is {"hoist":"functions-and-types"} if (options.hoist === 'functions-and-types') { return ( !functionsHoistedNodes.has(outerDef.node.type) && diff --git a/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts b/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts index 5746214f7aa9..e4c420092c93 100644 --- a/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts +++ b/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts @@ -549,6 +549,25 @@ let y; }, { code: ` +let x = foo((x, y) => {}); +let y; + `, + errors: [ + { + data: { + name: 'x', + shadowedColumn: 5, + shadowedLine: 2, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + languageOptions: { parserOptions: { ecmaVersion: 6 } }, + options: [{ hoist: 'functions' }], + }, + { + code: ` type Foo = 1; type A = 1; `, @@ -619,6 +638,273 @@ interface A {} ], options: [{ hoist: 'types' }], }, + { + code: ` +{ + type A = 1; +} +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 5, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'types' }], + }, + { + code: ` +{ + interface A {} +} +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 5, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'types' }], + }, + + { + code: ` +type Foo = 1; +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'all' }], + }, + { + code: ` +interface Foo {} +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'all' }], + }, + { + code: ` +interface Foo {} +interface A {} + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 11, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'all' }], + }, + { + code: ` +type Foo = 1; +interface A {} + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 11, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'all' }], + }, + { + code: ` +{ + type A = 1; +} +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 5, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'all' }], + }, + { + code: ` +{ + interface A {} +} +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 5, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'all' }], + }, + + { + code: ` +type Foo = 1; +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'functions-and-types' }], + }, + { + code: ` +interface Foo {} +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'functions-and-types' }], + }, + { + code: ` +interface Foo {} +interface A {} + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 11, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'functions-and-types' }], + }, + { + code: ` +type Foo = 1; +interface A {} + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 11, + shadowedLine: 3, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'functions-and-types' }], + }, + { + code: ` +{ + type A = 1; +} +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 5, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'functions-and-types' }], + }, + { + code: ` +{ + interface A {} +} +type A = 1; + `, + errors: [ + { + data: { + name: 'A', + shadowedColumn: 6, + shadowedLine: 5, + }, + messageId: 'noShadow', + type: AST_NODE_TYPES.Identifier, + }, + ], + options: [{ hoist: 'functions-and-types' }], + }, + { code: ` function foo any>(fn: T, args: any[]) {} @@ -1099,5 +1385,99 @@ const person = { options: [{ ignoreOnInitialization: true }], }, { code: 'const [x = y => y] = [].map(y => y);' }, + + { + code: ` +type Foo = 1; +type A = 1; + `, + options: [{ hoist: 'never' }], + }, + { + code: ` +interface Foo {} +type A = 1; + `, + options: [{ hoist: 'never' }], + }, + { + code: ` +interface Foo {} +interface A {} + `, + options: [{ hoist: 'never' }], + }, + { + code: ` +type Foo = 1; +interface A {} + `, + options: [{ hoist: 'never' }], + }, + { + code: ` +{ + type A = 1; +} +type A = 1; + `, + options: [{ hoist: 'never' }], + }, + { + code: ` +{ + interface Foo {} +} +type A = 1; + `, + options: [{ hoist: 'never' }], + }, + + { + code: ` +type Foo = 1; +type A = 1; + `, + options: [{ hoist: 'functions' }], + }, + { + code: ` +interface Foo {} +type A = 1; + `, + options: [{ hoist: 'functions' }], + }, + { + code: ` +interface Foo {} +interface A {} + `, + options: [{ hoist: 'functions' }], + }, + { + code: ` +type Foo = 1; +interface A {} + `, + options: [{ hoist: 'functions' }], + }, + { + code: ` +{ + type A = 1; +} +type A = 1; + `, + options: [{ hoist: 'functions' }], + }, + { + code: ` +{ + interface Foo {} +} +type A = 1; + `, + options: [{ hoist: 'functions' }], + }, ], }); From 0a63dc275b1daf07845485fdffeb0f91e4b92723 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Thu, 2 Jan 2025 19:22:27 +0200 Subject: [PATCH 3/5] add docs --- .../eslint-plugin/docs/rules/no-shadow.mdx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/no-shadow.mdx b/packages/eslint-plugin/docs/rules/no-shadow.mdx index ecd105f6e334..1e5255881d9a 100644 --- a/packages/eslint-plugin/docs/rules/no-shadow.mdx +++ b/packages/eslint-plugin/docs/rules/no-shadow.mdx @@ -17,18 +17,52 @@ It adds support for TypeScript's `this` parameters and global augmentation, and This rule adds the following options: ```ts +type AdditionalHoistOptionEntries = 'types' | 'functions-and-types'; + +type HoistOptionEntries = + | BaseNoShadowHoistOptionEntries + | AdditionalHoistOptionEntries; + interface Options extends BaseNoShadowOptions { + hoist?: HoistOptionEntries; ignoreTypeValueShadow?: boolean; ignoreFunctionTypeParameterNameValueShadow?: boolean; } const defaultOptions: Options = { ...baseNoShadowDefaultOptions, + hoist: 'functions-and-types', ignoreTypeValueShadow: true, ignoreFunctionTypeParameterNameValueShadow: true, }; ``` +### hoist: `types` + +Examples of incorrect code for the `{ "hoist": ["types"] }` option: + +```ts option='{ "hoist": ["types"] }' showPlaygroundButton +type Bar = 1; +type Foo = 1; +``` + +### hoist: `functions-and-types` + +Examples of incorrect code for the `{ "hoist": ["functions-and-types"] }` option: + +```ts option='{ "hoist": ["functions-and-types"] }' showPlaygroundButton +// types +type Bar = 1; +type Foo = 1; + +// functions +if (true) { + let b = 6; +} + +function b() {} +``` + ### `ignoreTypeValueShadow` {/* insert option description */} From b1649719a28890ca8833cd9312ba6b629b57121f Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Thu, 2 Jan 2025 22:29:08 +0200 Subject: [PATCH 4/5] snapshots --- .../eslint-plugin/docs/rules/no-shadow.mdx | 8 ++--- .../no-shadow.shot | 29 ++++++++++++++++++- .../tests/schema-snapshots/no-shadow.shot | 4 ++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-shadow.mdx b/packages/eslint-plugin/docs/rules/no-shadow.mdx index 1e5255881d9a..41c274b4bf92 100644 --- a/packages/eslint-plugin/docs/rules/no-shadow.mdx +++ b/packages/eslint-plugin/docs/rules/no-shadow.mdx @@ -39,18 +39,18 @@ const defaultOptions: Options = { ### hoist: `types` -Examples of incorrect code for the `{ "hoist": ["types"] }` option: +Examples of incorrect code for the `{ "hoist": "types" }` option: -```ts option='{ "hoist": ["types"] }' showPlaygroundButton +```ts option='{ "hoist": "types" }' showPlaygroundButton type Bar = 1; type Foo = 1; ``` ### hoist: `functions-and-types` -Examples of incorrect code for the `{ "hoist": ["functions-and-types"] }` option: +Examples of incorrect code for the `{ "hoist": "functions-and-types" }` option: -```ts option='{ "hoist": ["functions-and-types"] }' showPlaygroundButton +```ts option='{ "hoist": "functions-and-types" }' showPlaygroundButton // types type Bar = 1; type Foo = 1; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-shadow.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-shadow.shot index 46c8fe00fb7d..30d6e41c45b1 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-shadow.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-shadow.shot @@ -1,6 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Validating rule docs no-shadow.mdx code examples ESLint output 1`] = ` +"Options: { "hoist": "types" } + +type Bar = 1; + ~~~ 'Foo' is already declared in the upper scope on line 2 column 6. +type Foo = 1; +" +`; + +exports[`Validating rule docs no-shadow.mdx code examples ESLint output 2`] = ` +"Options: { "hoist": "functions-and-types" } + +// types +type Bar = 1; + ~~~ 'Foo' is already declared in the upper scope on line 3 column 6. +type Foo = 1; + +// functions +if (true) { + let b = 6; + ~ 'b' is already declared in the upper scope on line 10 column 10. +} + +function b() {} +" +`; + +exports[`Validating rule docs no-shadow.mdx code examples ESLint output 3`] = ` "Options: { "ignoreTypeValueShadow": true } type Foo = number; @@ -15,7 +42,7 @@ function f() { " `; -exports[`Validating rule docs no-shadow.mdx code examples ESLint output 2`] = ` +exports[`Validating rule docs no-shadow.mdx code examples ESLint output 4`] = ` "Options: { "ignoreFunctionTypeParameterNameValueShadow": true } const test = 1; diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-shadow.shot b/packages/eslint-plugin/tests/schema-snapshots/no-shadow.shot index 22a2dba566de..1bf895c38491 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-shadow.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-shadow.shot @@ -21,7 +21,7 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos }, "hoist": { "description": "Whether to report shadowing before outer functions or variables are defined.", - "enum": ["all", "functions", "never"], + "enum": ["all", "functions", "functions-and-types", "never", "types"], "type": "string" }, "ignoreFunctionTypeParameterNameValueShadow": { @@ -53,7 +53,9 @@ type Options = [ /** Whether to report shadowing before outer functions or variables are defined. */ hoist?: | 'functions' + | 'functions-and-types' | 'never' + | 'types' /** Whether to report shadowing before outer functions or variables are defined. */ | 'all'; /** Whether to ignore function parameters named the same as a variable. */ From 7dd6b06b6669f714edce7e65204c2902c0a723f6 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Sun, 12 Jan 2025 21:46:21 +0200 Subject: [PATCH 5/5] match original implementation --- packages/eslint-plugin/src/rules/no-shadow.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index ebc3cc88f20c..ed3d01830368 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -520,10 +520,14 @@ export default createRule({ const inner = getNameRange(variable); const outer = getNameRange(scopeVar); - if (!inner || !outer || inner[1] >= outer[0] || !outerDef) { + if (!inner || !outer || inner[1] >= outer[0]) { return false; } + if (!outerDef) { + return true; + } + if (options.hoist === 'functions') { return !functionsHoistedNodes.has(outerDef.node.type); } 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