aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/js_ast.zig347
-rw-r--r--src/js_parser/js_parser.zig208
2 files changed, 506 insertions, 49 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig
index 35bc67c13..88bf444f0 100644
--- a/src/js_ast.zig
+++ b/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();
- }
-
- 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;
- }
+ var runner = Runner{
+ .caller = caller,
+ .function_name = function_name,
+ .macro = &macro,
+ .allocator = allocator,
+ .id = id,
+ .log = log,
+ .source = source,
+ };
+ defer runner.visited.deinit(allocator);
- 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,
+ );
}
};
};
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index e525c4330..e5add04bf 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -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 => {},
}
}