From 36adb1e93a4ab2f6d36e8482c4abda73e7311816 Mon Sep 17 00:00:00 2001 From: Morlay Date: Mon, 19 Oct 2020 10:27:37 +0800 Subject: [PATCH] [feat]: added option checkKeyMustBeforeSpread of react/jsx-key --- docs/rules/jsx-key.md | 10 ++++++++++ lib/rules/jsx-key.js | 29 ++++++++++++++++++++++++++++- tests/lib/rules/jsx-key.js | 31 ++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/docs/rules/jsx-key.md b/docs/rules/jsx-key.md index 705c627043..224d643f43 100644 --- a/docs/rules/jsx-key.md +++ b/docs/rules/jsx-key.md @@ -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 +; +``` + ## When not to use If you are not using JSX then you can disable this rule. diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index 3bf496edb2..2e4abe14bb 100644 --- a/lib/rules/jsx-key.js +++ b/lib/rules/jsx-key.js @@ -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'); @@ -14,7 +15,8 @@ const pragmaUtil = require('../util/pragma'); // ------------------------------------------------------------------------------ const defaultOptions = { - checkFragmentShorthand: false + checkFragmentShorthand: false, + checkKeyMustBeforeSpread: false }; module.exports = { @@ -31,6 +33,10 @@ module.exports = { checkFragmentShorthand: { type: 'boolean', default: defaultOptions.checkFragmentShorthand + }, + checkKeyMustBeforeSpread: { + type: 'boolean', + default: defaultOptions.checkKeyMustBeforeSpread } }, additionalProperties: false @@ -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); @@ -61,9 +68,29 @@ module.exports = { return body.filter((item) => item.type === 'ReturnStatement')[0]; } + function isKeyAfterSpread(attributes) { + let hasFoundSpread = false; + return attributes.some((attribute) => { + if (attribute.type === 'JSXSpreadAttribute') { + hasFoundSpread = true; + return false; + } + if (attribute.type !== 'JSXAttribute') { + return false; + } + return hasFoundSpread && propName(attribute) === 'key'; + }); + } + return { JSXElement(node) { if (hasProp(node.openingElement.attributes, 'key')) { + if (checkKeyMustBeforeSpread && isKeyAfterSpread(node.openingElement.attributes)) { + context.report({ + node, + 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`' + }); + } return; } diff --git a/tests/lib/rules/jsx-key.js b/tests/lib/rules/jsx-key.js index 49632f59e9..1b6728a15c 100644 --- a/tests/lib/rules/jsx-key.js +++ b/tests/lib/rules/jsx-key.js @@ -48,7 +48,12 @@ ruleTester.run('jsx-key', rule, { {code: '[1, 2, 3].map(function(x) { return; });'}, {code: 'foo(() =>
);'}, {code: 'foo(() => <>);', parser: parsers.BABEL_ESLINT}, - {code: '<>;', parser: parsers.BABEL_ESLINT} + {code: '<>;', parser: parsers.BABEL_ESLINT}, + {code: ';', parser: parsers.BABEL_ESLINT}, + {code: ';', parser: parsers.BABEL_ESLINT, options: [{checkKeyMustBeforeSpread: true}]}, + {code: ';', parser: parsers.TYPESCRIPT_ESLINT, options: [{checkKeyMustBeforeSpread: true}]}, + {code: '
;', parser: parsers.BABEL_ESLINT, options: [{checkKeyMustBeforeSpread: true}]}, + {code: '
;', parser: parsers.TYPESCRIPT_ESLINT, options: [{checkKeyMustBeforeSpread: true}]} ], invalid: [].concat({ code: '[];', @@ -88,5 +93,29 @@ 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: '[];', + parser: parsers.BABEL_ESLINT, + options: [{checkKeyMustBeforeSpread: true}], + settings, + 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`'}] + }, { + code: '[];', + parser: parsers.TYPESCRIPT_ESLINT, + options: [{checkKeyMustBeforeSpread: true}], + settings, + 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`'}] + }, { + code: '[
];', + parser: parsers.BABEL_ESLINT, + options: [{checkKeyMustBeforeSpread: true}], + settings, + 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`'}] + }, { + code: '[
];', + parser: parsers.TYPESCRIPT_ESLINT, + options: [{checkKeyMustBeforeSpread: true}], + settings, + 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`'}] }) });