@flint.fyi/ts 0.14.4 → 0.14.6
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 +15 -0
- package/lib/createTypeScriptFileFromProgram.js +4 -1
- package/lib/formatDiagnostic.js +1 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/normalizeRange.js +3 -6
- package/lib/plugin.d.ts +17 -1
- package/lib/plugin.js +9 -1
- 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/forDirections.js +15 -8
- package/lib/rules/forDirections.test.js +25 -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/formatDiagnostic.ts +1 -1
- package/src/index.ts +3 -0
- package/src/normalizeRange.ts +3 -8
- package/src/plugin.ts +9 -1
- 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/forDirections.test.ts +25 -0
- package/src/rules/forDirections.ts +20 -9
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @flint/ts
|
|
2
2
|
|
|
3
|
+
## 0.14.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b789918: fix(ts): [forDirections] allow multi-part conditions
|
|
8
|
+
- Updated dependencies [1bbae2e]
|
|
9
|
+
- Updated dependencies [11abdff]
|
|
10
|
+
- @flint.fyi/core@0.16.0
|
|
11
|
+
|
|
12
|
+
## 0.14.5
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 7e21021: fix(ts): [chainedAssignments] allow non-assignment right-side operators
|
|
17
|
+
|
|
3
18
|
## 0.14.4
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -37,6 +37,9 @@ export function createTypeScriptFileFromProgram(program, sourceFile) {
|
|
|
37
37
|
report: (report) => {
|
|
38
38
|
reports.push({
|
|
39
39
|
...report,
|
|
40
|
+
fix: report.fix && !Array.isArray(report.fix)
|
|
41
|
+
? [report.fix]
|
|
42
|
+
: report.fix,
|
|
40
43
|
message: rule.messages[report.message],
|
|
41
44
|
range: normalizeRange(report.range, sourceFile),
|
|
42
45
|
});
|
|
@@ -53,7 +56,7 @@ export function createTypeScriptFileFromProgram(program, sourceFile) {
|
|
|
53
56
|
visitors[NodeSyntaxKinds[node.kind]]?.(node);
|
|
54
57
|
node.forEachChild(visit);
|
|
55
58
|
};
|
|
56
|
-
sourceFile
|
|
59
|
+
visit(sourceFile);
|
|
57
60
|
return reports;
|
|
58
61
|
},
|
|
59
62
|
};
|
package/lib/formatDiagnostic.js
CHANGED
|
@@ -92,7 +92,7 @@ function formatCodeSpan(file, start, length, indent, squiggleColor) {
|
|
|
92
92
|
gutterSeparator;
|
|
93
93
|
context += squiggleColor;
|
|
94
94
|
if (i === firstLine) {
|
|
95
|
-
const lastCharForLine = i === lastLine ? lastLineChar :
|
|
95
|
+
const lastCharForLine = i === lastLine ? lastLineChar : undefined;
|
|
96
96
|
context += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
|
|
97
97
|
context += lineContent
|
|
98
98
|
.slice(firstLineChar, lastCharForLine)
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { getTSNodeRange } from "./getTSNodeRange.js";
|
|
2
2
|
export * from "./language.js";
|
|
3
3
|
export { ts } from "./plugin.js";
|
|
4
|
+
export { getDeclarationsIfGlobal } from "./utils/getDeclarationsIfGlobal.js";
|
|
4
5
|
export { isGlobalDeclaration } from "./utils/isGlobalDeclaration.js";
|
|
6
|
+
export { isGlobalDeclarationOfName } from "./utils/isGlobalDeclarationOfName.js";
|
|
7
|
+
export { isGlobalVariable } from "./utils/isGlobalVariable.js";
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { getTSNodeRange } from "./getTSNodeRange.js";
|
|
2
2
|
export * from "./language.js";
|
|
3
3
|
export { ts } from "./plugin.js";
|
|
4
|
+
export { getDeclarationsIfGlobal } from "./utils/getDeclarationsIfGlobal.js";
|
|
4
5
|
export { isGlobalDeclaration } from "./utils/isGlobalDeclaration.js";
|
|
6
|
+
export { isGlobalDeclarationOfName } from "./utils/isGlobalDeclarationOfName.js";
|
|
7
|
+
export { isGlobalVariable } from "./utils/isGlobalVariable.js";
|
package/lib/normalizeRange.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
+
import { getColumnAndLineOfPosition, } from "@flint.fyi/core";
|
|
1
2
|
export function normalizeRange(original, sourceFile) {
|
|
2
3
|
const onCharacters = isNode(original)
|
|
3
4
|
? { begin: original.getStart(), end: original.getEnd() }
|
|
4
5
|
: original;
|
|
5
6
|
return {
|
|
6
|
-
begin:
|
|
7
|
-
end:
|
|
7
|
+
begin: getColumnAndLineOfPosition(sourceFile, onCharacters.begin),
|
|
8
|
+
end: getColumnAndLineOfPosition(sourceFile, onCharacters.end),
|
|
8
9
|
};
|
|
9
10
|
}
|
|
10
11
|
function isNode(value) {
|
|
11
12
|
return typeof value === "object" && value !== null && "kind" in value;
|
|
12
13
|
}
|
|
13
|
-
function normalizeRangePosition(raw, sourceFile) {
|
|
14
|
-
const { character, line } = sourceFile.getLineAndCharacterOfPosition(raw);
|
|
15
|
-
return { column: character, line, raw };
|
|
16
|
-
}
|
package/lib/plugin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const ts: import("@flint.fyi/core").Plugin<import("@flint.fyi/core").RuleAbout,
|
|
1
|
+
export declare const ts: import("@flint.fyi/core").Plugin<import("@flint.fyi/core").RuleAbout, "all", [import("@flint.fyi/core").Rule<{
|
|
2
2
|
readonly description: "Reports returning a value with type `any` from a function.";
|
|
3
3
|
readonly id: "anyReturns";
|
|
4
4
|
readonly preset: "logical";
|
|
@@ -51,6 +51,10 @@ export declare const ts: import("@flint.fyi/core").Plugin<import("@flint.fyi/cor
|
|
|
51
51
|
readonly id: "elseIfDuplicates";
|
|
52
52
|
readonly preset: "logical";
|
|
53
53
|
}, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "duplicateCondition", undefined>, import("@flint.fyi/core").Rule<{
|
|
54
|
+
readonly description: "Reports empty block statements that should contain code.";
|
|
55
|
+
readonly id: "emptyBlocks";
|
|
56
|
+
readonly preset: "stylistic";
|
|
57
|
+
}, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "emptyBlock", undefined>, import("@flint.fyi/core").Rule<{
|
|
54
58
|
readonly description: "Reports using empty destructuring patterns that destructure no values.";
|
|
55
59
|
readonly id: "emptyDestructures";
|
|
56
60
|
readonly preset: "logical";
|
|
@@ -146,6 +150,10 @@ export declare const ts: import("@flint.fyi/core").Plugin<import("@flint.fyi/cor
|
|
|
146
150
|
readonly id: "sequences";
|
|
147
151
|
readonly preset: "untyped";
|
|
148
152
|
}, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "noSequences", undefined>, import("@flint.fyi/core").Rule<{
|
|
153
|
+
readonly description: "Reports variable declarations that shadow JavaScript's restricted names.";
|
|
154
|
+
readonly id: "shadowedRestrictedNames";
|
|
155
|
+
readonly preset: "untyped";
|
|
156
|
+
}, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "shadowedRestrictedName", undefined>, import("@flint.fyi/core").Rule<{
|
|
149
157
|
readonly description: "Reports array literals with holes (sparse arrays).";
|
|
150
158
|
readonly id: "sparseArrays";
|
|
151
159
|
readonly preset: "logical";
|
|
@@ -170,10 +178,18 @@ export declare const ts: import("@flint.fyi/core").Plugin<import("@flint.fyi/cor
|
|
|
170
178
|
readonly id: "unicodeBOMs";
|
|
171
179
|
readonly preset: "stylistic";
|
|
172
180
|
}, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "noBOM", undefined>, import("@flint.fyi/core").Rule<{
|
|
181
|
+
readonly description: "Reports standalone block statements that don't create a meaningful scope.";
|
|
182
|
+
readonly id: "unnecessaryBlocks";
|
|
183
|
+
readonly preset: "stylistic";
|
|
184
|
+
}, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "unnecessaryBlock", undefined>, import("@flint.fyi/core").Rule<{
|
|
173
185
|
readonly description: "Reports catch clauses that only rethrow the caught error without modification.";
|
|
174
186
|
readonly id: "unnecessaryCatches";
|
|
175
187
|
readonly preset: "logical";
|
|
176
188
|
}, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "unnecessaryCatch", undefined>, import("@flint.fyi/core").Rule<{
|
|
189
|
+
readonly description: "Reports string concatenation using the + operator when both operands are string literals.";
|
|
190
|
+
readonly id: "unnecessaryConcatenation";
|
|
191
|
+
readonly preset: "stylistic";
|
|
192
|
+
}, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "unnecessaryConcatenation", undefined>, import("@flint.fyi/core").Rule<{
|
|
177
193
|
readonly description: "Reports negating the left operand of `in` or `instanceof` relations.";
|
|
178
194
|
readonly id: "unsafeNegations";
|
|
179
195
|
readonly preset: "untyped";
|
package/lib/plugin.js
CHANGED
|
@@ -12,6 +12,7 @@ import debuggerStatements from "./rules/debuggerStatements.js";
|
|
|
12
12
|
import defaultCaseLast from "./rules/defaultCaseLast.js";
|
|
13
13
|
import duplicateArguments from "./rules/duplicateArguments.js";
|
|
14
14
|
import elseIfDuplicates from "./rules/elseIfDuplicates.js";
|
|
15
|
+
import emptyBlocks from "./rules/emptyBlocks.js";
|
|
15
16
|
import emptyDestructures from "./rules/emptyDestructures.js";
|
|
16
17
|
import emptyStaticBlocks from "./rules/emptyStaticBlocks.js";
|
|
17
18
|
import exceptionAssignments from "./rules/exceptionAssignments.js";
|
|
@@ -35,13 +36,16 @@ import returnAssignments from "./rules/returnAssignments.js";
|
|
|
35
36
|
import selfAssignments from "./rules/selfAssignments.js";
|
|
36
37
|
import selfComparisons from "./rules/selfComparisons.js";
|
|
37
38
|
import sequences from "./rules/sequences.js";
|
|
39
|
+
import shadowedRestrictedNames from "./rules/shadowedRestrictedNames.js";
|
|
38
40
|
import sparseArrays from "./rules/sparseArrays.js";
|
|
39
41
|
import symbolDescriptions from "./rules/symbolDescriptions.js";
|
|
40
42
|
import typeofComparisons from "./rules/typeofComparisons.js";
|
|
41
43
|
import unassignedVariables from "./rules/unassignedVariables.js";
|
|
42
44
|
import undefinedVariables from "./rules/undefinedVariables.js";
|
|
43
45
|
import unicodeBOMs from "./rules/unicodeBOMs.js";
|
|
46
|
+
import unnecessaryBlocks from "./rules/unnecessaryBlocks.js";
|
|
44
47
|
import unnecessaryCatches from "./rules/unnecessaryCatches.js";
|
|
48
|
+
import unnecessaryConcatenation from "./rules/unnecessaryConcatenation.js";
|
|
45
49
|
import unsafeNegations from "./rules/unsafeNegations.js";
|
|
46
50
|
import variableDeletions from "./rules/variableDeletions.js";
|
|
47
51
|
import voidOperator from "./rules/voidOperator.js";
|
|
@@ -49,7 +53,7 @@ export const ts = createPlugin({
|
|
|
49
53
|
files: {
|
|
50
54
|
all: ["**/*.{cjs,js,jsx,mjs,ts,tsx}"],
|
|
51
55
|
},
|
|
52
|
-
name: "
|
|
56
|
+
name: "TypeScript",
|
|
53
57
|
rules: [
|
|
54
58
|
anyReturns,
|
|
55
59
|
asyncPromiseExecutors,
|
|
@@ -64,6 +68,7 @@ export const ts = createPlugin({
|
|
|
64
68
|
defaultCaseLast,
|
|
65
69
|
duplicateArguments,
|
|
66
70
|
elseIfDuplicates,
|
|
71
|
+
emptyBlocks,
|
|
67
72
|
emptyDestructures,
|
|
68
73
|
emptyStaticBlocks,
|
|
69
74
|
exceptionAssignments,
|
|
@@ -87,13 +92,16 @@ export const ts = createPlugin({
|
|
|
87
92
|
selfAssignments,
|
|
88
93
|
selfComparisons,
|
|
89
94
|
sequences,
|
|
95
|
+
shadowedRestrictedNames,
|
|
90
96
|
sparseArrays,
|
|
91
97
|
symbolDescriptions,
|
|
92
98
|
typeofComparisons,
|
|
93
99
|
unassignedVariables,
|
|
94
100
|
undefinedVariables,
|
|
95
101
|
unicodeBOMs,
|
|
102
|
+
unnecessaryBlocks,
|
|
96
103
|
unnecessaryCatches,
|
|
104
|
+
unnecessaryConcatenation,
|
|
97
105
|
unsafeNegations,
|
|
98
106
|
variableDeletions,
|
|
99
107
|
voidOperator,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 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 {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import rule from "./emptyBlocks.js";
|
|
2
|
+
import { ruleTester } from "./ruleTester.js";
|
|
3
|
+
ruleTester.describe(rule, {
|
|
4
|
+
invalid: [
|
|
5
|
+
{
|
|
6
|
+
code: `
|
|
7
|
+
if (condition) {}
|
|
8
|
+
`,
|
|
9
|
+
snapshot: `
|
|
10
|
+
if (condition) {}
|
|
11
|
+
~~
|
|
12
|
+
Empty block statements should be removed or contain code.
|
|
13
|
+
`,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
code: `
|
|
17
|
+
if (condition) {
|
|
18
|
+
} else {}
|
|
19
|
+
`,
|
|
20
|
+
snapshot: `
|
|
21
|
+
if (condition) {
|
|
22
|
+
~
|
|
23
|
+
Empty block statements should be removed or contain code.
|
|
24
|
+
} else {}
|
|
25
|
+
~
|
|
26
|
+
~~
|
|
27
|
+
Empty block statements should be removed or contain code.
|
|
28
|
+
`,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
code: `
|
|
32
|
+
while (condition) {}
|
|
33
|
+
`,
|
|
34
|
+
snapshot: `
|
|
35
|
+
while (condition) {}
|
|
36
|
+
~~
|
|
37
|
+
Empty block statements should be removed or contain code.
|
|
38
|
+
`,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
code: `
|
|
42
|
+
for (let i = 0; i < 10; i++) {}
|
|
43
|
+
`,
|
|
44
|
+
snapshot: `
|
|
45
|
+
for (let i = 0; i < 10; i++) {}
|
|
46
|
+
~~
|
|
47
|
+
Empty block statements should be removed or contain code.
|
|
48
|
+
`,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
code: `
|
|
52
|
+
switch (value) {}
|
|
53
|
+
`,
|
|
54
|
+
snapshot: `
|
|
55
|
+
switch (value) {}
|
|
56
|
+
~~
|
|
57
|
+
Empty block statements should be removed or contain code.
|
|
58
|
+
`,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
code: `
|
|
62
|
+
do {} while (condition);
|
|
63
|
+
`,
|
|
64
|
+
snapshot: `
|
|
65
|
+
do {} while (condition);
|
|
66
|
+
~~
|
|
67
|
+
Empty block statements should be removed or contain code.
|
|
68
|
+
`,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
code: `
|
|
72
|
+
if (x) {
|
|
73
|
+
doSomething();
|
|
74
|
+
} else if (y) {}
|
|
75
|
+
`,
|
|
76
|
+
snapshot: `
|
|
77
|
+
if (x) {
|
|
78
|
+
doSomething();
|
|
79
|
+
} else if (y) {}
|
|
80
|
+
~~
|
|
81
|
+
Empty block statements should be removed or contain code.
|
|
82
|
+
`,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
code: `
|
|
86
|
+
try {
|
|
87
|
+
doSomething();
|
|
88
|
+
} finally {}
|
|
89
|
+
`,
|
|
90
|
+
snapshot: `
|
|
91
|
+
try {
|
|
92
|
+
doSomething();
|
|
93
|
+
} finally {}
|
|
94
|
+
~~
|
|
95
|
+
Empty block statements should be removed or contain code.
|
|
96
|
+
`,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
valid: [
|
|
100
|
+
`if (condition) { doSomething(); }`,
|
|
101
|
+
`while (condition) { doSomething(); }`,
|
|
102
|
+
`for (let i = 0; i < 10; i++) { doSomething(); }`,
|
|
103
|
+
`switch (value) { case 1: break; }`,
|
|
104
|
+
`do { doSomething(); } while (condition);`,
|
|
105
|
+
`function test() {}`,
|
|
106
|
+
`const fn = function() {};`,
|
|
107
|
+
`const arrow = () => {};`,
|
|
108
|
+
`class MyClass { method() {} }`,
|
|
109
|
+
`class MyClass { constructor() {} }`,
|
|
110
|
+
`class MyClass { get value() {} }`,
|
|
111
|
+
`class MyClass { set value(v) {} }`,
|
|
112
|
+
`
|
|
113
|
+
if (condition) {
|
|
114
|
+
// Intentionally empty
|
|
115
|
+
}
|
|
116
|
+
`,
|
|
117
|
+
`
|
|
118
|
+
while (condition) {
|
|
119
|
+
/* Do nothing */
|
|
120
|
+
}
|
|
121
|
+
`,
|
|
122
|
+
`
|
|
123
|
+
try {
|
|
124
|
+
doSomething();
|
|
125
|
+
} catch (error) {}
|
|
126
|
+
`,
|
|
127
|
+
`
|
|
128
|
+
try {
|
|
129
|
+
doSomething();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
// Ignore errors
|
|
132
|
+
}
|
|
133
|
+
`,
|
|
134
|
+
],
|
|
135
|
+
});
|
|
@@ -7,6 +7,13 @@ function getConditionDirection(condition, counterName) {
|
|
|
7
7
|
}
|
|
8
8
|
const leftName = getCounterName(condition.left);
|
|
9
9
|
const rightName = getCounterName(condition.right);
|
|
10
|
+
if (!leftName && !rightName) {
|
|
11
|
+
return condition.operatorToken.kind ===
|
|
12
|
+
ts.SyntaxKind.AmpersandAmpersandToken
|
|
13
|
+
? (getConditionDirection(condition.left, counterName) ??
|
|
14
|
+
getConditionDirection(condition.right, counterName))
|
|
15
|
+
: undefined;
|
|
16
|
+
}
|
|
10
17
|
const isCounterLeft = leftName === counterName;
|
|
11
18
|
const isCounterRight = rightName === counterName;
|
|
12
19
|
if (!isCounterLeft && !isCounterRight) {
|
|
@@ -38,10 +45,10 @@ function getConditionDirection(condition, counterName) {
|
|
|
38
45
|
function getCounterName(node) {
|
|
39
46
|
return ts.isIdentifier(node) ? node.text : undefined;
|
|
40
47
|
}
|
|
41
|
-
function
|
|
42
|
-
if (ts.isPostfixUnaryExpression(
|
|
43
|
-
ts.isPrefixUnaryExpression(
|
|
44
|
-
switch (
|
|
48
|
+
function getIncrementorDirection(incrementor) {
|
|
49
|
+
if (ts.isPostfixUnaryExpression(incrementor) ||
|
|
50
|
+
ts.isPrefixUnaryExpression(incrementor)) {
|
|
51
|
+
switch (incrementor.operator) {
|
|
45
52
|
case ts.SyntaxKind.MinusMinusToken:
|
|
46
53
|
return -1;
|
|
47
54
|
case ts.SyntaxKind.PlusPlusToken:
|
|
@@ -50,8 +57,8 @@ function getUpdateDirection(update) {
|
|
|
50
57
|
return undefined;
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
|
-
if (ts.isBinaryExpression(
|
|
54
|
-
const { operatorToken, right } =
|
|
60
|
+
if (ts.isBinaryExpression(incrementor)) {
|
|
61
|
+
const { operatorToken, right } = incrementor;
|
|
55
62
|
if (operatorToken.kind === ts.SyntaxKind.PlusEqualsToken ||
|
|
56
63
|
operatorToken.kind === ts.SyntaxKind.MinusEqualsToken) {
|
|
57
64
|
if (ts.isNumericLiteral(right)) {
|
|
@@ -106,12 +113,12 @@ export default typescriptLanguage.createRule({
|
|
|
106
113
|
if (!declaration || !ts.isIdentifier(declaration.name)) {
|
|
107
114
|
return;
|
|
108
115
|
}
|
|
109
|
-
const updateDirection =
|
|
116
|
+
const updateDirection = getIncrementorDirection(node.incrementor);
|
|
110
117
|
if (!updateDirection) {
|
|
111
118
|
return;
|
|
112
119
|
}
|
|
113
120
|
const conditionDirection = getConditionDirection(node.condition, declaration.name.text);
|
|
114
|
-
if (
|
|
121
|
+
if (conditionDirection && conditionDirection !== updateDirection) {
|
|
115
122
|
context.report({
|
|
116
123
|
message: "wrongDirection",
|
|
117
124
|
range: getTSNodeRange(node.incrementor, context.sourceFile),
|
|
@@ -80,6 +80,30 @@ for (let index = 0; index < 10; index += -1) {}
|
|
|
80
80
|
for (let index = 0; index < 10; index += -1) {}
|
|
81
81
|
~~~~~~~~~~~
|
|
82
82
|
The update moves the counter in the wrong direction for this loop condition.
|
|
83
|
+
`,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
code: `
|
|
87
|
+
declare context text: string;
|
|
88
|
+
for (let i = 10; i >= 0 && text[i] !== " "; i++) { }
|
|
89
|
+
`,
|
|
90
|
+
snapshot: `
|
|
91
|
+
declare context text: string;
|
|
92
|
+
for (let i = 10; i >= 0 && text[i] !== " "; i++) { }
|
|
93
|
+
~~~
|
|
94
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
95
|
+
`,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
code: `
|
|
99
|
+
declare context text: string;
|
|
100
|
+
for (let i = 10; text[i] !== " " && i >= 0; i++) { }
|
|
101
|
+
`,
|
|
102
|
+
snapshot: `
|
|
103
|
+
declare context text: string;
|
|
104
|
+
for (let i = 10; text[i] !== " " && i >= 0; i++) { }
|
|
105
|
+
~~~
|
|
106
|
+
The update moves the counter in the wrong direction for this loop condition.
|
|
83
107
|
`,
|
|
84
108
|
},
|
|
85
109
|
],
|
|
@@ -95,5 +119,6 @@ for (let index = 0; index < 10; index += -1) {}
|
|
|
95
119
|
`for (let index = 10; index > 0; index -= 1) { }`,
|
|
96
120
|
`for (let index = 0; index < 10; index += 2) { }`,
|
|
97
121
|
`for (let index = 10; index > 0; index -= 2) { }`,
|
|
122
|
+
`for (let i = 10; i >= 0 && text[i] === "\\"; i--) { }`,
|
|
98
123
|
],
|
|
99
124
|
});
|
|
@@ -159,8 +159,8 @@ function calculate() {
|
|
|
159
159
|
snapshot: `
|
|
160
160
|
function calculate() {
|
|
161
161
|
return 077;
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
~~~
|
|
163
|
+
This legacy octal numeric literal evaluates to 63. Did you mean that value, or to use a modern octal syntax such as 0o77?
|
|
164
164
|
}
|
|
165
165
|
`,
|
|
166
166
|
suggestions: [
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
+
readonly description: "Reports variable declarations that shadow JavaScript's restricted names.";
|
|
3
|
+
readonly id: "shadowedRestrictedNames";
|
|
4
|
+
readonly preset: "untyped";
|
|
5
|
+
}, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "shadowedRestrictedName", undefined>;
|
|
6
|
+
export default _default;
|