diff --git a/Cargo.lock b/Cargo.lock index 207fdd7bd91e1..dc2d6088b9eca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1603,6 +1603,7 @@ dependencies = [ name = "oxc_ecmascript" version = "0.31.0" dependencies = [ + "num-bigint", "num-traits", "oxc_ast", "oxc_span", diff --git a/crates/oxc_ecmascript/Cargo.toml b/crates/oxc_ecmascript/Cargo.toml index 1bbad8035fc16..d4dd533a989b6 100644 --- a/crates/oxc_ecmascript/Cargo.toml +++ b/crates/oxc_ecmascript/Cargo.toml @@ -21,7 +21,9 @@ test = true doctest = false [dependencies] -num-traits = { workspace = true } oxc_ast = { workspace = true } oxc_span = { workspace = true } oxc_syntax = { workspace = true } + +num-bigint = { workspace = true } +num-traits = { workspace = true } diff --git a/crates/oxc_ecmascript/src/lib.rs b/crates/oxc_ecmascript/src/lib.rs index 1a8a53fcb73ec..f7a1fe56fe12b 100644 --- a/crates/oxc_ecmascript/src/lib.rs +++ b/crates/oxc_ecmascript/src/lib.rs @@ -10,6 +10,8 @@ mod prop_name; mod string_char_at; mod string_index_of; mod string_last_index_of; +mod string_to_big_int; +mod to_big_int; mod to_boolean; mod to_int_32; mod to_number; @@ -23,6 +25,8 @@ pub use self::{ string_char_at::StringCharAt, string_index_of::StringIndexOf, string_last_index_of::StringLastIndexOf, + string_to_big_int::StringToBigInt, + to_big_int::ToBigInt, to_boolean::ToBoolean, to_int_32::ToInt32, to_number::{NumberValue, ToNumber}, diff --git a/crates/oxc_ecmascript/src/string_to_big_int.rs b/crates/oxc_ecmascript/src/string_to_big_int.rs new file mode 100644 index 0000000000000..844366e9f62c6 --- /dev/null +++ b/crates/oxc_ecmascript/src/string_to_big_int.rs @@ -0,0 +1,42 @@ +use num_traits::Zero; + +use num_bigint::BigInt; + +/// `StringToBigInt` +/// +/// +pub trait StringToBigInt<'a> { + fn string_to_big_int(&self) -> Option; +} + +impl<'a> StringToBigInt<'a> for &str { + fn string_to_big_int(&self) -> Option { + if self.contains('\u{000b}') { + // vertical tab is not always whitespace + return None; + } + + let s = self.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); + } +} diff --git a/crates/oxc_ecmascript/src/to_big_int.rs b/crates/oxc_ecmascript/src/to_big_int.rs new file mode 100644 index 0000000000000..8fed6fd11ef4d --- /dev/null +++ b/crates/oxc_ecmascript/src/to_big_int.rs @@ -0,0 +1,70 @@ +use num_bigint::BigInt; +use num_traits::{One, Zero}; + +use oxc_ast::ast::Expression; +use oxc_syntax::operator::UnaryOperator; + +use crate::{StringToBigInt, ToBoolean, ToJsString}; + +/// `ToBigInt` +/// +/// +pub trait ToBigInt<'a> { + fn to_big_int(&self) -> Option; +} + +impl<'a> ToBigInt<'a> for Expression<'a> { + #[expect(clippy::cast_possible_truncation)] + fn to_big_int(&self) -> Option { + match self { + Expression::NumericLiteral(number_literal) => { + let value = number_literal.value; + if value.abs() < 2_f64.powi(53) && value.fract() == 0.0 { + Some(BigInt::from(value as i64)) + } else { + None + } + } + Expression::BigIntLiteral(bigint_literal) => { + let value = bigint_literal.raw.as_str().trim_end_matches('n').string_to_big_int(); + debug_assert!(value.is_some(), "Failed to parse {}", bigint_literal.raw); + value + } + 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 => { + self.to_boolean().map( + |boolean| { + if boolean { + BigInt::one() + } else { + BigInt::zero() + } + }, + ) + } + UnaryOperator::UnaryNegation => { + unary_expr.argument.to_big_int().map(std::ops::Neg::neg) + } + UnaryOperator::BitwiseNot => { + unary_expr.argument.to_big_int().map(std::ops::Not::not) + } + UnaryOperator::UnaryPlus => unary_expr.argument.to_big_int(), + _ => None, + }, + Expression::StringLiteral(string_literal) => { + string_literal.value.as_str().string_to_big_int() + } + Expression::TemplateLiteral(_) => { + self.to_js_string().and_then(|value| value.as_ref().string_to_big_int()) + } + _ => None, + } + } +} diff --git a/crates/oxc_minifier/src/node_util/mod.rs b/crates/oxc_minifier/src/node_util/mod.rs index e18ee4b424321..1268dcd3c0add 100644 --- a/crates/oxc_minifier/src/node_util/mod.rs +++ b/crates/oxc_minifier/src/node_util/mod.rs @@ -5,11 +5,9 @@ mod may_have_side_effects; use std::borrow::Cow; use num_bigint::BigInt; -use num_traits::{One, Zero}; use oxc_ast::ast::*; -use oxc_ecmascript::{NumberValue, ToBoolean, ToJsString, ToNumber}; +use oxc_ecmascript::{NumberValue, StringToBigInt, ToBigInt, ToBoolean, ToJsString, ToNumber}; use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable}; -use oxc_syntax::operator::UnaryOperator; pub use self::{is_literal_value::IsLiteralValue, may_have_side_effects::MayHaveSideEffects}; @@ -114,55 +112,8 @@ pub trait NodeUtil<'a> { expr.to_number() } - #[allow(clippy::cast_possible_truncation)] fn get_bigint_value(&self, expr: &Expression<'a>) -> Option { - match expr { - Expression::NumericLiteral(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) => { - let value = - self.get_string_bigint_value(bigint_literal.raw.as_str().trim_end_matches('n')); - debug_assert!(value.is_some(), "Failed to parse {}", bigint_literal.raw); - value - } - 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 => self.get_boolean_value(expr).map(|boolean| { - if boolean.is_true() { - BigInt::one() - } else { - BigInt::zero() - } - }), - UnaryOperator::UnaryNegation => { - self.get_bigint_value(&unary_expr.argument).map(std::ops::Neg::neg) - } - UnaryOperator::BitwiseNot => { - self.get_bigint_value(&unary_expr.argument).map(std::ops::Not::not) - } - UnaryOperator::UnaryPlus => self.get_bigint_value(&unary_expr.argument), - _ => None, - }, - Expression::StringLiteral(string_literal) => { - self.get_string_bigint_value(&string_literal.value) - } - Expression::TemplateLiteral(_) => { - self.get_string_value(expr).and_then(|value| self.get_string_bigint_value(&value)) - } - _ => None, - } + expr.to_big_int() } /// Port from [closure-compiler](https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L234) @@ -175,33 +126,7 @@ pub trait NodeUtil<'a> { /// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/NodeUtil.java#L540) fn get_string_bigint_value(&self, raw_string: &str) -> Option { - 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); + raw_string.string_to_big_int() } /// Evaluate and attempt to determine which primitive value type it could resolve to. diff --git a/crates/oxc_minifier/src/tri.rs b/crates/oxc_minifier/src/tri.rs index 815ae3413b9da..f5be34ec1b884 100644 --- a/crates/oxc_minifier/src/tri.rs +++ b/crates/oxc_minifier/src/tri.rs @@ -33,21 +33,6 @@ impl From for Tri { } impl Tri { - pub fn is_true(self) -> bool { - self == Tri::True - } - - pub fn map(self, f: F) -> Option - where - F: FnOnce(Tri) -> U, - { - match self { - Self::True => Some(f(Tri::True)), - Self::False => Some(f(Tri::False)), - Self::Unknown => None, - } - } - pub fn not(self) -> Self { match self { Self::True => Self::False,