diff --git a/CHANGELOG.md b/CHANGELOG.md index ca44284b72..6fa165d487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,16 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`jsx-no-literals`]: properly error on children with noAttributeStrings: true ([#3317][] @TildaDares) * [`jsx-key`]: catch key errors inside conditional statements ([#3320][] @TildaDares) * [`display-name`]: Accept forwardRef and Memo nesting in newer React versions ([#3321][] @TildaDares) +* [`jsx-key`]: avoid a crash from optional chaining from [#3320][] ([#3327][] @ljharb) +* [`jsx-key`]: avoid a crash on a non-array node.body from [#3320][] ([#3328][] @ljharb) ### Changed * [Refactor] [`jsx-indent-props`]: improved readability of the checkNodesIndent function ([#3315][] @caroline223) * [Tests] [`jsx-indent`], [`jsx-one-expression-per-line`]: add passing test cases ([#3314][] @ROSSROSALES) * [Refactor] `boolean-prop-naming`, `jsx-indent`: avoid assigning to arguments ([#3316][] @caroline223) +[#3328]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3328 +[#3327]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3327 [#3321]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3321 [#3320]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3320 [#3317]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3317 diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index 2b61cc20a1..e3731945a8 100644 --- a/lib/rules/jsx-key.js +++ b/lib/rules/jsx-key.js @@ -95,7 +95,7 @@ module.exports = { if (node.alternate) { getReturnStatements(node.alternate, returnStatements); } - } else { + } else if (Array.isArray(node.body)) { node.body.forEach((item) => { if (item.type === 'IfStatement') { getReturnStatements(item, returnStatements); diff --git a/tests/helpers/parsers.js b/tests/helpers/parsers.js index 12a4df78b0..b0e0a7af9c 100644 --- a/tests/helpers/parsers.js +++ b/tests/helpers/parsers.js @@ -57,7 +57,8 @@ const parsers = { } const features = new Set([].concat(test.features || [])); delete test.features; - const es = test.parserOptions && test.parserOptions.ecmaVersion; + + const es = features.has('class fields') ? 2022 : (features.has('optional chaining') ? 2020 : (test.parserOptions && test.parserOptions.ecmaVersion) || undefined); // eslint-disable-line no-nested-ternary function addComment(testObject, parser) { const extras = [].concat( @@ -132,9 +133,9 @@ const parsers = { return [].concat( skipBase ? [] : addComment( - Object.assign({}, test, features.has('class fields') && { + Object.assign({}, test, typeof es !== 'undefined' && { parserOptions: Object.assign({}, test.parserOptions, { - ecmaVersion: Math.max((test.parserOptions && test.parserOptions.ecmaVersion) || 0, 2022), + ecmaVersion: Math.max((test.parserOptions && test.parserOptions.ecmaVersion) || 0, es), }), }), 'default' diff --git a/tests/lib/rules/jsx-key.js b/tests/lib/rules/jsx-key.js index 4dae8973f3..6c007bb8bc 100644 --- a/tests/lib/rules/jsx-key.js +++ b/tests/lib/rules/jsx-key.js @@ -124,6 +124,30 @@ ruleTester.run('jsx-key', rule, { const onTextButtonClick = (e, item) => trackLink([, getAnalyticsUiElement(item), item.name], e); `, }, + { + code: ` + function Component({ allRatings }) { + return ( + + {Object.entries(allRatings)?.map(([key, value], index) => { + const rate = value?.split(/(?=[%, /])/); + + if (!rate) return null; + + return ( +
  • + + {rate?.[0]} + {rate?.[1]} +
  • + ); + })} +
    + ); + } + `, + features: ['optional chaining'], + }, ]), 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 7a59da820c..1ec0592382 100644 --- a/tests/lib/rules/jsx-no-constructed-context-values.js +++ b/tests/lib/rules/jsx-no-constructed-context-values.js @@ -63,9 +63,6 @@ ruleTester.run('react-no-constructed-context-values', rule, { } `, features: ['optional chaining'], - parserOptions: { - ecmaVersion: 2020, - }, }, { code: ` diff --git a/tests/lib/rules/no-array-index-key.js b/tests/lib/rules/no-array-index-key.js index 1faa12b1eb..e765d001ab 100644 --- a/tests/lib/rules/no-array-index-key.js +++ b/tests/lib/rules/no-array-index-key.js @@ -136,9 +136,6 @@ ruleTester.run('no-array-index-key', rule, { { code: 'foo?.map(child => )', features: ['optional chaining'], - parserOptions: { - ecmaVersion: 2020, - }, } ), @@ -349,9 +346,6 @@ ruleTester.run('no-array-index-key', rule, { code: 'foo?.map((child, i) => )', errors: [{ messageId: 'noArrayIndex' }], features: ['optional chaining'], - parserOptions: { - ecmaVersion: 2020, - }, }, { code: ` diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js index e70717427a..80f6405d32 100644 --- a/tests/lib/rules/no-unused-prop-types.js +++ b/tests/lib/rules/no-unused-prop-types.js @@ -807,9 +807,6 @@ ruleTester.run('no-unused-prop-types', rule, { }; `, features: ['optional chaining'], - parserOptions: { - ecmaVersion: 2020, - }, }, { code: ` @@ -839,9 +836,6 @@ ruleTester.run('no-unused-prop-types', rule, { module.exports = HelloComponent(); `, features: ['optional chaining'], - parserOptions: { - ecmaVersion: 2020, - }, }, { code: ` @@ -871,9 +865,6 @@ ruleTester.run('no-unused-prop-types', rule, { module.exports = HelloComponent(); `, features: ['optional chaining'], - parserOptions: { - ecmaVersion: 2020, - }, }, { code: ` @@ -926,9 +917,6 @@ ruleTester.run('no-unused-prop-types', rule, { }; `, features: ['optional chaining'], - parserOptions: { - ecmaVersion: 2020, - }, }, { code: ` diff --git a/tests/lib/rules/no-unused-state.js b/tests/lib/rules/no-unused-state.js index 380296ec4e..c37145a047 100644 --- a/tests/lib/rules/no-unused-state.js +++ b/tests/lib/rules/no-unused-state.js @@ -353,9 +353,6 @@ eslintTester.run('no-unused-state', rule, { } `, features: ['optional chaining'], - parserOptions: { - ecmaVersion: 2020, - }, }, { code: `