diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md index 48b98849c41..4bc160e68cc 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md @@ -53,6 +53,16 @@ function head(items: T[]) { } ``` +- `allowConstantLoopConditions` (default `false`) - allows constant expressions in loops. + +Example of correct code for when `allowConstantLoopConditions` is `true`: + +```ts +while (true) {} +for (; true; ) {} +do {} while (true); +``` + ## When Not To Use It The main downside to using this rule is the need for type information. diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 8e75f7adb3f..420271a23b8 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -41,15 +41,9 @@ const isLiteral = (type: ts.Type): boolean => isLiteralType(type); // #endregion -type ExpressionWithTest = - | TSESTree.ConditionalExpression - | TSESTree.DoWhileStatement - | TSESTree.ForStatement - | TSESTree.IfStatement - | TSESTree.WhileStatement; - export type Options = [ { + allowConstantLoopConditions?: boolean; ignoreRhs?: boolean; }, ]; @@ -74,6 +68,9 @@ export default createRule({ { type: 'object', properties: { + allowConstantLoopConditions: { + type: 'boolean', + }, ignoreRhs: { type: 'boolean', }, @@ -91,10 +88,11 @@ export default createRule({ }, defaultOptions: [ { + allowConstantLoopConditions: false, ignoreRhs: false, }, ], - create(context, [{ ignoreRhs }]) { + create(context, [{ allowConstantLoopConditions, ignoreRhs }]) { const service = getParserServices(context); const checker = service.program.getTypeChecker(); @@ -165,14 +163,13 @@ export default createRule({ * Filters all LogicalExpressions to prevent some duplicate reports. */ function checkIfTestExpressionIsNecessaryConditional( - node: ExpressionWithTest, + node: TSESTree.ConditionalExpression | TSESTree.IfStatement, ): void { - if ( - node.test !== null && - node.test.type !== AST_NODE_TYPES.LogicalExpression - ) { - checkNode(node.test); + if (node.test.type === AST_NODE_TYPES.LogicalExpression) { + return; } + + checkNode(node.test); } /** @@ -187,14 +184,46 @@ export default createRule({ } } + /** + * Checks that a testable expression of a loop is necessarily conditional, reports otherwise. + */ + function checkIfLoopIsNecessaryConditional( + node: + | TSESTree.DoWhileStatement + | TSESTree.ForStatement + | TSESTree.WhileStatement, + ): void { + if ( + node.test === null || + node.test.type === AST_NODE_TYPES.LogicalExpression + ) { + return; + } + + /** + * Allow: + * while (true) {} + * for (;true;) {} + * do {} while (true) + */ + if ( + allowConstantLoopConditions && + isBooleanLiteralType(getNodeType(node.test), true) + ) { + return; + } + + checkNode(node.test); + } + return { BinaryExpression: checkIfBinaryExpressionIsNecessaryConditional, ConditionalExpression: checkIfTestExpressionIsNecessaryConditional, - DoWhileStatement: checkIfTestExpressionIsNecessaryConditional, - ForStatement: checkIfTestExpressionIsNecessaryConditional, + DoWhileStatement: checkIfLoopIsNecessaryConditional, + ForStatement: checkIfLoopIsNecessaryConditional, IfStatement: checkIfTestExpressionIsNecessaryConditional, - WhileStatement: checkIfTestExpressionIsNecessaryConditional, LogicalExpression: checkLogicalExpressionForUnnecessaryConditionals, + WhileStatement: checkIfLoopIsNecessaryConditional, }; }, }); diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts index c5d90fd01ef..30e511ac119 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -97,6 +97,14 @@ declare const b2: true; if(b1 && b2) {}`, options: [{ ignoreRhs: true }], }, + { + code: ` +while(true) {} +for (;true;) {} +do {} while(true) + `, + options: [{ allowConstantLoopConditions: true }], + }, ], invalid: [ // Ensure that it's checking in all the right places @@ -201,5 +209,18 @@ const t1 = (b1 && b2) ? 'yes' : 'no'`, ruleError(9, 13, 'alwaysTruthy'), ], }, + { + code: ` +while(true) {} +for (;true;) {} +do {} while(true) + `, + options: [{ allowConstantLoopConditions: false }], + errors: [ + ruleError(2, 7, 'alwaysTruthy'), + ruleError(3, 7, 'alwaysTruthy'), + ruleError(4, 13, 'alwaysTruthy'), + ], + }, ], });