diff --git a/packages/eslint-plugin/src/rules/no-for-in-array.ts b/packages/eslint-plugin/src/rules/no-for-in-array.ts index ad70180f3570..6d104b639ebe 100644 --- a/packages/eslint-plugin/src/rules/no-for-in-array.ts +++ b/packages/eslint-plugin/src/rules/no-for-in-array.ts @@ -6,6 +6,7 @@ import { getParserServices, isTypeArrayTypeOrUnionOfArrayTypes, } from '../util'; +import { getForStatementHeadLoc } from '../util/getForStatementHeadLoc'; export default createRule({ name: 'no-for-in-array', @@ -36,7 +37,7 @@ export default createRule({ (type.flags & ts.TypeFlags.StringLike) !== 0 ) { context.report({ - node, + loc: getForStatementHeadLoc(context.sourceCode, node), messageId: 'forInViolation', }); } diff --git a/packages/eslint-plugin/src/util/getForStatementHeadLoc.ts b/packages/eslint-plugin/src/util/getForStatementHeadLoc.ts new file mode 100644 index 000000000000..8a7711fbe0b6 --- /dev/null +++ b/packages/eslint-plugin/src/util/getForStatementHeadLoc.ts @@ -0,0 +1,34 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { nullThrows } from '@typescript-eslint/utils/eslint-utils'; + +/** + * Gets the location of the head of the given for statement variant for reporting. + * + * - `for (const foo in bar) expressionOrBlock` + * ^^^^^^^^^^^^^^^^^^^^^^ + * + * - `for (const foo of bar) expressionOrBlock` + * ^^^^^^^^^^^^^^^^^^^^^^ + * + * - `for await (const foo of bar) expressionOrBlock` + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * - `for (let i = 0; i < 10; i++) expressionOrBlock` + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + */ +export function getForStatementHeadLoc( + sourceCode: TSESLint.SourceCode, + node: + | TSESTree.ForInStatement + | TSESTree.ForOfStatement + | TSESTree.ForStatement, +): TSESTree.SourceLocation { + const closingParens = nullThrows( + sourceCode.getTokenBefore(node.body, token => token.value === ')'), + 'for statement must have a closing parenthesis.', + ); + return { + start: structuredClone(node.loc.start), + end: structuredClone(closingParens.loc.end), + }; +} diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-for-in-array.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-for-in-array.shot index e5f4312e8cf0..dd18cfc5434d 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-for-in-array.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-for-in-array.shot @@ -6,18 +6,14 @@ exports[`Validating rule docs no-for-in-array.mdx code examples ESLint output 1` declare const array: string[]; for (const i in array) { -~~~~~~~~~~~~~~~~~~~~~~~~ For-in loops over arrays skips holes, returns indices as strings, and may visit the prototype chain or other enumerable properties. Use a more robust iteration method such as for-of or array.forEach instead. +~~~~~~~~~~~~~~~~~~~~~~ For-in loops over arrays skips holes, returns indices as strings, and may visit the prototype chain or other enumerable properties. Use a more robust iteration method such as for-of or array.forEach instead. console.log(array[i]); -~~~~~~~~~~~~~~~~~~~~~~~~ } -~ for (const i in array) { -~~~~~~~~~~~~~~~~~~~~~~~~ For-in loops over arrays skips holes, returns indices as strings, and may visit the prototype chain or other enumerable properties. Use a more robust iteration method such as for-of or array.forEach instead. +~~~~~~~~~~~~~~~~~~~~~~ For-in loops over arrays skips holes, returns indices as strings, and may visit the prototype chain or other enumerable properties. Use a more robust iteration method such as for-of or array.forEach instead. console.log(i, array[i]); -~~~~~~~~~~~~~~~~~~~~~~~~~~~ } -~ " `; diff --git a/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts b/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts index cb0fff64e361..35b1e1ae23a9 100644 --- a/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts +++ b/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts @@ -1,5 +1,4 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/no-for-in-array'; import { getFixturesRootDir } from '../RuleTester'; @@ -38,7 +37,10 @@ for (const x in [3, 4, 5]) { errors: [ { messageId: 'forInViolation', - type: AST_NODE_TYPES.ForInStatement, + line: 2, + column: 1, + endLine: 2, + endColumn: 27, }, ], }, @@ -52,7 +54,10 @@ for (const x in z) { errors: [ { messageId: 'forInViolation', - type: AST_NODE_TYPES.ForInStatement, + line: 3, + column: 1, + endLine: 3, + endColumn: 19, }, ], }, @@ -67,7 +72,10 @@ const fn = (arr: number[]) => { errors: [ { messageId: 'forInViolation', - type: AST_NODE_TYPES.ForInStatement, + line: 3, + column: 3, + endLine: 3, + endColumn: 23, }, ], }, @@ -82,7 +90,10 @@ const fn = (arr: number[] | string[]) => { errors: [ { messageId: 'forInViolation', - type: AST_NODE_TYPES.ForInStatement, + line: 3, + column: 3, + endLine: 3, + endColumn: 23, }, ], }, @@ -97,7 +108,72 @@ const fn = (arr: T) => { errors: [ { messageId: 'forInViolation', - type: AST_NODE_TYPES.ForInStatement, + line: 3, + column: 3, + endLine: 3, + endColumn: 23, + }, + ], + }, + { + code: noFormat` +for (const x + in + ( + ( + ( + [3, 4, 5] + ) + ) + ) + ) + // weird + /* spot for a */ + // comment + /* ) */ + /* ( */ + { + console.log(x); +} + `, + errors: [ + { + messageId: 'forInViolation', + line: 2, + column: 1, + endLine: 11, + endColumn: 4, + }, + ], + }, + { + code: noFormat` +for (const x + in + ( + ( + ( + [3, 4, 5] + ) + ) + ) + ) + // weird + /* spot for a */ + // comment + /* ) */ + /* ( */ + + ((((console.log('body without braces '))))); + + `, + errors: [ + { + messageId: 'forInViolation', + line: 2, + column: 1, + endLine: 11, + endColumn: 4, }, ], },