-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[jsx-curly-brace-presence] Bail out in warning JSX expression when some chars exist #1458
[jsx-curly-brace-presence] Bail out in warning JSX expression when some chars exist #1458
Conversation
…ons when some chars exist
@@ -103,34 +127,42 @@ module.exports = { | |||
node: literalNode, | |||
message: 'Need to wrap this literal in a JSX expression.', | |||
fix: function(fixer) { | |||
// Leave it to the author to fix as it can be fixed by either a | |||
// real character or unicode | |||
if (containsHTMLEntity(literalNode.raw)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return rawStringValue.replace(/\\/g, '\\\\'); | ||
} | ||
|
||
function needToEscapeCharacterForJSX(raw, cooked) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it is also necessary to check for patterns like U+2029
because it doesn't seem like it is recognized in both JS and JSX. Correct me if I am wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to see some whitespace tests as well. babel repl
return rawStringValue.includes('\\'); | ||
} | ||
|
||
function containsHTMLEntity(rawStringValue) { | ||
return rawStringValue.match(/&([A-Za-z\d]+);/g); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There can also be a #
after the &
: {
.
You could also use /regex/.text(value)
to return a boolean instead of null
or an array of matches. No need for the g
modifer.
} | ||
|
||
function containsDisallowedJSXTextChars(rawStringValue) { | ||
return rawStringValue.match(/{|<|>|}/g); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be written as /[{<>}]/.test(rawStringValue)
} | ||
|
||
function containsQuoteCharacters(value) { | ||
return value.match(/'|"/g); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be written as /['"]/.test(value)
options: [{props: 'never'}], | ||
errors: [{message: unnecessaryCurlyMessage}] | ||
}, | ||
{ | ||
code: '<MyComponent prop=\'bar\'>foo</MyComponent>', | ||
output: '<MyComponent prop={\"bar\"}>foo</MyComponent>', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
\"
is a useless escape here. Do you mean "
or \\"
? There are several other occurrences of this throughout this file; you might want to go through it from top to bottom correcting all the useless escapes you come across.
(It might even be worth using template literals for all/most of these tests to make them easier to read: No need to escape '
s.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember I did this because of an Eslint rule. But I agree with you. I will try to use template literals on at least the tests in which I have to escape quote characters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, it's fine to add /* eslint-disable that-rule */
at the top of this file if needed. You should be allowed to break rules when you can motivate why. But I'm not a maintainer of this repo so I can't say for sure if that's OK. But it would be sad if it's not allowed to make it easier to succeed writing correct tests.
@@ -338,30 +336,6 @@ ruleTester.run('jsx-curly-brace-presence', rule, { | |||
] | |||
}, | |||
{ | |||
code: '<App>{\'foo "bar"\'}</App>', | |||
output: '<App>foo \"bar\"</App>', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This autofix could have been kept. <App>foo "bar"</App>
is valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't remember – have we addressed or talked about this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, since it is valid, I added back the fix for cases like this and the tests.
}, | ||
{ | ||
code: '<App>{\"foo \'bar\'\"}</App>', | ||
output: '<App>foo \'bar\'</App>', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This autofix could also have been kept.
}, | ||
{ | ||
code: '<App prop=\'foo · bar\' />', | ||
output: '<App prop=\'foo · bar\' />', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't spot the difference between code
and output
here.
}, | ||
{ | ||
code: '<App>foo · bar</App>', | ||
output: '<App>foo · bar</App>', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not here either.
const expression = literalNode.parent.type === 'JSXAttribute' ? | ||
`{"${escapeDoubleQuotes( | ||
`{"${escapeDoubleQuotes(escapeBackslashes( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also need to escape line terminators here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually wanted to ask you about them. I know you mentioned them here.
similarly for the line terminators: CR, LF, U+2028 and U+2029
I am not quite familiar with those. Do you type exactly those characters of any of the sets into the string to terminate a line? I tried having them in a JSX expression and it just displays those characters. Sorry about my lack of knowledge on the subject matter. 😀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Press the Enter key to type a LF (\n
) (or most likely CRLF \r\n
on Windows).
You can also run one of the following in the Chrome or Firefox dev tools console to copy the character to the clipboard:
copy('\n')
copy('\r')
copy('\u2028')
copy('\u2029')
Then you can simply paste the character.
But since the tests are written as JavaScript strings you should be able to use JavaScript escapes:
input: '<div propWithLineTerminators="\n\r\u2028\u2029"/>',
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think we should bail out if we find any line terminators at all, because of Babel's whitespace stripping thing.
Can't thank you enough for the feedback! Will update the PR soon based on your suggestions! |
errors: [ | ||
{message: missingCurlyMessage}, {message: missingCurlyMessage} | ||
], | ||
options: ['always'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check out the babel repl. It strips some whitespace. We either have to replicate that, or bail out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing it out! If we were to bail out, I would check for multiple whitespaces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should bail out if there's a line terminator in there. Multiple spaces on a single line should be fine.
I think I have covered most of the things you said. Let me know if you have any other improvement in mind. 😀 |
@@ -59,6 +59,10 @@ module.exports = { | |||
{props: ruleOptions, children: ruleOptions} : | |||
Object.assign({}, DEFAULT_CONFIG, ruleOptions); | |||
|
|||
function containsLineTerminators(rawStringValue) { | |||
return /[\n\r\u2028\u2029]+/.test(rawStringValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need for the +
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work on this, @jackyho112!
I can't find anything more to say on this on this PR, and can't think about any more edge cases to cover now.
(Note to other reviewers: All comments above are resolved. GitHub just simply can't know to mark them as outdated.)
return rawStringValue.includes('\\'); | ||
} | ||
|
||
function containsHTMLEntity(rawStringValue) { | ||
return /&([A-Za-z\d#]+);/.test(rawStringValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Final little nitpick: The parentheses in the regex are unnecessary. (Sorry, I can't help myself when I see a regex.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's all good. I am actually quite grateful for all your regex comments. I learned a lot from them! 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM but let's get lots of reviews on it.
Reviewers: Don't feel intimidated by the walls of comments me and @jackyho112 have exchanged! If you look a the implementation (one file), you'll see that it's pretty clear and concise. The rest of the changes are just docs and tests updates. TL;DR on the whole issue: Some characters make it unclear* if braces are unnecessary or not. If we find such characters, bail out. * Because readability can suffer badly. |
This is an attempt to fix #1449, so that the jsx-curly-brace-presence rule, when set to get rid of unnecessary curly braces(JSX expression), will bail out or deem a JSX expression necessary when a character that needs to be escaped in its JSX form is found in the expression. It also improves the fix in enforcing curly braces by escaping backslashes in its JSX form.
Gigantic thanks to @lydell for suggesting the implementation plan for this PR. I hope I didn't screw it up too much. 😄
Let me know if there is any improvement you have in mind.
Fixes #1479. Fixes #1449.