@flint.fyi/ts 0.14.4 → 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 (57) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/createTypeScriptFileFromProgram.js +4 -1
  3. package/lib/index.d.ts +3 -0
  4. package/lib/index.js +3 -0
  5. package/lib/plugin.d.ts +17 -1
  6. package/lib/plugin.js +8 -0
  7. package/lib/rules/chainedAssignments.js +2 -1
  8. package/lib/rules/chainedAssignments.test.js +24 -0
  9. package/lib/rules/debuggerStatements.test.js +4 -4
  10. package/lib/rules/emptyBlocks.d.ts +6 -0
  11. package/lib/rules/emptyBlocks.js +66 -0
  12. package/lib/rules/emptyBlocks.test.d.ts +1 -0
  13. package/lib/rules/emptyBlocks.test.js +135 -0
  14. package/lib/rules/octalNumbers.test.js +2 -2
  15. package/lib/rules/shadowedRestrictedNames.d.ts +6 -0
  16. package/lib/rules/shadowedRestrictedNames.js +95 -0
  17. package/lib/rules/shadowedRestrictedNames.test.d.ts +1 -0
  18. package/lib/rules/shadowedRestrictedNames.test.js +147 -0
  19. package/lib/rules/symbolDescriptions.test.js +4 -4
  20. package/lib/rules/unicodeBOMs.test.js +1 -0
  21. package/lib/rules/unnecessaryBlocks.d.ts +6 -0
  22. package/lib/rules/unnecessaryBlocks.js +75 -0
  23. package/lib/rules/unnecessaryBlocks.test.d.ts +1 -0
  24. package/lib/rules/unnecessaryBlocks.test.js +147 -0
  25. package/lib/rules/unnecessaryConcatenation.d.ts +6 -0
  26. package/lib/rules/unnecessaryConcatenation.js +38 -0
  27. package/lib/rules/unnecessaryConcatenation.test.d.ts +1 -0
  28. package/lib/rules/unnecessaryConcatenation.test.js +87 -0
  29. package/lib/rules/voidOperator.test.js +2 -2
  30. package/lib/utils/getDeclarationsIfGlobal.d.ts +2 -0
  31. package/lib/utils/getDeclarationsIfGlobal.js +7 -0
  32. package/lib/utils/isGlobalDeclaration.js +2 -3
  33. package/lib/utils/isGlobalDeclarationOfName.d.ts +1 -1
  34. package/lib/utils/isGlobalDeclarationOfName.js +4 -0
  35. package/package.json +3 -3
  36. package/src/createTypeScriptFileFromProgram.ts +5 -1
  37. package/src/index.ts +3 -0
  38. package/src/plugin.ts +8 -0
  39. package/src/rules/chainedAssignments.test.ts +24 -0
  40. package/src/rules/chainedAssignments.ts +4 -1
  41. package/src/rules/debuggerStatements.test.ts +4 -4
  42. package/src/rules/emptyBlocks.test.ts +136 -0
  43. package/src/rules/emptyBlocks.ts +73 -0
  44. package/src/rules/octalNumbers.test.ts +2 -2
  45. package/src/rules/shadowedRestrictedNames.test.ts +148 -0
  46. package/src/rules/shadowedRestrictedNames.ts +105 -0
  47. package/src/rules/symbolDescriptions.test.ts +4 -4
  48. package/src/rules/unicodeBOMs.test.ts +1 -0
  49. package/src/rules/unnecessaryBlocks.test.ts +148 -0
  50. package/src/rules/unnecessaryBlocks.ts +89 -0
  51. package/src/rules/unnecessaryConcatenation.test.ts +88 -0
  52. package/src/rules/unnecessaryConcatenation.ts +44 -0
  53. package/src/rules/voidOperator.test.ts +2 -2
  54. package/src/utils/getDeclarationsIfGlobal.ts +14 -0
  55. package/src/utils/isGlobalDeclaration.ts +3 -5
  56. package/src/utils/isGlobalDeclarationOfName.ts +6 -1
  57. package/tsconfig.tsbuildinfo +1 -1
