diff --git a/rules/no-array-for-each.js b/rules/no-array-for-each.js index be0a55e2cd..98cbe49f28 100644 --- a/rules/no-array-for-each.js +++ b/rules/no-array-for-each.js @@ -317,18 +317,34 @@ function isFunctionParametersSafeToFix(callbackFunction, {context, scope, callEx return true; } +// TODO[@fisker]: Improve `./utils/is-left-hand-side.js` with similar logic +function isAssignmentLeftHandSide(node) { + const {parent} = node; + + switch (parent.type) { + case 'AssignmentExpression': + return parent.left === node; + case 'UpdateExpression': + return parent.argument === node; + case 'Property': + return parent.value === node && isAssignmentLeftHandSide(parent); + case 'AssignmentPattern': + return parent.left === node && isAssignmentLeftHandSide(parent); + case 'ArrayPattern': + return parent.elements.includes(node) && isAssignmentLeftHandSide(parent); + case 'ObjectPattern': + return parent.properties.includes(node) && isAssignmentLeftHandSide(parent); + default: + return false; + } +} + function isFunctionParameterVariableReassigned(callbackFunction, context) { return context.getDeclaredVariables(callbackFunction) .filter(variable => variable.defs[0].type === 'Parameter') - .some(variable => { - const {references} = variable; - return references.some(reference => { - const node = reference.identifier; - const {parent} = node; - return parent.type === 'UpdateExpression' - || (parent.type === 'AssignmentExpression' && parent.left === node); - }); - }); + .some(variable => + variable.references.some(reference => isAssignmentLeftHandSide(reference.identifier)), + ); } function isFixable(callExpression, {scope, functionInfo, allIdentifiers, context}) { diff --git a/test/no-array-for-each.mjs b/test/no-array-for-each.mjs index 450cc960d1..aa77f39e2a 100644 --- a/test/no-array-for-each.mjs +++ b/test/no-array-for-each.mjs @@ -441,6 +441,67 @@ test.snapshot({ // Arrow function body 'array.forEach((arrayInArray) => arrayInArray.forEach(element => bar(element)));', 'array.forEach((arrayInArray) => arrayInArray?.forEach(element => bar(element)));', + + // Destructuring assign + ...[ + '({element} = bar)', + '({element: a} = bar)', + '({a: element} = bar)', + '({[element]: a} = bar)', + '({[a]: element} = bar)', + '({element = a} = bar)', + '({a = element} = bar)', + '[element] = bar', + '[element = a] = bar', + '[a = element] = bar', + '({deep: {element}} = bar)', + '({deep: {element: a}} = bar)', + '({deep: {a: element}} = bar)', + '({deep: {[element]: a}} = bar)', + '({deep: {[a]: element}} = bar)', + '({deep: {element = a}} = bar)', + '({deep: {a = element}} = bar)', + '({deep: [element]} = bar)', + '({deep: [element = a]} = bar)', + '({deep: [a = element]} = bar)', + '[{element}] = bar', + '[{element: a}] = bar', + '[{a: element}] = bar', + '[{[element]: a}] = bar', + '[{[a]: element}] = bar', + '[{element = a}] = bar', + '[{a = element}] = bar', + '[[element]] = bar', + '[[element = a]] = bar', + '[[a = element]] = bar', + ].map(code => outdent` + foo.forEach(element => { + ${code}; + }); + `), + outdent` + foo.forEach(element => { + [ + bar = ((element) => { + [element] = array; + })(element) + ] = baz; + }); + `, + outdent` + foo.forEach(element => { + [ + bar = ((element = array) => element)(element) + ] = baz; + }); + `, + outdent` + foo.forEach(element => { + [ + bar = (([element] = array) => element)(element) + ] = baz; + }); + `, ], }); diff --git a/test/snapshots/no-array-for-each.mjs.md b/test/snapshots/no-array-for-each.mjs.md index 714f5dd1b6..da2d17333f 100644 --- a/test/snapshots/no-array-for-each.mjs.md +++ b/test/snapshots/no-array-for-each.mjs.md @@ -4125,3 +4125,753 @@ Generated by [AVA](https://avajs.dev). > 1 | array.forEach((arrayInArray) => arrayInArray?.forEach(element => bar(element)));␊ | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ ` + +## Invalid #231 + 1 | foo.forEach(element => { + 2 | ({element} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({element} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({element} = bar);␊ + 3 | });␊ + ` + +## Invalid #232 + 1 | foo.forEach(element => { + 2 | ({element: a} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | ({element: a} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({element: a} = bar);␊ + 3 | });␊ + ` + +## Invalid #233 + 1 | foo.forEach(element => { + 2 | ({a: element} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({a: element} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({a: element} = bar);␊ + 3 | });␊ + ` + +## Invalid #234 + 1 | foo.forEach(element => { + 2 | ({[element]: a} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | ({[element]: a} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({[element]: a} = bar);␊ + 3 | });␊ + ` + +## Invalid #235 + 1 | foo.forEach(element => { + 2 | ({[a]: element} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({[a]: element} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({[a]: element} = bar);␊ + 3 | });␊ + ` + +## Invalid #236 + 1 | foo.forEach(element => { + 2 | ({element = a} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({element = a} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({element = a} = bar);␊ + 3 | });␊ + ` + +## Invalid #237 + 1 | foo.forEach(element => { + 2 | ({a = element} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | ({a = element} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({a = element} = bar);␊ + 3 | });␊ + ` + +## Invalid #238 + 1 | foo.forEach(element => { + 2 | [element] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | [element] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [element] = bar;␊ + 3 | });␊ + ` + +## Invalid #239 + 1 | foo.forEach(element => { + 2 | [element = a] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | [element = a] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [element = a] = bar;␊ + 3 | });␊ + ` + +## Invalid #240 + 1 | foo.forEach(element => { + 2 | [a = element] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | [a = element] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [a = element] = bar;␊ + 3 | });␊ + ` + +## Invalid #241 + 1 | foo.forEach(element => { + 2 | ({deep: {element}} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({deep: {element}} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: {element}} = bar);␊ + 3 | });␊ + ` + +## Invalid #242 + 1 | foo.forEach(element => { + 2 | ({deep: {element: a}} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | ({deep: {element: a}} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: {element: a}} = bar);␊ + 3 | });␊ + ` + +## Invalid #243 + 1 | foo.forEach(element => { + 2 | ({deep: {a: element}} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({deep: {a: element}} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: {a: element}} = bar);␊ + 3 | });␊ + ` + +## Invalid #244 + 1 | foo.forEach(element => { + 2 | ({deep: {[element]: a}} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | ({deep: {[element]: a}} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: {[element]: a}} = bar);␊ + 3 | });␊ + ` + +## Invalid #245 + 1 | foo.forEach(element => { + 2 | ({deep: {[a]: element}} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({deep: {[a]: element}} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: {[a]: element}} = bar);␊ + 3 | });␊ + ` + +## Invalid #246 + 1 | foo.forEach(element => { + 2 | ({deep: {element = a}} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({deep: {element = a}} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: {element = a}} = bar);␊ + 3 | });␊ + ` + +## Invalid #247 + 1 | foo.forEach(element => { + 2 | ({deep: {a = element}} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | ({deep: {a = element}} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: {a = element}} = bar);␊ + 3 | });␊ + ` + +## Invalid #248 + 1 | foo.forEach(element => { + 2 | ({deep: [element]} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({deep: [element]} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: [element]} = bar);␊ + 3 | });␊ + ` + +## Invalid #249 + 1 | foo.forEach(element => { + 2 | ({deep: [element = a]} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | ({deep: [element = a]} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: [element = a]} = bar);␊ + 3 | });␊ + ` + +## Invalid #250 + 1 | foo.forEach(element => { + 2 | ({deep: [a = element]} = bar); + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | ({deep: [a = element]} = bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | ({deep: [a = element]} = bar);␊ + 3 | });␊ + ` + +## Invalid #251 + 1 | foo.forEach(element => { + 2 | [{element}] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | [{element}] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [{element}] = bar;␊ + 3 | });␊ + ` + +## Invalid #252 + 1 | foo.forEach(element => { + 2 | [{element: a}] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | [{element: a}] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [{element: a}] = bar;␊ + 3 | });␊ + ` + +## Invalid #253 + 1 | foo.forEach(element => { + 2 | [{a: element}] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | [{a: element}] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [{a: element}] = bar;␊ + 3 | });␊ + ` + +## Invalid #254 + 1 | foo.forEach(element => { + 2 | [{[element]: a}] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | [{[element]: a}] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [{[element]: a}] = bar;␊ + 3 | });␊ + ` + +## Invalid #255 + 1 | foo.forEach(element => { + 2 | [{[a]: element}] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | [{[a]: element}] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [{[a]: element}] = bar;␊ + 3 | });␊ + ` + +## Invalid #256 + 1 | foo.forEach(element => { + 2 | [{element = a}] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | [{element = a}] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [{element = a}] = bar;␊ + 3 | });␊ + ` + +## Invalid #257 + 1 | foo.forEach(element => { + 2 | [{a = element}] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | [{a = element}] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [{a = element}] = bar;␊ + 3 | });␊ + ` + +## Invalid #258 + 1 | foo.forEach(element => { + 2 | [[element]] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | [[element]] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [[element]] = bar;␊ + 3 | });␊ + ` + +## Invalid #259 + 1 | foo.forEach(element => { + 2 | [[element = a]] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (let element of foo) {␊ + 2 | [[element = a]] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [[element = a]] = bar;␊ + 3 | });␊ + ` + +## Invalid #260 + 1 | foo.forEach(element => { + 2 | [[a = element]] = bar; + 3 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | [[a = element]] = bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [[a = element]] = bar;␊ + 3 | });␊ + ` + +## Invalid #261 + 1 | foo.forEach(element => { + 2 | [ + 3 | bar = ((element) => { + 4 | [element] = array; + 5 | })(element) + 6 | ] = baz; + 7 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | [␊ + 3 | bar = ((element) => {␊ + 4 | [element] = array;␊ + 5 | })(element)␊ + 6 | ] = baz;␊ + 7 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [␊ + 3 | bar = ((element) => {␊ + 4 | [element] = array;␊ + 5 | })(element)␊ + 6 | ] = baz;␊ + 7 | });␊ + ` + +## Invalid #262 + 1 | foo.forEach(element => { + 2 | [ + 3 | bar = ((element = array) => element)(element) + 4 | ] = baz; + 5 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | [␊ + 3 | bar = ((element = array) => element)(element)␊ + 4 | ] = baz;␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [␊ + 3 | bar = ((element = array) => element)(element)␊ + 4 | ] = baz;␊ + 5 | });␊ + ` + +## Invalid #263 + 1 | foo.forEach(element => { + 2 | [ + 3 | bar = (([element] = array) => element)(element) + 4 | ] = baz; + 5 | }); + +> Output + + `␊ + 1 | for (const element of foo) {␊ + 2 | [␊ + 3 | bar = (([element] = array) => element)(element)␊ + 4 | ] = baz;␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.forEach(element => {␊ + | ^^^^^^^ Use \`for…of\` instead of \`.forEach(…)\`.␊ + 2 | [␊ + 3 | bar = (([element] = array) => element)(element)␊ + 4 | ] = baz;␊ + 5 | });␊ + ` diff --git a/test/snapshots/no-array-for-each.mjs.snap b/test/snapshots/no-array-for-each.mjs.snap index 022e5555db..79f3f00f0f 100644 Binary files a/test/snapshots/no-array-for-each.mjs.snap and b/test/snapshots/no-array-for-each.mjs.snap differ