aboutsummaryrefslogtreecommitdiff
path: root/src/js_parser.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/js_parser.zig')
-rw-r--r--src/js_parser.zig112
1 files changed, 66 insertions, 46 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig
index e4dbaf25b..13e264251 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -1600,6 +1600,7 @@ pub const SideEffects = enum(u1) {
}
pub fn simplifyBoolean(p: anytype, expr: Expr) Expr {
+ if (!p.options.features.dead_code_elimination) return expr;
switch (expr.data) {
.e_unary => |e| {
if (e.op == .un_not) {
@@ -1614,14 +1615,14 @@ pub const SideEffects = enum(u1) {
.e_binary => |e| {
switch (e.op) {
.bin_logical_and => {
- const effects = SideEffects.toBoolean(e.right.data);
+ const effects = SideEffects.toBoolean(p, e.right.data);
if (effects.ok and effects.value and effects.side_effects == .no_side_effects) {
// "if (anything && truthyNoSideEffects)" => "if (anything)"
return e.left;
}
},
.bin_logical_or => {
- const effects = SideEffects.toBoolean(e.right.data);
+ const effects = SideEffects.toBoolean(p, e.right.data);
if (effects.ok and !effects.value and effects.side_effects == .no_side_effects) {
// "if (anything || falsyNoSideEffects)" => "if (anything)"
return e.left;
@@ -1647,6 +1648,7 @@ pub const SideEffects = enum(u1) {
}
pub fn simpifyUnusedExpr(p: anytype, expr: Expr) ?Expr {
+ if (!p.options.features.dead_code_elimination) return expr;
switch (expr.data) {
.e_null, .e_undefined, .e_missing, .e_boolean, .e_number, .e_big_int, .e_string, .e_this, .e_reg_exp, .e_function, .e_arrow, .e_import_meta => {
return null;
@@ -1909,7 +1911,8 @@ pub const SideEffects = enum(u1) {
//
// We can't trim the entire branch as dead or calling foo() will incorrectly
// assign to a global variable instead.
- pub fn shouldKeepStmtInDeadControlFlow(stmt: Stmt, allocator: Allocator) bool {
+ pub fn shouldKeepStmtInDeadControlFlow(p: anytype, stmt: Stmt, allocator: Allocator) bool {
+ if (!p.options.features.dead_code_elimination) return true;
switch (stmt.data) {
// Omit these statements entirely
.s_empty, .s_expr, .s_throw, .s_return, .s_break, .s_continue, .s_class, .s_debugger => return false,
@@ -1940,7 +1943,7 @@ pub const SideEffects = enum(u1) {
.s_block => |block| {
for (block.stmts) |child| {
- if (shouldKeepStmtInDeadControlFlow(child, allocator)) {
+ if (shouldKeepStmtInDeadControlFlow(p, child, allocator)) {
return true;
}
}
@@ -1949,43 +1952,43 @@ pub const SideEffects = enum(u1) {
},
.s_if => |_if_| {
- if (shouldKeepStmtInDeadControlFlow(_if_.yes, allocator)) {
+ if (shouldKeepStmtInDeadControlFlow(p, _if_.yes, allocator)) {
return true;
}
const no = _if_.no orelse return false;
- return shouldKeepStmtInDeadControlFlow(no, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, no, allocator);
},
.s_while => {
- return shouldKeepStmtInDeadControlFlow(stmt.data.s_while.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, stmt.data.s_while.body, allocator);
},
.s_do_while => {
- return shouldKeepStmtInDeadControlFlow(stmt.data.s_do_while.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, stmt.data.s_do_while.body, allocator);
},
.s_for => |__for__| {
if (__for__.init) |init_| {
- if (shouldKeepStmtInDeadControlFlow(init_, allocator)) {
+ if (shouldKeepStmtInDeadControlFlow(p, init_, allocator)) {
return true;
}
}
- return shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, __for__.body, allocator);
},
.s_for_in => |__for__| {
- return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, __for__.init, allocator) or shouldKeepStmtInDeadControlFlow(p, __for__.body, allocator);
},
.s_for_of => |__for__| {
- return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, __for__.init, allocator) or shouldKeepStmtInDeadControlFlow(p, __for__.body, allocator);
},
.s_label => |label| {
- return shouldKeepStmtInDeadControlFlow(label.stmt, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, label.stmt, allocator);
},
else => return true,
}
@@ -2091,11 +2094,15 @@ pub const SideEffects = enum(u1) {
pub const toTypeOf = Expr.Data.typeof;
- pub fn toNullOrUndefined(exp: Expr.Data) Result {
+ pub fn toNullOrUndefined(p: anytype, exp: 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) {
// 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 = SideEffects.no_side_effects, .ok = true };
+ return Result{ .value = false, .side_effects = .no_side_effects, .ok = true };
},
.e_object, .e_array, .e_class => {
@@ -2179,7 +2186,7 @@ pub const SideEffects = enum(u1) {
},
.bin_comma => {
- const res = toNullOrUndefined(e.right.data);
+ const res = toNullOrUndefined(p, e.right.data);
if (res.ok) {
return Result{ .ok = true, .value = res.value, .side_effects = SideEffects.could_have_side_effects };
}
@@ -2193,7 +2200,11 @@ pub const SideEffects = enum(u1) {
return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects };
}
- pub fn toBoolean(exp: Expr.Data) Result {
+ pub fn toBoolean(p: anytype, exp: 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) {
.e_null, .e_undefined => {
return Result{ .ok = true, .value = false, .side_effects = .no_side_effects };
@@ -2227,7 +2238,7 @@ pub const SideEffects = enum(u1) {
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
},
.un_not => {
- var result = toBoolean(e_.value.data);
+ var result = toBoolean(p, e_.value.data);
if (result.ok) {
result.value = !result.value;
return result;
@@ -2240,21 +2251,21 @@ pub const SideEffects = enum(u1) {
switch (e_.op) {
.bin_logical_or => {
// "anything || truthy" is truthy
- const result = toBoolean(e_.right.data);
+ const result = toBoolean(p, e_.right.data);
if (result.value and result.ok) {
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
}
},
.bin_logical_and => {
// "anything && falsy" is falsy
- const result = toBoolean(e_.right.data);
+ const result = toBoolean(p, e_.right.data);
if (!result.value and result.ok) {
return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
}
},
.bin_comma => {
// "anything, truthy/falsy" is truthy/falsy
- var result = toBoolean(e_.right.data);
+ var result = toBoolean(p, e_.right.data);
if (result.ok) {
result.side_effects = .could_have_side_effects;
return result;
@@ -4952,20 +4963,20 @@ fn NewParser_(
}
},
.s_if => |if_statement| {
- const result = SideEffects.toBoolean(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(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(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;
}
@@ -12573,7 +12584,7 @@ fn NewParser_(
// Remove unnecessary optional chains
if (p.options.features.minify_syntax) {
- const result = SideEffects.toNullOrUndefined(left.data);
+ const result = SideEffects.toNullOrUndefined(p, left.data);
if (result.ok and !result.value) {
optional_start = null;
}
@@ -14010,7 +14021,7 @@ fn NewParser_(
.name = part,
.name_loc = loc,
- .can_be_removed_if_unused = true,
+ .can_be_removed_if_unused = p.options.features.dead_code_elimination,
},
loc,
);
@@ -14473,6 +14484,7 @@ fn NewParser_(
}
fn bindingCanBeRemovedIfUnused(p: *P, binding: Binding) bool {
+ if (!p.options.features.dead_code_elimination) return false;
switch (binding.data) {
.b_array => |bi| {
for (bi.items) |*item| {
@@ -14511,6 +14523,7 @@ fn NewParser_(
}
fn stmtsCanBeRemovedIfUnused(p: *P, stmts: []Stmt) bool {
+ if (!p.options.features.dead_code_elimination) return false;
for (stmts) |stmt| {
switch (stmt.data) {
// These never have side effects
@@ -15344,7 +15357,7 @@ fn NewParser_(
// Mark the control flow as dead if the branch is never taken
switch (e_.op) {
.bin_logical_or => {
- const side_effects = SideEffects.toBoolean(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;
@@ -15356,7 +15369,7 @@ fn NewParser_(
}
},
.bin_logical_and => {
- const side_effects = SideEffects.toBoolean(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;
@@ -15368,7 +15381,7 @@ fn NewParser_(
}
},
.bin_nullish_coalescing => {
- const side_effects = SideEffects.toNullOrUndefined(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;
@@ -15448,7 +15461,7 @@ fn NewParser_(
}
},
.bin_nullish_coalescing => {
- const nullorUndefined = SideEffects.toNullOrUndefined(e_.left.data);
+ const nullorUndefined = SideEffects.toNullOrUndefined(p, e_.left.data);
if (nullorUndefined.ok) {
if (!nullorUndefined.value) {
return e_.left;
@@ -15465,7 +15478,7 @@ fn NewParser_(
}
},
.bin_logical_or => {
- const side_effects = SideEffects.toBoolean(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) {
@@ -15480,7 +15493,7 @@ fn NewParser_(
}
},
.bin_logical_and => {
- const side_effects = SideEffects.toBoolean(e_.left.data);
+ const side_effects = SideEffects.toBoolean(p, e_.left.data);
if (side_effects.ok) {
if (!side_effects.value) {
return e_.left;
@@ -15846,7 +15859,7 @@ fn NewParser_(
if (p.options.features.minify_syntax)
e_.value = SideEffects.simplifyBoolean(p, e_.value);
- const side_effects = SideEffects.toBoolean(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);
}
@@ -15998,7 +16011,7 @@ fn NewParser_(
e_.test_ = SideEffects.simplifyBoolean(p, e_.test_);
- const side_effects = SideEffects.toBoolean(e_.test_.data);
+ const side_effects = SideEffects.toBoolean(p, e_.test_.data);
if (!side_effects.ok) {
e_.yes = p.visitExpr(e_.yes);
@@ -16571,6 +16584,7 @@ fn NewParser_(
}
pub fn classCanBeRemovedIfUnused(p: *P, class: *G.Class) bool {
+ if (!p.options.features.dead_code_elimination) return false;
if (class.extends) |*extends| {
if (!p.exprCanBeRemovedIfUnused(extends)) {
return false;
@@ -16609,6 +16623,7 @@ fn NewParser_(
// When React Fast Refresh is enabled, anything that's a JSX component should not be removable
// This is to improve the reliability of fast refresh between page loads.
pub fn exprCanBeRemovedIfUnused(p: *P, expr: *const Expr) bool {
+ if (!p.options.features.dead_code_elimination) return false;
switch (expr.data) {
.e_null,
.e_undefined,
@@ -17743,7 +17758,7 @@ fn NewParser_(
const orig_dead = p.is_control_flow_dead;
if (p.options.features.replace_exports.count() > 0) {
if (p.options.features.replace_exports.getPtr("default")) |entry| {
- p.is_control_flow_dead = entry.* != .replace;
+ p.is_control_flow_dead = p.options.features.dead_code_elimination and (entry.* != .replace);
mark_for_replace = true;
}
}
@@ -17997,7 +18012,8 @@ fn NewParser_(
}
},
.s_expr => |data| {
- const should_trim_primitive = !p.options.features.minify_syntax and !data.value.isPrimitiveLiteral();
+ const should_trim_primitive = p.options.features.dead_code_elimination and
+ (p.options.features.minify_syntax and data.value.isPrimitiveLiteral());
p.stmt_expr_value = data.value.data;
defer p.stmt_expr_value = .{ .e_missing = .{} };
@@ -18168,7 +18184,7 @@ fn NewParser_(
data.body = p.visitLoopBody(data.body);
data.test_ = SideEffects.simplifyBoolean(p, data.test_);
- const result = SideEffects.toBoolean(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);
}
@@ -18186,7 +18202,7 @@ fn NewParser_(
data.test_ = SideEffects.simplifyBoolean(p, data.test_);
}
- const effects = SideEffects.toBoolean(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;
@@ -18218,7 +18234,7 @@ fn NewParser_(
if (p.options.features.minify_syntax) {
if (effects.ok) {
if (effects.value) {
- if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(data.no.?, p.allocator)) {
+ if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(p, data.no.?, p.allocator)) {
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.simpifyUnusedExpr(p, data.test_)) |test_| {
@@ -18232,7 +18248,7 @@ fn NewParser_(
}
} else {
// The test is falsy
- if (!SideEffects.shouldKeepStmtInDeadControlFlow(data.yes, p.allocator)) {
+ if (!SideEffects.shouldKeepStmtInDeadControlFlow(p, data.yes, p.allocator)) {
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.simpifyUnusedExpr(p, data.test_)) |test_| {
@@ -18287,7 +18303,7 @@ fn NewParser_(
if (data.test_) |test_| {
data.test_ = SideEffects.simplifyBoolean(p, p.visitExpr(test_));
- const result = SideEffects.toBoolean(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;
}
@@ -18428,7 +18444,8 @@ fn NewParser_(
.s_function => |data| {
// We mark it as dead, but the value may not actually be dead
// We just want to be sure to not increment the usage counts for anything in the function
- const mark_as_dead = data.func.flags.contains(.is_export) and p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.func.name.?.ref.?);
+ const mark_as_dead = p.options.features.dead_code_elimination and data.func.flags.contains(.is_export) and
+ p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.func.name.?.ref.?);
const original_is_dead = p.is_control_flow_dead;
if (mark_as_dead) {
@@ -18478,7 +18495,8 @@ fn NewParser_(
return;
},
.s_class => |data| {
- const mark_as_dead = data.is_export and p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.class.class_name.?.ref.?);
+ const mark_as_dead = p.options.features.dead_code_elimination and data.is_export and
+ p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.class.class_name.?.ref.?);
const original_is_dead = p.is_control_flow_dead;
if (mark_as_dead) {
@@ -18730,7 +18748,7 @@ fn NewParser_(
if (decl.binding.data == .b_identifier) {
if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(decl.binding.data.b_identifier.ref))) |replacer| {
replacement = replacer;
- if (replacer.* != .replace) {
+ if (p.options.features.dead_code_elimination and (replacer.* != .replace)) {
p.is_control_flow_dead = true;
}
}
@@ -20442,7 +20460,7 @@ fn NewParser_(
if (p.is_control_flow_dead) {
var end: usize = 0;
for (visited.items) |item| {
- if (!SideEffects.shouldKeepStmtInDeadControlFlow(item, p.allocator)) {
+ if (!SideEffects.shouldKeepStmtInDeadControlFlow(p, item, p.allocator)) {
continue;
}
@@ -20531,7 +20549,9 @@ fn NewParser_(
var output = ListManaged(Stmt).initCapacity(p.allocator, stmts.items.len) catch unreachable;
for (stmts.items) |stmt| {
- if (is_control_flow_dead and !SideEffects.shouldKeepStmtInDeadControlFlow(stmt, p.allocator)) {
+ if (is_control_flow_dead and p.options.features.dead_code_elimination and
+ !SideEffects.shouldKeepStmtInDeadControlFlow(p, stmt, p.allocator))
+ {
// Strip unnecessary statements if the control flow is dead here
continue;
}