From 7f87310ae922cd43653ef913f7d1e1404029ad30 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Thu, 12 Dec 2019 22:51:15 -0300 Subject: [PATCH] [Fix] `jsx-curly-brace-presence`: Fix `curly-brace-presence` edge cases --- lib/rules/jsx-curly-brace-presence.js | 55 ++++++++++++++++-- tests/lib/rules/jsx-curly-brace-presence.js | 63 +++++++++++++++++++++ 2 files changed, 113 insertions(+), 5 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 95a8806bf7..deeda0bc97 100755 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -62,6 +62,7 @@ module.exports = { }, create(context) { + const HTML_ENTITY_REGEX = () => /&[A-Za-z\d#]+;/g; const ruleOptions = context.options[0]; const userConfig = typeof ruleOptions === 'string' ? {props: ruleOptions, children: ruleOptions} : @@ -76,7 +77,11 @@ module.exports = { } function containsHTMLEntity(rawStringValue) { - return /&[A-Za-z\d#]+;/.test(rawStringValue); + return HTML_ENTITY_REGEX().test(rawStringValue); + } + + function containsOnlyHtmlEntities(rawStringValue) { + return rawStringValue.replace(HTML_ENTITY_REGEX(), '').trim() === ''; } function containsDisallowedJSXTextChars(rawStringValue) { @@ -111,6 +116,42 @@ module.exports = { return false; } + function isLineBreak(text) { + return containsLineTerminators(text) && text.trim() === ''; + } + + function wrapNonHTMLEntities(text) { + const HTML_ENTITY = ''; + const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map(word => ( + word === '' ? '' : `{${JSON.stringify(word)}}` + )).join(HTML_ENTITY); + + const htmlEntities = text.match(HTML_ENTITY_REGEX()); + return htmlEntities.reduce((acc, htmlEntitiy) => ( + acc.replace(HTML_ENTITY, htmlEntitiy) + ), withCurlyBraces); + } + + function wrapWithCurlyBraces(rawText) { + if (!containsLineTerminators(rawText)) { + return `{${JSON.stringify(rawText)}}`; + } + + return rawText.split('\n').map((line) => { + if (line.trim() === '') { + return line; + } + const firstCharIndex = line.search(/[^\s]/); + const leftWhitespace = line.slice(0, firstCharIndex); + const text = line.slice(firstCharIndex); + + if (containsHTMLEntity(line)) { + return `${leftWhitespace}${wrapNonHTMLEntities(text)}`; + } + return `${leftWhitespace}{${JSON.stringify(text)}}`; + }).join('\n'); + } + /** * Report and fix an unnecessary curly brace violation on a node * @param {ASTNode} JSXExpressionNode - The AST node with an unnecessary JSX expression @@ -153,8 +194,9 @@ module.exports = { // by either using the real character or the unicode equivalent. // If it contains any line terminator character, bail out as well. if ( - containsHTMLEntity(literalNode.raw) || - containsLineTerminators(literalNode.raw) + containsOnlyHtmlEntities(literalNode.raw) || + (literalNode.parent.type === 'JSXAttribute' && containsLineTerminators(literalNode.raw)) || + isLineBreak(literalNode.raw) ) { return null; } @@ -163,7 +205,7 @@ module.exports = { `{"${escapeDoubleQuotes(escapeBackslashes( literalNode.raw.substring(1, literalNode.raw.length - 1) ))}"}` : - `{${JSON.stringify(literalNode.value)}}`; + wrapWithCurlyBraces(literalNode.raw); return fixer.replaceText(literalNode, expression); } @@ -299,7 +341,10 @@ module.exports = { } function shouldCheckForMissingCurly(node, config) { - if (node.raw.trim() === '') { + if ( + isLineBreak(node.raw) || + containsOnlyHtmlEntities(node.raw) + ) { return false; } const parent = node.parent; diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index ec295c9337..2b7268741d 100755 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -391,6 +391,15 @@ ruleTester.run('jsx-curly-brace-presence', rule, { `, parser: parsers.BABEL_ESLINT, options: [{children: 'always'}] + }, + { + code: ` + +   +   + + `, + options: [{children: 'always'}] } ], @@ -698,6 +707,60 @@ ruleTester.run('jsx-curly-brace-presence', rule, { {message: missingCurlyMessage}, {message: missingCurlyMessage} ], options: ['always'] + }, + { + code: ` + + foo bar +
foo bar foo
+ + foo bar foo bar + + foo bar + + +
+ `, + output: ` + + {"foo bar"} +
{"foo bar foo"}
+ + {"foo bar "}{"foo bar"} + + {"foo bar"} + + +
+ `, + errors: [ + {message: missingCurlyMessage}, + {message: missingCurlyMessage}, + {message: missingCurlyMessage}, + {message: missingCurlyMessage}, + {message: missingCurlyMessage} + ], + options: [{children: 'always'}] + }, + { + code: ` + + <Component> +    +   + + `, + output: ` + + <{"Component"}> +    +   + + `, + errors: [ + {message: missingCurlyMessage} + ], + options: [{children: 'always'}] } ] });