Skip to content

Commit

Permalink
[feat]: added option checkKeyMustBeforeSpread of react/jsx-key
Browse files Browse the repository at this point in the history
  • Loading branch information
morlay committed Oct 19, 2020
1 parent efd3da3 commit fd32cd5
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 2 deletions.
10 changes: 10 additions & 0 deletions docs/rules/jsx-key.md
Expand Up @@ -47,6 +47,16 @@ The following patterns are considered warnings:
data.map(x => <>{x}</>);
```

### `checkKeyMustBeforeSpread` (default: `false`)

When `true` the rule will check if key prop after spread to avoid [createElement fallback](https://github.com/facebook/react/issues/20031#issuecomment-710346866).

The following patterns are considered warnings:

```jsx
<span {...spread} key={"key-after-spread"} />;
```

## When not to use

If you are not using JSX then you can disable this rule.
Expand Down
34 changes: 33 additions & 1 deletion lib/rules/jsx-key.js
Expand Up @@ -6,6 +6,7 @@
'use strict';

const hasProp = require('jsx-ast-utils/hasProp');
const propName = require('jsx-ast-utils/propName');
const docsUrl = require('../util/docsUrl');
const pragmaUtil = require('../util/pragma');

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

const defaultOptions = {
checkFragmentShorthand: false
checkFragmentShorthand: false,
checkKeyMustBeforeSpread: false
};

module.exports = {
Expand All @@ -31,6 +33,10 @@ module.exports = {
checkFragmentShorthand: {
type: 'boolean',
default: defaultOptions.checkFragmentShorthand
},
checkKeyMustBeforeSpread: {
type: 'boolean',
default: defaultOptions.checkKeyMustBeforeSpread
}
},
additionalProperties: false
Expand All @@ -40,6 +46,7 @@ module.exports = {
create(context) {
const options = Object.assign({}, defaultOptions, context.options[0]);
const checkFragmentShorthand = options.checkFragmentShorthand;
const checkKeyMustBeforeSpread = options.checkKeyMustBeforeSpread;
const reactPragma = pragmaUtil.getFromContext(context);
const fragmentPragma = pragmaUtil.getFragmentFromContext(context);

Expand All @@ -61,9 +68,34 @@ module.exports = {
return body.filter((item) => item.type === 'ReturnStatement')[0];
}

function isKeyAfterSpread(attributes) {
let keyAttributeIndex = -1;
let spreadAttributeIndex = -1;

attributes.forEach((attribute, i) => {
if (attribute.type === 'JSXSpreadAttribute') {
spreadAttributeIndex = i;
}

if (attribute.type === 'JSXAttribute') {
if (propName(attribute) === 'key') {
keyAttributeIndex = i;
}
}
});

return spreadAttributeIndex !== -1 && keyAttributeIndex > spreadAttributeIndex;
}

return {
JSXElement(node) {
if (hasProp(node.openingElement.attributes, 'key')) {
if (checkKeyMustBeforeSpread && isKeyAfterSpread(node.openingElement.attributes)) {
context.report({
node,
message: '"key" prop must before "...spread"'
});
}
return;
}

Expand Down
10 changes: 9 additions & 1 deletion tests/lib/rules/jsx-key.js
Expand Up @@ -48,7 +48,9 @@ ruleTester.run('jsx-key', rule, {
{code: '[1, 2, 3].map(function(x) { return; });'},
{code: 'foo(() => <div />);'},
{code: 'foo(() => <></>);', parser: parsers.BABEL_ESLINT},
{code: '<></>;', parser: parsers.BABEL_ESLINT}
{code: '<></>;', parser: parsers.BABEL_ESLINT},
{code: '<App {...{}} />;', parser: parsers.BABEL_ESLINT},
{code: '<App key="keyBeforeSpread" {...{}} />;', parser: parsers.BABEL_ESLINT}
],
invalid: [].concat({
code: '[<App />];',
Expand Down Expand Up @@ -88,5 +90,11 @@ ruleTester.run('jsx-key', rule, {
options: [{checkFragmentShorthand: true}],
settings,
errors: [{message: 'Missing "key" prop for element in array. Shorthand fragment syntax does not support providing keys. Use Act.Frag instead'}]
}, {
code: '[<App {...obj} key="keyAfterSpread" />];',
parser: parsers.BABEL_ESLINT,
options: [{checkKeyMustBeforeSpread: true}],
settings,
errors: [{message: '"key" prop must before "...spread"'}]
})
});

0 comments on commit fd32cd5

Please sign in to comment.