diff --git a/packages/eslint-plugin/src/rules/prefer-for-of.ts b/packages/eslint-plugin/src/rules/prefer-for-of.ts index 40fac431664d..40067e0c1ffa 100644 --- a/packages/eslint-plugin/src/rules/prefer-for-of.ts +++ b/packages/eslint-plugin/src/rules/prefer-for-of.ts @@ -23,6 +23,32 @@ export default util.createRule({ }, defaultOptions: [], create(context) { + function getTwoVariableDeclarationWithArrayLength( + node: TSESTree.Node | null, + ): [TSESTree.VariableDeclarator, TSESTree.VariableDeclarator] | undefined { + if ( + node !== null && + node.type === AST_NODE_TYPES.VariableDeclaration && + node.kind !== 'const' && + node.declarations.length === 2 + ) { + const decl1 = node.declarations[0]; + const decl2 = node.declarations[1]; + const combi1 = + isZeroInitialized(decl1) && isAssigningArrayLength(decl2); + if (combi1) { + return [decl1, decl2]; + } + const combi2 = + isZeroInitialized(decl2) && isAssigningArrayLength(decl1); + if (combi2) { + return [decl2, decl1]; + } + } + + return undefined; + } + function isSingleVariableDeclaration( node: TSESTree.Node | null, ): node is TSESTree.VariableDeclaration { @@ -34,6 +60,16 @@ export default util.createRule({ ); } + function isAssigningArrayLength( + node: TSESTree.VariableDeclarator, + ): boolean { + return ( + node.init !== null && + node.init.type === AST_NODE_TYPES.MemberExpression && + isMatchingIdentifier(node.init.property, 'length') + ); + } + function isLiteral(node: TSESTree.Expression, value: number): boolean { return node.type === AST_NODE_TYPES.Literal && node.value === value; } @@ -49,6 +85,23 @@ export default util.createRule({ return node.type === AST_NODE_TYPES.Identifier && node.name === name; } + function isLessThanLengthVariable( + node: TSESTree.Node | null, + indexName: string, + arrVarName: string, + ): boolean { + if ( + node !== null && + node.type === AST_NODE_TYPES.BinaryExpression && + node.operator === '<' && + isMatchingIdentifier(node.left, indexName) && + isMatchingIdentifier(node.right, arrVarName) + ) { + return true; + } + return false; + } + function isLessThanLengthExpression( node: TSESTree.Node | null, name: string, @@ -183,6 +236,43 @@ export default util.createRule({ return { 'ForStatement:exit'(node: TSESTree.ForStatement): void { + const twoVarDecl = getTwoVariableDeclarationWithArrayLength(node.init); + if (twoVarDecl) { + const indexPosVar = twoVarDecl[0]; + const arraLengthVar = twoVarDecl[1]; + const arrayExpression = + arraLengthVar.init?.type === AST_NODE_TYPES.MemberExpression && + arraLengthVar.init?.object; + if ( + !indexPosVar || + !isZeroInitialized(indexPosVar) || + indexPosVar.id.type !== AST_NODE_TYPES.Identifier || + arraLengthVar.id.type !== AST_NODE_TYPES.Identifier + ) { + return; + } + + const indexName = indexPosVar.id.name; + const arrVarName = arraLengthVar.id.name; + if (!isLessThanLengthVariable(node.test, indexName, arrVarName)) { + return; + } + const indexVar = context + .getDeclaredVariables(node.init!) + .find(el => el.name === indexName); + if ( + indexVar && + arrayExpression && + isIncrement(node.update, indexName) && + isIndexOnlyUsedWithArray(node.body, indexVar, arrayExpression) + ) { + context.report({ + node, + messageId: 'preferForOf', + }); + } + } + if (!isSingleVariableDeclaration(node.init)) { return; } diff --git a/packages/eslint-plugin/tests/rules/prefer-for-of.test.ts b/packages/eslint-plugin/tests/rules/prefer-for-of.test.ts index 0e22722afb43..9231bbfed0ed 100644 --- a/packages/eslint-plugin/tests/rules/prefer-for-of.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-for-of.test.ts @@ -40,12 +40,6 @@ for (var f = 0; f <= 40; f++) { `, ` for (var g = 0; g <= 40; g++) doMath(g); -`, - ` -for(var h=0, len=arr.length; h < len; h++) {} -`, - ` -for(var i=0, len=arr.length; i < len; i++) arr[i]; `, ` var m = 0; @@ -181,6 +175,15 @@ for (var c = 0; c < arr.length; c++) { ` for (var d = 0; d < arr.length; d++) doMath?.(d); `, + ` +for (var d = 0, e=5; d < arr.length; d++) { d+e }; + `, + ` +for (var d = 0, e=5, c = arr.length; d < c; d++) { d+e }; + `, + ` +for(let x = arr.length, i=0; x != i; x-=1) {} +`, ], invalid: [ { @@ -367,5 +370,47 @@ for (let i = 0; i < arr.length; i++) { }, ], }, + { + code: ` +for (let i = 0, l = arr.length; i < l; i++) { + console.log(arr[i]) +} + `, + errors: [ + { + messageId: 'preferForOf', + }, + ], + }, + { + code: ` +for(var h=0, len=arr.length; h < len; h++) {} + `, + errors: [ + { + messageId: 'preferForOf', + }, + ], + }, + { + code: ` +for(var i=0, len=arr.length; i < len; i++) arr[i]; + `, + errors: [ + { + messageId: 'preferForOf', + }, + ], + }, + { + code: ` +for(var len=arr.length, i=0; i < len; i++) arr[i]; + `, + errors: [ + { + messageId: 'preferForOf', + }, + ], + }, ], });