Skip to content

Commit 6e54f69

Browse files
authored
Merge pull request #1458 from jackyho112/make-jsx-curly-brace-presence-not-care-about-some-cases
[jsx-curly-brace-presence] Bail out in warning JSX expression when some chars exist
2 parents a120758 + c4ff64c commit 6e54f69

File tree

3 files changed

+233
-147
lines changed

3 files changed

+233
-147
lines changed

docs/rules/jsx-curly-brace-presence.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ will be warned and fixed to:
122122
<App prop="Hello world">Hello world</App>;
123123
```
124124

125-
* If the rule is set to enforce curly braces and the strings have quotes, it will be fixed with double quotes for JSX children and the normal way for JSX attributes.
125+
* If the rule is set to enforce curly braces and the strings have quotes, it will be fixed with double quotes for JSX children and the normal way for JSX attributes. Also, double quotes will be escaped in the fix.
126126

127127
For example:
128128

@@ -137,12 +137,14 @@ will warned and fixed to:
137137
<App prop={"Hello \"foo\" world"}>{"Hello 'foo' \"bar\" world"}</App>;
138138
```
139139

140-
* If the rule is set to get rid of unnecessary curly braces and the strings have escaped characters, it will not warn or fix for JSX children because JSX expressions are necessary in this case. For instance:
140+
* If the rule is set to get rid of unnecessary curly braces(JSX expression) and there are characters that need to be escaped in its JSX form, such as quote characters, [forbidden JSX text characters](https://facebook.github.io/jsx/), escaped characters and anything that looks like HTML entity names, the code will not be warned because the fix may make the code less readable.
141141

142142
The following pattern will not be given a warning even if `'never'` is passed.
143143

144144
```jsx
145+
<Color text={"\u00a0"} />
145146
<App>{"Hello \u00b7 world"}</App>;
147+
<style type="text/css">{'.main { margin-top: 0; }'}</style>;
146148
```
147149

148150
## When Not To Use It

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

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/**
22
* @fileoverview Enforce curly braces or disallow unnecessary curly brace in JSX
33
* @author Jacky Ho
4+
* @author Simon Lydell
45
*/
56
'use strict';
67

@@ -59,18 +60,45 @@ module.exports = {
5960
{props: ruleOptions, children: ruleOptions} :
6061
Object.assign({}, DEFAULT_CONFIG, ruleOptions);
6162

62-
function containsBackslashForEscaping(rawStringValue) {
63+
function containsLineTerminators(rawStringValue) {
64+
return /[\n\r\u2028\u2029]/.test(rawStringValue);
65+
}
66+
67+
function containsBackslash(rawStringValue) {
6368
return rawStringValue.includes('\\');
6469
}
6570

71+
function containsHTMLEntity(rawStringValue) {
72+
return /&[A-Za-z\d#]+;/.test(rawStringValue);
73+
}
74+
75+
function containsDisallowedJSXTextChars(rawStringValue) {
76+
return /[{<>}]/.test(rawStringValue);
77+
}
78+
79+
function containsQuoteCharacters(value) {
80+
return /['"]/.test(value);
81+
}
82+
6683
function escapeDoubleQuotes(rawStringValue) {
6784
return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"');
6885
}
6986

87+
function escapeBackslashes(rawStringValue) {
88+
return rawStringValue.replace(/\\/g, '\\\\');
89+
}
90+
91+
function needToEscapeCharacterForJSX(raw) {
92+
return (
93+
containsBackslash(raw) ||
94+
containsHTMLEntity(raw) ||
95+
containsDisallowedJSXTextChars(raw)
96+
);
97+
}
98+
7099
/**
71100
* Report and fix an unnecessary curly brace violation on a node
72101
* @param {ASTNode} node - The AST node with an unnecessary JSX expression
73-
* @param {String} text - The text to replace the unnecessary JSX expression
74102
*/
75103
function reportUnnecessaryCurly(JSXExpressionNode) {
76104
context.report({
@@ -83,11 +111,10 @@ module.exports = {
83111

84112
let textToReplace;
85113
if (parentType === 'JSXAttribute') {
86-
textToReplace = `"${escapeDoubleQuotes(
87-
expressionType === 'TemplateLiteral' ?
88-
expression.quasis[0].value.raw :
89-
expression.raw.substring(1, expression.raw.length - 1)
90-
)}"`;
114+
textToReplace = `"${expressionType === 'TemplateLiteral' ?
115+
expression.quasis[0].value.raw :
116+
expression.raw.substring(1, expression.raw.length - 1)
117+
}"`;
91118
} else {
92119
textToReplace = expressionType === 'TemplateLiteral' ?
93120
expression.quasis[0].value.cooked : expression.value;
@@ -103,34 +130,51 @@ module.exports = {
103130
node: literalNode,
104131
message: 'Need to wrap this literal in a JSX expression.',
105132
fix: function(fixer) {
133+
// If a HTML entity name is found, bail out because it can be fixed
134+
// by either using the real character or the unicode equivalent.
135+
// If it contains any line terminator character, bail out as well.
136+
if (
137+
containsHTMLEntity(literalNode.raw) ||
138+
containsLineTerminators(literalNode.raw)
139+
) {
140+
return null;
141+
}
142+
106143
const expression = literalNode.parent.type === 'JSXAttribute' ?
107-
`{"${escapeDoubleQuotes(
144+
`{"${escapeDoubleQuotes(escapeBackslashes(
108145
literalNode.raw.substring(1, literalNode.raw.length - 1)
109-
)}"}` :
146+
))}"}` :
110147
`{${JSON.stringify(literalNode.value)}}`;
111148

112149
return fixer.replaceText(literalNode, expression);
113150
}
114151
});
115152
}
116153

154+
// Bail out if there is any character that needs to be escaped in JSX
155+
// because escaping decreases readiblity and the original code may be more
156+
// readible anyway or intentional for other specific reasons
117157
function lintUnnecessaryCurly(JSXExpressionNode) {
118158
const expression = JSXExpressionNode.expression;
119159
const expressionType = expression.type;
120160
const parentType = JSXExpressionNode.parent.type;
121161

122162
if (
123163
expressionType === 'Literal' &&
124-
typeof expression.value === 'string' && (
125-
parentType === 'JSXAttribute' ||
126-
!containsBackslashForEscaping(expression.raw))
164+
typeof expression.value === 'string' &&
165+
!needToEscapeCharacterForJSX(expression.raw) && (
166+
parentType === 'JSXElement' ||
167+
!containsQuoteCharacters(expression.value)
168+
)
127169
) {
128170
reportUnnecessaryCurly(JSXExpressionNode);
129171
} else if (
130172
expressionType === 'TemplateLiteral' &&
131-
expression.expressions.length === 0 && (
132-
parentType === 'JSXAttribute' ||
133-
!containsBackslashForEscaping(expression.quasis[0].value.raw))
173+
expression.expressions.length === 0 &&
174+
!needToEscapeCharacterForJSX(expression.quasis[0].value.raw) && (
175+
parentType === 'JSXElement' ||
176+
!containsQuoteCharacters(expression.quasis[0].value.cooked)
177+
)
134178
) {
135179
reportUnnecessaryCurly(JSXExpressionNode);
136180
}
@@ -170,17 +214,13 @@ module.exports = {
170214

171215
return {
172216
JSXExpressionContainer: node => {
173-
const parent = node.parent;
174-
175-
if (shouldCheckForUnnecessaryCurly(parent, userConfig)) {
217+
if (shouldCheckForUnnecessaryCurly(node.parent, userConfig)) {
176218
lintUnnecessaryCurly(node);
177219
}
178220
},
179221

180222
Literal: node => {
181-
const parentType = node.parent.type;
182-
183-
if (shouldCheckForMissingCurly(parentType, userConfig)) {
223+
if (shouldCheckForMissingCurly(node.parent.type, userConfig)) {
184224
reportMissingCurly(node);
185225
}
186226
}

0 commit comments

Comments
 (0)