@flint.fyi/ts 0.14.3 → 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.
Files changed (147) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/lib/createTypeScriptFileFromProgram.js +5 -1
  3. package/lib/index.d.ts +3 -0
  4. package/lib/index.js +3 -0
  5. package/lib/language.d.ts +1 -0
  6. package/lib/plugin.d.ts +61 -1
  7. package/lib/plugin.js +30 -0
  8. package/lib/rules/anyReturns.d.ts +6 -0
  9. package/lib/rules/anyReturns.js +187 -0
  10. package/lib/rules/anyReturns.test.d.ts +1 -0
  11. package/lib/rules/anyReturns.test.js +647 -0
  12. package/lib/rules/caseDuplicates.d.ts +6 -0
  13. package/lib/rules/caseDuplicates.js +49 -0
  14. package/lib/rules/caseDuplicates.test.d.ts +1 -0
  15. package/lib/rules/caseDuplicates.test.js +307 -0
  16. package/lib/rules/chainedAssignments.js +2 -1
  17. package/lib/rules/chainedAssignments.test.js +24 -0
  18. package/lib/rules/debuggerStatements.test.js +4 -4
  19. package/lib/rules/elseIfDuplicates.d.ts +6 -0
  20. package/lib/rules/elseIfDuplicates.js +53 -0
  21. package/lib/rules/elseIfDuplicates.test.d.ts +1 -0
  22. package/lib/rules/elseIfDuplicates.test.js +176 -0
  23. package/lib/rules/emptyBlocks.d.ts +6 -0
  24. package/lib/rules/emptyBlocks.js +66 -0
  25. package/lib/rules/emptyBlocks.test.d.ts +1 -0
  26. package/lib/rules/emptyBlocks.test.js +135 -0
  27. package/lib/rules/forDirections.d.ts +6 -0
  28. package/lib/rules/forDirections.js +124 -0
  29. package/lib/rules/forDirections.test.d.ts +1 -0
  30. package/lib/rules/forDirections.test.js +99 -0
  31. package/lib/rules/functionNewCalls.d.ts +6 -0
  32. package/lib/rules/functionNewCalls.js +60 -0
  33. package/lib/rules/functionNewCalls.test.d.ts +1 -0
  34. package/lib/rules/functionNewCalls.test.js +115 -0
  35. package/lib/rules/negativeZeroComparisons.js +4 -22
  36. package/lib/rules/nonOctalDecimalEscapes.d.ts +6 -0
  37. package/lib/rules/nonOctalDecimalEscapes.js +57 -0
  38. package/lib/rules/nonOctalDecimalEscapes.test.d.ts +1 -0
  39. package/lib/rules/nonOctalDecimalEscapes.test.js +69 -0
  40. package/lib/rules/numericLiteralParsing.d.ts +6 -0
  41. package/lib/rules/numericLiteralParsing.js +97 -0
  42. package/lib/rules/numericLiteralParsing.test.d.ts +1 -0
  43. package/lib/rules/numericLiteralParsing.test.js +99 -0
  44. package/lib/rules/octalNumbers.test.js +2 -2
  45. package/lib/rules/selfAssignments.d.ts +6 -0
  46. package/lib/rules/selfAssignments.js +44 -0
  47. package/lib/rules/selfAssignments.test.d.ts +1 -0
  48. package/lib/rules/selfAssignments.test.js +66 -0
  49. package/lib/rules/selfComparisons.d.ts +6 -0
  50. package/lib/rules/selfComparisons.js +41 -0
  51. package/lib/rules/selfComparisons.test.d.ts +1 -0
  52. package/lib/rules/selfComparisons.test.js +297 -0
  53. package/lib/rules/sequences.d.ts +6 -0
  54. package/lib/rules/sequences.js +34 -0
  55. package/lib/rules/sequences.test.d.ts +1 -0
  56. package/lib/rules/sequences.test.js +48 -0
  57. package/lib/rules/shadowedRestrictedNames.d.ts +6 -0
  58. package/lib/rules/shadowedRestrictedNames.js +95 -0
  59. package/lib/rules/shadowedRestrictedNames.test.d.ts +1 -0
  60. package/lib/rules/shadowedRestrictedNames.test.js +147 -0
  61. package/lib/rules/symbolDescriptions.test.js +4 -4
  62. package/lib/rules/typeofComparisons.d.ts +6 -0
  63. package/lib/rules/typeofComparisons.js +78 -0
  64. package/lib/rules/typeofComparisons.test.d.ts +1 -0
  65. package/lib/rules/typeofComparisons.test.js +85 -0
  66. package/lib/rules/unicodeBOMs.test.js +1 -0
  67. package/lib/rules/unnecessaryBlocks.d.ts +6 -0
  68. package/lib/rules/unnecessaryBlocks.js +75 -0
  69. package/lib/rules/unnecessaryBlocks.test.d.ts +1 -0
  70. package/lib/rules/unnecessaryBlocks.test.js +147 -0
  71. package/lib/rules/unnecessaryConcatenation.d.ts +6 -0
  72. package/lib/rules/unnecessaryConcatenation.js +38 -0
  73. package/lib/rules/unnecessaryConcatenation.test.d.ts +1 -0
  74. package/lib/rules/unnecessaryConcatenation.test.js +87 -0
  75. package/lib/rules/utils/discriminateAnyType.d.ts +12 -0
  76. package/lib/rules/utils/discriminateAnyType.js +41 -0
  77. package/lib/rules/utils/getThisExpression.d.ts +2 -0
  78. package/lib/rules/utils/getThisExpression.js +23 -0
  79. package/lib/rules/utils/isUnsafeAssignment.d.ts +13 -0
  80. package/lib/rules/utils/isUnsafeAssignment.js +76 -0
  81. package/lib/rules/utils/operators.d.ts +4 -0
  82. package/lib/rules/utils/operators.js +36 -0
  83. package/lib/rules/voidOperator.test.js +2 -2
  84. package/lib/utils/declarationIncludesGlobal.d.ts +2 -0
  85. package/lib/utils/declarationIncludesGlobal.js +5 -0
  86. package/lib/utils/declarationsIncludeGlobal.js +2 -5
  87. package/lib/utils/getDeclarationsIfGlobal.d.ts +2 -0
  88. package/lib/utils/getDeclarationsIfGlobal.js +7 -0
  89. package/lib/utils/hasSameTokens.d.ts +2 -0
  90. package/lib/utils/hasSameTokens.js +30 -0
  91. package/lib/utils/isGlobalDeclaration.js +2 -3
  92. package/lib/utils/isGlobalDeclarationOfName.d.ts +5 -0
  93. package/lib/utils/isGlobalDeclarationOfName.js +35 -0
  94. package/package.json +3 -3
  95. package/src/createTypeScriptFileFromProgram.ts +6 -1
  96. package/src/index.ts +3 -0
  97. package/src/language.ts +1 -0
  98. package/src/plugin.ts +30 -0
  99. package/src/rules/anyReturns.test.ts +649 -0
  100. package/src/rules/anyReturns.ts +263 -0
  101. package/src/rules/caseDuplicates.test.ts +308 -0
  102. package/src/rules/caseDuplicates.ts +57 -0
  103. package/src/rules/chainedAssignments.test.ts +24 -0
  104. package/src/rules/chainedAssignments.ts +4 -1
  105. package/src/rules/debuggerStatements.test.ts +4 -4
  106. package/src/rules/elseIfDuplicates.test.ts +177 -0
  107. package/src/rules/elseIfDuplicates.ts +69 -0
  108. package/src/rules/emptyBlocks.test.ts +136 -0
  109. package/src/rules/emptyBlocks.ts +73 -0
  110. package/src/rules/forDirections.test.ts +100 -0
  111. package/src/rules/forDirections.ts +163 -0
  112. package/src/rules/functionNewCalls.test.ts +116 -0
  113. package/src/rules/functionNewCalls.ts +75 -0
  114. package/src/rules/negativeZeroComparisons.ts +8 -27
  115. package/src/rules/nonOctalDecimalEscapes.test.ts +70 -0
  116. package/src/rules/nonOctalDecimalEscapes.ts +70 -0
  117. package/src/rules/numericLiteralParsing.test.ts +100 -0
  118. package/src/rules/numericLiteralParsing.ts +116 -0
  119. package/src/rules/octalNumbers.test.ts +2 -2
  120. package/src/rules/selfAssignments.test.ts +67 -0
  121. package/src/rules/selfAssignments.ts +50 -0
  122. package/src/rules/selfComparisons.test.ts +298 -0
  123. package/src/rules/selfComparisons.ts +45 -0
  124. package/src/rules/sequences.test.ts +49 -0
  125. package/src/rules/sequences.ts +37 -0
  126. package/src/rules/shadowedRestrictedNames.test.ts +148 -0
  127. package/src/rules/shadowedRestrictedNames.ts +105 -0
  128. package/src/rules/symbolDescriptions.test.ts +4 -4
  129. package/src/rules/typeofComparisons.test.ts +86 -0
  130. package/src/rules/typeofComparisons.ts +88 -0
  131. package/src/rules/unicodeBOMs.test.ts +1 -0
  132. package/src/rules/unnecessaryBlocks.test.ts +148 -0
  133. package/src/rules/unnecessaryBlocks.ts +89 -0
  134. package/src/rules/unnecessaryConcatenation.test.ts +88 -0
  135. package/src/rules/unnecessaryConcatenation.ts +44 -0
  136. package/src/rules/utils/discriminateAnyType.ts +66 -0
  137. package/src/rules/utils/getThisExpression.ts +24 -0
  138. package/src/rules/utils/isUnsafeAssignment.ts +113 -0
  139. package/src/rules/utils/operators.ts +39 -0
  140. package/src/rules/voidOperator.test.ts +2 -2
  141. package/src/utils/declarationIncludesGlobal.ts +9 -0
  142. package/src/utils/declarationsIncludeGlobal.ts +3 -7
  143. package/src/utils/getDeclarationsIfGlobal.ts +14 -0
  144. package/src/utils/hasSameTokens.ts +45 -0
  145. package/src/utils/isGlobalDeclaration.ts +3 -5
  146. package/src/utils/isGlobalDeclarationOfName.ts +56 -0
  147. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,307 @@
