From 4d19cd6377f92ad8f7a0493ff1b2b85ada0c579b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 10 Jul 2022 09:44:43 -0700 Subject: [PATCH 1/3] [Fix] `jsx-key`: avoid a crash from optional chaining from #3320 Fixes #3327 --- CHANGELOG.md | 2 ++ lib/rules/jsx-key.js | 2 +- tests/helpers/parsers.js | 6 +++-- tests/lib/rules/jsx-key.js | 24 +++++++++++++++++++ .../jsx-no-constructed-context-values.js | 3 --- tests/lib/rules/no-array-index-key.js | 6 ----- tests/lib/rules/no-unused-prop-types.js | 12 ---------- tests/lib/rules/no-unused-state.js | 3 --- 8 files changed, 31 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca44284b72..300aabda17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,14 @@ 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) ### 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) +[#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..e2aa757d3d 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 (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..560282f05b 100644 --- a/tests/helpers/parsers.js +++ b/tests/helpers/parsers.js @@ -130,11 +130,13 @@ const parsers = { const tsOld = !skipTS && !features.has('no-ts-old'); const tsNew = !skipTS && !features.has('no-ts-new'); + const minES = features.has('class fields') ? 2022 : (features.has('optional chaining') ? 2020 : 5); // eslint-disable-line no-nested-ternary + return [].concat( skipBase ? [] : addComment( - Object.assign({}, test, features.has('class fields') && { + Object.assign({}, test, minES > 5 && { parserOptions: Object.assign({}, test.parserOptions, { - ecmaVersion: Math.max((test.parserOptions && test.parserOptions.ecmaVersion) || 0, 2022), + ecmaVersion: Math.max((test.parserOptions && test.parserOptions.ecmaVersion) || 0, minES), }), }), '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: ` From 86f17d25d2a2b9bf9024cb5f6b67ad3bb8474911 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 10 Jul 2022 10:00:35 -0700 Subject: [PATCH 2/3] [Tests] fix ecmaVersion logic --- tests/helpers/parsers.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/helpers/parsers.js b/tests/helpers/parsers.js index 560282f05b..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( @@ -130,13 +131,11 @@ const parsers = { const tsOld = !skipTS && !features.has('no-ts-old'); const tsNew = !skipTS && !features.has('no-ts-new'); - const minES = features.has('class fields') ? 2022 : (features.has('optional chaining') ? 2020 : 5); // eslint-disable-line no-nested-ternary - return [].concat( skipBase ? [] : addComment( - Object.assign({}, test, minES > 5 && { + Object.assign({}, test, typeof es !== 'undefined' && { parserOptions: Object.assign({}, test.parserOptions, { - ecmaVersion: Math.max((test.parserOptions && test.parserOptions.ecmaVersion) || 0, minES), + ecmaVersion: Math.max((test.parserOptions && test.parserOptions.ecmaVersion) || 0, es), }), }), 'default' From f7fe38f7ac8e3dfde4d0befe17b5c28ff5fed55a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 11 Jul 2022 08:02:21 -0700 Subject: [PATCH 3/3] [Fix] `jsx-key`: avoid a crash on a non-array node.body Fixes #3328 --- CHANGELOG.md | 2 ++ lib/rules/jsx-key.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 300aabda17..6fa165d487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`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 diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index e2aa757d3d..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 if (node.body) { + } else if (Array.isArray(node.body)) { node.body.forEach((item) => { if (item.type === 'IfStatement') { getReturnStatements(item, returnStatements);