Skip to content

Commit

Permalink
[Fix] jsx-key: detect keys in logical expression and conditional ex…
Browse files Browse the repository at this point in the history
…pression

Fixes #3481.
  • Loading branch information
metreniuk authored and ljharb committed Nov 15, 2022
1 parent 865ed16 commit d350a48
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`jsx-no-target-blank`]: allow ternaries with literals ([#3464][] @akulsr0)
* [`sort-prop-types`]: restore autofixing ([#2574][] @ROSSROSALES)
* [`no-unknown-property`]: add `inert` attribute ([#3484][] @ljharb)
* [`jsx-key`]: detect keys in logical expression and conditional expression ([#3490][] @metreniuk)

### Changed
* [Perf] component detection: improve performance by avoiding traversing parents unnecessarily ([#3459][] @golopot)
* [Docs] `forbid-component-props`: inclusive language w/ allowlist ([#3473][] @AndersDJohnson)
* [Docs] automate doc generation with `eslint-doc-generator` ([#3469][] @bmish)

[#3490]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3490
[#3484]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3484
[#3473]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3473
[#3469]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3469
Expand Down
8 changes: 4 additions & 4 deletions docs/rules/jsx-key.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ data.map(x => <Hello>{x}</Hello>);
```

```jsx
<Hello {...{ key: id, id, caption }} />
Array.from([1, 2, 3], (x) => <Hello>{x}</Hello>);
```

```jsx
Array.from([1, 2, 3], (x) => <Hello>{x}</Hello>);
<Hello {...{ key: id, id, caption }} />
```

In the last example the key is being spread, which is currently possible, but discouraged in favor of the statically provided key.
Expand All @@ -40,11 +40,11 @@ data.map((x) => <Hello key={x.id}>{x}</Hello>);
```

```jsx
<Hello key={id} {...{ id, caption }} />
Array.from([1, 2, 3], (x) => <Hello key={x.id}>{x}</Hello>);
```

```jsx
Array.from([1, 2, 3], (x) => <Hello key={x.id}>{x}</Hello>);
<Hello key={id} {...{ id, caption }} />
```

## Rule Options
Expand Down
14 changes: 12 additions & 2 deletions lib/rules/jsx-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,20 @@ module.exports = {
*/
function checkArrowFunctionWithJSX(node) {
const isArrFn = node && node.type === 'ArrowFunctionExpression';

if (isArrFn && (node.body.type === 'JSXElement' || node.body.type === 'JSXFragment')) {
const shouldCheckNode = (n) => n && (n.type === 'JSXElement' || n.type === 'JSXFragment');
if (isArrFn && shouldCheckNode(node.body)) {
checkIteratorElement(node.body);
}
if (node.body.type === 'ConditionalExpression') {
if (shouldCheckNode(node.body.consequent)) {
checkIteratorElement(node.body.consequent);
}
if (shouldCheckNode(node.body.alternate)) {
checkIteratorElement(node.body.alternate);
}
} else if (node.body.type === 'LogicalExpression' && shouldCheckNode(node.body.right)) {
checkIteratorElement(node.body.right);
}
}

const childrenToArraySelector = `:matches(
Expand Down
18 changes: 16 additions & 2 deletions tests/lib/rules/jsx-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ ruleTester.run('jsx-key', rule, {
{ code: '[<App key={0} />, <App key={1} />];' },
{ code: '[1, 2, 3].map(function(x) { return <App key={x} /> });' },
{ code: '[1, 2, 3].map(x => <App key={x} />);' },
{ code: '[1, 2 ,3].map(x => x && <App x={x} key={x} />);' },
{ code: '[1, 2 ,3].map(x => x ? <App x={x} key="1" /> : <OtherApp x={x} key="2" />);' },
{ code: '[1, 2, 3].map(x => { return <App key={x} /> });' },
{ code: 'Array.from([1, 2, 3], function(x) { return <App key={x} /> });' },
{ code: 'Array.from([1, 2, 3], (x => <App key={x} />));' },
Expand Down Expand Up @@ -188,10 +190,10 @@ ruleTester.run('jsx-key', rule, {
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 />));
Expand Down Expand Up @@ -225,6 +227,18 @@ ruleTester.run('jsx-key', rule, {
code: '[1, 2 ,3].map(x => <App />);',
errors: [{ messageId: 'missingIterKey' }],
},
{
code: '[1, 2 ,3].map(x => x && <App x={x} />);',
errors: [{ messageId: 'missingIterKey' }],
},
{
code: '[1, 2 ,3].map(x => x ? <App x={x} key="1" /> : <OtherApp x={x} />);',
errors: [{ messageId: 'missingIterKey' }],
},
{
code: '[1, 2 ,3].map(x => x ? <App x={x} /> : <OtherApp x={x} key="2" />);',
errors: [{ messageId: 'missingIterKey' }],
},
{
code: '[1, 2 ,3].map(x => { return <App /> });',
errors: [{ messageId: 'missingIterKey' }],
Expand Down

0 comments on commit d350a48

Please sign in to comment.