1
+ import rule from "./caseDuplicates.js";
2
+ import { ruleTester } from "./ruleTester.js";
3
+ ruleTester.describe(rule, {
4
+ invalid: [
5
+ {
6
+ code: `
7
+ switch (value) {
8
+ case 1:
9
+ break;
10
+ case 1:
11
+ break;
12
+ }
13
+ `,
14
+ snapshot: `
15
+ switch (value) {
16
+ case 1:
17
+ break;
18
+ case 1:
19
+ ~~~~~~
20
+ This case duplicates a previous case, so it will never be reached.
21
+ break;
22
+ }
23
+ `,
24
+ },
25
+ {
26
+ code: `
27
+ switch (value) {
28
+ case "a":
29
+ break;
30
+ case "b":
31
+ break;
32
+ case "a":
33
+ break;
34
+ }
35
+ `,
36
+ snapshot: `
37
+ switch (value) {
38
+ case "a":
39
+ break;
40
+ case "b":
41
+ break;
42
+ case "a":
43
+ ~~~~~~~~
44
+ This case duplicates a previous case, so it will never be reached.
45
+ break;
46
+ }
47
+ `,
48
+ },
49
+ {
50
+ code: `
51
+ switch (value) {
52
+ case x + 1:
53
+ break;
54
+ case x + 1:
55
+ break;
56
+ }
57
+ `,
58
+ snapshot: `
59
+ switch (value) {
60
+ case x + 1:
61
+ break;
62
+ case x + 1:
63
+ ~~~~~~~~~~
64
+ This case duplicates a previous case, so it will never be reached.
65
+ break;
66
+ }
67
+ `,
68
+ },
69
+ {
70
+ code: `
71
+ switch (value) {
72
+ case 1:
73
+ break;
74
+ case 2:
75
+ break;
76
+ case 1:
77
+ break;
78
+ }
79
+ `,
80
+ snapshot: `
81
+ switch (value) {
82
+ case 1:
83
+ break;
84
+ case 2:
85
+ break;
86
+ case 1:
87
+ ~~~~~~
88
+ This case duplicates a previous case, so it will never be reached.
89
+ break;
90
+ }
91
+ `,
92
+ },
93
+ {
94
+ code: `
95
+ switch (value) {
96
+ case true:
97
+ break;
98
+ case false:
99
+ break;
100
+ case true:
101
+ break;
102
+ }
103
+ `,
104
+ snapshot: `
105
+ switch (value) {
106
+ case true:
107
+ break;
108
+ case false:
109
+ break;
110
+ case true:
111
+ ~~~~~~~~~
112
+ This case duplicates a previous case, so it will never be reached.
113
+ break;
114
+ }
115
+ `,
116
+ },
117
+ {
118
+ code: `
119
+ switch (value) {
120
+ case obj.property:
121
+ break;
122
+ case obj.property:
123
+ break;
124
+ }
125
+ `,
126
+ snapshot: `
127
+ switch (value) {
128
+ case obj.property:
129
+ break;
130
+ case obj.property:
131
+ ~~~~~~~~~~~~~~~~~
132
+ This case duplicates a previous case, so it will never be reached.
133
+ break;
134
+ }
135
+ `,
136
+ },
137
+ {
138
+ code: `
139
+ const result = (input: number) => {
140
+ switch (input) {
141
+ case 1:
142
+ return "one";
143
+ case 2:
144
+ return "two";
145
+ case 1:
146
+ return "duplicate";
147
+ }
148
+ };
149
+ `,
150
+ snapshot: `
151
+ const result = (input: number) => {
152
+ switch (input) {
153
+ case 1:
154
+ return "one";
155
+ case 2:
156
+ return "two";
157
+ case 1:
158
+ ~~~~~~
159
+ This case duplicates a previous case, so it will never be reached.
160
+ return "duplicate";
161
+ }
162
+ };
163
+ `,
164
+ },
165
+ {
166
+ code: `
167
+ switch (value) {
168
+ case 1: case 1: break;
169
+ }
170
+ `,
171
+ snapshot: `
172
+ switch (value) {
173
+ case 1: case 1: break;
174
+ ~~~~~~
175
+ This case duplicates a previous case, so it will never be reached.
176
+ }
177
+ `,
178
+ },
179
+ {
180
+ code: `
181
+ const condition = true;
182
+ switch (value) {
183
+ case condition ? "a" : "b":
184
+ break;
185
+ case condition ? "a" : "b":
186
+ break;
187
+ }
188
+ `,
189
+ snapshot: `
190
+ const condition = true;
191
+ switch (value) {
192
+ case condition ? "a" : "b":
193
+ break;
194
+ case condition ? "a" : "b":
195
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
196
+ This case duplicates a previous case, so it will never be reached.
197
+ break;
198
+ }
199
+ `,
200
+ },
201
+ {
202
+ code: `
203
+ switch (value) {
204
+ case /* comment */ 1:
205
+ break;
206
+ case 1:
207
+ break;
208
+ }
209
+ `,
210
+ snapshot: `
211
+ switch (value) {
212
+ case /* comment */ 1:
213
+ break;
214
+ case 1:
215
+ ~~~~~~
216
+ This case duplicates a previous case, so it will never be reached.
217
+ break;
218
+ }
219
+ `,
220
+ },
221
+ {
222
+ code: `
223
+ switch (value) {
224
+ case obj.property:
225
+ break;
226
+ case obj /* comment */ .property:
227
+ break;
228
+ }
229
+ `,
230
+ snapshot: `
231
+ switch (value) {
232
+ case obj.property:
233
+ break;
234
+ case obj /* comment */ .property:
235
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
236
+ This case duplicates a previous case, so it will never be reached.
237
+ break;
238
+ }
239
+ `,
240
+ },
241
+ ],
242
+ valid: [
243
+ `switch (value) { case 1: break; case 2: break; }`,
244
+ `switch (value) { case 1: break; case 2: break; case 3: break; }`,
245
+ `switch (value) { case "a": break; case "b": break; }`,
246
+ `switch (value) { case true: break; case false: break; }`,
247
+ `switch (value) { case 1: case 2: break; }`,
248
+ `switch (value) { default: break; }`,
249
+ `
250
+ switch (value) {
251
+ case 1:
252
+ break;
253
+ case 2:
254
+ break;
255
+ }
256
+ `,
257
+ `
258
+ switch (value) {
259
+ case x:
260
+ break;
261
+ case y:
262
+ break;
263
+ }
264
+ `,
265
+ `
266
+ switch (value) {
267
+ case obj.a:
268
+ break;
269
+ case obj.b:
270
+ break;
271
+ }
272
+ `,
273
+ `
274
+ switch (value) {
275
+ case 1:
276
+ console.log("one");
277
+ break;
278
+ case 2:
279
+ console.log("two");
280
+ break;
281
+ default:
282
+ console.log("default");
283
+ break;
284
+ }
285
+ `,
286
+ `
287
+ switch (value) {
288
+ case x + 1:
289
+ break;
290
+ case x + 2:
291
+ break;
292
+ }
293
+ `,
294
+ `
295
+ const result = (input: number) => {
296
+ switch (input) {
297
+ case 1:
298
+ return "one";
299
+ case 2:
300
+ return "two";
301
+ default:
302
+ return "unknown";
303
+ }
304
+ };
305
+ `,
306
+ ],
307
+ });
@@ -30,7 +30,8 @@ export default typescriptLanguage.createRule({
30
30
  return;
31
31
  }
32
32
  const rightSide = unwrapParenthesizedExpression(node.right);
33
- if (!ts.isBinaryExpression(rightSide)) {
33
+ if (!ts.isBinaryExpression(rightSide) ||
34
+ !tsutils.isAssignmentKind(rightSide.operatorToken.kind)) {
34
35
  return;
35
36
  }
36
37
  const parent = unwrapParenthesizedExpressionsParent(node);
@@ -233,6 +233,30 @@ class MyClass {
233
233
  second = 0;
234
234
  }
235
235
  }
236
+ `,
237
+ `
238
+ let result = "";
239
+ result += "a" + "b";
240
+ `,
241
+ `
242
+ let result = 0;
243
+ result += 1 + 2;
244
+ `,
245
+ `
246
+ let result = 0;
247
+ result += 1 * 2;
248
+ `,
249
+ `
250
+ let result = 0;
251
+ result += 1 - 2;
252
+ `,
253
+ `
254
+ let result = 0;
255
+ result += 1 / 2;
256
+ `,
257
+ `
258
+ let result = 0;
259
+ result += 1 ** 2;
236
260
  `,
237
261
  ],
238
262
  });
@@ -29,8 +29,8 @@ function test() {
29
29
  snapshot: `
30
30
  function test() {
31
31
  debugger;
32
- ~~~~~~~~~
33
- Debugger statements should not be used in production code.
32
+ ~~~~~~~~~
33
+ Debugger statements should not be used in production code.
34
34
  }
35
35
  `,
36
36
  suggestions: [
@@ -53,8 +53,8 @@ if (condition) {
53
53
  snapshot: `
54
54
  if (condition) {
55
55
  debugger;
56
- ~~~~~~~~~
57
- Debugger statements should not be used in production code.
56
+ ~~~~~~~~~
57
+ Debugger statements should not be used in production code.
58
58
  }
59
59
  `,
60
60
  suggestions: [
@@ -0,0 +1,6 @@
1
+ declare const _default: import("@flint.fyi/core").Rule<{
2
+ readonly description: "Reports duplicate conditions in if-else-if chains that make code unreachable.";
3
+ readonly id: "elseIfDuplicates";
4
+ readonly preset: "logical";
5
+ }, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "duplicateCondition", undefined>;
6
+ export default _default;
@@ -0,0 +1,53 @@
1
+ import * as ts from "typescript";
2
+ import { getTSNodeRange } from "../getTSNodeRange.js";
3
+ import { typescriptLanguage } from "../language.js";
4
+ import { hasSameTokens } from "../utils/hasSameTokens.js";
5
+ export default typescriptLanguage.createRule({
6
+ about: {
7
+ description: "Reports duplicate conditions in if-else-if chains that make code unreachable.",
8
+ id: "elseIfDuplicates",
9
+ preset: "logical",
10
+ },
11
+ messages: {
12
+ duplicateCondition: {
13
+ primary: "This condition is identical to a previous condition in the if-else-if chain.",
14
+ secondary: [
15
+ "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.",
16
+ "This typically indicates a copy-paste error or logic mistake.",
17
+ ],
18
+ suggestions: [
19
+ "Verify the condition logic is correct and update to check for the intended condition.",
20
+ ],
21
+ },
22
+ },
23
+ setup(context) {
24
+ function checkIfStatement(node) {
25
+ const seen = [];
26
+ let current = node;
27
+ while (true) {
28
+ if (seen.some((previous) => hasSameTokens(previous, current.expression, context.sourceFile))) {
29
+ context.report({
30
+ message: "duplicateCondition",
31
+ range: getTSNodeRange(current.expression, context.sourceFile),
32
+ });
33
+ }
34
+ if (!current.elseStatement ||
35
+ !ts.isIfStatement(current.elseStatement)) {
36
+ break;
37
+ }
38
+ seen.push(current.expression);
39
+ current = current.elseStatement;
40
+ }
41
+ }
42
+ return {
43
+ visitors: {
44
+ IfStatement: (node) => {
45
+ if (!ts.isIfStatement(node.parent) ||
46
+ node.parent.elseStatement !== node) {
47
+ checkIfStatement(node);
48
+ }
49
+ },
50
+ },
51
+ };
52
+ },
53
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,176 @@
1
+ import rule from "./elseIfDuplicates.js";
2
+ import { ruleTester } from "./ruleTester.js";
3
+ ruleTester.describe(rule, {
4
+ invalid: [
5
+ {
6
+ code: `
7
+ if (isSomething(value)) {
8
+ doFirst();
9
+ } else if (isSomething(value)) {
10
+ doSecond();
11
+ }
12
+ `,
13
+ snapshot: `
14
+ if (isSomething(value)) {
15
+ doFirst();
16
+ } else if (isSomething(value)) {
17
+ ~~~~~~~~~~~~~~~~~~
18
+ This condition is identical to a previous condition in the if-else-if chain.
19
+ doSecond();
20
+ }
21
+ `,
22
+ },
23
+ {
24
+ code: `
25
+ if (isSomething /* ... */ (value)) {
26
+ doFirst();
27
+ } else if (isSomething(value /* ... */)) {
28
+ doSecond();
29
+ }
30
+ `,
31
+ snapshot: `
32
+ if (isSomething /* ... */ (value)) {
33
+ doFirst();
34
+ } else if (isSomething(value /* ... */)) {
35
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36
+ This condition is identical to a previous condition in the if-else-if chain.
37
+ doSecond();
38
+ }
39
+ `,
40
+ },
41
+ {
42
+ code: `
43
+ if (first) {
44
+ handleFirst();
45
+ } else if (second) {
46
+ handleSecond();
47
+ } else if (third && fourth) {
48
+ handleThird();
49
+ } else if (third && fourth) {
50
+ handleFourth();
51
+ }
52
+ `,
53
+ snapshot: `
54
+ if (first) {
55
+ handleFirst();
56
+ } else if (second) {
57
+ handleSecond();
58
+ } else if (third && fourth) {
59
+ handleThird();
60
+ } else if (third && fourth) {
61
+ ~~~~~~~~~~~~~~~
62
+ This condition is identical to a previous condition in the if-else-if chain.
63
+ handleFourth();
64
+ }
65
+ `,
66
+ },
67
+ {
68
+ code: `
69
+ if (count === 1) {
70
+ handleOne();
71
+ } else if (count === 2) {
72
+ handleTwo();
73
+ } else if (count === 3) {
74
+ handleThree();
75
+ } else if (count === 2) {
76
+ handleFour();
77
+ } else if (count === 5) {
78
+ handleFive();
79
+ }
80
+ `,
81
+ snapshot: `
82
+ if (count === 1) {
83
+ handleOne();
84
+ } else if (count === 2) {
85
+ handleTwo();
86
+ } else if (count === 3) {
87
+ handleThree();
88
+ } else if (count === 2) {
89
+ ~~~~~~~~~~~
90
+ This condition is identical to a previous condition in the if-else-if chain.
91
+ handleFour();
92
+ } else if (count === 5) {
93
+ handleFive();
94
+ }
95
+ `,
96
+ },
97
+ {
98
+ code: `
99
+ if (value === "first") {
100
+ process();
101
+ } else if (value === "second") {
102
+ process();
103
+ } else if (value === "first") {
104
+ process();
105
+ }
106
+ `,
107
+ snapshot: `
108
+ if (value === "first") {
109
+ process();
110
+ } else if (value === "second") {
111
+ process();
112
+ } else if (value === "first") {
113
+ ~~~~~~~~~~~~~~~~~
114
+ This condition is identical to a previous condition in the if-else-if chain.
115
+ process();
116
+ }
117
+ `,
118
+ },
119
+ {
120
+ code: `
121
+ if (isValid && isActive) {
122
+ execute();
123
+ } else if (isPending) {
124
+ wait();
125
+ } else if (isValid && isActive) {
126
+ execute();
127
+ }
128
+ `,
129
+ snapshot: `
130
+ if (isValid && isActive) {
131
+ execute();
132
+ } else if (isPending) {
133
+ wait();
134
+ } else if (isValid && isActive) {
135
+ ~~~~~~~~~~~~~~~~~~~
136
+ This condition is identical to a previous condition in the if-else-if chain.
137
+ execute();
138
+ }
139
+ `,
140
+ },
141
+ {
142
+ code: `
143
+ if (value) {
144
+ first();
145
+ } else if (value) {
146
+ second();
147
+ } else if (value) {
148
+ third();
149
+ }
150
+ `,
151
+ snapshot: `
152
+ if (value) {
153
+ first();
154
+ } else if (value) {
155
+ ~~~~~
156
+ This condition is identical to a previous condition in the if-else-if chain.
157
+ second();
158
+ } else if (value) {
159
+ ~~~~~
160
+ This condition is identical to a previous condition in the if-else-if chain.
161
+ third();
162
+ }
163
+ `,
164
+ },
165
+ ],
166
+ valid: [
167
+ `if (isSomething(value)) { doFirst(); } else if (isSomethingElse(value)) { doSecond(); }`,
168
+ `if (first) { handleFirst(); } else if (second) { handleSecond(); } else if (third && fourth) { handleThird(); } else if (third && fifth) { handleFourth(); }`,
169
+ `if (count === 1) { handleOne(); } else if (count === 2) { handleTwo(); } else if (count === 3) { handleThree(); } else if (count === 4) { handleFour(); }`,
170
+ `if (value === "first") { process(); } else if (value === "second") { process(); } else if (value === "third") { process(); }`,
171
+ `if (isValid) { execute(); } else if (isPending) { wait(); } else if (isInvalid) { reject(); }`,
172
+ `if (value) { process(); }`,
173
+ `if (first) { doFirst(); } else { doSecond(); }`,
174
+ `if (first) { doFirst(); } else if (second) { doSecond(); } else { doDefault(); }`,
175
+ ],
176
+ });
@@ -0,0 +1,6 @@
1
+ declare const _default: import("@flint.fyi/core").Rule<{
2
+ readonly description: "Reports empty block statements that should contain code.";
3
+ readonly id: "emptyBlocks";
4
+ readonly preset: "stylistic";
5
+ }, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "emptyBlock", undefined>;
6
+ export default _default;
@@ -0,0 +1,66 @@
1
+ import * as ts from "typescript";
2
+ import { getTSNodeRange } from "../getTSNodeRange.js";
3
+ import { typescriptLanguage } from "../language.js";
4
+ const allowedParents = new Set([
5
+ ts.SyntaxKind.ArrowFunction,
6
+ ts.SyntaxKind.CatchClause,
7
+ ts.SyntaxKind.Constructor,
8
+ ts.SyntaxKind.FunctionDeclaration,
9
+ ts.SyntaxKind.FunctionExpression,
10
+ ts.SyntaxKind.GetAccessor,
11
+ ts.SyntaxKind.MethodDeclaration,
12
+ ts.SyntaxKind.SetAccessor,
13
+ ]);
14
+ export default typescriptLanguage.createRule({
15
+ about: {
16
+ description: "Reports empty block statements that should contain code.",
17
+ id: "emptyBlocks",
18
+ preset: "stylistic",
19
+ },
20
+ messages: {
21
+ emptyBlock: {
22
+ primary: "Empty block statements should be removed or contain code.",
23
+ secondary: [
24
+ "Empty blocks can indicate incomplete code or areas where logic was removed but the block structure was left behind.",
25
+ "They can also reduce code readability by cluttering the codebase with unnecessary braces.",
26
+ ],
27
+ suggestions: [
28
+ "Add a comment explaining why the block is intentionally empty, or remove the empty block entirely.",
29
+ ],
30
+ },
31
+ },
32
+ setup(context) {
33
+ function hasComments(block) {
34
+ const fullText = context.sourceFile.getFullText();
35
+ const openBrace = block.getStart(context.sourceFile);
36
+ const closeBrace = block.getEnd();
37
+ const innerText = fullText.substring(openBrace + 1, closeBrace - 1);
38
+ // Check if there are any non-whitespace characters (which would be comments)
39
+ // since we already know there are no statements
40
+ return /\S+/.test(innerText.trim());
41
+ }
42
+ function isEmptyBlock(block) {
43
+ return block.statements.length === 0 && !hasComments(block);
44
+ }
45
+ return {
46
+ visitors: {
47
+ Block: (node) => {
48
+ if (!allowedParents.has(node.parent.kind) && isEmptyBlock(node)) {
49
+ context.report({
50
+ message: "emptyBlock",
51
+ range: getTSNodeRange(node, context.sourceFile),
52
+ });
53
+ }
54
+ },
55
+ CaseBlock: (node) => {
56
+ if (node.clauses.length === 0) {
57
+ context.report({
58
+ message: "emptyBlock",
59
+ range: getTSNodeRange(node, context.sourceFile),
60
+ });
61
+ }
62
+ },
63
+ },
64
+ };
65
+ },
66
+ });
@@ -0,0 +1 @@
1
+ export {};