mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 18:43:44 +00:00
fix(minifier): preserve optional chain base side effects (#22219)
Currently ```ts x = (foo(), null)?.y ``` is folded to ```ts x = void 0; ``` This is incorrect as `foo` may have side effect. This PR fixes this and insteads folds it to: ```ts x = (foo(), void 0); ``` This preserves the side effect of the call to `foo`.
This commit is contained in:
1 parent
e554cd8f90
commit
0e13d17c8b
3 files changed
+21
-8
No files matched your search
@@ -74,27 +74,33 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
pub fn fold_chain_expr(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::ChainExpression(e) = expr else { return };
|
||||
let left_expr = match &e.expression {
|
||||
let span = e.span;
|
||||
let left_expr = match &mut e.expression {
|
||||
match_member_expression!(ChainElement) => {
|
||||
let member_expr = e.expression.to_member_expression();
|
||||
let member_expr = e.expression.to_member_expression_mut();
|
||||
if !member_expr.optional() {
|
||||
return;
|
||||
}
|
||||
member_expr.object()
|
||||
member_expr.object_mut()
|
||||
}
|
||||
ChainElement::CallExpression(call_expr) => {
|
||||
if !call_expr.optional {
|
||||
return;
|
||||
}
|
||||
&call_expr.callee
|
||||
&mut call_expr.callee
|
||||
}
|
||||
ChainElement::TSNonNullExpression(_) => return,
|
||||
};
|
||||
let ty = left_expr.value_type(ctx);
|
||||
if let Some(changed) = (ty.is_null() || ty.is_undefined())
|
||||
.then(|| ctx.value_to_expr(e.span, ConstantValue::Undefined))
|
||||
{
|
||||
*expr = changed;
|
||||
if ty.is_null() || ty.is_undefined() {
|
||||
*expr = if left_expr.may_have_side_effects(ctx) {
|
||||
ctx.ast.expression_sequence(
|
||||
span,
|
||||
ctx.ast.vec_from_array([left_expr.take_in(ctx.ast), ctx.ast.void_0(span)]),
|
||||
)
|
||||
} else {
|
||||
ctx.value_to_expr(span, ConstantValue::Undefined)
|
||||
};
|
||||
ctx.state.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,6 +684,8 @@ fn test_fold_opt_chain() {
|
||||
fold("x = null?.[foo]", "x = void 0");
|
||||
fold("x = undefined?.()", "x = void 0");
|
||||
fold("x = null?.()", "x = void 0");
|
||||
fold("x = (foo(), null)?.y", "x = (foo(), void 0)");
|
||||
fold("x = (foo(), null)?.()", "x = (foo(), void 0)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -24,6 +24,11 @@ fn test_remove_unused_expression() {
|
||||
test("void x", "x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_unused_optional_chain_keeps_base_side_effects() {
|
||||
test("let log = []; (log.push('base'), null)?.x;", "[].push('base')");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_unused_this() {
|
||||
// In a derived class constructor, `this` before `super()` throws a ReferenceError,
|
||||
|
||||
Reference in new issue
Block a user