mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
[bun.js] Auto type coerction for macros!
This commit is contained in:
347
src/js_ast.zig
347
src/js_ast.zig
@@ -18,9 +18,10 @@ const ObjectPool = @import("./pool.zig").ObjectPool;
|
||||
const ImportRecord = @import("import_record.zig").ImportRecord;
|
||||
const allocators = @import("allocators.zig");
|
||||
const JSC = @import("javascript_core");
|
||||
|
||||
const HTTP = @import("http");
|
||||
const RefCtx = @import("./ast/base.zig").RefCtx;
|
||||
const _hash_map = @import("hash_map.zig");
|
||||
const JSONParser = @import("./json_parser.zig");
|
||||
const StringHashMap = _hash_map.StringHashMap;
|
||||
const AutoHashMap = _hash_map.AutoHashMap;
|
||||
const StringHashMapUnmanaged = _hash_map.StringHashMapUnmanaged;
|
||||
@@ -990,6 +991,7 @@ pub const E = struct {
|
||||
comma_after_spread: ?logger.Loc = null,
|
||||
is_single_line: bool = false,
|
||||
is_parenthesized: bool = false,
|
||||
was_originally_macro: bool = false,
|
||||
|
||||
pub fn push(this: *Array, allocator: std.mem.Allocator, item: Expr) !void {
|
||||
try this.items.push(allocator, item);
|
||||
@@ -1289,6 +1291,7 @@ pub const E = struct {
|
||||
comma_after_spread: ?logger.Loc = null,
|
||||
is_single_line: bool = false,
|
||||
is_parenthesized: bool = false,
|
||||
was_originally_macro: bool = false,
|
||||
|
||||
pub const Rope = struct {
|
||||
head: Expr,
|
||||
@@ -2115,11 +2118,11 @@ pub const Expr = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toEmpty(expr: *Expr) Expr {
|
||||
pub fn toEmpty(expr: Expr) Expr {
|
||||
return Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = expr.loc };
|
||||
}
|
||||
pub fn isEmpty(expr: *Expr) bool {
|
||||
return std.meta.activeTag(expr.data) == .e_missing;
|
||||
pub fn isEmpty(expr: Expr) bool {
|
||||
return expr.data == .e_missing;
|
||||
}
|
||||
pub const Query = struct { expr: Expr, loc: logger.Loc, i: u32 = 0 };
|
||||
|
||||
@@ -3222,11 +3225,11 @@ pub const Expr = struct {
|
||||
};
|
||||
}
|
||||
|
||||
// The given "expr" argument should be the operand of a "!" prefix operator
|
||||
// (i.e. the "x" in "!x"). This returns a simplified expression for the
|
||||
// whole operator (i.e. the "!x") if it can be simplified, or false if not.
|
||||
// It's separate from "Not()" above to avoid allocation on failure in case
|
||||
// that is undesired.
|
||||
/// The given "expr" argument should be the operand of a "!" prefix operator
|
||||
/// (i.e. the "x" in "!x"). This returns a simplified expression for the
|
||||
/// whole operator (i.e. the "!x") if it can be simplified, or false if not.
|
||||
/// It's separate from "Not()" above to avoid allocation on failure in case
|
||||
/// that is undesired.
|
||||
pub fn maybeSimplifyNot(expr: *Expr, allocator: std.mem.Allocator) ?Expr {
|
||||
switch (expr.data) {
|
||||
.e_null, .e_undefined => {
|
||||
@@ -3412,7 +3415,7 @@ pub const Expr = struct {
|
||||
.bin_nullish_coalescing => {
|
||||
const left = binary.left.data.knownPrimitive();
|
||||
const right = binary.right.data.knownPrimitive();
|
||||
if (left == .@"null" or right == .@"undefined")
|
||||
if (left == .@"null" or left == .@"undefined")
|
||||
break :brk right;
|
||||
|
||||
if (left != .unknown) {
|
||||
@@ -4664,6 +4667,8 @@ pub const Macro = struct {
|
||||
pub const JSNode = struct {
|
||||
loc: logger.Loc,
|
||||
data: Data,
|
||||
visited: bool = false,
|
||||
|
||||
pub const Class = JSCBase.NewClass(
|
||||
JSNode,
|
||||
.{
|
||||
@@ -7475,19 +7480,294 @@ 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;
|
||||
threadlocal var expr_nodes_buf: [1]JSNode = undefined;
|
||||
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;
|
||||
}
|
||||
|
||||
return Expr.init(E.Undefined, E.Undefined{}, this.caller.loc);
|
||||
} else if (value.isNull()) {
|
||||
return Expr.init(E.Null, E.Null{}, this.caller.loc);
|
||||
}
|
||||
|
||||
this.is_top_level = false;
|
||||
|
||||
if (value.isError() or value.isAggregateError(global) or value.isException(global.vm())) {
|
||||
this.macro.vm.defaultErrorHandler(value, null);
|
||||
return error.MacroFailed;
|
||||
}
|
||||
|
||||
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(JSNode, value.asObjectRef())) |node| {
|
||||
_entry.value_ptr.* = node.toExpr();
|
||||
node.visited = true;
|
||||
node.updateSymbolsMap(Visitor, visitor);
|
||||
return _entry.value_ptr.*;
|
||||
}
|
||||
|
||||
if (JSCBase.GetJSPrivateData(JSC.BuildError, value.asObjectRef()) != null) {
|
||||
this.macro.vm.defaultErrorHandler(value, null);
|
||||
return error.MacroFailed;
|
||||
}
|
||||
|
||||
if (JSCBase.GetJSPrivateData(JSC.ResolveError, 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 (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 out_expr;
|
||||
}
|
||||
|
||||
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{ .utf8 = zig_string.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);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.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.*;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const out = Expr.init(
|
||||
E.Array,
|
||||
E.Array{
|
||||
.items = ExprNodeList.init(array[0..i]),
|
||||
.was_originally_macro = true,
|
||||
},
|
||||
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,
|
||||
},
|
||||
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.*;
|
||||
}
|
||||
|
||||
var promise = JSC.JSPromise.resolvedPromise(global, value);
|
||||
while (promise.status(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 => {},
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
macro: Macro,
|
||||
_: *logger.Log,
|
||||
_: std.mem.Allocator,
|
||||
log: *logger.Log,
|
||||
allocator: std.mem.Allocator,
|
||||
function_name: string,
|
||||
caller: Expr,
|
||||
args: []Expr,
|
||||
_: *const logger.Source,
|
||||
source: *const logger.Source,
|
||||
id: i32,
|
||||
comptime Visitor: type,
|
||||
visitor: Visitor,
|
||||
@@ -7505,33 +7785,24 @@ pub const Macro = struct {
|
||||
|
||||
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);
|
||||
js.JSValueProtect(macro.vm.global.ref(), result.asRef());
|
||||
defer js.JSValueUnprotect(macro.vm.global.ref(), result.asRef());
|
||||
var promise = JSC.JSPromise.resolvedPromise(macro.vm.global, result);
|
||||
_ = macro.vm.tick();
|
||||
|
||||
while (promise.status(macro.vm.global.vm()) == .Pending) {
|
||||
macro.vm.tick();
|
||||
}
|
||||
var runner = Runner{
|
||||
.caller = caller,
|
||||
.function_name = function_name,
|
||||
.macro = ¯o,
|
||||
.allocator = allocator,
|
||||
.id = id,
|
||||
.log = log,
|
||||
.source = source,
|
||||
};
|
||||
defer runner.visited.deinit(allocator);
|
||||
|
||||
if (promise.status(macro.vm.global.vm()) == .Rejected) {
|
||||
macro.vm.defaultErrorHandler(promise.result(macro.vm.global.vm()), null);
|
||||
return error.MacroFailed;
|
||||
}
|
||||
|
||||
const value = promise.result(macro.vm.global.vm());
|
||||
|
||||
if (value.isError() or value.isAggregateError(macro.vm.global) or value.isException(macro.vm.global.vm())) {
|
||||
macro.vm.defaultErrorHandler(value, null);
|
||||
return error.MacroFailed;
|
||||
}
|
||||
|
||||
if (JSCBase.GetJSPrivateData(JSNode, value.asObjectRef())) |node| {
|
||||
node.updateSymbolsMap(Visitor, visitor);
|
||||
return node.toExpr();
|
||||
} else {
|
||||
return Expr{ .data = .{ .e_missing = .{} }, .loc = caller.loc };
|
||||
}
|
||||
return try runner.coerce(
|
||||
result,
|
||||
macro.vm.global,
|
||||
Visitor,
|
||||
visitor,
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1051,20 +1051,102 @@ pub const SideEffects = enum(u1) {
|
||||
},
|
||||
|
||||
.bin_logical_and, .bin_logical_or, .bin_nullish_coalescing => {
|
||||
bin.right = simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty();
|
||||
// Preserve short-circuit behavior: the left expression is only unused if
|
||||
// the right expression can be completely removed. Otherwise, the left
|
||||
// expression is important for the branch.
|
||||
if (simpifyUnusedExpr(p, bin.right)) |right| {
|
||||
bin.right = right;
|
||||
} else {
|
||||
|
||||
if (bin.right.isEmpty())
|
||||
return simpifyUnusedExpr(p, bin.left);
|
||||
}
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
|
||||
.e_object => {
|
||||
// Arrays with "..." spread expressions can't be unwrapped because the
|
||||
// "..." triggers code evaluation via iterators. In that case, just trim
|
||||
// the other items instead and leave the array expression there.
|
||||
|
||||
var properties_slice = expr.data.e_object.properties.slice();
|
||||
var end: usize = 0;
|
||||
var any_computed = false;
|
||||
for (properties_slice) |spread| {
|
||||
end = 0;
|
||||
any_computed = any_computed or spread.flags.is_computed;
|
||||
if (spread.kind == .spread) {
|
||||
// Spread properties must always be evaluated
|
||||
for (properties_slice) |prop_| {
|
||||
var prop = prop_;
|
||||
if (prop_.kind != .spread) {
|
||||
if (prop.value != null) {
|
||||
if (simpifyUnusedExpr(p, prop.value.?)) |value| {
|
||||
prop.value = value;
|
||||
} else if (!prop.flags.is_computed) {
|
||||
continue;
|
||||
} else {
|
||||
prop.value = p.e(E.Number{ .value = 0.0 }, prop.value.?.loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
properties_slice[end] = prop_;
|
||||
end += 1;
|
||||
}
|
||||
|
||||
properties_slice = properties_slice[0..end];
|
||||
expr.data.e_object.properties = G.Property.List.init(properties_slice);
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
if (any_computed) {
|
||||
// Otherwise, the object can be completely removed. We only need to keep any
|
||||
// object properties with side effects. Apply this simplification recursively.
|
||||
// for (properties_slice) |prop| {
|
||||
// if (prop.flags.is_computed) {
|
||||
// // Make sure "ToString" is still evaluated on the key
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// keep this for now because we need better test coverage to do this correctly
|
||||
return expr;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
.e_array => {
|
||||
var items = expr.data.e_array.items.slice();
|
||||
|
||||
for (items) |item| {
|
||||
if (item.data == .e_spread) {
|
||||
var end: usize = 0;
|
||||
for (items) |item__| {
|
||||
var item_ = item__;
|
||||
if (item_.data != .e_missing) {
|
||||
items[end] = item_;
|
||||
end += 1;
|
||||
}
|
||||
|
||||
expr.data.e_array.items = ExprNodeList.init(items[0..end]);
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, the array can be completely removed. We only need to keep any
|
||||
// array items with side effects. Apply this simplification recursively.
|
||||
return Expr.joinAllWithCommaCallback(
|
||||
items,
|
||||
@TypeOf(p),
|
||||
p,
|
||||
simpifyUnusedExpr,
|
||||
p.allocator,
|
||||
);
|
||||
},
|
||||
|
||||
.e_new => |call| {
|
||||
// A constructor call that has been marked "__PURE__" can be removed if all arguments
|
||||
// can be removed. The annotation causes us to ignore the target.
|
||||
@@ -2909,6 +2991,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,
|
||||
|
||||
/// Used for transforming export default -> module.exports
|
||||
has_export_default: bool = false,
|
||||
@@ -11417,6 +11500,7 @@ pub fn NewParser(
|
||||
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;
|
||||
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(
|
||||
@@ -11429,7 +11513,10 @@ pub fn NewParser(
|
||||
&.{},
|
||||
name,
|
||||
MacroVisitor,
|
||||
MacroVisitor{ .p = p, .loc = expr.loc },
|
||||
MacroVisitor{
|
||||
.p = p,
|
||||
.loc = expr.loc,
|
||||
},
|
||||
) catch return expr);
|
||||
|
||||
if (macro_result.data != .e_template) {
|
||||
@@ -11694,7 +11781,7 @@ pub fn NewParser(
|
||||
// "(null ?? this.fn)" => "this.fn"
|
||||
// "(null ?? this.fn)()" => "(0, this.fn)()"
|
||||
if (is_call_target and e_.right.hasValueForThisInCall()) {
|
||||
return Expr.joinWithComma(Expr{ .data = Prefill.Data.Zero, .loc = e_.left.loc }, e_.right, p.allocator);
|
||||
return Expr.joinWithComma(Expr{ .data = .{ .e_number = .{ .value = 0.0 } }, .loc = e_.left.loc }, e_.right, p.allocator);
|
||||
}
|
||||
|
||||
return e_.right;
|
||||
@@ -11993,10 +12080,9 @@ pub fn NewParser(
|
||||
return p.e(E.Boolean{ .value = !side_effects.value }, expr.loc);
|
||||
}
|
||||
|
||||
// maybe won't do this idk
|
||||
// if (Expr.maybeSimplifyNot(&e_.value, p.allocator)) |exp| {
|
||||
// return exp;
|
||||
// }
|
||||
if (e_.value.maybeSimplifyNot(p.allocator)) |exp| {
|
||||
return exp;
|
||||
}
|
||||
},
|
||||
.un_void => {
|
||||
if (p.exprCanBeRemovedIfUnused(&e_.value)) {
|
||||
@@ -12088,6 +12174,16 @@ pub fn NewParser(
|
||||
)) |_expr| {
|
||||
return _expr;
|
||||
}
|
||||
|
||||
if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) {
|
||||
if (p.has_called_macro) {
|
||||
if (e_.target.data == .e_object and e_.target.data.e_object.was_originally_macro) {
|
||||
if (e_.target.get(e_.name)) |obj| {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.e_if => |e_| {
|
||||
@@ -12342,6 +12438,8 @@ pub fn NewParser(
|
||||
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_ } };
|
||||
const start_error_count = p.log.msgs.items.len;
|
||||
p.has_called_macro = true;
|
||||
const macro_result =
|
||||
p.options.macro_context.call(
|
||||
record.path.text,
|
||||
@@ -12356,7 +12454,9 @@ pub fn NewParser(
|
||||
MacroVisitor{ .p = p, .loc = expr.loc },
|
||||
) catch |err| {
|
||||
if (err == error.MacroFailed) {
|
||||
p.log.addError(p.source, expr.loc, "macro threw exception") catch unreachable;
|
||||
if (p.log.msgs.items.len == start_error_count) {
|
||||
p.log.addError(p.source, expr.loc, "macro threw exception") catch unreachable;
|
||||
}
|
||||
} else {
|
||||
p.log.addErrorFmt(p.source, expr.loc, p.allocator, "{s} error in macro", .{@errorName(err)}) catch unreachable;
|
||||
}
|
||||
@@ -12862,6 +12962,24 @@ pub fn NewParser(
|
||||
return .{ .stmt = p.s(S.SExpr{ .value = value }, value.loc), .ok = true };
|
||||
}
|
||||
|
||||
// fn maybeInlineMacroObject(p: *P, decl: *G.Decl, macro: Expr) void {
|
||||
// if (decl.value == null) return;
|
||||
// switch (decl.binding.data) {
|
||||
// .b_identifier => |ident| {
|
||||
// if (macro.get(p.loadNameFromRef(ident.ref))) |val| {
|
||||
// decl
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// 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)
|
||||
// {
|
||||
// p.maybeInlineMacroObject(&data.decls[i], data.decls[i].value.?);
|
||||
// }
|
||||
// }
|
||||
|
||||
// EDot nodes represent a property access. This function may return an
|
||||
// expression to replace the property access with. It assumes that the
|
||||
// target of the EDot expression has already been visited.
|
||||
@@ -13232,6 +13350,74 @@ pub fn NewParser(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 => {},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user