Repository: jsx-eslint/eslint-plugin-react (js)

Issue:
(react/jsx-key) still warns when wrapping with React.Children.toArray
The (react/jsx-key) rule seems to still warn for instances wrapped with the `React.Children.toArray` utility. 

This seems to be a bug since React no longer warns about missing keys when wrapping and considers it valid (the toArray utility provides keys to each item automatically)

```javascript

// React and lint rule both warn
{
  things.map(item => <Thing {...item} />)
}

// lint rule warns but React considers this to be valid
{
  React.Children.toArray(
    things.map(item => <Thing {...item} />)
  )
}
```

### Suggested Solution:

The rule accounts for instances wrapped with `React.Children.toArray` correctly and does not warn.

**_Edit:_**
I incorrectly described this as the no-array-index (as key) rule from this eslint plugin, but it is actually: `[eslint] Missing "key" prop for element in iterator (react/jsx-key)`. Updated the body of the issue plus the title to reflect the correct rule.

Pull request:
Ignore jsx-key if inside React.Children.toArray()
`jsx-key` rule should always succeed if we're inside `React.Children.toArray()` because omitting `key` there doesn't cause a React warning.

Fixes #1574.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b9e5592..9a36c5e3 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 a23d8438..b1e0a62a 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;


Test patch:
diff --git a/tests/lib/rules/jsx-key.js b/tests/lib/rules/jsx-key.js
index 451bcb0d..15b89c19 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 => <App />));' },
+    {
+      code: `
+        import { Children } from "react";
+        Children.toArray([1, 2 ,3].map(x => <App />));
+      `,
+    },
+    {
+      // 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 => <App />));
+        Act.Children.toArray(Array.from([1, 2 ,3], x => <App />));
+        Children.toArray([1, 2 ,3].map(x => <App />));
+        Children.toArray(Array.from([1, 2 ,3], x => <App />));
+        // ReactChildren.toArray([1, 2 ,3].map(x => <App />));
+        // ReactChildren.toArray(Array.from([1, 2 ,3], x => <App />));
+        // toArray([1, 2 ,3].map(x => <App />));
+        // toArray(Array.from([1, 2 ,3], x => <App />));
+      `,
+      settings,
+    },
   ]),
   invalid: parsers.all([
     {