@flint.fyi/ts 0.14.3 → 0.14.4
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 +10 -0
- package/lib/createTypeScriptFileFromProgram.js +1 -0
- package/lib/language.d.ts +1 -0
- package/lib/plugin.d.ts +44 -0
- package/lib/plugin.js +22 -0
- package/lib/rules/anyReturns.d.ts +6 -0
- package/lib/rules/anyReturns.js +187 -0
- package/lib/rules/anyReturns.test.d.ts +1 -0
- package/lib/rules/anyReturns.test.js +647 -0
- package/lib/rules/caseDuplicates.d.ts +6 -0
- package/lib/rules/caseDuplicates.js +49 -0
- package/lib/rules/caseDuplicates.test.d.ts +1 -0
- package/lib/rules/caseDuplicates.test.js +307 -0
- package/lib/rules/elseIfDuplicates.d.ts +6 -0
- package/lib/rules/elseIfDuplicates.js +53 -0
- package/lib/rules/elseIfDuplicates.test.d.ts +1 -0
- package/lib/rules/elseIfDuplicates.test.js +176 -0
- package/lib/rules/forDirections.d.ts +6 -0
- package/lib/rules/forDirections.js +124 -0
- package/lib/rules/forDirections.test.d.ts +1 -0
- package/lib/rules/forDirections.test.js +99 -0
- package/lib/rules/functionNewCalls.d.ts +6 -0
- package/lib/rules/functionNewCalls.js +60 -0
- package/lib/rules/functionNewCalls.test.d.ts +1 -0
- package/lib/rules/functionNewCalls.test.js +115 -0
- package/lib/rules/negativeZeroComparisons.js +4 -22
- package/lib/rules/nonOctalDecimalEscapes.d.ts +6 -0
- package/lib/rules/nonOctalDecimalEscapes.js +57 -0
- package/lib/rules/nonOctalDecimalEscapes.test.d.ts +1 -0
- package/lib/rules/nonOctalDecimalEscapes.test.js +69 -0
- package/lib/rules/numericLiteralParsing.d.ts +6 -0
- package/lib/rules/numericLiteralParsing.js +97 -0
- package/lib/rules/numericLiteralParsing.test.d.ts +1 -0
- package/lib/rules/numericLiteralParsing.test.js +99 -0
- package/lib/rules/selfAssignments.d.ts +6 -0
- package/lib/rules/selfAssignments.js +44 -0
- package/lib/rules/selfAssignments.test.d.ts +1 -0
- package/lib/rules/selfAssignments.test.js +66 -0
- package/lib/rules/selfComparisons.d.ts +6 -0
- package/lib/rules/selfComparisons.js +41 -0
- package/lib/rules/selfComparisons.test.d.ts +1 -0
- package/lib/rules/selfComparisons.test.js +297 -0
- package/lib/rules/sequences.d.ts +6 -0
- package/lib/rules/sequences.js +34 -0
- package/lib/rules/sequences.test.d.ts +1 -0
- package/lib/rules/sequences.test.js +48 -0
- package/lib/rules/typeofComparisons.d.ts +6 -0
- package/lib/rules/typeofComparisons.js +78 -0
- package/lib/rules/typeofComparisons.test.d.ts +1 -0
- package/lib/rules/typeofComparisons.test.js +85 -0
- package/lib/rules/utils/discriminateAnyType.d.ts +12 -0
- package/lib/rules/utils/discriminateAnyType.js +41 -0
- package/lib/rules/utils/getThisExpression.d.ts +2 -0
- package/lib/rules/utils/getThisExpression.js +23 -0
- package/lib/rules/utils/isUnsafeAssignment.d.ts +13 -0
- package/lib/rules/utils/isUnsafeAssignment.js +76 -0
- package/lib/rules/utils/operators.d.ts +4 -0
- package/lib/rules/utils/operators.js +36 -0
- package/lib/utils/declarationIncludesGlobal.d.ts +2 -0
- package/lib/utils/declarationIncludesGlobal.js +5 -0
- package/lib/utils/declarationsIncludeGlobal.js +2 -5
- package/lib/utils/hasSameTokens.d.ts +2 -0
- package/lib/utils/hasSameTokens.js +30 -0
- package/lib/utils/isGlobalDeclarationOfName.d.ts +5 -0
- package/lib/utils/isGlobalDeclarationOfName.js +31 -0
- package/package.json +1 -1
- package/src/createTypeScriptFileFromProgram.ts +1 -0
- package/src/language.ts +1 -0
- package/src/plugin.ts +22 -0
- package/src/rules/anyReturns.test.ts +649 -0
- package/src/rules/anyReturns.ts +263 -0
- package/src/rules/caseDuplicates.test.ts +308 -0
- package/src/rules/caseDuplicates.ts +57 -0
- package/src/rules/elseIfDuplicates.test.ts +177 -0
- package/src/rules/elseIfDuplicates.ts +69 -0
- package/src/rules/forDirections.test.ts +100 -0
- package/src/rules/forDirections.ts +163 -0
- package/src/rules/functionNewCalls.test.ts +116 -0
- package/src/rules/functionNewCalls.ts +75 -0
- package/src/rules/negativeZeroComparisons.ts +8 -27
- package/src/rules/nonOctalDecimalEscapes.test.ts +70 -0
- package/src/rules/nonOctalDecimalEscapes.ts +70 -0
- package/src/rules/numericLiteralParsing.test.ts +100 -0
- package/src/rules/numericLiteralParsing.ts +116 -0
- package/src/rules/selfAssignments.test.ts +67 -0
- package/src/rules/selfAssignments.ts +50 -0
- package/src/rules/selfComparisons.test.ts +298 -0
- package/src/rules/selfComparisons.ts +45 -0
- package/src/rules/sequences.test.ts +49 -0
- package/src/rules/sequences.ts +37 -0
- package/src/rules/typeofComparisons.test.ts +86 -0
- package/src/rules/typeofComparisons.ts +88 -0
- package/src/rules/utils/discriminateAnyType.ts +66 -0
- package/src/rules/utils/getThisExpression.ts +24 -0
- package/src/rules/utils/isUnsafeAssignment.ts +113 -0
- package/src/rules/utils/operators.ts +39 -0
- package/src/utils/declarationIncludesGlobal.ts +9 -0
- package/src/utils/declarationsIncludeGlobal.ts +3 -7
- package/src/utils/hasSameTokens.ts +45 -0
- package/src/utils/isGlobalDeclarationOfName.ts +51 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import rule from "./elseIfDuplicates.js";
|
|
2
|
+
import { ruleTester } from "./ruleTester.js";
|
|
3
|
+
|
|
4
|
+
ruleTester.describe(rule, {
|
|
5
|
+
invalid: [
|
|
6
|
+
{
|
|
7
|
+
code: `
|
|
8
|
+
if (isSomething(value)) {
|
|
9
|
+
doFirst();
|
|
10
|
+
} else if (isSomething(value)) {
|
|
11
|
+
doSecond();
|
|
12
|
+
}
|
|
13
|
+
`,
|
|
14
|
+
snapshot: `
|
|
15
|
+
if (isSomething(value)) {
|
|
16
|
+
doFirst();
|
|
17
|
+
} else if (isSomething(value)) {
|
|
18
|
+
~~~~~~~~~~~~~~~~~~
|
|
19
|
+
This condition is identical to a previous condition in the if-else-if chain.
|
|
20
|
+
doSecond();
|
|
21
|
+
}
|
|
22
|
+
`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
code: `
|
|
26
|
+
if (isSomething /* ... */ (value)) {
|
|
27
|
+
doFirst();
|
|
28
|
+
} else if (isSomething(value /* ... */)) {
|
|
29
|
+
doSecond();
|
|
30
|
+
}
|
|
31
|
+
`,
|
|
32
|
+
snapshot: `
|
|
33
|
+
if (isSomething /* ... */ (value)) {
|
|
34
|
+
doFirst();
|
|
35
|
+
} else if (isSomething(value /* ... */)) {
|
|
36
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
37
|
+
This condition is identical to a previous condition in the if-else-if chain.
|
|
38
|
+
doSecond();
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
code: `
|
|
44
|
+
if (first) {
|
|
45
|
+
handleFirst();
|
|
46
|
+
} else if (second) {
|
|
47
|
+
handleSecond();
|
|
48
|
+
} else if (third && fourth) {
|
|
49
|
+
handleThird();
|
|
50
|
+
} else if (third && fourth) {
|
|
51
|
+
handleFourth();
|
|
52
|
+
}
|
|
53
|
+
`,
|
|
54
|
+
snapshot: `
|
|
55
|
+
if (first) {
|
|
56
|
+
handleFirst();
|
|
57
|
+
} else if (second) {
|
|
58
|
+
handleSecond();
|
|
59
|
+
} else if (third && fourth) {
|
|
60
|
+
handleThird();
|
|
61
|
+
} else if (third && fourth) {
|
|
62
|
+
~~~~~~~~~~~~~~~
|
|
63
|
+
This condition is identical to a previous condition in the if-else-if chain.
|
|
64
|
+
handleFourth();
|
|
65
|
+
}
|
|
66
|
+
`,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
code: `
|
|
70
|
+
if (count === 1) {
|
|
71
|
+
handleOne();
|
|
72
|
+
} else if (count === 2) {
|
|
73
|
+
handleTwo();
|
|
74
|
+
} else if (count === 3) {
|
|
75
|
+
handleThree();
|
|
76
|
+
} else if (count === 2) {
|
|
77
|
+
handleFour();
|
|
78
|
+
} else if (count === 5) {
|
|
79
|
+
handleFive();
|
|
80
|
+
}
|
|
81
|
+
`,
|
|
82
|
+
snapshot: `
|
|
83
|
+
if (count === 1) {
|
|
84
|
+
handleOne();
|
|
85
|
+
} else if (count === 2) {
|
|
86
|
+
handleTwo();
|
|
87
|
+
} else if (count === 3) {
|
|
88
|
+
handleThree();
|
|
89
|
+
} else if (count === 2) {
|
|
90
|
+
~~~~~~~~~~~
|
|
91
|
+
This condition is identical to a previous condition in the if-else-if chain.
|
|
92
|
+
handleFour();
|
|
93
|
+
} else if (count === 5) {
|
|
94
|
+
handleFive();
|
|
95
|
+
}
|
|
96
|
+
`,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
code: `
|
|
100
|
+
if (value === "first") {
|
|
101
|
+
process();
|
|
102
|
+
} else if (value === "second") {
|
|
103
|
+
process();
|
|
104
|
+
} else if (value === "first") {
|
|
105
|
+
process();
|
|
106
|
+
}
|
|
107
|
+
`,
|
|
108
|
+
snapshot: `
|
|
109
|
+
if (value === "first") {
|
|
110
|
+
process();
|
|
111
|
+
} else if (value === "second") {
|
|
112
|
+
process();
|
|
113
|
+
} else if (value === "first") {
|
|
114
|
+
~~~~~~~~~~~~~~~~~
|
|
115
|
+
This condition is identical to a previous condition in the if-else-if chain.
|
|
116
|
+
process();
|
|
117
|
+
}
|
|
118
|
+
`,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
code: `
|
|
122
|
+
if (isValid && isActive) {
|
|
123
|
+
execute();
|
|
124
|
+
} else if (isPending) {
|
|
125
|
+
wait();
|
|
126
|
+
} else if (isValid && isActive) {
|
|
127
|
+
execute();
|
|
128
|
+
}
|
|
129
|
+
`,
|
|
130
|
+
snapshot: `
|
|
131
|
+
if (isValid && isActive) {
|
|
132
|
+
execute();
|
|
133
|
+
} else if (isPending) {
|
|
134
|
+
wait();
|
|
135
|
+
} else if (isValid && isActive) {
|
|
136
|
+
~~~~~~~~~~~~~~~~~~~
|
|
137
|
+
This condition is identical to a previous condition in the if-else-if chain.
|
|
138
|
+
execute();
|
|
139
|
+
}
|
|
140
|
+
`,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
code: `
|
|
144
|
+
if (value) {
|
|
145
|
+
first();
|
|
146
|
+
} else if (value) {
|
|
147
|
+
second();
|
|
148
|
+
} else if (value) {
|
|
149
|
+
third();
|
|
150
|
+
}
|
|
151
|
+
`,
|
|
152
|
+
snapshot: `
|
|
153
|
+
if (value) {
|
|
154
|
+
first();
|
|
155
|
+
} else if (value) {
|
|
156
|
+
~~~~~
|
|
157
|
+
This condition is identical to a previous condition in the if-else-if chain.
|
|
158
|
+
second();
|
|
159
|
+
} else if (value) {
|
|
160
|
+
~~~~~
|
|
161
|
+
This condition is identical to a previous condition in the if-else-if chain.
|
|
162
|
+
third();
|
|
163
|
+
}
|
|
164
|
+
`,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
valid: [
|
|
168
|
+
`if (isSomething(value)) { doFirst(); } else if (isSomethingElse(value)) { doSecond(); }`,
|
|
169
|
+
`if (first) { handleFirst(); } else if (second) { handleSecond(); } else if (third && fourth) { handleThird(); } else if (third && fifth) { handleFourth(); }`,
|
|
170
|
+
`if (count === 1) { handleOne(); } else if (count === 2) { handleTwo(); } else if (count === 3) { handleThree(); } else if (count === 4) { handleFour(); }`,
|
|
171
|
+
`if (value === "first") { process(); } else if (value === "second") { process(); } else if (value === "third") { process(); }`,
|
|
172
|
+
`if (isValid) { execute(); } else if (isPending) { wait(); } else if (isInvalid) { reject(); }`,
|
|
173
|
+
`if (value) { process(); }`,
|
|
174
|
+
`if (first) { doFirst(); } else { doSecond(); }`,
|
|
175
|
+
`if (first) { doFirst(); } else if (second) { doSecond(); } else { doDefault(); }`,
|
|
176
|
+
],
|
|
177
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { getTSNodeRange } from "../getTSNodeRange.js";
|
|
4
|
+
import { typescriptLanguage } from "../language.js";
|
|
5
|
+
import { hasSameTokens } from "../utils/hasSameTokens.js";
|
|
6
|
+
|
|
7
|
+
export default typescriptLanguage.createRule({
|
|
8
|
+
about: {
|
|
9
|
+
description:
|
|
10
|
+
"Reports duplicate conditions in if-else-if chains that make code unreachable.",
|
|
11
|
+
id: "elseIfDuplicates",
|
|
12
|
+
preset: "logical",
|
|
13
|
+
},
|
|
14
|
+
messages: {
|
|
15
|
+
duplicateCondition: {
|
|
16
|
+
primary:
|
|
17
|
+
"This condition is identical to a previous condition in the if-else-if chain.",
|
|
18
|
+
secondary: [
|
|
19
|
+
"When an if-else-if chain has duplicate conditions, the duplicate branch will never execute because the earlier identical condition will always be evaluated first.",
|
|
20
|
+
"This typically indicates a copy-paste error or logic mistake.",
|
|
21
|
+
],
|
|
22
|
+
suggestions: [
|
|
23
|
+
"Verify the condition logic is correct and update to check for the intended condition.",
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
setup(context) {
|
|
28
|
+
function checkIfStatement(node: ts.IfStatement) {
|
|
29
|
+
const seen: ts.Expression[] = [];
|
|
30
|
+
let current: ts.IfStatement = node;
|
|
31
|
+
|
|
32
|
+
while (true) {
|
|
33
|
+
if (
|
|
34
|
+
seen.some((previous) =>
|
|
35
|
+
hasSameTokens(previous, current.expression, context.sourceFile),
|
|
36
|
+
)
|
|
37
|
+
) {
|
|
38
|
+
context.report({
|
|
39
|
+
message: "duplicateCondition",
|
|
40
|
+
range: getTSNodeRange(current.expression, context.sourceFile),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
!current.elseStatement ||
|
|
46
|
+
!ts.isIfStatement(current.elseStatement)
|
|
47
|
+
) {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
seen.push(current.expression);
|
|
52
|
+
current = current.elseStatement;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
visitors: {
|
|
58
|
+
IfStatement: (node) => {
|
|
59
|
+
if (
|
|
60
|
+
!ts.isIfStatement(node.parent) ||
|
|
61
|
+
node.parent.elseStatement !== node
|
|
62
|
+
) {
|
|
63
|
+
checkIfStatement(node);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import rule from "./forDirections.js";
|
|
2
|
+
import { ruleTester } from "./ruleTester.js";
|
|
3
|
+
|
|
4
|
+
ruleTester.describe(rule, {
|
|
5
|
+
invalid: [
|
|
6
|
+
{
|
|
7
|
+
code: `
|
|
8
|
+
for (let i = 0; i < 10; i--) {}
|
|
9
|
+
`,
|
|
10
|
+
snapshot: `
|
|
11
|
+
for (let i = 0; i < 10; i--) {}
|
|
12
|
+
~~~
|
|
13
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
14
|
+
`,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: `
|
|
18
|
+
for (let index = 0; index < 10; index--) {}
|
|
19
|
+
`,
|
|
20
|
+
snapshot: `
|
|
21
|
+
for (let index = 0; index < 10; index--) {}
|
|
22
|
+
~~~~~~~
|
|
23
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
24
|
+
`,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
code: `
|
|
28
|
+
for (let index = 10; index >= 0; index++) {}
|
|
29
|
+
`,
|
|
30
|
+
snapshot: `
|
|
31
|
+
for (let index = 10; index >= 0; index++) {}
|
|
32
|
+
~~~~~~~
|
|
33
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
34
|
+
`,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
code: `
|
|
38
|
+
for (let index = 0; index > 10; index++) {}
|
|
39
|
+
`,
|
|
40
|
+
snapshot: `
|
|
41
|
+
for (let index = 0; index > 10; index++) {}
|
|
42
|
+
~~~~~~~
|
|
43
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
44
|
+
`,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
code: `
|
|
48
|
+
for (let index = 0; 10 > index; index--) {}
|
|
49
|
+
`,
|
|
50
|
+
snapshot: `
|
|
51
|
+
for (let index = 0; 10 > index; index--) {}
|
|
52
|
+
~~~~~~~
|
|
53
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
54
|
+
`,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
code: `
|
|
58
|
+
for (let index = 0; index < 10; index -= 1) {}
|
|
59
|
+
`,
|
|
60
|
+
snapshot: `
|
|
61
|
+
for (let index = 0; index < 10; index -= 1) {}
|
|
62
|
+
~~~~~~~~~~
|
|
63
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
64
|
+
`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
code: `
|
|
68
|
+
for (let index = 10; index > 0; index += 1) {}
|
|
69
|
+
`,
|
|
70
|
+
snapshot: `
|
|
71
|
+
for (let index = 10; index > 0; index += 1) {}
|
|
72
|
+
~~~~~~~~~~
|
|
73
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
74
|
+
`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
code: `
|
|
78
|
+
for (let index = 0; index < 10; index += -1) {}
|
|
79
|
+
`,
|
|
80
|
+
snapshot: `
|
|
81
|
+
for (let index = 0; index < 10; index += -1) {}
|
|
82
|
+
~~~~~~~~~~~
|
|
83
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
84
|
+
`,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
valid: [
|
|
88
|
+
`for (let i = 0; i < 10; i++) { }`,
|
|
89
|
+
`for (let index = 0; index < 10; index++) { }`,
|
|
90
|
+
`for (let index = 10; index >= 0; index--) { }`,
|
|
91
|
+
`for (let index = 0; 10 > index; index++) { }`,
|
|
92
|
+
`for (let index = 10; 0 < index; index--) { }`,
|
|
93
|
+
`for (let index = 10; index >= 0; index += step) { }`,
|
|
94
|
+
`for (let index = 0; index <= 10; index -= 0) { }`,
|
|
95
|
+
`for (let index = 0; index < 10; index += 1) { }`,
|
|
96
|
+
`for (let index = 10; index > 0; index -= 1) { }`,
|
|
97
|
+
`for (let index = 0; index < 10; index += 2) { }`,
|
|
98
|
+
`for (let index = 10; index > 0; index -= 2) { }`,
|
|
99
|
+
],
|
|
100
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { getTSNodeRange } from "../getTSNodeRange.js";
|
|
4
|
+
import { typescriptLanguage } from "../language.js";
|
|
5
|
+
|
|
6
|
+
function getConditionDirection(condition: ts.Expression, counterName: string) {
|
|
7
|
+
if (!ts.isBinaryExpression(condition)) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const leftName = getCounterName(condition.left);
|
|
12
|
+
const rightName = getCounterName(condition.right);
|
|
13
|
+
|
|
14
|
+
const isCounterLeft = leftName === counterName;
|
|
15
|
+
const isCounterRight = rightName === counterName;
|
|
16
|
+
|
|
17
|
+
if (!isCounterLeft && !isCounterRight) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { operatorToken } = condition;
|
|
22
|
+
|
|
23
|
+
if (isCounterLeft) {
|
|
24
|
+
if (
|
|
25
|
+
operatorToken.kind === ts.SyntaxKind.LessThanToken ||
|
|
26
|
+
operatorToken.kind === ts.SyntaxKind.LessThanEqualsToken
|
|
27
|
+
) {
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
if (
|
|
31
|
+
operatorToken.kind === ts.SyntaxKind.GreaterThanToken ||
|
|
32
|
+
operatorToken.kind === ts.SyntaxKind.GreaterThanEqualsToken
|
|
33
|
+
) {
|
|
34
|
+
return -1;
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
if (
|
|
38
|
+
operatorToken.kind === ts.SyntaxKind.LessThanToken ||
|
|
39
|
+
operatorToken.kind === ts.SyntaxKind.LessThanEqualsToken
|
|
40
|
+
) {
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
operatorToken.kind === ts.SyntaxKind.GreaterThanToken ||
|
|
45
|
+
operatorToken.kind === ts.SyntaxKind.GreaterThanEqualsToken
|
|
46
|
+
) {
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getCounterName(node: ts.Expression) {
|
|
55
|
+
return ts.isIdentifier(node) ? node.text : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getUpdateDirection(update: ts.Expression) {
|
|
59
|
+
if (
|
|
60
|
+
ts.isPostfixUnaryExpression(update) ||
|
|
61
|
+
ts.isPrefixUnaryExpression(update)
|
|
62
|
+
) {
|
|
63
|
+
switch (update.operator) {
|
|
64
|
+
case ts.SyntaxKind.MinusMinusToken:
|
|
65
|
+
return -1;
|
|
66
|
+
case ts.SyntaxKind.PlusPlusToken:
|
|
67
|
+
return 1;
|
|
68
|
+
default:
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (ts.isBinaryExpression(update)) {
|
|
74
|
+
const { operatorToken, right } = update;
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
operatorToken.kind === ts.SyntaxKind.PlusEqualsToken ||
|
|
78
|
+
operatorToken.kind === ts.SyntaxKind.MinusEqualsToken
|
|
79
|
+
) {
|
|
80
|
+
if (ts.isNumericLiteral(right)) {
|
|
81
|
+
const value = Number(right.text);
|
|
82
|
+
if (operatorToken.kind === ts.SyntaxKind.PlusEqualsToken) {
|
|
83
|
+
return value > 0 ? 1 : value < 0 ? -1 : 0;
|
|
84
|
+
}
|
|
85
|
+
return value > 0 ? -1 : value < 0 ? 1 : 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
ts.isPrefixUnaryExpression(right) &&
|
|
90
|
+
right.operator === ts.SyntaxKind.MinusToken &&
|
|
91
|
+
ts.isNumericLiteral(right.operand)
|
|
92
|
+
) {
|
|
93
|
+
const value = Number(right.operand.text);
|
|
94
|
+
if (operatorToken.kind === ts.SyntaxKind.PlusEqualsToken) {
|
|
95
|
+
return value > 0 ? -1 : 1;
|
|
96
|
+
}
|
|
97
|
+
return value > 0 ? 1 : -1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export default typescriptLanguage.createRule({
|
|
106
|
+
about: {
|
|
107
|
+
description:
|
|
108
|
+
"Reports for loops with counter variables that move in the wrong direction.",
|
|
109
|
+
id: "forDirections",
|
|
110
|
+
preset: "stylistic",
|
|
111
|
+
},
|
|
112
|
+
messages: {
|
|
113
|
+
wrongDirection: {
|
|
114
|
+
primary:
|
|
115
|
+
"The update moves the counter in the wrong direction for this loop condition.",
|
|
116
|
+
secondary: [
|
|
117
|
+
"A for loop with a counter that moves in the wrong direction relative to its stop condition will run infinitely or never execute.",
|
|
118
|
+
"When the counter increases but the condition expects it to decrease (or vice versa), the loop logic is incorrect.",
|
|
119
|
+
],
|
|
120
|
+
suggestions: [
|
|
121
|
+
"Verify the loop counter update direction matches the condition.",
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
setup(context) {
|
|
126
|
+
return {
|
|
127
|
+
visitors: {
|
|
128
|
+
ForStatement: (node) => {
|
|
129
|
+
if (
|
|
130
|
+
!node.condition ||
|
|
131
|
+
!node.incrementor ||
|
|
132
|
+
!node.initializer ||
|
|
133
|
+
!ts.isVariableDeclarationList(node.initializer)
|
|
134
|
+
) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const declaration = node.initializer.declarations.at(0);
|
|
139
|
+
if (!declaration || !ts.isIdentifier(declaration.name)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const updateDirection = getUpdateDirection(node.incrementor);
|
|
144
|
+
if (!updateDirection) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const conditionDirection = getConditionDirection(
|
|
149
|
+
node.condition,
|
|
150
|
+
declaration.name.text,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (updateDirection !== conditionDirection) {
|
|
154
|
+
context.report({
|
|
155
|
+
message: "wrongDirection",
|
|
156
|
+
range: getTSNodeRange(node.incrementor, context.sourceFile),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import rule from "./functionNewCalls.js";
|
|
2
|
+
import { ruleTester } from "./ruleTester.js";
|
|
3
|
+
|
|
4
|
+
ruleTester.describe(rule, {
|
|
5
|
+
invalid: [
|
|
6
|
+
{
|
|
7
|
+
code: `
|
|
8
|
+
const fn = new Function("a", "b", "return a + b");
|
|
9
|
+
`,
|
|
10
|
+
snapshot: `
|
|
11
|
+
const fn = new Function("a", "b", "return a + b");
|
|
12
|
+
~~~~~~~~
|
|
13
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
14
|
+
`,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: `
|
|
18
|
+
const fn = Function("a", "return a");
|
|
19
|
+
`,
|
|
20
|
+
snapshot: `
|
|
21
|
+
const fn = Function("a", "return a");
|
|
22
|
+
~~~~~~~~
|
|
23
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
24
|
+
`,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
code: `
|
|
28
|
+
const fn = new Function("return 1");
|
|
29
|
+
`,
|
|
30
|
+
snapshot: `
|
|
31
|
+
const fn = new Function("return 1");
|
|
32
|
+
~~~~~~~~
|
|
33
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
34
|
+
`,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
code: `
|
|
38
|
+
const fn = Function();
|
|
39
|
+
`,
|
|
40
|
+
snapshot: `
|
|
41
|
+
const fn = Function();
|
|
42
|
+
~~~~~~~~
|
|
43
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
44
|
+
`,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
code: `
|
|
48
|
+
const fn = new globalThis.Function("return 1");
|
|
49
|
+
`,
|
|
50
|
+
snapshot: `
|
|
51
|
+
const fn = new globalThis.Function("return 1");
|
|
52
|
+
~~~~~~~~~~~~~~~~~~~
|
|
53
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
54
|
+
`,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
code: `
|
|
58
|
+
const fn = globalThis.Function("return 1");
|
|
59
|
+
`,
|
|
60
|
+
snapshot: `
|
|
61
|
+
const fn = globalThis.Function("return 1");
|
|
62
|
+
~~~~~~~~~~~~~~~~~~~
|
|
63
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
64
|
+
`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
code: `
|
|
68
|
+
const fn = new window.Function("return 1");
|
|
69
|
+
`,
|
|
70
|
+
snapshot: `
|
|
71
|
+
const fn = new window.Function("return 1");
|
|
72
|
+
~~~~~~~~~~~~~~~
|
|
73
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
74
|
+
`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
code: `
|
|
78
|
+
const fn = window.Function("return 1");
|
|
79
|
+
`,
|
|
80
|
+
snapshot: `
|
|
81
|
+
const fn = window.Function("return 1");
|
|
82
|
+
~~~~~~~~~~~~~~~
|
|
83
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
84
|
+
`,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
code: `
|
|
88
|
+
const result = new Function("a", "b", "return a + b")(1, 2);
|
|
89
|
+
`,
|
|
90
|
+
snapshot: `
|
|
91
|
+
const result = new Function("a", "b", "return a + b")(1, 2);
|
|
92
|
+
~~~~~~~~
|
|
93
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
94
|
+
`,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
code: `
|
|
98
|
+
const CustomFunction = Function;
|
|
99
|
+
const fn = new CustomFunction();
|
|
100
|
+
`,
|
|
101
|
+
snapshot: `
|
|
102
|
+
const CustomFunction = Function;
|
|
103
|
+
const fn = new CustomFunction();
|
|
104
|
+
~~~~~~~~~~~~~~
|
|
105
|
+
Dynamically creating functions with the Function constructor is insecure and slow.
|
|
106
|
+
`,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
valid: [
|
|
110
|
+
`const fn = function(a, b) { return a + b; };`,
|
|
111
|
+
`const fn = (a, b) => a + b;`,
|
|
112
|
+
`function add(a, b) { return a + b; }`,
|
|
113
|
+
`class MyFunction {}`,
|
|
114
|
+
`const fn = new MyFunction();`,
|
|
115
|
+
],
|
|
116
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { getTSNodeRange } from "../getTSNodeRange.js";
|
|
4
|
+
import { typescriptLanguage } from "../language.js";
|
|
5
|
+
import { isGlobalDeclaration } from "../utils/isGlobalDeclaration.js";
|
|
6
|
+
import { isGlobalDeclarationOfName } from "../utils/isGlobalDeclarationOfName.js";
|
|
7
|
+
|
|
8
|
+
export default typescriptLanguage.createRule({
|
|
9
|
+
about: {
|
|
10
|
+
description:
|
|
11
|
+
"Reports using the Function constructor to create functions from strings.",
|
|
12
|
+
id: "functionNewCalls",
|
|
13
|
+
preset: "logical",
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
noFunctionConstructor: {
|
|
17
|
+
primary:
|
|
18
|
+
"Dynamically creating functions with the Function constructor is insecure and slow.",
|
|
19
|
+
secondary: [
|
|
20
|
+
"Using the `Function` constructor to create functions from strings is similar to `eval()` and introduces security risks and performance issues.",
|
|
21
|
+
"Code passed to the Function constructor is executed in the global scope, making it harder to optimize and potentially allowing arbitrary code execution if user input is involved.",
|
|
22
|
+
],
|
|
23
|
+
suggestions: [
|
|
24
|
+
"Define functions using function declarations (`function name() {}`) or arrow functions (`() => {}`) instead.",
|
|
25
|
+
"If dynamic code generation is truly necessary, consider safer alternatives like passing functions as parameters or using a more constrained domain-specific approach.",
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
setup(context) {
|
|
30
|
+
function checkNode(node: ts.CallExpression | ts.NewExpression) {
|
|
31
|
+
if (isFunctionConstructor(node)) {
|
|
32
|
+
context.report({
|
|
33
|
+
message: "noFunctionConstructor",
|
|
34
|
+
range: getTSNodeRange(node.expression, context.sourceFile),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isFunctionConstructor(node: ts.CallExpression | ts.NewExpression) {
|
|
40
|
+
if (ts.isIdentifier(node.expression)) {
|
|
41
|
+
if (
|
|
42
|
+
isGlobalDeclarationOfName(
|
|
43
|
+
node.expression,
|
|
44
|
+
"Function",
|
|
45
|
+
context.typeChecker,
|
|
46
|
+
)
|
|
47
|
+
) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
} else if (
|
|
51
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
52
|
+
isGlobalDeclaration(node.expression, context.typeChecker)
|
|
53
|
+
) {
|
|
54
|
+
const propertyName = node.expression.name.text;
|
|
55
|
+
if (propertyName !== "Function") {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const object = node.expression.expression;
|
|
60
|
+
if (ts.isIdentifier(object)) {
|
|
61
|
+
return object.text === "globalThis" || object.text === "window";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
visitors: {
|
|
70
|
+
CallExpression: checkNode,
|
|
71
|
+
NewExpression: checkNode,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
});
|