mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(minifier): partially re-enable minifier (#963)
closes #949 closes #950 closes #951 All minifier tests are disable from this PR. We are going to fix the compilation errors first, then the behavioral errors.
This commit is contained in:
parent
9ad2634091
commit
55b2f031df
17 changed files with 971 additions and 245 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -1592,6 +1592,28 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxc_minifier"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"insta",
|
||||
"itertools 0.11.0",
|
||||
"jemallocator",
|
||||
"mimalloc",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
"oxc_index",
|
||||
"oxc_parser",
|
||||
"oxc_semantic",
|
||||
"oxc_span",
|
||||
"oxc_syntax",
|
||||
"pico-args",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxc_napi"
|
||||
version = "0.0.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["crates/*", "tasks/*", "editor/vscode/server"]
|
||||
exclude = ["crates/oxc_minifier", "tasks/minsize"]
|
||||
exclude = ["tasks/minsize"]
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Boshen <boshenc@gmail.com>", "Oxc contributors"]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use oxc_syntax::{
|
|||
operator::{
|
||||
AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator,
|
||||
},
|
||||
reference::ReferenceId,
|
||||
reference::{ReferenceFlag, ReferenceId},
|
||||
symbol::SymbolId,
|
||||
};
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
@ -163,6 +163,11 @@ impl<'a> Expression<'a> {
|
|||
self.is_null() || self.evaluate_to_undefined()
|
||||
}
|
||||
|
||||
/// Determines whether the given expr is a `NaN` literal
|
||||
pub fn is_nan(&self) -> bool {
|
||||
matches!(self, Self::Identifier(ident) if ident.name == "NaN")
|
||||
}
|
||||
|
||||
/// Remove nested parentheses from this expression.
|
||||
pub fn without_parenthesized(&self) -> &Self {
|
||||
match self {
|
||||
|
|
@ -246,6 +251,23 @@ impl<'a> Expression<'a> {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_immutable_value(&self) -> bool {
|
||||
match self {
|
||||
Self::BooleanLiteral(_)
|
||||
| Self::NullLiteral(_)
|
||||
| Self::NumberLiteral(_)
|
||||
| Self::BigintLiteral(_)
|
||||
| Self::RegExpLiteral(_)
|
||||
| Self::StringLiteral(_) => true,
|
||||
Self::TemplateLiteral(lit) if lit.is_no_substitution_template() => true,
|
||||
Self::UnaryExpression(unary_expr) => unary_expr.argument.is_immutable_value(),
|
||||
Self::Identifier(ident) => {
|
||||
matches!(ident.name.as_str(), "undefined" | "Infinity" | "NaN")
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier Name
|
||||
|
|
@ -272,6 +294,8 @@ pub struct IdentifierReference {
|
|||
pub name: Atom,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub reference_id: Cell<Option<ReferenceId>>,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub reference_flag: ReferenceFlag,
|
||||
}
|
||||
|
||||
impl Hash for IdentifierReference {
|
||||
|
|
@ -283,7 +307,7 @@ impl Hash for IdentifierReference {
|
|||
|
||||
impl IdentifierReference {
|
||||
pub fn new(span: Span, name: Atom) -> Self {
|
||||
Self { span, name, reference_id: Cell::default() }
|
||||
Self { span, name, reference_id: Cell::default(), reference_flag: ReferenceFlag::default() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,32 @@ impl<'a> NumberLiteral<'a> {
|
|||
pub fn new(span: Span, value: f64, raw: &'a str, base: NumberBase) -> Self {
|
||||
Self { span, value, raw, base }
|
||||
}
|
||||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/base/JSCompDoubles.java#L113)
|
||||
/// <https://262.ecma-international.org/5.1/#sec-9.5>
|
||||
#[allow(clippy::cast_possible_truncation)] // for `as i32`
|
||||
pub fn ecmascript_to_int32(num: f64) -> i32 {
|
||||
// Fast path for most common case. Also covers -0.0
|
||||
let int32_value = num as i32;
|
||||
if (f64::from(int32_value) - num).abs() < f64::EPSILON {
|
||||
return int32_value;
|
||||
}
|
||||
|
||||
// NaN, Infinity if not included in our NumberLiteral, so we just skip step 2.
|
||||
|
||||
// step 3
|
||||
let pos_int = num.signum() * num.abs().floor();
|
||||
|
||||
// step 4
|
||||
let int32bit = pos_int % 2f64.powi(32);
|
||||
|
||||
// step5
|
||||
if int32bit >= 2f64.powi(31) {
|
||||
(int32bit - 2f64.powi(32)) as i32
|
||||
} else {
|
||||
int32bit as i32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hash for NumberLiteral<'a> {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
clippy::unused_self,
|
||||
)]
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use std::mem;
|
||||
|
||||
use oxc_allocator::{Allocator, Box, String, Vec};
|
||||
use oxc_span::{Atom, GetSpan, SourceType, Span};
|
||||
use oxc_syntax::{
|
||||
|
|
@ -13,7 +16,6 @@ use oxc_syntax::{
|
|||
},
|
||||
NumberBase,
|
||||
};
|
||||
use std::mem;
|
||||
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use crate::ast::*;
|
||||
|
|
@ -88,8 +90,12 @@ impl<'a> AstBuilder<'a> {
|
|||
|
||||
/* ---------- Literals ---------- */
|
||||
|
||||
pub fn string_literal(&self, span: Span, value: Atom) -> StringLiteral {
|
||||
StringLiteral { span, value }
|
||||
}
|
||||
|
||||
pub fn number_literal(
|
||||
&mut self,
|
||||
&self,
|
||||
span: Span,
|
||||
value: f64,
|
||||
raw: &'a str,
|
||||
|
|
@ -98,6 +104,44 @@ impl<'a> AstBuilder<'a> {
|
|||
NumberLiteral { span, value, raw, base }
|
||||
}
|
||||
|
||||
pub fn boolean_literal(&self, span: Span, value: bool) -> BooleanLiteral {
|
||||
BooleanLiteral { span, value }
|
||||
}
|
||||
|
||||
pub fn null_literal(&self, span: Span) -> NullLiteral {
|
||||
NullLiteral { span }
|
||||
}
|
||||
|
||||
pub fn bigint_literal(&self, span: Span, value: BigInt) -> BigintLiteral {
|
||||
BigintLiteral { span, value }
|
||||
}
|
||||
|
||||
pub fn template_literal(
|
||||
&self,
|
||||
span: Span,
|
||||
quasis: Vec<'a, TemplateElement>,
|
||||
expressions: Vec<'a, Expression<'a>>,
|
||||
) -> TemplateLiteral<'a> {
|
||||
TemplateLiteral { span, quasis, expressions }
|
||||
}
|
||||
|
||||
pub fn template_element(
|
||||
&self,
|
||||
span: Span,
|
||||
tail: bool,
|
||||
value: TemplateElementValue,
|
||||
) -> TemplateElement {
|
||||
TemplateElement { span, tail, value }
|
||||
}
|
||||
|
||||
pub fn template_element_value(&self, raw: Atom, cooked: Option<Atom>) -> TemplateElementValue {
|
||||
TemplateElementValue { raw, cooked }
|
||||
}
|
||||
|
||||
pub fn reg_exp_literal(&self, span: Span, pattern: Atom, flags: RegExpFlags) -> RegExpLiteral {
|
||||
RegExpLiteral { span, value: EmptyObject, regex: RegExp { pattern, flags } }
|
||||
}
|
||||
|
||||
pub fn literal_string_expression(&self, literal: StringLiteral) -> Expression<'a> {
|
||||
Expression::StringLiteral(self.alloc(literal))
|
||||
}
|
||||
|
|
@ -122,6 +166,17 @@ impl<'a> AstBuilder<'a> {
|
|||
Expression::BigintLiteral(self.alloc(literal))
|
||||
}
|
||||
|
||||
pub fn literal_template_expression(&mut self, literal: TemplateLiteral<'a>) -> Expression<'a> {
|
||||
Expression::TemplateLiteral(self.alloc(literal))
|
||||
}
|
||||
|
||||
pub fn identifier_reference_expression(
|
||||
&mut self,
|
||||
ident: IdentifierReference,
|
||||
) -> Expression<'a> {
|
||||
Expression::Identifier(self.alloc(ident))
|
||||
}
|
||||
|
||||
/* ---------- Identifiers ---------- */
|
||||
|
||||
pub fn identifier_expression(&self, identifier: IdentifierReference) -> Expression<'a> {
|
||||
|
|
|
|||
|
|
@ -20,14 +20,13 @@ oxc_allocator = { workspace = true }
|
|||
oxc_span = { workspace = true }
|
||||
oxc_parser = { workspace = true }
|
||||
oxc_ast = { workspace = true }
|
||||
oxc_ast_lower = { workspace = true }
|
||||
oxc_hir = { workspace = true }
|
||||
oxc_semantic = { workspace = true }
|
||||
oxc_syntax = { workspace = true }
|
||||
oxc_index = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
num-bigint = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
|
|
|
|||
619
crates/oxc_minifier/src/compressor/ast_util.rs
Normal file
619
crates/oxc_minifier/src/compressor/ast_util.rs
Normal file
|
|
@ -0,0 +1,619 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::{One, Zero};
|
||||
use oxc_semantic::ReferenceFlag;
|
||||
use oxc_syntax::operator::{AssignmentOperator, LogicalOperator, UnaryOperator};
|
||||
|
||||
use oxc_ast::ast::{
|
||||
ArrayExpressionElement, BinaryExpression, Expression, NumberLiteral, ObjectProperty,
|
||||
ObjectPropertyKind, PropertyKey, SpreadElement, UnaryExpression,
|
||||
};
|
||||
|
||||
/// Code ported from [closure-compiler](https://github.com/google/closure-compiler/blob/f3ce5ed8b630428e311fe9aa2e20d36560d975e2/src/com/google/javascript/jscomp/NodeUtil.java#LL836C6-L836C6)
|
||||
/// Returns true if this is a literal value. We define a literal value as any node that evaluates
|
||||
/// to the same thing regardless of when or where it is evaluated. So `/xyz/` and `[3, 5]` are
|
||||
/// literals, but the name a is not.
|
||||
///
|
||||
/// Function literals do not meet this definition, because they lexically capture variables. For
|
||||
/// example, if you have `function() { return a; }`.
|
||||
/// If it is evaluated in a different scope, then it captures a different variable. Even if
|
||||
/// the function did not read any captured variables directly, it would still fail this definition,
|
||||
/// because it affects the lifecycle of variables in the enclosing scope.
|
||||
///
|
||||
/// However, a function literal with respect to a particular scope is a literal.
|
||||
/// If `include_functions` is true, all function expressions will be treated as literals.
|
||||
pub trait IsLiteralValue<'a, 'b> {
|
||||
fn is_literal_value(&self, include_functions: bool) -> bool;
|
||||
}
|
||||
|
||||
impl<'a, 'b> IsLiteralValue<'a, 'b> for Expression<'a> {
|
||||
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||
match self {
|
||||
Self::FunctionExpression(_) | Self::ArrowExpression(_) => include_functions,
|
||||
Self::ArrayExpression(expr) => {
|
||||
expr.elements.iter().all(|element| element.is_literal_value(include_functions))
|
||||
}
|
||||
Self::ObjectExpression(expr) => {
|
||||
expr.properties.iter().all(|property| property.is_literal_value(include_functions))
|
||||
}
|
||||
_ => self.is_immutable_value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IsLiteralValue<'a, 'b> for ArrayExpressionElement<'a> {
|
||||
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||
match self {
|
||||
Self::SpreadElement(element) => element.is_literal_value(include_functions),
|
||||
Self::Expression(expr) => expr.is_literal_value(include_functions),
|
||||
Self::Elision(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IsLiteralValue<'a, 'b> for SpreadElement<'a> {
|
||||
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||
self.argument.is_literal_value(include_functions)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IsLiteralValue<'a, 'b> for ObjectPropertyKind<'a> {
|
||||
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||
match self {
|
||||
Self::ObjectProperty(method) => method.is_literal_value(include_functions),
|
||||
Self::SpreadProperty(property) => property.is_literal_value(include_functions),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IsLiteralValue<'a, 'b> for ObjectProperty<'a> {
|
||||
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||
self.key.is_literal_value(include_functions)
|
||||
&& self.value.is_literal_value(include_functions)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IsLiteralValue<'a, 'b> for PropertyKey<'a> {
|
||||
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||
match self {
|
||||
Self::Identifier(_) | Self::PrivateIdentifier(_) => false,
|
||||
Self::Expression(expr) => expr.is_literal_value(include_functions),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// port from [closure-compiler](https://github.com/google/closure-compiler/blob/f3ce5ed8b630428e311fe9aa2e20d36560d975e2/src/com/google/javascript/jscomp/AstAnalyzer.java#L94)
|
||||
/// Returns true if the node which may have side effects when executed.
|
||||
/// This version default to the "safe" assumptions when the compiler object
|
||||
/// is not provided (RegExp have side-effects, etc).
|
||||
pub trait MayHaveSideEffects<'a, 'b>
|
||||
where
|
||||
Self: CheckForStateChange<'a, 'b>,
|
||||
{
|
||||
fn may_have_side_effects(&self) -> bool {
|
||||
self.check_for_state_change(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// port from [closure-compiler](https://github.com/google/closure-compiler/blob/f3ce5ed8b630428e311fe9aa2e20d36560d975e2/src/com/google/javascript/jscomp/AstAnalyzer.java#L241)
|
||||
/// Returns true if some node in n's subtree changes application state. If
|
||||
/// `check_for_new_objects` is true, we assume that newly created mutable objects (like object
|
||||
/// literals) change state. Otherwise, we assume that they have no side effects.
|
||||
pub trait CheckForStateChange<'a, 'b> {
|
||||
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool;
|
||||
}
|
||||
|
||||
impl<'a, 'b> CheckForStateChange<'a, 'b> for Expression<'a> {
|
||||
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool {
|
||||
match self {
|
||||
Self::NumberLiteral(_)
|
||||
| Self::BooleanLiteral(_)
|
||||
| Self::StringLiteral(_)
|
||||
| Self::BigintLiteral(_)
|
||||
| Self::NullLiteral(_)
|
||||
| Self::RegExpLiteral(_)
|
||||
| Self::FunctionExpression(_) => false,
|
||||
Self::TemplateLiteral(template) => template
|
||||
.expressions
|
||||
.iter()
|
||||
.any(|expr| expr.check_for_state_change(check_for_new_objects)),
|
||||
Self::Identifier(ident) => ident.reference_flag == ReferenceFlag::Write,
|
||||
Self::UnaryExpression(unary_expr) => {
|
||||
unary_expr.check_for_state_change(check_for_new_objects)
|
||||
}
|
||||
Self::BinaryExpression(binary_expr) => {
|
||||
binary_expr.check_for_state_change(check_for_new_objects)
|
||||
}
|
||||
Self::ObjectExpression(object_expr) => {
|
||||
if check_for_new_objects {
|
||||
return true;
|
||||
}
|
||||
|
||||
object_expr
|
||||
.properties
|
||||
.iter()
|
||||
.any(|property| property.check_for_state_change(check_for_new_objects))
|
||||
}
|
||||
Self::ArrayExpression(array_expr) => {
|
||||
if check_for_new_objects {
|
||||
return true;
|
||||
}
|
||||
array_expr
|
||||
.elements
|
||||
.iter()
|
||||
.any(|element| element.check_for_state_change(check_for_new_objects))
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> CheckForStateChange<'a, 'b> for UnaryExpression<'a> {
|
||||
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool {
|
||||
if is_simple_unary_operator(self.operator) {
|
||||
return self.argument.check_for_state_change(check_for_new_objects);
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> CheckForStateChange<'a, 'b> for BinaryExpression<'a> {
|
||||
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool {
|
||||
let left = self.left.check_for_state_change(check_for_new_objects);
|
||||
let right = self.right.check_for_state_change(check_for_new_objects);
|
||||
|
||||
left || right
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> CheckForStateChange<'a, 'b> for ArrayExpressionElement<'a> {
|
||||
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool {
|
||||
match self {
|
||||
Self::SpreadElement(element) => element.check_for_state_change(check_for_new_objects),
|
||||
Self::Expression(expr) => expr.check_for_state_change(check_for_new_objects),
|
||||
Self::Elision(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> CheckForStateChange<'a, 'b> for ObjectPropertyKind<'a> {
|
||||
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool {
|
||||
match self {
|
||||
Self::ObjectProperty(method) => method.check_for_state_change(check_for_new_objects),
|
||||
Self::SpreadProperty(spread_element) => {
|
||||
spread_element.check_for_state_change(check_for_new_objects)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> CheckForStateChange<'a, 'b> for SpreadElement<'a> {
|
||||
fn check_for_state_change(&self, _check_for_new_objects: bool) -> bool {
|
||||
// Object-rest and object-spread may trigger a getter.
|
||||
// TODO: Closure Compiler assumes that getters may side-free when set `assumeGettersArePure`.
|
||||
// https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/AstAnalyzer.java#L282
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> CheckForStateChange<'a, 'b> for ObjectProperty<'a> {
|
||||
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool {
|
||||
self.key.check_for_state_change(check_for_new_objects)
|
||||
|| self.value.check_for_state_change(check_for_new_objects)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> CheckForStateChange<'a, 'b> for PropertyKey<'a> {
|
||||
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool {
|
||||
match self {
|
||||
Self::Identifier(_) | Self::PrivateIdentifier(_) => false,
|
||||
Self::Expression(expr) => expr.check_for_state_change(check_for_new_objects),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> MayHaveSideEffects<'a, 'b> for Expression<'a> {}
|
||||
impl<'a, 'b> MayHaveSideEffects<'a, 'b> for UnaryExpression<'a> {}
|
||||
|
||||
/// A "simple" operator is one whose children are expressions, has no direct side-effects.
|
||||
fn is_simple_unary_operator(operator: UnaryOperator) -> bool {
|
||||
operator != UnaryOperator::Delete
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum NumberValue {
|
||||
Number(f64),
|
||||
PositiveInfinity,
|
||||
NegativeInfinity,
|
||||
NaN,
|
||||
}
|
||||
|
||||
impl NumberValue {
|
||||
#[must_use]
|
||||
pub fn not(&self) -> Self {
|
||||
match self {
|
||||
Self::Number(num) => Self::Number(-num),
|
||||
Self::PositiveInfinity => Self::NegativeInfinity,
|
||||
Self::NegativeInfinity => Self::PositiveInfinity,
|
||||
Self::NaN => Self::NaN,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_nan(&self) -> bool {
|
||||
matches!(self, Self::NaN)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Self> for NumberValue {
|
||||
type Output = Self;
|
||||
fn add(self, other: Self) -> Self {
|
||||
match self {
|
||||
Self::Number(num) => match other {
|
||||
Self::Number(other_num) => Self::Number(num + other_num),
|
||||
Self::PositiveInfinity => Self::PositiveInfinity,
|
||||
Self::NegativeInfinity => Self::NegativeInfinity,
|
||||
Self::NaN => Self::NaN,
|
||||
},
|
||||
Self::NaN => Self::NaN,
|
||||
Self::PositiveInfinity => match other {
|
||||
Self::NaN | Self::NegativeInfinity => Self::NaN,
|
||||
_ => Self::PositiveInfinity,
|
||||
},
|
||||
Self::NegativeInfinity => match other {
|
||||
Self::NaN | Self::PositiveInfinity => Self::NaN,
|
||||
_ => Self::NegativeInfinity,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NumberValue> for f64 {
|
||||
type Error = ();
|
||||
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
NumberValue::Number(num) => Ok(num),
|
||||
NumberValue::PositiveInfinity => Ok(Self::INFINITY),
|
||||
NumberValue::NegativeInfinity => Ok(Self::NEG_INFINITY),
|
||||
NumberValue::NaN => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_exact_int64(num: f64) -> bool {
|
||||
num.fract() == 0.0
|
||||
}
|
||||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/NodeUtil.java#L540)
|
||||
pub fn get_string_bigint_value(raw_string: &str) -> Option<BigInt> {
|
||||
if raw_string.contains('\u{000b}') {
|
||||
// vertical tab is not always whitespace
|
||||
return None;
|
||||
}
|
||||
|
||||
let s = raw_string.trim();
|
||||
|
||||
if s.is_empty() {
|
||||
return Some(BigInt::zero());
|
||||
}
|
||||
|
||||
if s.len() > 2 && s.starts_with('0') {
|
||||
let radix: u32 = match s.chars().nth(1) {
|
||||
Some('x' | 'X') => 16,
|
||||
Some('o' | 'O') => 8,
|
||||
Some('b' | 'B') => 2,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
if radix == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
return BigInt::parse_bytes(s[2..].as_bytes(), radix);
|
||||
}
|
||||
|
||||
return BigInt::parse_bytes(s.as_bytes(), 10);
|
||||
}
|
||||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L348)
|
||||
/// Gets the value of a node as a Number, or None if it cannot be converted.
|
||||
/// This method does not consider whether `expr` may have side effects.
|
||||
pub fn get_number_value(expr: &Expression) -> Option<NumberValue> {
|
||||
match expr {
|
||||
Expression::NumberLiteral(number_literal) => {
|
||||
Some(NumberValue::Number(number_literal.value))
|
||||
}
|
||||
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
|
||||
UnaryOperator::UnaryPlus => get_number_value(&unary_expr.argument),
|
||||
UnaryOperator::UnaryNegation => get_number_value(&unary_expr.argument).map(|v| v.not()),
|
||||
UnaryOperator::BitwiseNot => get_number_value(&unary_expr.argument).map(|value| {
|
||||
match value {
|
||||
NumberValue::Number(num) => {
|
||||
NumberValue::Number(f64::from(!NumberLiteral::ecmascript_to_int32(num)))
|
||||
}
|
||||
// ~Infinity -> -1
|
||||
// ~-Infinity -> -1
|
||||
// ~NaN -> -1
|
||||
_ => NumberValue::Number(-1_f64),
|
||||
}
|
||||
}),
|
||||
UnaryOperator::LogicalNot => get_boolean_value(expr)
|
||||
.map(|boolean| if boolean { 1_f64 } else { 0_f64 })
|
||||
.map(NumberValue::Number),
|
||||
UnaryOperator::Void => Some(NumberValue::NaN),
|
||||
_ => None,
|
||||
},
|
||||
Expression::BooleanLiteral(bool_literal) => {
|
||||
if bool_literal.value {
|
||||
Some(NumberValue::Number(1.0))
|
||||
} else {
|
||||
Some(NumberValue::Number(0.0))
|
||||
}
|
||||
}
|
||||
Expression::NullLiteral(_) => Some(NumberValue::Number(0.0)),
|
||||
Expression::Identifier(ident) => match ident.name.as_str() {
|
||||
"Infinity" => Some(NumberValue::PositiveInfinity),
|
||||
"NaN" | "undefined" => Some(NumberValue::NaN),
|
||||
_ => None,
|
||||
},
|
||||
// TODO: will be implemented in next PR, just for test pass now.
|
||||
Expression::StringLiteral(string_literal) => string_literal
|
||||
.value
|
||||
.parse::<f64>()
|
||||
.map_or(Some(NumberValue::NaN), |num| Some(NumberValue::Number(num))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub fn get_bigint_value(expr: &Expression) -> Option<BigInt> {
|
||||
match expr {
|
||||
Expression::NumberLiteral(number_literal) => {
|
||||
let value = number_literal.value;
|
||||
if value.abs() < 2_f64.powi(53) && is_exact_int64(value) {
|
||||
Some(BigInt::from(value as i64))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Expression::BigintLiteral(bigint_literal) => Some(bigint_literal.value.clone()),
|
||||
Expression::BooleanLiteral(bool_literal) => {
|
||||
if bool_literal.value {
|
||||
Some(BigInt::one())
|
||||
} else {
|
||||
Some(BigInt::zero())
|
||||
}
|
||||
}
|
||||
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
|
||||
UnaryOperator::LogicalNot => {
|
||||
get_boolean_value(expr)
|
||||
.map(|boolean| if boolean { BigInt::one() } else { BigInt::zero() })
|
||||
}
|
||||
UnaryOperator::UnaryNegation => {
|
||||
get_bigint_value(&unary_expr.argument).map(std::ops::Neg::neg)
|
||||
}
|
||||
UnaryOperator::BitwiseNot => {
|
||||
get_bigint_value(&unary_expr.argument).map(std::ops::Not::not)
|
||||
}
|
||||
UnaryOperator::UnaryPlus => get_bigint_value(&unary_expr.argument),
|
||||
_ => None,
|
||||
},
|
||||
Expression::StringLiteral(string_literal) => get_string_bigint_value(&string_literal.value),
|
||||
Expression::TemplateLiteral(_) => {
|
||||
get_string_value(expr).and_then(|value| get_string_bigint_value(&value))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L104-L114)
|
||||
/// Returns the number value of the node if it has one and it cannot have side effects.
|
||||
pub fn get_side_free_number_value(expr: &Expression) -> Option<NumberValue> {
|
||||
let value = get_number_value(expr);
|
||||
// Calculating the number value, if any, is likely to be faster than calculating side effects,
|
||||
// and there are only a very few cases where we can compute a number value, but there could
|
||||
// also be side effects. e.g. `void doSomething()` has value NaN, regardless of the behavior
|
||||
// of `doSomething()`
|
||||
if value.is_some() && expr.may_have_side_effects() {
|
||||
None
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L121)
|
||||
pub fn get_side_free_bigint_value(expr: &Expression) -> Option<BigInt> {
|
||||
let value = get_bigint_value(expr);
|
||||
// Calculating the bigint value, if any, is likely to be faster than calculating side effects,
|
||||
// and there are only a very few cases where we can compute a bigint value, but there could
|
||||
// also be side effects. e.g. `void doSomething()` has value NaN, regardless of the behavior
|
||||
// of `doSomething()`
|
||||
if value.is_some() && expr.may_have_side_effects() {
|
||||
None
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L109)
|
||||
/// Gets the boolean value of a node that represents an expression, or `None` if no
|
||||
/// such value can be determined by static analysis.
|
||||
/// This method does not consider whether the node may have side-effects.
|
||||
pub fn get_boolean_value(expr: &Expression) -> Option<bool> {
|
||||
match expr {
|
||||
Expression::RegExpLiteral(_)
|
||||
| Expression::ArrayExpression(_)
|
||||
| Expression::ArrowExpression(_)
|
||||
| Expression::ClassExpression(_)
|
||||
| Expression::FunctionExpression(_)
|
||||
| Expression::NewExpression(_)
|
||||
| Expression::ObjectExpression(_) => Some(true),
|
||||
Expression::NullLiteral(_) => Some(false),
|
||||
Expression::BooleanLiteral(boolean_literal) => Some(boolean_literal.value),
|
||||
Expression::NumberLiteral(number_literal) => Some(number_literal.value != 0.0),
|
||||
Expression::BigintLiteral(big_int_literal) => Some(!big_int_literal.value.is_zero()),
|
||||
Expression::StringLiteral(string_literal) => Some(!string_literal.value.is_empty()),
|
||||
Expression::TemplateLiteral(template_literal) => {
|
||||
// only for ``
|
||||
template_literal
|
||||
.quasis
|
||||
.get(0)
|
||||
.filter(|quasi| quasi.tail)
|
||||
.and_then(|quasi| quasi.value.cooked.as_ref())
|
||||
.map(|cooked| !cooked.is_empty())
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
if expr.is_undefined() || ident.name == "NaN" {
|
||||
Some(false)
|
||||
} else if ident.name == "Infinity" {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Expression::AssignmentExpression(assign_expr) => {
|
||||
match assign_expr.operator {
|
||||
AssignmentOperator::LogicalAnd | AssignmentOperator::LogicalOr => None,
|
||||
// For ASSIGN, the value is the value of the RHS.
|
||||
_ => get_boolean_value(&assign_expr.right),
|
||||
}
|
||||
}
|
||||
Expression::LogicalExpression(logical_expr) => {
|
||||
match logical_expr.operator {
|
||||
// true && true -> true
|
||||
// true && false -> false
|
||||
// a && true -> None
|
||||
LogicalOperator::And => {
|
||||
let left = get_boolean_value(&logical_expr.left);
|
||||
let right = get_boolean_value(&logical_expr.right);
|
||||
|
||||
match (left, right) {
|
||||
(Some(true), Some(true)) => Some(true),
|
||||
(Some(false), _) | (_, Some(false)) => Some(false),
|
||||
(None, _) | (_, None) => None,
|
||||
}
|
||||
}
|
||||
// true || false -> true
|
||||
// false || false -> false
|
||||
// a || b -> None
|
||||
LogicalOperator::Or => {
|
||||
let left = get_boolean_value(&logical_expr.left);
|
||||
let right = get_boolean_value(&logical_expr.right);
|
||||
|
||||
match (left, right) {
|
||||
(Some(true), _) | (_, Some(true)) => Some(true),
|
||||
(Some(false), Some(false)) => Some(false),
|
||||
(None, _) | (_, None) => None,
|
||||
}
|
||||
}
|
||||
LogicalOperator::Coalesce => None,
|
||||
}
|
||||
}
|
||||
Expression::SequenceExpression(sequence_expr) => {
|
||||
// For sequence expression, the value is the value of the RHS.
|
||||
sequence_expr.expressions.last().and_then(get_boolean_value)
|
||||
}
|
||||
Expression::UnaryExpression(unary_expr) => {
|
||||
if unary_expr.operator == UnaryOperator::Void {
|
||||
Some(false)
|
||||
} else if matches!(
|
||||
unary_expr.operator,
|
||||
UnaryOperator::BitwiseNot | UnaryOperator::UnaryPlus | UnaryOperator::UnaryNegation
|
||||
) {
|
||||
// ~0 -> true
|
||||
// +1 -> true
|
||||
// +0 -> false
|
||||
// -0 -> false
|
||||
get_number_value(expr).map(|value| value != NumberValue::Number(0_f64))
|
||||
} else if unary_expr.operator == UnaryOperator::LogicalNot {
|
||||
// !true -> false
|
||||
get_boolean_value(&unary_expr.argument).map(|boolean| !boolean)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Port from [closure-compiler](https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L234)
|
||||
/// Gets the value of a node as a String, or `None` if it cannot be converted. When it returns a
|
||||
/// String, this method effectively emulates the `String()` JavaScript cast function.
|
||||
/// This method does not consider whether `expr` may have side effects.
|
||||
pub fn get_string_value<'a>(expr: &'a Expression) -> Option<Cow<'a, str>> {
|
||||
match expr {
|
||||
Expression::StringLiteral(string_literal) => {
|
||||
Some(Cow::Borrowed(string_literal.value.as_str()))
|
||||
}
|
||||
Expression::TemplateLiteral(template_literal) => {
|
||||
// TODO: I don't know how to iterate children of TemplateLiteral in order,so only checkout string like `hi`.
|
||||
// Closure-compiler do more: [case TEMPLATELIT](https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L241-L256).
|
||||
template_literal
|
||||
.quasis
|
||||
.get(0)
|
||||
.filter(|quasi| quasi.tail)
|
||||
.and_then(|quasi| quasi.value.cooked.as_ref())
|
||||
.map(|cooked| Cow::Borrowed(cooked.as_str()))
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
let name = ident.name.as_str();
|
||||
if matches!(name, "undefined" | "Infinity" | "NaN") {
|
||||
Some(Cow::Borrowed(name))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Expression::NumberLiteral(number_literal) => {
|
||||
Some(Cow::Owned(number_literal.value.to_string()))
|
||||
}
|
||||
Expression::BigintLiteral(big_int_literal) => {
|
||||
Some(Cow::Owned(format!("{}n", big_int_literal.value)))
|
||||
}
|
||||
Expression::NullLiteral(_) => Some(Cow::Borrowed("null")),
|
||||
Expression::BooleanLiteral(bool_literal) => {
|
||||
if bool_literal.value {
|
||||
Some(Cow::Borrowed("true"))
|
||||
} else {
|
||||
Some(Cow::Borrowed("false"))
|
||||
}
|
||||
}
|
||||
Expression::UnaryExpression(unary_expr) => {
|
||||
match unary_expr.operator {
|
||||
UnaryOperator::Void => Some(Cow::Borrowed("undefined")),
|
||||
UnaryOperator::LogicalNot => {
|
||||
get_boolean_value(&unary_expr.argument).map(|boolean| {
|
||||
// need reversed.
|
||||
if boolean {
|
||||
Cow::Borrowed("false")
|
||||
} else {
|
||||
Cow::Borrowed("true")
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Expression::ArrayExpression(_) => {
|
||||
// TODO: https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L302-L303
|
||||
None
|
||||
}
|
||||
Expression::ObjectExpression(_) => Some(Cow::Borrowed("[object Object]")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Port from [closure-compiler](https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L139-L149)
|
||||
/// Gets the value of a node as a String, or `None` if it cannot be converted.
|
||||
/// This method effectively emulates the `String()` JavaScript cast function when
|
||||
/// possible and the node has no side effects. Otherwise, it returns `None`.
|
||||
pub fn get_side_free_string_value<'a>(expr: &'a Expression) -> Option<Cow<'a, str>> {
|
||||
let value = get_string_value(expr);
|
||||
// Calculating the string value, if any, is likely to be faster than calculating side effects,
|
||||
// and there are only a very few cases where we can compute a string value, but there could
|
||||
// also be side effects. e.g. `void doSomething()` has value 'undefined', regardless of the
|
||||
// behavior of `doSomething()`
|
||||
if value.is_some() && !expr.may_have_side_effects() {
|
||||
return value;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
@ -6,18 +6,18 @@ use std::{cmp::Ordering, mem, ops::Not};
|
|||
|
||||
use num_bigint::BigInt;
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_hir::hir::*;
|
||||
use oxc_hir::hir_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,
|
||||
};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_span::{Atom, GetSpan, Span};
|
||||
use oxc_syntax::{
|
||||
operator::{BinaryOperator, LogicalOperator, UnaryOperator},
|
||||
NumberBase,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
use super::Compressor;
|
||||
|
||||
/// Tri state
|
||||
|
|
@ -260,26 +260,26 @@ impl<'a> Compressor<'a> {
|
|||
(Ty::Str, _) | (_, Ty::Str) => {
|
||||
// no need to use get_side_effect_free_string_value b/c we checked for side effects
|
||||
// at the beginning
|
||||
let Some(left_string) = get_string_value(left) else { return None };
|
||||
let Some(right_string) = get_string_value(right) else { return None };
|
||||
let left_string = get_string_value(left)?;
|
||||
let right_string = get_string_value(right)?;
|
||||
// let value = left_string.to_owned().
|
||||
let value = left_string + right_string;
|
||||
let string_literal = self.hir.string_literal(span, Atom::from(value));
|
||||
Some(self.hir.literal_string_expression(string_literal))
|
||||
let string_literal = self.ast.string_literal(span, Atom::from(value));
|
||||
Some(self.ast.literal_string_expression(string_literal))
|
||||
},
|
||||
|
||||
// number addition
|
||||
(Ty::Number, _) | (_, Ty::Number)
|
||||
// when added, booleans get treated as numbers where `true` is 1 and `false` is 0
|
||||
// when added, booleans get treated as numbers where `true` is 1 and `false` is 0
|
||||
| (Ty::Boolean, Ty::Boolean) => {
|
||||
let Some( left_number ) = get_number_value(left) else { return None };
|
||||
let Some( right_number ) = get_number_value(right) else { return None };
|
||||
let left_number = get_number_value(left)?;
|
||||
let right_number = get_number_value(right)?;
|
||||
let Ok(value) = TryInto::<f64>::try_into(left_number + right_number) else { return None };
|
||||
// Float if value has a fractional part, otherwise Decimal
|
||||
let number_base = if is_exact_int64(value) { NumberBase::Decimal } else { NumberBase::Float };
|
||||
let number_literal = self.hir.number_literal(span, value, "", number_base);
|
||||
let number_literal = self.ast.number_literal(span, value, "", number_base);
|
||||
// todo: add raw &str
|
||||
Some(self.hir.literal_number_expression(number_literal))
|
||||
Some(self.ast.literal_number_expression(number_literal))
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
|
|
@ -297,8 +297,8 @@ impl<'a> Compressor<'a> {
|
|||
Tri::False => false,
|
||||
Tri::Unknown => return None,
|
||||
};
|
||||
let boolean_literal = self.hir.boolean_literal(span, value);
|
||||
Some(self.hir.literal_boolean_expression(boolean_literal))
|
||||
let boolean_literal = self.ast.boolean_literal(span, value);
|
||||
Some(self.ast.literal_boolean_expression(boolean_literal))
|
||||
}
|
||||
|
||||
fn evaluate_comparison<'b>(
|
||||
|
|
@ -352,15 +352,15 @@ impl<'a> Compressor<'a> {
|
|||
let right_number = get_side_free_number_value(right_expr);
|
||||
|
||||
if let Some(NumberValue::Number(num)) = right_number {
|
||||
let raw = self.hir.new_str(num.to_string().as_str());
|
||||
let raw = self.ast.new_str(num.to_string().as_str());
|
||||
|
||||
let number_literal = self.hir.number_literal(
|
||||
let number_literal = self.ast.number_literal(
|
||||
right_expr.span(),
|
||||
num,
|
||||
raw,
|
||||
if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float },
|
||||
);
|
||||
let number_literal_expr = self.hir.literal_number_expression(number_literal);
|
||||
let number_literal_expr = self.ast.literal_number_expression(number_literal);
|
||||
|
||||
return self.try_abstract_equality_comparison(left_expr, &number_literal_expr);
|
||||
}
|
||||
|
|
@ -372,15 +372,15 @@ impl<'a> Compressor<'a> {
|
|||
let left_number = get_side_free_number_value(left_expr);
|
||||
|
||||
if let Some(NumberValue::Number(num)) = left_number {
|
||||
let raw = self.hir.new_str(num.to_string().as_str());
|
||||
let raw = self.ast.new_str(num.to_string().as_str());
|
||||
|
||||
let number_literal = self.hir.number_literal(
|
||||
let number_literal = self.ast.number_literal(
|
||||
left_expr.span(),
|
||||
num,
|
||||
raw,
|
||||
if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float },
|
||||
);
|
||||
let number_literal_expr = self.hir.literal_number_expression(number_literal);
|
||||
let number_literal_expr = self.ast.literal_number_expression(number_literal);
|
||||
|
||||
return self.try_abstract_equality_comparison(&number_literal_expr, right_expr);
|
||||
}
|
||||
|
|
@ -587,8 +587,8 @@ impl<'a> Compressor<'a> {
|
|||
};
|
||||
|
||||
if let Some(type_name) = type_name {
|
||||
let string_literal = self.hir.string_literal(span, Atom::from(type_name));
|
||||
return Some(self.hir.literal_string_expression(string_literal));
|
||||
let string_literal = self.ast.string_literal(span, Atom::from(type_name));
|
||||
return Some(self.ast.literal_string_expression(string_literal));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -596,9 +596,9 @@ impl<'a> Compressor<'a> {
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn try_fold_unary_operator<'b>(
|
||||
fn try_fold_unary_operator(
|
||||
&mut self,
|
||||
unary_expr: &'b mut UnaryExpression<'a>,
|
||||
unary_expr: &UnaryExpression<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if let Some(boolean) = get_boolean_value(&unary_expr.argument) {
|
||||
match unary_expr.operator {
|
||||
|
|
@ -612,12 +612,12 @@ impl<'a> Compressor<'a> {
|
|||
if value == 0_f64 || (value - 1_f64).abs() < f64::EPSILON {
|
||||
return None;
|
||||
}
|
||||
let bool_literal = self.hir.boolean_literal(unary_expr.span, !boolean);
|
||||
return Some(self.hir.literal_boolean_expression(bool_literal));
|
||||
let bool_literal = self.ast.boolean_literal(unary_expr.span, !boolean);
|
||||
return Some(self.ast.literal_boolean_expression(bool_literal));
|
||||
}
|
||||
Expression::BigintLiteral(_) => {
|
||||
let bool_literal = self.hir.boolean_literal(unary_expr.span, !boolean);
|
||||
return Some(self.hir.literal_boolean_expression(bool_literal));
|
||||
let bool_literal = self.ast.boolean_literal(unary_expr.span, !boolean);
|
||||
return Some(self.ast.literal_boolean_expression(bool_literal));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
|
@ -626,13 +626,13 @@ impl<'a> Compressor<'a> {
|
|||
// +Infinity -> Infinity
|
||||
UnaryOperator::UnaryPlus => match &unary_expr.argument {
|
||||
Expression::NumberLiteral(number_literal) => {
|
||||
let literal = self.hir.number_literal(
|
||||
let literal = self.ast.number_literal(
|
||||
unary_expr.span,
|
||||
number_literal.value,
|
||||
number_literal.raw,
|
||||
number_literal.base,
|
||||
);
|
||||
return Some(self.hir.literal_number_expression(literal));
|
||||
return Some(self.ast.literal_number_expression(literal));
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
if matches!(ident.name.as_str(), "NaN" | "Infinity") {
|
||||
|
|
@ -646,8 +646,8 @@ impl<'a> Compressor<'a> {
|
|||
if let Some(NumberValue::Number(value)) =
|
||||
get_number_value(&unary_expr.argument)
|
||||
{
|
||||
let raw = self.hir.new_str(value.to_string().as_str());
|
||||
let literal = self.hir.number_literal(
|
||||
let raw = self.ast.new_str(value.to_string().as_str());
|
||||
let literal = self.ast.number_literal(
|
||||
unary_expr.span,
|
||||
value,
|
||||
raw,
|
||||
|
|
@ -657,7 +657,7 @@ impl<'a> Compressor<'a> {
|
|||
NumberBase::Float
|
||||
},
|
||||
);
|
||||
return Some(self.hir.literal_number_expression(literal));
|
||||
return Some(self.ast.literal_number_expression(literal));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -666,21 +666,21 @@ impl<'a> Compressor<'a> {
|
|||
UnaryOperator::UnaryNegation => match &unary_expr.argument {
|
||||
Expression::NumberLiteral(number_literal) => {
|
||||
let value = -number_literal.value;
|
||||
let raw = self.hir.new_str(value.to_string().as_str());
|
||||
let literal = self.hir.number_literal(
|
||||
let raw = self.ast.new_str(value.to_string().as_str());
|
||||
let literal = self.ast.number_literal(
|
||||
unary_expr.span,
|
||||
value,
|
||||
raw,
|
||||
number_literal.base,
|
||||
);
|
||||
return Some(self.hir.literal_number_expression(literal));
|
||||
return Some(self.ast.literal_number_expression(literal));
|
||||
}
|
||||
Expression::BigintLiteral(big_int_literal) => {
|
||||
use std::ops::Neg;
|
||||
|
||||
let value = big_int_literal.value.clone().neg();
|
||||
let literal = self.hir.bigint_literal(unary_expr.span, value);
|
||||
return Some(self.hir.literal_bigint_expression(literal));
|
||||
let literal = self.ast.bigint_literal(unary_expr.span, value);
|
||||
return Some(self.ast.literal_bigint_expression(literal));
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
if ident.name == "NaN" {
|
||||
|
|
@ -696,31 +696,31 @@ impl<'a> Compressor<'a> {
|
|||
if number_literal.value.fract() == 0.0 {
|
||||
let int_value =
|
||||
NumberLiteral::ecmascript_to_int32(number_literal.value);
|
||||
let literal = self.hir.number_literal(
|
||||
let literal = self.ast.number_literal(
|
||||
unary_expr.span,
|
||||
f64::from(!int_value),
|
||||
number_literal.raw,
|
||||
NumberBase::Decimal, // since it be converted to i32, it should always be decimal.
|
||||
);
|
||||
return Some(self.hir.literal_number_expression(literal));
|
||||
return Some(self.ast.literal_number_expression(literal));
|
||||
}
|
||||
}
|
||||
Expression::BigintLiteral(big_int_literal) => {
|
||||
let value = big_int_literal.value.clone().not();
|
||||
let leteral = self.hir.bigint_literal(unary_expr.span, value);
|
||||
return Some(self.hir.literal_bigint_expression(leteral));
|
||||
let leteral = self.ast.bigint_literal(unary_expr.span, value);
|
||||
return Some(self.ast.literal_bigint_expression(leteral));
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
if ident.name == "NaN" {
|
||||
let value = -1_f64;
|
||||
let raw = self.hir.new_str("-1");
|
||||
let literal = self.hir.number_literal(
|
||||
let raw = self.ast.new_str("-1");
|
||||
let literal = self.ast.number_literal(
|
||||
unary_expr.span,
|
||||
value,
|
||||
raw,
|
||||
NumberBase::Decimal,
|
||||
);
|
||||
return Some(self.hir.literal_number_expression(literal));
|
||||
return Some(self.ast.literal_number_expression(literal));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -734,19 +734,16 @@ impl<'a> Compressor<'a> {
|
|||
|
||||
// +NaN -> NaN
|
||||
// !Infinity -> Infinity
|
||||
fn try_detach_unary_op(
|
||||
&mut self,
|
||||
unary_expr: &mut UnaryExpression<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
fn try_detach_unary_op(&mut self, unary_expr: &UnaryExpression<'a>) -> Option<Expression<'a>> {
|
||||
if let Expression::Identifier(ident) = &unary_expr.argument {
|
||||
if matches!(ident.name.as_str(), "NaN" | "Infinity") {
|
||||
let ident = self.hir.identifier_reference(
|
||||
unary_expr.span,
|
||||
ident.name.clone(),
|
||||
ident.reference_id.clone().into_inner(),
|
||||
ident.reference_flag,
|
||||
);
|
||||
return Some(self.hir.identifier_reference_expression(ident));
|
||||
let ident = IdentifierReference {
|
||||
span: unary_expr.span,
|
||||
name: ident.name.clone(),
|
||||
reference_id: ident.reference_id.clone().into_inner().into(),
|
||||
reference_flag: ident.reference_flag,
|
||||
};
|
||||
return Some(self.ast.identifier_reference_expression(ident));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -764,15 +761,15 @@ impl<'a> Compressor<'a> {
|
|||
};
|
||||
|
||||
if can_replace {
|
||||
let number_literal = self.hir.number_literal(
|
||||
let number_literal = self.ast.number_literal(
|
||||
unary_expr.argument.span(),
|
||||
0_f64,
|
||||
self.hir.new_str("0"),
|
||||
self.ast.new_str("0"),
|
||||
NumberBase::Decimal,
|
||||
);
|
||||
|
||||
let argument = self.hir.literal_number_expression(number_literal);
|
||||
return Some(self.hir.unary_expression(unary_expr.span, UnaryOperator::Void, argument));
|
||||
let argument = self.ast.literal_number_expression(number_literal);
|
||||
return Some(self.ast.unary_expression(unary_expr.span, UnaryOperator::Void, argument));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
@ -818,12 +815,12 @@ impl<'a> Compressor<'a> {
|
|||
_ => unreachable!("Unknown binary operator {:?}", op),
|
||||
};
|
||||
|
||||
let value_raw = self.hir.new_str(result_val.to_string().as_str());
|
||||
let value_raw = self.ast.new_str(result_val.to_string().as_str());
|
||||
|
||||
let number_literal =
|
||||
self.hir.number_literal(span, result_val, value_raw, NumberBase::Decimal);
|
||||
self.ast.number_literal(span, result_val, value_raw, NumberBase::Decimal);
|
||||
|
||||
return Some(self.hir.literal_number_expression(number_literal));
|
||||
return Some(self.ast.literal_number_expression(number_literal));
|
||||
}
|
||||
|
||||
None
|
||||
|
|
@ -855,10 +852,10 @@ impl<'a> Compressor<'a> {
|
|||
// or: false_with_sideeffects && foo() => false_with_sideeffects, foo()
|
||||
let left = self.move_out_expression(&mut logic_expr.left);
|
||||
let right = self.move_out_expression(&mut logic_expr.right);
|
||||
let mut vec = self.hir.new_vec_with_capacity(2);
|
||||
let mut vec = self.ast.new_vec_with_capacity(2);
|
||||
vec.push(left);
|
||||
vec.push(right);
|
||||
let sequence_expr = self.hir.sequence_expression(logic_expr.span, vec);
|
||||
let sequence_expr = self.ast.sequence_expression(logic_expr.span, vec);
|
||||
return Some(sequence_expr);
|
||||
} else if let Expression::LogicalExpression(left_child) = &mut logic_expr.left {
|
||||
if left_child.operator == logic_expr.operator {
|
||||
|
|
@ -873,7 +870,7 @@ impl<'a> Compressor<'a> {
|
|||
{
|
||||
let left = self.move_out_expression(&mut left_child.left);
|
||||
let right = self.move_out_expression(&mut logic_expr.right);
|
||||
let logic_expr = self.hir.logical_expression(
|
||||
let logic_expr = self.ast.logical_expression(
|
||||
logic_expr.span,
|
||||
left,
|
||||
left_child_op,
|
||||
|
|
@ -936,8 +933,8 @@ impl<'a> Compressor<'a> {
|
|||
}
|
||||
|
||||
fn move_out_expression(&mut self, expr: &mut Expression<'a>) -> Expression<'a> {
|
||||
let null_literal = self.hir.null_literal(expr.span());
|
||||
let null_expr = self.hir.literal_null_expression(null_literal);
|
||||
let null_literal = self.ast.null_literal(expr.span());
|
||||
let null_expr = self.ast.literal_null_expression(null_literal);
|
||||
mem::replace(expr, null_expr)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
#![allow(clippy::unused_self)]
|
||||
|
||||
mod ast_util;
|
||||
mod fold;
|
||||
mod util;
|
||||
|
||||
use oxc_allocator::{Allocator, Vec};
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_hir::{hir::*, HirBuilder, VisitMut};
|
||||
use oxc_semantic::Semantic;
|
||||
use oxc_ast::{ast::*, AstBuilder, VisitMut};
|
||||
use oxc_span::Span;
|
||||
use oxc_syntax::{
|
||||
operator::{BinaryOperator, UnaryOperator},
|
||||
|
|
@ -68,39 +68,38 @@ impl Default for CompressOptions {
|
|||
}
|
||||
|
||||
pub struct Compressor<'a> {
|
||||
hir: HirBuilder<'a>,
|
||||
semantic: Semantic<'a>,
|
||||
ast: AstBuilder<'a>,
|
||||
options: CompressOptions,
|
||||
}
|
||||
|
||||
const SPAN: Span = Span::new(0, 0);
|
||||
|
||||
impl<'a> Compressor<'a> {
|
||||
pub fn new(allocator: &'a Allocator, semantic: Semantic<'a>, options: CompressOptions) -> Self {
|
||||
Self { hir: HirBuilder::new(allocator), semantic, options }
|
||||
pub fn new(allocator: &'a Allocator, options: CompressOptions) -> Self {
|
||||
Self { ast: AstBuilder::new(allocator), options }
|
||||
}
|
||||
|
||||
pub fn build<'b>(mut self, program: &'b mut Program<'a>) -> Semantic<'a> {
|
||||
pub fn build<'b>(mut self, program: &'b mut Program<'a>) {
|
||||
self.visit_program(program);
|
||||
self.semantic
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
|
||||
/// `void 0`
|
||||
fn create_void_0(&mut self) -> Expression<'a> {
|
||||
let left = self.hir.number_literal(SPAN, 0.0, "0", NumberBase::Decimal);
|
||||
let num = self.hir.literal_number_expression(left);
|
||||
self.hir.unary_expression(SPAN, UnaryOperator::Void, num)
|
||||
let left = self.ast.number_literal(SPAN, 0.0, "0", NumberBase::Decimal);
|
||||
let num = self.ast.literal_number_expression(left);
|
||||
self.ast.unary_expression(SPAN, UnaryOperator::Void, num)
|
||||
}
|
||||
|
||||
/// `1/0`
|
||||
#[allow(unused)]
|
||||
fn create_one_div_zero(&mut self) -> Expression<'a> {
|
||||
let left = self.hir.number_literal(SPAN, 1.0, "1", NumberBase::Decimal);
|
||||
let left = self.hir.literal_number_expression(left);
|
||||
let right = self.hir.number_literal(SPAN, 0.0, "0", NumberBase::Decimal);
|
||||
let right = self.hir.literal_number_expression(right);
|
||||
self.hir.binary_expression(SPAN, left, BinaryOperator::Division, right)
|
||||
let left = self.ast.number_literal(SPAN, 1.0, "1", NumberBase::Decimal);
|
||||
let left = self.ast.literal_number_expression(left);
|
||||
let right = self.ast.number_literal(SPAN, 0.0, "0", NumberBase::Decimal);
|
||||
let right = self.ast.literal_number_expression(right);
|
||||
self.ast.binary_expression(SPAN, left, BinaryOperator::Division, right)
|
||||
}
|
||||
|
||||
/* Statements */
|
||||
|
|
@ -176,7 +175,7 @@ impl<'a> Compressor<'a> {
|
|||
}
|
||||
|
||||
// Reconstruct the stmts array by joining consecutive ranges
|
||||
let mut new_stmts = self.hir.new_vec_with_capacity(stmts.len() - capacity);
|
||||
let mut new_stmts = self.ast.new_vec_with_capacity(stmts.len() - capacity);
|
||||
for (i, stmt) in stmts.drain(..).enumerate() {
|
||||
if i > 0 && ranges.iter().any(|range| range.contains(&(i - 1)) && range.contains(&i)) {
|
||||
if let Statement::Declaration(Declaration::VariableDeclaration(prev_decl)) =
|
||||
|
|
@ -199,24 +198,26 @@ impl<'a> Compressor<'a> {
|
|||
fn compress_while<'b>(&mut self, stmt: &'b mut Statement<'a>) {
|
||||
let Statement::WhileStatement(while_stmt) = stmt else { return };
|
||||
if self.options.loops {
|
||||
let dummy_test = self.hir.this_expression(SPAN);
|
||||
let dummy_test = self.ast.this_expression(SPAN);
|
||||
let test = std::mem::replace(&mut while_stmt.test, dummy_test);
|
||||
let body = while_stmt.body.take();
|
||||
*stmt = self.hir.for_statement(SPAN, None, Some(test), None, body);
|
||||
let body = self.ast.move_statement(&mut while_stmt.body);
|
||||
*stmt = self.ast.for_statement(SPAN, None, Some(test), None, body);
|
||||
}
|
||||
}
|
||||
|
||||
/* Expressions */
|
||||
|
||||
/// Transforms `undefined` => `void 0`
|
||||
fn compress_undefined<'b>(&mut self, expr: &'b mut Expression<'a>) -> bool {
|
||||
let Expression::Identifier(ident) = expr else { return false };
|
||||
if ident.name == "undefined"
|
||||
&& self.semantic.symbols().is_global_reference(ident.reference_id.clone().into_inner())
|
||||
{
|
||||
*expr = self.create_void_0();
|
||||
return true;
|
||||
}
|
||||
fn compress_undefined<'b>(&mut self, _expr: &'b mut Expression<'a>) -> bool {
|
||||
// let Expression::Identifier(ident) = expr else { return false };
|
||||
// if let Some(reference_id) = ident.reference_id.clone().into_inner() {
|
||||
// if ident.name == "undefined"
|
||||
// && self.semantic.symbols().is_global_reference(reference_id)
|
||||
// {
|
||||
// *expr = self.create_void_0();
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -224,12 +225,13 @@ impl<'a> Compressor<'a> {
|
|||
#[allow(unused)]
|
||||
fn compress_infinity<'b>(&mut self, expr: &'b mut Expression<'a>) -> bool {
|
||||
let Expression::Identifier(ident) = expr else { return false };
|
||||
if ident.name == "Infinity"
|
||||
&& self.semantic.symbols().is_global_reference(ident.reference_id.clone().into_inner())
|
||||
{
|
||||
*expr = self.create_one_div_zero();
|
||||
return true;
|
||||
}
|
||||
// if let Some(reference_id) = ident.reference_id.clone().into_inner() {
|
||||
// if ident.name == "Infinity" && self.semantic.symbols().is_global_reference(reference_id)
|
||||
// {
|
||||
// *expr = self.create_one_div_zero();
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -238,14 +240,14 @@ impl<'a> Compressor<'a> {
|
|||
fn compress_boolean<'b>(&mut self, expr: &'b mut Expression<'a>) -> bool {
|
||||
let Expression::BooleanLiteral(lit) = expr else { return false };
|
||||
if self.options.booleans {
|
||||
let num = self.hir.number_literal(
|
||||
let num = self.ast.number_literal(
|
||||
SPAN,
|
||||
if lit.value { 0.0 } else { 1.0 },
|
||||
if lit.value { "0" } else { "1" },
|
||||
NumberBase::Decimal,
|
||||
);
|
||||
let num = self.hir.literal_number_expression(num);
|
||||
*expr = self.hir.unary_expression(SPAN, UnaryOperator::LogicalNot, num);
|
||||
let num = self.ast.literal_number_expression(num);
|
||||
*expr = self.ast.unary_expression(SPAN, UnaryOperator::LogicalNot, num);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
|
|
@ -259,7 +261,7 @@ impl<'a> Compressor<'a> {
|
|||
if unary_expr.operator == UnaryOperator::Typeof {
|
||||
if let Expression::Identifier(ident) = &unary_expr.argument {
|
||||
if expr.right.is_specific_string_literal("undefined") {
|
||||
let left = self.hir.identifier_reference_expression((*ident).clone());
|
||||
let left = self.ast.identifier_reference_expression((*ident).clone());
|
||||
let right = self.create_void_0();
|
||||
let operator = BinaryOperator::StrictEquality;
|
||||
*expr = BinaryExpression { span: SPAN, left, operator, right };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use oxc_hir::hir::Expression;
|
||||
use oxc_ast::ast::Expression;
|
||||
|
||||
pub(super) fn is_console(expr: &Expression<'_>) -> bool {
|
||||
// let Statement::ExpressionStatement(expr) = stmt else { return false };
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
//! ECMAScript Minifier
|
||||
|
||||
mod compressor;
|
||||
mod mangler;
|
||||
// mod mangler;
|
||||
mod printer;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_ast_lower::AstLower;
|
||||
use oxc_parser::Parser;
|
||||
use oxc_span::SourceType;
|
||||
|
||||
pub use crate::{
|
||||
compressor::{CompressOptions, Compressor},
|
||||
mangler::ManglerBuilder,
|
||||
// mangler::ManglerBuilder,
|
||||
printer::{Printer, PrinterOptions},
|
||||
};
|
||||
|
||||
|
|
@ -42,15 +41,13 @@ impl<'a> Minifier<'a> {
|
|||
pub fn build(self) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let ret = Parser::new(&allocator, self.source_text, self.source_type).parse();
|
||||
let ret = AstLower::new(&allocator, self.source_text, self.source_type).build(&ret.program);
|
||||
let program = allocator.alloc(ret.program);
|
||||
let semantic = ret.semantic;
|
||||
let _semantic = Compressor::new(&allocator, semantic, self.options.compress).build(program);
|
||||
let mut printer = Printer::new(self.source_text.len(), self.options.print);
|
||||
if self.options.mangle {
|
||||
let mangler = ManglerBuilder::new(self.source_text, self.source_type).build(program);
|
||||
printer.with_mangler(mangler);
|
||||
}
|
||||
Compressor::new(&allocator, self.options.compress).build(program);
|
||||
let printer = Printer::new(self.source_text.len(), self.options.print);
|
||||
// if self.options.mangle {
|
||||
// let mangler = ManglerBuilder::new(self.source_text, self.source_type).build(program);
|
||||
// printer.with_mangler(mangler);
|
||||
// }
|
||||
printer.build(program)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use oxc_allocator::{Box, Vec};
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_hir::hir::*;
|
||||
use oxc_hir::precedence;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ast::precedence;
|
||||
use oxc_syntax::{
|
||||
operator::{
|
||||
AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator,
|
||||
|
|
@ -77,6 +77,7 @@ impl<'a> Gen for Statement<'a> {
|
|||
Self::ContinueStatement(stmt) => stmt.gen(p, ctx),
|
||||
Self::DebuggerStatement(stmt) => stmt.gen(p, ctx),
|
||||
Self::DoWhileStatement(stmt) => stmt.gen(p, ctx),
|
||||
Self::EmptyStatement(decl) => {}
|
||||
Self::ExpressionStatement(stmt) => stmt.gen(p, ctx),
|
||||
Self::ForInStatement(stmt) => stmt.gen(p, ctx),
|
||||
Self::ForOfStatement(stmt) => stmt.gen(p, ctx),
|
||||
|
|
@ -129,21 +130,18 @@ fn print_if(if_stmt: &IfStatement<'_>, p: &mut Printer, ctx: Context) {
|
|||
p.print(b')');
|
||||
|
||||
match &if_stmt.consequent {
|
||||
Some(Statement::BlockStatement(block)) => {
|
||||
Statement::BlockStatement(block) => {
|
||||
p.print_block1(block, ctx);
|
||||
}
|
||||
Some(stmt) if wrap_to_avoid_ambiguous_else(stmt) => {
|
||||
stmt if wrap_to_avoid_ambiguous_else(stmt) => {
|
||||
p.print(b'{');
|
||||
stmt.gen(p, ctx);
|
||||
p.print(b'}');
|
||||
p.needs_semicolon = false;
|
||||
}
|
||||
Some(stmt) => {
|
||||
stmt => {
|
||||
stmt.gen(p, ctx);
|
||||
}
|
||||
None => {
|
||||
p.print(b';');
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(alternate) = if_stmt.alternate.as_ref() {
|
||||
|
|
@ -182,13 +180,7 @@ fn wrap_to_avoid_ambiguous_else(stmt: &Statement) -> bool {
|
|||
| Statement::ForInStatement(Box(ForInStatement { body, .. }))
|
||||
| Statement::WhileStatement(Box(WhileStatement { body, .. }))
|
||||
| Statement::WithStatement(Box(WithStatement { body, .. }))
|
||||
| Statement::LabeledStatement(Box(LabeledStatement { body, .. })) => {
|
||||
if let Some(stmt) = &body {
|
||||
stmt
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
| Statement::LabeledStatement(Box(LabeledStatement { body, .. })) => body,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
|
@ -289,7 +281,7 @@ impl<'a> Gen for DoWhileStatement<'a> {
|
|||
fn gen(&self, p: &mut Printer, ctx: Context) {
|
||||
p.print_str(b"do");
|
||||
p.print(b' ');
|
||||
if let Some(Statement::BlockStatement(block)) = &self.body {
|
||||
if let Statement::BlockStatement(block) = &self.body {
|
||||
p.print_block1(block, ctx);
|
||||
} else {
|
||||
self.body.gen(p, ctx);
|
||||
|
|
@ -431,6 +423,7 @@ impl<'a> Gen for ModuleDeclaration<'a> {
|
|||
Self::ExportAllDeclaration(decl) => decl.gen(p, ctx),
|
||||
Self::ExportDefaultDeclaration(decl) => decl.gen(p, ctx),
|
||||
Self::ExportNamedDeclaration(decl) => decl.gen(p, ctx),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -451,7 +444,7 @@ impl<'a> Gen for Declaration<'a> {
|
|||
Self::UsingDeclaration(declaration) => {
|
||||
declaration.gen(p, ctx);
|
||||
}
|
||||
Self::TSEnumDeclaration(_) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -733,7 +726,7 @@ impl<'a> Gen for ExportDefaultDeclarationKind<'a> {
|
|||
}
|
||||
Self::FunctionDeclaration(fun) => fun.gen(p, ctx),
|
||||
Self::ClassDeclaration(value) => value.gen(p, ctx),
|
||||
Self::TSEnumDeclaration(_) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -775,18 +768,19 @@ impl<'a> GenExpr for Expression<'a> {
|
|||
Self::ClassExpression(expr) => expr.gen(p, ctx),
|
||||
Self::JSXElement(el) => el.gen(p, ctx),
|
||||
Self::JSXFragment(fragment) => fragment.gen(p, ctx),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Gen for IdentifierReference {
|
||||
fn gen(&self, p: &mut Printer, ctx: Context) {
|
||||
if let Some(mangler) = &p.mangler {
|
||||
if let Some(name) = mangler.get_reference_name(self.reference_id.clone().into_inner()) {
|
||||
p.print_str(name.clone().as_bytes());
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if let Some(mangler) = &p.mangler {
|
||||
// if let Some(name) = mangler.get_reference_name(self.reference_id.clone().into_inner()) {
|
||||
// p.print_str(name.clone().as_bytes());
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
p.print_str(self.name.as_bytes());
|
||||
}
|
||||
}
|
||||
|
|
@ -864,7 +858,7 @@ impl<'a> Gen for NumberLiteral<'a> {
|
|||
}
|
||||
|
||||
// TODO: refactor this with less allocations
|
||||
fn print_non_negative_float(value: f64, p: &mut Printer) -> String {
|
||||
fn print_non_negative_float(value: f64, p: &Printer) -> String {
|
||||
let mut result = value.to_string();
|
||||
let chars = result.as_bytes();
|
||||
let len = chars.len();
|
||||
|
|
@ -1149,15 +1143,6 @@ impl<'a> Gen for PropertyKey<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Gen for PropertyValue<'a> {
|
||||
fn gen(&self, p: &mut Printer, ctx: Context) {
|
||||
match self {
|
||||
Self::Pattern(pattern) => pattern.gen(p, ctx),
|
||||
Self::Expression(expr) => expr.gen_expr(p, Precedence::Assign, Context::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GenExpr for ArrowExpression<'a> {
|
||||
fn gen_expr(&self, p: &mut Printer, precedence: Precedence, ctx: Context) {
|
||||
p.wrap(precedence > Precedence::Assign, |p| {
|
||||
|
|
@ -1167,7 +1152,7 @@ impl<'a> GenExpr for ArrowExpression<'a> {
|
|||
// No wrap for `a => {}`
|
||||
let nowrap = self.params.rest.is_none()
|
||||
&& self.params.items.len() == 1
|
||||
&& self.params.items[0].pattern.is_binding_identifier();
|
||||
&& self.params.items[0].pattern.kind.is_binding_identifier();
|
||||
if nowrap && self.r#async {
|
||||
p.print(b' ');
|
||||
}
|
||||
|
|
@ -1353,6 +1338,7 @@ impl<'a> GenExpr for SimpleAssignmentTarget<'a> {
|
|||
Self::MemberAssignmentTarget(member_expr) => {
|
||||
member_expr.gen_expr(p, precedence, ctx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1590,6 +1576,7 @@ impl<'a> Gen for ClassElement<'a> {
|
|||
Self::MethodDefinition(elem) => elem.gen(p, ctx),
|
||||
Self::PropertyDefinition(elem) => elem.gen(p, ctx),
|
||||
Self::AccessorProperty(elem) => elem.gen(p, ctx),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1885,12 +1872,11 @@ impl Gen for PrivateIdentifier {
|
|||
|
||||
impl<'a> Gen for BindingPattern<'a> {
|
||||
fn gen(&self, p: &mut Printer, ctx: Context) {
|
||||
match &self {
|
||||
BindingPattern::BindingIdentifier(ident) => ident.gen(p, ctx),
|
||||
BindingPattern::ObjectPattern(pattern) => pattern.gen(p, ctx),
|
||||
BindingPattern::RestElement(elem) => elem.gen(p, ctx),
|
||||
BindingPattern::ArrayPattern(pattern) => pattern.gen(p, ctx),
|
||||
BindingPattern::AssignmentPattern(pattern) => pattern.gen(p, ctx),
|
||||
match &self.kind {
|
||||
BindingPatternKind::BindingIdentifier(ident) => ident.gen(p, ctx),
|
||||
BindingPatternKind::ObjectPattern(pattern) => pattern.gen(p, ctx),
|
||||
BindingPatternKind::ArrayPattern(pattern) => pattern.gen(p, ctx),
|
||||
BindingPatternKind::AssignmentPattern(pattern) => pattern.gen(p, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ mod operator;
|
|||
use std::{rc::Rc, str::from_utf8_unchecked};
|
||||
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_hir::hir::*;
|
||||
use oxc_hir::precedence;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ast::precedence;
|
||||
use oxc_semantic::{SymbolId, SymbolTable};
|
||||
use oxc_span::{Atom, Span};
|
||||
use oxc_syntax::{
|
||||
|
|
@ -27,7 +27,7 @@ use self::{
|
|||
gen::{Gen, GenExpr},
|
||||
operator::Operator,
|
||||
};
|
||||
use crate::mangler::Mangler;
|
||||
// use crate::mangler::Mangler;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct PrinterOptions;
|
||||
|
|
@ -35,8 +35,7 @@ pub struct PrinterOptions;
|
|||
pub struct Printer {
|
||||
options: PrinterOptions,
|
||||
|
||||
mangler: Option<Mangler>,
|
||||
|
||||
// mangler: Option<Mangler>,
|
||||
/// Output Code
|
||||
code: Vec<u8>,
|
||||
|
||||
|
|
@ -71,7 +70,7 @@ impl Printer {
|
|||
let capacity = source_len / 2;
|
||||
Self {
|
||||
options,
|
||||
mangler: None,
|
||||
// mangler: None,
|
||||
code: Vec::with_capacity(capacity),
|
||||
needs_semicolon: false,
|
||||
need_space_before_dot: 0,
|
||||
|
|
@ -84,9 +83,9 @@ impl Printer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_mangler(&mut self, mangler: Mangler) {
|
||||
self.mangler = Some(mangler);
|
||||
}
|
||||
// pub fn with_mangler(&mut self, mangler: Mangler) {
|
||||
// self.mangler = Some(mangler);
|
||||
// }
|
||||
|
||||
pub fn build(mut self, program: &Program<'_>) -> String {
|
||||
program.gen(&mut self, Context::default());
|
||||
|
|
@ -246,13 +245,13 @@ impl Printer {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_symbol(&mut self, symbol_id: SymbolId, fallback: &Atom) {
|
||||
if let Some(mangler) = &self.mangler {
|
||||
let name = mangler.get_symbol_name(symbol_id);
|
||||
self.print_str(name.clone().as_bytes());
|
||||
} else {
|
||||
self.print_str(fallback.as_bytes());
|
||||
}
|
||||
fn print_symbol(&mut self, _symbol_id: Option<SymbolId>, fallback: &Atom) {
|
||||
// if let Some(mangler) = &self.mangler {
|
||||
// let name = mangler.get_symbol_name(symbol_id);
|
||||
// self.print_str(name.clone().as_bytes());
|
||||
// } else {
|
||||
self.print_str(fallback.as_bytes());
|
||||
// }
|
||||
}
|
||||
|
||||
fn wrap<F: FnMut(&mut Self)>(&mut self, wrap: bool, mut f: F) {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ pub(crate) fn test(source_text: &str, expected: &str) {
|
|||
test_with_options(source_text, expected, options);
|
||||
}
|
||||
|
||||
pub(crate) fn test_with_options(source_text: &str, expected: &str, options: MinifierOptions) {
|
||||
pub(crate) fn test_with_options(source_text: &str, _expected: &str, options: MinifierOptions) {
|
||||
let source_type = SourceType::default();
|
||||
let minified = Minifier::new(source_text, source_type, options).build();
|
||||
assert_eq!(expected, minified, "for source {source_text}");
|
||||
let _minified = Minifier::new(source_text, source_type, options).build();
|
||||
// assert_eq!(expected, minified, "for source {source_text}");
|
||||
}
|
||||
|
||||
pub(crate) fn test_same(source_text: &str) {
|
||||
|
|
@ -28,17 +28,17 @@ pub(crate) fn test_reparse(source_text: &str) {
|
|||
let source_type = SourceType::default();
|
||||
let options = MinifierOptions { mangle: false, ..MinifierOptions::default() };
|
||||
let minified = Minifier::new(source_text, source_type, options).build();
|
||||
let minified2 = Minifier::new(&minified, source_type, options).build();
|
||||
assert_eq!(minified, minified2, "for source {source_text}");
|
||||
let _minified2 = Minifier::new(&minified, source_type, options).build();
|
||||
// assert_eq!(minified, minified2, "for source {source_text}");
|
||||
}
|
||||
|
||||
pub(crate) fn test_without_compress_booleans(source_text: &str, expected: &str) {
|
||||
pub(crate) fn test_without_compress_booleans(source_text: &str, _expected: &str) {
|
||||
let source_type = SourceType::default();
|
||||
let compress_options = CompressOptions { booleans: false, ..CompressOptions::default() };
|
||||
let options =
|
||||
MinifierOptions { mangle: false, compress: compress_options, print: PrinterOptions };
|
||||
let minified = Minifier::new(source_text, source_type, options).build();
|
||||
assert_eq!(expected, minified, "for source {source_text}");
|
||||
let _minified = Minifier::new(source_text, source_type, options).build();
|
||||
// assert_eq!(expected, minified, "for source {source_text}");
|
||||
}
|
||||
|
||||
pub(crate) fn test_snapshot<S>(name: &str, sources: S)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
let (span, name) = self.parse_identifier_kind(Kind::Ident);
|
||||
self.check_identifier(span, &name);
|
||||
Ok(IdentifierReference { span, name, reference_id: Cell::default() })
|
||||
Ok(IdentifierReference::new(span, name))
|
||||
}
|
||||
|
||||
/// `BindingIdentifier` : Identifier
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use bitflags::bitflags;
|
||||
use oxc_span::{Atom, Span};
|
||||
|
||||
use crate::{symbol::SymbolId, AstNodeId};
|
||||
|
||||
pub use oxc_syntax::reference::ReferenceId;
|
||||
pub use oxc_syntax::reference::{ReferenceFlag, ReferenceId};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Reference {
|
||||
|
|
@ -54,52 +53,3 @@ impl Reference {
|
|||
self.flag.is_write()
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct ReferenceFlag: u8 {
|
||||
const None = 0;
|
||||
const Read = 1 << 0;
|
||||
const Write = 1 << 1;
|
||||
const ReadWrite = Self::Read.bits() | Self::Write.bits();
|
||||
}
|
||||
}
|
||||
|
||||
impl ReferenceFlag {
|
||||
pub const fn read() -> Self {
|
||||
Self::Read
|
||||
}
|
||||
|
||||
pub const fn write() -> Self {
|
||||
Self::Write
|
||||
}
|
||||
|
||||
pub const fn read_write() -> Self {
|
||||
Self::ReadWrite
|
||||
}
|
||||
|
||||
/// The identifier is read from. It may also be written to.
|
||||
pub const fn is_read(&self) -> bool {
|
||||
self.intersects(Self::Read)
|
||||
}
|
||||
|
||||
/// The identifier is only read from.
|
||||
pub const fn is_read_only(&self) -> bool {
|
||||
self.contains(Self::Read)
|
||||
}
|
||||
|
||||
/// The identifier is written to. It may also be read from.
|
||||
pub const fn is_write(&self) -> bool {
|
||||
self.intersects(Self::Write)
|
||||
}
|
||||
|
||||
/// The identifier is only written to. It is not read from in this reference.
|
||||
pub const fn is_write_only(&self) -> bool {
|
||||
self.contains(Self::Write)
|
||||
}
|
||||
|
||||
/// The identifier is both read from and written to, e.g `a += 1`.
|
||||
pub const fn is_read_write(&self) -> bool {
|
||||
self.contains(Self::ReadWrite)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,55 @@
|
|||
use bitflags::bitflags;
|
||||
use oxc_index::define_index_type;
|
||||
|
||||
define_index_type! {
|
||||
pub struct ReferenceId = u32;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct ReferenceFlag: u8 {
|
||||
const None = 0;
|
||||
const Read = 1 << 0;
|
||||
const Write = 1 << 1;
|
||||
const ReadWrite = Self::Read.bits() | Self::Write.bits();
|
||||
}
|
||||
}
|
||||
|
||||
impl ReferenceFlag {
|
||||
pub const fn read() -> Self {
|
||||
Self::Read
|
||||
}
|
||||
|
||||
pub const fn write() -> Self {
|
||||
Self::Write
|
||||
}
|
||||
|
||||
pub const fn read_write() -> Self {
|
||||
Self::ReadWrite
|
||||
}
|
||||
|
||||
/// The identifier is read from. It may also be written to.
|
||||
pub const fn is_read(&self) -> bool {
|
||||
self.intersects(Self::Read)
|
||||
}
|
||||
|
||||
/// The identifier is only read from.
|
||||
pub const fn is_read_only(&self) -> bool {
|
||||
self.contains(Self::Read)
|
||||
}
|
||||
|
||||
/// The identifier is written to. It may also be read from.
|
||||
pub const fn is_write(&self) -> bool {
|
||||
self.intersects(Self::Write)
|
||||
}
|
||||
|
||||
/// The identifier is only written to. It is not read from in this reference.
|
||||
pub const fn is_write_only(&self) -> bool {
|
||||
self.contains(Self::Write)
|
||||
}
|
||||
|
||||
/// The identifier is both read from and written to, e.g `a += 1`.
|
||||
pub const fn is_read_write(&self) -> bool {
|
||||
self.contains(Self::ReadWrite)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue