Skip to content
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

Conversation

jackyho112
Copy link
Contributor

@jackyho112 jackyho112 commented Sep 30, 2017

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.

@jackyho112 jackyho112 changed the title Make jsx-curly-brace-presence bail out in warning JSX expression in some cases [jsx-curly-brace-presence] Bail out in warning JSX expression when some characters exist Sep 30, 2017
@@ -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)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lydell

We could use String.fromCharCode here to fix but based on your comment, it may not be what the author has in mind. I think maybe it's best to keep it simple and let the author decide.

return rawStringValue.replace(/\\/g, '\\\\');
}

function needToEscapeCharacterForJSX(raw, cooked) {
Copy link
Contributor Author

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.

Copy link

@lydell lydell left a 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);
Copy link

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);
Copy link

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);
Copy link

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>',
Copy link

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.)

Copy link
Contributor Author

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.

Copy link

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>',
Copy link

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.

Copy link

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?

Copy link
Contributor Author

@jackyho112 jackyho112 Oct 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lydell

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>',
Copy link

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 &middot; bar\' />',
output: '<App prop=\'foo &middot; bar\' />',
Copy link

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 &middot; bar</App>',
output: '<App>foo &middot; bar</App>',
Copy link

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(
Copy link

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.

Copy link
Contributor Author

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. 😀

Copy link

@lydell lydell Oct 1, 2017

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"/>',

Copy link
Contributor Author

@jackyho112 jackyho112 Oct 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the example you mentioned, the output would be <div propWithLineTerminators={"\\n\\r\\u2028\\u2029"}/>? Because these characters would be displayed as they are in a JSX attribute.

In that case, wouldn't the parser have already handled that?

For <div propWithLineTerminators="\n\r\u2028\u2029"/>

screen shot 2017-10-01 at 12 44 28 pm

Copy link

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.

@jackyho112
Copy link
Contributor Author

@lydell

Can't thank you enough for the feedback! Will update the PR soon based on your suggestions!

errors: [
{message: missingCurlyMessage}, {message: missingCurlyMessage}
],
options: ['always']
Copy link

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.

Copy link
Contributor Author

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?

Copy link

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.

@jackyho112 jackyho112 changed the title [jsx-curly-brace-presence] Bail out in warning JSX expression when some characters exist [jsx-curly-brace-presence] Bail out in warning JSX expression when some chars exist Oct 1, 2017
@jackyho112
Copy link
Contributor Author

@lydell

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);
Copy link

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.

Copy link

@lydell lydell left a 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);
Copy link

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.)

Copy link
Contributor Author

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! 😄

Copy link
Member

@ljharb ljharb left a 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.

@lydell
Copy link

lydell commented Oct 10, 2017

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants