From 148f70c4bc2c1d1a9df105755d57687fcbbbe1a4 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Tue, 8 Jul 2025 17:06:16 -0700 Subject: [PATCH 1/3] feat(jsx-key): check for array related method calls with jsx elements/fragments --- lib/rules/jsx-key.js | 19 +++++++++++++++++++ tests/lib/rules/jsx-key.js | 14 ++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index 825d21f4bb..47f800fbd2 100644 --- a/lib/rules/jsx-key.js +++ b/lib/rules/jsx-key.js @@ -263,6 +263,25 @@ module.exports = { } }, + // eslint-disable-next-line no-multi-str + 'CallExpression[callee.type="MemberExpression"][callee.property.name=/^push|unshift|splice|with$/],\ + CallExpression[callee.type="OptionalMemberExpression"][callee.property.name=/^push|unshift|splice|with$/],\ + OptionalCallExpression[callee.type="MemberExpression"][callee.property.name=/^push|unshift|splice|with$/],\ + OptionalCallExpression[callee.type="OptionalMemberExpression"][callee.property.name=/^push|unshift|splice|with$/]'(node) { + if (node.arguments.length === 0) { + return; + } + + node.arguments.forEach((arg) => { + if (arg.type === 'JSXElement' && !hasProp(arg.openingElement.attributes, 'key')) { + report(context, messages.missingIterKey, 'missingArrayKey', { node }); + } + if (arg.type === 'JSXFragment') { + report(context, messages.missingIterKey, 'missingArrayKeyUsePrag', { node }); + } + }); + }, + // Array.prototype.map // eslint-disable-next-line no-multi-str 'CallExpression[callee.type="MemberExpression"][callee.property.name="map"],\ diff --git a/tests/lib/rules/jsx-key.js b/tests/lib/rules/jsx-key.js index 2c5ba7a5c1..3b94b08c3f 100644 --- a/tests/lib/rules/jsx-key.js +++ b/tests/lib/rules/jsx-key.js @@ -205,6 +205,12 @@ ruleTester.run('jsx-key', rule, { `, settings, }, + { + code: 'arr.push();', + }, + { + code: 'arr.push();', + }, ]), invalid: parsers.all([ { @@ -424,5 +430,13 @@ ruleTester.run('jsx-key', rule, { options: [{ checkKeyMustBeforeSpread: true }], errors: [{ messageId: 'keyBeforeSpread' }], }, + { + code: 'arr.push();', + errors: [{ messageId: 'missingArrayKey' }], + }, + { + code: 'arr.push(<>);', + errors: [{ messageId: 'missingArrayKeyUsePrag' }], + }, ]), }); From 10cdb5e639570747e30eb22c7fd99b43bb758912 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Tue, 8 Jul 2025 17:09:04 -0700 Subject: [PATCH 2/3] fixup! feat(jsx-key): check for array related method calls with jsx elements/fragments --- docs/rules/jsx-key.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/rules/jsx-key.md b/docs/rules/jsx-key.md index 0426f9f81a..250466eef0 100644 --- a/docs/rules/jsx-key.md +++ b/docs/rules/jsx-key.md @@ -27,6 +27,11 @@ Array.from([1, 2, 3], (x) => {x}); ``` +```jsx +arr = []; +arr.push(); +``` + In the last example the key is being spread, which is currently possible, but discouraged in favor of the statically provided key. Examples of **correct** code for this rule: @@ -47,6 +52,11 @@ Array.from([1, 2, 3], (x) => {x}); ``` +```jsx +arr = []; +arr.push(); +``` + ## Rule Options ```js From d46df39fb3af8a9f40a93c025dd2493ceae85990 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Tue, 8 Jul 2025 17:47:15 -0700 Subject: [PATCH 3/3] fixup! fixup! feat(jsx-key): check for array related method calls with jsx elements/fragments --- lib/rules/jsx-key.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index 47f800fbd2..e49532665a 100644 --- a/lib/rules/jsx-key.js +++ b/lib/rules/jsx-key.js @@ -264,17 +264,14 @@ module.exports = { }, // eslint-disable-next-line no-multi-str - 'CallExpression[callee.type="MemberExpression"][callee.property.name=/^push|unshift|splice|with$/],\ - CallExpression[callee.type="OptionalMemberExpression"][callee.property.name=/^push|unshift|splice|with$/],\ - OptionalCallExpression[callee.type="MemberExpression"][callee.property.name=/^push|unshift|splice|with$/],\ - OptionalCallExpression[callee.type="OptionalMemberExpression"][callee.property.name=/^push|unshift|splice|with$/]'(node) { - if (node.arguments.length === 0) { - return; - } - + 'CallExpression[callee.type="MemberExpression"][callee.property.name=/^push|unshift|splice|with|concat$/],\ + CallExpression[callee.type="OptionalMemberExpression"][callee.property.name=/^push|unshift|splice|with|concat$/],\ + OptionalCallExpression[callee.type="MemberExpression"][callee.property.name=/^push|unshift|splice|with|concat$/],\ + OptionalCallExpression[callee.type="OptionalMemberExpression"][callee.property.name=/^push|unshift|splice|with|concat$/]'(node) { node.arguments.forEach((arg) => { if (arg.type === 'JSXElement' && !hasProp(arg.openingElement.attributes, 'key')) { report(context, messages.missingIterKey, 'missingArrayKey', { node }); + return; } if (arg.type === 'JSXFragment') { report(context, messages.missingIterKey, 'missingArrayKeyUsePrag', { node });