aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-30 13:49:46 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-30 13:49:46 -0700
commit4cdc8939ab81de83e393d5e52fc2598cfb0bb928 (patch)
tree1653f745685f780bebc4895b20b48f7d01c9970e
parentec256209a857390784713609f0f85a54349df380 (diff)
downloadbun-4cdc8939ab81de83e393d5e52fc2598cfb0bb928.tar.gz
bun-4cdc8939ab81de83e393d5e52fc2598cfb0bb928.tar.zst
bun-4cdc8939ab81de83e393d5e52fc2598cfb0bb928.zip
Wire up macro-injected imports and move some structs above the gigantic parser type to reduce bun compile time a little
-rw-r--r--src/javascript/jsc/base.zig2
-rw-r--r--src/js_ast.zig505
-rw-r--r--src/js_parser/js_parser.zig461
3 files changed, 538 insertions, 430 deletions
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index b0583dc53..acdb43bda 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -1551,6 +1551,7 @@ pub fn castObj(obj: js.JSObjectRef, comptime Type: type) *Type {
}
const JSNode = @import("../../js_ast.zig").Macro.JSNode;
const LazyPropertiesObject = @import("../../js_ast.zig").Macro.LazyPropertiesObject;
+const ModuleNamespace = @import("../../js_ast.zig").Macro.ModuleNamespace;
pub const JSPrivateDataPtr = TaggedPointerUnion(.{
ResolveError,
BuildError,
@@ -1562,6 +1563,7 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
Router,
JSNode,
LazyPropertiesObject,
+ ModuleNamespace,
});
pub inline fn GetJSPrivateData(comptime Type: type, ref: js.JSObjectRef) ?*Type {
diff --git a/src/js_ast.zig b/src/js_ast.zig
index a494f879a..56cab7a33 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -2593,6 +2593,9 @@ pub const Expr = struct {
e_class,
e_require,
+ // This should never make it to the printer
+ inline_identifier,
+
pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void {
return try std.json.stringify(@tagName(self), opts, o);
}
@@ -3137,6 +3140,10 @@ pub const Expr = struct {
e_new_target: E.NewTarget,
e_import_meta: E.ImportMeta,
+ // This type should not exist outside of MacroContext
+ // If it ends up in JSParser or JSPrinter, it is a bug.
+ inline_identifier: i32,
+
pub const Store = struct {
const often = 512;
const medium = 256;
@@ -4019,6 +4026,8 @@ pub const Macro = struct {
caller: Expr,
args: []Expr,
function_name: string,
+ comptime Visitor: type,
+ visitor: *Visitor,
) anyerror!Expr {
Expr.Data.Store.disable_reset = true;
Stmt.Data.Store.disable_reset = true;
@@ -4096,6 +4105,8 @@ pub const Macro = struct {
args,
source,
hash,
+ comptime Visitor,
+ visitor,
);
// this.macros.getOrPut(key: K)
}
@@ -4164,6 +4175,11 @@ pub const Macro = struct {
.get = JSBindings.getPropertyNodes,
.ro = true,
},
+
+ .namespace = .{
+ .get = JSBindings.getModuleNamespace,
+ .ro = true,
+ },
},
);
@@ -4174,6 +4190,30 @@ pub const Macro = struct {
// so it's safe to just avoid that and do it here like this:
return JSNode.Class.make(JavaScript.VirtualMachine.vm.global.ref(), ptr);
}
+
+ pub fn updateSymbolsMap(this: *JSNode, comptime Visitor: type, visitor: Visitor) void {
+ switch (this.data) {
+ Tag.fragment => |frag| {
+ for (frag) |child| {
+ if (child.data == .inline_inject) {
+ child.updateSymbolsMap(Visitor, visitor);
+ }
+ }
+ },
+ Tag.inline_inject => |inject| {
+ for (inject) |child| {
+ child.updateSymbolsMap(Visitor, visitor);
+ }
+ },
+
+ Tag.s_import => |import| {
+ visitor.visitImport(import);
+ },
+
+ else => {},
+ }
+ }
+
pub const JSBindings = struct {
const getAllocator = JSCBase.getAllocator;
@@ -4765,6 +4805,20 @@ pub const Macro = struct {
.e_undefined => |value| {
return Expr{ .loc = this.loc, .data = .{ .e_undefined = value } };
},
+ .fragment => |fragment| {
+ if (fragment.len == 0) return Expr{ .loc = this.loc, .data = .{ .e_missing = E.Missing{} } };
+
+ var left = toExpr(fragment[0]);
+
+ if (fragment.len == 1) return left;
+
+ for (fragment[1..]) |item| {
+ const right = toExpr(item);
+ left = Expr.joinWithComma(left, right, default_allocator);
+ }
+
+ return left;
+ },
else => {
return Expr{ .loc = this.loc, .data = .{ .e_missing = .{} } };
},
@@ -4828,6 +4882,7 @@ pub const Macro = struct {
g_property: *G.Property,
inline_inject: []JSNode,
+ inline_identifier: i32,
pub fn callArgs(this: Data) ExprNodeList {
if (this == .e_call)
@@ -4889,127 +4944,17 @@ pub const Macro = struct {
inline_true,
inline_false,
inline_inject,
+ inline_identifier,
+
fragment,
pub const ids: std.EnumArray(Tag, Expr.Data) = brk: {
var list = std.EnumArray(Tag, Expr.Data).initFill(Expr.Data{ .e_number = E.Number{ .value = 0.0 } });
- list.set(Tag.e_array, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_array)) },
- });
- list.set(Tag.e_unary, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_unary)) },
- });
- list.set(Tag.e_binary, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_binary)) },
- });
- list.set(Tag.e_boolean, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_boolean)) },
- });
- list.set(Tag.e_super, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_super)) },
- });
- list.set(Tag.e_null, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_null)) },
- });
- list.set(Tag.e_undefined, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_undefined)) },
- });
- list.set(Tag.e_function, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_function)) },
- });
- list.set(Tag.e_new_target, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_new_target)) },
- });
- list.set(Tag.e_import_meta, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_import_meta)) },
- });
- list.set(Tag.e_call, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_call)) },
- });
- list.set(Tag.e_dot, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_dot)) },
- });
- list.set(Tag.e_index, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_index)) },
- });
- list.set(Tag.e_arrow, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_arrow)) },
- });
- list.set(Tag.e_identifier, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_identifier)) },
- });
- list.set(Tag.e_import_identifier, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_import_identifier)) },
- });
- list.set(Tag.e_private_identifier, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_private_identifier)) },
- });
- list.set(Tag.e_jsx_element, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_jsx_element)) },
- });
- list.set(Tag.e_missing, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_missing)) },
- });
- list.set(Tag.e_number, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_number)) },
- });
- list.set(Tag.e_big_int, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_big_int)) },
- });
- list.set(Tag.e_object, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_object)) },
- });
- list.set(Tag.e_spread, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_spread)) },
- });
- list.set(Tag.e_string, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_string)) },
- });
- list.set(Tag.e_template_part, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_template_part)) },
- });
- list.set(Tag.e_template, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_template)) },
- });
- list.set(Tag.e_reg_exp, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_reg_exp)) },
- });
- list.set(Tag.e_await, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_await)) },
- });
- list.set(Tag.e_yield, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_yield)) },
- });
- list.set(Tag.e_if, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_if)) },
- });
- list.set(Tag.e_import, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_import)) },
- });
- list.set(Tag.e_this, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_this)) },
- });
- list.set(Tag.e_class, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_class)) },
- });
- list.set(Tag.e_require, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_require)) },
- });
- list.set(Tag.s_import, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.s_import)) },
- });
- list.set(Tag.g_property, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.g_property)) },
- });
- list.set(Tag.s_block, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.s_block)) },
- });
- list.set(Tag.inline_true, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.inline_true)) },
- });
- list.set(Tag.inline_false, Expr.Data{
- .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.inline_false)) },
- });
+ const fields: []std.builtin.TypeInfo.EnumField = @typeInfo(Tag).Enum.fields;
+ for (fields) |field| {
+ list.set(field.value, Expr.Data{ .e_number = E.Number{ .value = @intToFloat(f64, field.value) } });
+ }
+
break :brk list;
};
@@ -5023,12 +4968,11 @@ pub const Macro = struct {
.{ "undefined", Tag.e_undefined },
.{ "function", Tag.e_function },
.{ "new_target", Tag.e_new_target },
- .{ "import_meta", Tag.e_import_meta },
+ .{ "import-meta", Tag.e_import_meta },
.{ "call", Tag.e_call },
.{ "dot", Tag.e_dot },
.{ "index", Tag.e_index },
.{ "arrow", Tag.e_arrow },
- .{ "id", Tag.e_identifier },
.{ "import-id", Tag.e_import_identifier },
.{ "private-id", Tag.e_private_identifier },
.{ "jsx", Tag.e_jsx_element },
@@ -5054,6 +4998,8 @@ pub const Macro = struct {
.{ "true", Tag.inline_true },
.{ "false", Tag.inline_false },
.{ "inject", Tag.inline_inject },
+
+ .{ "id", Tag.inline_identifier },
});
pub const as_expr_tag: std.EnumArray(Tag, Expr.Tag) = brk: {
@@ -5198,6 +5144,89 @@ pub const Macro = struct {
};
};
+ pub const ModuleNamespace = struct {
+ import_data: ImportData,
+
+ pub const Class = JSCBase.NewClass(
+ ModuleNamespace,
+ .{
+ .name = "ModuleNamespace",
+ .read_only = true,
+ },
+ .{
+ .getProperty = .{
+ .rfn = getProperty,
+ },
+ .hasProperty = .{
+ .rfn = hasProperty,
+ },
+ .getPropertyNames = .{
+ .rfn = getPropertyNames,
+ },
+ },
+ .{},
+ );
+
+ pub fn getProperty(
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ propertyName: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) callconv(.C) js.JSValueRef {
+ var this: *ModuleNamespace = JSCBase.GetJSPrivateData(ModuleNamespace, thisObject) orelse return null;
+
+ const len = js.JSStringGetLength(propertyName);
+ const properties = this.import_data.import.items;
+ var ptr = js.JSStringGetCharacters8Ptr(propertyName);
+ var property_slice = ptr[0..len];
+ var value_node: JSNode = undefined;
+
+ for (properties) |property| {
+ if (strings.eql(property.alias, str)) {
+ const value = property.value orelse return js.JSValueMakeUndefined(ctx);
+ value_node = JSNode.initExpr(value);
+ return JSC.JSValue.jsNumberFromInt32(SymbolMap.generateImportHash(property.alias, this.import_data.path)).asRef();
+ }
+ }
+
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ pub fn hasProperty(
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ propertyName: js.JSStringRef,
+ ) callconv(.C) bool {
+ var this: *ModuleNamespace = JSCBase.GetJSPrivateData(ModuleNamespace, thisObject) orelse return false;
+
+ const len = js.JSStringGetLength(propertyName);
+ const properties = this.import_data.import.items;
+ var ptr = js.JSStringGetCharacters8Ptr(propertyName);
+ var property_slice = ptr[0..len];
+
+ for (properties) |property| {
+ if (strings.eql(property.alias, str)) return true;
+ }
+
+ return false;
+ }
+
+ pub fn getPropertyNames(
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ props: js.JSPropertyNameAccumulatorRef,
+ ) callconv(.C) void {
+ var this: *ModuleNamespace = JSCBase.GetJSPrivateData(ModuleNamespace, thisObject) orelse return;
+
+ const items = this.import_data.import.items;
+
+ for (items) |clause| {
+ const str = clause.alias;
+ js.JSPropertyNameAccumulatorAddName(props, js.JSStringCreateStatic(str.ptr, str.len));
+ }
+ }
+ };
+
pub const JSNodeList = std.ArrayListUnmanaged(JSNode);
pub fn NewJSXWriter(comptime P: type) type {
@@ -5666,6 +5695,29 @@ pub const Macro = struct {
return true;
},
+ Tag.inline_identifier => {
+ // id only accepts "to" and it must be a int32
+ self.args.ensureUnusedCapacity(2);
+ self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = comptime Tag.ids.get(Tag.inline_identifier) });
+
+ if (propertyValueNamed(props, "to")) |prop| {
+ switch (prop.data) {
+ .e_number, .e_if, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => {
+ self.args.appendAssumeCapacity(p.visitExpr(prop));
+ },
+ else => {
+ self.log.addError(self.p.source, prop.loc, "\"to\" prop must be a number", .{}) catch unreachable;
+ self.args.appendAssumeCapacity(prop);
+ },
+ }
+ } else {
+ self.log.addError(self.p.source, prop.loc, "\"to\" prop is required", .{}) catch unreachable;
+ self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = .{ .e_number = .{ .value = @floatToInt(f64, 0) } } });
+ }
+
+ return true;
+ },
+
Tag.s_import => {
const default_property_ = propertyValueNamed(props, "default");
const path_property = propertyValueNamed(props, "path") orelse {
@@ -5923,15 +5975,6 @@ pub const Macro = struct {
}
pub const SymbolMap = struct {
- // this uses an i32 here instead of a typical Ref
- // we only want the final return value's symbols to be added to the symbol map
- // the point that the symbol name is referenced may not be when it's added to the symbol map
- // this lets you do:
- map: std.AutoArrayHashMap(i32, Symbol) = undefined,
- allocator: *std.mem.Allocator,
- loaded: bool = false,
- loc: logger.Loc,
-
pub fn generateImportHash(name: string, path: string) i32 {
var hasher = std.hash.Wyhash.init(8);
hasher.update(path);
@@ -5939,30 +5982,6 @@ pub const Macro = struct {
hasher.update(name);
return @bitCast(i32, @truncate(u32, hasher.final()));
}
-
- pub fn putImport(self: *SymbolMap, import: *ImportData) void {
- // we use items here
- std.debug.assert(import.default_name == null);
-
- const count = @intCast(u32, @intCast(u32, import.import.items.len));
-
- if (!self.loaded) {
- self.loaded = true;
- self.map = std.AutoArrayHashMap(i32, Symbol).init(self);
- }
-
- self.map.ensureUnusedCapacity(count) catch unreachable;
-
- for (import.import.items) |clause| {
- self.map.putAssumeCapacity(
- generateImportHash(clause.alias, import.path),
- Symbol{
- .kind = Symbol.Kind.import,
- .original_name = clause.name,
- },
- );
- }
- }
};
pub const Writer = struct {
@@ -6235,14 +6254,24 @@ pub const Macro = struct {
if (!JSLexer.isIdentifier(alias)) throwTypeError(writer.ctx, "import alias must be an identifier", writer.exception);
- import.import.items[import_item_i] = ClauseItem{ .alias = alias, .name = name, .alias_loc = writer.loc };
+ import.import.items[import_item_i] = ClauseItem{
+ .alias = alias,
+ .original_name = name,
+ .name = .{ .loc = writer.loc, .ref = Ref.None },
+ .alias_loc = writer.loc,
+ };
import_item_i += 1;
}
}
if (has_default) {
- import.import.items[import_item_i] = ClauseItem{ .alias = import_default_name, .name = "default", .alias_loc = writer.loc };
+ import.import.items[import_item_i] = ClauseItem{
+ .alias = import_default_name,
+ .name = .{ .loc = writer.loc, .ref = Ref.None },
+ .original_name = name,
+ .alias_loc = writer.loc,
+ };
import_item_i += 1;
}
@@ -6426,6 +6455,11 @@ pub const Macro = struct {
return true;
},
+ .inline_identifier => {
+ const to = (writer.nextJSValue() orelse return false).toInt32();
+ expr.* = Expr{ .data = .{ .inline_identifier = to }, .loc = writer.loc };
+ return true;
+ },
else => {
return false;
@@ -6456,100 +6490,96 @@ pub const Macro = struct {
}
pub fn writeFromJS(writer: *Writer) ?JSNode {
- const out_node: JSNode = brk: {
- switch (TagOrJSNode.fromJSValueRef(writer, writer.ctx, (writer.eatArg() orelse return null).asRef())) {
- TagOrJSNode.tag => |tag| {
- if (tag == Tag.inline_inject) {
- const count: u32 = (writer.eatArg() orelse return false).toU32();
- var i: u32 = 0;
- while (i < count) : (i += 1) {
- const next_value = (writer.eatArg() orelse return null);
- const next_value_ref = next_value.asRef();
- if (js.JSValueIsArray(writer.ctx, next_value)) {
- const array = next_value;
- const array_len = JSC.JSValue.getLengthOfArray(next_value, JavaScript.VirtualMachine.vm.global);
-
- var array_i: u32 = 0;
- while (array_i < array_len) : (array_i += 1) {
- var current_value = JSC.JSObject.getIndex(array, JavaScript.VirtualMachine.vm.global, i);
-
- switch (TagOrJSNode.fromJSValueRef(writer, writer.ctx, current_value.asRef())) {
- .node => |node| {
- if (node.data != .s_import) {
- throwTypeError(writer.ctx, "inject must only contain imports", writer.exception);
- return null;
- }
- writer.inject.append(node);
- },
- .tag => |t| {
- if (!writer.writeFromJSWithTagInNode(t)) return null;
- },
- .invalid => {
+ switch (TagOrJSNode.fromJSValueRef(writer, writer.ctx, (writer.eatArg() orelse return null).asRef())) {
+ TagOrJSNode.tag => |tag| {
+ if (tag == Tag.inline_inject) {
+ const count: u32 = (writer.eatArg() orelse return false).toU32();
+ var i: u32 = 0;
+ while (i < count) : (i += 1) {
+ const next_value = (writer.eatArg() orelse return null);
+ const next_value_ref = next_value.asRef();
+ if (js.JSValueIsArray(writer.ctx, next_value)) {
+ const array = next_value;
+ const array_len = JSC.JSValue.getLengthOfArray(next_value, JavaScript.VirtualMachine.vm.global);
+
+ var array_i: u32 = 0;
+ while (array_i < array_len) : (array_i += 1) {
+ var current_value = JSC.JSObject.getIndex(array, JavaScript.VirtualMachine.vm.global, i);
+
+ switch (TagOrJSNode.fromJSValueRef(writer, writer.ctx, current_value.asRef())) {
+ .node => |node| {
+ if (node.data != .s_import) {
+ throwTypeError(writer.ctx, "inject must only contain imports", writer.exception);
return null;
- },
- }
+ }
+ writer.inject.append(node);
+ },
+ .tag => |t| {
+ if (!writer.writeFromJSWithTagInNode(t)) return null;
+ },
+ .invalid => {
+ return null;
+ },
}
- i += 1;
- continue;
}
+ i += 1;
+ continue;
}
- return JSNode{ .data = .{ .inline_inject = writer.inject.toOwnedSlice() }, .loc = writer.loc };
}
+ return JSNode{ .data = .{ .inline_inject = writer.inject.toOwnedSlice() }, .loc = writer.loc };
+ }
- if (tag == Tag.fragment) {
- const count: u32 = (writer.eatArg() orelse return null).toU32();
- // collapse single-item fragments
- switch (count) {
- 0 => {
- return JSNode{ .data = .{ .fragment = &[_]JSNode{} }, .loc = writer.loc };
- },
+ if (tag == Tag.fragment) {
+ const count: u32 = (writer.eatArg() orelse return null).toU32();
+ // collapse single-item fragments
+ switch (count) {
+ 0 => {
+ return JSNode{ .data = .{ .fragment = &[_]JSNode{} }, .loc = writer.loc };
+ },
- 1 => {
- var _node = writer.writeFromJS() orelse return null;
- while (true) {
- switch (_node.data) {
- .fragment => |fragment| {
- if (fragment.len == 1) {
- _node = fragment[0];
- continue;
- }
-
- break :brk _node;
- },
- else => {
- break :brk _node;
- },
- }
+ 1 => {
+ var _node = writer.writeFromJS() orelse return null;
+ while (true) {
+ switch (_node.data) {
+ .fragment => |fragment| {
+ if (fragment.len == 1) {
+ _node = fragment[0];
+ continue;
+ }
+
+ return _node;
+ },
+ else => {
+ return _node;
+ },
}
- },
- else => {},
- }
-
- var i: u32 = 0;
- var fragment = std.ArrayList(JSNode).initCapacity(writer.allocator, count) catch return null;
- while (i < count) : (i += 1) {
- const node = writer.writeFromJS() orelse return null;
- fragment.append(node) catch unreachable;
- }
+ }
+ },
+ else => {},
+ }
- break :brk JSNode{ .data = .{ .fragment = fragment.toOwnedSlice() }, .loc = writer.loc };
+ var i: u32 = 0;
+ var fragment = std.ArrayList(JSNode).initCapacity(writer.allocator, count) catch return null;
+ while (i < count) : (i += 1) {
+ const node = writer.writeFromJS() orelse return null;
+ fragment.append(node) catch unreachable;
}
- var expr: Expr = Expr{ .loc = writer.loc, .data = .{ .e_null = E.Null{} } };
+ return JSNode{ .data = .{ .fragment = fragment.toOwnedSlice() }, .loc = writer.loc };
+ }
- if (!writer.writeFromJSWithTagInExpr(tag, &expr)) return null;
- break :brk JSNode.initExpr(expr);
- },
- TagOrJSNode.node => |node| {
- break :brk node;
- },
- TagOrJSNode.invalid => {
- return null;
- },
- }
- };
+ var expr: Expr = Expr{ .loc = writer.loc, .data = .{ .e_null = E.Null{} } };
- if (writer.inject.items.len > 0) {}
+ if (!writer.writeFromJSWithTagInExpr(tag, &expr)) return null;
+ return JSNode.initExpr(expr);
+ },
+ TagOrJSNode.node => |node| {
+ return node;
+ },
+ TagOrJSNode.invalid => {
+ return null;
+ },
+ }
}
};
@@ -6817,6 +6847,8 @@ pub const Macro = struct {
args: []Expr,
source: *const logger.Source,
id: i32,
+ comptime Visitor: type,
+ visitor: Visitor,
) Expr {
if (comptime isDebug) Output.prettyln("<r><d>[macro]<r> call <d><b>{s}<r>", .{function_name});
@@ -6843,7 +6875,8 @@ pub const Macro = struct {
const value = promise.result(macro.vm.global.vm());
- if (JSCBase.GetJSPrivateData(JSNode, value.asObjectRef())) |node| {
+ if (JSCBase.GetJSPrivateData(JSNode, value.asObjectRef())) |*node| {
+ node.updateSymbolsMap(Visitor, visitor);
return node.toExpr();
} else {
return Expr{ .data = .{ .e_missing = .{} }, .loc = caller.loc };
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index af04478b8..162ffc0ef 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -22,6 +22,31 @@ const JSXFactoryName = "JSX";
const JSXAutomaticName = "jsx_module";
const MacroRefs = std.AutoArrayHashMap(Ref, u32);
+// If we are currently in a hoisted child of the module scope, relocate these
+// declarations to the top level and return an equivalent assignment statement.
+// Make sure to check that the declaration kind is "var" before calling this.
+// And make sure to check that the returned statement is not the zero value.
+//
+// This is done to make some transformations non-destructive
+// Without relocating vars to the top level, simplifying this:
+// if (false) var foo = 1;
+// to nothing is unsafe
+// Because "foo" was defined. And now it's not.
+pub const RelocateVars = struct {
+ pub const Mode = enum { normal, for_in_or_for_of };
+
+ stmt: ?Stmt = null,
+ ok: bool = false,
+};
+
+const VisitArgsOpts = struct {
+ body: []Stmt = &([_]Stmt{}),
+ has_rest_arg: bool = false,
+
+ // This is true if the function is an arrow function or a method
+ is_unique_formal_parameters: bool = false,
+};
+
const BunJSX = struct {
pub threadlocal var bun_jsx_identifier: E.Identifier = undefined;
};
@@ -65,6 +90,152 @@ pub fn locAfterOp(e: E.Binary) logger.Loc {
}
const ExportsStringName = "exports";
+const TransposeState = struct {
+ is_await_target: bool = false,
+ is_then_catch_target: bool = false,
+ loc: logger.Loc,
+};
+
+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
+ pub fn canFollowTypeArgumentsInExpression(token: js_lexer.T) bool {
+ switch (token) {
+ // These are the only tokens can legally follow a type argument list. So we
+ // definitely want to treat them as type arg lists.
+ .t_open_paren, // foo<x>(
+ .t_no_substitution_template_literal, // foo<T> `...`
+ // foo<T> `...${100}...`
+ .t_template_head,
+ => {
+ return true;
+ },
+ // These cases can't legally follow a type arg list. However, they're not
+ // legal expressions either. The user is probably in the middle of a
+ // generic type. So treat it as such.
+ .t_dot, // foo<x>.
+ .t_close_paren, // foo<x>)
+ .t_close_bracket, // foo<x>]
+ .t_colon, // foo<x>:
+ .t_semicolon, // foo<x>;
+ .t_question, // foo<x>?
+ .t_equals_equals, // foo<x> ==
+ .t_equals_equals_equals, // foo<x> ===
+ .t_exclamation_equals, // foo<x> !=
+ .t_exclamation_equals_equals, // foo<x> !==
+ .t_ampersand_ampersand, // foo<x> &&
+ .t_bar_bar, // foo<x> ||
+ .t_question_question, // foo<x> ??
+ .t_caret, // foo<x> ^
+ .t_ampersand, // foo<x> &
+ .t_bar, // foo<x> |
+ .t_close_brace, // foo<x> }
+ .t_end_of_file, // foo<x>
+ => {
+ return true;
+ },
+
+ // We don't want to treat these as type arguments. Otherwise we'll parse
+ // this as an invocation expression. Instead, we want to parse out the
+ // expression in isolation from the type arguments.
+ .t_comma, // foo<x>,
+ .t_open_brace, // foo<x> {
+ => {
+ return false;
+ },
+ else => {
+ // Anything else treat as an expression
+ return false;
+ },
+ }
+ }
+ pub const Identifier = struct {
+ pub const StmtIdentifier = enum {
+ s_type,
+
+ s_namespace,
+
+ s_abstract,
+
+ s_module,
+
+ s_interface,
+
+ s_declare,
+ };
+ pub fn forStr(str: string) ?StmtIdentifier {
+ switch (str.len) {
+ "type".len => {
+ return if (std.mem.readIntNative(u32, str[0..4]) == comptime std.mem.readIntNative(u32, "type")) .s_type else null;
+ },
+ "interface".len => {
+ if (strings.eqlComptime(str, "interface")) {
+ return .s_interface;
+ } else if (strings.eqlComptime(str, "namespace")) {
+ return .s_namespace;
+ } else {
+ return null;
+ }
+ },
+ "abstract".len => {
+ if (strings.eqlComptime(str, "abstract")) {
+ return .s_abstract;
+ } else {
+ return null;
+ }
+ },
+ "declare".len => {
+ if (strings.eqlComptime(str, "declare")) {
+ return .s_declare;
+ } else {
+ return null;
+ }
+ },
+ "module".len => {
+ if (strings.eqlComptime(str, "module")) {
+ return .s_module;
+ } else {
+ return null;
+ }
+ },
+ else => {
+ return null;
+ },
+ }
+ }
+ pub const IMap = std.ComptimeStringMap(Kind, .{
+ .{ "unique", .unique },
+ .{ "abstract", .abstract },
+ .{ "asserts", .asserts },
+ .{ "keyof", .prefix },
+ .{ "readonly", .prefix },
+ .{ "infer", .prefix },
+ .{ "any", .primitive },
+ .{ "never", .primitive },
+ .{ "unknown", .primitive },
+ .{ "undefined", .primitive },
+ .{ "object", .primitive },
+ .{ "number", .primitive },
+ .{ "string", .primitive },
+ .{ "boolean", .primitive },
+ .{ "bigint", .primitive },
+ .{ "symbol", .primitive },
+ });
+ pub const Kind = enum {
+ normal,
+ unique,
+ abstract,
+ asserts,
+ prefix,
+ primitive,
+ };
+ };
+
+ pub const SkipTypeOptions = struct {
+ is_return_type: bool = false,
+ };
+};
+
// We must prevent collisions from generated names.
// We want to avoid adding a pass over all the symbols in the file.
// To do that:
@@ -2700,6 +2871,20 @@ const FastRefresh = struct {};
const ImportItemForNamespaceMap = std.StringArrayHashMap(LocRef);
+pub const MacroState = struct {
+ refs: MacroRefs,
+ prepend_stmts: *List(Stmt) = undefined,
+ imports: std.AutoArrayHashMap(i32, Ref),
+
+ pub fn init(allocator: *std.mem.Allocator) MacroState {
+ return MacroState{
+ .refs = MacroRefs.init(allocator),
+ .prepend_stmts = undefined,
+ .imports = std.AutoArrayHashMap(i32, Ref).init(allocator),
+ };
+ }
+};
+
pub fn NewParser(
comptime js_parser_features: ParserFeatures,
) type {
@@ -2709,16 +2894,17 @@ pub fn NewParser(
const only_scan_imports_and_do_not_visit = js_parser_features.scan_only;
const is_react_fast_refresh_enabled = js_parser_features.react_fast_refresh;
- const ImportRecordList = if (only_scan_imports_and_do_not_visit) *std.ArrayList(ImportRecord) else std.ArrayList(ImportRecord);
- const NamedImportsType = if (only_scan_imports_and_do_not_visit) *js_ast.Ast.NamedImports else js_ast.Ast.NamedImports;
- const NeedsJSXType = if (only_scan_imports_and_do_not_visit) bool else void;
- const ParsePassSymbolUsageType = if (only_scan_imports_and_do_not_visit and is_typescript_enabled) *ScanPassResult.ParsePassSymbolUsageMap else void;
// P is for Parser!
// public only because of Binding.ToExpr
return struct {
+ const ImportRecordList = if (only_scan_imports_and_do_not_visit) *std.ArrayList(ImportRecord) else std.ArrayList(ImportRecord);
+ const NamedImportsType = if (only_scan_imports_and_do_not_visit) *js_ast.Ast.NamedImports else js_ast.Ast.NamedImports;
+ const NeedsJSXType = if (only_scan_imports_and_do_not_visit) bool else void;
+ const ParsePassSymbolUsageType = if (only_scan_imports_and_do_not_visit and is_typescript_enabled) *ScanPassResult.ParsePassSymbolUsageMap else void;
+
const P = @This();
pub const jsx_transform_type: JSXTransformType = js_parser_jsx;
- macro_refs: MacroRefs = undefined,
+ macro: MacroState = undefined,
allocator: *std.mem.Allocator,
options: Parser.Options,
log: *logger.Log,
@@ -2964,12 +3150,6 @@ pub fn NewParser(
scope_order_to_visit: []ScopeOrder = &([_]ScopeOrder{}),
- const TransposeState = struct {
- is_await_target: bool = false,
- is_then_catch_target: bool = false,
- loc: logger.Loc,
- };
-
pub fn transposeImport(p: *P, arg: Expr, state: anytype) Expr {
// The argument must be a string
if (@as(Expr.Tag, arg.data) == .e_string) {
@@ -4364,146 +4544,6 @@ pub fn NewParser(
return decorators.items;
}
- 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
- pub fn canFollowTypeArgumentsInExpression(p: *P) bool {
- switch (p.lexer.token) {
- // These are the only tokens can legally follow a type argument list. So we
- // definitely want to treat them as type arg lists.
- .t_open_paren, // foo<x>(
- .t_no_substitution_template_literal, // foo<T> `...`
- // foo<T> `...${100}...`
- .t_template_head,
- => {
- return true;
- },
- // These cases can't legally follow a type arg list. However, they're not
- // legal expressions either. The user is probably in the middle of a
- // generic type. So treat it as such.
- .t_dot, // foo<x>.
- .t_close_paren, // foo<x>)
- .t_close_bracket, // foo<x>]
- .t_colon, // foo<x>:
- .t_semicolon, // foo<x>;
- .t_question, // foo<x>?
- .t_equals_equals, // foo<x> ==
- .t_equals_equals_equals, // foo<x> ===
- .t_exclamation_equals, // foo<x> !=
- .t_exclamation_equals_equals, // foo<x> !==
- .t_ampersand_ampersand, // foo<x> &&
- .t_bar_bar, // foo<x> ||
- .t_question_question, // foo<x> ??
- .t_caret, // foo<x> ^
- .t_ampersand, // foo<x> &
- .t_bar, // foo<x> |
- .t_close_brace, // foo<x> }
- .t_end_of_file, // foo<x>
- => {
- return true;
- },
-
- // We don't want to treat these as type arguments. Otherwise we'll parse
- // this as an invocation expression. Instead, we want to parse out the
- // expression in isolation from the type arguments.
- .t_comma, // foo<x>,
- .t_open_brace, // foo<x> {
- => {
- return false;
- },
- else => {
- // Anything else treat as an expression
- return false;
- },
- }
- }
- pub const Identifier = struct {
- pub const StmtIdentifier = enum {
- s_type,
-
- s_namespace,
-
- s_abstract,
-
- s_module,
-
- s_interface,
-
- s_declare,
- };
- pub fn forStr(str: string) ?StmtIdentifier {
- switch (str.len) {
- "type".len => {
- return if (std.mem.readIntNative(u32, str[0..4]) == std.mem.readIntNative(u32, "type")) .s_type else null;
- },
- "interface".len => {
- if (strings.eqlComptime(str, "interface")) {
- return .s_interface;
- } else if (strings.eqlComptime(str, "namespace")) {
- return .s_namespace;
- } else {
- return null;
- }
- },
- "abstract".len => {
- if (strings.eqlComptime(str, "abstract")) {
- return .s_abstract;
- } else {
- return null;
- }
- },
- "declare".len => {
- if (strings.eqlComptime(str, "declare")) {
- return .s_declare;
- } else {
- return null;
- }
- },
- "module".len => {
- if (strings.eqlComptime(str, "module")) {
- return .s_module;
- } else {
- return null;
- }
- },
- else => {
- return null;
- },
- }
- }
- pub const IMap = std.ComptimeStringMap(Kind, .{
- .{ "unique", .unique },
- .{ "abstract", .abstract },
- .{ "asserts", .asserts },
- .{ "keyof", .prefix },
- .{ "readonly", .prefix },
- .{ "infer", .prefix },
- .{ "any", .primitive },
- .{ "never", .primitive },
- .{ "unknown", .primitive },
- .{ "undefined", .primitive },
- .{ "object", .primitive },
- .{ "number", .primitive },
- .{ "string", .primitive },
- .{ "boolean", .primitive },
- .{ "bigint", .primitive },
- .{ "symbol", .primitive },
- });
- pub const Kind = enum {
- normal,
- unique,
- abstract,
- asserts,
- prefix,
- primitive,
- };
- };
-
- pub const SkipTypeOptions = struct {
- is_return_type: bool = false,
- };
- };
-
fn skipTypeScriptType(p: *P, level: js_ast.Op.Level) anyerror!void {
try p.skipTypeScriptTypeWithOpts(level, .{});
}
@@ -6224,7 +6264,7 @@ pub fn NewParser(
}
if (is_macro) {
- try p.macro_refs.put(ref, stmt.import_record_index);
+ try p.macro.refs.put(ref, stmt.import_record_index);
}
}
@@ -6248,7 +6288,7 @@ pub fn NewParser(
}
if (is_macro) {
- try p.macro_refs.put(ref, stmt.import_record_index);
+ try p.macro.refs.put(ref, stmt.import_record_index);
}
}
}
@@ -8012,7 +8052,7 @@ pub fn NewParser(
_ = try p.skipTypeScriptTypeArguments(false);
// Check the token after this and backtrack if it's the wrong one
- if (!TypeScript.canFollowTypeArgumentsInExpression(p)) {
+ if (!TypeScript.canFollowTypeArgumentsInExpression(p.lexer.token)) {
// try p.lexer.unexpected(); return error.SyntaxError;
return error.Backtrack;
}
@@ -9557,6 +9597,35 @@ pub fn NewParser(
}
}
+ pub const MacroVisitor = struct {
+ p: *P,
+
+ loc: logger.Loc,
+
+ pub fn visitImport(this: MacroVisitor, import_data: js_ast.Macro.JSNode.ImportData) void {
+ var p = this.p;
+
+ const record = p.addImportRecord(.import, import_data.loc, import_data.path);
+ p.macro.imports.ensureUnusedCapacity(import_data.import.items) catch unreachable;
+
+ var import = import_data.import;
+
+ p.is_import_item.ensureCapacity(
+ @intCast(u32, p.is_import_item.count() + import.items.len),
+ ) catch unreachable;
+
+ for (import.items) |*clause| {
+ const name_ref = p.declareSymbol(.import, this.loc, clause.alias) catch unreachable;
+ clause.name = LocRef{ .loc = this.loc, .ref = name_ref };
+
+ p.macro.imports.putAssumeCapacity(js_ast.Macro.JSNode.SymbolMap.generateImportHash(clause.alias, import_data.path), name_ref);
+ p.is_import_item.put(name_ref, true) catch unreachable;
+ }
+
+ p.macro.prepend_stmts.append(p.s(import, this.loc)) catch unreachable;
+ }
+ };
+
pub fn panic(p: *P, comptime str: string, args: anytype) noreturn {
@setCold(true);
var panic_buffer = p.allocator.alloc(u8, 32 * 1024) catch unreachable;
@@ -11186,10 +11255,11 @@ pub fn NewParser(
if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) {
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| {
+ if (p.macro.refs.get(ref)) |import_record_id| {
const name = p.symbols.items[ref.inner_index].original_name;
const record = &p.import_records.items[import_record_id];
- return p.options.macro_context.call(
+ // We must visit it to convert inline_identifiers and record usage
+ return p.visitExpr(p.options.macro_context.call(
record.path.text,
p.source.path.sourceDir(),
p.log,
@@ -11198,13 +11268,31 @@ pub fn NewParser(
expr,
&.{},
name,
- ) catch return expr;
+ MacroVisitor,
+ MacroVisitor{ .p = p, .loc = expr.loc },
+ ) catch return expr);
}
}
}
}
},
+ .inline_identifier => |id| {
+ const ref = p.macro.imports.get(id) orelse {
+ p.panic("Internal error: missing identifier from macro: {d}", .{id});
+ };
+
+ const ident = E.ImportIdentifier{
+ .was_originally_identifier = false,
+ };
+
+ if (!p.is_control_flow_dead) {
+ p.recordUsage(ref);
+ }
+
+ return p.e(ident, expr.loc);
+ },
+
.e_binary => |e_| {
switch (e_.left.data) {
// Special-case private identifiers
@@ -12050,7 +12138,7 @@ pub fn NewParser(
const is_macro_ref: bool = if (comptime FeatureFlags.is_macro_enabled and
jsx_transform_type != .macro)
- e_.target.data == .e_import_identifier and p.macro_refs.contains(e_.target.data.e_import_identifier.ref)
+ e_.target.data == .e_import_identifier and p.macro.refs.contains(e_.target.data.e_import_identifier.ref)
else
false;
@@ -12081,20 +12169,24 @@ pub fn NewParser(
if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) {
if (is_macro_ref) {
const ref = e_.target.data.e_import_identifier.ref;
- const import_record_id = p.macro_refs.get(ref).?;
+ const import_record_id = p.macro.refs.get(ref).?;
const name = p.symbols.items[ref.inner_index].original_name;
const record = &p.import_records.items[import_record_id];
const copied = Expr{ .loc = expr.loc, .data = .{ .e_call = e_ } };
- return p.options.macro_context.call(
- record.path.text,
- p.source.path.sourceDir(),
- p.log,
- p.source,
- record.range,
- copied,
- &.{},
- name,
- ) catch return expr;
+ return p.visitExpr(
+ p.options.macro_context.call(
+ record.path.text,
+ p.source.path.sourceDir(),
+ p.log,
+ p.source,
+ record.range,
+ copied,
+ &.{},
+ name,
+ MacroVisitor,
+ MacroVisitor{ .p = p, .loc = expr.loc },
+ ) catch return expr,
+ );
}
}
@@ -12159,14 +12251,6 @@ pub fn NewParser(
return expr;
}
- const VisitArgsOpts = struct {
- body: []Stmt = &([_]Stmt{}),
- has_rest_arg: bool = false,
-
- // This is true if the function is an arrow function or a method
- is_unique_formal_parameters: bool = false,
- };
-
fn visitArgs(p: *P, args: []G.Arg, opts: VisitArgsOpts) void {
const strict_loc = fnBodyContainsUseStrict(opts.body);
const has_simple_args = isSimpleParameterList(args, opts.has_rest_arg);
@@ -12449,23 +12533,6 @@ pub fn NewParser(
return p.jsxStringsToMemberExpression(loc, if (is_static and !p.options.jsx.development) p.jsxs_runtime.ref else p.jsx_runtime.ref);
}
- // If we are currently in a hoisted child of the module scope, relocate these
- // declarations to the top level and return an equivalent assignment statement.
- // Make sure to check that the declaration kind is "var" before calling this.
- // And make sure to check that the returned statement is not the zero value.
- //
- // This is done to make some transformations non-destructive
- // Without relocating vars to the top level, simplifying this:
- // if (false) var foo = 1;
- // to nothing is unsafe
- // Because "foo" was defined. And now it's not.
- pub const RelocateVars = struct {
- pub const Mode = enum { normal, for_in_or_for_of };
-
- stmt: ?Stmt = null,
- ok: bool = false,
- };
-
fn maybeRelocateVarsToTopLevel(p: *P, decls: []G.Decl, mode: RelocateVars.Mode) RelocateVars {
// Only do this when the scope is not already top-level and when we're not inside a function.
if (p.current_scope == p.module_scope) {
@@ -14125,6 +14192,11 @@ pub fn NewParser(
var visited = try List(Stmt).initCapacity(p.allocator, stmts.items.len);
var before = List(Stmt).init(p.allocator);
var after = List(Stmt).init(p.allocator);
+
+ if (p.current_scope == p.module_scope) {
+ p.macro.prepend_stmts = &before;
+ }
+
defer before.deinit();
defer visited.deinit();
defer after.deinit();
@@ -15322,6 +15394,7 @@ pub fn NewParser(
.require_transposer = undefined,
.require_resolve_transposer = undefined,
.source = source,
+ .macro = MacroState.init(allocator),
.needs_jsx_import = if (comptime only_scan_imports_and_do_not_visit) false else NeedsJSXType{},
.lexer = lexer,