diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b9e559291..f5975115a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,9 +8,19 @@ 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)
+* [`jsx-no-constructed-context-values`]: fix false positive for usage in non-components ([#3448][] @golopot)
+### Changed
+* [Docs] [`no-unknown-property`]: fix typo in link ([#3445][] @denkristoffer)
+* [Perf] component detection: improve performance by optimizing getId ([#3451][] @golopot)
+
+[#3451]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3451
+[#3448]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3448
+[#3445]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3445
[#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/docs/rules/no-unknown-property.md b/docs/rules/no-unknown-property.md
index d142def850..e885474f20 100644
--- a/docs/rules/no-unknown-property.md
+++ b/docs/rules/no-unknown-property.md
@@ -58,7 +58,7 @@ var AtomPanel = ;
If you are using a library that passes something as a prop to JSX elements, it is recommended to add those props to the ignored properties.
-For example, if you use [emotion](https://emotion.sh/docs/introduction) and its [`css` prop](https://emotion.sh/docs/css-prop)),
+For example, if you use [emotion](https://emotion.sh/docs/introduction) and its [`css` prop](https://emotion.sh/docs/css-prop),
add the following to your `.eslintrc` config file:
```js
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/lib/rules/jsx-no-constructed-context-values.js b/lib/rules/jsx-no-constructed-context-values.js
index af7c9575df..ac34ead85e 100644
--- a/lib/rules/jsx-no-constructed-context-values.js
+++ b/lib/rules/jsx-no-constructed-context-values.js
@@ -6,6 +6,7 @@
'use strict';
+const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
@@ -139,7 +140,8 @@ module.exports = {
messages,
},
- create(context) {
+ // eslint-disable-next-line arrow-body-style
+ create: Components.detect((context, components, utils) => {
return {
JSXOpeningElement(node) {
const openingElementName = node.name;
@@ -184,6 +186,10 @@ module.exports = {
return;
}
+ if (!utils.getParentComponent(node)) {
+ return;
+ }
+
// Report found error
const constructType = constructInfo.type;
const constructNode = constructInfo.node;
@@ -214,5 +220,5 @@ module.exports = {
});
},
};
- },
+ }),
};
diff --git a/lib/util/Components.js b/lib/util/Components.js
index 7b931c2234..b112923f63 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -21,7 +21,7 @@ const isFirstLetterCapitalized = require('./isFirstLetterCapitalized');
const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport');
function getId(node) {
- return node && node.range.join(':');
+ return node ? `${node.range[0]}:${node.range[1]}` : '';
}
function usedPropTypesAreEquivalent(propA, propB) {
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([
{
diff --git a/tests/lib/rules/jsx-no-constructed-context-values.js b/tests/lib/rules/jsx-no-constructed-context-values.js
index 1ec0592382..c8c9dee995 100644
--- a/tests/lib/rules/jsx-no-constructed-context-values.js
+++ b/tests/lib/rules/jsx-no-constructed-context-values.js
@@ -31,13 +31,13 @@ const ruleTester = new RuleTester({ parserOptions });
ruleTester.run('react-no-constructed-context-values', rule, {
valid: parsers.all([
{
- code: '',
+ code: 'const Component = () => ',
},
{
- code: '',
+ code: 'const Component = () => ',
},
{
- code: '',
+ code: 'const Component = () => ',
},
{
code: 'function Component() { const foo = useMemo(() => { return {} }, []); return ()}',
@@ -137,6 +137,16 @@ ruleTester.run('react-no-constructed-context-values', rule, {
}
`,
},
+ {
+ code: `
+ const root = ReactDOM.createRoot(document.getElementById('root'));
+ root.render(
+
+
+
+ );
+ `,
+ },
]),
invalid: parsers.all([
{