diff options
-rw-r--r-- | src/js_ast.zig | 622 | ||||
-rw-r--r-- | src/js_lexer.zig | 3 | ||||
-rw-r--r-- | src/js_parser/js_parser.zig | 186 | ||||
-rw-r--r-- | src/json_parser.zig | 43 |
4 files changed, 499 insertions, 355 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig index 88bf444f0..c457984e1 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -5324,7 +5324,8 @@ pub const Macro = struct { return Expr{ .loc = this.loc, .data = .{ .e_spread = value } }; }, .e_string => |value| { - return Expr{ .loc = this.loc, .data = .{ .e_string = value } }; + this.data.e_string.* = value.clone(JavaScript.VirtualMachine.vm.allocator) catch unreachable; + return Expr{ .loc = this.loc, .data = .{ .e_string = this.data.e_string } }; }, .e_template_part => |value| { return Expr{ .loc = this.loc, .data = .{ .e_template_part = value } }; @@ -7480,16 +7481,6 @@ pub const Macro = struct { } pub const Runner = struct { - caller: Expr, - function_name: string, - macro: *const Macro, - allocator: std.mem.Allocator, - id: i32, - log: *logger.Log, - source: *const logger.Source, - is_top_level: bool = true, - visited: VisitMap = VisitMap{}, - const VisitMap = std.AutoHashMapUnmanaged(JSC.JSValue, Expr); threadlocal var args_buf: [2]js.JSObjectRef = undefined; @@ -7497,267 +7488,374 @@ pub const Macro = struct { threadlocal var exception_holder: Zig.ZigException.Holder = undefined; pub const MacroError = error{MacroFailed}; - fn coerce( - this: *Runner, - value: JSC.JSValue, - global: *JSC.JSGlobalObject, - comptime Visitor: type, - visitor: Visitor, - ) MacroError!Expr { - if (value.isUndefined()) { - if (this.is_top_level) { - return this.caller; + pub fn NewRun(comptime Visitor: type) type { + return struct { + const Run = @This(); + caller: Expr, + function_name: string, + macro: *const Macro, + global: *JSC.JSGlobalObject, + allocator: std.mem.Allocator, + id: i32, + log: *logger.Log, + source: *const logger.Source, + visited: VisitMap = VisitMap{}, + visitor: Visitor, + is_top_level: bool = false, + + pub fn runAsync( + macro: Macro, + log: *logger.Log, + allocator: std.mem.Allocator, + function_name: string, + caller: Expr, + args: []Expr, + source: *const logger.Source, + id: i32, + visitor: Visitor, + ) callconv(.Async) MacroError!Expr { + var macro_callback = macro.vm.macros.get(id) orelse return caller; + + var result = js.JSObjectCallAsFunctionReturnValueHoldingAPILock(macro.vm.global.ref(), macro_callback, null, args.len + 1, &args_buf); + + var runner = Run{ + .caller = caller, + .function_name = function_name, + .macro = ¯o, + .allocator = allocator, + .global = macro.vm.global, + .id = id, + .log = log, + .source = source, + .visited = VisitMap{}, + .visitor = visitor, + }; + + defer runner.visited.deinit(allocator); + + return try runner.run( + result, + ); } - return Expr.init(E.Undefined, E.Undefined{}, this.caller.loc); - } else if (value.isNull()) { - return Expr.init(E.Null, E.Null{}, this.caller.loc); - } + pub fn run( + this: *Run, + value: JSC.JSValue, + ) MacroError!Expr { + return try switch (JSC.ZigConsoleClient.Formatter.Tag.get(value, this.global).tag) { + .Error => this.coerce(value, .Error), + .Undefined => this.coerce(value, .Undefined), + .Null => this.coerce(value, .Null), + .Private => this.coerce(value, .Private), + .Boolean => this.coerce(value, .Boolean), + .Array => this.coerce(value, .Array), + .Object => this.coerce(value, .Object), + .JSON => this.coerce(value, .JSON), + .Integer => this.coerce(value, .Integer), + .Double => this.coerce(value, .Double), + .String => this.coerce(value, .String), + .Promise => this.coerce(value, .Promise), + else => brk: { + this.log.addErrorFmt( + this.source, + this.caller.loc, + this.allocator, + "cannot coerce {s} to Bun's AST. Please return a valid macro using the JSX syntax", + .{@tagName(value.jsType())}, + ) catch unreachable; + break :brk error.MacroFailed; + }, + }; + } - this.is_top_level = false; + pub fn coerce( + this: *Run, + value: JSC.JSValue, + comptime tag: JSC.ZigConsoleClient.Formatter.Tag, + ) MacroError!Expr { + switch (comptime tag) { + .Error => { + this.macro.vm.defaultErrorHandler(value, null); + return this.caller; + }, + .Undefined => if (this.is_top_level) + return this.caller + else + return Expr.init(E.Undefined, E.Undefined{}, this.caller.loc), + .Null => return Expr.init(E.Null, E.Null{}, this.caller.loc), + .Private => { + this.is_top_level = false; + var _entry = this.visited.getOrPut(this.allocator, value) catch unreachable; + if (_entry.found_existing) { + return _entry.value_ptr.*; + } - if (value.isError() or value.isAggregateError(global) or value.isException(global.vm())) { - this.macro.vm.defaultErrorHandler(value, null); - return error.MacroFailed; - } + if (JSCBase.GetJSPrivateData(JSNode, value.asObjectRef())) |node| { + _entry.value_ptr.* = node.toExpr(); + node.visited = true; + node.updateSymbolsMap(Visitor, this.visitor); + return _entry.value_ptr.*; + } - const console_tag = JSC.ZigConsoleClient.Formatter.Tag.get(value, global); - switch (console_tag.tag) { - .Error, .Undefined, .Null => unreachable, - .Private => { - var _entry = this.visited.getOrPut(this.allocator, value) catch unreachable; - if (_entry.found_existing) { - return _entry.value_ptr.*; - } + if (JSCBase.GetJSPrivateData(JSC.BuildError, value.asObjectRef()) != null) { + this.macro.vm.defaultErrorHandler(value, null); + return error.MacroFailed; + } - if (JSCBase.GetJSPrivateData(JSNode, value.asObjectRef())) |node| { - _entry.value_ptr.* = node.toExpr(); - node.visited = true; - node.updateSymbolsMap(Visitor, visitor); - return _entry.value_ptr.*; - } + if (JSCBase.GetJSPrivateData(JSC.ResolveError, value.asObjectRef()) != null) { + this.macro.vm.defaultErrorHandler(value, null); + return error.MacroFailed; + } - if (JSCBase.GetJSPrivateData(JSC.BuildError, value.asObjectRef()) != null) { - this.macro.vm.defaultErrorHandler(value, null); - return error.MacroFailed; - } + // alright this is insane + if (JSCBase.GetJSPrivateData(JSC.WebCore.Response, value.asObjectRef())) |response| { + switch (response.body.value) { + .Unconsumed => { + if (response.body.len > 0) { + var mime_type = HTTP.MimeType.other; + if (response.body.init.headers) |headers| { + if (headers.getHeaderIndex("content-type")) |content_type| { + mime_type = HTTP.MimeType.init(headers.asStr(headers.entries.get(content_type).value)); + } + } - if (JSCBase.GetJSPrivateData(JSC.ResolveError, value.asObjectRef()) != null) { - this.macro.vm.defaultErrorHandler(value, null); - return error.MacroFailed; - } + if (response.body.ptr) |_ptr| { + var zig_string = JSC.ZigString.init(_ptr[0..response.body.len]); + + if (mime_type.category == .json) { + var source = logger.Source.initPathString("fetch.json", zig_string.slice()); + var out_expr = JSONParser.ParseJSONForMacro(&source, this.log, this.allocator) catch { + return error.MacroFailed; + }; + switch (out_expr.data) { + .e_object => { + out_expr.data.e_object.was_originally_macro = true; + }, + .e_array => { + out_expr.data.e_array.was_originally_macro = true; + }, + else => {}, + } + + return out_expr; + } - // alright this is insane - if (JSCBase.GetJSPrivateData(JSC.WebCore.Response, value.asObjectRef())) |response| { - switch (response.body.value) { - .Unconsumed => { - if (response.body.len > 0) { - var mime_type = HTTP.MimeType.other; - if (response.body.init.headers) |headers| { - if (headers.getHeaderIndex("content-type")) |content_type| { - mime_type = HTTP.MimeType.init(headers.asStr(headers.entries.get(content_type).value)); - } - } + if (mime_type.category.isTextLike()) { + zig_string.detectEncoding(); + const utf8 = if (zig_string.is16Bit()) + zig_string.toSlice(this.allocator).slice() + else + zig_string.slice(); - if (response.body.ptr) |_ptr| { - var zig_string = JSC.ZigString.init(_ptr[0..response.body.len]); - - if (mime_type.category == .json) { - var source = logger.Source.initPathString("fetch.json", zig_string.slice()); - var out_expr = JSONParser.ParseJSON(&source, this.log, this.allocator) catch { - return error.MacroFailed; - }; - switch (out_expr.data) { - .e_object => { - out_expr.data.e_object.was_originally_macro = true; - }, - .e_array => { - out_expr.data.e_array.was_originally_macro = true; - }, - else => {}, - } + return Expr.init(E.String, E.String{ .utf8 = utf8 }, this.caller.loc); + } - return out_expr; + return Expr.init(E.String, E.String{ .utf8 = zig_string.toBase64DataURL(this.allocator) catch unreachable }, this.caller.loc); + } } - if (mime_type.category.isTextLike()) { - zig_string.detectEncoding(); - const utf8 = if (zig_string.is16Bit()) - zig_string.toSlice(this.allocator).slice() - else - zig_string.slice(); - - return Expr.init(E.String, E.String{ .utf8 = utf8 }, this.caller.loc); + return Expr.init(E.String, E.String.empty, this.caller.loc); + }, + .Empty => { + return Expr.init(E.String, E.String.empty, this.caller.loc); + }, + .String => |str| { + var zig_string = JSC.ZigString.init(str); + + zig_string.detectEncoding(); + if (zig_string.is16Bit()) { + var slice = zig_string.toSlice(this.allocator); + if (response.body.ptr_allocator) |allocator| response.body.deinit(allocator); + return Expr.init(E.String, E.String{ .utf8 = slice.slice() }, this.caller.loc); } - return Expr.init(E.String, E.String{ .utf8 = zig_string.toBase64DataURL(this.allocator) catch unreachable }, this.caller.loc); - } + return Expr.init(E.String, E.String{ .utf8 = zig_string.slice() }, this.caller.loc); + }, + .ArrayBuffer => |buffer| { + return Expr.init( + E.String, + E.String{ .utf8 = JSC.ZigString.init(buffer.slice()).toBase64DataURL(this.allocator) catch unreachable }, + this.caller.loc, + ); + }, } + } + }, - return Expr.init(E.String, E.String.empty, this.caller.loc); - }, - .Empty => { - return Expr.init(E.String, E.String.empty, this.caller.loc); - }, - .String => |str| { - var zig_string = JSC.ZigString.init(str); - - zig_string.detectEncoding(); - if (zig_string.is16Bit()) { - var slice = zig_string.toSlice(this.allocator); - if (response.body.ptr_allocator) |allocator| response.body.deinit(allocator); - return Expr.init(E.String, E.String{ .utf8 = slice.slice() }, this.caller.loc); + .Boolean => { + return Expr{ .data = .{ .e_boolean = .{ .value = value.toBoolean() } }, .loc = this.caller.loc }; + }, + JSC.ZigConsoleClient.Formatter.Tag.Array => { + this.is_top_level = false; + + var _entry = this.visited.getOrPut(this.allocator, value) catch unreachable; + if (_entry.found_existing) { + switch (_entry.value_ptr.*.data) { + .e_object, .e_array => { + this.log.addErrorFmt(this.source, this.caller.loc, this.allocator, "converting circular structure to Bun AST is not implemented yet", .{}) catch unreachable; + return error.MacroFailed; + }, + else => {}, } + return _entry.value_ptr.*; + } - return Expr.init(E.String, E.String{ .utf8 = zig_string.slice() }, this.caller.loc); - }, - .ArrayBuffer => |buffer| { - return Expr.init( - E.String, - E.String{ .utf8 = JSC.ZigString.init(buffer.slice()).toBase64DataURL(this.allocator) catch unreachable }, + var iter = JSC.JSArrayIterator.init(value, this.global); + if (iter.len == 0) { + const result = Expr.init( + E.Array, + E.Array{ + .items = ExprNodeList.init(&[_]Expr{}), + .was_originally_macro = true, + }, this.caller.loc, ); - }, - } - } - }, - - .Boolean => { - return Expr{ .data = .{ .e_boolean = .{ .value = value.toBoolean() } }, .loc = this.caller.loc }; - }, - JSC.ZigConsoleClient.Formatter.Tag.Array => { - var _entry = this.visited.getOrPut(this.allocator, value) catch unreachable; - if (_entry.found_existing) { - return _entry.value_ptr.*; - } + _entry.value_ptr.* = result; + return result; + } + var array = this.allocator.alloc(Expr, iter.len) catch unreachable; + var out = Expr.init( + E.Array, + E.Array{ + .items = ExprNodeList.init(array[0..0]), + .was_originally_macro = true, + }, + this.caller.loc, + ); + _entry.value_ptr.* = out; + + errdefer this.allocator.free(array); + var i: usize = 0; + while (iter.next()) |item| { + array[i] = try this.run(item); + if (array[i].isMissing()) + continue; + i += 1; + } + out.data.e_array.items = ExprNodeList.init(array); + _entry.value_ptr.* = out; + return out; + }, + // TODO: optimize this + JSC.ZigConsoleClient.Formatter.Tag.Object => { + this.is_top_level = false; + var _entry = this.visited.getOrPut(this.allocator, value) catch unreachable; + if (_entry.found_existing) { + switch (_entry.value_ptr.*.data) { + .e_object, .e_array => { + this.log.addErrorFmt(this.source, this.caller.loc, this.allocator, "converting circular structure to Bun AST is not implemented yet", .{}) catch unreachable; + return error.MacroFailed; + }, + else => {}, + } + return _entry.value_ptr.*; + } - var iter = JSC.JSArrayIterator.init(value, global); - if (iter.len == 0) { - return Expr.init( - E.Array, - E.Array{ - .items = ExprNodeList.init(&[_]Expr{}), - .was_originally_macro = true, - }, - this.caller.loc, - ); - } - var array = this.allocator.alloc(Expr, iter.len) catch unreachable; - errdefer this.allocator.free(array); - var i: usize = 0; - while (iter.next()) |item| { - array[i] = try this.coerce(item, global, Visitor, visitor); - if (array[i].isMissing()) - continue; - i += 1; - } + var object = value.asObjectRef(); + var array = JSC.C.JSObjectCopyPropertyNames(this.global.ref(), object); + defer JSC.C.JSPropertyNameArrayRelease(array); + const count_ = JSC.C.JSPropertyNameArrayGetCount(array); + var properties = this.allocator.alloc(G.Property, count_) catch unreachable; + errdefer this.allocator.free(properties); + var out = Expr.init( + E.Object, + E.Object{ + .properties = BabyList(G.Property).init(properties), + .was_originally_macro = true, + }, + this.caller.loc, + ); + _entry.value_ptr.* = out; + + var i: usize = 0; + while (i < count_) : (i += 1) { + var property_name_ref = JSC.C.JSPropertyNameArrayGetNameAtIndex(array, i); + defer JSC.C.JSStringRelease(property_name_ref); + properties[i] = G.Property{ + .key = Expr.init(E.String, E.String{ .utf8 = this.allocator.dupe( + u8, + JSC.C.JSStringGetCharacters8Ptr(property_name_ref)[0..JSC.C.JSStringGetLength(property_name_ref)], + ) catch unreachable }, this.caller.loc), + .value = try this.run( + JSC.JSValue.fromRef(JSC.C.JSObjectGetProperty(this.global.ref(), object, property_name_ref, null)), + ), + }; + } + out.data.e_object.properties = BabyList(G.Property).init(properties[0..i]); + _entry.value_ptr.* = out; + return out; + }, - const out = Expr.init( - E.Array, - E.Array{ - .items = ExprNodeList.init(array[0..i]), - .was_originally_macro = true, + .JSON => { + this.is_top_level = false; + // if (console_tag.cell == .JSDate) { + // // in the code for printing dates, it never exceeds this amount + // var iso_string_buf = this.allocator.alloc(u8, 36) catch unreachable; + // var str = JSC.ZigString.init(""); + // value.jsonStringify(this.global, 0, &str); + // var out_buf: []const u8 = std.fmt.bufPrint(iso_string_buf, "{}", .{str}) catch ""; + // if (out_buf.len > 2) { + // // trim the quotes + // out_buf = out_buf[1 .. out_buf.len - 1]; + // } + // return Expr.init(E.New, E.New{.target = Expr.init(E.Dot{.target = E}) }) + // } }, - this.caller.loc, - ); - _entry.value_ptr.* = out; - return out; - }, - // TODO: optimize this - JSC.ZigConsoleClient.Formatter.Tag.Object => { - var _entry = this.visited.getOrPut(this.allocator, value) catch unreachable; - if (_entry.found_existing) { - return _entry.value_ptr.*; - } - var object = value.asObjectRef(); - var array = JSC.C.JSObjectCopyPropertyNames(global.ref(), object); - defer JSC.C.JSPropertyNameArrayRelease(array); - const count_ = JSC.C.JSPropertyNameArrayGetCount(array); - var properties = this.allocator.alloc(G.Property, count_) catch unreachable; - errdefer this.allocator.free(properties); - var i: usize = 0; - while (i < count_) : (i += 1) { - var property_name_ref = JSC.C.JSPropertyNameArrayGetNameAtIndex(array, i); - defer JSC.C.JSStringRelease(property_name_ref); - properties[i] = G.Property{ - .key = Expr.init(E.String, E.String{ .utf8 = this.allocator.dupe( - u8, - JSC.C.JSStringGetCharacters8Ptr(property_name_ref)[0..JSC.C.JSStringGetLength(property_name_ref)], - ) catch unreachable }, this.caller.loc), - .value = try this.coerce( - JSC.JSValue.fromRef(JSC.C.JSObjectGetProperty(global.ref(), object, property_name_ref, null)), - global, - Visitor, - visitor, - ), - }; - } - const out = Expr.init( - E.Object, - E.Object{ - .properties = BabyList(G.Property).init(properties[0..i]), - .was_originally_macro = true, + .Integer => { + return Expr.init(E.Number, E.Number{ .value = @intToFloat(f64, value.toInt32()) }, this.caller.loc); }, - this.caller.loc, - ); - _entry.value_ptr.* = out; - return out; - }, - - .JSON => { - // if (console_tag.cell == .JSDate) { - // // in the code for printing dates, it never exceeds this amount - // var iso_string_buf = this.allocator.alloc(u8, 36) catch unreachable; - // var str = JSC.ZigString.init(""); - // value.jsonStringify(global, 0, &str); - // var out_buf: []const u8 = std.fmt.bufPrint(iso_string_buf, "{}", .{str}) catch ""; - // if (out_buf.len > 2) { - // // trim the quotes - // out_buf = out_buf[1 .. out_buf.len - 1]; - // } - // return Expr.init(E.New, E.New{.target = Expr.init(E.Dot{.target = E}) }) - // } - }, - - .Integer => { - return Expr.init(E.Number, E.Number{ .value = @intToFloat(f64, value.toInt32()) }, this.caller.loc); - }, - .Double => { - return Expr.init(E.Number, E.Number{ .value = value.asNumber() }, this.caller.loc); - }, - .String => { - var zig_str = value.getZigString(global); - zig_str.detectEncoding(); - var sliced = zig_str.toSlice(this.allocator); - return Expr.init(E.String, E.String{ .utf8 = sliced.slice() }, this.caller.loc); - }, - .Promise => { - var _entry = this.visited.getOrPut(this.allocator, value) catch unreachable; - if (_entry.found_existing) { - return _entry.value_ptr.*; - } + .Double => { + return Expr.init(E.Number, E.Number{ .value = value.asNumber() }, this.caller.loc); + }, + .String => { + var zig_str = value.getZigString(this.global); + zig_str.detectEncoding(); + var sliced = zig_str.toSlice(this.allocator); + return Expr.init(E.String, E.String{ .utf8 = sliced.slice() }, this.caller.loc); + }, + .Promise => { + var _entry = this.visited.getOrPut(this.allocator, value) catch unreachable; + if (_entry.found_existing) { + return _entry.value_ptr.*; + } - var promise = JSC.JSPromise.resolvedPromise(global, value); - while (promise.status(global.vm()) == .Pending) { - this.macro.vm.tick(); - } + var promise = JSC.JSPromise.resolvedPromise(this.global, value); + while (promise.status(this.global.vm()) == .Pending) { + this.macro.vm.tick(); + } - const result = try this.coerce(promise.result(global.vm()), global, Visitor, visitor); - _entry.value_ptr.* = result; - return result; - }, - else => {}, - } + const rejected = promise.status(this.global.vm()) == .Rejected; + const promise_result = promise.result(this.global.vm()); + + if (promise_result.isUndefined() and this.is_top_level) { + this.is_top_level = false; + return this.caller; + } + + if (rejected or promise_result.isError() or promise_result.isAggregateError(this.global) or promise_result.isException(this.global.vm())) { + this.macro.vm.defaultErrorHandler(promise_result, null); + return error.MacroFailed; + } + this.is_top_level = false; + const result = try this.run(promise_result); + + _entry.value_ptr.* = result; + return result; + }, + else => {}, + } - this.log.addErrorFmt( - this.source, - this.caller.loc, - this.allocator, - "cannot coerce {s} to Bun's AST. Please return a valid macro using the JSX syntax", - .{@tagName(console_tag.cell)}, - ) catch unreachable; - return error.MacroFailed; + this.log.addErrorFmt( + this.source, + this.caller.loc, + this.allocator, + "cannot coerce {s} to Bun's AST. Please return a valid macro using the JSX syntax", + .{@tagName(value.jsType())}, + ) catch unreachable; + return error.MacroFailed; + } + }; } pub fn run( @@ -7780,29 +7878,29 @@ pub const Macro = struct { macro.vm.global.ref(), &expr_nodes_buf[0], ); - args_buf[1] = null; + const Run = NewRun(Visitor); - var macro_callback = macro.vm.macros.get(id) orelse return caller; - var result = js.JSObjectCallAsFunctionReturnValueHoldingAPILock(macro.vm.global.ref(), macro_callback, null, args.len + 1, &args_buf); - - var runner = Runner{ - .caller = caller, - .function_name = function_name, - .macro = ¯o, - .allocator = allocator, - .id = id, - .log = log, - .source = source, - }; - defer runner.visited.deinit(allocator); + // Give it >= 256 KB stack space + // Cast to usize to ensure we get an 8 byte aligned pointer + const PooledFrame = ObjectPool([@maximum(@sizeOf(@Frame(Run.runAsync)), 256_000) / @sizeOf(usize)]usize, null, true, 1); + var pooled_frame = PooledFrame.get(default_allocator); + defer pooled_frame.release(); + + var result: MacroError!Expr = error.MacroFailed; - return try runner.coerce( - result, - macro.vm.global, - Visitor, + _ = nosuspend @asyncCall(std.mem.asBytes(&pooled_frame.data), &result, Run.runAsync, .{ + macro, + log, + allocator, + function_name, + caller, + args, + source, + id, visitor, - ); + }); + return result; } }; }; diff --git a/src/js_lexer.zig b/src/js_lexer.zig index 45c8dd982..878751eea 100644 --- a/src/js_lexer.zig +++ b/src/js_lexer.zig @@ -51,6 +51,9 @@ pub const JSONOptions = struct { ignore_trailing_escape_sequences: bool = false, json_warn_duplicate_keys: bool = true, + + /// mark as originally for a macro to enable inlining + was_originally_macro: bool = false, }; pub fn NewLexer(comptime json_options: JSONOptions) type { diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index e5add04bf..1c5e63b06 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -2958,6 +2958,8 @@ pub fn NewParser( pub const parser_features = js_parser_features; const P = @This(); pub const jsx_transform_type: JSXTransformType = js_parser_jsx; + const allow_macros = FeatureFlags.is_macro_enabled and jsx_transform_type != .macro; + const MacroCallCountType = if (allow_macros) u32 else u0; macro: MacroState = undefined, allocator: Allocator, options: Parser.Options, @@ -2991,7 +2993,7 @@ pub fn NewParser( promise_ref: ?Ref = null, scopes_in_order_visitor_index: usize = 0, has_classic_runtime_warned: bool = false, - has_called_macro: bool = false, + macro_call_count: MacroCallCountType = 0, /// Used for transforming export default -> module.exports has_export_default: bool = false, @@ -6178,7 +6180,6 @@ pub fn NewParser( try p.lexer.expectContextualKeyword("from"); _ = try p.parsePath(); try p.lexer.expectOrInsertSemicolon(); - Output.debug("Imported type-only import", .{}); return p.s(S.TypeScript{}, loc); }, else => {}, @@ -6241,7 +6242,7 @@ pub fn NewParser( } } - const macro_remap = if ((comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) and !is_macro) + const macro_remap = if ((comptime allow_macros) and !is_macro) p.options.macro_context.getRemap(path.text) else null; @@ -11099,7 +11100,7 @@ pub fn NewParser( if (func.name) |name| { if (name.ref) |name_ref| { p.recordDeclaredSymbol(name_ref) catch unreachable; - const symbol_name = p.symbols.items[name_ref.inner_index].original_name; + const symbol_name = p.loadNameFromRef(name_ref); if (isEvalOrArguments(symbol_name)) { p.markStrictModeFeature(.eval_or_arguments, js_lexer.rangeOfIdentifier(p.source, name.loc), symbol_name) catch unreachable; } @@ -11495,12 +11496,12 @@ pub fn NewParser( if (e_.tag) |tag| { e_.tag = p.visitExpr(tag); - if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) { + if (comptime allow_macros) { if (e_.tag.?.data == .e_import_identifier) { const ref = e_.tag.?.data.e_import_identifier.ref; if (p.macro.refs.get(ref)) |import_record_id| { const name = p.symbols.items[ref.inner_index].original_name; - p.has_called_macro = true; + p.macro_call_count += 1; const record = &p.import_records.items[import_record_id]; // We must visit it to convert inline_identifiers and record usage const macro_result = (p.options.macro_context.call( @@ -12175,8 +12176,8 @@ pub fn NewParser( return _expr; } - if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) { - if (p.has_called_macro) { + if (comptime allow_macros) { + if (p.macro_call_count > 0) { if (e_.target.data == .e_object and e_.target.data.e_object.was_originally_macro) { if (e_.target.get(e_.name)) |obj| { return obj; @@ -12431,7 +12432,7 @@ pub fn NewParser( } } - if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) { + if (comptime allow_macros) { if (is_macro_ref) { const ref = e_.target.data.e_import_identifier.ref; const import_record_id = p.macro.refs.get(ref).?; @@ -12439,7 +12440,7 @@ pub fn NewParser( const record = &p.import_records.items[import_record_id]; const copied = Expr{ .loc = expr.loc, .data = .{ .e_call = e_ } }; const start_error_count = p.log.msgs.items.len; - p.has_called_macro = true; + p.macro_call_count += 1; const macro_result = p.options.macro_context.call( record.path.text, @@ -12972,8 +12973,8 @@ pub fn NewParser( // } // } // } - // if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) { - // if (p.has_called_macro and data.decls[i].value != null and + // if (comptime allow_macros) { + // if (p.macro_call_count and data.decls[i].value != null and // data.decls[i].value.?.data == .e_object and data.decls[i].value.?.data.e_object.was_originally_macro) // { // p.maybeInlineMacroObject(&data.decls[i], data.decls[i].value.?); @@ -13339,87 +13340,18 @@ pub fn NewParser( var val = data.decls[i].value.?; const was_anonymous_named_expr = p.isAnonymousNamedExpr(val); - data.decls[i].value = p.visitExpr(val); + const prev_macro_call_count = p.macro_call_count; - // Optionally preserve the name - switch (data.decls[i].binding.data) { - .b_identifier => |id| { - data.decls[i].value = p.maybeKeepExprSymbolName( - data.decls[i].value.?, - p.symbols.items[id.ref.inner_index].original_name, - was_anonymous_named_expr, - ); - }, - .b_object => |bound_object| { - if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) { - if (p.has_called_macro and data.decls[i].value != null and - data.decls[i].value.?.data == .e_object and - data.decls[i].value.?.data.e_object.was_originally_macro) - { - bail: { - var object = data.decls[i].value.?.data.e_object; - for (bound_object.properties) |property| { - if (property.flags.is_spread) break :bail; - } - var output_properties = object.properties.slice(); - var end: u32 = 0; - for (bound_object.properties) |property| { - if (property.key.asString(p.allocator)) |name| { - if (object.asProperty(name)) |query| { - output_properties[end] = output_properties[query.i]; - end += 1; - } - } - } + data.decls[i].value = p.visitExpr(val); - object.properties.len = end; - } - } - } - }, - .b_array => |bound_array| { - if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) { - if (p.has_called_macro and data.decls[i].value != null and - data.decls[i].value.?.data == .e_array and - data.decls[i].value.?.data.e_array.was_originally_macro) - { - bail: { - var array = data.decls[i].value.?.data.e_array; - if (bound_array.has_spread) break :bail; - array.items.len = @minimum(array.items.len, @truncate(u32, bound_array.items.len)); - var slice = array.items.slice(); - outer: for (bound_array.items[0..array.items.len]) |item, item_i| { - const child_expr = slice[item_i]; - switch (item.binding.data) { - .b_object => |bound_object| { - if (child_expr.data != .e_object) continue :outer; - - for (bound_object.properties) |property| { - if (property.flags.is_spread) continue :outer; - } - var object = child_expr.data.e_object; - var output_properties = object.properties.slice(); - var end: u32 = 0; - for (bound_object.properties) |property| { - if (property.key.asString(p.allocator)) |name| { - if (object.asProperty(name)) |query| { - output_properties[end] = output_properties[query.i]; - end += 1; - } - } - } - - object.properties.len = end; - }, - else => {}, - } - } - } - } - } - }, - else => {}, - } + p.visitDecl( + &data.decls[i], + was_anonymous_named_expr, + if (comptime allow_macros) + prev_macro_call_count != p.macro_call_count + else + false, + ); } } @@ -13954,6 +13886,78 @@ pub fn NewParser( try stmts.append(stmt.*); } + fn visitBindingAndExprForMacro(p: *P, binding: Binding, expr: Expr) void { + switch (binding.data) { + .b_object => |bound_object| { + if (expr.data == .e_object and + expr.data.e_object.was_originally_macro) + { + var object = expr.data.e_object; + for (bound_object.properties) |property| { + if (property.flags.is_spread) return; + } + var output_properties = object.properties.slice(); + var end: u32 = 0; + for (bound_object.properties) |property| { + if (property.key.asString(p.allocator)) |name| { + if (object.asProperty(name)) |query| { + switch (query.expr.data) { + .e_object, .e_array => p.visitBindingAndExprForMacro(property.value, query.expr), + else => {}, + } + output_properties[end] = output_properties[query.i]; + end += 1; + } + } + } + + object.properties.len = end; + } + }, + .b_array => |bound_array| { + if (expr.data == .e_array and + expr.data.e_array.was_originally_macro and !bound_array.has_spread) + { + var array = expr.data.e_array; + + array.items.len = @minimum(array.items.len, @truncate(u32, bound_array.items.len)); + var slice = array.items.slice(); + for (bound_array.items[0..array.items.len]) |item, item_i| { + const child_expr = slice[item_i]; + if (item.binding.data == .b_missing) { + slice[item_i] = p.e(E.Missing{}, expr.loc); + continue; + } + + p.visitBindingAndExprForMacro(item.binding, child_expr); + } + } + }, + else => {}, + } + } + + fn visitDecl(p: *P, decl: *Decl, was_anonymous_named_expr: bool, could_be_macro: bool) void { + // Optionally preserve the name + switch (decl.binding.data) { + .b_identifier => |id| { + decl.value = p.maybeKeepExprSymbolName( + decl.value.?, + p.symbols.items[id.ref.inner_index].original_name, + was_anonymous_named_expr, + ); + }, + .b_object, .b_array => { + if (comptime allow_macros) { + if (could_be_macro and decl.value != null) { + p.visitBindingAndExprForMacro(decl.binding, decl.value.?); + } + } + }, + else => {}, + } + } + pub fn markExportedDeclsInsideNamespace(p: *P, ns_ref: Ref, decls: []G.Decl) void { for (decls) |decl| { p.markExportedBindingInsideNamespace(ns_ref, decl.binding); diff --git a/src/json_parser.zig b/src/json_parser.zig index c732bfd12..c0c695e35 100644 --- a/src/json_parser.zig +++ b/src/json_parser.zig @@ -178,7 +178,11 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { is_single_line = false; } try p.lexer.expect(.t_close_bracket); - return p.e(E.Array{ .items = ExprNodeList.fromList(exprs), .is_single_line = is_single_line }, loc); + return p.e(E.Array{ + .items = ExprNodeList.fromList(exprs), + .is_single_line = is_single_line, + .was_originally_macro = comptime opts.was_originally_macro, + }, loc); }, .t_open_brace => { try p.lexer.next(); @@ -247,6 +251,7 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { return p.e(E.Object{ .properties = G.Property.List.fromList(properties), .is_single_line = is_single_line, + .was_originally_macro = comptime opts.was_originally_macro, }, loc); }, else => { @@ -482,10 +487,21 @@ const DotEnvJSONParser = JSONLikeParser(js_lexer.JSONOptions{ .allow_trailing_commas = true, .is_json = true, }); -var empty_string = E.String{ .utf8 = "" }; + const TSConfigParser = JSONLikeParser(js_lexer.JSONOptions{ .allow_comments = true, .is_json = true, .allow_trailing_commas = true }); +const JSONParserForMacro = JSONLikeParser( + js_lexer.JSONOptions{ + .allow_comments = true, + .is_json = true, + .json_warn_duplicate_keys = false, + .allow_trailing_commas = true, + .was_originally_macro = true, + }, +); + var empty_object = E.Object{}; var empty_array = E.Array{}; +var empty_string = E.String{ .utf8 = "" }; var empty_string_data = Expr.Data{ .e_string = &empty_string }; var empty_object_data = Expr.Data{ .e_object = &empty_object }; var empty_array_data = Expr.Data{ .e_array = &empty_array }; @@ -513,6 +529,29 @@ pub fn ParseJSON(source: *const logger.Source, log: *logger.Log, allocator: std. return try parser.parseExpr(false); } +pub fn ParseJSONForMacro(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator) !Expr { + var parser = try JSONParserForMacro.init(allocator, source.*, log); + switch (source.contents.len) { + // This is to be consisntent with how disabled JS files are handled + 0 => { + return Expr{ .loc = logger.Loc{ .start = 0 }, .data = empty_object_data }; + }, + // This is a fast pass I guess + 2 => { + if (strings.eqlComptime(source.contents[0..1], "\"\"") or strings.eqlComptime(source.contents[0..1], "''")) { + return Expr{ .loc = logger.Loc{ .start = 0 }, .data = empty_string_data }; + } else if (strings.eqlComptime(source.contents[0..1], "{}")) { + return Expr{ .loc = logger.Loc{ .start = 0 }, .data = empty_object_data }; + } else if (strings.eqlComptime(source.contents[0..1], "[]")) { + return Expr{ .loc = logger.Loc{ .start = 0 }, .data = empty_array_data }; + } + }, + else => {}, + } + + return try parser.parseExpr(false); +} + pub const JSONParseResult = struct { expr: Expr, tag: Tag, |