Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ impl<'a> PeepholeOptimizations {

pub fn substitute_call_expression(expr: &mut CallExpression<'a>, ctx: &mut Ctx<'a, '_>) {
Self::try_flatten_arguments(&mut expr.arguments, ctx);
Self::try_rewrite_object_callee_indirect_call(expr, ctx);
}

pub fn substitute_new_expression(expr: &mut NewExpression<'a>, ctx: &mut Ctx<'a, '_>) {
Expand Down Expand Up @@ -885,6 +886,49 @@ impl<'a> PeepholeOptimizations {
ctx.state.changed = true;
}

/// `Object(expr)(args)` -> `(0, expr)(args)`
///
/// If `expr` is `null` or `undefined`, both before and after throws an TypeError ("something is not a function").
/// It is because `Object(expr)` returns `{}`.
///
/// If `expr` is other primitive values, both before and after throws an TypeError ("something is not a function").
/// It is because `Object(expr)` returns the Object wrapped values (e.g. `new Boolean()`).
///
/// If `expr` is an object / function, `Object(expr)` returns `expr` as-is.
/// Note that we need to wrap `expr` as `(0, expr)` so that the `this` value is preserved.
///
/// <https://tc39.es/ecma262/2025/multipage/fundamental-objects.html#sec-object-value>
fn try_rewrite_object_callee_indirect_call(
expr: &mut CallExpression<'a>,
ctx: &mut Ctx<'a, '_>,
) {
let Expression::CallExpression(inner_call) = &mut expr.callee else { return };
if inner_call.optional || inner_call.arguments.len() != 1 {
return;
}
let Expression::Identifier(callee) = &inner_call.callee else {
return;
};
if callee.name != "Object" || !ctx.is_global_reference(callee) {
return;
}

let span = inner_call.span;
let Some(arg_expr) = inner_call.arguments[0].as_expression_mut() else {
return;
};

let new_callee = ctx.ast.expression_sequence(
span,
ctx.ast.vec_from_array([
ctx.ast.expression_numeric_literal(span, 0.0, None, NumberBase::Decimal),
arg_expr.take_in(ctx.ast),
]),
);
expr.callee = new_callee;
ctx.state.changed = true;
}

/// Remove name from function expressions if it is not used.
///
/// e.g. `var a = function f() {}` -> `var a = function () {}`
Expand Down Expand Up @@ -1832,4 +1876,14 @@ mod test {
test("var {y: z, 'z': y} = x", "var {y: z, z: y} = x");
test("var {y: y, 'z': z} = x", "var {y, z} = x");
}

#[test]
fn test_object_callee_indirect_call() {
test("Object(f)(1,2)", "f(1, 2)");
test("(Object(g))(a)", "g(a)");
test("Object(a.b)(x)", "(0, a.b)(x)");
test_same("Object?.(f)(1)");
test_same("function Object(x){return x} Object(f)(1)");
test_same("Object(...a)(1)");
}
}
4 changes: 2 additions & 2 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ Original | minified | minified | gzip | gzip | Iterations | Fi

1.25 MB | 646.98 kB | 646.76 kB | 160.27 kB | 163.73 kB | 2 | three.js

2.14 MB | 717.51 kB | 724.14 kB | 161.88 kB | 181.07 kB | 2 | victory.js
2.14 MB | 715.66 kB | 724.14 kB | 161.78 kB | 181.07 kB | 2 | victory.js

3.20 MB | 1.01 MB | 1.01 MB | 324.08 kB | 331.56 kB | 2 | echarts.js

6.69 MB | 2.25 MB | 2.31 MB | 463.12 kB | 488.28 kB | 3 | antd.js
6.69 MB | 2.23 MB | 2.31 MB | 462.26 kB | 488.28 kB | 3 | antd.js

10.95 MB | 3.35 MB | 3.49 MB | 860.97 kB | 915.50 kB | 2 | typescript.js

Loading