diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b9e559291..9a36c5e320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`no-unknown-property`]: add `dialog` attributes ([#3436][] @ljharb) * [`no-arrow-function-lifecycle`]: when converting from an arrow, remove the semi and wrapping parens ([#3337][] @ljharb) +* [`jsx-key`]: Ignore elements inside `React.Children.toArray()` ([#1591][] @silvenon) [#3436]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3436 [#3337]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3337 +[#1591]: https://github.com/jsx-eslint/eslint-plugin-react/pull/1591 ## [7.31.8] - 2022.09.08 diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index a23d843845..b1e0a62a4f 100644 --- a/lib/rules/jsx-key.js +++ b/lib/rules/jsx-key.js @@ -155,10 +155,33 @@ module.exports = { } } + const childrenToArraySelector = `:matches( + CallExpression + [callee.object.object.name=${reactPragma}] + [callee.object.property.name=Children] + [callee.property.name=toArray], + CallExpression + [callee.object.name=Children] + [callee.property.name=toArray] + )`.replace(/\s/g, ''); + let isWithinChildrenToArray = false; + const seen = new WeakSet(); return { + [childrenToArraySelector]() { + isWithinChildrenToArray = true; + }, + + [`${childrenToArraySelector}:exit`]() { + isWithinChildrenToArray = false; + }, + 'ArrayExpression, JSXElement > JSXElement'(node) { + if (isWithinChildrenToArray) { + return; + } + const jsx = (node.type === 'ArrayExpression' ? node.elements : node.parent.children).filter((x) => x && x.type === 'JSXElement'); if (jsx.length === 0) { return; @@ -205,7 +228,7 @@ module.exports = { }, JSXFragment(node) { - if (!checkFragmentShorthand) { + if (!checkFragmentShorthand || isWithinChildrenToArray) { return; } @@ -226,6 +249,10 @@ module.exports = { CallExpression[callee.type="OptionalMemberExpression"][callee.property.name="map"],\ OptionalCallExpression[callee.type="MemberExpression"][callee.property.name="map"],\ OptionalCallExpression[callee.type="OptionalMemberExpression"][callee.property.name="map"]'(node) { + if (isWithinChildrenToArray) { + return; + } + const fn = node.arguments.length > 0 && node.arguments[0]; if (!fn || !astUtil.isFunctionLikeExpression(fn)) { return; @@ -238,6 +265,10 @@ module.exports = { // Array.from 'CallExpression[callee.type="MemberExpression"][callee.property.name="from"]'(node) { + if (isWithinChildrenToArray) { + return; + } + const fn = node.arguments.length > 1 && node.arguments[1]; if (!astUtil.isFunctionLikeExpression(fn)) { return; diff --git a/tests/lib/rules/jsx-key.js b/tests/lib/rules/jsx-key.js index 451bcb0d57..15b89c19b7 100644 --- a/tests/lib/rules/jsx-key.js +++ b/tests/lib/rules/jsx-key.js @@ -176,6 +176,33 @@ ruleTester.run('jsx-key', rule, { `, features: ['types', 'no-babel-old'], }, + { code: 'React.Children.toArray([1, 2 ,3].map(x => ));' }, + { + code: ` + import { Children } from "react"; + Children.toArray([1, 2 ,3].map(x => )); + `, + }, + { + // TODO: uncomment the commented lines below + code: ` + import Act from 'react'; + import { Children as ReactChildren } from 'react'; + + const { Children } = Act; + const { toArray } = Children; + + Act.Children.toArray([1, 2 ,3].map(x => )); + Act.Children.toArray(Array.from([1, 2 ,3], x => )); + Children.toArray([1, 2 ,3].map(x => )); + Children.toArray(Array.from([1, 2 ,3], x => )); + // ReactChildren.toArray([1, 2 ,3].map(x => )); + // ReactChildren.toArray(Array.from([1, 2 ,3], x => )); + // toArray([1, 2 ,3].map(x => )); + // toArray(Array.from([1, 2 ,3], x => )); + `, + settings, + }, ]), invalid: parsers.all([ {