Skip to content

Commit 68c61c0

Browse files
authored
feat: support TS syntax in no-shadow (#19565)
1 parent e8a1cb8 commit 68c61c0

File tree

5 files changed

+2399
-12
lines changed

5 files changed

+2399
-12
lines changed

docs/src/rules/no-shadow.md

Lines changed: 137 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ if (true) {
5353

5454
## Options
5555

56-
This rule takes one option, an object, with properties `"builtinGlobals"`, `"hoist"`, `"allow"` and `"ignoreOnInitialization"`.
56+
This rule takes one option, an object, with the following properties:
57+
- `"builtinGlobals"`
58+
- `"hoist"`
59+
- `"allow"`
60+
- `"ignoreOnInitialization"`
61+
- `"ignoreTypeValueShadow"` (TypeScript only)
62+
- `"ignoreFunctionTypeParameterNameValueShadow"` (TypeScript only)
5763

5864
```json
5965
{
@@ -82,11 +88,13 @@ function foo() {
8288

8389
### hoist
8490

85-
The `hoist` option has three settings:
91+
The `hoist` option has five settings:
8692

87-
* `functions` (by default) - reports shadowing before the outer functions are defined.
88-
* `all` - reports all shadowing before the outer variables/functions are defined.
89-
* `never` - never report shadowing before the outer variables/functions are defined.
93+
- `functions` (by default) - reports shadowing before the outer functions are defined.
94+
- `all` - reports all shadowing before the outer variables/functions are defined.
95+
- `never` - never report shadowing before the outer variables/functions are defined.
96+
- `types` (TypeScript only) - reports shadowing before the outer types are defined.
97+
- `functions-and-types` (TypeScript only) - reports shadowing before the outer functions and types are defined.
9098

9199
#### hoist: functions
92100

@@ -168,6 +176,44 @@ function b() {}
168176

169177
Because `const a` and `const b` in the `if` statement are before the declarations in the outer scope, they are correct.
170178

179+
#### hoist: types
180+
181+
Examples of **incorrect** code for the `{ "hoist": "types" }` option:
182+
183+
::: incorrect
184+
185+
```ts
186+
/*eslint no-shadow: ["error", { "hoist": "types" }]*/
187+
188+
type Bar<Foo> = 1;
189+
type Foo = 1;
190+
```
191+
192+
:::
193+
194+
#### hoist: functions-and-types
195+
196+
Examples of **incorrect** code for the `{ "hoist": "functions-and-types" }` option:
197+
198+
::: incorrect
199+
200+
```ts
201+
/*eslint no-shadow: ["error", { "hoist": "functions-and-types" }]*/
202+
203+
// types
204+
type Bar<Foo> = 1;
205+
type Foo = 1;
206+
207+
// functions
208+
if (true) {
209+
const b = 6;
210+
}
211+
212+
function b() {}
213+
```
214+
215+
:::
216+
171217
### allow
172218

173219
The `allow` option is an array of identifier names for which shadowing is allowed. For example, `"resolve"`, `"reject"`, `"done"`, `"cb"`.
@@ -229,3 +275,89 @@ const y = (y => y)()
229275
:::
230276

231277
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.
278+
279+
### ignoreTypeValueShadow
280+
281+
Whether to ignore types named the same as a variable. Default: `true`.
282+
283+
This is generally safe because you cannot use variables in type locations without a typeof operator, so there's little risk of confusion.
284+
285+
Examples of **correct** code with `{ "ignoreTypeValueShadow": true }`:
286+
287+
::: correct
288+
289+
```ts
290+
/*eslint no-shadow: ["error", { "ignoreTypeValueShadow": true }]*/
291+
292+
type Foo = number;
293+
interface Bar {
294+
prop: number;
295+
}
296+
297+
function f() {
298+
const Foo = 1;
299+
const Bar = 'test';
300+
}
301+
```
302+
303+
:::
304+
305+
**Note:** Shadowing specifically refers to two identical identifiers that are in different, nested scopes. This is different from redeclaration, which is when two identical identifiers are in the same scope. Redeclaration is covered by the [`no-redeclare`](/rules/no-redeclare) rule instead.
306+
307+
308+
### ignoreFunctionTypeParameterNameValueShadow
309+
310+
Whether to ignore function parameters named the same as a variable. Default: `true`.
311+
312+
Each of a function type's arguments creates a value variable within the scope of the function type. This is done so that you can reference the type later using the typeof operator:
313+
314+
```ts
315+
type Func = (test: string) => typeof test;
316+
317+
declare const fn: Func;
318+
const result = fn('str'); // typeof result === string
319+
```
320+
321+
This means that function type arguments shadow value variable names in parent scopes:
322+
323+
```ts
324+
let test = 1;
325+
type TestType = typeof test; // === number
326+
type Func = (test: string) => typeof test; // this "test" references the argument, not the variable
327+
328+
declare const fn: Func;
329+
const result = fn('str'); // typeof result === string
330+
```
331+
332+
If you do not use the `typeof` operator in a function type return type position, you can safely turn this option on.
333+
334+
Examples of **correct** code with `{ "ignoreFunctionTypeParameterNameValueShadow": true }`:
335+
336+
::: correct
337+
338+
```ts
339+
/*eslint no-shadow: ["error", { "ignoreFunctionTypeParameterNameValueShadow": true }]*/
340+
341+
const test = 1;
342+
type Func = (test: string) => typeof test;
343+
```
344+
345+
:::
346+
347+
### Why does the rule report on enum members that share the same name as a variable in a parent scope?
348+
349+
This isn't a bug — the rule is working exactly as intended! The report is correct because of a lesser-known aspect of enums: enum members introduce a variable within the enum's own scope, allowing them to be referenced without needing a qualifier.
350+
351+
Here's a simple example to explain:
352+
353+
```ts
354+
const A = 2;
355+
enum Test {
356+
A = 1,
357+
B = A,
358+
}
359+
360+
console.log(Test.B); // what should be logged?
361+
```
362+
363+
At first glance, you might think it should log `2`, because the outer variable A's value is `2`. However, it actually logs `1`, the value of `Test.A`. This happens because inside the enum `B = A` is treated as `B = Test.A`. Due to this behavior, the enum member has shadowed the outer variable declaration.

0 commit comments

Comments
 (0)