aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-11-04 15:27:29 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-11-04 15:27:29 -0700
commitfd57e2d9a630a2ba0d229419e11f39abd97f88bf (patch)
treee090f0cc333175e3852487bf2e360a44a1ceabfa /src
parent303a5ea898cd1df71a00caabd326c74940b379fc (diff)
downloadbun-fd57e2d9a630a2ba0d229419e11f39abd97f88bf.tar.gz
bun-fd57e2d9a630a2ba0d229419e11f39abd97f88bf.tar.zst
bun-fd57e2d9a630a2ba0d229419e11f39abd97f88bf.zip
[JSX] Match esbuild behavior for multiline JSX string literals
Diffstat (limited to '')
-rw-r--r--src/js_lexer.zig79
-rw-r--r--src/js_printer.zig20
2 files changed, 67 insertions, 32 deletions
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});