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

fix: Correctly generate StringLiteral in JSXAttribute #15487

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

liuxingbaoyu
Copy link
Member

@liuxingbaoyu liuxingbaoyu commented Mar 12, 2023

Q                       A
Fixed Issues? Fixes #15472, Fixes #16065
Patch: Bug Fix?
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass?
Documentation PR Link
Any Dependency Changes?
License MIT

@liuxingbaoyu liuxingbaoyu added PR: Bug Fix 🐛 A type of pull request used for our changelog categories pkg: generator pkg: parser area: jsx labels Mar 12, 2023
@babel-bot
Copy link
Collaborator

babel-bot commented Mar 12, 2023

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/55798/

Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

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

Unfortunately we cannot do this in a Babel 7 release, but I think that a better fix would be to not handle HTML entities in the parser/generator (so the string's .value would still be &), and instead convert them in the JSX transform plugin.

Comment on lines 233 to 255
const val = inJsx
? `"${htmlEncode(node.value, {
level: EntityLevel.HTML,
mode: EncodingMode.UTF8,
})}"`
: jsesc(node.value, this.format.jsescOption);
Copy link
Member

Choose a reason for hiding this comment

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

Is it safe to do just this, avoiding the extra dependency?

Suggested change
const val = inJsx
? `"${htmlEncode(node.value, {
level: EntityLevel.HTML,
mode: EncodingMode.UTF8,
})}"`
: jsesc(node.value, this.format.jsescOption);
let { value } = node.value;
if (inJsx) value = value.replace(/&/g, "&");
value = jsesc(value, this.format.jsescOption);

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure if & is necessary, " definitely needs this.
Also for \, it seems that jsx should not escape.

Copy link
Contributor

@andersk andersk Mar 13, 2023

Choose a reason for hiding this comment

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

Right.

  • " must be escaped so we can round-trip """"".
  • & must be escaped so we can round-trip """"""""""".
  • \ must not be escaped as \\, because a JSX parser won’t unescape that way.

@liuxingbaoyu
Copy link
Member Author

Unfortunately we cannot do this in a Babel 7 release

This shouldn't be a breaking change, is there something I haven't noticed?

but I think that a better fix would be to not handle HTML entities in the parser/generator (so the string's .value would still be &), and instead convert them in the JSX transform plugin.

I'm actually leaning toward creating a new node type in the same form as it does now. But this will affect a lot in compatibility, so I chose to set .extra,

@liuxingbaoyu
Copy link
Member Author

image
Now will no longer affect the bundled size.

import jsesc from "jsesc";
// eslint-disable-next-line import/extensions
import { escapeUTF8 as htmlEncode } from "entities/lib/escape.js";
Copy link
Contributor

Choose a reason for hiding this comment

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

escapeUTF8 escapes ' as ', which is an HTML 5 addition that is not allowed by JSX.

I think escapeAttribute is the right function?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

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

This didn't contain <>, nothing seemed to fit, so I copy-pasted the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Inside a double-quoted attribute, escaping <> doesn’t hurt but isn’t strictly necessary. The parser is only looking for entity references and a terminating ".

@liuxingbaoyu
Copy link
Member Author

#16065
This has the same reason. StringLiteral in js will not become two lines, which is not the case in jsx.

@liuxingbaoyu
Copy link
Member Author

liuxingbaoyu commented Nov 6, 2023

@nicolo-ribaudo Can you re-review this? :)
Thanks!

@JLHwung
Copy link
Contributor

JLHwung commented Nov 9, 2023

CI error seems related.

Comment on lines +254 to +256
const val = inJsx
? `"${escape(node.value)}"`
: jsesc(node.value, this.format.jsescOption);
Copy link
Member

Choose a reason for hiding this comment

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

Could we not handle HTML entities here & while parsing at all, and instead handle them in the JSX transform? 🤔

i.e. <X y="a &lt; b" /> is just a normal string according to the parser/generator, and when transforming it to plain JS the plugin will unescape HTML entities and other JSX weirdnesses. By doing so, we need to handle JSX encoding just in one place (the plugin) rather than twice (the parser and the generator).

Copy link
Contributor

Choose a reason for hiding this comment

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

No. A string literal must be escaped differently for the different contexts. In JS, \ must be escaped as \\, and in JSX, it must not. In JS, " must be escaped as \", and in JSX, it must be escaped as &quot;. These differences must be handled by the parser and generator to support users of the parser and generator that transplant a string literal node from one context to another. And this doesn’t happen in just one plugin; for example, the React plugin transforms JSX differently from the Solid plugin. See #15472.

Copy link
Member Author

Choose a reason for hiding this comment

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

That would be a huge breaking change, meaning escaping would have to be handled manually by plugins and users. Given that it's not difficult for us to support this, and we've supported it in the past, there are some users downstream who need it, so I'd be inclined to leave it as is.
And even so, we still need to check whether a StringLiteral is in jsx or js. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: jsx pkg: generator pkg: parser PR: Bug Fix 🐛 A type of pull request used for our changelog categories
Projects
None yet
5 participants