Skip to content

Commit bfe255f

Browse files
authored
feat(eslint-plugin): [no-shadow] add option ignoreFunctionTypeParameterNameValueShadow (typescript-eslint#2470)
1 parent ffdfade commit bfe255f

File tree

4 files changed

+197
-17
lines changed

4 files changed

+197
-17
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
"footer-max-length": [
5858
0
5959
],
60+
"footer-max-line-length": [
61+
0
62+
],
6063
"header-max-length": [
6164
0
6265
]

packages/eslint-plugin/docs/rules/no-shadow.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,21 @@ This rule adds the following options:
2323
```ts
2424
interface Options extends BaseNoShadowOptions {
2525
ignoreTypeValueShadow?: boolean;
26+
ignoreFunctionTypeParameterNameValueShadow?: boolean;
2627
}
2728

2829
const defaultOptions: Options = {
2930
...baseNoShadowDefaultOptions,
3031
ignoreTypeValueShadow: true,
32+
ignoreFunctionTypeParameterNameValueShadow: true,
3133
};
3234
```
3335

3436
### `ignoreTypeValueShadow`
3537

36-
When set to `true`, the rule will ignore when you name a type and a variable with the same name.
38+
When set to `true`, the rule will ignore the case when you name a type the same as a variable.
39+
40+
TypeScript allows types and variables to shadow one-another. This is generally safe because you cannot use variables in type locations without a `typeof` operator, so there's little risk of confusion.
3741

3842
Examples of **correct** code with `{ ignoreTypeValueShadow: true }`:
3943

@@ -47,4 +51,37 @@ interface Bar {
4751
const Bar = 'test';
4852
```
4953

54+
### `ignoreFunctionTypeParameterNameValueShadow`
55+
56+
When set to `true`, the rule will ignore the case when you name a function type argument the same as a variable.
57+
58+
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:
59+
60+
```ts
61+
type Func = (test: string) => typeof test;
62+
63+
declare const fn: Func;
64+
const result = fn('str'); // typeof result === string
65+
```
66+
67+
This means that function type arguments shadow value variable names in parent scopes:
68+
69+
```ts
70+
let test = 1;
71+
type TestType = typeof test; // === number
72+
type Func = (test: string) => typeof test; // this "test" references the argument, not the variable
73+
74+
declare const fn: Func;
75+
const result = fn('str'); // typeof result === string
76+
```
77+
78+
If you do not use the `typeof` operator in a function type return type position, you can safely turn this option on.
79+
80+
Examples of **correct** code with `{ ignoreFunctionTypeParameterNameValueShadow: true }`:
81+
82+
```ts
83+
const test = 1;
84+
type Func = (test: string) => typeof test;
85+
```
86+
5087
<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-shadow.md)</sup>

packages/eslint-plugin/src/rules/no-shadow.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type Options = [
1212
builtinGlobals?: boolean;
1313
hoist?: 'all' | 'functions' | 'never';
1414
ignoreTypeValueShadow?: boolean;
15+
ignoreFunctionTypeParameterNameValueShadow?: boolean;
1516
},
1617
];
1718

@@ -45,6 +46,9 @@ export default util.createRule<Options, MessageIds>({
4546
ignoreTypeValueShadow: {
4647
type: 'boolean',
4748
},
49+
ignoreFunctionTypeParameterNameValueShadow: {
50+
type: 'boolean',
51+
},
4852
},
4953
additionalProperties: false,
5054
},
@@ -59,6 +63,7 @@ export default util.createRule<Options, MessageIds>({
5963
builtinGlobals: false,
6064
hoist: 'functions',
6165
ignoreTypeValueShadow: true,
66+
ignoreFunctionTypeParameterNameValueShadow: true,
6267
},
6368
],
6469
create(context, [options]) {
@@ -77,15 +82,37 @@ export default util.createRule<Options, MessageIds>({
7782
return false;
7883
}
7984

80-
if (
81-
!('isValueVariable' in shadowed) ||
82-
!('isValueVariable' in variable)
83-
) {
84-
// one of them is an eslint global variable
85+
if (!('isValueVariable' in variable)) {
86+
// this shouldn't happen...
87+
return false;
88+
}
89+
90+
const isShadowedValue =
91+
'isValueVariable' in shadowed ? shadowed.isValueVariable : true;
92+
return variable.isValueVariable !== isShadowedValue;
93+
}
94+
95+
function isFunctionTypeParameterNameValueShadow(
96+
variable: TSESLint.Scope.Variable,
97+
shadowed: TSESLint.Scope.Variable,
98+
): boolean {
99+
if (options.ignoreFunctionTypeParameterNameValueShadow !== true) {
100+
return false;
101+
}
102+
103+
if (!('isValueVariable' in variable)) {
104+
// this shouldn't happen...
105+
return false;
106+
}
107+
108+
const isShadowedValue =
109+
'isValueVariable' in shadowed ? shadowed.isValueVariable : true;
110+
if (!isShadowedValue) {
85111
return false;
86112
}
87113

88-
return variable.isValueVariable !== shadowed.isValueVariable;
114+
const id = variable.identifiers[0];
115+
return util.isFunctionType(id.parent);
89116
}
90117

91118
/**
@@ -117,12 +144,12 @@ export default util.createRule<Options, MessageIds>({
117144
}
118145

119146
/**
120-
* Checks if a variable of the class name in the class scope of ClassDeclaration.
147+
* Checks if a variable of the class name in the class scope of TSEnumDeclaration.
121148
*
122-
* ClassDeclaration creates two variables of its name into its outer scope and its class scope.
149+
* TSEnumDeclaration creates two variables of its name into its outer scope and its class scope.
123150
* So we should ignore the variable in the class scope.
124151
* @param variable The variable to check.
125-
* @returns Whether or not the variable of the class name in the class scope of ClassDeclaration.
152+
* @returns Whether or not the variable of the class name in the class scope of TSEnumDeclaration.
126153
*/
127154
function isDuplicatedEnumNameVariable(
128155
variable: TSESLint.Scope.Variable,
@@ -273,6 +300,11 @@ export default util.createRule<Options, MessageIds>({
273300
continue;
274301
}
275302

303+
// ignore function type parameter name shadowing if configured
304+
if (isFunctionTypeParameterNameValueShadow(variable, shadowed)) {
305+
continue;
306+
}
307+
276308
const isESLintGlobal = 'writeable' in shadowed;
277309
if (
278310
(shadowed.identifiers.length > 0 ||

packages/eslint-plugin/tests/rules/no-shadow.test.ts

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,58 @@ const x = 1;
5656
type x = string;
5757
}
5858
`,
59+
{
60+
code: `
61+
type Foo = 1;
62+
`,
63+
options: [{ ignoreTypeValueShadow: true }],
64+
globals: {
65+
Foo: 'writable',
66+
},
67+
},
68+
{
69+
code: `
70+
type Foo = 1;
71+
`,
72+
options: [
73+
{
74+
ignoreTypeValueShadow: false,
75+
builtinGlobals: false,
76+
},
77+
],
78+
globals: {
79+
Foo: 'writable',
80+
},
81+
},
82+
// https://github.com/typescript-eslint/typescript-eslint/issues/2360
83+
`
84+
enum Direction {
85+
left = 'left',
86+
right = 'right',
87+
}
88+
`,
89+
// https://github.com/typescript-eslint/typescript-eslint/issues/2447
90+
{
91+
code: `
92+
const test = 1;
93+
type Fn = (test: string) => typeof test;
94+
`,
95+
options: [{ ignoreFunctionTypeParameterNameValueShadow: true }],
96+
},
97+
{
98+
code: `
99+
type Fn = (Foo: string) => typeof Foo;
100+
`,
101+
options: [
102+
{
103+
ignoreFunctionTypeParameterNameValueShadow: true,
104+
builtinGlobals: false,
105+
},
106+
],
107+
globals: {
108+
Foo: 'writable',
109+
},
110+
},
59111
],
60112
invalid: [
61113
{
@@ -109,6 +161,69 @@ const x = 1;
109161
},
110162
],
111163
},
164+
{
165+
code: `
166+
type Foo = 1;
167+
`,
168+
options: [
169+
{
170+
ignoreTypeValueShadow: false,
171+
builtinGlobals: true,
172+
},
173+
],
174+
globals: {
175+
Foo: 'writable',
176+
},
177+
errors: [
178+
{
179+
messageId: 'noShadow',
180+
data: {
181+
name: 'Foo',
182+
},
183+
line: 2,
184+
},
185+
],
186+
},
187+
// https://github.com/typescript-eslint/typescript-eslint/issues/2447
188+
{
189+
code: `
190+
const test = 1;
191+
type Fn = (test: string) => typeof test;
192+
`,
193+
options: [{ ignoreFunctionTypeParameterNameValueShadow: false }],
194+
errors: [
195+
{
196+
messageId: 'noShadow',
197+
data: {
198+
name: 'test',
199+
},
200+
line: 3,
201+
},
202+
],
203+
},
204+
{
205+
code: `
206+
type Fn = (Foo: string) => typeof Foo;
207+
`,
208+
options: [
209+
{
210+
ignoreFunctionTypeParameterNameValueShadow: false,
211+
builtinGlobals: true,
212+
},
213+
],
214+
globals: {
215+
Foo: 'writable',
216+
},
217+
errors: [
218+
{
219+
messageId: 'noShadow',
220+
data: {
221+
name: 'Foo',
222+
},
223+
line: 2,
224+
},
225+
],
226+
},
112227
],
113228
});
114229

@@ -431,13 +546,6 @@ function foo(cb) {
431546
`,
432547
options: [{ allow: ['cb'] }],
433548
},
434-
// https://github.com/typescript-eslint/typescript-eslint/issues/2360
435-
`
436-
enum Direction {
437-
left = 'left',
438-
right = 'right',
439-
}
440-
`,
441549
],
442550
invalid: [
443551
{

0 commit comments

Comments
 (0)
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