Skip to content

Commit 7f87310

Browse files
rafbgarcialjharb
authored andcommitted
[Fix] jsx-curly-brace-presence: Fix curly-brace-presence edge cases
1 parent 1ef658a commit 7f87310

File tree

2 files changed

+113
-5
lines changed

2 files changed

+113
-5
lines changed

lib/rules/jsx-curly-brace-presence.js

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ module.exports = {
6262
},
6363

6464
create(context) {
65+
const HTML_ENTITY_REGEX = () => /&[A-Za-z\d#]+;/g;
6566
const ruleOptions = context.options[0];
6667
const userConfig = typeof ruleOptions === 'string' ?
6768
{props: ruleOptions, children: ruleOptions} :
@@ -76,7 +77,11 @@ module.exports = {
7677
}
7778

7879
function containsHTMLEntity(rawStringValue) {
79-
return /&[A-Za-z\d#]+;/.test(rawStringValue);
80+
return HTML_ENTITY_REGEX().test(rawStringValue);
81+
}
82+
83+
function containsOnlyHtmlEntities(rawStringValue) {
84+
return rawStringValue.replace(HTML_ENTITY_REGEX(), '').trim() === '';
8085
}
8186

8287
function containsDisallowedJSXTextChars(rawStringValue) {
@@ -111,6 +116,42 @@ module.exports = {
111116
return false;
112117
}
113118

119+
function isLineBreak(text) {
120+
return containsLineTerminators(text) && text.trim() === '';
121+
}
122+
123+
function wrapNonHTMLEntities(text) {
124+
const HTML_ENTITY = '<HTML_ENTITY>';
125+
const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map(word => (
126+
word === '' ? '' : `{${JSON.stringify(word)}}`
127+
)).join(HTML_ENTITY);
128+
129+
const htmlEntities = text.match(HTML_ENTITY_REGEX());
130+
return htmlEntities.reduce((acc, htmlEntitiy) => (
131+
acc.replace(HTML_ENTITY, htmlEntitiy)
132+
), withCurlyBraces);
133+
}
134+
135+
function wrapWithCurlyBraces(rawText) {
136+
if (!containsLineTerminators(rawText)) {
137+
return `{${JSON.stringify(rawText)}}`;
138+
}
139+
140+
return rawText.split('\n').map((line) => {
141+
if (line.trim() === '') {
142+
return line;
143+
}
144+
const firstCharIndex = line.search(/[^\s]/);
145+
const leftWhitespace = line.slice(0, firstCharIndex);
146+
const text = line.slice(firstCharIndex);
147+
148+
if (containsHTMLEntity(line)) {
149+
return `${leftWhitespace}${wrapNonHTMLEntities(text)}`;
150+
}
151+
return `${leftWhitespace}{${JSON.stringify(text)}}`;
152+
}).join('\n');
153+
}
154+
114155
/**
115156
* Report and fix an unnecessary curly brace violation on a node
116157
* @param {ASTNode} JSXExpressionNode - The AST node with an unnecessary JSX expression
@@ -153,8 +194,9 @@ module.exports = {
153194
// by either using the real character or the unicode equivalent.
154195
// If it contains any line terminator character, bail out as well.
155196
if (
156-
containsHTMLEntity(literalNode.raw) ||
157-
containsLineTerminators(literalNode.raw)
197+
containsOnlyHtmlEntities(literalNode.raw) ||
198+
(literalNode.parent.type === 'JSXAttribute' && containsLineTerminators(literalNode.raw)) ||
199+
isLineBreak(literalNode.raw)
158200
) {
159201
return null;
160202
}
@@ -163,7 +205,7 @@ module.exports = {
163205
`{"${escapeDoubleQuotes(escapeBackslashes(
164206
literalNode.raw.substring(1, literalNode.raw.length - 1)
165207
))}"}` :
166-
`{${JSON.stringify(literalNode.value)}}`;
208+
wrapWithCurlyBraces(literalNode.raw);
167209

168210
return fixer.replaceText(literalNode, expression);
169211
}
@@ -299,7 +341,10 @@ module.exports = {
299341
}
300342

301343
function shouldCheckForMissingCurly(node, config) {
302-
if (node.raw.trim() === '') {
344+
if (
345+
isLineBreak(node.raw) ||
346+
containsOnlyHtmlEntities(node.raw)
347+
) {
303348
return false;
304349
}
305350
const parent = node.parent;

tests/lib/rules/jsx-curly-brace-presence.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,15 @@ ruleTester.run('jsx-curly-brace-presence', rule, {
391391
`,
392392
parser: parsers.BABEL_ESLINT,
393393
options: [{children: 'always'}]
394+
},
395+
{
396+
code: `
397+
<App>
398+
<Component />&nbsp;
399+
&nbsp;
400+
</App>
401+
`,
402+
options: [{children: 'always'}]
394403
}
395404
],
396405

@@ -698,6 +707,60 @@ ruleTester.run('jsx-curly-brace-presence', rule, {
698707
{message: missingCurlyMessage}, {message: missingCurlyMessage}
699708
],
700709
options: ['always']
710+
},
711+
{
712+
code: `
713+
<App>
714+
foo bar
715+
<div>foo bar foo</div>
716+
<span>
717+
foo bar <i>foo bar</i>
718+
<strong>
719+
foo bar
720+
</strong>
721+
</span>
722+
</App>
723+
`,
724+
output: `
725+
<App>
726+
{"foo bar"}
727+
<div>{"foo bar foo"}</div>
728+
<span>
729+
{"foo bar "}<i>{"foo bar"}</i>
730+
<strong>
731+
{"foo bar"}
732+
</strong>
733+
</span>
734+
</App>
735+
`,
736+
errors: [
737+
{message: missingCurlyMessage},
738+
{message: missingCurlyMessage},
739+
{message: missingCurlyMessage},
740+
{message: missingCurlyMessage},
741+
{message: missingCurlyMessage}
742+
],
743+
options: [{children: 'always'}]
744+
},
745+
{
746+
code: `
747+
<App>
748+
&lt;Component&gt;
749+
&nbsp;<Component />&nbsp;
750+
&nbsp;
751+
</App>
752+
`,
753+
output: `
754+
<App>
755+
&lt;{"Component"}&gt;
756+
&nbsp;<Component />&nbsp;
757+
&nbsp;
758+
</App>
759+
`,
760+
errors: [
761+
{message: missingCurlyMessage}
762+
],
763+
options: [{children: 'always'}]
701764
}
702765
]
703766
});

0 commit comments

Comments
 (0)