diff options
author | 2022-03-11 00:03:09 -0800 | |
---|---|---|
committer | 2022-03-11 00:03:09 -0800 | |
commit | 44b0c8153a7092f97c36c5aab82b692f672c3ddf (patch) | |
tree | 5554a3e9e69bd689739c77745cb4a4bf87e98fd6 /src | |
parent | c8f6337f1f9f22b48286c8bed1e47d44f0657cd0 (diff) | |
download | bun-44b0c8153a7092f97c36c5aab82b692f672c3ddf.tar.gz bun-44b0c8153a7092f97c36c5aab82b692f672c3ddf.tar.zst bun-44b0c8153a7092f97c36c5aab82b692f672c3ddf.zip |
Source Maps for client-side errors & columns
Diffstat (limited to 'src')
-rw-r--r-- | src/api/schema.d.ts | 1 | ||||
-rw-r--r-- | src/api/schema.js | 8 | ||||
-rw-r--r-- | src/api/schema.peechy | 3 | ||||
-rw-r--r-- | src/api/schema.zig | 5 | ||||
-rw-r--r-- | src/http.zig | 13 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/exports.zig | 122 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers-handwritten.h | 2 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 35 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/encoding.zig | 3 | ||||
-rw-r--r-- | src/js_ast.zig | 8 | ||||
-rw-r--r-- | src/js_parser/js_parser.zig | 237 | ||||
-rw-r--r-- | src/js_printer.zig | 23 | ||||
-rw-r--r-- | src/runtime/hmr.ts | 2 | ||||
-rw-r--r-- | src/sourcemap/sourcemap.zig | 27 | ||||
-rw-r--r-- | src/string_immutable.zig | 13 |
15 files changed, 331 insertions, 171 deletions
diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts index 3e8474787..fb761317d 100644 --- a/src/api/schema.d.ts +++ b/src/api/schema.d.ts @@ -616,6 +616,7 @@ export interface WebsocketMessageWelcome { epoch: uint32; javascriptReloader: Reloader; cwd: string; + assetPrefix: string; } export interface WebsocketMessageFileChangeNotification { diff --git a/src/api/schema.js b/src/api/schema.js index 4dfa2e245..2117e8c6d 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -2667,6 +2667,7 @@ function decodeWebsocketMessageWelcome(bb) { result["epoch"] = bb.readUint32(); result["javascriptReloader"] = Reloader[bb.readByte()]; result["cwd"] = bb.readString(); + result["assetPrefix"] = bb.readString(); return result; } @@ -2696,6 +2697,13 @@ function encodeWebsocketMessageWelcome(message, bb) { } else { throw new Error('Missing required field "cwd"'); } + + var value = message["assetPrefix"]; + if (value != null) { + bb.writeString(value); + } else { + throw new Error('Missing required field "assetPrefix"'); + } } function decodeWebsocketMessageFileChangeNotification(bb) { diff --git a/src/api/schema.peechy b/src/api/schema.peechy index 9fe2fdc7a..9dce08756 100644 --- a/src/api/schema.peechy +++ b/src/api/schema.peechy @@ -501,6 +501,7 @@ struct WebsocketMessageWelcome { uint32 epoch; Reloader javascriptReloader; string cwd; + string assetPrefix; } struct WebsocketMessageFileChangeNotification { @@ -584,4 +585,4 @@ message BunInstall { bool disable_manifest_cache = 16; string global_dir = 17; string global_bin_dir = 18; -}
\ No newline at end of file +} diff --git a/src/api/schema.zig b/src/api/schema.zig index 2374103eb..bbec8a98c 100644 --- a/src/api/schema.zig +++ b/src/api/schema.zig @@ -2575,12 +2575,16 @@ pub const Api = struct { /// cwd cwd: []const u8, + /// assetPrefix + asset_prefix: []const u8, + pub fn decode(reader: anytype) anyerror!WebsocketMessageWelcome { var this = std.mem.zeroes(WebsocketMessageWelcome); this.epoch = try reader.readValue(u32); this.javascript_reloader = try reader.readValue(Reloader); this.cwd = try reader.readValue([]const u8); + this.asset_prefix = try reader.readValue([]const u8); return this; } @@ -2588,6 +2592,7 @@ pub const Api = struct { try writer.writeInt(this.epoch); try writer.writeEnum(this.javascript_reloader); try writer.writeValue(@TypeOf(this.cwd), this.cwd); + try writer.writeValue(@TypeOf(this.asset_prefix), this.asset_prefix); } }; diff --git a/src/http.zig b/src/http.zig index 06aef3094..2e1f153c0 100644 --- a/src/http.zig +++ b/src/http.zig @@ -1818,6 +1818,7 @@ pub const RequestContext = struct { } const welcome_message = Api.WebsocketMessageWelcome{ + .asset_prefix = handler.ctx.bundler.options.routes.asset_prefix_path, .epoch = WebsocketHandler.toTimestamp( @intCast(u64, (handler.ctx.timer.started.timestamp.tv_sec * std.time.ns_per_s)) + @intCast(u64, handler.ctx.timer.started.timestamp.tv_nsec), ), @@ -2251,7 +2252,12 @@ pub const RequestContext = struct { } if (this.rctx.has_called_done) return; this.buffer.reset(); - this.buffer = try chunk.printSourceMapContents(source, this.buffer, false); + this.buffer = try chunk.printSourceMapContents( + source, + this.buffer, + this.rctx.header("Mappings-Only") == null, + false, + ); defer { this.buffer.reset(); SocketPrinterInternal.buffer.?.* = this.buffer; @@ -2893,6 +2899,11 @@ pub const RequestContext = struct { } } + if (ctx.bundler.options.routes.asset_prefix_path.len > 0 and + strings.hasPrefix(input_path, ctx.bundler.options.routes.asset_prefix_path)) + { + input_path = input_path[ctx.bundler.options.routes.asset_prefix_path.len..]; + } if (input_path.len == 0) return ctx.sendNotFound(); const pathname = Fs.PathName.init(input_path); diff --git a/src/javascript/jsc/bindings/exports.zig b/src/javascript/jsc/bindings/exports.zig index 75a7c96be..0a0a045a3 100644 --- a/src/javascript/jsc/bindings/exports.zig +++ b/src/javascript/jsc/bindings/exports.zig @@ -389,7 +389,12 @@ pub const ZigStackTrace = extern struct { frames_ptr: [*c]ZigStackFrame, frames_len: u8, - pub fn toAPI(this: *const ZigStackTrace, allocator: std.mem.Allocator) !Api.StackTrace { + pub fn toAPI( + this: *const ZigStackTrace, + allocator: std.mem.Allocator, + root_path: string, + origin: ?*const ZigURL, + ) !Api.StackTrace { var stack_trace: Api.StackTrace = std.mem.zeroes(Api.StackTrace); { var source_lines_iter = this.sourceLineIterator(); @@ -424,7 +429,11 @@ pub const ZigStackTrace = extern struct { stack_trace.frames = stack_frames; for (_frames) |frame, i| { - stack_frames[i] = try frame.toAPI(allocator); + stack_frames[i] = try frame.toAPI( + root_path, + origin, + allocator, + ); } } } @@ -480,18 +489,24 @@ pub const ZigStackFrame = extern struct { position: ZigStackFramePosition, code_type: ZigStackFrameCode, - pub fn toAPI(this: *const ZigStackFrame, allocator: std.mem.Allocator) !Api.StackFrame { + /// This informs formatters whether to display as a blob URL or not + remapped: bool = false, + + pub fn toAPI(this: *const ZigStackFrame, root_path: string, origin: ?*const ZigURL, allocator: std.mem.Allocator) !Api.StackFrame { var frame: Api.StackFrame = std.mem.zeroes(Api.StackFrame); if (this.function_name.len > 0) { frame.function_name = try allocator.dupe(u8, this.function_name.slice()); } if (this.source_url.len > 0) { - frame.file = try allocator.dupe(u8, this.source_url.slice()); + frame.file = try std.fmt.allocPrint(allocator, "{any}", .{this.sourceURLFormatter(root_path, origin, true, false)}); } frame.position.source_offset = this.position.source_offset; - frame.position.line = this.position.line; + + // For remapped code, we add 1 to the line number + frame.position.line = this.position.line + @as(i32, @boolToInt(this.remapped)); + frame.position.line_start = this.position.line_start; frame.position.line_stop = this.position.line_stop; frame.position.column_start = this.position.column_start; @@ -508,7 +523,8 @@ pub const ZigStackFrame = extern struct { position: ZigStackFramePosition, enable_color: bool, origin: ?*const ZigURL, - + exclude_line_column: bool = false, + remapped: bool = false, root_path: string = "", pub fn format(this: SourceURLFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { if (this.enable_color) { @@ -516,16 +532,19 @@ pub const ZigStackFrame = extern struct { } var source_slice = this.source_url.slice(); - if (this.origin) |origin| { - try writer.writeAll(origin.displayProtocol()); - try writer.writeAll("://"); - try writer.writeAll(origin.displayHostname()); - try writer.writeAll(":"); - try writer.writeAll(origin.port); - try writer.writeAll("/blob:"); - - if (strings.startsWith(source_slice, this.root_path)) { - source_slice = source_slice[this.root_path.len..]; + + if (!this.remapped) { + if (this.origin) |origin| { + try writer.writeAll(origin.displayProtocol()); + try writer.writeAll("://"); + try writer.writeAll(origin.displayHostname()); + try writer.writeAll(":"); + try writer.writeAll(origin.port); + try writer.writeAll("/blob:"); + + if (strings.startsWith(source_slice, this.root_path)) { + source_slice = source_slice[this.root_path.len..]; + } } } @@ -539,30 +558,32 @@ pub const ZigStackFrame = extern struct { } } - if (this.position.line > -1 and this.position.column_start > -1) { - if (this.enable_color) { - try std.fmt.format( - writer, - // : - comptime Output.prettyFmt("<d>:<r><yellow>{d}<r><d>:<yellow>{d}<r>", true), - .{ this.position.line + 1, this.position.column_start }, - ); - } else { - try std.fmt.format(writer, ":{d}:{d}", .{ this.position.line + 1, this.position.column_start }); - } - } else if (this.position.line > -1) { - if (this.enable_color) { - try std.fmt.format( - writer, - comptime Output.prettyFmt("<d>:<r><yellow>{d}<r>", true), - .{ + if (!this.exclude_line_column) { + if (this.position.line > -1 and this.position.column_start > -1) { + if (this.enable_color) { + try std.fmt.format( + writer, + // : + comptime Output.prettyFmt("<d>:<r><yellow>{d}<r><d>:<yellow>{d}<r>", true), + .{ this.position.line + 1, this.position.column_start }, + ); + } else { + try std.fmt.format(writer, ":{d}:{d}", .{ this.position.line + 1, this.position.column_start }); + } + } else if (this.position.line > -1) { + if (this.enable_color) { + try std.fmt.format( + writer, + comptime Output.prettyFmt("<d>:<r><yellow>{d}<r>", true), + .{ + this.position.line + 1, + }, + ); + } else { + try std.fmt.format(writer, ":{d}", .{ this.position.line + 1, - }, - ); - } else { - try std.fmt.format(writer, ":{d}", .{ - this.position.line + 1, - }); + }); + } } } } @@ -621,8 +642,16 @@ pub const ZigStackFrame = extern struct { return NameFormatter{ .function_name = this.function_name, .code_type = this.code_type, .enable_color = enable_color }; } - pub fn sourceURLFormatter(this: *const ZigStackFrame, root_path: string, origin: ?*const ZigURL, comptime enable_color: bool) SourceURLFormatter { - return SourceURLFormatter{ .source_url = this.source_url, .origin = origin, .root_path = root_path, .position = this.position, .enable_color = enable_color }; + pub fn sourceURLFormatter(this: *const ZigStackFrame, root_path: string, origin: ?*const ZigURL, exclude_line_column: bool, comptime enable_color: bool) SourceURLFormatter { + return SourceURLFormatter{ + .source_url = this.source_url, + .exclude_line_column = exclude_line_column, + .origin = origin, + .root_path = root_path, + .position = this.position, + .enable_color = enable_color, + .remapped = this.remapped, + }; } }; @@ -670,6 +699,8 @@ pub const ZigException = extern struct { exception: ?*anyopaque, + remapped: bool = false, + pub const shim = Shimmer("Zig", "Exception", @This()); pub const name = "ZigException"; pub const namespace = shim.namespace; @@ -736,7 +767,12 @@ pub const ZigException = extern struct { return shim.cppFn("fromException", .{exception}); } - pub fn addToErrorList(this: *ZigException, error_list: *std.ArrayList(Api.JsException)) !void { + pub fn addToErrorList( + this: *ZigException, + error_list: *std.ArrayList(Api.JsException), + root_path: string, + origin: ?*const ZigURL, + ) !void { const _name: string = @field(this, "name").slice(); const message: string = @field(this, "message").slice(); @@ -757,7 +793,7 @@ pub const ZigException = extern struct { } if (this.stack.frames_len > 0) { - api_exception.stack = try this.stack.toAPI(error_list.allocator); + api_exception.stack = try this.stack.toAPI(error_list.allocator, root_path, origin); is_empty = false; } diff --git a/src/javascript/jsc/bindings/headers-handwritten.h b/src/javascript/jsc/bindings/headers-handwritten.h index 56d5c0773..8e2bafc76 100644 --- a/src/javascript/jsc/bindings/headers-handwritten.h +++ b/src/javascript/jsc/bindings/headers-handwritten.h @@ -68,6 +68,7 @@ typedef struct ZigStackFrame { ZigString source_url; ZigStackFramePosition position; ZigStackFrameCode code_type; + bool remapped; } ZigStackFrame; typedef struct ZigStackTrace { @@ -90,6 +91,7 @@ typedef struct ZigException { ZigString message; ZigStackTrace stack; void* exception; + bool remapped; } ZigException; typedef uint8_t JSErrorCode; diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 36c8174ad..622364a6c 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -2921,7 +2921,7 @@ pub const VirtualMachine = struct { } if (exception_list) |list| { - zig_exception.addToErrorList(list) catch {}; + zig_exception.addToErrorList(list, this.bundler.fs.top_level_dir, &this.origin) catch {}; } } } @@ -3067,6 +3067,7 @@ pub const VirtualMachine = struct { frame.sourceURLFormatter( dir, origin, + false, allow_ansi_colors, ), }, @@ -3081,6 +3082,7 @@ pub const VirtualMachine = struct { frame.sourceURLFormatter( dir, origin, + false, allow_ansi_colors, ), }, @@ -3095,10 +3097,13 @@ pub const VirtualMachine = struct { exception: *ZigException, error_instance: JSValue, exception_list: ?*ExceptionList, - ) !void { + ) void { error_instance.toZigException(this.global, exception); - if (exception_list) |list| { - try exception.addToErrorList(list); + // defer this so that it copies correctly + defer { + if (exception_list) |list| { + exception.addToErrorList(list, this.bundler.fs.top_level_dir, &this.origin) catch unreachable; + } } var frames: []JSC.ZigStackFrame = exception.stack.frames_ptr[0..exception.stack.frames_len]; @@ -3108,14 +3113,22 @@ pub const VirtualMachine = struct { if (this.source_mappings.resolveMapping( top.source_url.slice(), @maximum(top.position.line, 0), - @maximum(top.position.column_stop, 0), + @maximum(top.position.column_start, 0), )) |mapping| { var log = logger.Log.init(default_allocator); var original_source = _fetch(this.global, top.source_url.slice(), "", &log, true) catch return; const code = original_source.source_code.slice(); top.position.line = mapping.original.lines; + top.position.line_start = mapping.original.lines; + top.position.line_stop = mapping.original.lines + 1; top.position.column_start = mapping.original.columns; + top.position.column_stop = mapping.original.columns + 1; + exception.remapped = true; + top.remapped = true; + // This expression range is no longer accurate top.position.expression_start = mapping.original.columns; + top.position.expression_stop = mapping.original.columns + 1; + if (strings.getLinesInText( code, @intCast(u32, top.position.line), @@ -3133,6 +3146,13 @@ pub const VirtualMachine = struct { } exception.stack.source_lines_len = @intCast(u8, lines_.len); + + top.position.column_stop = @intCast(i32, source_lines[lines_.len - 1].len); + top.position.line_stop = top.position.column_stop; + + // This expression range is no longer accurate + top.position.expression_start = mapping.original.columns; + top.position.expression_stop = top.position.column_stop; } } @@ -3145,6 +3165,7 @@ pub const VirtualMachine = struct { @maximum(frame.position.column_start, 0), )) |mapping| { frame.position.line = mapping.original.lines; + frame.remapped = true; frame.position.column_start = mapping.original.columns; } } @@ -3154,7 +3175,7 @@ pub const VirtualMachine = struct { pub fn printErrorInstance(this: *VirtualMachine, error_instance: JSValue, exception_list: ?*ExceptionList, comptime Writer: type, writer: Writer, comptime allow_ansi_color: bool) !void { var exception_holder = ZigException.Holder.init(); var exception = exception_holder.zigException(); - try this.remapZigException(exception, error_instance, exception_list); + this.remapZigException(exception, error_instance, exception_list); this.had_errors = true; var line_numbers = exception.stack.source_lines_numbers[0..exception.stack.source_lines_len]; @@ -3217,7 +3238,7 @@ pub const VirtualMachine = struct { writer.writeByteNTimes(' ', pad) catch unreachable; const top = exception.stack.frames()[0]; var remainder = std.mem.trim(u8, source.text, "\n"); - if (@intCast(usize, top.position.column_stop) > remainder.len) { + if (@intCast(usize, top.position.column_stop) > remainder.len or (top.position.column_stop - top.position.column_start) <= 0) { writer.print( comptime Output.prettyFmt( "<r><d>{d} |<r> {s}\n", diff --git a/src/javascript/jsc/webcore/encoding.zig b/src/javascript/jsc/webcore/encoding.zig index 79c3125f7..8d9486907 100644 --- a/src/javascript/jsc/webcore/encoding.zig +++ b/src/javascript/jsc/webcore/encoding.zig @@ -486,8 +486,7 @@ pub const TextDecoder = struct { } } } - - return str.toValueGC(ctx.ptr()).asObjectRef(); + return str.toValue(ctx.ptr()).asObjectRef(); }, EncodingLabel.@"UTF-16LE" => { diff --git a/src/js_ast.zig b/src/js_ast.zig index 331f9a3c7..902028de3 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -631,6 +631,7 @@ pub const G = struct { class_name: ?LocRef = null, extends: ?ExprNodeIndex = null, body_loc: logger.Loc = logger.Loc.Empty, + close_brace_loc: logger.Loc = logger.Loc.Empty, properties: []Property = &([_]Property{}), }; @@ -1058,6 +1059,7 @@ pub const E = struct { is_single_line: bool = false, is_parenthesized: bool = false, was_originally_macro: bool = false, + close_bracket_loc: logger.Loc = logger.Loc.Empty, pub fn push(this: *Array, allocator: std.mem.Allocator, item: Expr) !void { try this.items.push(allocator, item); @@ -1113,6 +1115,8 @@ pub const E = struct { // True if there is a comment containing "@__PURE__" or "#__PURE__" preceding // this call expression. See the comment inside ECall for more details. can_be_unwrapped_if_unused: bool = false, + + close_parens_loc: logger.Loc, }; pub const NewTarget = struct { range: logger.Range, @@ -1125,6 +1129,7 @@ pub const E = struct { args: ExprNodeList = ExprNodeList{}, optional_chain: ?OptionalChain = null, is_direct_eval: bool = false, + close_paren_loc: logger.Loc = logger.Loc.Empty, // True if there is a comment containing "@__PURE__" or "#__PURE__" preceding // this call expression. This is an annotation used for tree shaking, and @@ -1292,6 +1297,8 @@ pub const E = struct { flags: Flags.JSXElement = Flags.JSXElement{}, + close_tag_loc: logger.Loc = logger.Loc.Empty, + pub const SpecialProp = enum { __self, // old react transform used this as a prop __source, @@ -1360,6 +1367,7 @@ pub const E = struct { is_single_line: bool = false, is_parenthesized: bool = false, was_originally_macro: bool = false, + close_brace_loc: logger.Loc = logger.Loc.Empty, pub const Rope = struct { head: Expr, diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index 603bef93e..1945f9fa4 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -46,7 +46,10 @@ fn _disabledAssert(_: bool) void { } const assert = if (Environment.allow_assert) std.debug.assert else _disabledAssert; - +const ExprListLoc = struct { + list: ExprNodeList, + loc: logger.Loc, +}; pub const LocRef = js_ast.LocRef; pub const S = js_ast.S; pub const B = js_ast.B; @@ -174,6 +177,87 @@ const TransposeState = struct { loc: logger.Loc, }; +const JSXTag = struct { + pub const TagType = enum { fragment, tag }; + pub const Data = union(TagType) { + fragment: u8, + tag: Expr, + + pub fn asExpr(d: *const Data) ?ExprNodeIndex { + switch (d.*) { + .tag => |tag| { + return tag; + }, + else => { + return null; + }, + } + } + }; + data: Data, + range: logger.Range, + name: string = "", + + pub fn parse(comptime P: type, p: *P) anyerror!JSXTag { + const loc = p.lexer.loc(); + + // A missing tag is a fragment + if (p.lexer.token == .t_greater_than) { + return JSXTag{ + .range = logger.Range{ .loc = loc, .len = 0 }, + .data = Data{ .fragment = 1 }, + .name = "", + }; + } + + // The tag is an identifier + var name = p.lexer.identifier; + var tag_range = p.lexer.range(); + try p.lexer.expectInsideJSXElement(.t_identifier); + + // Certain identifiers are strings + // <div + // <button + // <Hello-:Button + if (strings.containsComptime(name, "-:") or (p.lexer.token != .t_dot and name[0] >= 'a' and name[0] <= 'z')) { + return JSXTag{ + .data = Data{ .tag = p.e(E.String{ + .utf8 = name, + }, loc) }, + .range = tag_range, + }; + } + + // Otherwise, this is an identifier + // <Button> + var tag = p.e(E.Identifier{ .ref = try p.storeNameInRef(name) }, loc); + + // Parse a member expression chain + // <Button.Red> + while (p.lexer.token == .t_dot) { + try p.lexer.nextInsideJSXElement(); + const member_range = p.lexer.range(); + const member = p.lexer.identifier; + try p.lexer.expectInsideJSXElement(.t_identifier); + + if (strings.indexOfChar(member, '-')) |index| { + try p.log.addError(p.source, logger.Loc{ .start = member_range.loc.start + @intCast(i32, index) }, "Unexpected \"-\""); + return error.SyntaxError; + } + + var _name = try p.allocator.alloc(u8, name.len + 1 + member.len); + std.mem.copy(u8, _name, name); + _name[name.len] = '.'; + std.mem.copy(u8, _name[name.len + 1 .. _name.len], member); + name = _name; + tag_range.len = member_range.loc.start + member_range.len - tag_range.loc.start; + tag = p.e(E.Dot{ .target = tag, .name = member, .name_loc = member_range.loc }, loc); + } + + return JSXTag{ .data = Data{ .tag = tag }, .range = tag_range, .name = name }; + } +}; + pub const TypeScript = struct { // This function is taken from the official TypeScript compiler source code: // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts @@ -757,6 +841,7 @@ pub const ImportScanner = struct { .extends = class.class.extends, .body_loc = class.class.body_loc, .properties = class.class.properties, + .close_brace_loc = class.class.close_brace_loc, }, stmt.loc), }; @@ -5974,7 +6059,7 @@ fn NewParser_( } if (stmt.data.s_function.func.name) |name| { - defaultName = js_ast.LocRef{ .loc = defaultLoc, .ref = name.ref }; + defaultName = js_ast.LocRef{ .loc = name.loc, .ref = name.ref }; } else { defaultName = try p.createDefaultName(defaultLoc); } @@ -6009,12 +6094,12 @@ fn NewParser_( .s_function => |func_container| { if (func_container.func.name) |name| { - break :default_name_getter LocRef{ .loc = defaultLoc, .ref = name.ref }; + break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; } else {} }, .s_class => |class| { if (class.class.class_name) |name| { - break :default_name_getter LocRef{ .loc = defaultLoc, .ref = name.ref }; + break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; } else {} }, else => {}, @@ -7272,7 +7357,7 @@ fn NewParser_( try p.lexer.expect(.t_string_literal); try p.lexer.expect(.t_close_paren); const args = try ExprNodeList.one(p.allocator, path); - value.data = .{ .e_call = Expr.Data.Store.All.append(E.Call, E.Call{ .target = value, .args = args }) }; + value.data = .{ .e_call = Expr.Data.Store.All.append(E.Call, E.Call{ .target = value, .close_paren_loc = p.lexer.loc(), .args = args }) }; } else { // "import Foo = Bar" // "import Foo = Bar.Baz" @@ -9522,12 +9607,13 @@ fn NewParser_( p.allow_in = old_allow_in; p.allow_private_identifiers = old_allow_private_identifiers; - + const close_brace_loc = p.lexer.loc(); try p.lexer.expect(.t_close_brace); return G.Class{ .class_name = name, .extends = extends, + .close_brace_loc = close_brace_loc, .ts_decorators = ExprNodeList.init(class_opts.ts_decorators), .class_keyword = class_keyword, .body_loc = body_loc, @@ -9603,7 +9689,7 @@ fn NewParser_( return expr; } - pub fn parseCallArgs(p: *P) anyerror!ExprNodeList { + pub fn parseCallArgs(p: *P) anyerror!ExprListLoc { // Allow "in" inside call arguments const old_allow_in = p.allow_in; p.allow_in = true; @@ -9629,9 +9715,9 @@ fn NewParser_( } try p.lexer.next(); } - + const close_paren_loc = p.lexer.loc(); try p.lexer.expect(.t_close_paren); - return ExprNodeList.fromList(args); + return ExprListLoc{ .list = ExprNodeList.fromList(args), .loc = close_paren_loc }; } pub fn parseSuffix(p: *P, _left: Expr, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr { @@ -9684,8 +9770,8 @@ fn NewParser_( else => {}, } - var name = p.lexer.identifier; - var name_loc = p.lexer.loc(); + const name = p.lexer.identifier; + const name_loc = p.lexer.loc(); try p.lexer.next(); const ref = p.storeNameInRef(name) catch unreachable; left = p.e(E.Index{ @@ -9705,8 +9791,8 @@ fn NewParser_( try p.lexer.expect(.t_identifier); } - var name = p.lexer.identifier; - var name_loc = p.lexer.loc(); + const name = p.lexer.identifier; + const name_loc = p.lexer.loc(); try p.lexer.next(); left = p.e(E.Dot{ .target = left, .name = name, .name_loc = name_loc, .optional_chain = old_optional_chain }, left.loc); @@ -9751,9 +9837,11 @@ fn NewParser_( return left; } + const list_loc = try p.parseCallArgs(); left = p.e(E.Call{ .target = left, - .args = try p.parseCallArgs(), + .args = list_loc.list, + .close_paren_loc = list_loc.loc, .optional_chain = optional_start, }, left.loc); }, @@ -9773,10 +9861,13 @@ fn NewParser_( return left; } - left = p.e( - E.Call{ .target = left, .args = try p.parseCallArgs(), .optional_chain = optional_start }, - left.loc, - ); + const list_loc = try p.parseCallArgs(); + left = p.e(E.Call{ + .target = left, + .args = list_loc.list, + .close_paren_loc = list_loc.loc, + .optional_chain = optional_start, + }, left.loc); }, else => { if (p.lexer.token == .t_private_identifier and p.allow_private_identifiers) { @@ -9878,10 +9969,12 @@ fn NewParser_( return left; } + const list_loc = try p.parseCallArgs(); left = p.e( E.Call{ .target = left, - .args = try p.parseCallArgs(), + .args = list_loc.list, + .close_paren_loc = list_loc.loc, .optional_chain = old_optional_chain, }, left.loc, @@ -10832,13 +10925,17 @@ fn NewParser_( } } + var close_parens_loc = logger.Loc.Empty; if (p.lexer.token == .t_open_paren) { - args = try p.parseCallArgs(); + const call_args = try p.parseCallArgs(); + args = call_args.list; + close_parens_loc = call_args.loc; } return p.e(E.New{ .target = target, .args = args, + .close_parens_loc = close_parens_loc, }, loc); }, .t_open_bracket => { @@ -10898,6 +10995,7 @@ fn NewParser_( is_single_line = false; } + const close_bracket_loc = p.lexer.loc(); try p.lexer.expect(.t_close_bracket); p.allow_in = old_allow_in; @@ -10915,6 +11013,7 @@ fn NewParser_( .items = ExprNodeList.fromList(items), .comma_after_spread = comma_after_spread.toNullable(), .is_single_line = is_single_line, + .close_bracket_loc = close_bracket_loc, }, loc); }, .t_open_brace => { @@ -10967,6 +11066,7 @@ fn NewParser_( is_single_line = false; } + const close_brace_loc = p.lexer.loc(); try p.lexer.expect(.t_close_brace); p.allow_in = old_allow_in; @@ -10987,6 +11087,7 @@ fn NewParser_( else null, .is_single_line = is_single_line, + .close_brace_loc = close_brace_loc, }, loc); }, .t_less_than => { @@ -11172,87 +11273,6 @@ fn NewParser_( return p.e(E.Import{ .expr = value, .leading_interior_comments = comments, .import_record_index = 0 }, loc); } - const JSXTag = struct { - pub const TagType = enum { fragment, tag }; - pub const Data = union(TagType) { - fragment: u8, - tag: Expr, - - pub fn asExpr(d: *const Data) ?ExprNodeIndex { - switch (d.*) { - .tag => |tag| { - return tag; - }, - else => { - return null; - }, - } - } - }; - data: Data, - range: logger.Range, - name: string = "", - - pub fn parse(p: *P) anyerror!JSXTag { - const loc = p.lexer.loc(); - - // A missing tag is a fragment - if (p.lexer.token == .t_greater_than) { - return JSXTag{ - .range = logger.Range{ .loc = loc, .len = 0 }, - .data = Data{ .fragment = 1 }, - .name = "", - }; - } - - // The tag is an identifier - var name = p.lexer.identifier; - var tag_range = p.lexer.range(); - try p.lexer.expectInsideJSXElement(.t_identifier); - - // Certain identifiers are strings - // <div - // <button - // <Hello-:Button - if (strings.contains(name, "-:") or (p.lexer.token != .t_dot and name[0] >= 'a' and name[0] <= 'z')) { - return JSXTag{ - .data = Data{ .tag = p.e(E.String{ - .utf8 = name, - }, loc) }, - .range = tag_range, - }; - } - - // Otherwise, this is an identifier - // <Button> - var tag = p.e(E.Identifier{ .ref = try p.storeNameInRef(name) }, loc); - - // Parse a member expression chain - // <Button.Red> - while (p.lexer.token == .t_dot) { - try p.lexer.nextInsideJSXElement(); - const member_range = p.lexer.range(); - const member = p.lexer.identifier; - try p.lexer.expectInsideJSXElement(.t_identifier); - - if (strings.indexOfChar(member, '-')) |index| { - try p.log.addError(p.source, logger.Loc{ .start = member_range.loc.start + @intCast(i32, index) }, "Unexpected \"-\""); - return error.SyntaxError; - } - - var _name = try p.allocator.alloc(u8, name.len + 1 + member.len); - std.mem.copy(u8, _name, name); - _name[name.len] = '.'; - std.mem.copy(u8, _name[name.len + 1 .. _name.len], member); - name = _name; - tag_range.len = member_range.loc.start + member_range.len - tag_range.loc.start; - tag = p.e(E.Dot{ .target = tag, .name = member, .name_loc = member_range.loc }, loc); - } - - return JSXTag{ .data = Data{ .tag = tag }, .range = tag_range, .name = name }; - } - }; - fn parseJSXPropValueIdentifier(p: *P, previous_string_with_backslash_loc: *logger.Loc) !Expr { // Use NextInsideJSXElement() not Next() so we can parse a JSX-style string literal try p.lexer.nextInsideJSXElement(); @@ -11276,7 +11296,7 @@ fn NewParser_( p.needs_jsx_import = true; } - var tag = try JSXTag.parse(p); + var tag = try JSXTag.parse(P, p); // The tag may have TypeScript type arguments: "<Foo<T>/>" if (is_typescript_enabled) { @@ -11439,8 +11459,11 @@ fn NewParser_( // A slash here is a self-closing element if (p.lexer.token == .t_slash) { + const close_tag_loc = p.lexer.loc(); // Use NextInsideJSXElement() not Next() so we can parse ">>" as ">" + try p.lexer.nextInsideJSXElement(); + if (p.lexer.token != .t_greater_than) { try p.lexer.expected(.t_greater_than); } @@ -11450,6 +11473,7 @@ fn NewParser_( .properties = properties, .key = key_prop, .flags = flags, + .close_tag_loc = close_tag_loc, }, loc); } @@ -11498,7 +11522,8 @@ fn NewParser_( // This is the closing element try p.lexer.nextInsideJSXElement(); - const end_tag = try JSXTag.parse(p); + const end_tag = try JSXTag.parse(P, p); + if (!strings.eql(end_tag.name, tag.name)) { try p.log.addRangeErrorFmt(p.source, end_tag.range, p.allocator, "Expected closing tag </{s}> to match opening tag <{s}>", .{ end_tag.name, @@ -11517,6 +11542,7 @@ fn NewParser_( .properties = properties, .key = key_prop, .flags = flags, + .close_tag_loc = end_tag.range.loc, }, loc); }, else => { @@ -12061,6 +12087,7 @@ fn NewParser_( .args = ExprNodeList.init(args[0..i]), // Enable tree shaking .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, + .close_paren_loc = e_.close_tag_loc, }, expr.loc); }, // function jsxDEV(type, config, maybeKey, source, self) { @@ -12121,7 +12148,7 @@ fn NewParser_( .value = p.e(E.Array{ .items = e_.children, .is_single_line = e_.children.len < 2, - }, expr.loc), + }, e_.close_tag_loc), }) catch unreachable; }, } @@ -12201,6 +12228,7 @@ fn NewParser_( // Enable tree shaking .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, .was_jsx_element = true, + .close_paren_loc = e_.close_tag_loc, }, expr.loc); }, else => unreachable, @@ -16177,6 +16205,7 @@ fn NewParser_( }, logger.Loc.Empty, ), + .close_parens_loc = logger.Loc.Empty, }, logger.Loc.Empty), }; first_decl[1] = G.Decl{ diff --git a/src/js_printer.zig b/src/js_printer.zig index 0c1d640f0..270f35630 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -913,6 +913,8 @@ pub fn NewPrinter( p.needs_semicolon = false; p.options.unindent(); p.printIndent(); + if (class.close_brace_loc.start > class.body_loc.start) + p.addSourceMapping(class.close_brace_loc); p.print("}"); } @@ -1483,6 +1485,10 @@ pub fn NewPrinter( } } + if (e.close_parens_loc.start > expr.loc.start) { + p.addSourceMapping(e.close_parens_loc); + } + p.print(")"); } @@ -1539,7 +1545,9 @@ pub fn NewPrinter( p.printExpr(arg, .comma, ExprFlag.None()); } } - + if (e.close_paren_loc.start > expr.loc.start) { + p.addSourceMapping(e.close_paren_loc); + } p.print(")"); if (wrap) { p.print(")"); @@ -1815,6 +1823,7 @@ pub fn NewPrinter( p.print("class"); if (e.class_name) |name| { p.maybePrintSpace(); + p.addSourceMapping(name.loc); p.printSymbol(name.ref orelse Global.panic("internal error: expected E.Class's name symbol to have a ref\n{s}", .{e})); p.maybePrintSpace(); } @@ -1857,6 +1866,10 @@ pub fn NewPrinter( } } + if (e.close_bracket_loc.start > expr.loc.start) { + p.addSourceMapping(e.close_bracket_loc); + } + p.print("]"); }, .e_object => |e| { @@ -1896,6 +1909,9 @@ pub fn NewPrinter( p.printSpace(); } } + if (e.close_brace_loc.start > expr.loc.start) { + p.addSourceMapping(e.close_brace_loc); + } p.print("}"); if (wrap) { p.print(")"); @@ -2682,8 +2698,9 @@ pub fn NewPrinter( switch (property.key.data) { .e_string => |str| { + p.addSourceMapping(property.key.loc); + if (str.isUTF8()) { - p.addSourceMapping(property.key.loc); p.printSpaceBeforeIdentifier(); // Example case: // const Menu = React.memo(function Menu({ @@ -2708,7 +2725,6 @@ pub fn NewPrinter( p.printQuotedUTF8(str.utf8, false); } } else if (p.canPrintIdentifierUTF16(str.value)) { - p.addSourceMapping(property.key.loc); p.printSpaceBeforeIdentifier(); p.printIdentifierUTF16(str.value) catch unreachable; @@ -2787,6 +2803,7 @@ pub fn NewPrinter( p.printSpaceBeforeIdentifier(); const name = s.func.name orelse Global.panic("Internal error: expected func to have a name ref\n{s}", .{s}); const nameRef = name.ref orelse Global.panic("Internal error: expected func to have a name\n{s}", .{s}); + if (s.func.flags.contains(.is_export)) { if (!rewrite_esm_to_cjs) { p.print("export "); diff --git a/src/runtime/hmr.ts b/src/runtime/hmr.ts index a52e387d9..75d4e0bea 100644 --- a/src/runtime/hmr.ts +++ b/src/runtime/hmr.ts @@ -475,6 +475,7 @@ if (typeof window !== "undefined") { loaders = { css: new CSSLoader(), }; + assetPrefixPath: string = ""; sessionId: number; @@ -1224,6 +1225,7 @@ if (typeof window !== "undefined") { this.epoch = welcome.epoch; this.javascriptReloader = welcome.javascriptReloader; this.cwd = welcome.cwd; + this.assetPrefixPath = welcome.assetPrefix; switch (this.javascriptReloader) { case API.Reloader.fast_refresh: { diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig index 9ed4c80ad..a0cb35f96 100644 --- a/src/sourcemap/sourcemap.zig +++ b/src/sourcemap/sourcemap.zig @@ -681,12 +681,13 @@ pub const LineOffsetTable = struct { if (strings.indexOfNewlineOrNonASCIICheckStart(remaining, @as(u32, len_), false)) |j| { column += @intCast(i32, j); remaining = remaining[j..]; - continue; } else { // if there are no more lines, we are done! column += @intCast(i32, remaining.len); remaining = remaining[remaining.len..]; } + + continue; }, else => {}, } @@ -726,7 +727,7 @@ pub const LineOffsetTable = struct { }, } - remaining = remaining[@minimum(cp_len, remaining.len)..]; + remaining = remaining[cp_len..]; } // Mark the start of the next line @@ -834,6 +835,7 @@ pub const Chunk = struct { chunk: Chunk, source: Logger.Source, mutable: MutableString, + include_sources_contents: bool, comptime ascii_only: bool, ) !MutableString { var output = mutable; @@ -851,14 +853,17 @@ pub const Chunk = struct { } output.growIfNeeded( - filename.len + 2 + source.contents.len + chunk.buffer.list.items.len + 32 + 39 + 29 + 22 + 20, + filename.len + 2 + (source.contents.len * @as(usize, @boolToInt(include_sources_contents))) + chunk.buffer.list.items.len + 32 + 39 + 29 + 22 + 20, ) catch unreachable; try output.append("{\n \"version\":3,\n \"sources\": ["); output = try JSPrinter.quoteForJSON(filename, output, ascii_only); - try output.append("],\n \"sourcesContent\": ["); - output = try JSPrinter.quoteForJSON(source.contents, output, ascii_only); + if (include_sources_contents) { + try output.append("],\n \"sourcesContent\": ["); + output = try JSPrinter.quoteForJSON(source.contents, output, ascii_only); + } + try output.append("],\n \"mappings\": "); output = try JSPrinter.quoteForJSON(chunk.buffer.list.items, output, ascii_only); try output.append(", \"names\": []\n}"); @@ -1009,7 +1014,7 @@ pub const Chunk = struct { i = j; continue; } else { - b.generated_column += @intCast(i32, slice[i..].len); + b.generated_column += @intCast(i32, slice[i..].len) + 1; i = n; break; } @@ -1017,7 +1022,7 @@ pub const Chunk = struct { '\r', '\n', 0x2028, 0x2029 => { // windows newline if (c == '\r') { - const newline_check = b.last_generated_update + i; + const newline_check = b.last_generated_update + i + 1; if (newline_check < output.len and output[newline_check] == '\n') { continue; } @@ -1077,10 +1082,12 @@ pub const Chunk = struct { } pub fn addSourceMapping(b: *ThisBuilder, loc: Logger.Loc, output: []const u8) void { + if ( // exclude generated code from source - if (b.prev_loc.eql(loc) or loc.start == Logger.Loc.Empty.start) { + b.prev_loc.eql(loc) or + // don't insert mappings for same location twice + loc.start == Logger.Loc.Empty.start) return; - } b.prev_loc = loc; const list = b.line_offset_tables; @@ -1112,7 +1119,7 @@ pub const Chunk = struct { .generated_column = b.generated_column, .source_index = b.prev_state.source_index, .original_line = original_line, - .original_column = b.prev_state.original_column, + .original_column = original_column, }); // This line now has a mapping on it, so don't insert another one diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 409ded4f1..a5e3e7104 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -12,6 +12,19 @@ pub inline fn containsChar(self: string, char: u8) bool { pub inline fn contains(self: string, str: string) bool { return std.mem.indexOf(u8, self, str) != null; } +pub inline fn containsComptime(self: string, comptime str: string) bool { + var remain = self; + const Int = std.meta.Int(.unsigned, str.len * 8); + + while (remain.len >= comptime str.len) { + if (@bitCast(Int, remain.ptr[0..str.len].*) == @bitCast(Int, str.ptr[0..str.len].*)) { + return true; + } + remain = remain[str.len..]; + } + + return false; +} pub const includes = contains; pub inline fn containsAny(in: anytype, target: string) bool { |