@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/createTypeScriptFileFromProgram.js +4 -1
  3. package/lib/formatDiagnostic.js +1 -1
  4. package/lib/index.d.ts +3 -0
  5. package/lib/index.js +3 -0
  6. package/lib/normalizeRange.js +3 -6
  7. package/lib/plugin.d.ts +17 -1
  8. package/lib/plugin.js +9 -1
  9. package/lib/rules/chainedAssignments.js +2 -1
  10. package/lib/rules/chainedAssignments.test.js +24 -0
  11. package/lib/rules/debuggerStatements.test.js +4 -4
  12. package/lib/rules/emptyBlocks.d.ts +6 -0
  13. package/lib/rules/emptyBlocks.js +66 -0
  14. package/lib/rules/emptyBlocks.test.d.ts +1 -0
  15. package/lib/rules/emptyBlocks.test.js +135 -0
  16. package/lib/rules/forDirections.js +15 -8
  17. package/lib/rules/forDirections.test.js +25 -0
  18. package/lib/rules/octalNumbers.test.js +2 -2
  19. package/lib/rules/shadowedRestrictedNames.d.ts +6 -0
  20. package/lib/rules/shadowedRestrictedNames.js +95 -0
  21. package/lib/rules/shadowedRestrictedNames.test.d.ts +1 -0
  22. package/lib/rules/shadowedRestrictedNames.test.js +147 -0
  23. package/lib/rules/symbolDescriptions.test.js +4 -4
  24. package/lib/rules/unicodeBOMs.test.js +1 -0
  25. package/lib/rules/unnecessaryBlocks.d.ts +6 -0
  26. package/lib/rules/unnecessaryBlocks.js +75 -0
  27. package/lib/rules/unnecessaryBlocks.test.d.ts +1 -0
  28. package/lib/rules/unnecessaryBlocks.test.js +147 -0
  29. package/lib/rules/unnecessaryConcatenation.d.ts +6 -0
  30. package/lib/rules/unnecessaryConcatenation.js +38 -0
  31. package/lib/rules/unnecessaryConcatenation.test.d.ts +1 -0
  32. package/lib/rules/unnecessaryConcatenation.test.js +87 -0
  33. package/lib/rules/voidOperator.test.js +2 -2
  34. package/lib/utils/getDeclarationsIfGlobal.d.ts +2 -0
  35. package/lib/utils/getDeclarationsIfGlobal.js +7 -0
  36. package/lib/utils/isGlobalDeclaration.js +2 -3
  37. package/lib/utils/isGlobalDeclarationOfName.d.ts +1 -1
  38. package/lib/utils/isGlobalDeclarationOfName.js +4 -0
  39. package/package.json +3 -3
  40. package/src/createTypeScriptFileFromProgram.ts +5 -1
  41. package/src/formatDiagnostic.ts +1 -1
  42. package/src/index.ts +3 -0
  43. package/src/normalizeRange.ts +3 -8
  44. package/src/plugin.ts +9 -1
  45. package/src/rules/chainedAssignments.test.ts +24 -0
  46. package/src/rules/chainedAssignments.ts +4 -1
  47. package/src/rules/debuggerStatements.test.ts +4 -4
  48. package/src/rules/emptyBlocks.test.ts +136 -0
  49. package/src/rules/emptyBlocks.ts +73 -0
  50. package/src/rules/forDirections.test.ts +25 -0
  51. package/src/rules/forDirections.ts +20 -9
  52. package/src/rules/octalNumbers.test.ts +2 -2
  53. package/src/rules/shadowedRestrictedNames.test.ts +148 -0
  54. package/src/rules/shadowedRestrictedNames.ts +105 -0
  55. package/src/rules/symbolDescriptions.test.ts +4 -4
  56. package/src/rules/unicodeBOMs.test.ts +1 -0
  57. package/src/rules/unnecessaryBlocks.test.ts +148 -0
  58. package/src/rules/unnecessaryBlocks.ts +89 -0
  59. package/src/rules/unnecessaryConcatenation.test.ts +88 -0
  60. package/src/rules/unnecessaryConcatenation.ts +44 -0
  61. package/src/rules/voidOperator.test.ts +2 -2
  62. package/src/utils/getDeclarationsIfGlobal.ts +14 -0
  63. package/src/utils/isGlobalDeclaration.ts +3 -5
  64. package/src/utils/isGlobalDeclarationOfName.ts +6 -1
  65. package/tsconfig.tsbuildinfo +1 -1
@@ -16,6 +16,10 @@ export function isGlobalDeclarationOfName(node, name, typeChecker) {
16
16
  declaration.initializer.kind === ts.SyntaxKind.Identifier) {
17
17
  return isGlobalDeclarationOfName(declaration.initializer, name, typeChecker);
18
18
  }