package/src/plugin.ts CHANGED
@@ -13,6 +13,7 @@ import debuggerStatements from "./rules/debuggerStatements.js";
13
13
  import defaultCaseLast from "./rules/defaultCaseLast.js";
14
14
  import duplicateArguments from "./rules/duplicateArguments.js";
15
15
  import elseIfDuplicates from "./rules/elseIfDuplicates.js";
16
+ import emptyBlocks from "./rules/emptyBlocks.js";
16
17
  import emptyDestructures from "./rules/emptyDestructures.js";
17
18
  import emptyStaticBlocks from "./rules/emptyStaticBlocks.js";
18
19
  import exceptionAssignments from "./rules/exceptionAssignments.js";
@@ -36,13 +37,16 @@ import returnAssignments from "./rules/returnAssignments.js";
36
37
  import selfAssignments from "./rules/selfAssignments.js";
37
38
  import selfComparisons from "./rules/selfComparisons.js";
38
39
  import sequences from "./rules/sequences.js";
40
+ import shadowedRestrictedNames from "./rules/shadowedRestrictedNames.js";
39
41
  import sparseArrays from "./rules/sparseArrays.js";
40
42
  import symbolDescriptions from "./rules/symbolDescriptions.js";
41
43
  import typeofComparisons from "./rules/typeofComparisons.js";
42
44
  import unassignedVariables from "./rules/unassignedVariables.js";
43
45
  import undefinedVariables from "./rules/undefinedVariables.js";
44
46
  import unicodeBOMs from "./rules/unicodeBOMs.js";
47
+ import unnecessaryBlocks from "./rules/unnecessaryBlocks.js";
45
48
  import unnecessaryCatches from "./rules/unnecessaryCatches.js";
49
+ import unnecessaryConcatenation from "./rules/unnecessaryConcatenation.js";
46
50
  import unsafeNegations from "./rules/unsafeNegations.js";
47
51
  import variableDeletions from "./rules/variableDeletions.js";
48
52
  import voidOperator from "./rules/voidOperator.js";
