diff --git a/.changeset/huge-signs-joke.md b/.changeset/huge-signs-joke.md new file mode 100644 index 000000000..18c419198 --- /dev/null +++ b/.changeset/huge-signs-joke.md @@ -0,0 +1,5 @@ +--- +"@flint.fyi/ts": patch +--- + +fix(ts): [forDirections] allow multi-part conditions diff --git a/packages/ts/src/rules/forDirections.test.ts b/packages/ts/src/rules/forDirections.test.ts index ce7fa483b..6932f356c 100644 --- a/packages/ts/src/rules/forDirections.test.ts +++ b/packages/ts/src/rules/forDirections.test.ts @@ -81,6 +81,30 @@ for (let index = 0; index < 10; index += -1) {} for (let index = 0; index < 10; index += -1) {} ~~~~~~~~~~~ The update moves the counter in the wrong direction for this loop condition. +`, + }, + { + code: ` +declare context text: string; +for (let i = 10; i >= 0 && text[i] !== " "; i++) { } +`, + snapshot: ` +declare context text: string; +for (let i = 10; i >= 0 && text[i] !== " "; i++) { } + ~~~ + The update moves the counter in the wrong direction for this loop condition. +`, + }, + { + code: ` +declare context text: string; +for (let i = 10; text[i] !== " " && i >= 0; i++) { } +`, + snapshot: ` +declare context text: string; +for (let i = 10; text[i] !== " " && i >= 0; i++) { } + ~~~ + The update moves the counter in the wrong direction for this loop condition. `, }, ], @@ -96,5 +120,6 @@ for (let index = 0; index < 10; index += -1) {} `for (let index = 10; index > 0; index -= 1) { }`, `for (let index = 0; index < 10; index += 2) { }`, `for (let index = 10; index > 0; index -= 2) { }`, + `for (let i = 10; i >= 0 && text[i] === "\\"; i--) { }`, ], }); diff --git a/packages/ts/src/rules/forDirections.ts b/packages/ts/src/rules/forDirections.ts index 9cb6d04da..72db84cc3 100644 --- a/packages/ts/src/rules/forDirections.ts +++ b/packages/ts/src/rules/forDirections.ts @@ -3,7 +3,10 @@ import * as ts from "typescript"; import { getTSNodeRange } from "../getTSNodeRange.js"; import { typescriptLanguage } from "../language.js"; -function getConditionDirection(condition: ts.Expression, counterName: string) { +function getConditionDirection( + condition: ts.Expression, + counterName: string, +): -1 | 1 | undefined { if (!ts.isBinaryExpression(condition)) { return undefined; } @@ -11,6 +14,14 @@ function getConditionDirection(condition: ts.Expression, counterName: string) { const leftName = getCounterName(condition.left); const rightName = getCounterName(condition.right); + if (!leftName && !rightName) { + return condition.operatorToken.kind === + ts.SyntaxKind.AmpersandAmpersandToken + ? (getConditionDirection(condition.left, counterName) ?? + getConditionDirection(condition.right, counterName)) + : undefined; + } + const isCounterLeft = leftName === counterName; const isCounterRight = rightName === counterName; @@ -55,12 +66,12 @@ function getCounterName(node: ts.Expression) { return ts.isIdentifier(node) ? node.text : undefined; } -function getUpdateDirection(update: ts.Expression) { +function getIncrementorDirection(incrementor: ts.Expression) { if ( - ts.isPostfixUnaryExpression(update) || - ts.isPrefixUnaryExpression(update) + ts.isPostfixUnaryExpression(incrementor) || + ts.isPrefixUnaryExpression(incrementor) ) { - switch (update.operator) { + switch (incrementor.operator) { case ts.SyntaxKind.MinusMinusToken: return -1; case ts.SyntaxKind.PlusPlusToken: @@ -70,8 +81,8 @@ function getUpdateDirection(update: ts.Expression) { } } - if (ts.isBinaryExpression(update)) { - const { operatorToken, right } = update; + if (ts.isBinaryExpression(incrementor)) { + const { operatorToken, right } = incrementor; if ( operatorToken.kind === ts.SyntaxKind.PlusEqualsToken || @@ -140,7 +151,7 @@ export default typescriptLanguage.createRule({ return; } - const updateDirection = getUpdateDirection(node.incrementor); + const updateDirection = getIncrementorDirection(node.incrementor); if (!updateDirection) { return; } @@ -150,7 +161,7 @@ export default typescriptLanguage.createRule({ declaration.name.text, ); - if (updateDirection !== conditionDirection) { + if (conditionDirection && conditionDirection !== updateDirection) { context.report({ message: "wrongDirection", range: getTSNodeRange(node.incrementor, context.sourceFile),