@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.
- package/CHANGELOG.md +16 -0
- package/lib/createTypeScriptFileFromProgram.js +5 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/language.d.ts +1 -0
- package/lib/plugin.d.ts +61 -1
- package/lib/plugin.js +30 -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/chainedAssignments.js +2 -1
- package/lib/rules/chainedAssignments.test.js +24 -0
- package/lib/rules/debuggerStatements.test.js +4 -4
- 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/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.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/octalNumbers.test.js +2 -2
- 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/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/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/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/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/rules/voidOperator.test.js +2 -2
- 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/getDeclarationsIfGlobal.d.ts +2 -0
- package/lib/utils/getDeclarationsIfGlobal.js +7 -0
- package/lib/utils/hasSameTokens.d.ts +2 -0
- package/lib/utils/hasSameTokens.js +30 -0
- package/lib/utils/isGlobalDeclaration.js +2 -3
- package/lib/utils/isGlobalDeclarationOfName.d.ts +5 -0
- package/lib/utils/isGlobalDeclarationOfName.js +35 -0
- package/package.json +3 -3
- package/src/createTypeScriptFileFromProgram.ts +6 -1
- package/src/index.ts +3 -0
- package/src/language.ts +1 -0
- package/src/plugin.ts +30 -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/chainedAssignments.test.ts +24 -0
- package/src/rules/chainedAssignments.ts +4 -1
- package/src/rules/debuggerStatements.test.ts +4 -4
- package/src/rules/elseIfDuplicates.test.ts +177 -0
- package/src/rules/elseIfDuplicates.ts +69 -0
- package/src/rules/emptyBlocks.test.ts +136 -0
- package/src/rules/emptyBlocks.ts +73 -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/octalNumbers.test.ts +2 -2
- 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/shadowedRestrictedNames.test.ts +148 -0
- package/src/rules/shadowedRestrictedNames.ts +105 -0
- package/src/rules/symbolDescriptions.test.ts +4 -4
- package/src/rules/typeofComparisons.test.ts +86 -0
- package/src/rules/typeofComparisons.ts +88 -0
- 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/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/rules/voidOperator.test.ts +2 -2
- package/src/utils/declarationIncludesGlobal.ts +9 -0
- package/src/utils/declarationsIncludeGlobal.ts +3 -7
- package/src/utils/getDeclarationsIfGlobal.ts +14 -0
- package/src/utils/hasSameTokens.ts +45 -0
- package/src/utils/isGlobalDeclaration.ts +3 -5
- package/src/utils/isGlobalDeclarationOfName.ts +56 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import rule from "./nonOctalDecimalEscapes.js";
|
|
2
|
+
import { ruleTester } from "./ruleTester.js";
|
|
3
|
+
ruleTester.describe(rule, {
|
|
4
|
+
invalid: [
|
|
5
|
+
{
|
|
6
|
+
code: `
|
|
7
|
+
"\\8"
|
|
8
|
+
`,
|
|
9
|
+
snapshot: `
|
|
10
|
+
"\\8"
|
|
11
|
+
~~
|
|
12
|
+
Non-octal decimal escape sequences (\\8 and \\9) should not be used in string literals.
|
|
13
|
+
`,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
code: `
|
|
17
|
+
"\\9"
|
|
18
|
+
`,
|
|
19
|
+
snapshot: `
|
|
20
|
+
"\\9"
|
|
21
|
+
~~
|
|
22
|
+
Non-octal decimal escape sequences (\\8 and \\9) should not be used in string literals.
|
|
23
|
+
`,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
code: `
|
|
27
|
+
"w\\8less"
|
|
28
|
+
`,
|
|
29
|
+
snapshot: `
|
|
30
|
+
"w\\8less"
|
|
31
|
+
~~
|
|
32
|
+
Non-octal decimal escape sequences (\\8 and \\9) should not be used in string literals.
|
|
33
|
+
`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
code: `
|
|
37
|
+
"December 1\\9"
|
|
38
|
+
`,
|
|
39
|
+
snapshot: `
|
|
40
|
+
"December 1\\9"
|
|
41
|
+
~~
|
|
42
|
+
Non-octal decimal escape sequences (\\8 and \\9) should not be used in string literals.
|
|
43
|
+
`,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
code: `
|
|
47
|
+
"Don't use \\8 and \\9 escapes."
|
|
48
|
+
`,
|
|
49
|
+
snapshot: `
|
|
50
|
+
"Don't use \\8 and \\9 escapes."
|
|
51
|
+
~~
|
|
52
|
+
Non-octal decimal escape sequences (\\8 and \\9) should not be used in string literals.
|
|
53
|
+
~~
|
|
54
|
+
Non-octal decimal escape sequences (\\8 and \\9) should not be used in string literals.
|
|
55
|
+
`,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
code: `
|
|
59
|
+
"\\0\\8"
|
|
60
|
+
`,
|
|
61
|
+
snapshot: `
|
|
62
|
+
"\\0\\8"
|
|
63
|
+
~~
|
|
64
|
+
Non-octal decimal escape sequences (\\8 and \\9) should not be used in string literals.
|
|
65
|
+
`,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
valid: ['"8"', '"9"', '"w8less"', '"December 19"', '"\\\\8"', '"\\0\\u0038"'],
|
|
69
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
+
readonly description: "Reports parseInt calls with binary, hexadecimal, or octal strings that can be replaced with numeric literals.";
|
|
3
|
+
readonly id: "numericLiteralParsing";
|
|
4
|
+
readonly preset: "stylistic";
|
|
5
|
+
}, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "preferLiteral", undefined>;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import { getTSNodeRange } from "../getTSNodeRange.js";
|
|
3
|
+
import { typescriptLanguage } from "../language.js";
|
|
4
|
+
import { isGlobalDeclaration } from "../utils/isGlobalDeclaration.js";
|
|
5
|
+
function convertToLiteral(value, radix) {
|
|
6
|
+
const parsed = Number.parseInt(value, radix);
|
|
7
|
+
if (Number.isNaN(parsed)) {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
switch (radix) {
|
|
11
|
+
case 2:
|
|
12
|
+
return `0b${parsed.toString(2)}`;
|
|
13
|
+
case 8:
|
|
14
|
+
return `0o${parsed.toString(8)}`;
|
|
15
|
+
case 16:
|
|
16
|
+
return `0x${parsed.toString(16).toUpperCase()}`;
|
|
17
|
+
default:
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function getRadixValue(node) {
|
|
22
|
+
if (!ts.isNumericLiteral(node)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const value = Number(node.text);
|
|
26
|
+
if (![2, 8, 16].includes(value)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
// TODO: Use a util like getStaticValue
|
|
32
|
+
function getStringValue(node) {
|
|
33
|
+
return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)
|
|
34
|
+
? node.text
|
|
35
|
+
: undefined;
|
|
36
|
+
}
|
|
37
|
+
export default typescriptLanguage.createRule({
|
|
38
|
+
about: {
|
|
39
|
+
description: "Reports parseInt calls with binary, hexadecimal, or octal strings that can be replaced with numeric literals.",
|
|
40
|
+
id: "numericLiteralParsing",
|
|
41
|
+
preset: "stylistic",
|
|
42
|
+
},
|
|
43
|
+
messages: {
|
|
44
|
+
preferLiteral: {
|
|
45
|
+
primary: "Use {{ literal }} instead of parseInt with radix {{ radix }}.",
|
|
46
|
+
secondary: [
|
|
47
|
+
"Binary, octal, and hexadecimal numeric literals are more readable and direct than using parseInt with a radix.",
|
|
48
|
+
"Numeric literals are supported natively and don't require function calls.",
|
|
49
|
+
],
|
|
50
|
+
suggestions: ["Replace the parseInt call with the numeric literal."],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
setup(context) {
|
|
54
|
+
function checkParseIntCall(node) {
|
|
55
|
+
if (node.arguments.length !== 2) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const stringValue = getStringValue(node.arguments[0]);
|
|
59
|
+
if (!stringValue) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const radixValue = getRadixValue(node.arguments[1]);
|
|
63
|
+
if (!radixValue) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
context.report({
|
|
67
|
+
data: {
|
|
68
|
+
literal: convertToLiteral(stringValue, radixValue),
|
|
69
|
+
radix: String(radixValue),
|
|
70
|
+
},
|
|
71
|
+
message: "preferLiteral",
|
|
72
|
+
range: getTSNodeRange(node, context.sourceFile),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
visitors: {
|
|
77
|
+
CallExpression: (node) => {
|
|
78
|
+
if (ts.isIdentifier(node.expression)) {
|
|
79
|
+
if (node.expression.text === "parseInt" &&
|
|
80
|
+
isGlobalDeclaration(node.expression, context.typeChecker)) {
|
|
81
|
+
checkParseIntCall(node);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (ts.isPropertyAccessExpression(node.expression)) {
|
|
85
|
+
if (ts.isIdentifier(node.expression.expression) &&
|
|
86
|
+
node.expression.expression.text === "Number" &&
|
|
87
|
+
ts.isIdentifier(node.expression.name) &&
|
|
88
|
+
node.expression.name.text === "parseInt" &&
|
|
89
|
+
isGlobalDeclaration(node.expression.expression, context.typeChecker)) {
|
|
90
|
+
checkParseIntCall(node);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import rule from "./numericLiteralParsing.js";
|
|
2
|
+
import { ruleTester } from "./ruleTester.js";
|
|
3
|
+
ruleTester.describe(rule, {
|
|
4
|
+
invalid: [
|
|
5
|
+
{
|
|
6
|
+
code: `
|
|
7
|
+
const binary = parseInt("111110111", 2);
|
|
8
|
+
`,
|
|
9
|
+
snapshot: `
|
|
10
|
+
const binary = parseInt("111110111", 2);
|
|
11
|
+
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
12
|
+
Use 0b111110111 instead of parseInt with radix 2.
|
|
13
|
+
`,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
code: `
|
|
17
|
+
const octal = parseInt("767", 8);
|
|
18
|
+
`,
|
|
19
|
+
snapshot: `
|
|
20
|
+
const octal = parseInt("767", 8);
|
|
21
|
+
~~~~~~~~~~~~~~~~~~
|
|
22
|
+
Use 0o767 instead of parseInt with radix 8.
|
|
23
|
+
`,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
code: `
|
|
27
|
+
const hex = parseInt("1F7", 16);
|
|
28
|
+
`,
|
|
29
|
+
snapshot: `
|
|
30
|
+
const hex = parseInt("1F7", 16);
|
|
31
|
+
~~~~~~~~~~~~~~~~~~~
|
|
32
|
+
Use 0x1F7 instead of parseInt with radix 16.
|
|
33
|
+
`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
code: `
|
|
37
|
+
const value = Number.parseInt("111110111", 2);
|
|
38
|
+
`,
|
|
39
|
+
snapshot: `
|
|
40
|
+
const value = Number.parseInt("111110111", 2);
|
|
41
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
42
|
+
Use 0b111110111 instead of parseInt with radix 2.
|
|
43
|
+
`,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
code: `
|
|
47
|
+
const result = Number.parseInt("767", 8);
|
|
48
|
+
`,
|
|
49
|
+
snapshot: `
|
|
50
|
+
const result = Number.parseInt("767", 8);
|
|
51
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
52
|
+
Use 0o767 instead of parseInt with radix 8.
|
|
53
|
+
`,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
code: `
|
|
57
|
+
const num = Number.parseInt("1F7", 16);
|
|
58
|
+
`,
|
|
59
|
+
snapshot: `
|
|
60
|
+
const num = Number.parseInt("1F7", 16);
|
|
61
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
62
|
+
Use 0x1F7 instead of parseInt with radix 16.
|
|
63
|
+
`,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
code: `
|
|
67
|
+
const value = parseInt(\`111110111\`, 2);
|
|
68
|
+
`,
|
|
69
|
+
snapshot: `
|
|
70
|
+
const value = parseInt(\`111110111\`, 2);
|
|
71
|
+
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
72
|
+
Use 0b111110111 instead of parseInt with radix 2.
|
|
73
|
+
`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
valid: [
|
|
77
|
+
`const decimal = parseInt("123");`,
|
|
78
|
+
`const value = parseInt("123", 10);`,
|
|
79
|
+
`const ternary = parseInt("102", 3);`,
|
|
80
|
+
`const binary = 0b111110111;`,
|
|
81
|
+
`const octal = 0o767;`,
|
|
82
|
+
`const hex = 0x1F7;`,
|
|
83
|
+
`const dynamic = parseInt(value, 2);`,
|
|
84
|
+
`const variable = parseInt("111", radix);`,
|
|
85
|
+
`const single = parseInt("111");`,
|
|
86
|
+
`Number.parseInt("123");`,
|
|
87
|
+
`Number.parseInt("123", 10);`,
|
|
88
|
+
`
|
|
89
|
+
function parseInt(...values: unknown[]) {}
|
|
90
|
+
parseInt("1", 2);
|
|
91
|
+
export {};
|
|
92
|
+
`,
|
|
93
|
+
`
|
|
94
|
+
const Number = { parseInt(...values: unknown[]) {} };
|
|
95
|
+
Number.parseInt("1", 2);
|
|
96
|
+
export {};
|
|
97
|
+
`,
|
|
98
|
+
],
|
|
99
|
+
});
|
|
@@ -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 self-assignments which have no effect and are likely errors.";
|
|
3
|
+
readonly id: "selfAssignments";
|
|
4
|
+
readonly preset: "logical";
|
|
5
|
+
}, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "selfAssignment", undefined>;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,44 @@
|
|
|
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 self-assignments which have no effect and are likely errors.",
|
|
8
|
+
id: "selfAssignments",
|
|
9
|
+
preset: "logical",
|
|
10
|
+
},
|
|
11
|
+
messages: {
|
|
12
|
+
selfAssignment: {
|
|
13
|
+
primary: "This value is being assigned to itself, which does nothing.",
|
|
14
|
+
secondary: [
|
|
15
|
+
"Self-assignments have no effect and typically indicate an incomplete refactoring or copy-paste error.",
|
|
16
|
+
],
|
|
17
|
+
suggestions: [
|
|
18
|
+
"Review the assignment and ensure you're assigning the correct value.",
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
setup(context) {
|
|
23
|
+
return {
|
|
24
|
+
visitors: {
|
|
25
|
+
BinaryExpression: (node) => {
|
|
26
|
+
if (node.operatorToken.kind !== ts.SyntaxKind.EqualsToken &&
|
|
27
|
+
node.operatorToken.kind !==
|
|
28
|
+
ts.SyntaxKind.AmpersandAmpersandEqualsToken &&
|
|
29
|
+
node.operatorToken.kind !== ts.SyntaxKind.BarBarEqualsToken &&
|
|
30
|
+
node.operatorToken.kind !==
|
|
31
|
+
ts.SyntaxKind.QuestionQuestionEqualsToken) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (hasSameTokens(node.left, node.right, context.sourceFile)) {
|
|
35
|
+
context.report({
|
|
36
|
+
message: "selfAssignment",
|
|
37
|
+
range: getTSNodeRange(node, context.sourceFile),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ruleTester } from "./ruleTester.js";
|
|
2
|
+
import rule from "./selfAssignments.js";
|
|
3
|
+
ruleTester.describe(rule, {
|
|
4
|
+
invalid: [
|
|
5
|
+
{
|
|
6
|
+
code: `
|
|
7
|
+
value = value;
|
|
8
|
+
`,
|
|
9
|
+
snapshot: `
|
|
10
|
+
value = value;
|
|
11
|
+
~~~~~~~~~~~~~
|
|
12
|
+
This value is being assigned to itself, which does nothing.
|
|
13
|
+
`,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
code: `
|
|
17
|
+
count &&= count;
|
|
18
|
+
`,
|
|
19
|
+
snapshot: `
|
|
20
|
+
count &&= count;
|
|
21
|
+
~~~~~~~~~~~~~~~
|
|
22
|
+
This value is being assigned to itself, which does nothing.
|
|
23
|
+
`,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
code: `
|
|
27
|
+
flag ||= flag;
|
|
28
|
+
`,
|
|
29
|
+
snapshot: `
|
|
30
|
+
flag ||= flag;
|
|
31
|
+
~~~~~~~~~~~~~
|
|
32
|
+
This value is being assigned to itself, which does nothing.
|
|
33
|
+
`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
code: `
|
|
37
|
+
data ??= data;
|
|
38
|
+
`,
|
|
39
|
+
snapshot: `
|
|
40
|
+
data ??= data;
|
|
41
|
+
~~~~~~~~~~~~~
|
|
42
|
+
This value is being assigned to itself, which does nothing.
|
|
43
|
+
`,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
code: `
|
|
47
|
+
a.b ??= a.b;
|
|
48
|
+
`,
|
|
49
|
+
snapshot: `
|
|
50
|
+
a.b ??= a.b;
|
|
51
|
+
~~~~~~~~~~~
|
|
52
|
+
This value is being assigned to itself, which does nothing.
|
|
53
|
+
`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
valid: [
|
|
57
|
+
`value = other;`,
|
|
58
|
+
`first = second;`,
|
|
59
|
+
`let value = value;`,
|
|
60
|
+
`const data = data;`,
|
|
61
|
+
`value += value;`,
|
|
62
|
+
`count -= count;`,
|
|
63
|
+
`a ||= b;`,
|
|
64
|
+
`a.b ||= a.c;`,
|
|
65
|
+
],
|
|
66
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
+
readonly description: "Reports comparing a value to itself.";
|
|
3
|
+
readonly id: "selfComparisons";
|
|
4
|
+
readonly preset: "logical";
|
|
5
|
+
}, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "noSelfComparison", undefined>;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { typescriptLanguage } from "../language.js";
|
|
2
|
+
import { hasSameTokens } from "../utils/hasSameTokens.js";
|
|
3
|
+
import { isComparisonOperator } from "./utils/operators.js";
|
|
4
|
+
export default typescriptLanguage.createRule({
|
|
5
|
+
about: {
|
|
6
|
+
description: "Reports comparing a value to itself.",
|
|
7
|
+
id: "selfComparisons",
|
|
8
|
+
preset: "logical",
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
noSelfComparison: {
|
|
12
|
+
primary: "Comparing a value to itself is unnecessary and likely indicates a logic error.",
|
|
13
|
+
secondary: [
|
|
14
|
+
"Self-comparisons always evaluate to the same result for a given operator.",
|
|
15
|
+
"This pattern often indicates a copy-paste error or typo where different variables were intended.",
|
|
16
|
+
],
|
|
17
|
+
suggestions: [
|
|
18
|
+
"Verify that you intended to compare two different values.",
|
|
19
|
+
"If checking for NaN, use `Number.isNaN()` or `isNaN()` instead.",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
setup(context) {
|
|
24
|
+
return {
|
|
25
|
+
visitors: {
|
|
26
|
+
BinaryExpression: (node) => {
|
|
27
|
+
if (isComparisonOperator(node.operatorToken) &&
|
|
28
|
+
hasSameTokens(node.left, node.right, context.sourceFile)) {
|
|
29
|
+
context.report({
|
|
30
|
+
message: "noSelfComparison",
|
|
31
|
+
range: {
|
|
32
|
+
begin: node.getStart(context.sourceFile),
|
|
33
|
+
end: node.getEnd(),
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|