aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--integration/snapshots/string-escapes.debug.js8
-rw-r--r--integration/snapshots/string-escapes.hmr.debug.js8
-rw-r--r--integration/snippets/jsx-spacing.jsx60
-rw-r--r--src/js_lexer.zig79
-rw-r--r--src/js_printer.zig20
5 files changed, 107 insertions, 68 deletions
diff --git a/integration/snapshots/string-escapes.debug.js b/integration/snapshots/string-escapes.debug.js
index 4a5c82b05..af9c53abf 100644
--- a/integration/snapshots/string-escapes.debug.js
+++ b/integration/snapshots/string-escapes.debug.js
@@ -337,10 +337,10 @@ const jsxVariants = jsx(JSXFrag, {
data: "\v"
}, undefined, false, undefined, this),
jsx("div", {
- data: "\u2028"
+ data: "\\u2028"
}, undefined, false, undefined, this),
jsx("div", {
- data: "\u2029"
+ data: "\\u2029"
}, undefined, false, undefined, this),
jsx("div", {
data: "😊"
@@ -392,11 +392,11 @@ const jsxVariants = jsx(JSXFrag, {
jsx("div", {
children: "\v"
}, undefined, false, undefined, this),
- jsx("div", {}, "\u2028", false, undefined, this),
+ jsx("div", {}, "\\u2028", false, undefined, this),
jsx("div", {
children: "\u2028"
}, undefined, false, undefined, this),
- jsx("div", {}, "\u2029", false, undefined, this),
+ jsx("div", {}, "\\u2029", false, undefined, this),
jsx("div", {
children: "\u2029"
}, undefined, false, undefined, this),
diff --git a/integration/snapshots/string-escapes.hmr.debug.js b/integration/snapshots/string-escapes.hmr.debug.js
index 7a2d88ad1..19045846a 100644
--- a/integration/snapshots/string-escapes.hmr.debug.js
+++ b/integration/snapshots/string-escapes.hmr.debug.js
@@ -346,10 +346,10 @@ var hmr = new HMR(2482749838, "string-escapes.js"), exports = hmr.exports;
data: "\v"
}, undefined, false, undefined, this),
jsx("div", {
- data: "\u2028"
+ data: "\\u2028"
}, undefined, false, undefined, this),
jsx("div", {
- data: "\u2029"
+ data: "\\u2029"
}, undefined, false, undefined, this),
jsx("div", {
data: "😊"
@@ -401,11 +401,11 @@ var hmr = new HMR(2482749838, "string-escapes.js"), exports = hmr.exports;
jsx("div", {
children: "\v"
}, undefined, false, undefined, this),
- jsx("div", {}, "\u2028", false, undefined, this),
+ jsx("div", {}, "\\u2028", false, undefined, this),
jsx("div", {
children: "\u2028"
}, undefined, false, undefined, this),
- jsx("div", {}, "\u2029", false, undefined, this),
+ jsx("div", {}, "\\u2029", false, undefined, this),
jsx("div", {
children: "\u2029"
}, undefined, false, undefined, this),
diff --git a/integration/snippets/jsx-spacing.jsx b/integration/snippets/jsx-spacing.jsx
index 73e31ebaa..5feab1830 100644
--- a/integration/snippets/jsx-spacing.jsx
+++ b/integration/snippets/jsx-spacing.jsx
@@ -1,36 +1,40 @@
import * as ReactDOM from "react-dom/server";
-const Tester = ({ description }) => {
- console.assert(
- description ===
- "foo\nbar \n\nbaz\n\nthis\ntest\n\nchecks\nnewlines\nare\ngood\nyeah\n\n",
- "Expected description to be 'foo\\nbar \\n\\nbaz\\n\\nthis\\ntest\\n\\nchecks\\nnewlines\\nare\\ngood\\nyeah\\n\\n' but was '" +
- description +
- "'"
+const ReturnDescriptionAsString = ({ description }) => description;
+
+export function test() {
+ const _bun = ReactDOM.renderToString(
+ <ReturnDescriptionAsString
+ description="line1
+line2 trailing space
+
+line4 no trailing space 'single quote' \t\f\v\uF000 `template string`
+
+line6 no trailing space
+line7 trailing newline that ${terminates} the string literal
+"
+ ></ReturnDescriptionAsString>
);
- return description;
-};
+ // convert HTML entities to unicode
+ const el = document.createElement("textarea");
+ el.innerHTML = _bun;
+ const bun = el.value;
-export function test() {
- const foo = ReactDOM.renderToString(
- <Tester
- description="foo
- bar
-
- baz
-
- this
- test
-
- checks
- newlines
- are
- good
- yeah
-
- "
- ></Tester>
+ const esbuild =
+ "line1\nline2 trailing space \n\nline4 no trailing space 'single quote' \\t\\f\\v\\uF000 `template string`\n\nline6 no trailing space\nline7 trailing newline that ${terminates} the string literal\n";
+
+ console.assert(
+ bun === esbuild,
+ `strings did not match: ${JSON.stringify(
+ {
+ received: bun,
+ expected: esbuild,
+ },
+ null,
+ 2
+ )}`
);
+
testDone(import.meta.url);
}
diff --git a/src/js_lexer.zig b/src/js_lexer.zig
index 505a45869..d378ea670 100644
--- a/src/js_lexer.zig
+++ b/src/js_lexer.zig
@@ -1986,11 +1986,22 @@ pub fn NewLexer(comptime json_options: JSONOptions) type {
needs_decode = true;
try lexer.step();
},
+
'\\' => {
backslash = logger.Range{ .loc = logger.Loc{
.start = @intCast(i32, lexer.end),
}, .len = 1 };
try lexer.step();
+
+ // JSX string literals do not support escaping
+ // They're "pre" escaped
+ switch (lexer.code_point) {
+ 'u', 0x0C, 0, '\t', std.ascii.control_code.VT, 0x08 => {
+ needs_decode = true;
+ },
+ else => {},
+ }
+
continue;
},
quote => {
@@ -2001,6 +2012,7 @@ pub fn NewLexer(comptime json_options: JSONOptions) type {
try lexer.step();
break :string_literal;
},
+
else => {
// Non-ASCII strings need the slow path
if (lexer.code_point >= 0x80) {
@@ -2158,43 +2170,46 @@ pub fn NewLexer(comptime json_options: JSONOptions) type {
return decoded.items;
}
+ inline fn maybeDecodeJSXEntity(lexer: *LexerType, text: string, out: *std.ArrayList(u16), cursor: *strings.CodepointIterator.Cursor) void {
+ if (strings.indexOfChar(text[cursor.width + cursor.i ..], ';')) |length| {
+ const end = cursor.width + cursor.i;
+ const entity = text[end .. end + length];
+ if (entity[0] == '#') {
+ var number = entity[1..entity.len];
+ var base: u8 = 10;
+ if (number.len > 1 and number[0] == 'x') {
+ number = number[1..number.len];
+ base = 16;
+ }
+
+ cursor.c = std.fmt.parseInt(i32, number, base) catch |err| brk: {
+ switch (err) {
+ error.InvalidCharacter => {
+ lexer.addError(lexer.start, "Invalid JSX entity escape: {s}", .{entity}, false);
+ },
+ error.Overflow => {
+ lexer.addError(lexer.start, "JSX entity escape is too big: {s}", .{entity}, false);
+ },
+ }
+
+ break :brk strings.unicode_replacement;
+ };
+
+ cursor.i += @intCast(u32, length);
+ cursor.width = 1;
+ } else if (tables.jsxEntity.get(entity)) |ent| {
+ cursor.c = ent;
+ cursor.i += @intCast(u32, length) + 1;
+ }
+ }
+ }
+
pub fn decodeJSXEntities(lexer: *LexerType, text: string, out: *std.ArrayList(u16)) !void {
const iterator = strings.CodepointIterator.init(text);
var cursor = strings.CodepointIterator.Cursor{};
while (iterator.next(&cursor)) {
- if (cursor.c == '&') {
- if (strings.indexOfChar(text[cursor.width + cursor.i ..], ';')) |length| {
- const end = cursor.width + cursor.i;
- const entity = text[end .. end + length];
- if (entity[0] == '#') {
- var number = entity[1..entity.len];
- var base: u8 = 10;
- if (number.len > 1 and number[0] == 'x') {
- number = number[1..number.len];
- base = 16;
- }
- cursor.c = std.fmt.parseInt(i32, number, base) catch |err| brk: {
- switch (err) {
- error.InvalidCharacter => {
- lexer.addError(lexer.start, "Invalid JSX entity escape: {s}", .{entity}, false);
- },
- error.Overflow => {
- lexer.addError(lexer.start, "JSX entity escape is too big: {s}", .{entity}, false);
- },
- }
-
- break :brk strings.unicode_replacement;
- };
-
- cursor.i += @intCast(u32, length);
- cursor.width = 1;
- } else if (tables.jsxEntity.get(entity)) |ent| {
- cursor.c = ent;
- cursor.i += @intCast(u32, length) + 1;
- }
- }
- }
+ if (cursor.c == '&') lexer.maybeDecodeJSXEntity(text, out, &cursor);
if (cursor.c <= 0xFFFF) {
try out.append(@intCast(u16, cursor.c));
diff --git a/src/js_printer.zig b/src/js_printer.zig
index f6468966e..d6be0aeb7 100644
--- a/src/js_printer.zig
+++ b/src/js_printer.zig
@@ -556,6 +556,14 @@ pub fn NewPrinter(
'`' => {
backtick_cost += 1;
},
+ '\r', '\n' => {
+ if (allow_backtick) {
+ return '`';
+ }
+ },
+ '\\' => {
+ i += 1;
+ },
'$' => {
if (i + 1 < str.len and str[i + 1] == '{') {
backtick_cost += 1;
@@ -1836,6 +1844,18 @@ pub fn NewPrinter(
'\\' => {
i += 1;
},
+ // We must escape here for JSX string literals that contain unescaped newlines
+ // Those will get transformed into a template string
+ // which can potentially have unescaped $
+ '$' => {
+ if (comptime c == '`') {
+ p.print(utf8[0..i]);
+ p.print("\\$");
+
+ utf8 = utf8[i + 1 ..];
+ i = 0;
+ }
+ },
c => {
p.print(utf8[0..i]);
p.print("\\" ++ &[_]u8{c});