use oxc_ast::{AstBuilder, ast::*};
use oxc_compat::ESFeature;
use oxc_ecmascript::{
GlobalContext,
constant_evaluation::{
ConstantEvaluation, ConstantEvaluationCtx, ConstantValue, binary_operation_evaluate_value,
},
side_effects::{
MayHaveSideEffects, MayHaveSideEffectsContext, PropertyReadSideEffects, is_pure_function,
},
};
use oxc_semantic::{IsGlobalReference, SymbolId};
use oxc_span::format_str;
use oxc_syntax::{reference::ReferenceId, scope::ScopeFlags};
use crate::{
generated::ancestor::Ancestor, options::CompressOptions, state::MinifierState,
symbol_value::SymbolValue,
};
use super::TraverseCtx;
pub fn is_exact_int64(num: f64) -> bool {
num.fract() == 0.0
}
impl<'a> GlobalContext<'a> for TraverseCtx<'a, MinifierState<'a>> {
fn is_global_reference(&self, ident: &IdentifierReference<'_>) -> bool {
ident.is_global_reference(self.scoping())
}
fn get_constant_value_for_reference_id(
&self,
reference_id: ReferenceId,
) -> Option<ConstantValue<'a>> {
self.scoping()
.get_reference(reference_id)
.symbol_id()
.and_then(|symbol_id| self.state.symbol_values.get_symbol_value(symbol_id))
.filter(|sv| sv.write_references_count == 0)
.and_then(|sv| sv.initialized_constant.as_ref())
.cloned()
}
}
impl<'a> GlobalContext<'a> for &TraverseCtx<'a, MinifierState<'a>> {
fn is_global_reference(&self, ident: &IdentifierReference<'_>) -> bool {
(*self).is_global_reference(ident)
}
fn get_constant_value_for_reference_id(
&self,
reference_id: ReferenceId,
) -> Option<ConstantValue<'a>> {
(*self).get_constant_value_for_reference_id(reference_id)
}
}
impl<'a> GlobalContext<'a> for &mut TraverseCtx<'a, MinifierState<'a>> {
fn is_global_reference(&self, ident: &IdentifierReference<'_>) -> bool {
(**self).is_global_reference(ident)
}
fn get_constant_value_for_reference_id(
&self,
reference_id: ReferenceId,
) -> Option<ConstantValue<'a>> {
(**self).get_constant_value_for_reference_id(reference_id)
}
}
impl<'a> MayHaveSideEffectsContext<'a> for TraverseCtx<'a, MinifierState<'a>> {
fn annotations(&self) -> bool {
self.state.options.treeshake.annotations
}
fn manual_pure_functions(&self, callee: &Expression) -> bool {
let pure_functions = &self.state.options.treeshake.manual_pure_functions;
if pure_functions.is_empty() {
return false;
}
is_pure_function(callee, pure_functions)
}
fn property_read_side_effects(&self) -> PropertyReadSideEffects {
self.state.options.treeshake.property_read_side_effects
}
fn unknown_global_side_effects(&self) -> bool {
self.state.options.treeshake.unknown_global_side_effects
}
}
impl<'a> MayHaveSideEffectsContext<'a> for &TraverseCtx<'a, MinifierState<'a>> {
fn annotations(&self) -> bool {
(*self).annotations()
}
fn manual_pure_functions(&self, callee: &Expression) -> bool {
(*self).manual_pure_functions(callee)
}
fn property_read_side_effects(&self) -> PropertyReadSideEffects {
(*self).property_read_side_effects()
}
fn unknown_global_side_effects(&self) -> bool {
(*self).unknown_global_side_effects()
}
}
impl<'a> MayHaveSideEffectsContext<'a> for &mut TraverseCtx<'a, MinifierState<'a>> {
fn annotations(&self) -> bool {
(**self).annotations()
}
fn manual_pure_functions(&self, callee: &Expression) -> bool {
(**self).manual_pure_functions(callee)
}
fn property_read_side_effects(&self) -> PropertyReadSideEffects {
(**self).property_read_side_effects()
}
fn unknown_global_side_effects(&self) -> bool {
(**self).unknown_global_side_effects()
}
}
impl<'a> ConstantEvaluationCtx<'a> for TraverseCtx<'a, MinifierState<'a>> {
fn ast(&self) -> AstBuilder<'a> {
self.ast
}
}
impl<'a> ConstantEvaluationCtx<'a> for &TraverseCtx<'a, MinifierState<'a>> {
fn ast(&self) -> AstBuilder<'a> {
(*self).ast()
}
}
impl<'a> ConstantEvaluationCtx<'a> for &mut TraverseCtx<'a, MinifierState<'a>> {
fn ast(&self) -> AstBuilder<'a> {
(**self).ast()
}
}
impl<'a> TraverseCtx<'a, MinifierState<'a>> {
pub fn options(&self) -> &CompressOptions {
&self.state.options
}
pub fn supports_feature(&self, feature: ESFeature) -> bool {
!self.options().target.has_feature(feature)
}
pub fn source_type(&self) -> SourceType {
self.state.source_type
}
pub fn is_global_reference(&self, ident: &IdentifierReference<'a>) -> bool {
ident.is_global_reference(self.scoping())
}
pub fn eval_binary(&self, e: &BinaryExpression<'a>) -> Option<Expression<'a>> {
if e.may_have_side_effects(self) {
None
} else {
e.evaluate_value(self).map(|v| self.value_to_expr(e.span, v))
}
}
pub fn eval_binary_operation(
&self,
operator: BinaryOperator,
left: &Expression<'a>,
right: &Expression<'a>,
) -> Option<ConstantValue<'a>> {
binary_operation_evaluate_value(operator, left, right, self)
}
pub fn value_to_expr(&self, span: Span, value: ConstantValue<'a>) -> Expression<'a> {
match value {
ConstantValue::Number(n) => {
let number_base =
if is_exact_int64(n) { NumberBase::Decimal } else { NumberBase::Float };
self.ast.expression_numeric_literal(span, n, None, number_base)
}
ConstantValue::BigInt(bigint) => {
let value = format_str!(self.ast.allocator, "{bigint}");
self.ast.expression_big_int_literal(span, value, None, BigintBase::Decimal)
}
ConstantValue::String(s) => {
self.ast.expression_string_literal(span, self.ast.str_from_cow(&s), None)
}
ConstantValue::Boolean(b) => self.ast.expression_boolean_literal(span, b),
ConstantValue::Undefined => self.ast.void_0(span),
ConstantValue::Null => self.ast.expression_null_literal(span),
}
}
pub fn is_expression_undefined(&self, expr: &Expression) -> bool {
match expr {
Expression::Identifier(ident) if self.is_identifier_undefined(ident) => true,
Expression::UnaryExpression(e) if e.operator.is_void() && e.argument.is_number() => {
true
}
_ => false,
}
}
#[inline]
pub fn is_identifier_undefined(&self, ident: &IdentifierReference) -> bool {
if ident.name == "undefined" && ident.is_global_reference(self.scoping()) {
return true;
}
false
}
pub fn init_value(&mut self, symbol_id: SymbolId, constant: Option<ConstantValue<'a>>) {
let mut exported = false;
if self.scoping.current_scope_id() == self.scoping().root_scope_id() {
for ancestor in self.ancestors() {
if ancestor.is_export_named_declaration()
|| ancestor.is_export_all_declaration()
|| ancestor.is_export_default_declaration()
{
exported = true;
}
}
}
let mut read_references_count = 0;
let mut write_references_count = 0;
for r in self.scoping().get_resolved_references(symbol_id) {
if r.is_read() {
read_references_count += 1;
}
if r.is_write() {
write_references_count += 1;
}
}
let scope_id = self.scoping().symbol_scope_id(symbol_id);
let scope_flags = self.scoping().scope_flags(scope_id);
let initialized_constant =
if scope_flags.contains(ScopeFlags::DirectEval) { None } else { constant };
let symbol_value = SymbolValue {
initialized_constant,
exported,
read_references_count,
write_references_count,
scope_id,
};
self.state.symbol_values.init_value(symbol_id, symbol_value);
}
pub fn expr_eq(&self, a: &Expression<'a>, b: &Expression<'a>) -> bool {
use oxc_span::ContentEq;
a.content_eq(b) || (self.is_expression_undefined(a) && self.is_expression_undefined(b))
}
pub fn string_to_equivalent_number_value(s: &str) -> Option<f64> {
if s.is_empty() {
return None;
}
let mut is_negative = false;
let mut int_value = 0i32;
let mut start = 0;
let bytes = s.as_bytes();
if bytes[0] == b'-' && s.len() > 1 {
is_negative = true;
int_value = -int_value;
start += 1;
}
if bytes[start] == b'0' && s.len() > 1 {
return None;
}
for b in &bytes[start..] {
if !b.is_ascii_digit() {
return None;
}
int_value = int_value.checked_mul(10).and_then(|v| {
let n = i32::from(b & 15);
if is_negative { v.checked_sub(n) } else { v.checked_add(n) }
})?;
}
Some(f64::from(int_value))
}
pub fn is_closest_function_scope_an_async_generator(&self) -> bool {
self.ancestors()
.find_map(|ancestor| match ancestor {
Ancestor::FunctionBody(body) => Some(*body.r#async() && *body.generator()),
Ancestor::ArrowFunctionExpressionBody(_) => Some(false),
_ => None,
})
.unwrap_or_default()
}
pub fn is_expression_whose_name_needs_to_be_kept(&self, expr: &Expression) -> bool {
let options = &self.options().keep_names;
if !options.class && !options.function {
return false;
}
if !expr.is_anonymous_function_definition() {
return false;
}
let is_class = matches!(expr.without_parentheses(), Expression::ClassExpression(_));
(options.class && is_class) || (options.function && !is_class)
}
}