mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(minifier): fold array and object constructors (#6257)
This will fold expressions like `new Object()` to `{}`, and `new Array()` to `[]`. Based on the closure compiler tests: b7e380b632/test/com/google/javascript/jscomp/PeepholeSubstituteAlternateSyntaxTest.java (L78).
This is outside my usual area, so feedback is welcome.
NOTE: this was previously a full stack of PRs, but Graphite decided to stop working completely for some reason and only gave me this error when I submitted a PR:
```
ERROR: Failed to submit PR for 10-02-feat_minifier_fold_single_arg_new_array_expressions:
{}
```
so I decided to just completely remake this stack and submit as 1 PR.
This commit is contained in:
parent
93c6db6550
commit
4008afe512
2 changed files with 214 additions and 8 deletions
|
|
@ -1,4 +1,6 @@
|
|||
use oxc_ast::ast::*;
|
||||
use oxc_allocator::Vec;
|
||||
use oxc_ast::{ast::*, NONE};
|
||||
use oxc_semantic::IsGlobalReference;
|
||||
use oxc_span::{GetSpan, SPAN};
|
||||
use oxc_syntax::number::ToJsInt32;
|
||||
use oxc_syntax::{
|
||||
|
|
@ -90,6 +92,24 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
|
|||
}
|
||||
}
|
||||
|
||||
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
match expr {
|
||||
Expression::NewExpression(new_expr) => {
|
||||
if let Some(new_expr) = self.try_fold_new_expression(new_expr, ctx) {
|
||||
*expr = new_expr;
|
||||
self.changed = true;
|
||||
}
|
||||
}
|
||||
Expression::CallExpression(call_expr) => {
|
||||
if let Some(call_expr) = self.try_fold_call_expression(call_expr, ctx) {
|
||||
*expr = call_expr;
|
||||
self.changed = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_binary_expression(
|
||||
&mut self,
|
||||
expr: &mut BinaryExpression<'a>,
|
||||
|
|
@ -328,6 +348,145 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fold_new_expression(
|
||||
&mut self,
|
||||
new_expr: &mut NewExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
// `new Object` -> `{}`
|
||||
if new_expr.arguments.is_empty()
|
||||
&& new_expr.callee.is_global_reference_name("Object", ctx.symbols())
|
||||
{
|
||||
Some(ctx.ast.expression_object(new_expr.span, Vec::new_in(ctx.ast.allocator), None))
|
||||
} else if new_expr.callee.is_global_reference_name("Array", ctx.symbols()) {
|
||||
// `new Array` -> `[]`
|
||||
if new_expr.arguments.is_empty() {
|
||||
Some(self.empty_array_literal(ctx))
|
||||
} else if new_expr.arguments.len() == 1 {
|
||||
let arg = new_expr.arguments.get_mut(0).and_then(|arg| arg.as_expression_mut())?;
|
||||
// `new Array(0)` -> `[]`
|
||||
if arg.is_number_0() {
|
||||
Some(self.empty_array_literal(ctx))
|
||||
}
|
||||
// `new Array(8)` -> `Array(8)`
|
||||
else if arg.is_number_literal() {
|
||||
Some(
|
||||
self.array_constructor_call(ctx.ast.move_vec(&mut new_expr.arguments), ctx),
|
||||
)
|
||||
}
|
||||
// `new Array(literal)` -> `[literal]`
|
||||
else if arg.is_literal() || matches!(arg, Expression::ArrayExpression(_)) {
|
||||
let mut elements = Vec::new_in(ctx.ast.allocator);
|
||||
let element =
|
||||
ctx.ast.array_expression_element_expression(ctx.ast.move_expression(arg));
|
||||
elements.push(element);
|
||||
Some(self.array_literal(elements, ctx))
|
||||
}
|
||||
// `new Array()` -> `Array()`
|
||||
else {
|
||||
Some(
|
||||
self.array_constructor_call(ctx.ast.move_vec(&mut new_expr.arguments), ctx),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// `new Array(1, 2, 3)` -> `[1, 2, 3]`
|
||||
let elements = Vec::from_iter_in(
|
||||
new_expr.arguments.iter_mut().filter_map(|arg| arg.as_expression_mut()).map(
|
||||
|arg| {
|
||||
ctx.ast
|
||||
.array_expression_element_expression(ctx.ast.move_expression(arg))
|
||||
},
|
||||
),
|
||||
ctx.ast.allocator,
|
||||
);
|
||||
Some(self.array_literal(elements, ctx))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fold_call_expression(
|
||||
&mut self,
|
||||
call_expr: &mut CallExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
// `Object()` -> `{}`
|
||||
if call_expr.arguments.is_empty()
|
||||
&& call_expr.callee.is_global_reference_name("Object", ctx.symbols())
|
||||
{
|
||||
Some(ctx.ast.expression_object(call_expr.span, Vec::new_in(ctx.ast.allocator), None))
|
||||
} else if call_expr.callee.is_global_reference_name("Array", ctx.symbols()) {
|
||||
// `Array()` -> `[]`
|
||||
if call_expr.arguments.is_empty() {
|
||||
Some(self.empty_array_literal(ctx))
|
||||
} else if call_expr.arguments.len() == 1 {
|
||||
let arg = call_expr.arguments.get_mut(0).and_then(|arg| arg.as_expression_mut())?;
|
||||
// `Array(0)` -> `[]`
|
||||
if arg.is_number_0() {
|
||||
Some(self.empty_array_literal(ctx))
|
||||
}
|
||||
// `Array(8)` -> `Array(8)`
|
||||
else if arg.is_number_literal() {
|
||||
Some(
|
||||
self.array_constructor_call(
|
||||
ctx.ast.move_vec(&mut call_expr.arguments),
|
||||
ctx,
|
||||
),
|
||||
)
|
||||
}
|
||||
// `Array(literal)` -> `[literal]`
|
||||
else if arg.is_literal() || matches!(arg, Expression::ArrayExpression(_)) {
|
||||
let mut elements = Vec::new_in(ctx.ast.allocator);
|
||||
let element =
|
||||
ctx.ast.array_expression_element_expression(ctx.ast.move_expression(arg));
|
||||
elements.push(element);
|
||||
Some(self.array_literal(elements, ctx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
// `Array(1, 2, 3)` -> `[1, 2, 3]`
|
||||
let elements = Vec::from_iter_in(
|
||||
call_expr.arguments.iter_mut().filter_map(|arg| arg.as_expression_mut()).map(
|
||||
|arg| {
|
||||
ctx.ast
|
||||
.array_expression_element_expression(ctx.ast.move_expression(arg))
|
||||
},
|
||||
),
|
||||
ctx.ast.allocator,
|
||||
);
|
||||
Some(self.array_literal(elements, ctx))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// returns an `Array()` constructor call with zero, one, or more arguments, copying from the input
|
||||
fn array_constructor_call(
|
||||
&self,
|
||||
arguments: Vec<'a, Argument<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let callee = ctx.ast.expression_identifier_reference(SPAN, "Array");
|
||||
ctx.ast.expression_call(SPAN, callee, NONE, arguments, false)
|
||||
}
|
||||
|
||||
/// returns an array literal `[]` of zero, one, or more elements, copying from the input
|
||||
fn array_literal(
|
||||
&self,
|
||||
elements: Vec<'a, ArrayExpressionElement<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
ctx.ast.expression_array(SPAN, elements, None)
|
||||
}
|
||||
|
||||
/// returns a new empty array literal expression: `[]`
|
||||
fn empty_array_literal(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
|
||||
self.array_literal(Vec::new_in(ctx.ast.allocator), ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://github.com/google/closure-compiler/blob/master/test/com/google/javascript/jscomp/PeepholeSubstituteAlternateSyntaxTest.java>
|
||||
|
|
@ -402,4 +561,51 @@ mod test {
|
|||
test_same("x += 1"); // The string concatenation may be triggered, so we don't fold this.
|
||||
test_same("x += -1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fold_literal_object_constructors() {
|
||||
test("x = new Object", "x = ({})");
|
||||
test("x = new Object()", "x = ({})");
|
||||
test("x = Object()", "x = ({})");
|
||||
|
||||
test_same("x = (function f(){function Object(){this.x=4}return new Object();})();");
|
||||
}
|
||||
|
||||
// tests from closure compiler
|
||||
#[test]
|
||||
fn fold_literal_array_constructors() {
|
||||
test("x = new Array", "x = []");
|
||||
test("x = new Array()", "x = []");
|
||||
test("x = Array()", "x = []");
|
||||
// do not fold optional chains
|
||||
test_same("x = Array?.()");
|
||||
|
||||
// One argument
|
||||
test("x = new Array(0)", "x = []");
|
||||
test("x = new Array(\"a\")", "x = [\"a\"]");
|
||||
test("x = new Array(7)", "x = Array(7)");
|
||||
test("x = new Array(y)", "x = Array(y)");
|
||||
test("x = new Array(foo())", "x = Array(foo())");
|
||||
test("x = Array(0)", "x = []");
|
||||
test("x = Array(\"a\")", "x = [\"a\"]");
|
||||
test_same("x = Array(7)");
|
||||
test_same("x = Array(y)");
|
||||
test_same("x = Array(foo())");
|
||||
|
||||
// 1+ arguments
|
||||
test("x = new Array(1, 2, 3, 4)", "x = [1, 2, 3, 4]");
|
||||
test("x = Array(1, 2, 3, 4)", "x = [1, 2, 3, 4]");
|
||||
test("x = new Array('a', 1, 2, 'bc', 3, {}, 'abc')", "x = ['a', 1, 2, 'bc', 3, {}, 'abc']");
|
||||
test("x = Array('a', 1, 2, 'bc', 3, {}, 'abc')", "x = ['a', 1, 2, 'bc', 3, {}, 'abc']");
|
||||
test("x = new Array(Array(1, '2', 3, '4'))", "x = [[1, '2', 3, '4']]");
|
||||
test("x = Array(Array(1, '2', 3, '4'))", "x = [[1, '2', 3, '4']]");
|
||||
test(
|
||||
"x = new Array(Object(), Array(\"abc\", Object(), Array(Array())))",
|
||||
"x = [{}, [\"abc\", {}, [[]]]]",
|
||||
);
|
||||
test(
|
||||
"x = new Array(Object(), Array(\"abc\", Object(), Array(Array())))",
|
||||
"x = [{}, [\"abc\", {}, [[]]]]",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
Original | Minified | esbuild | Gzip | esbuild
|
||||
|
||||
72.14 kB | 24.47 kB | 23.70 kB | 8.65 kB | 8.54 kB | react.development.js
|
||||
72.14 kB | 24.46 kB | 23.70 kB | 8.65 kB | 8.54 kB | react.development.js
|
||||
|
||||
173.90 kB | 61.69 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js
|
||||
|
||||
287.63 kB | 92.83 kB | 90.07 kB | 32.29 kB | 31.95 kB | jquery.js
|
||||
|
||||
342.15 kB | 124.14 kB | 118.14 kB | 44.81 kB | 44.37 kB | vue.js
|
||||
342.15 kB | 124.11 kB | 118.14 kB | 44.80 kB | 44.37 kB | vue.js
|
||||
|
||||
544.10 kB | 74.13 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js
|
||||
|
||||
555.77 kB | 278.70 kB | 270.13 kB | 91.39 kB | 90.80 kB | d3.js
|
||||
555.77 kB | 278.24 kB | 270.13 kB | 91.36 kB | 90.80 kB | d3.js
|
||||
|
||||
1.01 MB | 470.11 kB | 458.89 kB | 126.97 kB | 126.71 kB | bundle.min.js
|
||||
|
||||
1.25 MB | 671.00 kB | 646.76 kB | 164.72 kB | 163.73 kB | three.js
|
||||
1.25 MB | 670.97 kB | 646.76 kB | 164.72 kB | 163.73 kB | three.js
|
||||
|
||||
2.14 MB | 756.69 kB | 724.14 kB | 182.87 kB | 181.07 kB | victory.js
|
||||
2.14 MB | 756.33 kB | 724.14 kB | 182.74 kB | 181.07 kB | victory.js
|
||||
|
||||
3.20 MB | 1.05 MB | 1.01 MB | 334.10 kB | 331.56 kB | echarts.js
|
||||
|
||||
6.69 MB | 2.44 MB | 2.31 MB | 498.93 kB | 488.28 kB | antd.js
|
||||
6.69 MB | 2.44 MB | 2.31 MB | 498.86 kB | 488.28 kB | antd.js
|
||||
|
||||
10.95 MB | 3.59 MB | 3.49 MB | 913.96 kB | 915.50 kB | typescript.js
|
||||
10.95 MB | 3.59 MB | 3.49 MB | 913.92 kB | 915.50 kB | typescript.js
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue