@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,78 @@
1
+ import * as ts from "typescript";
2
+ import { getTSNodeRange } from "../getTSNodeRange.js";
3
+ import { typescriptLanguage } from "../language.js";
4
+ const validTypeofValues = new Set([
5
+ "bigint",
6
+ "boolean",
7
+ "function",
8
+ "number",
9
+ "object",
10
+ "string",
11
+ "symbol",
12
+ "undefined",
13
+ ]);
14
+ // TODO: Reuse a shared getStaticValue-style utility?
15
+ function getStringValue(node) {
16
+ return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)
17
+ ? node.text
18
+ : undefined;
19
+ }
20
+ function getTypeofOperand(node) {
21
+ return ts.isTypeOfExpression(node) && node.expression;
22
+ }
23
+ export default typescriptLanguage.createRule({
24
+ about: {
25
+ description: "Reports typeof expressions that compare impossible string literals.",
26
+ id: "typeofComparisons",
27
+ preset: "untyped",
28
+ },
29
+ messages: {
30
+ invalidValue: {
31
+ primary: "This string literal is not one that the typeof operator will ever produce.",
32
+ secondary: [
33
+ "The typeof operator returns one of a specific set of string values.",
34
+ "Comparing typeof to an invalid string is usually a typo and will never match.",
35
+ 'The only valid values are: "bigint", "boolean", "function", "number", "object", "string", "symbol", and "undefined".',
36
+ ],
37
+ suggestions: [
38
+ "Check for typos and use one of the valid typeof return values.",
39
+ ],
40
+ },
41
+ },
42
+ setup(context) {
43
+ function checkComparison(node) {
44
+ const leftTypeofOperand = getTypeofOperand(node.left);
45
+ const rightTypeofOperand = getTypeofOperand(node.right);
46
+ let comparisonValue;
47
+ if (leftTypeofOperand) {
48
+ comparisonValue = node.right;
49
+ }
50
+ else if (rightTypeofOperand) {
51
+ comparisonValue = node.left;
52
+ }
53
+ else {
54
+ return;
55
+ }
56
+ const stringValue = getStringValue(comparisonValue);
57
+ if (stringValue != null && !validTypeofValues.has(stringValue)) {
58
+ context.report({
59
+ message: "invalidValue",
60
+ range: getTSNodeRange(comparisonValue, context.sourceFile),
61
+ });
62
+ }
63
+ }
64
+ return {
65
+ visitors: {
66
+ BinaryExpression: (node) => {
67
+ if (node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken ||
68
+ node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken ||
69
+ node.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken ||
70
+ node.operatorToken.kind ===
71
+ ts.SyntaxKind.ExclamationEqualsEqualsToken) {
72
+ checkComparison(node);
73
+ }
74
+ },
75
+ },
76
+ };
77
+ },
78
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,85 @@
1
+ import { ruleTester } from "./ruleTester.js";
2
+ import rule from "./typeofComparisons.js";
3
+ ruleTester.describe(rule, {
4
+ invalid: [
5
+ {
6
+ code: `
7
+ if (typeof value === "") {
8
+ process();
9
+ }
10
+ `,
11
+ snapshot: `
12
+ if (typeof value === "") {
13
+ ~~
14
+ This string literal is not one that the typeof operator will ever produce.
15
+ process();
16
+ }
17
+ `,
18
+ },
19
+ {
20
+ code: `
21
+ if (typeof data != "invalid") {
22
+ reject();
23
+ }
24
+ `,
25
+ snapshot: `
26
+ if (typeof data != "invalid") {
27
+ ~~~~~~~~~
28
+ This string literal is not one that the typeof operator will ever produce.
29
+ reject();
30
+ }
31
+ `,
32
+ },
33
+ {
34
+ code: `
35
+ if ("invalid" === typeof flag) {
36
+ toggle();
37
+ }
38
+ `,
39
+ snapshot: `
40
+ if ("invalid" === typeof flag) {
41
+ ~~~~~~~~~
42
+ This string literal is not one that the typeof operator will ever produce.
43
+ toggle();
44
+ }
45
+ `,
46
+ },
47
+ {
48
+ code: `
49
+ if (typeof value === "String") {
50
+ process();
51
+ }
52
+ `,
53
+ snapshot: `
54
+ if (typeof value === "String") {
55
+ ~~~~~~~~
56
+ This string literal is not one that the typeof operator will ever produce.
57
+ process();
58
+ }
59
+ `,
60
+ },
61
+ {
62
+ code: `
63
+ const isValid = typeof input !== "array";
64
+ `,
65
+ snapshot: `
66
+ const isValid = typeof input !== "array";
67
+ ~~~~~~~
68
+ This string literal is not one that the typeof operator will ever produce.
69
+ `,
70
+ },
71
+ ],
72
+ valid: [
73
+ `if (typeof value === "string") { process(); }`,
74
+ `if (typeof variable == "undefined") { handle(); }`,
75
+ `if (typeof data != "number") { reject(); }`,
76
+ `if (typeof callback !== "function") { throw new Error("Invalid callback"); }`,
77
+ `if (typeof flag === "boolean") { toggle(); }`,
78
+ `if (typeof value === "object") { parse(); }`,
79
+ `if (typeof value === "symbol") { handle(); }`,
80
+ `if (typeof value === "bigint") { compute(); }`,
81
+ `if (typeof value === other) { process(); }`,
82
+ `if (typeof value === typeof other) { compare(); }`,
83
+ `const type = typeof value;`,
84
+ ],
85
+ });
@@ -1,3 +1,4 @@
1
+ // flint-disable-file invalidCodeLines -- This rule checks the first character of code files.
1
2
  import { ruleTester } from "./ruleTester.js";
2
3
  import rule from "./unicodeBOMs.js";
3
4
  ruleTester.describe(rule, {
@@ -0,0 +1,6 @@
1
+ declare const _default: import("@flint.fyi/core").Rule<{
2
+ readonly description: "Reports standalone block statements that don't create a meaningful scope.";
3
+ readonly id: "unnecessaryBlocks";
4
+ readonly preset: "stylistic";
5
+ }, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "unnecessaryBlock", undefined>;
6
+ export default _default;
@@ -0,0 +1,75 @@
1
+ import * as ts from "typescript";
2
+ import { typescriptLanguage } from "../language.js";
3
+ export default typescriptLanguage.createRule({
4
+ about: {
5
+ description: "Reports standalone block statements that don't create a meaningful scope.",
6
+ id: "unnecessaryBlocks",
7
+ preset: "stylistic",
8
+ },
9
+ messages: {
10
+ unnecessaryBlock: {
11
+ primary: "This standalone block statement is unnecessary and doesn't change any variable scopes.",
12
+ secondary: [
13
+ "Standalone block statements that aren't part of control flow (if/else, loops, switch) or don't create a meaningful lexical scope can be confusing and should be avoided.",
14
+ "In modern JavaScript and TypeScript, blocks primarily serve to create lexical scope for `let` and `const` variables, but this is often better achieved through other means.",
15
+ ],
16
+ suggestions: [
17
+ "Remove the block statement and move its contents to the parent scope.",
18
+ "If you need lexical scoping, consider using an IIFE (Immediately Invoked Function Expression) or extracting to a separate function.",
19
+ ],
20
+ },
21
+ },
22
+ setup(context) {
23
+ function isValidBlock(node) {
24
+ const parent = node.parent;
25
+ // Valid blocks: function bodies, arrow functions, class/interface bodies, etc.
26
+ if (ts.isFunctionDeclaration(parent) ||
27
+ ts.isFunctionExpression(parent) ||
28
+ ts.isArrowFunction(parent) ||
29
+ ts.isMethodDeclaration(parent) ||
30
+ ts.isConstructorDeclaration(parent) ||
31
+ ts.isGetAccessorDeclaration(parent) ||
32
+ ts.isSetAccessorDeclaration(parent) ||
33
+ ts.isModuleBlock(parent)) {
34
+ return true;
35
+ }
36
+ // Valid blocks: control flow statements
37
+ if (ts.isIfStatement(parent) ||
38
+ ts.isForStatement(parent) ||
39
+ ts.isForInStatement(parent) ||
40
+ ts.isForOfStatement(parent) ||
41
+ ts.isWhileStatement(parent) ||
42
+ ts.isDoStatement(parent) ||
43
+ ts.isWithStatement(parent) ||
44
+ ts.isTryStatement(parent) ||
45
+ ts.isCatchClause(parent)) {
46
+ return true;
47
+ }
48
+ // Valid block: switch case/default clause with block
49
+ // In ES6+, blocks in switch cases create scope for let/const
50
+ if (ts.isCaseClause(parent) || ts.isDefaultClause(parent)) {
51
+ return true;
52
+ }
53
+ // Valid block: labeled statement
54
+ if (ts.isLabeledStatement(parent)) {
55
+ return true;
56
+ }
57
+ return false;
58
+ }
59
+ return {
60
+ visitors: {
61
+ Block: (node) => {
62
+ if (!isValidBlock(node)) {
63
+ context.report({
64
+ message: "unnecessaryBlock",
65
+ range: {
66
+ begin: node.getStart(context.sourceFile),
67
+ end: node.getStart(context.sourceFile) + 1,
68
+ },
69
+ });
70
+ }
71
+ },
72
+ },
73
+ };
74
+ },
75
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,147 @@
1
+ import { ruleTester } from "./ruleTester.js";
2
+ import rule from "./unnecessaryBlocks.js";
3
+ ruleTester.describe(rule, {
4
+ invalid: [
5
+ {
6
+ code: `
7
+ {
8
+ const x = 1;
9
+ }
10
+ `,
11
+ snapshot: `
12
+ {
13
+ ~
14
+ This standalone block statement is unnecessary and doesn't change any variable scopes.
15
+ const x = 1;
16
+ }
17
+ `,
18
+ },
19
+ {
20
+ code: `
21
+ function test() {
22
+ {
23
+ const y = 2;
24
+ }
25
+ }
26
+ `,
27
+ snapshot: `
28
+ function test() {
29
+ {
30
+ ~
31
+ This standalone block statement is unnecessary and doesn't change any variable scopes.
32
+ const y = 2;
33
+ }
34
+ }
35
+ `,
36
+ },
37
+ {
38
+ code: `
39
+ if (condition) {
40
+ doSomething();
41
+ {
42
+ const nested = true;
43
+ }
44
+ }
45
+ `,
46
+ snapshot: `
47
+ if (condition) {
48
+ doSomething();
49
+ {
50
+ ~
51
+ This standalone block statement is unnecessary and doesn't change any variable scopes.
52
+ const nested = true;
53
+ }
54
+ }
55
+ `,
56
+ },
57
+ {
58
+ code: `
59
+ {
60
+ console.log("standalone");
61
+ }
62
+ `,
63
+ snapshot: `
64
+ {
65
+ ~
66
+ This standalone block statement is unnecessary and doesn't change any variable scopes.
67
+ console.log("standalone");
68
+ }
69
+ `,
70
+ },
71
+ {
72
+ code: `
73
+ function outer() {
74
+ {
75
+ console.log("inner");
76
+ }
77
+ {
78
+ console.log("another");
79
+ }
80
+ }
81
+ `,
82
+ snapshot: `
83
+ function outer() {
84
+ {
85
+ ~
86
+ This standalone block statement is unnecessary and doesn't change any variable scopes.
87
+ console.log("inner");
88
+ }
89
+ {
90
+ ~
91
+ This standalone block statement is unnecessary and doesn't change any variable scopes.
92
+ console.log("another");
93
+ }
94
+ }
95
+ `,
96
+ },
97
+ ],
98
+ valid: [
99
+ `if (condition) { doSomething(); }`,
100
+ `for (let i = 0; i < 10; i++) { console.log(i); }`,
101
+ `while (condition) { doWork(); }`,
102
+ `do { doWork(); } while (condition);`,
103
+ `function test() { return 42; }`,
104
+ `const arrow = () => { return 1; };`,
105
+ `
106
+ switch (value) {
107
+ case 1: {
108
+ const x = 1;
109
+ break;
110
+ }
111
+ case 2: {
112
+ const y = 2;
113
+ break;
114
+ }
115
+ }
116
+ `,
117
+ `
118
+ try {
119
+ doSomething();
120
+ } catch (error) {
121
+ handleError(error);
122
+ }
123
+ `,
124
+ `
125
+ class MyClass {
126
+ method() {
127
+ return 1;
128
+ }
129
+ }
130
+ `,
131
+ `
132
+ for (const item of items) {
133
+ processItem(item);
134
+ }
135
+ `,
136
+ `
137
+ for (const key in object) {
138
+ processKey(key);
139
+ }
140
+ `,
141
+ `
142
+ label: {
143
+ break label;
144
+ }
145
+ `,
146
+ ],
147
+ });
@@ -0,0 +1,6 @@
1
+ declare const _default: import("@flint.fyi/core").Rule<{
2
+ readonly description: "Reports string concatenation using the + operator when both operands are string literals.";
3
+ readonly id: "unnecessaryConcatenation";
4
+ readonly preset: "stylistic";
5
+ }, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "unnecessaryConcatenation", undefined>;
6
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import * as ts from "typescript";
2
+ import { getTSNodeRange } from "../getTSNodeRange.js";
3
+ import { typescriptLanguage } from "../language.js";
4
+ export default typescriptLanguage.createRule({
5
+ about: {
6
+ description: "Reports string concatenation using the + operator when both operands are string literals.",
7
+ id: "unnecessaryConcatenation",
8
+ preset: "stylistic",
9
+ },
10
+ messages: {
11
+ unnecessaryConcatenation: {
12
+ primary: "This string concatenation can be streamlined into a single string literal.",
13
+ secondary: [
14
+ "Concatenating string literals with the + operator is unnecessary and reduces code readability.",
15
+ "String literals can be combined into a single literal, which is clearer and potentially more performant.",
16
+ ],
17
+ suggestions: [
18
+ "Combine the string literals into a single string literal.",
19
+ ],
20
+ },
21
+ },
22
+ setup(context) {
23
+ return {
24
+ visitors: {
25
+ BinaryExpression: (node) => {
26
+ if (node.operatorToken.kind === ts.SyntaxKind.PlusToken &&
27
+ ts.isStringLiteral(node.left) &&
28
+ ts.isStringLiteral(node.right)) {
29
+ context.report({
30
+ message: "unnecessaryConcatenation",
31
+ range: getTSNodeRange(node.operatorToken, context.sourceFile),
32
+ });
33
+ }
34
+ },
35
+ },
36
+ };
37
+ },
38
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,87 @@
1
+ import { ruleTester } from "./ruleTester.js";
2
+ import rule from "./unnecessaryConcatenation.js";
3
+ ruleTester.describe(rule, {
4
+ invalid: [
5
+ {
6
+ code: `
7
+ const message = "Hello" + "World";
8
+ `,
9
+ snapshot: `
10
+ const message = "Hello" + "World";
11
+ ~
12
+ This string concatenation can be streamlined into a single string literal.
13
+ `,
14
+ },
15
+ {
16
+ code: `
17
+ const path = 'foo' + 'bar';
18
+ `,
19
+ snapshot: `
20
+ const path = 'foo' + 'bar';
21
+ ~
22
+ This string concatenation can be streamlined into a single string literal.
23
+ `,
24
+ },
25
+ {
26
+ code: `
27
+ const result = "abc" + "def" + "ghi";
28
+ `,
29
+ snapshot: `
30
+ const result = "abc" + "def" + "ghi";
31
+ ~
32
+ This string concatenation can be streamlined into a single string literal.
33
+ `,
34
+ },
35
+ {
36
+ code: `
37
+ const text = "first" + "second";
38
+ `,
39
+ snapshot: `
40
+ const text = "first" + "second";
41
+ ~
42
+ This string concatenation can be streamlined into a single string literal.
43
+ `,
44
+ },
45
+ {
46
+ code: `
47
+ const mixed = 'single' + "double";
48
+ `,
49
+ snapshot: `
50
+ const mixed = 'single' + "double";
51
+ ~
52
+ This string concatenation can be streamlined into a single string literal.
53
+ `,
54
+ },
55
+ {
56
+ code: `
57
+ const withSpace = "Hello " + "World";
58
+ `,
59
+ snapshot: `
60
+ const withSpace = "Hello " + "World";
61
+ ~
62
+ This string concatenation can be streamlined into a single string literal.
63
+ `,
64
+ },
65
+ {
66
+ code: `
67
+ const longString = "This is a very long string that " +
68
+ "continues on the next line";
69
+ `,
70
+ snapshot: `
71
+ const longString = "This is a very long string that " +
72
+ ~
73
+ This string concatenation can be streamlined into a single string literal.
74
+ "continues on the next line";
75
+ `,
76
+ },
77
+ ],
78
+ valid: [
79
+ `const message = "Hello" + variable;`,
80
+ `const message = variable + "World";`,
81
+ `const result = variable1 + variable2;`,
82
+ `const template = \`Hello\${name}World\`;`,
83
+ `const number = 1 + 2;`,
84
+ `const mixed = "Hello" + getName() + "World";`,
85
+ `const value = "prefix" + getValue();`,
86
+ ],
87
+ });
@@ -0,0 +1,12 @@
1
+ import * as ts from "typescript";
2
+ export declare enum AnyType {
3
+ Any = 0,
4
+ PromiseAny = 1,
5
+ AnyArray = 2,
6
+ Safe = 3
7
+ }
8
+ /**
9
+ * @returns `AnyType.Any` if the type is `any`, `AnyType.AnyArray` if the type is `any[]` or `readonly any[]`, `AnyType.PromiseAny` if the type is `Promise&lt;any>`,
10
+ * otherwise it returns `AnyType.Safe`.
11
+ */
12
+ export declare function discriminateAnyType(type: ts.Type, checker: ts.TypeChecker, program: ts.Program, tsNode: ts.Node): AnyType;
@@ -0,0 +1,41 @@
1
+ import * as tsutils from "ts-api-utils";
2
+ import * as ts from "typescript";
3
+ export var AnyType;
4
+ (function (AnyType) {
5
+ AnyType[AnyType["Any"] = 0] = "Any";
6
+ AnyType[AnyType["PromiseAny"] = 1] = "PromiseAny";
7
+ AnyType[AnyType["AnyArray"] = 2] = "AnyArray";
8
+ AnyType[AnyType["Safe"] = 3] = "Safe";
9
+ })(AnyType || (AnyType = {}));
10
+ /**
11
+ * @returns `AnyType.Any` if the type is `any`, `AnyType.AnyArray` if the type is `any[]` or `readonly any[]`, `AnyType.PromiseAny` if the type is `Promise&lt;any>`,
12
+ * otherwise it returns `AnyType.Safe`.
13
+ */
14
+ export function discriminateAnyType(type, checker, program, tsNode) {
15
+ return discriminateAnyTypeWorker(type, checker, program, tsNode, new Set());
16
+ }
17
+ function discriminateAnyTypeWorker(type, checker, program, tsNode, visited) {
18
+ if (visited.has(type)) {
19
+ return AnyType.Safe;
20
+ }
21
+ visited.add(type);
22
+ if (tsutils.isTypeFlagSet(type, ts.TypeFlags.Any)) {
23
+ return AnyType.Any;
24
+ }
25
+ if (checker.isArrayType(type) &&
26
+ tsutils.isTypeFlagSet(checker.getTypeArguments(type)[0], ts.TypeFlags.Any)) {
27
+ return AnyType.AnyArray;
28
+ }
29
+ for (const part of tsutils.typeConstituents(type)) {
30
+ if (tsutils.isThenableType(checker, tsNode, part)) {
31
+ const awaitedType = checker.getAwaitedType(part);
32
+ if (awaitedType) {
33
+ const awaitedAnyType = discriminateAnyTypeWorker(awaitedType, checker, program, tsNode, visited);
34
+ if (awaitedAnyType === AnyType.Any) {
35
+ return AnyType.PromiseAny;
36
+ }
37
+ }
38
+ }
39
+ }
40
+ return AnyType.Safe;
41
+ }
@@ -0,0 +1,2 @@
1
+ import * as ts from "typescript";
2
+ export declare function getThisExpression(node: ts.Node): null | ts.ThisExpression;
@@ -0,0 +1,23 @@
1
+ import * as tsutils from "ts-api-utils";
2
+ import * as ts from "typescript";
3
+ export function getThisExpression(node) {
4
+ while (true) {
5
+ node = skipParentheses(node);
6
+ if (ts.isCallExpression(node) || tsutils.isAccessExpression(node)) {
7
+ node = node.expression;
8
+ }
9
+ else if (tsutils.isThisExpression(node)) {
10
+ return node;
11
+ }
12
+ else {
13
+ break;
14
+ }
15
+ }
16
+ return null;
17
+ }
18
+ function skipParentheses(node) {
19
+ while (ts.isParenthesizedExpression(node)) {
20
+ node = node.expression;
21
+ }
22
+ return node;
23
+ }
@@ -0,0 +1,13 @@
1
+ import * as ts from "typescript";
2
+ /**
3
+ * Does a simple check to see if there is an any being assigned to a non-any type.
4
+ *
5
+ * This also checks generic positions to ensure there's no unsafe sub-assignments.
6
+ * Note: in the case of generic positions, it makes the assumption that the two types are the same.
7
+ * @example See tests for examples
8
+ * @returns false if it's safe, or an object with the two types if it's unsafe
9
+ */
10
+ export declare function isUnsafeAssignment(type: ts.Type, receiver: ts.Type, checker: ts.TypeChecker, senderNode: null | ts.Node): false | {
11
+ receiver: ts.Type;
12
+ sender: ts.Type;
13
+ };