Skip to content

Commit 9f0d5c4

Browse files
morlayljharb
authored andcommitted
[New]: jsx-key: added checkKeyMustBeforeSpread option for new jsx transform
Fixes #2830.
1 parent 01c7f5e commit 9f0d5c4

File tree

4 files changed

+73
-2
lines changed

4 files changed

+73
-2
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
55

66
## Unreleased
77

8+
### Added
9+
* [`jsx-key`]: added `checkKeyMustBeforeSpread` option for new jsx transform ([#2835][] @morlay)
10+
11+
[#2835]: https://github.com/yannickcr/eslint-plugin-react/pull/2835
12+
813
## [7.21.5] - 2020.10.19
914

1015
### Fixed

docs/rules/jsx-key.md

+10
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ The following patterns are considered warnings:
4747
data.map(x => <>{x}</>);
4848
```
4949

50+
### `checkKeyMustBeforeSpread` (default: `false`)
51+
52+
When `true` the rule will check if key prop after spread to avoid [createElement fallback](https://github.com/facebook/react/issues/20031#issuecomment-710346866).
53+
54+
The following patterns are considered warnings:
55+
56+
```jsx
57+
<span {...spread} key={"key-after-spread"} />;
58+
```
59+
5060
## When not to use
5161

5262
If you are not using JSX then you can disable this rule.

lib/rules/jsx-key.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
'use strict';
77

88
const hasProp = require('jsx-ast-utils/hasProp');
9+
const propName = require('jsx-ast-utils/propName');
910
const docsUrl = require('../util/docsUrl');
1011
const pragmaUtil = require('../util/pragma');
1112

@@ -14,7 +15,8 @@ const pragmaUtil = require('../util/pragma');
1415
// ------------------------------------------------------------------------------
1516

1617
const defaultOptions = {
17-
checkFragmentShorthand: false
18+
checkFragmentShorthand: false,
19+
checkKeyMustBeforeSpread: false
1820
};
1921

2022
module.exports = {
@@ -31,6 +33,10 @@ module.exports = {
3133
checkFragmentShorthand: {
3234
type: 'boolean',
3335
default: defaultOptions.checkFragmentShorthand
36+
},
37+
checkKeyMustBeforeSpread: {
38+
type: 'boolean',
39+
default: defaultOptions.checkKeyMustBeforeSpread
3440
}
3541
},
3642
additionalProperties: false
@@ -40,6 +46,7 @@ module.exports = {
4046
create(context) {
4147
const options = Object.assign({}, defaultOptions, context.options[0]);
4248
const checkFragmentShorthand = options.checkFragmentShorthand;
49+
const checkKeyMustBeforeSpread = options.checkKeyMustBeforeSpread;
4350
const reactPragma = pragmaUtil.getFromContext(context);
4451
const fragmentPragma = pragmaUtil.getFragmentFromContext(context);
4552

@@ -61,9 +68,29 @@ module.exports = {
6168
return body.filter((item) => item.type === 'ReturnStatement')[0];
6269
}
6370

71+
function isKeyAfterSpread(attributes) {
72+
let hasFoundSpread = false;
73+
return attributes.some((attribute) => {
74+
if (attribute.type === 'JSXSpreadAttribute') {
75+
hasFoundSpread = true;
76+
return false;
77+
}
78+
if (attribute.type !== 'JSXAttribute') {
79+
return false;
80+
}
81+
return hasFoundSpread && propName(attribute) === 'key';
82+
});
83+
}
84+
6485
return {
6586
JSXElement(node) {
6687
if (hasProp(node.openingElement.attributes, 'key')) {
88+
if (checkKeyMustBeforeSpread && isKeyAfterSpread(node.openingElement.attributes)) {
89+
context.report({
90+
node,
91+
message: '`key` prop must before any `{...spread}, to avoid conflicting with React’s new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html`'
92+
});
93+
}
6794
return;
6895
}
6996

tests/lib/rules/jsx-key.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ ruleTester.run('jsx-key', rule, {
4848
{code: '[1, 2, 3].map(function(x) { return; });'},
4949
{code: 'foo(() => <div />);'},
5050
{code: 'foo(() => <></>);', parser: parsers.BABEL_ESLINT},
51-
{code: '<></>;', parser: parsers.BABEL_ESLINT}
51+
{code: '<></>;', parser: parsers.BABEL_ESLINT},
52+
{code: '<App {...{}} />;', parser: parsers.BABEL_ESLINT},
53+
{code: '<App key="keyBeforeSpread" {...{}} />;', parser: parsers.BABEL_ESLINT, options: [{checkKeyMustBeforeSpread: true}]},
54+
{code: '<App key="keyBeforeSpread" {...{}} />;', parser: parsers.TYPESCRIPT_ESLINT, options: [{checkKeyMustBeforeSpread: true}]},
55+
{code: '<div key="keyBeforeSpread" {...{}} />;', parser: parsers.BABEL_ESLINT, options: [{checkKeyMustBeforeSpread: true}]},
56+
{code: '<div key="keyBeforeSpread" {...{}} />;', parser: parsers.TYPESCRIPT_ESLINT, options: [{checkKeyMustBeforeSpread: true}]}
5257
],
5358
invalid: [].concat({
5459
code: '[<App />];',
@@ -88,5 +93,29 @@ ruleTester.run('jsx-key', rule, {
8893
options: [{checkFragmentShorthand: true}],
8994
settings,
9095
errors: [{message: 'Missing "key" prop for element in array. Shorthand fragment syntax does not support providing keys. Use Act.Frag instead'}]
96+
}, {
97+
code: '[<App {...obj} key="keyAfterSpread" />];',
98+
parser: parsers.BABEL_ESLINT,
99+
options: [{checkKeyMustBeforeSpread: true}],
100+
settings,
101+
errors: [{message: '`key` prop must before any `{...spread}, to avoid conflicting with React’s new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html`'}]
102+
}, {
103+
code: '[<App {...obj} key="keyAfterSpread" />];',
104+
parser: parsers.TYPESCRIPT_ESLINT,
105+
options: [{checkKeyMustBeforeSpread: true}],
106+
settings,
107+
errors: [{message: '`key` prop must before any `{...spread}, to avoid conflicting with React’s new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html`'}]
108+
}, {
109+
code: '[<div {...obj} key="keyAfterSpread" />];',
110+
parser: parsers.BABEL_ESLINT,
111+
options: [{checkKeyMustBeforeSpread: true}],
112+
settings,
113+
errors: [{message: '`key` prop must before any `{...spread}, to avoid conflicting with React’s new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html`'}]
114+
}, {
115+
code: '[<div {...obj} key="keyAfterSpread" />];',
116+
parser: parsers.TYPESCRIPT_ESLINT,
117+
options: [{checkKeyMustBeforeSpread: true}],
118+
settings,
119+
errors: [{message: '`key` prop must before any `{...spread}, to avoid conflicting with React’s new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html`'}]
91120
})
92121
});

0 commit comments

Comments
 (0)