aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-11 00:03:09 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-11 00:03:09 -0800
commit44b0c8153a7092f97c36c5aab82b692f672c3ddf (patch)
tree5554a3e9e69bd689739c77745cb4a4bf87e98fd6 /src
parentc8f6337f1f9f22b48286c8bed1e47d44f0657cd0 (diff)
downloadbun-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.ts1
-rw-r--r--src/api/schema.js8
-rw-r--r--src/api/schema.peechy3
-rw-r--r--src/api/schema.zig5
-rw-r--r--src/http.zig13
-rw-r--r--src/javascript/jsc/bindings/exports.zig122
-rw-r--r--src/javascript/jsc/bindings/headers-handwritten.h2
-rw-r--r--src/javascript/jsc/javascript.zig35
-rw-r--r--src/javascript/jsc/webcore/encoding.zig3
-rw-r--r--src/js_ast.zig8
-rw-r--r--src/js_parser/js_parser.zig237
-rw-r--r--src/js_printer.zig23
-rw-r--r--src/runtime/hmr.ts2
-rw-r--r--src/sourcemap/sourcemap.zig27
-rw-r--r--src/string_immutable.zig13
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 {