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
base: main
Are you sure you want to change the base?
Conversation
liuxingbaoyu
commented
Mar 12, 2023
•
edited
edited
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 |
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/55798/ |
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.
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.
const val = inJsx | ||
? `"${htmlEncode(node.value, { | ||
level: EntityLevel.HTML, | ||
mode: EncodingMode.UTF8, | ||
})}"` | ||
: jsesc(node.value, this.format.jsescOption); |
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 it safe to do just this, avoiding the extra dependency?
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); |
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'm not sure if &
is necessary, "
definitely needs this.
Also for \
, it seems that jsx should not escape.
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.
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.
This shouldn't be a breaking change, is there something I haven't noticed?
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 |
import jsesc from "jsesc"; | ||
// eslint-disable-next-line import/extensions | ||
import { escapeUTF8 as htmlEncode } from "entities/lib/escape.js"; |
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.
escapeUTF8
escapes '
as '
, which is an HTML 5 addition that is not allowed by JSX.
I think escapeAttribute
is the right function?
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!
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 didn't contain <>
, nothing seemed to fit, so I copy-pasted the code.
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.
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 "
.
#16065 |
72b5e68
to
4f7c771
Compare
@nicolo-ribaudo Can you re-review this? :) |
03954d7
to
be3248d
Compare
CI error seems related. |
const val = inJsx | ||
? `"${escape(node.value)}"` | ||
: jsesc(node.value, this.format.jsescOption); |
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.
Could we not handle HTML entities here & while parsing at all, and instead handle them in the JSX transform? 🤔
i.e. <X y="a < 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).
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.
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 "
. 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.
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.
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. :)