Repository: jsx-eslint/eslint-plugin-react
Pull request #3538 (2023-02-24 23:52:33)

[Fix] `jsx-curly-brace-presence`: handle single and only expression template literals
fix https://github.com/jsx-eslint/eslint-plugin-react/issues/3537


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0f4f100..5ff143d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 
 ### Fixed
 * [`no-array-index-key`]: consider flatMap ([#3530][] @k-yle)
+* [`jsx-curly-brace-presence`]: handle single and only expression template literals ([#3538][] @taozhou-glean)
 
+[#3538]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3538
 [#3530]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3530
 [#3529]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3529
 
diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js
index c7b3e55d..28b72e86 100755
--- a/lib/rules/jsx-curly-brace-presence.js
+++ b/lib/rules/jsx-curly-brace-presence.js
@@ -131,6 +131,10 @@ module.exports = {
       return containsLineTerminators(text) && text.trim() === '';
     }
 
+    function isSingleExpressionTemplateLiteral(child) {
+      return child.type === 'TemplateLiteral' && child.expressions.length === 1 && child.quasis.map((quasis) => quasis.value.raw).join('') === '';
+    }
+
     function wrapNonHTMLEntities(text) {
       const HTML_ENTITY = '<HTML_ENTITY>';
       const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map((word) => (
@@ -177,6 +181,9 @@ module.exports = {
           if (jsxUtil.isJSX(expression)) {
             const sourceCode = context.getSourceCode();
             textToReplace = sourceCode.getText(expression);
+          } else if (isSingleExpressionTemplateLiteral(expression)) {
+            const sourceCode = context.getSourceCode();
+            textToReplace = `{${sourceCode.getText(expression.expressions[0])}}`;
           } else {
             const expressionType = expression && expression.type;
             const parentType = JSXExpressionNode.parent.type;
@@ -279,6 +286,9 @@ module.exports = {
         && !containsQuoteCharacters(expression.quasis[0].value.cooked)
       ) {
         reportUnnecessaryCurly(JSXExpressionNode);
+      } else if (
+        isSingleExpressionTemplateLiteral(expression)) {
+        reportUnnecessaryCurly(JSXExpressionNode);
       } else if (jsxUtil.isJSX(expression)) {
         reportUnnecessaryCurly(JSXExpressionNode);
       }


Test patch:
diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js
index 8c669292..284d008b 100755
--- a/tests/lib/rules/jsx-curly-brace-presence.js
+++ b/tests/lib/rules/jsx-curly-brace-presence.js
@@ -468,6 +468,14 @@ ruleTester.run('jsx-curly-brace-presence', rule, {
       `,
       features: ['no-ts'],
       options: ['never'],
+    },
+    {
+      code: '<App label={`${label}${suffix}`} />',
+      options: [{ props: 'never' }],
+    },
+    {
+      code: '<App>{`${label}${suffix}`}</App>',
+      options: [{ children: 'never' }],
     }
   )),
 
@@ -931,6 +939,18 @@ ruleTester.run('jsx-curly-brace-presence', rule, {
       errors: [{ messageId: 'unnecessaryCurly' }],
       options: [{ props: 'never', children: 'never', propElementValues: 'never' }],
       features: ['no-ts'],
+    },
+    {
+      code: '<App label={`${label}`} />',
+      output: '<App label={label} />',
+      errors: [{ messageId: 'unnecessaryCurly' }],
+      options: [{ props: 'never', children: 'never', propElementValues: 'never' }],
+    },
+    {
+      code: '<App>{`${label}`}</App>',
+      output: '<App>{label}</App>',
+      errors: [{ messageId: 'unnecessaryCurly' }],
+      options: [{ props: 'never', children: 'never', propElementValues: 'never' }],
     }
   )),
 });