19
+ // Special case: a property of an interface
20
+ if (ts.isPropertySignature(declaration)) {
21
+ return isGlobalDeclarationOfName(declaration.parent, name, typeChecker);
22
+ }
19
23
  return (isDeclarationOfName(declaration, name) &&
20
24
  declarationIncludesGlobal(declaration));
21
25
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flint.fyi/ts",
3
- "version": "0.14.4",
3
+ "version": "0.14.6",
4
4
  "description": "[Experimental] TypeScript language plugin for Flint.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,12 +17,12 @@
17
17
  "dependencies": {
18
18
  "@flint.fyi/core": "",
19
19
  "@flint.fyi/rule-tester": "",
20
- "@typescript-eslint/project-service": "^8.35.0",
20
+ "@typescript-eslint/project-service": "^8.46.2",
21
21
  "@typescript/vfs": "^1.6.1",
22
22
  "cached-factory": "^0.1.0",
23
23
  "debug-for-file": "^0.2.0",
24
24
  "ts-api-utils": "^2.1.0",
25
- "typescript": ">=5.8.0"
25
+ "typescript": ">=5.9.3"
26
26
  },
27
27
  "engines": {
28
28
  "node": ">=24.0.0"
@@ -53,6 +53,10 @@ export function createTypeScriptFileFromProgram(
53
53
  report: (report: RuleReport) => {
54
54
  reports.push({
55
55
  ...report,
56
+ fix:
57
+ report.fix && !Array.isArray(report.fix)
58
+ ? [report.fix]
59
+ : report.fix,
56
60
  message: rule.messages[report.message],
57
61
  range: normalizeRange(report.range, sourceFile),
58
62
  });
@@ -74,7 +78,7 @@ export function createTypeScriptFileFromProgram(
74
78
  node.forEachChild(visit);
75
79
  };
76
80
 
77
- sourceFile.forEachChild(visit);
81
+ visit(sourceFile);
78
82
 
79
83
  return reports;
80
84
  },
@@ -138,7 +138,7 @@ function formatCodeSpan(
138
138
  gutterSeparator;
139
139
  context += squiggleColor;
140
140
  if (i === firstLine) {
141
- const lastCharForLine = i === lastLine ? lastLineChar : void 0;
141
+ const lastCharForLine = i === lastLine ? lastLineChar : undefined;
142
142
  context += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
143
143
  context += lineContent
144
144
  .slice(firstLineChar, lastCharForLine)
package/src/index.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";
@@ -2,6 +2,7 @@ import type * as ts from "typescript";
2
2
 
3
3
  import {
4
4
  CharacterReportRange,
5
+ getColumnAndLineOfPosition,
5
6
  NormalizedReportRangeObject,
6
7
  } from "@flint.fyi/core";
7
8
 
@@ -14,17 +15,11 @@ export function normalizeRange(
14
15
  : original;
15
16
 
16
17
  return {
17
- begin: normalizeRangePosition(onCharacters.begin, sourceFile),
18
- end: normalizeRangePosition(onCharacters.end, sourceFile),
18
+ begin: getColumnAndLineOfPosition(sourceFile, onCharacters.begin),
19
+ end: getColumnAndLineOfPosition(sourceFile, onCharacters.end),
19
20
  };
20
21
  }
21
22
 
22
23
  function isNode(value: unknown): value is ts.Node {
23
24
  return typeof value === "object" && value !== null && "kind" in value;
24
25
  }
25
-
26
- function normalizeRangePosition(raw: number, sourceFile: ts.SourceFile) {
27
- const { character, line } = sourceFile.getLineAndCharacterOfPosition(raw);
28
-
29
- return { column: character, line, raw };
30
- }
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";
@@ -51,7 +55,7 @@ export const ts = createPlugin({
51
55
  files: {
52
56
  all: ["**/*.{cjs,js,jsx,mjs,ts,tsx}"],
53
57
  },
54
- name: "ts",
58
+ name: "TypeScript",
55
59
  rules: [
56
60
  anyReturns,
57
61
  asyncPromiseExecutors,
@@ -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
+ });
@@ -81,6 +81,30 @@ for (let index = 0; index < 10; index += -1) {}
81
81
  for (let index = 0; index < 10; index += -1) {}
82
82
  ~~~~~~~~~~~
83
83
  The update moves the counter in the wrong direction for this loop condition.
84
+ `,
85
+ },
86
+ {
87
+ code: `
88
+ declare context text: string;
89
+ for (let i = 10; i >= 0 && text[i] !== " "; i++) { }
90
+ `,
91
+ snapshot: `
92
+ declare context text: string;
93
+ for (let i = 10; i >= 0 && text[i] !== " "; i++) { }
94
+ ~~~
95
+ The update moves the counter in the wrong direction for this loop condition.
96
+ `,
97
+ },
98
+ {
99
+ code: `
100
+ declare context text: string;
101
+ for (let i = 10; text[i] !== " " && i >= 0; i++) { }
102
+ `,
103
+ snapshot: `
104
+ declare context text: string;
105
+ for (let i = 10; text[i] !== " " && i >= 0; i++) { }
106
+ ~~~
107
+ The update moves the counter in the wrong direction for this loop condition.
84
108
  `,
85
109
  },
86
110
  ],
@@ -96,5 +120,6 @@ for (let index = 0; index < 10; index += -1) {}
96
120
  `for (let index = 10; index > 0; index -= 1) { }`,
97
121
  `for (let index = 0; index < 10; index += 2) { }`,
98
122
  `for (let index = 10; index > 0; index -= 2) { }`,
123
+ `for (let i = 10; i >= 0 && text[i] === "\\"; i--) { }`,
99
124
  ],
100
125
  });
@@ -3,7 +3,10 @@ import * as ts from "typescript";
3
3
  import { getTSNodeRange } from "../getTSNodeRange.js";
4
4
  import { typescriptLanguage } from "../language.js";
5
5
 
6
- function getConditionDirection(condition: ts.Expression, counterName: string) {
6
+ function getConditionDirection(
7
+ condition: ts.Expression,
8
+ counterName: string,
9
+ ): -1 | 1 | undefined {
7
10
  if (!ts.isBinaryExpression(condition)) {
8
11
  return undefined;
9
12
  }
@@ -11,6 +14,14 @@ function getConditionDirection(condition: ts.Expression, counterName: string) {
11
14
  const leftName = getCounterName(condition.left);
12
15
  const rightName = getCounterName(condition.right);
13
16
 
17
+ if (!leftName && !rightName) {
18
+ return condition.operatorToken.kind ===
19
+ ts.SyntaxKind.AmpersandAmpersandToken
20
+ ? (getConditionDirection(condition.left, counterName) ??
21
+ getConditionDirection(condition.right, counterName))
22
+ : undefined;
23
+ }
24
+
14
25
  const isCounterLeft = leftName === counterName;
15
26
  const isCounterRight = rightName === counterName;
16
27
 
@@ -55,12 +66,12 @@ function getCounterName(node: ts.Expression) {
55
66
  return ts.isIdentifier(node) ? node.text : undefined;
56
67
  }
57
68
 
58
- function getUpdateDirection(update: ts.Expression) {
69
+ function getIncrementorDirection(incrementor: ts.Expression) {
59
70
  if (
60
- ts.isPostfixUnaryExpression(update) ||
61
- ts.isPrefixUnaryExpression(update)
71
+ ts.isPostfixUnaryExpression(incrementor) ||
72
+ ts.isPrefixUnaryExpression(incrementor)
62
73
  ) {
63
- switch (update.operator) {
74
+ switch (incrementor.operator) {
64
75
  case ts.SyntaxKind.MinusMinusToken:
65
76
  return -1;
66
77
  case ts.SyntaxKind.PlusPlusToken:
@@ -70,8 +81,8 @@ function getUpdateDirection(update: ts.Expression) {
70
81
  }
71
82
  }
72
83
 
73
- if (ts.isBinaryExpression(update)) {
74
- const { operatorToken, right } = update;
84
+ if (ts.isBinaryExpression(incrementor)) {
85
+ const { operatorToken, right } = incrementor;
75
86
 
76
87
  if (
77
88
  operatorToken.kind === ts.SyntaxKind.PlusEqualsToken ||
@@ -140,7 +151,7 @@ export default typescriptLanguage.createRule({
140
151
  return;
141
152
  }
142
153
 
143
- const updateDirection = getUpdateDirection(node.incrementor);
154
+ const updateDirection = getIncrementorDirection(node.incrementor);
144
155
  if (!updateDirection) {
145
156
  return;
146
157
  }
@@ -150,7 +161,7 @@ export default typescriptLanguage.createRule({
150
161
  declaration.name.text,
151
162
  );
152
163
 
153
- if (updateDirection !== conditionDirection) {
164
+ if (conditionDirection && conditionDirection !== updateDirection) {
154
165
  context.report({
155
166
  message: "wrongDirection",
156
167
  range: getTSNodeRange(node.incrementor, context.sourceFile),
@@ -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: [