@@ -66,6 +70,7 @@ export const ts = createPlugin({
66
70
  defaultCaseLast,
67
71
  duplicateArguments,
68
72
  elseIfDuplicates,
73
+ emptyBlocks,
69
74
  emptyDestructures,
70
75
  emptyStaticBlocks,
71
76
  exceptionAssignments,
@@ -89,13 +94,16 @@ export const ts = createPlugin({
89
94
  selfAssignments,
90
95
  selfComparisons,
91
96
  sequences,
97
+ shadowedRestrictedNames,
92
98
  sparseArrays,
93
99
  symbolDescriptions,
94
100
  typeofComparisons,
95
101
  unassignedVariables,
96
102
  undefinedVariables,
97
103
  unicodeBOMs,
104
+ unnecessaryBlocks,
98
105
  unnecessaryCatches,
106
+ unnecessaryConcatenation,
99
107
  unsafeNegations,
100
108
  variableDeletions,
101
109
  voidOperator,
@@ -234,6 +234,30 @@ class MyClass {
234
234
  second = 0;
235
235
  }
236
236
  }
237
+ `,
238
+ `
239
+ let result = "";
240
+ result += "a" + "b";
241
+ `,
242
+ `
243
+ let result = 0;
244
+ result += 1 + 2;
245
+ `,
246
+ `
247
+ let result = 0;
248
+ result += 1 * 2;
249
+ `,
250
+ `
251
+ let result = 0;
252
+ result += 1 - 2;
253
+ `,
254
+ `
255
+ let result = 0;
256
+ result += 1 / 2;
257
+ `,
258
+ `
259
+ let result = 0;
260
+ result += 1 ** 2;
237
261
  `,
238
262
  ],
239
263
  });
@@ -35,7 +35,10 @@ export default typescriptLanguage.createRule({
35
35
  }
36
36
 
37
37
  const rightSide = unwrapParenthesizedExpression(node.right);
38
- if (!ts.isBinaryExpression(rightSide)) {
38
+ if (
39
+ !ts.isBinaryExpression(rightSide) ||
40
+ !tsutils.isAssignmentKind(rightSide.operatorToken.kind)
41
+ ) {
39
42
  return;
40
43
  }
41
44
 
@@ -30,8 +30,8 @@ function test() {
30
30
  snapshot: `
31
31
  function test() {
32
32
  debugger;
33
- ~~~~~~~~~
34
- Debugger statements should not be used in production code.
33
+ ~~~~~~~~~
34
+ Debugger statements should not be used in production code.
35
35
  }
36
36
  `,
37
37
  suggestions: [
@@ -54,8 +54,8 @@ if (condition) {
54
54
  snapshot: `
55
55
  if (condition) {
56
56
  debugger;
57
- ~~~~~~~~~
58
- Debugger statements should not be used in production code.
57
+ ~~~~~~~~~
58
+ Debugger statements should not be used in production code.
59
59
  }
60
60
  `,
61
61
  suggestions: [
@@ -0,0 +1,136 @@
1
+ import rule from "./emptyBlocks.js";
2
+ import { ruleTester } from "./ruleTester.js";
3
+
4
+ ruleTester.describe(rule, {
5
+ invalid: [
6
+ {
7
+ code: `
8
+ if (condition) {}
9
+ `,
10
+ snapshot: `
11
+ if (condition) {}
12
+ ~~
13
+ Empty block statements should be removed or contain code.
14
+ `,
15
+ },
16
+ {
17
+ code: `
18
+ if (condition) {
19
+ } else {}
20
+ `,
21
+ snapshot: `
22
+ if (condition) {
23
+ ~
24
+ Empty block statements should be removed or contain code.
25
+ } else {}
26
+ ~
27
+ ~~
28
+ Empty block statements should be removed or contain code.
29
+ `,
30
+ },
31
+ {
32
+ code: `
33
+ while (condition) {}
34
+ `,
35
+ snapshot: `
36
+ while (condition) {}
37
+ ~~
38
+ Empty block statements should be removed or contain code.
39
+ `,
40
+ },
41
+ {
42
+ code: `
43
+ for (let i = 0; i < 10; i++) {}
44
+ `,
45
+ snapshot: `
46
+ for (let i = 0; i < 10; i++) {}
47
+ ~~
48
+ Empty block statements should be removed or contain code.
49
+ `,
50
+ },
51
+ {
52
+ code: `
53
+ switch (value) {}
54
+ `,
55
+ snapshot: `
56
+ switch (value) {}
57
+ ~~
58
+ Empty block statements should be removed or contain code.
59
+ `,
60
+ },
61
+ {
62
+ code: `
63
+ do {} while (condition);
64
+ `,
65
+ snapshot: `
66
+ do {} while (condition);
67
+ ~~
68
+ Empty block statements should be removed or contain code.
69
+ `,
70
+ },
71
+ {
72
+ code: `
73
+ if (x) {
74
+ doSomething();
75
+ } else if (y) {}
76
+ `,
77
+ snapshot: `
78
+ if (x) {
79
+ doSomething();
80
+ } else if (y) {}
81
+ ~~
82
+ Empty block statements should be removed or contain code.
83
+ `,
84
+ },
85
+ {
86
+ code: `
87
+ try {
88
+ doSomething();
89
+ } finally {}
90
+ `,
91
+ snapshot: `
92
+ try {
93
+ doSomething();
94
+ } finally {}
95
+ ~~
96
+ Empty block statements should be removed or contain code.
97
+ `,
98
+ },
99
+ ],
100
+ valid: [
101
+ `if (condition) { doSomething(); }`,
102
+ `while (condition) { doSomething(); }`,
103
+ `for (let i = 0; i < 10; i++) { doSomething(); }`,
104
+ `switch (value) { case 1: break; }`,
105
+ `do { doSomething(); } while (condition);`,
106
+ `function test() {}`,
107
+ `const fn = function() {};`,
108
+ `const arrow = () => {};`,
109
+ `class MyClass { method() {} }`,
110
+ `class MyClass { constructor() {} }`,
111
+ `class MyClass { get value() {} }`,
112
+ `class MyClass { set value(v) {} }`,
113
+ `
114
+ if (condition) {
115
+ // Intentionally empty
116
+ }
117
+ `,
118
+ `
119
+ while (condition) {
120
+ /* Do nothing */
121
+ }
122
+ `,
123
+ `
124
+ try {
125
+ doSomething();
126
+ } catch (error) {}
127
+ `,
128
+ `
129
+ try {
130
+ doSomething();
131
+ } catch (error) {
132
+ // Ignore errors
133
+ }
134
+ `,
135
+ ],
136
+ });
@@ -0,0 +1,73 @@
1
+ import * as ts from "typescript";
2
+
3
+ import { getTSNodeRange } from "../getTSNodeRange.js";
4
+ import { typescriptLanguage } from "../language.js";
5
+
6
+ const allowedParents = new Set([
7
+ ts.SyntaxKind.ArrowFunction,
8
+ ts.SyntaxKind.CatchClause,
9
+ ts.SyntaxKind.Constructor,
10
+ ts.SyntaxKind.FunctionDeclaration,
11
+ ts.SyntaxKind.FunctionExpression,
12
+ ts.SyntaxKind.GetAccessor,
13
+ ts.SyntaxKind.MethodDeclaration,
14
+ ts.SyntaxKind.SetAccessor,
15
+ ]);
16
+
17
+ export default typescriptLanguage.createRule({
18
+ about: {
19
+ description: "Reports empty block statements that should contain code.",
20
+ id: "emptyBlocks",
21
+ preset: "stylistic",
22
+ },
23
+ messages: {
24
+ emptyBlock: {
25
+ primary: "Empty block statements should be removed or contain code.",
26
+ secondary: [
27
+ "Empty blocks can indicate incomplete code or areas where logic was removed but the block structure was left behind.",
28
+ "They can also reduce code readability by cluttering the codebase with unnecessary braces.",
29
+ ],
30
+ suggestions: [
31
+ "Add a comment explaining why the block is intentionally empty, or remove the empty block entirely.",
32
+ ],
33
+ },
34
+ },
35
+ setup(context) {
36
+ function hasComments(block: ts.Block): boolean {
37
+ const fullText = context.sourceFile.getFullText();
38
+
39
+ const openBrace = block.getStart(context.sourceFile);
40
+ const closeBrace = block.getEnd();
41
+ const innerText = fullText.substring(openBrace + 1, closeBrace - 1);
42
+
43
+ // Check if there are any non-whitespace characters (which would be comments)
44
+ // since we already know there are no statements
45
+ return /\S+/.test(innerText.trim());
46
+ }
47
+
48
+ function isEmptyBlock(block: ts.Block): boolean {
49
+ return block.statements.length === 0 && !hasComments(block);
50
+ }
51
+
52
+ return {
53
+ visitors: {
54
+ Block: (node) => {
55
+ if (!allowedParents.has(node.parent.kind) && isEmptyBlock(node)) {
56
+ context.report({
57
+ message: "emptyBlock",
58
+ range: getTSNodeRange(node, context.sourceFile),
59
+ });
60
+ }
61
+ },
62
+ CaseBlock: (node) => {
63
+ if (node.clauses.length === 0) {
64
+ context.report({
65
+ message: "emptyBlock",
66
+ range: getTSNodeRange(node, context.sourceFile),
67
+ });
68
+ }
69
+ },
70
+ },
71
+ };
72
+ },
73
+ });
@@ -160,8 +160,8 @@ function calculate() {
160
160
  snapshot: `
161
161
  function calculate() {
162
162
  return 077;
163
- ~~~
164
- This legacy octal numeric literal evaluates to 63. Did you mean that value, or to use a modern octal syntax such as 0o77?
163
+ ~~~
164
+ This legacy octal numeric literal evaluates to 63. Did you mean that value, or to use a modern octal syntax such as 0o77?
165
165
  }
166
166
  `,
167
167
  suggestions: [
@@ -0,0 +1,148 @@
1
+ import { ruleTester } from "./ruleTester.js";
2
+ import rule from "./shadowedRestrictedNames.js";
3
+
4
+ ruleTester.describe(rule, {
5
+ invalid: [
6
+ {
7
+ code: `
8
+ let undefined = 5;
9
+ `,
10
+ snapshot: `
11
+ let undefined = 5;
12
+ ~~~~~~~~~
13
+ This variable misleadingly shadows the global \`undefined\`.
14
+ `,
15
+ },
16
+ {
17
+ code: `
18
+ const NaN = 123;
19
+ `,
20
+ snapshot: `
21
+ const NaN = 123;
22
+ ~~~
23
+ This variable misleadingly shadows the global \`NaN\`.
24
+ `,
25
+ },
26
+ {
27
+ code: `
28
+ var Infinity = 100;
29
+ `,
30
+ snapshot: `
31
+ var Infinity = 100;
32
+ ~~~~~~~~
33
+ This variable misleadingly shadows the global \`Infinity\`.
34
+ `,
35
+ },
36
+ {
37
+ code: `
38
+ function test(arguments) {
39
+ return arguments.length;
40
+ }
41
+ `,
42
+ snapshot: `
43
+ function test(arguments) {
44
+ ~~~~~~~~~
45
+ This variable misleadingly shadows the global \`arguments\`.
46
+ return arguments.length;
47
+ }
48
+ `,
49
+ },
50
+ {
51
+ code: `
52
+ function eval() {
53
+ return 42;
54
+ }
55
+ `,
56
+ snapshot: `
57
+ function eval() {
58
+ ~~~~
59
+ This variable misleadingly shadows the global \`eval\`.
60
+ return 42;
61
+ }
62
+ `,
63
+ },
64
+ {
65
+ code: `
66
+ const arrowFunc = (undefined) => undefined;
67
+ `,
68
+ snapshot: `
69
+ const arrowFunc = (undefined) => undefined;
70
+ ~~~~~~~~~
71
+ This variable misleadingly shadows the global \`undefined\`.
72
+ `,
73
+ },
74
+ {
75
+ code: `
76
+ class NaN {
77
+ constructor() {}
78
+ }
79
+ `,
80
+ snapshot: `
81
+ class NaN {
82
+ ~~~
83
+ This variable misleadingly shadows the global \`NaN\`.
84
+ constructor() {}
85
+ }
86
+ `,
87
+ },
88
+ {
89
+ code: `
90
+ const obj = {
91
+ method(eval) {
92
+ return eval;
93
+ }
94
+ };
95
+ `,
96
+ snapshot: `
97
+ const obj = {
98
+ method(eval) {
99
+ ~~~~
100
+ This variable misleadingly shadows the global \`eval\`.
101
+ return eval;
102
+ }
103
+ };
104
+ `,
105
+ },
106
+ {
107
+ code: `
108
+ function test() {
109
+ const { undefined } = obj;
110
+ }
111
+ `,
112
+ snapshot: `
113
+ function test() {
114
+ const { undefined } = obj;
115
+ ~~~~~~~~~
116
+ This variable misleadingly shadows the global \`undefined\`.
117
+ }
118
+ `,
119
+ },
120
+ {
121
+ code: `
122
+ function test() {
123
+ const [NaN] = array;
124
+ }
125
+ `,
126
+ snapshot: `
127
+ function test() {
128
+ const [NaN] = array;
129
+ ~~~
130
+ This variable misleadingly shadows the global \`NaN\`.
131
+ }
132
+ `,
133
+ },
134
+ ],
135
+ valid: [
136
+ `let value = undefined;`,
137
+ `const result = NaN;`,
138
+ `const max = Infinity;`,
139
+ `function test() { return arguments; }`,
140
+ `const code = eval("1 + 1");`,
141
+ `let myValue = 5;`,
142
+ `function myFunction() {}`,
143
+ `class MyClass {}`,
144
+ `const obj = { undefined: 5 };`,
145
+ `const key = "undefined";`,
146
+ `obj.undefined = 5;`,
147
+ ],
148
+ });
@@ -0,0 +1,105 @@
1
+ import * as ts from "typescript";
2
+
3
+ import { getTSNodeRange } from "../getTSNodeRange.js";
4
+ import { typescriptLanguage } from "../language.js";
5
+
6
+ const restrictedNames = new Set([
7
+ "arguments",
8
+ "eval",
9
+ "Infinity",
10
+ "NaN",
11
+ "undefined",
12
+ ]);
13
+
14
+ export default typescriptLanguage.createRule({
15
+ about: {
16
+ description:
17
+ "Reports variable declarations that shadow JavaScript's restricted names.",
18
+ id: "shadowedRestrictedNames",
19
+ preset: "untyped",
20
+ },
21
+ messages: {
22
+ shadowedRestrictedName: {
23
+ primary: "This variable misleadingly shadows the global `{{ name }}`.",
24
+ secondary: [
25
+ "JavaScript has certain built-in global identifiers that are considered restricted because shadowing them can lead to confusing or erroneous code.",
26
+ "When you declare a variable with a restricted name, it shadows the global identifier and can cause unexpected behavior.",
27
+ ],
28
+ suggestions: [
29
+ "Use a different variable name that doesn't shadow a restricted name.",
30
+ ],
31
+ },
32
+ },
33
+ setup(context) {
34
+ function checkIdentifier(node: ts.Identifier): void {
35
+ if (restrictedNames.has(node.text)) {
36
+ context.report({
37
+ data: {
38
+ name: node.text,
39
+ },
40
+ message: "shadowedRestrictedName",
41
+ range: getTSNodeRange(node, context.sourceFile),
42
+ });
43
+ }
44
+ }
45
+
46
+ function checkBindingName(name: ts.BindingName): void {
47
+ if (ts.isIdentifier(name)) {
48
+ checkIdentifier(name);
49
+ } else if (
50
+ ts.isObjectBindingPattern(name) ||
51
+ ts.isArrayBindingPattern(name)
52
+ ) {
53
+ for (const element of name.elements) {
54
+ if (ts.isBindingElement(element)) {
55
+ checkBindingName(element.name);
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ function checkParameters(
62
+ parameters: ts.NodeArray<ts.ParameterDeclaration>,
63
+ ): void {
64
+ for (const parameter of parameters) {
65
+ checkBindingName(parameter.name);
66
+ }
67
+ }
68
+
69
+ return {
70
+ visitors: {
71
+ ArrowFunction: (node) => {
72
+ checkParameters(node.parameters);
73
+ },
74
+ ClassDeclaration: (node) => {
75
+ if (node.name) {
76
+ checkIdentifier(node.name);
77
+ }
78
+ },
79
+ ClassExpression: (node) => {
80
+ if (node.name) {
81
+ checkIdentifier(node.name);
82
+ }
83
+ },
84
+ FunctionDeclaration: (node) => {
85
+ if (node.name) {
86
+ checkIdentifier(node.name);
87
+ }
88
+ checkParameters(node.parameters);
89
+ },
90
+ FunctionExpression: (node) => {
91
+ if (node.name) {
92
+ checkIdentifier(node.name);
93
+ }
94
+ checkParameters(node.parameters);
95
+ },
96
+ MethodDeclaration: (node) => {
97
+ checkParameters(node.parameters);
98
+ },
99
+ VariableDeclaration: (node) => {
100
+ checkBindingName(node.name);
101
+ },
102
+ },
103
+ };
104
+ },
105
+ });
@@ -44,8 +44,8 @@ function createSymbol() {
44
44
  snapshot: `
45
45
  function createSymbol() {
46
46
  return Symbol();
47
- ~~~~~~~~
48
- Symbols without descriptions are more difficult to debug or reason about.
47
+ ~~~~~~~~
48
+ Symbols without descriptions are more difficult to debug or reason about.
49
49
  }
50
50
  `,
51
51
  },
@@ -58,8 +58,8 @@ const obj = {
58
58
  snapshot: `
59
59
  const obj = {
60
60
  key: Symbol(),
61
- ~~~~~~~~
62
- Symbols without descriptions are more difficult to debug or reason about.
61
+ ~~~~~~~~
62
+ Symbols without descriptions are more difficult to debug or reason about.
63
63
  };
64
64
  `,
65
65
  },
@@ -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