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
17 changes: 9 additions & 8 deletions crates/oxc_minifier/src/compressor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#![allow(clippy::unused_self)]

mod ast_util;
mod fold;
pub(crate) mod ast_util;
mod options;
mod util;

Expand All @@ -16,24 +15,26 @@ use oxc_syntax::{
precedence::GetPrecedence,
};

use crate::folder::Folder;
// use crate::ast_passes::RemoveParens;

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>) {
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,183 +2,44 @@
//!
//! <https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>

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
///
/// <https://tc39.es/ecma262/#sec-ecmascript-language-types>
#[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 {
Expand Down Expand Up @@ -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,
Expand Down
45 changes: 45 additions & 0 deletions crates/oxc_minifier/src/folder/tri.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
74 changes: 74 additions & 0 deletions crates/oxc_minifier/src/folder/ty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use oxc_ast::ast::*;
use oxc_syntax::operator::{BinaryOperator, UnaryOperator};

/// JavaScript Language Type
///
/// <https://tc39.es/ecma262/#sec-ecmascript-language-types>
#[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,
}
}
}
Loading