Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/swc/tests/fixture/codegen/jsx-1/output/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default /*#__PURE__*/ React.createElement(A, {
className: b,
header: "C",
subheader: "D E"
subheader: "D\n E"
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function Component() {
return /*#__PURE__*/ React.createElement("div", {
name: "A B"
name: "A\n B"
});
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function test() {
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(A, {
b: "\\ "
b: "\\\n "
}));
}
5 changes: 3 additions & 2 deletions crates/swc_ecma_transforms_react/src/jsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2192,9 +2192,10 @@ fn transform_jsx_attr_str(v: &Wtf8) -> Wtf8Buf {
'\u{000c}' => buf.push_str("\\f"),
' ' => buf.push_char(' '),

'\n' | '\r' | '\t' => {
'\n' => buf.push_char('\n'),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice fix for LF, but this exposes a CRLF regression. In read_jsx_str (crates/swc_ecma_parser/src/lexer/mod.rs), when read_jsx_new_line(false) consumes \r\n, chunk_start is still set via cur_pos + BytePos(ch.len_utf8() as _) (line 1505), so the \n gets re-sliced and appended again. I reproduced this by changing the fixture input newline to CRLF: the emitted value becomes "bruh\\r\\n\\nbruh" (extra LF). Could we advance chunk_start to the current lexer position after consuming the newline sequence and add a CRLF fixture?

Copy link
Copy Markdown
Member

@kdy1 kdy1 Mar 31, 2026

Choose a reason for hiding this comment

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

This change makes multiline JSX attribute strings diverge from Babel semantics. Babel applies value.value.replace(/\n\s+/g, " ") in @babel/plugin-transform-react-jsx, so inputs like <div a="x\n y" /> compile to "x y" (newline + indentation collapsed), while <div a="x\ny" /> keeps the newline. With unconditional '\n' => buf.push_char('\n') here, SWC now emits "x\n y", changing runtime values for existing indented multiline attributes and regressing fixtures such as codegen/jsx-1, issues-1233, and issues-2162. Could we preserve \n only when it is not followed by horizontal whitespace, and keep collapsing \n[ \t]+ to a single space?

'\r' => buf.push_char('\r'),
'\t' => {
buf.push_char(' ');

while let Some(next) = iter.peek() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Multiline JSX attribute values still normalize \t to a space in this branch.

Because this arm does buf.push_char(' ') (and then consumes following spaces), an input like <A x="a\n\tb" /> is lowered to a runtime value of "a\n b" instead of preserving the tab ("a\n\tb", matching TypeScript/Babel behavior).

So the newline fix is correct, but tab-indented multiline attrs are still semantically changed. Could we preserve \t here (for example buf.push_char('\t')) and add a fixture for that case?

if next.to_char() == Some(' ') {
iter.next();
Comment on lines +2197 to 2201
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve tabs when keeping multiline JSX attr values

When a quoted JSX attribute spans lines in a tab-indented file, this branch still rewrites \t to a plain space and drops any following spaces. After the new \n/\r handling, an input like <div attr="a\n\tb" /> now lowers to a runtime value of "a\n b" instead of preserving the original tab, so multiline attribute strings are still corrupted for projects that indent JSX with tabs.

Useful? React with 👍 / 👎.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Newline in quoted JSX attribute value should be escaped, not collapsed to space
// https://github.com/swc-project/swc/issues/11550
const hello = <div data-anything="bruh
bruh">hello</div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Newline in quoted JSX attribute value should be escaped, not collapsed to space
// https://github.com/swc-project/swc/issues/11550
const hello = /*#__PURE__*/ React.createElement("div", {
"data-anything": "bruh\nbruh"
}, "hello");
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default /*#__PURE__*/ React.createElement(A, {
className: b,
header: "C",
subheader: "D E"
subheader: "D\n E"
});
Loading