Skip to content

Commit 63135f7

Browse files
authored
fix(eslint-plugin): [no-shadow] ignore ordering of type declarations (#10593)
* initial implementation * add relevant tests * add docs * snapshots * match original implementation
1 parent 6dda0a4 commit 63135f7

File tree

5 files changed

+551
-14
lines changed

5 files changed

+551
-14
lines changed

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

+34
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,52 @@ It adds support for TypeScript's `this` parameters and global augmentation, and
1717
This rule adds the following options:
1818

1919
```ts
20+
type AdditionalHoistOptionEntries = 'types' | 'functions-and-types';
21+
22+
type HoistOptionEntries =
23+
| BaseNoShadowHoistOptionEntries
24+
| AdditionalHoistOptionEntries;
25+
2026
interface Options extends BaseNoShadowOptions {
27+
hoist?: HoistOptionEntries;
2128
ignoreTypeValueShadow?: boolean;
2229
ignoreFunctionTypeParameterNameValueShadow?: boolean;
2330
}
2431

2532
const defaultOptions: Options = {
2633
...baseNoShadowDefaultOptions,
34+
hoist: 'functions-and-types',
2735
ignoreTypeValueShadow: true,
2836
ignoreFunctionTypeParameterNameValueShadow: true,
2937
};
3038
```
3139

40+
### hoist: `types`
41+
42+
Examples of incorrect code for the `{ "hoist": "types" }` option:
43+
44+
```ts option='{ "hoist": "types" }' showPlaygroundButton
45+
type Bar<Foo> = 1;
46+
type Foo = 1;
47+
```
48+
49+
### hoist: `functions-and-types`
50+
51+
Examples of incorrect code for the `{ "hoist": "functions-and-types" }` option:
52+
53+
```ts option='{ "hoist": "functions-and-types" }' showPlaygroundButton
54+
// types
55+
type Bar<Foo> = 1;
56+
type Foo = 1;
57+
58+
// functions
59+
if (true) {
60+
let b = 6;
61+
}
62+
63+
function b() {}
64+
```
65+
3266
### `ignoreTypeValueShadow`
3367

3468
{/* insert option description */}

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

+34-12
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export type Options = [
1111
{
1212
allow?: string[];
1313
builtinGlobals?: boolean;
14-
hoist?: 'all' | 'functions' | 'never';
14+
hoist?: 'all' | 'functions' | 'functions-and-types' | 'never' | 'types';
1515
ignoreFunctionTypeParameterNameValueShadow?: boolean;
1616
ignoreOnInitialization?: boolean;
1717
ignoreTypeValueShadow?: boolean;
@@ -28,6 +28,13 @@ const allowedFunctionVariableDefTypes = new Set([
2828
AST_NODE_TYPES.TSConstructorType,
2929
]);
3030

31+
const functionsHoistedNodes = new Set([AST_NODE_TYPES.FunctionDeclaration]);
32+
33+
const typesHoistedNodes = new Set([
34+
AST_NODE_TYPES.TSInterfaceDeclaration,
35+
AST_NODE_TYPES.TSTypeAliasDeclaration,
36+
]);
37+
3138
export default createRule<Options, MessageIds>({
3239
name: 'no-shadow',
3340
meta: {
@@ -63,7 +70,7 @@ export default createRule<Options, MessageIds>({
6370
type: 'string',
6471
description:
6572
'Whether to report shadowing before outer functions or variables are defined.',
66-
enum: ['all', 'functions', 'never'],
73+
enum: ['all', 'functions', 'functions-and-types', 'never', 'types'],
6774
},
6875
ignoreFunctionTypeParameterNameValueShadow: {
6976
type: 'boolean',
@@ -88,7 +95,7 @@ export default createRule<Options, MessageIds>({
8895
{
8996
allow: [],
9097
builtinGlobals: false,
91-
hoist: 'functions',
98+
hoist: 'functions-and-types',
9299
ignoreFunctionTypeParameterNameValueShadow: true,
93100
ignoreOnInitialization: false,
94101
ignoreTypeValueShadow: true,
@@ -513,15 +520,30 @@ export default createRule<Options, MessageIds>({
513520
const inner = getNameRange(variable);
514521
const outer = getNameRange(scopeVar);
515522

516-
return !!(
517-
inner &&
518-
outer &&
519-
inner[1] < outer[0] &&
520-
// Excepts FunctionDeclaration if is {"hoist":"function"}.
521-
(options.hoist !== 'functions' ||
522-
!outerDef ||
523-
outerDef.node.type !== AST_NODE_TYPES.FunctionDeclaration)
524-
);
523+
if (!inner || !outer || inner[1] >= outer[0]) {
524+
return false;
525+
}
526+
527+
if (!outerDef) {
528+
return true;
529+
}
530+
531+
if (options.hoist === 'functions') {
532+
return !functionsHoistedNodes.has(outerDef.node.type);
533+
}
534+
535+
if (options.hoist === 'types') {
536+
return !typesHoistedNodes.has(outerDef.node.type);
537+
}
538+
539+
if (options.hoist === 'functions-and-types') {
540+
return (
541+
!functionsHoistedNodes.has(outerDef.node.type) &&
542+
!typesHoistedNodes.has(outerDef.node.type)
543+
);
544+
}
545+
546+
return true;
525547
}
526548

527549
/**

packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-shadow.shot

+28-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)