diff --git a/crates/oxc_minifier/src/compressor/mod.rs b/crates/oxc_minifier/src/compressor/mod.rs index cbbc820e18b5..bc98490cf04a 100644 --- a/crates/oxc_minifier/src/compressor/mod.rs +++ b/crates/oxc_minifier/src/compressor/mod.rs @@ -1,7 +1,6 @@ #![allow(clippy::unused_self)] -mod ast_util; -mod fold; +pub(crate) mod ast_util; mod options; mod util; @@ -16,6 +15,7 @@ use oxc_syntax::{ precedence::GetPrecedence, }; +use crate::folder::Folder; // use crate::ast_passes::RemoveParens; pub use self::options::CompressOptions; @@ -23,17 +23,18 @@ pub use self::options::CompressOptions; pub struct Compressor<'a> { ast: AstBuilder<'a>, options: CompressOptions, + // prepass: RemoveParens<'a>, + folder: Folder<'a>, } const SPAN: Span = Span::new(0, 0); impl<'a> Compressor<'a> { pub fn new(allocator: &'a Allocator, options: CompressOptions) -> Self { - Self { - ast: AstBuilder::new(allocator), - options, /* prepass: RemoveParens::new(allocator) */ - } + let ast = AstBuilder::new(allocator); + let folder = Folder::new(ast).with_evaluate(options.evaluate); + Self { ast, options /* prepass: RemoveParens::new(allocator) */, folder } } pub fn build(mut self, program: &mut Program<'a>) { @@ -328,7 +329,7 @@ impl<'a> VisitMut<'a> for Compressor<'a> { fn visit_statement(&mut self, stmt: &mut Statement<'a>) { self.compress_block(stmt); self.compress_while(stmt); - self.fold_condition(stmt); + self.folder.fold_condition(stmt); walk_mut::walk_statement(self, stmt); } @@ -348,7 +349,7 @@ impl<'a> VisitMut<'a> for Compressor<'a> { fn visit_expression(&mut self, expr: &mut Expression<'a>) { walk_mut::walk_expression(self, expr); self.compress_console(expr); - self.fold_expression(expr); + self.folder.fold_expression(expr); if !self.compress_undefined(expr) { self.compress_boolean(expr); } diff --git a/crates/oxc_minifier/src/compressor/fold.rs b/crates/oxc_minifier/src/folder/mod.rs similarity index 86% rename from crates/oxc_minifier/src/compressor/fold.rs rename to crates/oxc_minifier/src/folder/mod.rs index 4fbd47088fbf..03cc24701d2e 100644 --- a/crates/oxc_minifier/src/compressor/fold.rs +++ b/crates/oxc_minifier/src/folder/mod.rs @@ -2,183 +2,44 @@ //! //! +mod tri; +mod ty; +mod util; + use std::{cmp::Ordering, mem}; -use num_bigint::BigInt; -#[allow(clippy::wildcard_imports)] -use oxc_ast::ast::*; +use oxc_ast::{ast::*, AstBuilder}; use oxc_span::{Atom, GetSpan, Span}; use oxc_syntax::{ number::NumberBase, operator::{BinaryOperator, LogicalOperator, UnaryOperator}, }; -use super::{ - ast_util::{ - get_boolean_value, get_number_value, get_side_free_bigint_value, - get_side_free_number_value, get_side_free_string_value, get_string_value, is_exact_int64, - IsLiteralValue, MayHaveSideEffects, NumberValue, - }, - Compressor, +use crate::compressor::ast_util::{ + get_boolean_value, get_number_value, get_side_free_bigint_value, get_side_free_number_value, + get_side_free_string_value, get_string_value, is_exact_int64, IsLiteralValue, + MayHaveSideEffects, NumberValue, }; -/// Tri state -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Tri { - True, - False, - Unknown, -} - -impl Tri { - pub fn not(self) -> Self { - match self { - Self::True => Self::False, - Self::False => Self::True, - Self::Unknown => Self::Unknown, - } - } - - pub fn xor(self, other: Self) -> Self { - self.for_int(-self.value() * other.value()) - } - - pub fn for_int(self, int: i8) -> Self { - match int { - -1 => Self::False, - 1 => Self::True, - _ => Self::Unknown, - } - } - - pub fn for_boolean(boolean: bool) -> Self { - if boolean { - Self::True - } else { - Self::False - } - } +use tri::Tri; +use ty::Ty; +use util::bigint_less_than_number; - pub fn value(self) -> i8 { - match self { - Self::True => 1, - Self::False => -1, - Self::Unknown => 0, - } - } +pub struct Folder<'a> { + ast: AstBuilder<'a>, + evaluate: bool, } -/// ported from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1250) -#[allow(clippy::cast_possible_truncation)] -fn bigint_less_than_number( - bigint_value: &BigInt, - number_value: &NumberValue, - invert: Tri, - will_negative: bool, -) -> Tri { - // if invert is false, then the number is on the right in tryAbstractRelationalComparison - // if it's true, then the number is on the left - match number_value { - NumberValue::NaN => Tri::for_boolean(will_negative), - NumberValue::PositiveInfinity => Tri::True.xor(invert), - NumberValue::NegativeInfinity => Tri::False.xor(invert), - NumberValue::Number(num) => { - if let Some(Ordering::Equal | Ordering::Greater) = - num.abs().partial_cmp(&2_f64.powi(53)) - { - Tri::Unknown - } else { - let number_as_bigint = BigInt::from(*num as i64); - - match bigint_value.cmp(&number_as_bigint) { - Ordering::Less => Tri::True.xor(invert), - Ordering::Greater => Tri::False.xor(invert), - Ordering::Equal => { - if is_exact_int64(*num) { - Tri::False - } else { - Tri::for_boolean(num.is_sign_positive()).xor(invert) - } - } - } - } - } +impl<'a> Folder<'a> { + pub fn new(ast: AstBuilder<'a>) -> Self { + Self { ast, evaluate: false } } -} - -/// JavaScript Language Type -/// -/// -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Ty { - BigInt, - Boolean, - Null, - Number, - Object, - Str, - Void, - Undetermined, -} - -impl<'a> From<&Expression<'a>> for Ty { - fn from(expr: &Expression<'a>) -> Self { - // TODO: complete this - match expr { - Expression::BigIntLiteral(_) => Self::BigInt, - Expression::BooleanLiteral(_) => Self::Boolean, - Expression::NullLiteral(_) => Self::Null, - Expression::NumericLiteral(_) => Self::Number, - Expression::StringLiteral(_) => Self::Str, - Expression::ObjectExpression(_) - | Expression::ArrayExpression(_) - | Expression::RegExpLiteral(_) - | Expression::FunctionExpression(_) => Self::Object, - Expression::Identifier(ident) => match ident.name.as_str() { - "undefined" => Self::Void, - "NaN" | "Infinity" => Self::Number, - _ => Self::Undetermined, - }, - Expression::UnaryExpression(unary_expr) => match unary_expr.operator { - UnaryOperator::Void => Self::Void, - UnaryOperator::UnaryNegation => { - let argument_ty = Self::from(&unary_expr.argument); - if argument_ty == Self::BigInt { - return Self::BigInt; - } - Self::Number - } - UnaryOperator::UnaryPlus => Self::Number, - UnaryOperator::LogicalNot => Self::Boolean, - UnaryOperator::Typeof => Self::Str, - _ => 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::Str || right_ty == Self::Str { - return Self::Str; - } - - // 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, - }, - _ => Self::Undetermined, - } + pub fn with_evaluate(mut self, yes: bool) -> Self { + self.evaluate = yes; + self } -} -impl<'a> Compressor<'a> { pub(crate) fn fold_expression<'b>(&mut self, expr: &'b mut Expression<'a>) { let folded_expr = match expr { Expression::BinaryExpression(binary_expr) => match binary_expr.operator { @@ -209,7 +70,7 @@ impl<'a> Compressor<'a> { // don't match (even though the produced code is valid). Additionally, We'll likely // want to add `evaluate` checks for all constant folding, not just additions, but // we're adding this here until a decision is made. - BinaryOperator::Addition if self.options.evaluate => { + BinaryOperator::Addition if self.evaluate => { self.try_fold_addition(binary_expr.span, &binary_expr.left, &binary_expr.right) } _ => None, diff --git a/crates/oxc_minifier/src/folder/tri.rs b/crates/oxc_minifier/src/folder/tri.rs new file mode 100644 index 000000000000..9bc0f541c36c --- /dev/null +++ b/crates/oxc_minifier/src/folder/tri.rs @@ -0,0 +1,45 @@ +/// Tri state +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Tri { + True, + False, + Unknown, +} + +impl Tri { + pub fn not(self) -> Self { + match self { + Self::True => Self::False, + Self::False => Self::True, + Self::Unknown => Self::Unknown, + } + } + + pub fn xor(self, other: Self) -> Self { + self.for_int(-self.value() * other.value()) + } + + pub fn for_int(self, int: i8) -> Self { + match int { + -1 => Self::False, + 1 => Self::True, + _ => Self::Unknown, + } + } + + pub fn for_boolean(boolean: bool) -> Self { + if boolean { + Self::True + } else { + Self::False + } + } + + pub fn value(self) -> i8 { + match self { + Self::True => 1, + Self::False => -1, + Self::Unknown => 0, + } + } +} diff --git a/crates/oxc_minifier/src/folder/ty.rs b/crates/oxc_minifier/src/folder/ty.rs new file mode 100644 index 000000000000..445a42e1e06b --- /dev/null +++ b/crates/oxc_minifier/src/folder/ty.rs @@ -0,0 +1,74 @@ +use oxc_ast::ast::*; +use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; + +/// JavaScript Language Type +/// +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Ty { + BigInt, + Boolean, + Null, + Number, + Object, + Str, + Void, + Undetermined, +} + +impl<'a> From<&Expression<'a>> for Ty { + fn from(expr: &Expression<'a>) -> Self { + // TODO: complete this + match expr { + Expression::BigIntLiteral(_) => Self::BigInt, + Expression::BooleanLiteral(_) => Self::Boolean, + Expression::NullLiteral(_) => Self::Null, + Expression::NumericLiteral(_) => Self::Number, + Expression::StringLiteral(_) => Self::Str, + Expression::ObjectExpression(_) + | Expression::ArrayExpression(_) + | Expression::RegExpLiteral(_) + | Expression::FunctionExpression(_) => Self::Object, + Expression::Identifier(ident) => match ident.name.as_str() { + "undefined" => Self::Void, + "NaN" | "Infinity" => Self::Number, + _ => Self::Undetermined, + }, + Expression::UnaryExpression(unary_expr) => match unary_expr.operator { + UnaryOperator::Void => Self::Void, + UnaryOperator::UnaryNegation => { + let argument_ty = Self::from(&unary_expr.argument); + if argument_ty == Self::BigInt { + return Self::BigInt; + } + Self::Number + } + UnaryOperator::UnaryPlus => Self::Number, + UnaryOperator::LogicalNot => Self::Boolean, + UnaryOperator::Typeof => Self::Str, + _ => 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::Str || right_ty == Self::Str { + return Self::Str; + } + + // 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, + }, + _ => Self::Undetermined, + } + } +} diff --git a/crates/oxc_minifier/src/folder/util.rs b/crates/oxc_minifier/src/folder/util.rs new file mode 100644 index 000000000000..a99983b2a22e --- /dev/null +++ b/crates/oxc_minifier/src/folder/util.rs @@ -0,0 +1,45 @@ +use std::cmp::Ordering; + +use num_bigint::BigInt; + +use super::tri::Tri; + +use crate::compressor::ast_util::{is_exact_int64, NumberValue}; + +/// ported from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1250) +#[allow(clippy::cast_possible_truncation)] +pub fn bigint_less_than_number( + bigint_value: &BigInt, + number_value: &NumberValue, + invert: Tri, + will_negative: bool, +) -> Tri { + // if invert is false, then the number is on the right in tryAbstractRelationalComparison + // if it's true, then the number is on the left + match number_value { + NumberValue::NaN => Tri::for_boolean(will_negative), + NumberValue::PositiveInfinity => Tri::True.xor(invert), + NumberValue::NegativeInfinity => Tri::False.xor(invert), + NumberValue::Number(num) => { + if let Some(Ordering::Equal | Ordering::Greater) = + num.abs().partial_cmp(&2_f64.powi(53)) + { + Tri::Unknown + } else { + let number_as_bigint = BigInt::from(*num as i64); + + match bigint_value.cmp(&number_as_bigint) { + Ordering::Less => Tri::True.xor(invert), + Ordering::Greater => Tri::False.xor(invert), + Ordering::Equal => { + if is_exact_int64(*num) { + Tri::False + } else { + Tri::for_boolean(num.is_sign_positive()).xor(invert) + } + } + } + } + } + } +} diff --git a/crates/oxc_minifier/src/lib.rs b/crates/oxc_minifier/src/lib.rs index d644212e1107..56df57785c03 100644 --- a/crates/oxc_minifier/src/lib.rs +++ b/crates/oxc_minifier/src/lib.rs @@ -1,7 +1,9 @@ +#![allow(clippy::wildcard_imports, clippy::unused_self)] //! ECMAScript Minifier mod ast_passes; mod compressor; +mod folder; mod mangler; use oxc_allocator::Allocator;