mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(mininifier): minimize variants of a instanceof b == true (#8241)
This commit is contained in:
parent
ccdc039f54
commit
ad9a0a9c4a
3 changed files with 116 additions and 59 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use oxc_ast::ast::Expression;
|
||||
use oxc_ast::ast::{BinaryExpression, Expression};
|
||||
use oxc_syntax::operator::{BinaryOperator, UnaryOperator};
|
||||
|
||||
/// JavaScript Language Type
|
||||
|
|
@ -81,27 +81,11 @@ impl<'a> From<&Expression<'a>> for ValueType {
|
|||
Self::Number
|
||||
}
|
||||
UnaryOperator::UnaryPlus => Self::Number,
|
||||
UnaryOperator::LogicalNot => Self::Boolean,
|
||||
UnaryOperator::LogicalNot | UnaryOperator::Delete => Self::Boolean,
|
||||
UnaryOperator::Typeof => Self::String,
|
||||
_ => Self::Undetermined,
|
||||
},
|
||||
Expression::BinaryExpression(binary_expr) => match binary_expr.operator {
|
||||
BinaryOperator::Addition => {
|
||||
let left_ty = Self::from(&binary_expr.left);
|
||||
let right_ty = Self::from(&binary_expr.right);
|
||||
if left_ty == Self::String || right_ty == Self::String {
|
||||
return Self::String;
|
||||
}
|
||||
// There are some pretty weird cases for object types:
|
||||
// {} + [] === "0"
|
||||
// [] + {} === "[object Object]"
|
||||
if left_ty == Self::Object || right_ty == Self::Object {
|
||||
return Self::Undetermined;
|
||||
}
|
||||
Self::Undetermined
|
||||
}
|
||||
_ => Self::Undetermined,
|
||||
UnaryOperator::BitwiseNot => Self::Undetermined,
|
||||
},
|
||||
Expression::BinaryExpression(e) => Self::from(&**e),
|
||||
Expression::SequenceExpression(e) => {
|
||||
e.expressions.last().map_or(ValueType::Undetermined, Self::from)
|
||||
}
|
||||
|
|
@ -109,3 +93,26 @@ impl<'a> From<&Expression<'a>> for ValueType {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&BinaryExpression<'a>> for ValueType {
|
||||
fn from(e: &BinaryExpression<'a>) -> Self {
|
||||
match e.operator {
|
||||
BinaryOperator::Addition => {
|
||||
let left_ty = Self::from(&e.left);
|
||||
let right_ty = Self::from(&e.right);
|
||||
if left_ty == Self::String || right_ty == Self::String {
|
||||
return Self::String;
|
||||
}
|
||||
// There are some pretty weird cases for object types:
|
||||
// {} + [] === "0"
|
||||
// [] + {} === "[object Object]"
|
||||
if left_ty == Self::Object || right_ty == Self::Object {
|
||||
return Self::Undetermined;
|
||||
}
|
||||
Self::Undetermined
|
||||
}
|
||||
BinaryOperator::Instanceof => Self::Boolean,
|
||||
_ => Self::Undetermined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use oxc_allocator::Vec;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::constant_evaluation::ValueType;
|
||||
use oxc_span::{GetSpan, SPAN};
|
||||
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
|
||||
|
||||
|
|
@ -57,12 +58,9 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
|
|||
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if let Some(folded_expr) = match expr {
|
||||
Expression::UnaryExpression(e) => Self::try_minimize_not(e, ctx),
|
||||
Expression::LogicalExpression(logical_expr) => {
|
||||
Self::try_minimize_logical(logical_expr, ctx)
|
||||
}
|
||||
Expression::ConditionalExpression(conditional_expr) => {
|
||||
Self::try_minimize_conditional(conditional_expr, ctx)
|
||||
}
|
||||
Expression::LogicalExpression(e) => Self::try_minimize_logical(e, ctx),
|
||||
Expression::BinaryExpression(e) => Self::try_minimize_binary(e, ctx),
|
||||
Expression::ConditionalExpression(e) => Self::try_minimize_conditional(e, ctx),
|
||||
_ => None,
|
||||
} {
|
||||
*expr = folded_expr;
|
||||
|
|
@ -275,7 +273,7 @@ impl<'a> PeepholeMinimizeConditions {
|
|||
Expression::BooleanLiteral(consequent_lit),
|
||||
) = (&expr.left, &expr.right)
|
||||
{
|
||||
if !is_in_boolean_context(ctx) {
|
||||
if !Self::is_in_boolean_context(ctx) {
|
||||
return None;
|
||||
}
|
||||
if consequent_lit.value {
|
||||
|
|
@ -357,7 +355,7 @@ impl<'a> PeepholeMinimizeConditions {
|
|||
}
|
||||
}
|
||||
|
||||
let in_boolean_context = is_in_boolean_context(ctx);
|
||||
let in_boolean_context = Self::is_in_boolean_context(ctx);
|
||||
|
||||
// `a ? false : true` -> `!a`
|
||||
// `a ? true : false` -> `!!a`
|
||||
|
|
@ -456,39 +454,68 @@ impl<'a> PeepholeMinimizeConditions {
|
|||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// returns `true` if the current node is in a context in which the return
|
||||
// value type is coerced to boolean.
|
||||
// For example `if (condition)` and `return condition`
|
||||
// inside the `if` stmt, `condition` is coerced to a boolean
|
||||
// whereas inside the return, it is not
|
||||
fn is_in_boolean_context(ctx: &mut TraverseCtx<'_>) -> bool {
|
||||
for ancestor in ctx.ancestors() {
|
||||
match ancestor {
|
||||
Ancestor::IfStatementTest(_)
|
||||
| Ancestor::WhileStatementTest(_)
|
||||
| Ancestor::ForStatementTest(_)
|
||||
| Ancestor::DoWhileStatementTest(_)
|
||||
| Ancestor::ExpressionStatementExpression(_) => return true,
|
||||
Ancestor::CallExpressionArguments(_)
|
||||
| Ancestor::AssignmentPatternRight(_)
|
||||
| Ancestor::BindingRestElementArgument(_)
|
||||
| Ancestor::JSXSpreadAttributeArgument(_)
|
||||
| Ancestor::NewExpressionArguments(_)
|
||||
| Ancestor::ObjectPropertyKey(_)
|
||||
| Ancestor::ObjectPropertyValue(_)
|
||||
| Ancestor::ReturnStatementArgument(_)
|
||||
| Ancestor::ThrowStatementArgument(_)
|
||||
| Ancestor::YieldExpressionArgument(_)
|
||||
| Ancestor::VariableDeclaratorInit(_) => return false,
|
||||
_ => continue,
|
||||
// returns `true` if the current node is in a context in which the return
|
||||
// value type is coerced to boolean.
|
||||
// For example `if (condition)` and `return condition`
|
||||
// inside the `if` stmt, `condition` is coerced to a boolean
|
||||
// whereas inside the return, it is not
|
||||
fn is_in_boolean_context(ctx: &mut TraverseCtx<'_>) -> bool {
|
||||
for ancestor in ctx.ancestors() {
|
||||
match ancestor {
|
||||
Ancestor::IfStatementTest(_)
|
||||
| Ancestor::WhileStatementTest(_)
|
||||
| Ancestor::ForStatementTest(_)
|
||||
| Ancestor::DoWhileStatementTest(_)
|
||||
| Ancestor::ExpressionStatementExpression(_) => return true,
|
||||
Ancestor::CallExpressionArguments(_)
|
||||
| Ancestor::AssignmentPatternRight(_)
|
||||
| Ancestor::BindingRestElementArgument(_)
|
||||
| Ancestor::JSXSpreadAttributeArgument(_)
|
||||
| Ancestor::NewExpressionArguments(_)
|
||||
| Ancestor::ObjectPropertyKey(_)
|
||||
| Ancestor::ObjectPropertyValue(_)
|
||||
| Ancestor::ReturnStatementArgument(_)
|
||||
| Ancestor::ThrowStatementArgument(_)
|
||||
| Ancestor::YieldExpressionArgument(_)
|
||||
| Ancestor::VariableDeclaratorInit(_) => return false,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
unreachable!();
|
||||
#[cfg(not(debug_assertions))]
|
||||
false
|
||||
}
|
||||
|
||||
fn try_minimize_binary(
|
||||
e: &mut BinaryExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
// let ctx = Ctx(ctx);
|
||||
if !ValueType::from(&e.left).is_boolean() {
|
||||
return None;
|
||||
}
|
||||
let Expression::BooleanLiteral(b) = &mut e.right else {
|
||||
return None;
|
||||
};
|
||||
match e.operator {
|
||||
BinaryOperator::Inequality | BinaryOperator::StrictInequality => {
|
||||
e.operator = BinaryOperator::Equality;
|
||||
b.value = !b.value;
|
||||
}
|
||||
BinaryOperator::StrictEquality => {
|
||||
e.operator = BinaryOperator::Equality;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(if b.value {
|
||||
ctx.ast.move_expression(&mut e.left)
|
||||
} else {
|
||||
let argument = ctx.ast.move_expression(&mut e.left);
|
||||
ctx.ast.expression_unary(e.span, UnaryOperator::LogicalNot, argument)
|
||||
})
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
unreachable!();
|
||||
#[cfg(not(debug_assertions))]
|
||||
false
|
||||
}
|
||||
|
||||
/// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeMinimizeConditionsTest.java>
|
||||
|
|
@ -1598,4 +1625,27 @@ mod test {
|
|||
test_same("x.y ? x.y : bar");
|
||||
test_same("x.y ? bar : x.y");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compress_binary() {
|
||||
test("a instanceof b === true", "a instanceof b");
|
||||
test("a instanceof b == true", "a instanceof b");
|
||||
test("a instanceof b === false", "!(a instanceof b)");
|
||||
test("a instanceof b == false", "!(a instanceof b)");
|
||||
|
||||
test("a instanceof b !== true", "!(a instanceof b)");
|
||||
test("a instanceof b != true", "!(a instanceof b)");
|
||||
test("a instanceof b !== false", "a instanceof b");
|
||||
test("a instanceof b != false", "a instanceof b");
|
||||
|
||||
test("delete x === true", "delete x");
|
||||
test("delete x == true", "delete x");
|
||||
test("delete x === false", "!(delete x)");
|
||||
test("delete x == false", "!(delete x)");
|
||||
|
||||
test("delete x !== true", "!(delete x)");
|
||||
test("delete x != true", "!(delete x)");
|
||||
test("delete x !== false", "delete x");
|
||||
test("delete x != false", "delete x");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Original | minified | minified | gzip | gzip | Fixture
|
|||
|
||||
2.14 MB | 726.19 kB | 724.14 kB | 180.18 kB | 181.07 kB | victory.js
|
||||
|
||||
3.20 MB | 1.01 MB | 1.01 MB | 331.91 kB | 331.56 kB | echarts.js
|
||||
3.20 MB | 1.01 MB | 1.01 MB | 331.90 kB | 331.56 kB | echarts.js
|
||||
|
||||
6.69 MB | 2.32 MB | 2.31 MB | 492.80 kB | 488.28 kB | antd.js
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue