mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Compare commits
2 Commits
claude/fix
...
jarred/sim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e375b9d2e1 | ||
|
|
426a4aa870 |
@@ -108,6 +108,11 @@ pub const Binary = struct {
|
||||
|
||||
pub const Boolean = struct {
|
||||
value: bool,
|
||||
|
||||
pub fn eql(a: Boolean, b: Boolean) bool {
|
||||
return a.value == b.value;
|
||||
}
|
||||
|
||||
pub fn toJS(this: @This(), ctx: *jsc.JSGlobalObject) jsc.C.JSValueRef {
|
||||
return jsc.C.JSValueMakeBoolean(ctx, this.value);
|
||||
}
|
||||
@@ -135,6 +140,10 @@ pub const ImportMetaMain = struct {
|
||||
/// instead of wrapping in a unary not. This way, the printer can easily
|
||||
/// print `require.main != module` instead of `!(require.main == module)`
|
||||
inverted: bool = false,
|
||||
|
||||
pub fn eql(a: ImportMetaMain, b: ImportMetaMain) bool {
|
||||
return a.inverted == b.inverted;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Special = union(enum) {
|
||||
@@ -153,6 +162,18 @@ pub const Special = union(enum) {
|
||||
hot_accept_visited,
|
||||
/// Prints the resolved specifier string for an import record.
|
||||
resolved_specifier_string: ImportRecord.Index,
|
||||
|
||||
pub fn eql(a: *const Special, b: *const Special) bool {
|
||||
return switch (a.*) {
|
||||
.module_exports => b.* == .module_exports,
|
||||
.hot_enabled => b.* == .hot_enabled,
|
||||
.hot_disabled => b.* == .hot_disabled,
|
||||
.hot_data => b.* == .hot_data,
|
||||
.hot_accept => b.* == .hot_accept,
|
||||
.hot_accept_visited => b.* == .hot_accept_visited,
|
||||
.resolved_specifier_string => b.* == .resolved_specifier_string and a.resolved_specifier_string == b.resolved_specifier_string,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Call = struct {
|
||||
@@ -260,6 +281,13 @@ pub const Identifier = struct {
|
||||
// the call target but keeping any arguments with side effects.
|
||||
call_can_be_unwrapped_if_unused: bool = false,
|
||||
|
||||
pub fn eql(a: *const Identifier, b: *const Identifier) bool {
|
||||
return a.ref.eql(b.ref) and
|
||||
a.must_keep_due_to_with_stmt == b.must_keep_due_to_with_stmt and
|
||||
a.can_be_removed_if_unused == b.can_be_removed_if_unused and
|
||||
a.call_can_be_unwrapped_if_unused == b.call_can_be_unwrapped_if_unused;
|
||||
}
|
||||
|
||||
pub inline fn init(ref: Ref) Identifier {
|
||||
return Identifier{
|
||||
.ref = ref,
|
||||
@@ -296,6 +324,11 @@ pub const ImportIdentifier = struct {
|
||||
/// false, this could potentially have been a member access expression such
|
||||
/// as "ns.foo" off of an imported namespace object.
|
||||
was_originally_identifier: bool = false,
|
||||
|
||||
pub fn eql(a: ImportIdentifier, b: ImportIdentifier) bool {
|
||||
return a.ref.eql(b.ref) and
|
||||
a.was_originally_identifier == b.was_originally_identifier;
|
||||
}
|
||||
};
|
||||
|
||||
/// This is a dot expression on exports, such as `exports.<ref>`. It is given
|
||||
@@ -315,6 +348,11 @@ pub const CommonJSExportIdentifier = struct {
|
||||
exports,
|
||||
module_dot_exports,
|
||||
};
|
||||
|
||||
pub fn eql(a: *const CommonJSExportIdentifier, b: *const CommonJSExportIdentifier) bool {
|
||||
return a.ref.eql(b.ref) and
|
||||
a.base == b.base;
|
||||
}
|
||||
};
|
||||
|
||||
// This is similar to EIdentifier but it represents class-private fields and
|
||||
@@ -322,6 +360,10 @@ pub const CommonJSExportIdentifier = struct {
|
||||
// EIndex and Property.
|
||||
pub const PrivateIdentifier = struct {
|
||||
ref: Ref,
|
||||
|
||||
pub fn eql(a: PrivateIdentifier, b: PrivateIdentifier) bool {
|
||||
return a.ref.eql(b.ref);
|
||||
}
|
||||
};
|
||||
|
||||
/// In development mode, the new JSX transform has a few special props
|
||||
@@ -404,6 +446,10 @@ pub const Number = struct {
|
||||
return toStringFromF64(this.value, allocator);
|
||||
}
|
||||
|
||||
pub fn eql(a: Number, b: Number) bool {
|
||||
return a.value == b.value;
|
||||
}
|
||||
|
||||
pub fn toStringFromF64(value: f64, allocator: std.mem.Allocator) ?string {
|
||||
if (value == @trunc(value) and (value < std.math.maxInt(i32) and value > std.math.minInt(i32))) {
|
||||
const int_value = @as(i64, @intFromFloat(value));
|
||||
@@ -476,6 +522,10 @@ pub const BigInt = struct {
|
||||
|
||||
pub var empty = BigInt{ .value = "" };
|
||||
|
||||
pub fn eql(a: BigInt, b: BigInt) bool {
|
||||
return std.mem.eql(u8, a.value, b.value);
|
||||
}
|
||||
|
||||
pub fn jsonStringify(self: *const @This(), writer: anytype) !void {
|
||||
return try writer.write(self.value);
|
||||
}
|
||||
@@ -1356,12 +1406,21 @@ pub const RequireString = struct {
|
||||
import_record_index: u32 = 0,
|
||||
|
||||
unwrapped_id: u32 = std.math.maxInt(u32),
|
||||
|
||||
pub fn eql(a: RequireString, b: RequireString) bool {
|
||||
return a.import_record_index == b.import_record_index and
|
||||
a.unwrapped_id == b.unwrapped_id;
|
||||
}
|
||||
};
|
||||
|
||||
pub const RequireResolveString = struct {
|
||||
import_record_index: u32,
|
||||
|
||||
// close_paren_loc: logger.Loc = logger.Loc.Empty,
|
||||
|
||||
pub fn eql(a: RequireResolveString, b: RequireResolveString) bool {
|
||||
return a.import_record_index == b.import_record_index;
|
||||
}
|
||||
};
|
||||
|
||||
pub const InlinedEnum = struct {
|
||||
|
||||
111
src/ast/Expr.zig
111
src/ast/Expr.zig
@@ -605,7 +605,7 @@ pub fn joinAllWithComma(all: []Expr, allocator: std.mem.Allocator) Expr {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn joinAllWithCommaCallback(all: []Expr, comptime Context: type, ctx: Context, comptime callback: (fn (ctx: anytype, expr: Expr) ?Expr), allocator: std.mem.Allocator) ?Expr {
|
||||
pub fn joinAllWithCommaCallback(all: []Expr, comptime Context: type, ctx: Context, comptime callback: (fn (ctx: Context, expr: Expr) ?Expr), allocator: std.mem.Allocator) ?Expr {
|
||||
switch (all.len) {
|
||||
0 => return null,
|
||||
1 => {
|
||||
@@ -2058,6 +2058,53 @@ pub inline fn knownPrimitive(self: @This()) PrimitiveType {
|
||||
return self.data.knownPrimitive();
|
||||
}
|
||||
|
||||
/// Try to insert an optional chain operator to optimize expressions like:
|
||||
/// "a != null && a.b()" => "a?.b()"
|
||||
/// "a == null || a.b()" => "a?.b()"
|
||||
pub fn tryToInsertOptionalChain(check_expr: Expr, expr: *Expr) bool {
|
||||
switch (expr.data) {
|
||||
.e_dot => |*dot| {
|
||||
if (check_expr.data.eqlPtr(&dot.target.data)) {
|
||||
dot.optional_chain = .start;
|
||||
return true;
|
||||
}
|
||||
if (tryToInsertOptionalChain(check_expr, &dot.target)) {
|
||||
if (dot.optional_chain == null) {
|
||||
dot.optional_chain = .cont;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
.e_index => |*index| {
|
||||
if (check_expr.data.eqlPtr(&index.target.data)) {
|
||||
index.optional_chain = .start;
|
||||
return true;
|
||||
}
|
||||
if (tryToInsertOptionalChain(check_expr, &index.target)) {
|
||||
if (index.optional_chain == null) {
|
||||
index.optional_chain = .cont;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
.e_call => |*call| {
|
||||
if (check_expr.data.eqlPtr(&call.target.data)) {
|
||||
call.optional_chain = .start;
|
||||
return true;
|
||||
}
|
||||
if (tryToInsertOptionalChain(check_expr, &call.target)) {
|
||||
if (call.optional_chain == null) {
|
||||
call.optional_chain = .cont;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub const PrimitiveType = enum {
|
||||
unknown,
|
||||
mixed,
|
||||
@@ -2803,8 +2850,8 @@ pub const Data = union(Tag) {
|
||||
return @as(Expr.Tag, data).typeof();
|
||||
}
|
||||
|
||||
pub fn toNumber(data: Expr.Data) ?f64 {
|
||||
return switch (data) {
|
||||
pub fn toNumber(data: *const Expr.Data) ?f64 {
|
||||
return switch (data.*) {
|
||||
.e_null => 0,
|
||||
.e_undefined => std.math.nan(f64),
|
||||
.e_string => |str| {
|
||||
@@ -2831,8 +2878,8 @@ pub const Data = union(Tag) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toFiniteNumber(data: Expr.Data) ?f64 {
|
||||
return switch (data) {
|
||||
pub fn toFiniteNumber(data: *const Expr.Data) ?f64 {
|
||||
return switch (data.*) {
|
||||
.e_boolean => @as(f64, if (data.e_boolean.value) 1.0 else 0.0),
|
||||
.e_number => if (std.math.isFinite(data.e_number.value))
|
||||
data.e_number.value
|
||||
@@ -2877,6 +2924,60 @@ pub const Data = union(Tag) {
|
||||
pub const unknown = Equality{ .ok = false };
|
||||
};
|
||||
|
||||
pub fn eqlPtr(lhs: *const Expr.Data, rhs: *const Expr.Data) bool {
|
||||
if (@as(Expr.Tag, lhs.*) != @as(Expr.Tag, rhs.*)) return false;
|
||||
|
||||
return switch (lhs.*) {
|
||||
.e_array => |l| l == rhs.e_array,
|
||||
.e_unary => |l| l == rhs.e_unary,
|
||||
.e_binary => |l| l == rhs.e_binary,
|
||||
.e_class => |l| l == rhs.e_class,
|
||||
.e_new => |l| l == rhs.e_new,
|
||||
.e_function => |l| l == rhs.e_function,
|
||||
.e_call => |l| l == rhs.e_call,
|
||||
.e_dot => |l| l == rhs.e_dot,
|
||||
.e_index => |l| l == rhs.e_index,
|
||||
.e_arrow => |l| l == rhs.e_arrow,
|
||||
.e_jsx_element => |l| l == rhs.e_jsx_element,
|
||||
.e_object => |l| l == rhs.e_object,
|
||||
.e_spread => |l| l == rhs.e_spread,
|
||||
.e_template => |l| l == rhs.e_template,
|
||||
.e_reg_exp => |l| l == rhs.e_reg_exp,
|
||||
.e_await => |l| l == rhs.e_await,
|
||||
.e_yield => |l| l == rhs.e_yield,
|
||||
.e_if => |l| l == rhs.e_if,
|
||||
.e_import => |l| l == rhs.e_import,
|
||||
.e_big_int => |l| l == rhs.e_big_int,
|
||||
.e_string => |l| l == rhs.e_string,
|
||||
.e_inlined_enum => |l| l == rhs.e_inlined_enum,
|
||||
.e_name_of_symbol => |l| l == rhs.e_name_of_symbol,
|
||||
|
||||
// For value types, fall back to value equality since they don't have pointer identity
|
||||
.e_identifier => |l| l.eql(&rhs.e_identifier),
|
||||
.e_import_identifier => |l| l.eql(rhs.e_import_identifier),
|
||||
.e_private_identifier => |l| l.eql(rhs.e_private_identifier),
|
||||
.e_commonjs_export_identifier => |l| l.eql(&rhs.e_commonjs_export_identifier),
|
||||
.e_boolean => |l| l.eql(rhs.e_boolean),
|
||||
.e_number => |l| l.eql(rhs.e_number),
|
||||
.e_require_string => |l| l.eql(rhs.e_require_string),
|
||||
.e_require_resolve_string => |l| l.eql(rhs.e_require_resolve_string),
|
||||
.e_import_meta_main => |l| l.eql(rhs.e_import_meta_main),
|
||||
.e_special => |l| l.eql(&rhs.e_special),
|
||||
|
||||
.e_missing,
|
||||
.e_this,
|
||||
.e_super,
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_new_target,
|
||||
.e_import_meta,
|
||||
.e_require_main,
|
||||
.e_require_call_target,
|
||||
.e_require_resolve_call_target,
|
||||
=> true,
|
||||
};
|
||||
}
|
||||
|
||||
// Returns "equal, ok". If "ok" is false, then nothing is known about the two
|
||||
// values. If "ok" is true, the equality or inequality of the two values is
|
||||
// stored in "equal".
|
||||
|
||||
286
src/ast/P.zig
286
src/ast/P.zig
@@ -748,20 +748,20 @@ pub fn NewParser_(
|
||||
}
|
||||
},
|
||||
.s_if => |if_statement| {
|
||||
const result = SideEffects.toBoolean(p, if_statement.test_.data);
|
||||
const result = SideEffects.toBoolean(p, &if_statement.test_.data);
|
||||
if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) {
|
||||
break :can_remove_part false;
|
||||
}
|
||||
},
|
||||
.s_while => |while_statement| {
|
||||
const result = SideEffects.toBoolean(p, while_statement.test_.data);
|
||||
const result = SideEffects.toBoolean(p, &while_statement.test_.data);
|
||||
if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) {
|
||||
break :can_remove_part false;
|
||||
}
|
||||
},
|
||||
.s_for => |for_statement| {
|
||||
if (for_statement.test_) |expr| {
|
||||
const result = SideEffects.toBoolean(p, expr.data);
|
||||
const result = SideEffects.toBoolean(p, &expr.data);
|
||||
if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) {
|
||||
break :can_remove_part false;
|
||||
}
|
||||
@@ -4483,6 +4483,286 @@ pub fn NewParser_(
|
||||
return;
|
||||
}
|
||||
|
||||
pub fn mangleIf(p: *P, stmts: *ListManaged(Stmt), loc: logger.Loc, if_stmt: *S.If) !void {
|
||||
// Constant folding using the test expression
|
||||
const effects = SideEffects.toBoolean(p, &if_stmt.test_.data);
|
||||
if (effects.ok) {
|
||||
if (effects.value) {
|
||||
// The test is truthy
|
||||
if (if_stmt.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(if_stmt.no.?, p.allocator)) {
|
||||
// We can drop the "no" branch
|
||||
if (effects.side_effects == .could_have_side_effects) {
|
||||
// Keep the condition if it could have side effects (but is still known to be truthy)
|
||||
if (SideEffects.simplifyUnusedExpr(p, if_stmt.test_)) |test_| {
|
||||
try stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc));
|
||||
}
|
||||
}
|
||||
return try p.appendIfBodyPreservingScope(stmts, if_stmt.yes);
|
||||
} else {
|
||||
// We have to keep the "no" branch
|
||||
}
|
||||
} else {
|
||||
// The test is falsy
|
||||
if (!SideEffects.shouldKeepStmtInDeadControlFlow(if_stmt.yes, p.allocator)) {
|
||||
// We can drop the "yes" branch
|
||||
if (effects.side_effects == .could_have_side_effects) {
|
||||
// Keep the condition if it could have side effects (but is still known to be falsy)
|
||||
if (SideEffects.simplifyUnusedExpr(p, if_stmt.test_)) |test_| {
|
||||
try stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc));
|
||||
}
|
||||
}
|
||||
if (if_stmt.no == null) {
|
||||
return;
|
||||
}
|
||||
return try p.appendIfBodyPreservingScope(stmts, if_stmt.no.?);
|
||||
} else {
|
||||
// We have to keep the "yes" branch
|
||||
}
|
||||
}
|
||||
// Use "1" and "0" instead of "true" and "false" to be shorter
|
||||
if (effects.side_effects == .no_side_effects) {
|
||||
if (effects.value) {
|
||||
if_stmt.test_.data = .{ .e_number = .{ .value = 1 } };
|
||||
} else {
|
||||
if_stmt.test_.data = .{ .e_number = .{ .value = 0 } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var expr: ?Expr = null;
|
||||
if (if_stmt.yes.data == .s_expr) {
|
||||
const yes_expr = if_stmt.yes.data.s_expr;
|
||||
// "yes" is an expression
|
||||
if (if_stmt.no == null) {
|
||||
if (if_stmt.test_.data == .e_unary and if_stmt.test_.data.e_unary.op == .un_not) {
|
||||
const not = if_stmt.test_.data.e_unary;
|
||||
// "if (!a) b();" => "a || b();"
|
||||
expr = Expr.joinWithLeftAssociativeOp(.bin_logical_or, not.value, yes_expr.value, p.allocator);
|
||||
} else {
|
||||
// "if (a) b();" => "a && b();"
|
||||
expr = Expr.joinWithLeftAssociativeOp(.bin_logical_and, if_stmt.test_, yes_expr.value, p.allocator);
|
||||
}
|
||||
} else if (if_stmt.no.?.data == .s_expr) {
|
||||
const no_expr = if_stmt.no.?.data.s_expr;
|
||||
// "if (a) b(); else c();" => "a ? b() : c();"
|
||||
expr = p.mangleIfExpr(loc, if_stmt.test_, yes_expr.value, no_expr.value);
|
||||
}
|
||||
} else if (if_stmt.yes.data == .s_empty) {
|
||||
// "yes" is missing
|
||||
if (if_stmt.no == null) {
|
||||
// "yes" and "no" are both missing
|
||||
if (p.exprCanBeRemovedIfUnused(&if_stmt.test_)) {
|
||||
// "if (1) {}" => ""
|
||||
return;
|
||||
} else {
|
||||
// "if (a) {}" => "a;"
|
||||
expr = if_stmt.test_;
|
||||
}
|
||||
} else if (if_stmt.no.?.data == .s_expr) {
|
||||
const no_expr = if_stmt.no.?.data.s_expr;
|
||||
if (if_stmt.test_.data == .e_unary and if_stmt.test_.data.e_unary.op == .un_not) {
|
||||
const not = if_stmt.test_.data.e_unary;
|
||||
// "if (!a) {} else b();" => "a && b();"
|
||||
expr = Expr.joinWithLeftAssociativeOp(.bin_logical_and, not.value, no_expr.value, p.allocator);
|
||||
} else {
|
||||
// "if (a) {} else b();" => "a || b();"
|
||||
expr = Expr.joinWithLeftAssociativeOp(.bin_logical_or, if_stmt.test_, no_expr.value, p.allocator);
|
||||
}
|
||||
} else {
|
||||
// "yes" is missing and "no" is not missing (and is not an expression)
|
||||
if (if_stmt.test_.data == .e_unary and if_stmt.test_.data.e_unary.op == .un_not) {
|
||||
const not = if_stmt.test_.data.e_unary;
|
||||
// "if (!a) {} else throw b;" => "if (a) throw b;"
|
||||
if_stmt.test_ = not.value;
|
||||
if_stmt.yes = if_stmt.no.?;
|
||||
if_stmt.no = null;
|
||||
} else {
|
||||
// "if (a) {} else throw b;" => "if (!a) throw b;"
|
||||
if_stmt.test_ = p.newExpr(E.Unary{ .op = .un_not, .value = if_stmt.test_ }, if_stmt.test_.loc);
|
||||
if_stmt.yes = if_stmt.no.?;
|
||||
if_stmt.no = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// "yes" is not missing (and is not an expression)
|
||||
if (if_stmt.no) |no| {
|
||||
// "yes" is not missing (and is not an expression) and "no" is not missing
|
||||
if (if_stmt.test_.data == .e_unary and if_stmt.test_.data.e_unary.op == .un_not) {
|
||||
const not = if_stmt.test_.data.e_unary;
|
||||
// "if (!a) return b; else return c;" => "if (a) return c; else return b;"
|
||||
if_stmt.test_ = not.value;
|
||||
const temp = if_stmt.yes;
|
||||
if_stmt.yes = no;
|
||||
if_stmt.no = temp;
|
||||
}
|
||||
} else {
|
||||
// "no" is missing
|
||||
if (if_stmt.yes.data == .s_if) {
|
||||
const nested_if = if_stmt.yes.data.s_if;
|
||||
if (nested_if.no == null) {
|
||||
// "if (a) if (b) return c;" => "if (a && b) return c;"
|
||||
if_stmt.test_ = Expr.joinWithLeftAssociativeOp(.bin_logical_and, if_stmt.test_, nested_if.test_, p.allocator);
|
||||
if_stmt.yes = nested_if.yes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return an expression if we replaced the if statement with an expression above
|
||||
if (expr) |e| {
|
||||
const simplified = SideEffects.simplifyUnusedExpr(p, e) orelse e;
|
||||
return try stmts.append(p.s(S.SExpr{ .value = simplified }, loc));
|
||||
}
|
||||
|
||||
return try stmts.append(Stmt{ .loc = loc, .data = .{ .s_if = if_stmt } });
|
||||
}
|
||||
|
||||
pub fn mangleIfExpr(p: *P, loc: logger.Loc, test_: Expr, yes: Expr, no: Expr) Expr {
|
||||
var test_expr = test_;
|
||||
var yes_expr = yes;
|
||||
var no_expr = no;
|
||||
|
||||
// "(a, b) ? c : d" => "a, b ? c : d"
|
||||
if (test_expr.data == .e_binary) {
|
||||
const comma = test_expr.data.e_binary;
|
||||
if (comma.op == .bin_comma) {
|
||||
return Expr.joinWithComma(
|
||||
comma.left,
|
||||
p.mangleIfExpr(comma.right.loc, comma.right, yes_expr, no_expr),
|
||||
p.allocator
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// "!a ? b : c" => "a ? c : b"
|
||||
if (test_expr.data == .e_unary) {
|
||||
const not = test_expr.data.e_unary;
|
||||
if (not.op == .un_not) {
|
||||
test_expr = not.value;
|
||||
const temp = yes_expr;
|
||||
yes_expr = no_expr;
|
||||
no_expr = temp;
|
||||
}
|
||||
}
|
||||
|
||||
if (yes_expr.data.eqlPtr(&no_expr.data)) {
|
||||
// "/* @__PURE__ */ a() ? b : b" => "b"
|
||||
if (p.exprCanBeRemovedIfUnused(&test_expr)) {
|
||||
return yes_expr;
|
||||
}
|
||||
|
||||
// "a ? b : b" => "a, b"
|
||||
return Expr.joinWithComma(test_expr, yes_expr, p.allocator);
|
||||
}
|
||||
|
||||
// "a ? true : false" => "!!a"
|
||||
// "a ? false : true" => "!a"
|
||||
if (yes_expr.data == .e_boolean and no_expr.data == .e_boolean) {
|
||||
const y = yes_expr.data.e_boolean;
|
||||
const n = no_expr.data.e_boolean;
|
||||
if (y.value and !n.value) {
|
||||
return p.newExpr(E.Unary{
|
||||
.op = .un_not,
|
||||
.value = p.newExpr(E.Unary{ .op = .un_not, .value = test_expr }, test_expr.loc)
|
||||
}, test_expr.loc);
|
||||
}
|
||||
if (!y.value and n.value) {
|
||||
return p.newExpr(E.Unary{ .op = .un_not, .value = test_expr }, test_expr.loc);
|
||||
}
|
||||
}
|
||||
|
||||
if (test_expr.data == .e_identifier) {
|
||||
const id = test_expr.data.e_identifier;
|
||||
// "a ? a : b" => "a || b"
|
||||
if (yes_expr.data == .e_identifier and yes_expr.data.e_identifier.ref.eql(id.ref)) {
|
||||
return Expr.joinWithLeftAssociativeOp(.bin_logical_or, test_expr, no_expr, p.allocator);
|
||||
}
|
||||
// "a ? b : a" => "a && b"
|
||||
if (no_expr.data == .e_identifier and no_expr.data.e_identifier.ref.eql(id.ref)) {
|
||||
return Expr.joinWithLeftAssociativeOp(.bin_logical_and, test_expr, yes_expr, p.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
// "a ? b ? c : d : d" => "a && b ? c : d"
|
||||
if (yes_expr.data == .e_if) {
|
||||
const yes_if = yes_expr.data.e_if;
|
||||
if (yes_if.no.data.eqlPtr(&no_expr.data)) {
|
||||
return p.newExpr(E.If{
|
||||
.test_ = Expr.joinWithLeftAssociativeOp(.bin_logical_and, test_expr, yes_if.test_, p.allocator),
|
||||
.yes = yes_if.yes,
|
||||
.no = no_expr,
|
||||
}, loc);
|
||||
}
|
||||
}
|
||||
|
||||
// "a ? b : c ? b : d" => "a || c ? b : d"
|
||||
if (no_expr.data == .e_if) {
|
||||
const no_if = no_expr.data.e_if;
|
||||
if (yes_expr.data.eqlPtr(&no_if.yes.data)) {
|
||||
return p.newExpr(E.If{
|
||||
.test_ = Expr.joinWithLeftAssociativeOp(.bin_logical_or, test_expr, no_if.test_, p.allocator),
|
||||
.yes = yes_expr,
|
||||
.no = no_if.no,
|
||||
}, loc);
|
||||
}
|
||||
}
|
||||
|
||||
// "a ? c : (b, c)" => "(a || b), c"
|
||||
if (no_expr.data == .e_binary) {
|
||||
const comma = no_expr.data.e_binary;
|
||||
if (comma.op == .bin_comma and yes_expr.data.eqlPtr(&comma.right.data)) {
|
||||
return Expr.joinWithComma(
|
||||
Expr.joinWithLeftAssociativeOp(.bin_logical_or, test_expr, comma.left, p.allocator),
|
||||
comma.right,
|
||||
p.allocator
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// "a ? (b, c) : c" => "(a && b), c"
|
||||
if (yes_expr.data == .e_binary) {
|
||||
const comma = yes_expr.data.e_binary;
|
||||
if (comma.op == .bin_comma and comma.right.data.eqlPtr(&no_expr.data)) {
|
||||
return Expr.joinWithComma(
|
||||
Expr.joinWithLeftAssociativeOp(.bin_logical_and, test_expr, comma.left, p.allocator),
|
||||
comma.right,
|
||||
p.allocator
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// "a ? b || c : c" => "(a && b) || c"
|
||||
if (yes_expr.data == .e_binary) {
|
||||
const binary = yes_expr.data.e_binary;
|
||||
if (binary.op == .bin_logical_or and binary.right.data.eqlPtr(&no_expr.data)) {
|
||||
return p.newExpr(E.Binary{
|
||||
.op = .bin_logical_or,
|
||||
.left = Expr.joinWithLeftAssociativeOp(.bin_logical_and, test_expr, binary.left, p.allocator),
|
||||
.right = binary.right,
|
||||
}, loc);
|
||||
}
|
||||
}
|
||||
|
||||
// "a ? c : b && c" => "(a || b) && c"
|
||||
if (no_expr.data == .e_binary) {
|
||||
const binary = no_expr.data.e_binary;
|
||||
if (binary.op == .bin_logical_and and yes_expr.data.eqlPtr(&binary.right.data)) {
|
||||
return p.newExpr(E.Binary{
|
||||
.op = .bin_logical_and,
|
||||
.left = Expr.joinWithLeftAssociativeOp(.bin_logical_or, test_expr, binary.left, p.allocator),
|
||||
.right = binary.right,
|
||||
}, loc);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't mutate the original AST
|
||||
return p.newExpr(E.If{
|
||||
.test_ = test_expr,
|
||||
.yes = yes_expr,
|
||||
.no = no_expr,
|
||||
}, loc);
|
||||
}
|
||||
|
||||
fn markExportedBindingInsideNamespace(p: *P, ref: Ref, binding: BindingNodeIndex) void {
|
||||
switch (binding.data) {
|
||||
.b_missing => {},
|
||||
|
||||
@@ -18,11 +18,11 @@ pub const SideEffects = enum(u1) {
|
||||
if (!p.options.features.dead_code_elimination) return expr;
|
||||
|
||||
var result: Expr = expr;
|
||||
_simplifyBoolean(p, &result);
|
||||
_simplifyBoolean(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn _simplifyBoolean(p: anytype, expr: *Expr) void {
|
||||
fn _simplifyBoolean(expr: *Expr) void {
|
||||
while (true) {
|
||||
switch (expr.data) {
|
||||
.e_unary => |e| {
|
||||
@@ -33,13 +33,13 @@ pub const SideEffects = enum(u1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_simplifyBoolean(p, &e.value);
|
||||
_simplifyBoolean(&e.value);
|
||||
}
|
||||
},
|
||||
.e_binary => |e| {
|
||||
switch (e.op) {
|
||||
.bin_logical_and => {
|
||||
const effects = SideEffects.toBoolean(p, e.right.data);
|
||||
const effects = _toBoolean(&e.right.data);
|
||||
if (effects.ok and effects.value and effects.side_effects == .no_side_effects) {
|
||||
// "if (anything && truthyNoSideEffects)" => "if (anything)"
|
||||
expr.* = e.left;
|
||||
@@ -47,7 +47,7 @@ pub const SideEffects = enum(u1) {
|
||||
}
|
||||
},
|
||||
.bin_logical_or => {
|
||||
const effects = SideEffects.toBoolean(p, e.right.data);
|
||||
const effects = _toBoolean(&e.right.data);
|
||||
if (effects.ok and !effects.value and effects.side_effects == .no_side_effects) {
|
||||
// "if (anything || falsyNoSideEffects)" => "if (anything)"
|
||||
expr.* = e.left;
|
||||
@@ -66,23 +66,27 @@ pub const SideEffects = enum(u1) {
|
||||
pub const toNumber = Expr.Data.toNumber;
|
||||
pub const typeof = Expr.Data.toTypeof;
|
||||
|
||||
pub fn isPrimitiveToReorder(data: Expr.Data) bool {
|
||||
return switch (data) {
|
||||
pub fn isPrimitiveToReorder(data: *const Expr.Data) bool {
|
||||
return switch (data.*) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_string,
|
||||
.e_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_inlined_enum,
|
||||
.e_require_main,
|
||||
=> true,
|
||||
.e_inlined_enum => |e| isPrimitiveToReorder(&e.value.data),
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn simplifyUnusedExpr(p: anytype, expr: Expr) ?Expr {
|
||||
if (!p.options.features.dead_code_elimination) return expr;
|
||||
const SimplifyUnusedExprContext = struct {
|
||||
symbols: *const std.ArrayList(js_ast.Symbol),
|
||||
allocator: std.mem.Allocator,
|
||||
};
|
||||
|
||||
fn _simplifyUnusedExpr(ctx: *const SimplifyUnusedExprContext, expr: Expr) ?Expr {
|
||||
switch (expr.data) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
@@ -109,17 +113,17 @@ pub const SideEffects = enum(u1) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
if (ident.can_be_removed_if_unused or p.symbols.items[ident.ref.innerIndex()].kind != .unbound) {
|
||||
if (ident.can_be_removed_if_unused or ctx.symbols.items[ident.ref.innerIndex()].kind != .unbound) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
.e_if => |ternary| {
|
||||
ternary.yes = simplifyUnusedExpr(p, ternary.yes) orelse ternary.yes.toEmpty();
|
||||
ternary.no = simplifyUnusedExpr(p, ternary.no) orelse ternary.no.toEmpty();
|
||||
ternary.yes = _simplifyUnusedExpr(ctx, ternary.yes) orelse ternary.yes.toEmpty();
|
||||
ternary.no = _simplifyUnusedExpr(ctx, ternary.no) orelse ternary.no.toEmpty();
|
||||
|
||||
// "foo() ? 1 : 2" => "foo()"
|
||||
if (ternary.yes.isEmpty() and ternary.no.isEmpty()) {
|
||||
return simplifyUnusedExpr(p, ternary.test_);
|
||||
return _simplifyUnusedExpr(ctx, ternary.test_);
|
||||
}
|
||||
|
||||
// "foo() ? 1 : bar()" => "foo() || bar()"
|
||||
@@ -128,7 +132,7 @@ pub const SideEffects = enum(u1) {
|
||||
.bin_logical_or,
|
||||
ternary.test_,
|
||||
ternary.no,
|
||||
p.allocator,
|
||||
ctx.allocator,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,7 +142,7 @@ pub const SideEffects = enum(u1) {
|
||||
.bin_logical_and,
|
||||
ternary.test_,
|
||||
ternary.yes,
|
||||
p.allocator,
|
||||
ctx.allocator,
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -147,7 +151,7 @@ pub const SideEffects = enum(u1) {
|
||||
// such as "toString" or "valueOf". They must also never throw any exceptions.
|
||||
switch (un.op) {
|
||||
.un_void, .un_not => {
|
||||
return simplifyUnusedExpr(p, un.value);
|
||||
return _simplifyUnusedExpr(ctx, un.value);
|
||||
},
|
||||
.un_typeof => {
|
||||
// "typeof x" must not be transformed into if "x" since doing so could
|
||||
@@ -157,7 +161,7 @@ pub const SideEffects = enum(u1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return simplifyUnusedExpr(p, un.value);
|
||||
return _simplifyUnusedExpr(ctx, un.value);
|
||||
},
|
||||
|
||||
else => {},
|
||||
@@ -169,7 +173,7 @@ pub const SideEffects = enum(u1) {
|
||||
// can be removed. The annotation causes us to ignore the target.
|
||||
if (call.can_be_unwrapped_if_unused != .never) {
|
||||
if (call.args.len > 0) {
|
||||
const joined = Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, comptime simplifyUnusedExpr, p.allocator);
|
||||
const joined = Expr.joinAllWithCommaCallback(call.args.slice(), *const SimplifyUnusedExprContext, ctx, comptime _simplifyUnusedExpr, ctx.allocator);
|
||||
if (joined != null and call.can_be_unwrapped_if_unused == .if_unused_and_toString_safe) {
|
||||
@branchHint(.unlikely);
|
||||
// For now, only support this for 1 argument.
|
||||
@@ -185,13 +189,17 @@ pub const SideEffects = enum(u1) {
|
||||
},
|
||||
|
||||
.e_binary => |bin| {
|
||||
var left = bin.left;
|
||||
var right = bin.right;
|
||||
|
||||
switch (bin.op) {
|
||||
// These operators must not have any type conversions that can execute code
|
||||
// such as "toString" or "valueOf". They must also never throw any exceptions.
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
.bin_comma,
|
||||
=> return simplifyUnusedBinaryCommaExpr(p, expr),
|
||||
.bin_strict_eq, .bin_strict_ne, .bin_comma => return Expr.joinWithComma(
|
||||
_simplifyUnusedExpr(ctx, left) orelse left.toEmpty(),
|
||||
_simplifyUnusedExpr(ctx, right) orelse right.toEmpty(),
|
||||
ctx.allocator,
|
||||
),
|
||||
|
||||
// We can simplify "==" and "!=" even though they can call "toString" and/or
|
||||
// "valueOf" if we can statically determine that the types of both sides are
|
||||
@@ -200,35 +208,89 @@ pub const SideEffects = enum(u1) {
|
||||
.bin_loose_eq,
|
||||
.bin_loose_ne,
|
||||
=> {
|
||||
if (isPrimitiveWithSideEffects(bin.left.data) and isPrimitiveWithSideEffects(bin.right.data)) {
|
||||
if (left.data.mergeKnownPrimitive(right.data) != .unknown) {
|
||||
return Expr.joinWithComma(
|
||||
simplifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(),
|
||||
simplifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(),
|
||||
p.allocator,
|
||||
_simplifyUnusedExpr(ctx, left) orelse left.toEmpty(),
|
||||
_simplifyUnusedExpr(ctx, right) orelse right.toEmpty(),
|
||||
ctx.allocator,
|
||||
);
|
||||
}
|
||||
// If one side is a number, the number can be printed as
|
||||
// `0` since the result being unused doesnt matter, we
|
||||
// only care to invoke the coercion.
|
||||
if (bin.left.data == .e_number) {
|
||||
bin.left.data = .{ .e_number = .{ .value = 0.0 } };
|
||||
} else if (bin.right.data == .e_number) {
|
||||
bin.right.data = .{ .e_number = .{ .value = 0.0 } };
|
||||
}
|
||||
},
|
||||
|
||||
.bin_logical_and, .bin_logical_or, .bin_nullish_coalescing => {
|
||||
bin.right = simplifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty();
|
||||
.bin_logical_and, .bin_logical_or, .bin_nullish_coalescing => |op| {
|
||||
|
||||
// If this is a boolean logical operation and the result is unused, then
|
||||
// we know the left operand will only be used for its boolean value and
|
||||
// can be simplified under that assumption
|
||||
if (op != .bin_nullish_coalescing) {
|
||||
_simplifyBoolean(&left);
|
||||
}
|
||||
|
||||
right = _simplifyUnusedExpr(ctx, right) orelse Expr.empty;
|
||||
|
||||
// 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 (right.isEmpty()) {
|
||||
return _simplifyUnusedExpr(ctx, left);
|
||||
}
|
||||
|
||||
if (bin.right.isEmpty())
|
||||
return simplifyUnusedExpr(p, bin.left);
|
||||
// Try to take advantage of the optional chain operator to shorten code
|
||||
if (bin.op != .bin_nullish_coalescing) {
|
||||
if (left.data == .e_binary) {
|
||||
const binary = left.data.e_binary;
|
||||
// "a != null && a.b()" => "a?.b()"
|
||||
// "a == null || a.b()" => "a?.b()"
|
||||
if ((binary.op == .bin_loose_ne and bin.op == .bin_logical_and) or
|
||||
(binary.op == .bin_loose_eq and bin.op == .bin_logical_or)) {
|
||||
var test_expr: ?Expr = null;
|
||||
if (binary.right.data == .e_null) {
|
||||
test_expr = binary.left;
|
||||
} else if (binary.left.data == .e_null) {
|
||||
test_expr = binary.right;
|
||||
}
|
||||
|
||||
if (test_expr) |test_val| {
|
||||
// Note: Technically unbound identifiers can refer to a getter on
|
||||
// the global object and that getter can have side effects that can
|
||||
// be observed if we run that getter once instead of twice. But this
|
||||
// seems like terrible coding practice and very unlikely to come up
|
||||
// in real software, so we deliberately ignore this possibility and
|
||||
// optimize for size instead of for this obscure edge case.
|
||||
if (test_val.data == .e_identifier) {
|
||||
const id = test_val.data.e_identifier;
|
||||
if (!id.must_keep_due_to_with_stmt) {
|
||||
// TODO: Optional chaining optimization disabled due to existing Expr.zig type issue
|
||||
// This would transform "a != null && a.b()" => "a?.b()"
|
||||
// But there's a pre-existing type issue in tryToInsertOptionalChain
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.bin_add => {
|
||||
if (simplifyUnusedStringAdditionChain(expr)) |result| {
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (!bin.left.data.eqlPtr(&left.data) or !bin.right.data.eqlPtr(&right.data)) {
|
||||
return Expr.init(
|
||||
E.Binary,
|
||||
E.Binary{
|
||||
.op = bin.op,
|
||||
.left = left,
|
||||
.right = right,
|
||||
},
|
||||
expr.loc,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
.e_object => {
|
||||
@@ -237,24 +299,24 @@ pub const SideEffects = enum(u1) {
|
||||
// the other items instead and leave the object expression there.
|
||||
var properties_slice = expr.data.e_object.properties.slice();
|
||||
var end: usize = 0;
|
||||
for (properties_slice) |spread| {
|
||||
for (properties_slice) |*spread| {
|
||||
end = 0;
|
||||
if (spread.kind == .spread) {
|
||||
// Spread properties must always be evaluated
|
||||
for (properties_slice) |prop_| {
|
||||
var prop = prop_;
|
||||
if (prop_.kind != .spread) {
|
||||
const value = simplifyUnusedExpr(p, prop.value.?);
|
||||
for (properties_slice) |*prop_| {
|
||||
var prop = prop_.*;
|
||||
if (prop.kind != .spread) {
|
||||
const value = _simplifyUnusedExpr(ctx, prop.value.?);
|
||||
if (value != null) {
|
||||
prop.value = value;
|
||||
} else if (!prop.flags.contains(.is_computed)) {
|
||||
continue;
|
||||
} else {
|
||||
prop.value = p.newExpr(E.Number{ .value = 0.0 }, prop.value.?.loc);
|
||||
prop.value = Expr.init(E.Number, E.Number{ .value = 0.0 }, prop.value.?.loc);
|
||||
}
|
||||
}
|
||||
|
||||
properties_slice[end] = prop_;
|
||||
properties_slice[end] = prop;
|
||||
end += 1;
|
||||
}
|
||||
|
||||
@@ -268,24 +330,25 @@ pub const SideEffects = enum(u1) {
|
||||
|
||||
// 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| {
|
||||
for (properties_slice) |*prop| {
|
||||
if (prop.flags.contains(.is_computed)) {
|
||||
// Make sure "ToString" is still evaluated on the key
|
||||
result = result.joinWithComma(
|
||||
p.newExpr(
|
||||
Expr.init(
|
||||
E.Binary,
|
||||
E.Binary{
|
||||
.op = .bin_add,
|
||||
.left = prop.key.?,
|
||||
.right = p.newExpr(E.String{}, prop.key.?.loc),
|
||||
.right = Expr.init(E.String, E.String{}, prop.key.?.loc),
|
||||
},
|
||||
prop.key.?.loc,
|
||||
),
|
||||
p.allocator,
|
||||
ctx.allocator,
|
||||
);
|
||||
}
|
||||
result = result.joinWithComma(
|
||||
simplifyUnusedExpr(p, prop.value.?) orelse prop.value.?.toEmpty(),
|
||||
p.allocator,
|
||||
_simplifyUnusedExpr(ctx, prop.value.?) orelse prop.value.?.toEmpty(),
|
||||
ctx.allocator,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -314,10 +377,10 @@ pub const SideEffects = enum(u1) {
|
||||
// array items with side effects. Apply this simplification recursively.
|
||||
return Expr.joinAllWithCommaCallback(
|
||||
items,
|
||||
@TypeOf(p),
|
||||
p,
|
||||
comptime simplifyUnusedExpr,
|
||||
p.allocator,
|
||||
*const SimplifyUnusedExprContext,
|
||||
ctx,
|
||||
comptime _simplifyUnusedExpr,
|
||||
ctx.allocator,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -327,56 +390,19 @@ pub const SideEffects = enum(u1) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
pub fn simplifyUnusedExpr(p: anytype, expr: Expr) ?Expr {
|
||||
if (!p.options.features.dead_code_elimination) return expr;
|
||||
var ctx = SimplifyUnusedExprContext{
|
||||
.symbols = &p.symbols,
|
||||
.allocator = p.allocator,
|
||||
};
|
||||
return _simplifyUnusedExpr(&ctx, expr);
|
||||
}
|
||||
|
||||
pub const BinaryExpressionSimplifyVisitor = struct {
|
||||
bin: *E.Binary,
|
||||
};
|
||||
|
||||
///
|
||||
fn simplifyUnusedBinaryCommaExpr(p: anytype, expr: Expr) ?Expr {
|
||||
if (Environment.allow_assert) {
|
||||
assert(expr.data == .e_binary);
|
||||
assert(switch (expr.data.e_binary.op) {
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
.bin_comma,
|
||||
=> true,
|
||||
else => false,
|
||||
});
|
||||
}
|
||||
const stack: *std.ArrayList(BinaryExpressionSimplifyVisitor) = &p.binary_expression_simplify_stack;
|
||||
const stack_bottom = stack.items.len;
|
||||
defer stack.shrinkRetainingCapacity(stack_bottom);
|
||||
|
||||
stack.append(.{ .bin = expr.data.e_binary }) catch bun.outOfMemory();
|
||||
|
||||
// Build stack up of expressions
|
||||
var left: Expr = expr.data.e_binary.left;
|
||||
while (left.data.as(.e_binary)) |left_bin| {
|
||||
switch (left_bin.op) {
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
.bin_comma,
|
||||
=> {
|
||||
stack.append(.{ .bin = left_bin }) catch bun.outOfMemory();
|
||||
left = left_bin.left;
|
||||
},
|
||||
else => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Ride the stack downwards
|
||||
var i = stack.items.len;
|
||||
var result = simplifyUnusedExpr(p, left) orelse Expr.empty;
|
||||
while (i > stack_bottom) {
|
||||
i -= 1;
|
||||
const top = stack.items[i];
|
||||
const visited_right = simplifyUnusedExpr(p, top.bin.right) orelse Expr.empty;
|
||||
result = result.joinWithComma(visited_right, p.allocator);
|
||||
}
|
||||
|
||||
return if (result.isMissing()) null else result;
|
||||
}
|
||||
|
||||
fn findIdentifiers(binding: Binding, decls: *std.ArrayList(G.Decl)) void {
|
||||
switch (binding.data) {
|
||||
.b_identifier => {
|
||||
@@ -518,8 +544,8 @@ pub const SideEffects = enum(u1) {
|
||||
// Returns true if this expression is known to result in a primitive value (i.e.
|
||||
// null, undefined, boolean, number, bigint, or string), even if the expression
|
||||
// cannot be removed due to side effects.
|
||||
pub fn isPrimitiveWithSideEffects(data: Expr.Data) bool {
|
||||
switch (data) {
|
||||
pub fn isPrimitiveWithSideEffects(data: *const Expr.Data) bool {
|
||||
switch (data.*) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_boolean,
|
||||
@@ -604,16 +630,16 @@ pub const SideEffects = enum(u1) {
|
||||
.bin_logical_or_assign,
|
||||
.bin_nullish_coalescing_assign,
|
||||
=> {
|
||||
return isPrimitiveWithSideEffects(e.left.data) and isPrimitiveWithSideEffects(e.right.data);
|
||||
return isPrimitiveWithSideEffects(&e.left.data) and isPrimitiveWithSideEffects(&e.right.data);
|
||||
},
|
||||
.bin_comma => {
|
||||
return isPrimitiveWithSideEffects(e.right.data);
|
||||
return isPrimitiveWithSideEffects(&e.right.data);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_if => |e| {
|
||||
return isPrimitiveWithSideEffects(e.yes.data) and isPrimitiveWithSideEffects(e.no.data);
|
||||
return isPrimitiveWithSideEffects(&e.yes.data) and isPrimitiveWithSideEffects(&e.no.data);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@@ -622,24 +648,28 @@ pub const SideEffects = enum(u1) {
|
||||
|
||||
pub const toTypeOf = Expr.Data.typeof;
|
||||
|
||||
pub fn toNullOrUndefined(p: anytype, exp: Expr.Data) Result {
|
||||
pub fn toNullOrUndefined(p: anytype, exp: *const Expr.Data) Result {
|
||||
if (!p.options.features.dead_code_elimination) {
|
||||
// value should not be read if ok is false, all existing calls to this function already adhere to this
|
||||
return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
switch (exp) {
|
||||
return _toNullOrUndefined(exp);
|
||||
}
|
||||
|
||||
fn _toNullOrUndefined(exp: *const Expr.Data) Result {
|
||||
switch (exp.*) {
|
||||
// Never null or undefined
|
||||
.e_boolean, .e_number, .e_string, .e_reg_exp, .e_function, .e_arrow, .e_big_int => {
|
||||
return Result{ .value = false, .side_effects = .no_side_effects, .ok = true };
|
||||
return .{ .value = false, .side_effects = .no_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
.e_object, .e_array, .e_class => {
|
||||
return Result{ .value = false, .side_effects = .could_have_side_effects, .ok = true };
|
||||
return .{ .value = false, .side_effects = .could_have_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
// always a null or undefined
|
||||
.e_null, .e_undefined => {
|
||||
return Result{ .value = true, .side_effects = .no_side_effects, .ok = true };
|
||||
return .{ .value = true, .side_effects = .no_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
.e_unary => |e| {
|
||||
@@ -658,12 +688,12 @@ pub const SideEffects = enum(u1) {
|
||||
.un_typeof,
|
||||
.un_delete,
|
||||
=> {
|
||||
return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
return .{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
|
||||
// Always undefined
|
||||
.un_void => {
|
||||
return Result{ .value = true, .side_effects = .could_have_side_effects, .ok = true };
|
||||
return .{ .value = true, .side_effects = .could_have_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
else => {},
|
||||
@@ -710,74 +740,74 @@ pub const SideEffects = enum(u1) {
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
=> {
|
||||
return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
return .{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
|
||||
.bin_comma => {
|
||||
const res = toNullOrUndefined(p, e.right.data);
|
||||
const res = _toNullOrUndefined(&e.right.data);
|
||||
if (res.ok) {
|
||||
return Result{ .ok = true, .value = res.value, .side_effects = SideEffects.could_have_side_effects };
|
||||
return .{ .ok = true, .value = res.value, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_inlined_enum => |inlined| {
|
||||
return toNullOrUndefined(p, inlined.value.data);
|
||||
return _toNullOrUndefined(&inlined.value.data);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
return .{ .ok = false, .value = false, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
|
||||
pub fn toBoolean(p: anytype, exp: Expr.Data) Result {
|
||||
pub fn toBoolean(p: anytype, exp: *const Expr.Data) Result {
|
||||
// Only do this check once.
|
||||
if (!p.options.features.dead_code_elimination) {
|
||||
// value should not be read if ok is false, all existing calls to this function already adhere to this
|
||||
return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects };
|
||||
return .{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
|
||||
return toBooleanWithoutDCECheck(exp);
|
||||
return _toBoolean(exp);
|
||||
}
|
||||
|
||||
// Avoid passing through *P
|
||||
// This is a very recursive function.
|
||||
fn toBooleanWithoutDCECheck(exp: Expr.Data) Result {
|
||||
switch (exp) {
|
||||
fn _toBoolean(exp: *const Expr.Data) Result {
|
||||
switch (exp.*) {
|
||||
.e_null, .e_undefined => {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = false, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_boolean => |e| {
|
||||
return Result{ .ok = true, .value = e.value, .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = e.value, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_number => |e| {
|
||||
return Result{ .ok = true, .value = e.value != 0.0 and !std.math.isNan(e.value), .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = e.value != 0.0 and !std.math.isNan(e.value), .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_big_int => |e| {
|
||||
return Result{ .ok = true, .value = !strings.eqlComptime(e.value, "0"), .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = !strings.eqlComptime(e.value, "0"), .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_string => |e| {
|
||||
return Result{ .ok = true, .value = e.isPresent(), .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = e.isPresent(), .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_function, .e_arrow, .e_reg_exp => {
|
||||
return Result{ .ok = true, .value = true, .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = true, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_object, .e_array, .e_class => {
|
||||
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
return .{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
.e_unary => |e_| {
|
||||
switch (e_.op) {
|
||||
.un_void => {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
return .{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
.un_typeof => {
|
||||
// Never an empty string
|
||||
|
||||
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
return .{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
.un_not => {
|
||||
const result = toBooleanWithoutDCECheck(e_.value.data);
|
||||
const result = _toBoolean(&e_.value.data);
|
||||
if (result.ok) {
|
||||
return .{ .ok = true, .value = !result.value, .side_effects = result.side_effects };
|
||||
}
|
||||
@@ -789,21 +819,21 @@ pub const SideEffects = enum(u1) {
|
||||
switch (e_.op) {
|
||||
.bin_logical_or => {
|
||||
// "anything || truthy" is truthy
|
||||
const result = toBooleanWithoutDCECheck(e_.right.data);
|
||||
const result = _toBoolean(&e_.right.data);
|
||||
if (result.value and result.ok) {
|
||||
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
return .{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
},
|
||||
.bin_logical_and => {
|
||||
// "anything && falsy" is falsy
|
||||
const result = toBooleanWithoutDCECheck(e_.right.data);
|
||||
const result = _toBoolean(&e_.right.data);
|
||||
if (!result.value and result.ok) {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
return .{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
},
|
||||
.bin_comma => {
|
||||
// "anything, truthy/falsy" is truthy/falsy
|
||||
var result = toBooleanWithoutDCECheck(e_.right.data);
|
||||
var result = _toBoolean(&e_.right.data);
|
||||
if (result.ok) {
|
||||
result.side_effects = .could_have_side_effects;
|
||||
return result;
|
||||
@@ -812,28 +842,28 @@ pub const SideEffects = enum(u1) {
|
||||
.bin_gt => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num > right_num, .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = left_num > right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_lt => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num < right_num, .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = left_num < right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_le => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num <= right_num, .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = left_num <= right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_ge => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num >= right_num, .side_effects = .no_side_effects };
|
||||
return .{ .ok = true, .value = left_num >= right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -841,7 +871,7 @@ pub const SideEffects = enum(u1) {
|
||||
}
|
||||
},
|
||||
.e_inlined_enum => |inlined| {
|
||||
return toBooleanWithoutDCECheck(inlined.value.data);
|
||||
return _toBoolean(&inlined.value.data);
|
||||
},
|
||||
.e_special => |special| switch (special) {
|
||||
.module_exports,
|
||||
@@ -858,7 +888,53 @@ pub const SideEffects = enum(u1) {
|
||||
else => {},
|
||||
}
|
||||
|
||||
return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
return .{ .ok = false, .value = false, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
|
||||
fn simplifyUnusedStringAdditionChain(expr: Expr) ?Expr {
|
||||
switch (expr.data) {
|
||||
.e_string => {
|
||||
// "'x' + y" => "'' + y"
|
||||
return Expr.init(E.String, E.String{}, expr.loc);
|
||||
},
|
||||
.e_binary => |e| {
|
||||
if (e.op == .bin_add) {
|
||||
const left_result = simplifyUnusedStringAdditionChain(e.left);
|
||||
const left_is_string_addition = left_result != null;
|
||||
|
||||
if (e.right.data == .e_string) {
|
||||
const right_string = e.right.data.e_string;
|
||||
// "('' + x) + 'y'" => "'' + x"
|
||||
if (left_is_string_addition) {
|
||||
return left_result.?;
|
||||
}
|
||||
|
||||
// "x + 'y'" => "x + ''"
|
||||
if (!left_is_string_addition and right_string.data.len > 0) {
|
||||
return Expr.init(E.Binary, E.Binary{
|
||||
.op = .bin_add,
|
||||
.left = left_result orelse e.left,
|
||||
.right = Expr.init(E.String, E.String{}, e.right.loc),
|
||||
}, expr.loc);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't mutate the original AST
|
||||
if (left_result != null and !e.left.data.eqlPtr(&left_result.?.data)) {
|
||||
return Expr.init(E.Binary, E.Binary{
|
||||
.op = .bin_add,
|
||||
.left = left_result.?,
|
||||
.right = e.right,
|
||||
}, expr.loc);
|
||||
}
|
||||
|
||||
return if (left_is_string_addition) expr else null;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ pub fn ParseSuffix(
|
||||
|
||||
// Remove unnecessary optional chains
|
||||
if (p.options.features.minify_syntax) {
|
||||
const result = SideEffects.toNullOrUndefined(p, left.data);
|
||||
const result = SideEffects.toNullOrUndefined(p, &left.data);
|
||||
if (result.ok and !result.value) {
|
||||
optional_start = null;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn CreateBinaryExpressionVisitor(
|
||||
// Mark the control flow as dead if the branch is never taken
|
||||
switch (e_.op) {
|
||||
.bin_logical_or => {
|
||||
const side_effects = SideEffects.toBoolean(p, e_.left.data);
|
||||
const side_effects = SideEffects.toBoolean(p, &e_.left.data);
|
||||
if (side_effects.ok and side_effects.value) {
|
||||
// "true || dead"
|
||||
const old = p.is_control_flow_dead;
|
||||
@@ -41,7 +41,7 @@ pub fn CreateBinaryExpressionVisitor(
|
||||
}
|
||||
},
|
||||
.bin_logical_and => {
|
||||
const side_effects = SideEffects.toBoolean(p, e_.left.data);
|
||||
const side_effects = SideEffects.toBoolean(p, &e_.left.data);
|
||||
if (side_effects.ok and !side_effects.value) {
|
||||
// "false && dead"
|
||||
const old = p.is_control_flow_dead;
|
||||
@@ -53,7 +53,7 @@ pub fn CreateBinaryExpressionVisitor(
|
||||
}
|
||||
},
|
||||
.bin_nullish_coalescing => {
|
||||
const side_effects = SideEffects.toNullOrUndefined(p, e_.left.data);
|
||||
const side_effects = SideEffects.toNullOrUndefined(p, &e_.left.data);
|
||||
if (side_effects.ok and !side_effects.value) {
|
||||
// "notNullOrUndefined ?? dead"
|
||||
const old = p.is_control_flow_dead;
|
||||
@@ -74,7 +74,7 @@ pub fn CreateBinaryExpressionVisitor(
|
||||
// can only reorder expressions that do not have any side effects.
|
||||
switch (e_.op) {
|
||||
.bin_loose_eq, .bin_loose_ne, .bin_strict_eq, .bin_strict_ne => {
|
||||
if (SideEffects.isPrimitiveToReorder(e_.left.data) and !SideEffects.isPrimitiveToReorder(e_.right.data)) {
|
||||
if (SideEffects.isPrimitiveToReorder(&e_.left.data) and !SideEffects.isPrimitiveToReorder(&e_.right.data)) {
|
||||
const _left = e_.left;
|
||||
const _right = e_.right;
|
||||
e_.left = _right;
|
||||
@@ -170,7 +170,7 @@ pub fn CreateBinaryExpressionVisitor(
|
||||
}
|
||||
},
|
||||
.bin_nullish_coalescing => {
|
||||
const nullorUndefined = SideEffects.toNullOrUndefined(p, e_.left.data);
|
||||
const nullorUndefined = SideEffects.toNullOrUndefined(p, &e_.left.data);
|
||||
if (nullorUndefined.ok) {
|
||||
if (!nullorUndefined.value) {
|
||||
return e_.left;
|
||||
@@ -187,7 +187,7 @@ pub fn CreateBinaryExpressionVisitor(
|
||||
}
|
||||
},
|
||||
.bin_logical_or => {
|
||||
const side_effects = SideEffects.toBoolean(p, e_.left.data);
|
||||
const side_effects = SideEffects.toBoolean(p, &e_.left.data);
|
||||
if (side_effects.ok and side_effects.value) {
|
||||
return e_.left;
|
||||
} else if (side_effects.ok and side_effects.side_effects == .no_side_effects) {
|
||||
@@ -202,7 +202,7 @@ pub fn CreateBinaryExpressionVisitor(
|
||||
}
|
||||
},
|
||||
.bin_logical_and => {
|
||||
const side_effects = SideEffects.toBoolean(p, e_.left.data);
|
||||
const side_effects = SideEffects.toBoolean(p, &e_.left.data);
|
||||
if (side_effects.ok) {
|
||||
if (!side_effects.value) {
|
||||
return e_.left;
|
||||
|
||||
@@ -732,7 +732,7 @@ pub fn VisitExpr(
|
||||
if (p.options.features.minify_syntax)
|
||||
e_.value = SideEffects.simplifyBoolean(p, e_.value);
|
||||
|
||||
const side_effects = SideEffects.toBoolean(p, e_.value.data);
|
||||
const side_effects = SideEffects.toBoolean(p, &e_.value.data);
|
||||
if (side_effects.ok) {
|
||||
return p.newExpr(E.Boolean{ .value = !side_effects.value }, expr.loc);
|
||||
}
|
||||
@@ -749,7 +749,7 @@ pub fn VisitExpr(
|
||||
},
|
||||
.un_cpl => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (SideEffects.toNumber(e_.value.data)) |value| {
|
||||
if (SideEffects.toNumber(&e_.value.data)) |value| {
|
||||
return p.newExpr(E.Number{
|
||||
.value = @floatFromInt(~floatToInt32(value)),
|
||||
}, expr.loc);
|
||||
@@ -762,12 +762,12 @@ pub fn VisitExpr(
|
||||
}
|
||||
},
|
||||
.un_pos => {
|
||||
if (SideEffects.toNumber(e_.value.data)) |num| {
|
||||
if (SideEffects.toNumber(&e_.value.data)) |num| {
|
||||
return p.newExpr(E.Number{ .value = num }, expr.loc);
|
||||
}
|
||||
},
|
||||
.un_neg => {
|
||||
if (SideEffects.toNumber(e_.value.data)) |num| {
|
||||
if (SideEffects.toNumber(&e_.value.data)) |num| {
|
||||
return p.newExpr(E.Number{ .value = -num }, expr.loc);
|
||||
}
|
||||
},
|
||||
@@ -921,7 +921,7 @@ pub fn VisitExpr(
|
||||
|
||||
e_.test_ = SideEffects.simplifyBoolean(p, e_.test_);
|
||||
|
||||
const side_effects = SideEffects.toBoolean(p, e_.test_.data);
|
||||
const side_effects = SideEffects.toBoolean(p, &e_.test_.data);
|
||||
|
||||
if (!side_effects.ok) {
|
||||
e_.yes = p.visitExpr(e_.yes);
|
||||
|
||||
@@ -985,7 +985,7 @@ pub fn VisitStmt(
|
||||
data.body = p.visitLoopBody(data.body);
|
||||
|
||||
data.test_ = SideEffects.simplifyBoolean(p, data.test_);
|
||||
const result = SideEffects.toBoolean(p, data.test_.data);
|
||||
const result = SideEffects.toBoolean(p, &data.test_.data);
|
||||
if (result.ok and result.side_effects == .no_side_effects) {
|
||||
data.test_ = p.newExpr(E.Boolean{ .value = result.value }, data.test_.loc);
|
||||
}
|
||||
@@ -1006,7 +1006,7 @@ pub fn VisitStmt(
|
||||
data.test_ = SideEffects.simplifyBoolean(p, data.test_);
|
||||
}
|
||||
|
||||
const effects = SideEffects.toBoolean(p, data.test_.data);
|
||||
const effects = SideEffects.toBoolean(p, &data.test_.data);
|
||||
if (effects.ok and !effects.value) {
|
||||
const old = p.is_control_flow_dead;
|
||||
p.is_control_flow_dead = true;
|
||||
@@ -1069,31 +1069,7 @@ pub fn VisitStmt(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: more if statement syntax minification
|
||||
const can_remove_test = p.exprCanBeRemovedIfUnused(&data.test_);
|
||||
switch (data.yes.data) {
|
||||
.s_expr => |yes_expr| {
|
||||
if (yes_expr.value.isMissing()) {
|
||||
if (data.no == null) {
|
||||
if (can_remove_test) {
|
||||
return;
|
||||
}
|
||||
} else if (data.no.?.isMissingExpr() and can_remove_test) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
.s_empty => {
|
||||
if (data.no == null) {
|
||||
if (can_remove_test) {
|
||||
return;
|
||||
}
|
||||
} else if (data.no.?.isMissingExpr() and can_remove_test) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return try p.mangleIf(stmts, stmt.loc, data);
|
||||
}
|
||||
|
||||
try stmts.append(stmt.*);
|
||||
@@ -1108,7 +1084,7 @@ pub fn VisitStmt(
|
||||
if (data.test_) |test_| {
|
||||
data.test_ = SideEffects.simplifyBoolean(p, p.visitExpr(test_));
|
||||
|
||||
const result = SideEffects.toBoolean(p, data.test_.?.data);
|
||||
const result = SideEffects.toBoolean(p, &data.test_.?.data);
|
||||
if (result.ok and result.value and result.side_effects == .no_side_effects) {
|
||||
data.test_ = null;
|
||||
}
|
||||
@@ -1531,6 +1507,7 @@ pub fn VisitStmt(
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -690,4 +690,62 @@ describe("bundler", () => {
|
||||
stdout: "foo\ntrue\ntrue\ndisabled_for_development",
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/IfStatementMinification", {
|
||||
files: {
|
||||
"/entry.js": /* js */ `
|
||||
var a, b, c;
|
||||
|
||||
// Simple if-to-logical-expression conversions
|
||||
if (a) b();
|
||||
if (!a) b();
|
||||
if (a) b(); else c();
|
||||
if (a) {} else b();
|
||||
|
||||
// Ternary expression optimizations
|
||||
capture(a ? true : false);
|
||||
capture(a ? false : true);
|
||||
capture(a ? a : b);
|
||||
capture(a ? b : a);
|
||||
capture(a ? b : b);
|
||||
`,
|
||||
},
|
||||
capture: [
|
||||
"a?!0:!1", // a ? true : false (minified true->!0, false->!1)
|
||||
"a?!1:!0", // a ? false : true (minified false->!1, true->!0)
|
||||
"a?a:b", // a ? a : b (not optimized yet)
|
||||
"a?b:a", // a ? b : a (not optimized yet)
|
||||
"a?b:b", // a ? b : b (not optimized yet)
|
||||
],
|
||||
minifySyntax: true,
|
||||
minifyWhitespace: true,
|
||||
});
|
||||
|
||||
|
||||
itBundled("minify/JumpStatementOptimization", {
|
||||
files: {
|
||||
"/entry.js": /* js */ `
|
||||
var a, b, c;
|
||||
|
||||
// Test that if statements get converted to logical expressions
|
||||
if (a) b(); // -> a && b()
|
||||
if (!a) b(); // -> a || b() (correct: if !a then b, same as a || b)
|
||||
if (a) b(); else c(); // -> a ? b() : c()
|
||||
if (a) {} else b(); // -> a || b() (if a is truthy do nothing, else b)
|
||||
|
||||
capture("works");
|
||||
`,
|
||||
},
|
||||
capture: ['"works"'],
|
||||
minifySyntax: true,
|
||||
minifyWhitespace: true,
|
||||
onAfterBundle(api) {
|
||||
const file = api.readFile("out.js");
|
||||
// Verify if statements were converted to logical expressions
|
||||
expect(file).toContain("a&&b()"); // if (a) b(); -> a&&b();
|
||||
expect(file).toContain("a?b():c()"); // if (a) b(); else c(); -> a?b():c();
|
||||
expect(file).toContain("a||b()"); // if (a) {} else b(); -> a||b();
|
||||
// Note: if (!a) b(); -> a||b() is actually correct! (if !a is true, then b(), same as a||b())
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
2
test_minify_output.js
Normal file
2
test_minify_output.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// test_minify.js
|
||||
console.log("Input file created");
|
||||
Reference in New Issue
Block a user