diff options
author | 2021-11-04 15:27:29 -0700 | |
---|---|---|
committer | 2021-11-04 15:27:29 -0700 | |
commit | fd57e2d9a630a2ba0d229419e11f39abd97f88bf (patch) | |
tree | e090f0cc333175e3852487bf2e360a44a1ceabfa /src | |
parent | 303a5ea898cd1df71a00caabd326c74940b379fc (diff) | |
download | bun-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.zig | 79 | ||||
-rw-r--r-- | src/js_printer.zig | 20 |
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}); |