@flint.fyi/ts 0.14.4 → 0.14.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/lib/createTypeScriptFileFromProgram.js +4 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/plugin.d.ts +17 -1
- package/lib/plugin.js +8 -0
- package/lib/rules/chainedAssignments.js +2 -1
- package/lib/rules/chainedAssignments.test.js +24 -0
- package/lib/rules/debuggerStatements.test.js +4 -4
- package/lib/rules/emptyBlocks.d.ts +6 -0
- package/lib/rules/emptyBlocks.js +66 -0
- package/lib/rules/emptyBlocks.test.d.ts +1 -0
- package/lib/rules/emptyBlocks.test.js +135 -0
- package/lib/rules/octalNumbers.test.js +2 -2
- package/lib/rules/shadowedRestrictedNames.d.ts +6 -0
- package/lib/rules/shadowedRestrictedNames.js +95 -0
- package/lib/rules/shadowedRestrictedNames.test.d.ts +1 -0
- package/lib/rules/shadowedRestrictedNames.test.js +147 -0
- package/lib/rules/symbolDescriptions.test.js +4 -4
- package/lib/rules/unicodeBOMs.test.js +1 -0
- package/lib/rules/unnecessaryBlocks.d.ts +6 -0
- package/lib/rules/unnecessaryBlocks.js +75 -0
- package/lib/rules/unnecessaryBlocks.test.d.ts +1 -0
- package/lib/rules/unnecessaryBlocks.test.js +147 -0
- package/lib/rules/unnecessaryConcatenation.d.ts +6 -0
- package/lib/rules/unnecessaryConcatenation.js +38 -0
- package/lib/rules/unnecessaryConcatenation.test.d.ts +1 -0
- package/lib/rules/unnecessaryConcatenation.test.js +87 -0
- package/lib/rules/voidOperator.test.js +2 -2
- package/lib/utils/getDeclarationsIfGlobal.d.ts +2 -0
- package/lib/utils/getDeclarationsIfGlobal.js +7 -0
- package/lib/utils/isGlobalDeclaration.js +2 -3
- package/lib/utils/isGlobalDeclarationOfName.d.ts +1 -1
- package/lib/utils/isGlobalDeclarationOfName.js +4 -0
- package/package.json +3 -3
- package/src/createTypeScriptFileFromProgram.ts +5 -1
- package/src/index.ts +3 -0
- package/src/plugin.ts +8 -0
- package/src/rules/chainedAssignments.test.ts +24 -0
- package/src/rules/chainedAssignments.ts +4 -1
- package/src/rules/debuggerStatements.test.ts +4 -4
- package/src/rules/emptyBlocks.test.ts +136 -0
- package/src/rules/emptyBlocks.ts +73 -0
- package/src/rules/octalNumbers.test.ts +2 -2
- package/src/rules/shadowedRestrictedNames.test.ts +148 -0
- package/src/rules/shadowedRestrictedNames.ts +105 -0
- package/src/rules/symbolDescriptions.test.ts +4 -4
- package/src/rules/unicodeBOMs.test.ts +1 -0
- package/src/rules/unnecessaryBlocks.test.ts +148 -0
- package/src/rules/unnecessaryBlocks.ts +89 -0
- package/src/rules/unnecessaryConcatenation.test.ts +88 -0
- package/src/rules/unnecessaryConcatenation.ts +44 -0
- package/src/rules/voidOperator.test.ts +2 -2
- package/src/utils/getDeclarationsIfGlobal.ts +14 -0
- package/src/utils/isGlobalDeclaration.ts +3 -5
- package/src/utils/isGlobalDeclarationOfName.ts +6 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/plugin.ts
CHANGED
|
@@ -13,6 +13,7 @@ import debuggerStatements from "./rules/debuggerStatements.js";
|
|
|
13
13
|
import defaultCaseLast from "./rules/defaultCaseLast.js";
|
|
14
14
|
import duplicateArguments from "./rules/duplicateArguments.js";
|
|
15
15
|
import elseIfDuplicates from "./rules/elseIfDuplicates.js";
|
|
16
|
+
import emptyBlocks from "./rules/emptyBlocks.js";
|
|
16
17
|
import emptyDestructures from "./rules/emptyDestructures.js";
|
|
17
18
|
import emptyStaticBlocks from "./rules/emptyStaticBlocks.js";
|
|
18
19
|
import exceptionAssignments from "./rules/exceptionAssignments.js";
|
|
@@ -36,13 +37,16 @@ import returnAssignments from "./rules/returnAssignments.js";
|
|
|
36
37
|
import selfAssignments from "./rules/selfAssignments.js";
|
|
37
38
|
import selfComparisons from "./rules/selfComparisons.js";
|
|
38
39
|
import sequences from "./rules/sequences.js";
|
|
40
|
+
import shadowedRestrictedNames from "./rules/shadowedRestrictedNames.js";
|
|
39
41
|
import sparseArrays from "./rules/sparseArrays.js";
|
|
40
42
|
import symbolDescriptions from "./rules/symbolDescriptions.js";
|
|
41
43
|
import typeofComparisons from "./rules/typeofComparisons.js";
|
|
42
44
|
import unassignedVariables from "./rules/unassignedVariables.js";
|
|
43
45
|
import undefinedVariables from "./rules/undefinedVariables.js";
|
|
44
46
|
import unicodeBOMs from "./rules/unicodeBOMs.js";
|
|
47
|
+
import unnecessaryBlocks from "./rules/unnecessaryBlocks.js";
|
|
45
48
|
import unnecessaryCatches from "./rules/unnecessaryCatches.js";
|
|
49
|
+
import unnecessaryConcatenation from "./rules/unnecessaryConcatenation.js";
|
|
46
50
|
import unsafeNegations from "./rules/unsafeNegations.js";
|
|
47
51
|
import variableDeletions from "./rules/variableDeletions.js";
|
|
48
52
|
import voidOperator from "./rules/voidOperator.js";
|
|
@@ -66,6 +70,7 @@ export const ts = createPlugin({
|
|
|
66
70
|
defaultCaseLast,
|
|
67
71
|
duplicateArguments,
|
|
68
72
|
elseIfDuplicates,
|
|
73
|
+
emptyBlocks,
|
|
69
74
|
emptyDestructures,
|
|
70
75
|
emptyStaticBlocks,
|
|
71
76
|
exceptionAssignments,
|
|
@@ -89,13 +94,16 @@ export const ts = createPlugin({
|
|
|
89
94
|
selfAssignments,
|
|
90
95
|
selfComparisons,
|
|
91
96
|
sequences,
|
|
97
|
+
shadowedRestrictedNames,
|
|
92
98
|
sparseArrays,
|
|
93
99
|
symbolDescriptions,
|
|
94
100
|
typeofComparisons,
|
|
95
101
|
unassignedVariables,
|
|
96
102
|
undefinedVariables,
|
|
97
103
|
unicodeBOMs,
|
|
104
|
+
unnecessaryBlocks,
|
|
98
105
|
unnecessaryCatches,
|
|
106
|
+
unnecessaryConcatenation,
|
|
99
107
|
unsafeNegations,
|
|
100
108
|
variableDeletions,
|
|
101
109
|
voidOperator,
|
|
@@ -234,6 +234,30 @@ class MyClass {
|
|
|
234
234
|
second = 0;
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
+
`,
|
|
238
|
+
`
|
|
239
|
+
let result = "";
|
|
240
|
+
result += "a" + "b";
|
|
241
|
+
`,
|
|
242
|
+
`
|
|
243
|
+
let result = 0;
|
|
244
|
+
result += 1 + 2;
|
|
245
|
+
`,
|
|
246
|
+
`
|
|
247
|
+
let result = 0;
|
|
248
|
+
result += 1 * 2;
|
|
249
|
+
`,
|
|
250
|
+
`
|
|
251
|
+
let result = 0;
|
|
252
|
+
result += 1 - 2;
|
|
253
|
+
`,
|
|
254
|
+
`
|
|
255
|
+
let result = 0;
|
|
256
|
+
result += 1 / 2;
|
|
257
|
+
`,
|
|
258
|
+
`
|
|
259
|
+
let result = 0;
|
|
260
|
+
result += 1 ** 2;
|
|
237
261
|
`,
|
|
238
262
|
],
|
|
239
263
|
});
|
|
@@ -35,7 +35,10 @@ export default typescriptLanguage.createRule({
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const rightSide = unwrapParenthesizedExpression(node.right);
|
|
38
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
!ts.isBinaryExpression(rightSide) ||
|
|
40
|
+
!tsutils.isAssignmentKind(rightSide.operatorToken.kind)
|
|
41
|
+
) {
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -30,8 +30,8 @@ function test() {
|
|
|
30
30
|
snapshot: `
|
|
31
31
|
function test() {
|
|
32
32
|
debugger;
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
~~~~~~~~~
|
|
34
|
+
Debugger statements should not be used in production code.
|
|
35
35
|
}
|
|
36
36
|
`,
|
|
37
37
|
suggestions: [
|
|
@@ -54,8 +54,8 @@ if (condition) {
|
|
|
54
54
|
snapshot: `
|
|
55
55
|
if (condition) {
|
|
56
56
|
debugger;
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
~~~~~~~~~
|
|
58
|
+
Debugger statements should not be used in production code.
|
|
59
59
|
}
|
|
60
60
|
`,
|
|
61
61
|
suggestions: [
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import rule from "./emptyBlocks.js";
|
|
2
|
+
import { ruleTester } from "./ruleTester.js";
|
|
3
|
+
|
|
4
|
+
ruleTester.describe(rule, {
|
|
5
|
+
invalid: [
|
|
6
|
+
{
|
|
7
|
+
code: `
|
|
8
|
+
if (condition) {}
|
|
9
|
+
`,
|
|
10
|
+
snapshot: `
|
|
11
|
+
if (condition) {}
|
|
12
|
+
~~
|
|
13
|
+
Empty block statements should be removed or contain code.
|
|
14
|
+
`,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: `
|
|
18
|
+
if (condition) {
|
|
19
|
+
} else {}
|
|
20
|
+
`,
|
|
21
|
+
snapshot: `
|
|
22
|
+
if (condition) {
|
|
23
|
+
~
|
|
24
|
+
Empty block statements should be removed or contain code.
|
|
25
|
+
} else {}
|
|
26
|
+
~
|
|
27
|
+
~~
|
|
28
|
+
Empty block statements should be removed or contain code.
|
|
29
|
+
`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
code: `
|
|
33
|
+
while (condition) {}
|
|
34
|
+
`,
|
|
35
|
+
snapshot: `
|
|
36
|
+
while (condition) {}
|
|
37
|
+
~~
|
|
38
|
+
Empty block statements should be removed or contain code.
|
|
39
|
+
`,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
code: `
|
|
43
|
+
for (let i = 0; i < 10; i++) {}
|
|
44
|
+
`,
|
|
45
|
+
snapshot: `
|
|
46
|
+
for (let i = 0; i < 10; i++) {}
|
|
47
|
+
~~
|
|
48
|
+
Empty block statements should be removed or contain code.
|
|
49
|
+
`,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
code: `
|
|
53
|
+
switch (value) {}
|
|
54
|
+
`,
|
|
55
|
+
snapshot: `
|
|
56
|
+
switch (value) {}
|
|
57
|
+
~~
|
|
58
|
+
Empty block statements should be removed or contain code.
|
|
59
|
+
`,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
code: `
|
|
63
|
+
do {} while (condition);
|
|
64
|
+
`,
|
|
65
|
+
snapshot: `
|
|
66
|
+
do {} while (condition);
|
|
67
|
+
~~
|
|
68
|
+
Empty block statements should be removed or contain code.
|
|
69
|
+
`,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
code: `
|
|
73
|
+
if (x) {
|
|
74
|
+
doSomething();
|
|
75
|
+
} else if (y) {}
|
|
76
|
+
`,
|
|
77
|
+
snapshot: `
|
|
78
|
+
if (x) {
|
|
79
|
+
doSomething();
|
|
80
|
+
} else if (y) {}
|
|
81
|
+
~~
|
|
82
|
+
Empty block statements should be removed or contain code.
|
|
83
|
+
`,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
code: `
|
|
87
|
+
try {
|
|
88
|
+
doSomething();
|
|
89
|
+
} finally {}
|
|
90
|
+
`,
|
|
91
|
+
snapshot: `
|
|
92
|
+
try {
|
|
93
|
+
doSomething();
|
|
94
|
+
} finally {}
|
|
95
|
+
~~
|
|
96
|
+
Empty block statements should be removed or contain code.
|
|
97
|
+
`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
valid: [
|
|
101
|
+
`if (condition) { doSomething(); }`,
|
|
102
|
+
`while (condition) { doSomething(); }`,
|
|
103
|
+
`for (let i = 0; i < 10; i++) { doSomething(); }`,
|
|
104
|
+
`switch (value) { case 1: break; }`,
|
|
105
|
+
`do { doSomething(); } while (condition);`,
|
|
106
|
+
`function test() {}`,
|
|
107
|
+
`const fn = function() {};`,
|
|
108
|
+
`const arrow = () => {};`,
|
|
109
|
+
`class MyClass { method() {} }`,
|
|
110
|
+
`class MyClass { constructor() {} }`,
|
|
111
|
+
`class MyClass { get value() {} }`,
|
|
112
|
+
`class MyClass { set value(v) {} }`,
|
|
113
|
+
`
|
|
114
|
+
if (condition) {
|
|
115
|
+
// Intentionally empty
|
|
116
|
+
}
|
|
117
|
+
`,
|
|
118
|
+
`
|
|
119
|
+
while (condition) {
|
|
120
|
+
/* Do nothing */
|
|
121
|
+
}
|
|
122
|
+
`,
|
|
123
|
+
`
|
|
124
|
+
try {
|
|
125
|
+
doSomething();
|
|
126
|
+
} catch (error) {}
|
|
127
|
+
`,
|
|
128
|
+
`
|
|
129
|
+
try {
|
|
130
|
+
doSomething();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// Ignore errors
|
|
133
|
+
}
|
|
134
|
+
`,
|
|
135
|
+
],
|
|
136
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { getTSNodeRange } from "../getTSNodeRange.js";
|
|
4
|
+
import { typescriptLanguage } from "../language.js";
|
|
5
|
+
|
|
6
|
+
const allowedParents = new Set([
|
|
7
|
+
ts.SyntaxKind.ArrowFunction,
|
|
8
|
+
ts.SyntaxKind.CatchClause,
|
|
9
|
+
ts.SyntaxKind.Constructor,
|
|
10
|
+
ts.SyntaxKind.FunctionDeclaration,
|
|
11
|
+
ts.SyntaxKind.FunctionExpression,
|
|
12
|
+
ts.SyntaxKind.GetAccessor,
|
|
13
|
+
ts.SyntaxKind.MethodDeclaration,
|
|
14
|
+
ts.SyntaxKind.SetAccessor,
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export default typescriptLanguage.createRule({
|
|
18
|
+
about: {
|
|
19
|
+
description: "Reports empty block statements that should contain code.",
|
|
20
|
+
id: "emptyBlocks",
|
|
21
|
+
preset: "stylistic",
|
|
22
|
+
},
|
|
23
|
+
messages: {
|
|
24
|
+
emptyBlock: {
|
|
25
|
+
primary: "Empty block statements should be removed or contain code.",
|
|
26
|
+
secondary: [
|
|
27
|
+
"Empty blocks can indicate incomplete code or areas where logic was removed but the block structure was left behind.",
|
|
28
|
+
"They can also reduce code readability by cluttering the codebase with unnecessary braces.",
|
|
29
|
+
],
|
|
30
|
+
suggestions: [
|
|
31
|
+
"Add a comment explaining why the block is intentionally empty, or remove the empty block entirely.",
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
setup(context) {
|
|
36
|
+
function hasComments(block: ts.Block): boolean {
|
|
37
|
+
const fullText = context.sourceFile.getFullText();
|
|
38
|
+
|
|
39
|
+
const openBrace = block.getStart(context.sourceFile);
|
|
40
|
+
const closeBrace = block.getEnd();
|
|
41
|
+
const innerText = fullText.substring(openBrace + 1, closeBrace - 1);
|
|
42
|
+
|
|
43
|
+
// Check if there are any non-whitespace characters (which would be comments)
|
|
44
|
+
// since we already know there are no statements
|
|
45
|
+
return /\S+/.test(innerText.trim());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isEmptyBlock(block: ts.Block): boolean {
|
|
49
|
+
return block.statements.length === 0 && !hasComments(block);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
visitors: {
|
|
54
|
+
Block: (node) => {
|
|
55
|
+
if (!allowedParents.has(node.parent.kind) && isEmptyBlock(node)) {
|
|
56
|
+
context.report({
|
|
57
|
+
message: "emptyBlock",
|
|
58
|
+
range: getTSNodeRange(node, context.sourceFile),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
CaseBlock: (node) => {
|
|
63
|
+
if (node.clauses.length === 0) {
|
|
64
|
+
context.report({
|
|
65
|
+
message: "emptyBlock",
|
|
66
|
+
range: getTSNodeRange(node, context.sourceFile),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
@@ -160,8 +160,8 @@ function calculate() {
|
|
|
160
160
|
snapshot: `
|
|
161
161
|
function calculate() {
|
|
162
162
|
return 077;
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
~~~
|
|
164
|
+
This legacy octal numeric literal evaluates to 63. Did you mean that value, or to use a modern octal syntax such as 0o77?
|
|
165
165
|
}
|
|
166
166
|
`,
|
|
167
167
|
suggestions: [
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { ruleTester } from "./ruleTester.js";
|
|
2
|
+
import rule from "./shadowedRestrictedNames.js";
|
|
3
|
+
|
|
4
|
+
ruleTester.describe(rule, {
|
|
5
|
+
invalid: [
|
|
6
|
+
{
|
|
7
|
+
code: `
|
|
8
|
+
let undefined = 5;
|
|
9
|
+
`,
|
|
10
|
+
snapshot: `
|
|
11
|
+
let undefined = 5;
|
|
12
|
+
~~~~~~~~~
|
|
13
|
+
This variable misleadingly shadows the global \`undefined\`.
|
|
14
|
+
`,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: `
|
|
18
|
+
const NaN = 123;
|
|
19
|
+
`,
|
|
20
|
+
snapshot: `
|
|
21
|
+
const NaN = 123;
|
|
22
|
+
~~~
|
|
23
|
+
This variable misleadingly shadows the global \`NaN\`.
|
|
24
|
+
`,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
code: `
|
|
28
|
+
var Infinity = 100;
|
|
29
|
+
`,
|
|
30
|
+
snapshot: `
|
|
31
|
+
var Infinity = 100;
|
|
32
|
+
~~~~~~~~
|
|
33
|
+
This variable misleadingly shadows the global \`Infinity\`.
|
|
34
|
+
`,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
code: `
|
|
38
|
+
function test(arguments) {
|
|
39
|
+
return arguments.length;
|
|
40
|
+
}
|
|
41
|
+
`,
|
|
42
|
+
snapshot: `
|
|
43
|
+
function test(arguments) {
|
|
44
|
+
~~~~~~~~~
|
|
45
|
+
This variable misleadingly shadows the global \`arguments\`.
|
|
46
|
+
return arguments.length;
|
|
47
|
+
}
|
|
48
|
+
`,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
code: `
|
|
52
|
+
function eval() {
|
|
53
|
+
return 42;
|
|
54
|
+
}
|
|
55
|
+
`,
|
|
56
|
+
snapshot: `
|
|
57
|
+
function eval() {
|
|
58
|
+
~~~~
|
|
59
|
+
This variable misleadingly shadows the global \`eval\`.
|
|
60
|
+
return 42;
|
|
61
|
+
}
|
|
62
|
+
`,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
code: `
|
|
66
|
+
const arrowFunc = (undefined) => undefined;
|
|
67
|
+
`,
|
|
68
|
+
snapshot: `
|
|
69
|
+
const arrowFunc = (undefined) => undefined;
|
|
70
|
+
~~~~~~~~~
|
|
71
|
+
This variable misleadingly shadows the global \`undefined\`.
|
|
72
|
+
`,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
code: `
|
|
76
|
+
class NaN {
|
|
77
|
+
constructor() {}
|
|
78
|
+
}
|
|
79
|
+
`,
|
|
80
|
+
snapshot: `
|
|
81
|
+
class NaN {
|
|
82
|
+
~~~
|
|
83
|
+
This variable misleadingly shadows the global \`NaN\`.
|
|
84
|
+
constructor() {}
|
|
85
|
+
}
|
|
86
|
+
`,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
code: `
|
|
90
|
+
const obj = {
|
|
91
|
+
method(eval) {
|
|
92
|
+
return eval;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
`,
|
|
96
|
+
snapshot: `
|
|
97
|
+
const obj = {
|
|
98
|
+
method(eval) {
|
|
99
|
+
~~~~
|
|
100
|
+
This variable misleadingly shadows the global \`eval\`.
|
|
101
|
+
return eval;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
`,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
code: `
|
|
108
|
+
function test() {
|
|
109
|
+
const { undefined } = obj;
|
|
110
|
+
}
|
|
111
|
+
`,
|
|
112
|
+
snapshot: `
|
|
113
|
+
function test() {
|
|
114
|
+
const { undefined } = obj;
|
|
115
|
+
~~~~~~~~~
|
|
116
|
+
This variable misleadingly shadows the global \`undefined\`.
|
|
117
|
+
}
|
|
118
|
+
`,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
code: `
|
|
122
|
+
function test() {
|
|
123
|
+
const [NaN] = array;
|
|
124
|
+
}
|
|
125
|
+
`,
|
|
126
|
+
snapshot: `
|
|
127
|
+
function test() {
|
|
128
|
+
const [NaN] = array;
|
|
129
|
+
~~~
|
|
130
|
+
This variable misleadingly shadows the global \`NaN\`.
|
|
131
|
+
}
|
|
132
|
+
`,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
valid: [
|
|
136
|
+
`let value = undefined;`,
|
|
137
|
+
`const result = NaN;`,
|
|
138
|
+
`const max = Infinity;`,
|
|
139
|
+
`function test() { return arguments; }`,
|
|
140
|
+
`const code = eval("1 + 1");`,
|
|
141
|
+
`let myValue = 5;`,
|
|
142
|
+
`function myFunction() {}`,
|
|
143
|
+
`class MyClass {}`,
|
|
144
|
+
`const obj = { undefined: 5 };`,
|
|
145
|
+
`const key = "undefined";`,
|
|
146
|
+
`obj.undefined = 5;`,
|
|
147
|
+
],
|
|
148
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { getTSNodeRange } from "../getTSNodeRange.js";
|
|
4
|
+
import { typescriptLanguage } from "../language.js";
|
|
5
|
+
|
|
6
|
+
const restrictedNames = new Set([
|
|
7
|
+
"arguments",
|
|
8
|
+
"eval",
|
|
9
|
+
"Infinity",
|
|
10
|
+
"NaN",
|
|
11
|
+
"undefined",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export default typescriptLanguage.createRule({
|
|
15
|
+
about: {
|
|
16
|
+
description:
|
|
17
|
+
"Reports variable declarations that shadow JavaScript's restricted names.",
|
|
18
|
+
id: "shadowedRestrictedNames",
|
|
19
|
+
preset: "untyped",
|
|
20
|
+
},
|
|
21
|
+
messages: {
|
|
22
|
+
shadowedRestrictedName: {
|
|
23
|
+
primary: "This variable misleadingly shadows the global `{{ name }}`.",
|
|
24
|
+
secondary: [
|
|
25
|
+
"JavaScript has certain built-in global identifiers that are considered restricted because shadowing them can lead to confusing or erroneous code.",
|
|
26
|
+
"When you declare a variable with a restricted name, it shadows the global identifier and can cause unexpected behavior.",
|
|
27
|
+
],
|
|
28
|
+
suggestions: [
|
|
29
|
+
"Use a different variable name that doesn't shadow a restricted name.",
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
setup(context) {
|
|
34
|
+
function checkIdentifier(node: ts.Identifier): void {
|
|
35
|
+
if (restrictedNames.has(node.text)) {
|
|
36
|
+
context.report({
|
|
37
|
+
data: {
|
|
38
|
+
name: node.text,
|
|
39
|
+
},
|
|
40
|
+
message: "shadowedRestrictedName",
|
|
41
|
+
range: getTSNodeRange(node, context.sourceFile),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function checkBindingName(name: ts.BindingName): void {
|
|
47
|
+
if (ts.isIdentifier(name)) {
|
|
48
|
+
checkIdentifier(name);
|
|
49
|
+
} else if (
|
|
50
|
+
ts.isObjectBindingPattern(name) ||
|
|
51
|
+
ts.isArrayBindingPattern(name)
|
|
52
|
+
) {
|
|
53
|
+
for (const element of name.elements) {
|
|
54
|
+
if (ts.isBindingElement(element)) {
|
|
55
|
+
checkBindingName(element.name);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function checkParameters(
|
|
62
|
+
parameters: ts.NodeArray<ts.ParameterDeclaration>,
|
|
63
|
+
): void {
|
|
64
|
+
for (const parameter of parameters) {
|
|
65
|
+
checkBindingName(parameter.name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
visitors: {
|
|
71
|
+
ArrowFunction: (node) => {
|
|
72
|
+
checkParameters(node.parameters);
|
|
73
|
+
},
|
|
74
|
+
ClassDeclaration: (node) => {
|
|
75
|
+
if (node.name) {
|
|
76
|
+
checkIdentifier(node.name);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
ClassExpression: (node) => {
|
|
80
|
+
if (node.name) {
|
|
81
|
+
checkIdentifier(node.name);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
FunctionDeclaration: (node) => {
|
|
85
|
+
if (node.name) {
|
|
86
|
+
checkIdentifier(node.name);
|
|
87
|
+
}
|
|
88
|
+
checkParameters(node.parameters);
|
|
89
|
+
},
|
|
90
|
+
FunctionExpression: (node) => {
|
|
91
|
+
if (node.name) {
|
|
92
|
+
checkIdentifier(node.name);
|
|
93
|
+
}
|
|
94
|
+
checkParameters(node.parameters);
|
|
95
|
+
},
|
|
96
|
+
MethodDeclaration: (node) => {
|
|
97
|
+
checkParameters(node.parameters);
|
|
98
|
+
},
|
|
99
|
+
VariableDeclaration: (node) => {
|
|
100
|
+
checkBindingName(node.name);
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
});
|
|
@@ -44,8 +44,8 @@ function createSymbol() {
|
|
|
44
44
|
snapshot: `
|
|
45
45
|
function createSymbol() {
|
|
46
46
|
return Symbol();
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
~~~~~~~~
|
|
48
|
+
Symbols without descriptions are more difficult to debug or reason about.
|
|
49
49
|
}
|
|
50
50
|
`,
|
|
51
51
|
},
|
|
@@ -58,8 +58,8 @@ const obj = {
|
|
|
58
58
|
snapshot: `
|
|
59
59
|
const obj = {
|
|
60
60
|
key: Symbol(),
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
~~~~~~~~
|
|
62
|
+
Symbols without descriptions are more difficult to debug or reason about.
|
|
63
63
|
};
|
|
64
64
|
`,
|
|
65
65
|
},
|