mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
feat(minifier): local traverse ctx and generated minifier traverse (#19106)
## Summary - generate a minifier-local traverse runtime in `oxc_minifier` - introduce handwritten `MinifierTraverseCtx` and move prior `Ctx` functionality into it - remove old `Ctx` usage and implement the needed traits on `MinifierTraverseCtx` - wire minifier passes/compressor to use local traverse - reuse `ancestor.rs` from `oxc_traverse` via path module in `oxc_minifier` ## Why - avoids orphan-rule friction around implementing traits on the prior context type - keeps minifier traversal concerns local to `oxc_minifier` ## Validation - `just fmt` - `cargo check -p oxc_ast_tools` - `cargo check -p oxc_minifier` - `cargo test -p oxc_minifier` - `just lint`
This commit is contained in:
42 files changed
+13187
-302
No files matched your search
Generated
+2
@@ -2089,6 +2089,7 @@ version = "0.112.0"
|
||||
dependencies = [
|
||||
"cow-utils",
|
||||
"insta",
|
||||
"itoa",
|
||||
"javascript-globals",
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -2104,6 +2105,7 @@ dependencies = [
|
||||
"oxc_semantic",
|
||||
"oxc_sourcemap",
|
||||
"oxc_span",
|
||||
"oxc_str",
|
||||
"oxc_syntax",
|
||||
"oxc_traverse",
|
||||
"pico-args",
|
||||
|
||||
@@ -33,10 +33,12 @@ oxc_parser = { workspace = true }
|
||||
oxc_regular_expression = { workspace = true }
|
||||
oxc_semantic = { workspace = true }
|
||||
oxc_span = { workspace = true }
|
||||
oxc_str = { workspace = true }
|
||||
oxc_syntax = { workspace = true }
|
||||
oxc_traverse = { workspace = true }
|
||||
|
||||
cow-utils = { workspace = true }
|
||||
itoa = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_semantic::{Scoping, SemanticBuilder};
|
||||
use oxc_traverse::ReusableTraverseCtx;
|
||||
|
||||
use crate::{
|
||||
CompressOptions,
|
||||
CompressOptions, ReusableTraverseCtx,
|
||||
peephole::{Normalize, NormalizeOptions, PeepholeOptimizations},
|
||||
state::MinifierState,
|
||||
};
|
||||
@@ -70,7 +69,7 @@ impl<'a> Compressor<'a> {
|
||||
fn run_in_loop(
|
||||
max_iterations: Option<u8>,
|
||||
program: &mut Program<'a>,
|
||||
ctx: &mut ReusableTraverseCtx<'a, MinifierState<'a>>,
|
||||
ctx: &mut ReusableTraverseCtx<'a>,
|
||||
) -> u8 {
|
||||
let mut iteration = 0u8;
|
||||
loop {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#[expect(clippy::redundant_pub_crate)]
|
||||
#[path = "../../../oxc_traverse/src/generated/ancestor.rs"]
|
||||
pub mod ancestor;
|
||||
pub mod traverse;
|
||||
pub mod walk;
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -45,12 +45,14 @@
|
||||
//! See the [crate documentation](https://github.com/oxc-project/oxc/tree/main/crates/oxc_minifier) for more details.
|
||||
|
||||
mod compressor;
|
||||
mod ctx;
|
||||
pub(crate) mod generated;
|
||||
mod keep_var;
|
||||
mod minifier_traverse;
|
||||
mod options;
|
||||
mod peephole;
|
||||
mod state;
|
||||
mod symbol_value;
|
||||
mod traverse_context;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_ast::ast::Program;
|
||||
@@ -63,6 +65,10 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
pub use oxc_mangler::{MangleOptions, MangleOptionsKeepNames};
|
||||
|
||||
pub(crate) use crate::generated::traverse::Traverse;
|
||||
#[doc(hidden)]
|
||||
pub(crate) use crate::traverse_context::MinifierTraverseCtx as TraverseCtx;
|
||||
pub(crate) use crate::traverse_context::ReusableMinifierTraverseCtx as ReusableTraverseCtx;
|
||||
pub use crate::{compressor::Compressor, options::*};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::ptr;
|
||||
|
||||
use oxc_ast::ast::Program;
|
||||
|
||||
use crate::{
|
||||
ReusableTraverseCtx, Traverse,
|
||||
generated::{ancestor::Ancestor, walk::walk_ast},
|
||||
};
|
||||
|
||||
pub fn traverse_mut_with_ctx<'a, Tr: Traverse<'a>>(
|
||||
traverser: &mut Tr,
|
||||
program: &mut Program<'a>,
|
||||
ctx: &mut ReusableTraverseCtx<'a>,
|
||||
) {
|
||||
let program = ptr::from_mut(program);
|
||||
let ctx = ctx.get_mut();
|
||||
|
||||
debug_assert!(ctx.ancestors_depth() == 1);
|
||||
debug_assert!(matches!(ctx.parent(), Ancestor::None));
|
||||
|
||||
// SAFETY: `program` originates from `&mut Program<'a>` and context stack invariants are checked.
|
||||
unsafe { walk_ast(traverser, program, ctx) };
|
||||
|
||||
debug_assert!(ctx.ancestors_depth() == 1);
|
||||
debug_assert!(matches!(ctx.parent(), Ancestor::None));
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use oxc_allocator::TakeIn;
|
||||
use oxc_ast::ast::*;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -13,10 +13,10 @@ impl<'a> PeepholeOptimizations {
|
||||
///
|
||||
/// `foo['bar']` -> `foo.bar`
|
||||
/// `foo?.['bar']` -> `foo?.bar`
|
||||
pub fn convert_to_dotted_properties(expr: &mut MemberExpression<'a>, ctx: &Ctx<'a, '_>) {
|
||||
pub fn convert_to_dotted_properties(expr: &mut MemberExpression<'a>, ctx: &TraverseCtx<'a>) {
|
||||
let MemberExpression::ComputedMemberExpression(e) = expr else { return };
|
||||
let Expression::StringLiteral(s) = &e.expression else { return };
|
||||
if Ctx::is_identifier_name_patched(&s.value) {
|
||||
if TraverseCtx::is_identifier_name_patched(&s.value) {
|
||||
let property = ctx.ast.identifier_name(s.span, s.value);
|
||||
*expr =
|
||||
MemberExpression::StaticMemberExpression(ctx.ast.alloc_static_member_expression(
|
||||
@@ -31,7 +31,7 @@ impl<'a> PeepholeOptimizations {
|
||||
if e.optional {
|
||||
return;
|
||||
}
|
||||
if let Some(n) = Ctx::string_to_equivalent_number_value(v) {
|
||||
if let Some(n) = TraverseCtx::string_to_equivalent_number_value(v) {
|
||||
e.expression = ctx.ast.expression_numeric_literal(s.span, n, None, NumberBase::Decimal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use oxc_ecmascript::{
|
||||
use oxc_span::{GetSpan, SPAN};
|
||||
use oxc_syntax::operator::{BinaryOperator, LogicalOperator};
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -17,7 +17,7 @@ use super::PeepholeOptimizations;
|
||||
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
|
||||
impl<'a> PeepholeOptimizations {
|
||||
#[expect(clippy::float_cmp)]
|
||||
pub fn fold_unary_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn fold_unary_expr(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::UnaryExpression(e) = expr else { return };
|
||||
match e.operator {
|
||||
// Do not fold `void 0` back to `undefined`.
|
||||
@@ -37,7 +37,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_static_member_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn fold_static_member_expr(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::StaticMemberExpression(e) = expr else { return };
|
||||
// TODO: tryFoldObjectPropAccess(n, left, name)
|
||||
if e.object.may_have_side_effects(ctx) {
|
||||
@@ -49,7 +49,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_computed_member_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn fold_computed_member_expr(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::ComputedMemberExpression(e) = expr else { return };
|
||||
// TODO: tryFoldObjectPropAccess(n, left, name)
|
||||
if e.object.may_have_side_effects(ctx) || e.expression.may_have_side_effects(ctx) {
|
||||
@@ -61,7 +61,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_logical_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn fold_logical_expr(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::LogicalExpression(e) = expr else { return };
|
||||
if let Some(changed) = match e.operator {
|
||||
LogicalOperator::And | LogicalOperator::Or => Self::try_fold_and_or(e, ctx),
|
||||
@@ -72,7 +72,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_chain_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn fold_chain_expr(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::ChainExpression(e) = expr else { return };
|
||||
let left_expr = match &e.expression {
|
||||
match_member_expression!(ChainElement) => {
|
||||
@@ -104,7 +104,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// port from [closure-compiler](https://github.com/google/closure-compiler/blob/09094b551915a6487a980a783831cba58b5739d1/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L587)
|
||||
pub fn try_fold_and_or(
|
||||
logical_expr: &mut LogicalExpression<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let op = logical_expr.operator;
|
||||
debug_assert!(matches!(op, LogicalOperator::And | LogicalOperator::Or));
|
||||
@@ -174,7 +174,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// Try to fold a nullish coalesce `foo ?? bar`.
|
||||
pub fn try_fold_coalesce(
|
||||
logical_expr: &mut LogicalExpression<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
debug_assert_eq!(logical_expr.operator, LogicalOperator::Coalesce);
|
||||
let left = &logical_expr.left;
|
||||
@@ -249,7 +249,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
pub fn fold_binary_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn fold_binary_expr(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::BinaryExpression(e) = expr else { return };
|
||||
// TODO: tryReduceOperandsForOp
|
||||
|
||||
@@ -353,7 +353,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
// Simplified version of `tryFoldAdd` from closure compiler.
|
||||
fn try_fold_add(e: &mut BinaryExpression<'a>, ctx: &Ctx<'a, '_>) -> Option<Expression<'a>> {
|
||||
fn try_fold_add(e: &mut BinaryExpression<'a>, ctx: &TraverseCtx<'a>) -> Option<Expression<'a>> {
|
||||
if !e.may_have_side_effects(ctx)
|
||||
&& let Some(v) = e.evaluate_value(ctx)
|
||||
{
|
||||
@@ -399,7 +399,7 @@ impl<'a> PeepholeOptimizations {
|
||||
left_expr: &mut Expression<'a>,
|
||||
right_expr: &mut Expression<'a>,
|
||||
parent_span: Span,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if let Expression::TemplateLiteral(left) = left_expr {
|
||||
// "`${a}b` + `x${y}`" => "`${a}bx${y}`"
|
||||
@@ -486,7 +486,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
fn try_fold_left_child_op(
|
||||
e: &mut BinaryExpression<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let op = e.operator;
|
||||
debug_assert!(matches!(
|
||||
@@ -521,7 +521,7 @@ impl<'a> PeepholeOptimizations {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn fold_call_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn fold_call_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::CallExpression(e) = expr else { return };
|
||||
if !ctx.is_global_expr("Number", &e.callee) {
|
||||
return;
|
||||
@@ -561,7 +561,7 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.state.changed = true;
|
||||
}
|
||||
|
||||
pub fn fold_binary_typeof_comparison(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn fold_binary_typeof_comparison(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::BinaryExpression(e) = expr else { return };
|
||||
// `typeof a == typeof a` -> `true`, `typeof a != typeof a` -> `false`
|
||||
if e.operator.is_equality()
|
||||
@@ -620,8 +620,8 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_object_exp(e: &mut ObjectExpression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
fn should_fold_spread_element<'a>(e: &Expression<'a>, ctx: &Ctx<'a, '_>) -> bool {
|
||||
pub fn fold_object_exp(e: &mut ObjectExpression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
fn should_fold_spread_element<'a>(e: &Expression<'a>, ctx: &TraverseCtx<'a>) -> bool {
|
||||
match e {
|
||||
Expression::ArrayExpression(o) if o.elements.is_empty() => true,
|
||||
Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) => true,
|
||||
@@ -692,7 +692,10 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.state.changed = true;
|
||||
}
|
||||
|
||||
fn is_spread_inlineable_object_literal(e: &ObjectExpression<'a>, ctx: &Ctx<'a, '_>) -> bool {
|
||||
fn is_spread_inlineable_object_literal(
|
||||
e: &ObjectExpression<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
e.properties.iter().all(|p| match p {
|
||||
ObjectPropertyKind::SpreadProperty(_) => true,
|
||||
ObjectPropertyKind::ObjectProperty(p) => {
|
||||
@@ -712,7 +715,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// Inline constant values in template literals
|
||||
///
|
||||
/// - `foo${1}bar${i}` => `foo1bar${i}`
|
||||
pub fn inline_template_literal(t: &mut TemplateLiteral<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn inline_template_literal(t: &mut TemplateLiteral<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let has_expr_to_inline = t
|
||||
.expressions
|
||||
.iter()
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use crate::generated::ancestor::Ancestor;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, ConstantValue};
|
||||
use oxc_span::GetSpan;
|
||||
use oxc_traverse::Ancestor;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
impl<'a> PeepholeOptimizations {
|
||||
pub fn init_symbol_value(decl: &VariableDeclarator<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn init_symbol_value(decl: &VariableDeclarator<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let BindingPattern::BindingIdentifier(ident) = &decl.id else { return };
|
||||
let Some(symbol_id) = ident.symbol_id.get() else { return };
|
||||
let value = if decl.kind.is_var() || Self::is_for_statement_init(ctx) {
|
||||
@@ -21,11 +21,11 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.init_value(symbol_id, value);
|
||||
}
|
||||
|
||||
fn is_for_statement_init(ctx: &Ctx<'a, '_>) -> bool {
|
||||
fn is_for_statement_init(ctx: &TraverseCtx<'a>) -> bool {
|
||||
ctx.ancestors().nth(1).is_some_and(Ancestor::is_parent_of_for_statement_left)
|
||||
}
|
||||
|
||||
pub fn inline_identifier_reference(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn inline_identifier_reference(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::Identifier(ident) = expr else { return };
|
||||
let reference_id = ident.reference_id();
|
||||
let Some(symbol_id) = ctx.scoping().get_reference(reference_id).symbol_id() else { return };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
use oxc_allocator::TakeIn;
|
||||
use oxc_ast::{NONE, ast::*};
|
||||
use oxc_compat::ESFeature;
|
||||
@@ -16,7 +16,7 @@ impl<'a> PeepholeOptimizations {
|
||||
test: Expression<'a>,
|
||||
consequent: Expression<'a>,
|
||||
alternate: Expression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let mut cond_expr = ctx.ast.conditional_expression(span, test, consequent, alternate);
|
||||
Self::minimize_conditional_expression(&mut cond_expr, ctx)
|
||||
@@ -26,7 +26,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// `MangleIfExpr`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2745>
|
||||
pub fn minimize_conditional_expression(
|
||||
expr: &mut ConditionalExpression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
match &mut expr.test {
|
||||
// "(a, b) ? c : d" => "a, b ? c : d"
|
||||
@@ -416,7 +416,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// - `x ? a = 0 : a = 1` -> `a = x ? 0 : 1`
|
||||
fn try_merge_conditional_expression_inside(
|
||||
expr: &mut ConditionalExpression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let (
|
||||
Expression::AssignmentExpression(consequent),
|
||||
@@ -461,7 +461,7 @@ impl<'a> PeepholeOptimizations {
|
||||
target_id_name: &str,
|
||||
expr_to_inject: &mut Expression<'a>,
|
||||
expr: &mut Expression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
if Self::inject_optional_chaining_if_matched_inner(
|
||||
target_id_name,
|
||||
@@ -486,7 +486,7 @@ impl<'a> PeepholeOptimizations {
|
||||
target_id_name: &str,
|
||||
expr_to_inject: &mut Expression<'a>,
|
||||
expr: &mut Expression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
match expr {
|
||||
Expression::StaticMemberExpression(e) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ use oxc_ecmascript::{
|
||||
use oxc_semantic::ReferenceFlags;
|
||||
use oxc_span::GetSpan;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -27,7 +27,7 @@ impl<'a> PeepholeOptimizations {
|
||||
op: LogicalOperator,
|
||||
a: Expression<'a>,
|
||||
b: Expression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
// "(a, b) op c" => "a, b op c"
|
||||
if let Expression::SequenceExpression(mut sequence_expr) = a {
|
||||
@@ -67,7 +67,7 @@ impl<'a> PeepholeOptimizations {
|
||||
// ^^^^^^^^^^^^^^ `ctx.expression_value_type(&e.left).is_boolean()` is `true`.
|
||||
// `x >> +y !== 0` -> `x >> +y`
|
||||
// ^^^^^^^ ctx.expression_value_type(&e.left).is_number()` is `true`.
|
||||
pub fn minimize_binary(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn minimize_binary(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::BinaryExpression(e) = expr else { return };
|
||||
if !e.operator.is_equality() {
|
||||
return;
|
||||
@@ -124,7 +124,7 @@ impl<'a> PeepholeOptimizations {
|
||||
///
|
||||
/// In `IsLooselyEqual`, `true` and `false` are converted to `1` and `0` first.
|
||||
/// <https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islooselyequal>
|
||||
pub fn minimize_loose_boolean(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn minimize_loose_boolean(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::BinaryExpression(e) = e else { return };
|
||||
if !matches!(e.operator, BinaryOperator::Equality | BinaryOperator::Inequality) {
|
||||
return;
|
||||
@@ -173,7 +173,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// This can only be done for resolved identifiers as this would avoid setting `a` when `a` is truthy.
|
||||
pub fn minimize_normal_assignment_to_combined_logical_assignment(
|
||||
expr: &mut AssignmentExpression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if !ctx.supports_feature(ESFeature::ES2021LogicalAssignmentOperators)
|
||||
|| !matches!(expr.operator, AssignmentOperator::Assign)
|
||||
@@ -212,7 +212,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// Compress `a = a + b` to `a += b`
|
||||
pub fn minimize_normal_assignment_to_combined_assignment(
|
||||
expr: &mut AssignmentExpression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if !matches!(expr.operator, AssignmentOperator::Assign) {
|
||||
return;
|
||||
@@ -235,7 +235,7 @@ impl<'a> PeepholeOptimizations {
|
||||
#[expect(clippy::float_cmp)]
|
||||
pub fn minimize_assignment_to_update_expression(
|
||||
expr: &mut Expression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let Expression::AssignmentExpression(e) = expr else { return };
|
||||
if !matches!(e.operator, AssignmentOperator::Subtraction) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, IsInt32OrUint32};
|
||||
use oxc_span::GetSpan;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -13,7 +13,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// `SimplifyBooleanExpr`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2059>
|
||||
pub fn minimize_expression_in_boolean_context(
|
||||
expr: &mut Expression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
match expr {
|
||||
// "!!a" => "a"
|
||||
|
||||
@@ -2,13 +2,13 @@ use oxc_allocator::TakeIn;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_span::GetSpan;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
impl<'a> PeepholeOptimizations {
|
||||
/// `mangleFor`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_parser.go#L9801>
|
||||
pub fn minimize_for_statement(for_stmt: &mut ForStatement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn minimize_for_statement(for_stmt: &mut ForStatement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
// Get the first statement in the loop
|
||||
let mut first = &for_stmt.body;
|
||||
if let Statement::BlockStatement(block_stmt) = first {
|
||||
@@ -106,7 +106,7 @@ impl<'a> PeepholeOptimizations {
|
||||
span: Span,
|
||||
body: Option<Statement<'a>>,
|
||||
replace: Option<Statement<'a>>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Statement<'a> {
|
||||
match body {
|
||||
Some(Statement::BlockStatement(mut block_stmt)) if !block_stmt.body.is_empty() => {
|
||||
|
||||
@@ -4,7 +4,7 @@ use oxc_ast::ast::*;
|
||||
use oxc_semantic::ScopeFlags;
|
||||
use oxc_span::GetSpan;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -12,7 +12,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// `MangleIf`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_parser/js_parser.go#L9860>
|
||||
pub fn try_minimize_if(
|
||||
if_stmt: &mut IfStatement<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<Statement<'a>> {
|
||||
Self::wrap_to_avoid_ambiguous_else(if_stmt, ctx);
|
||||
if let Statement::ExpressionStatement(expr_stmt) = &mut if_stmt.consequent {
|
||||
@@ -126,7 +126,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
/// Wrap to avoid ambiguous else.
|
||||
/// `if (foo) if (bar) baz else quaz` -> `if (foo) { if (bar) baz else quaz }`
|
||||
fn wrap_to_avoid_ambiguous_else(if_stmt: &mut IfStatement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
fn wrap_to_avoid_ambiguous_else(if_stmt: &mut IfStatement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if let Statement::IfStatement(if2) = &mut if_stmt.consequent
|
||||
&& if2.consequent.is_jump_statement()
|
||||
&& if2.alternate.is_some()
|
||||
|
||||
@@ -4,12 +4,12 @@ use oxc_compat::ESFeature;
|
||||
use oxc_semantic::ReferenceFlags;
|
||||
use oxc_span::{ContentEq, GetSpan, SPAN};
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
impl<'a> PeepholeOptimizations {
|
||||
pub fn minimize_logical_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn minimize_logical_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::LogicalExpression(e) = expr else { return };
|
||||
if let Some(changed) = Self::try_compress_is_null_or_undefined(e, ctx) {
|
||||
*expr = changed;
|
||||
@@ -32,7 +32,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// - `document.all == null` is `true`
|
||||
fn try_compress_is_null_or_undefined(
|
||||
expr: &mut LogicalExpression<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let op = expr.operator;
|
||||
let target_ops = match op {
|
||||
@@ -78,7 +78,7 @@ impl<'a> PeepholeOptimizations {
|
||||
right: &mut Expression<'a>,
|
||||
span: Span,
|
||||
(find_op, replace_op): (BinaryOperator, BinaryOperator),
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
enum LeftPairValueResult {
|
||||
Null(Span),
|
||||
@@ -163,7 +163,7 @@ impl<'a> PeepholeOptimizations {
|
||||
pub fn has_no_side_effect_for_evaluation_same_target(
|
||||
assignment_target: &AssignmentTarget<'a>,
|
||||
expr: &Expression,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
if let (
|
||||
AssignmentTarget::AssignmentTargetIdentifier(write_id_ref),
|
||||
@@ -207,7 +207,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// Also `a || (foo, bar, a = b)` to `a ||= (foo, bar, b)`
|
||||
fn try_compress_logical_expression_to_assignment_expression(
|
||||
expr: &mut Expression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if !ctx.supports_feature(ESFeature::ES2021LogicalAssignmentOperators) {
|
||||
return;
|
||||
@@ -286,7 +286,10 @@ impl<'a> PeepholeOptimizations {
|
||||
///
|
||||
/// When creating AssignmentTargetIdentifier from normal expressions, the identifier only has ReferenceFlags::Write.
|
||||
/// But assignment expressions changes the value, so we should add ReferenceFlags::Read.
|
||||
pub fn mark_assignment_target_as_read(assign_target: &AssignmentTarget, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn mark_assignment_target_as_read(
|
||||
assign_target: &AssignmentTarget,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if let AssignmentTarget::AssignmentTargetIdentifier(id) = assign_target {
|
||||
let reference = ctx.scoping_mut().get_reference_mut(id.reference_id());
|
||||
reference.flags_mut().insert(ReferenceFlags::Read);
|
||||
|
||||
@@ -3,19 +3,23 @@ use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::constant_evaluation::DetermineValueType;
|
||||
use oxc_span::GetSpan;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
impl<'a> PeepholeOptimizations {
|
||||
pub fn minimize_not(span: Span, expr: Expression<'a>, ctx: &mut Ctx<'a, '_>) -> Expression<'a> {
|
||||
pub fn minimize_not(
|
||||
span: Span,
|
||||
expr: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let mut unary = ctx.ast.expression_unary(span, UnaryOperator::LogicalNot, expr);
|
||||
Self::minimize_unary(&mut unary, ctx);
|
||||
unary
|
||||
}
|
||||
|
||||
/// `MaybeSimplifyNot`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L73>
|
||||
pub fn minimize_unary(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn minimize_unary(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::UnaryExpression(e) = expr else { return };
|
||||
if !e.operator.is_not() {
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{iter, ops::ControlFlow};
|
||||
|
||||
use crate::generated::ancestor::Ancestor;
|
||||
use oxc_allocator::{Box, TakeIn, Vec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ast_visit::Visit;
|
||||
@@ -9,9 +10,8 @@ use oxc_ecmascript::{
|
||||
};
|
||||
use oxc_semantic::ScopeFlags;
|
||||
use oxc_span::{ContentEq, GetSpan};
|
||||
use oxc_traverse::Ancestor;
|
||||
|
||||
use crate::{ctx::Ctx, keep_var::KeepVar};
|
||||
use crate::{TraverseCtx, keep_var::KeepVar};
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -33,7 +33,7 @@ impl<'a> PeepholeOptimizations {
|
||||
///
|
||||
/// ## MinimizeExitPoints:
|
||||
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/MinimizeExitPoints.java>
|
||||
pub fn minimize_statements(stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn minimize_statements(stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||
let mut old_stmts = stmts.take_in(ctx.ast);
|
||||
let mut is_control_flow_dead = false;
|
||||
let mut keep_var = KeepVar::new(ctx.ast);
|
||||
@@ -299,7 +299,7 @@ impl<'a> PeepholeOptimizations {
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
is_control_flow_dead: &mut bool,
|
||||
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> ControlFlow<()> {
|
||||
match stmt {
|
||||
Statement::EmptyStatement(_) => (),
|
||||
@@ -349,7 +349,7 @@ impl<'a> PeepholeOptimizations {
|
||||
fn join_sequence(
|
||||
a: &mut Expression<'a>,
|
||||
b: &mut Expression<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let a = a.take_in(ctx.ast);
|
||||
let b = b.take_in(ctx.ast);
|
||||
@@ -384,7 +384,7 @@ impl<'a> PeepholeOptimizations {
|
||||
mut var_decl: Box<'a, VariableDeclaration<'a>>,
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if let Some(first_decl) = var_decl.declarations.first_mut()
|
||||
&& let Some(first_decl_init) = first_decl.init.as_mut()
|
||||
@@ -447,7 +447,7 @@ impl<'a> PeepholeOptimizations {
|
||||
mut expr_stmt: Box<'a, ExpressionStatement<'a>>,
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let changed = Self::substitute_single_use_symbol_in_statement(
|
||||
&mut expr_stmt.expression,
|
||||
@@ -523,7 +523,7 @@ impl<'a> PeepholeOptimizations {
|
||||
fn merge_assignment_to_declaration(
|
||||
assign_expr: &mut AssignmentExpression<'a>,
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
if assign_expr.operator != AssignmentOperator::Assign {
|
||||
return false;
|
||||
@@ -576,7 +576,7 @@ impl<'a> PeepholeOptimizations {
|
||||
mut switch_stmt: Box<'a, SwitchStatement<'a>>,
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let changed = Self::substitute_single_use_symbol_in_statement(
|
||||
&mut switch_stmt.discriminant,
|
||||
@@ -637,7 +637,7 @@ impl<'a> PeepholeOptimizations {
|
||||
mut if_stmt: Box<'a, IfStatement<'a>>,
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> ControlFlow<()> {
|
||||
let changed =
|
||||
Self::substitute_single_use_symbol_in_statement(&mut if_stmt.test, result, ctx, false);
|
||||
@@ -790,7 +790,7 @@ impl<'a> PeepholeOptimizations {
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
is_control_flow_dead: &mut bool,
|
||||
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if let Some(ret_argument_expr) = &mut ret_stmt.argument {
|
||||
let changed = Self::substitute_single_use_symbol_in_statement(
|
||||
@@ -846,7 +846,7 @@ impl<'a> PeepholeOptimizations {
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
is_control_flow_dead: &mut bool,
|
||||
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let changed = Self::substitute_single_use_symbol_in_statement(
|
||||
&mut throw_stmt.argument,
|
||||
@@ -875,7 +875,7 @@ impl<'a> PeepholeOptimizations {
|
||||
mut for_stmt: Box<'a, ForStatement<'a>>,
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if let Some(init) = &mut for_stmt.init {
|
||||
match init {
|
||||
@@ -976,7 +976,7 @@ impl<'a> PeepholeOptimizations {
|
||||
mut for_in_stmt: Box<'a, ForInStatement<'a>>,
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// Annex B.3.5 allows initializers in non-strict mode
|
||||
// <https://tc39.es/ecma262/multipage/additional-ecmascript-features-for-web-browsers.html#sec-initializers-in-forin-statement-heads>
|
||||
@@ -1069,7 +1069,7 @@ impl<'a> PeepholeOptimizations {
|
||||
fn handle_for_of_statement(
|
||||
mut for_of_stmt: Box<'a, ForOfStatement<'a>>,
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let is_block_scoped_decl = matches!(&for_of_stmt.left, ForStatementLeft::VariableDeclaration(var_decl) if !var_decl.kind.is_var());
|
||||
let changed = Self::substitute_single_use_symbol_in_statement(
|
||||
@@ -1114,7 +1114,7 @@ impl<'a> PeepholeOptimizations {
|
||||
fn handle_block(
|
||||
result: &mut Vec<'a, Statement<'a>>,
|
||||
block_stmt: Box<'a, BlockStatement<'a>>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let keep_block = block_stmt.body.iter().any(Self::statement_cares_about_scope);
|
||||
if keep_block {
|
||||
@@ -1166,7 +1166,7 @@ impl<'a> PeepholeOptimizations {
|
||||
fn substitute_single_use_symbol_in_statement(
|
||||
expr_in_stmt: &mut Expression<'a>,
|
||||
stmts: &mut Vec<'a, Statement<'a>>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
non_scoped_literal_only: bool,
|
||||
) -> bool {
|
||||
if Self::keep_top_level_var_in_script_mode(ctx)
|
||||
@@ -1204,7 +1204,7 @@ impl<'a> PeepholeOptimizations {
|
||||
fn substitute_single_use_symbol_within_declaration(
|
||||
kind: VariableDeclarationKind,
|
||||
declarations: &mut Vec<'a, VariableDeclarator<'a>>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
if Self::keep_top_level_var_in_script_mode(ctx)
|
||||
|| ctx.current_scope_flags().contains_direct_eval()
|
||||
@@ -1240,7 +1240,7 @@ impl<'a> PeepholeOptimizations {
|
||||
fn substitute_single_use_symbol_in_expression_from_declarators(
|
||||
target_expr: &mut Expression<'a>,
|
||||
declarators: &mut [VariableDeclarator<'a>],
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
non_scoped_literal_only: bool,
|
||||
) -> usize {
|
||||
let last_non_inlined_index = declarators.iter_mut().rposition(|prev_decl| {
|
||||
@@ -1297,7 +1297,7 @@ impl<'a> PeepholeOptimizations {
|
||||
search_for: &str,
|
||||
replacement: &mut Expression<'a>,
|
||||
replacement_has_side_effect: bool,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<bool> {
|
||||
match target_expr {
|
||||
Expression::Identifier(id) => {
|
||||
|
||||
@@ -23,12 +23,8 @@ use rustc_hash::FxHashSet;
|
||||
|
||||
use oxc_allocator::Vec;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_traverse::{ReusableTraverseCtx, Traverse, traverse_mut_with_ctx};
|
||||
|
||||
use crate::{
|
||||
ctx::{Ctx, TraverseCtx},
|
||||
state::MinifierState,
|
||||
};
|
||||
use crate::{ReusableTraverseCtx, Traverse, TraverseCtx, minifier_traverse::traverse_mut_with_ctx};
|
||||
|
||||
pub use self::normalize::{Normalize, NormalizeOptions};
|
||||
|
||||
@@ -36,11 +32,7 @@ pub use self::normalize::{Normalize, NormalizeOptions};
|
||||
pub struct PeepholeOptimizations;
|
||||
|
||||
impl<'a> PeepholeOptimizations {
|
||||
pub fn run_once(
|
||||
&mut self,
|
||||
program: &mut Program<'a>,
|
||||
ctx: &mut ReusableTraverseCtx<'a, MinifierState<'a>>,
|
||||
) {
|
||||
pub fn run_once(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
|
||||
traverse_mut_with_ctx(self, program, ctx);
|
||||
}
|
||||
|
||||
@@ -79,7 +71,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// which would change the semantics.
|
||||
pub fn member_object_may_be_mutated(
|
||||
assignment_target: &AssignmentTarget<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
let object = match assignment_target {
|
||||
AssignmentTarget::ComputedMemberExpression(member_expr) => &member_expr.object,
|
||||
@@ -97,7 +89,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// or if the expression is not a simple identifier/this reference.
|
||||
pub fn is_expression_that_reference_may_change(
|
||||
expr: &Expression<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
match expr {
|
||||
Expression::Identifier(id) => {
|
||||
@@ -114,7 +106,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
impl<'a> Traverse<'a> for PeepholeOptimizations {
|
||||
fn enter_program(&mut self, _program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
ctx.state.symbol_values.clear();
|
||||
ctx.state.changed = false;
|
||||
@@ -136,7 +128,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
}
|
||||
|
||||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::minimize_statements(stmts, ctx);
|
||||
}
|
||||
|
||||
@@ -144,12 +135,10 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::keep_track_of_pure_functions(stmt, ctx);
|
||||
}
|
||||
|
||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
if ctx.state.dce {
|
||||
match stmt {
|
||||
Statement::BlockStatement(_) => Self::try_optimize_block(stmt, ctx),
|
||||
@@ -213,7 +202,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_for_statement(stmt, ctx);
|
||||
Self::minimize_for_statement(stmt, ctx);
|
||||
}
|
||||
@@ -222,7 +210,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_return_statement(stmt, ctx);
|
||||
}
|
||||
|
||||
@@ -234,7 +221,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_variable_declaration(decl, ctx);
|
||||
}
|
||||
|
||||
@@ -243,12 +229,10 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
decl: &mut VariableDeclarator<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::init_symbol_value(decl, ctx);
|
||||
}
|
||||
|
||||
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
if ctx.state.dce {
|
||||
match expr {
|
||||
Expression::TemplateLiteral(t) => {
|
||||
@@ -370,7 +354,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
return;
|
||||
}
|
||||
if expr.operator.is_not() {
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::minimize_expression_in_boolean_context(&mut expr.argument, ctx);
|
||||
}
|
||||
}
|
||||
@@ -379,7 +362,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_call_expression(e, ctx);
|
||||
Self::remove_empty_spread_arguments(&mut e.arguments);
|
||||
}
|
||||
@@ -388,7 +370,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_new_expression(e, ctx);
|
||||
Self::remove_empty_spread_arguments(&mut e.arguments);
|
||||
}
|
||||
@@ -397,7 +378,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_object_property(prop, ctx);
|
||||
}
|
||||
|
||||
@@ -409,7 +389,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_assignment_target_property(node, ctx);
|
||||
}
|
||||
|
||||
@@ -421,7 +400,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_assignment_target_property_property(prop, ctx);
|
||||
}
|
||||
|
||||
@@ -429,7 +407,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_binding_property(prop, ctx);
|
||||
}
|
||||
|
||||
@@ -441,7 +418,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_method_definition(prop, ctx);
|
||||
}
|
||||
|
||||
@@ -453,7 +429,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_property_definition(prop, ctx);
|
||||
}
|
||||
|
||||
@@ -465,7 +440,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::substitute_accessor_property(prop, ctx);
|
||||
}
|
||||
|
||||
@@ -477,8 +451,7 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = Ctx::new(ctx);
|
||||
Self::convert_to_dotted_properties(expr, &ctx);
|
||||
Self::convert_to_dotted_properties(expr, ctx);
|
||||
}
|
||||
|
||||
fn enter_class_body(&mut self, _body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
@@ -492,7 +465,6 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = &mut Ctx::new(ctx);
|
||||
Self::remove_dead_code_exit_class_body(body, ctx);
|
||||
Self::remove_unused_private_members(body, ctx);
|
||||
ctx.state.class_symbols_stack.pop_class_scope(Self::get_declared_private_symbols(body));
|
||||
@@ -502,8 +474,7 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
|
||||
if ctx.state.dce {
|
||||
return;
|
||||
}
|
||||
let ctx = Ctx::new(ctx);
|
||||
Self::substitute_catch_clause(catch, &ctx);
|
||||
Self::substitute_catch_clause(catch, ctx);
|
||||
}
|
||||
|
||||
fn exit_private_field_expression(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::generated::ancestor::Ancestor;
|
||||
use oxc_allocator::{TakeIn, Vec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{
|
||||
@@ -7,12 +8,8 @@ use oxc_ecmascript::{
|
||||
use oxc_semantic::IsGlobalReference;
|
||||
use oxc_span::GetSpan;
|
||||
use oxc_syntax::scope::ScopeFlags;
|
||||
use oxc_traverse::{Ancestor, ReusableTraverseCtx, Traverse, traverse_mut_with_ctx};
|
||||
|
||||
use crate::{
|
||||
ctx::{Ctx, TraverseCtx},
|
||||
state::MinifierState,
|
||||
};
|
||||
use crate::{ReusableTraverseCtx, Traverse, TraverseCtx, minifier_traverse::traverse_mut_with_ctx};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NormalizeOptions {
|
||||
@@ -46,16 +43,12 @@ pub struct Normalize {
|
||||
}
|
||||
|
||||
impl<'a> Normalize {
|
||||
pub fn build(
|
||||
&mut self,
|
||||
program: &mut Program<'a>,
|
||||
ctx: &mut ReusableTraverseCtx<'a, MinifierState<'a>>,
|
||||
) {
|
||||
pub fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
|
||||
traverse_mut_with_ctx(self, program, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Traverse<'a, MinifierState<'a>> for Normalize {
|
||||
impl<'a> Traverse<'a> for Normalize {
|
||||
fn exit_program(&mut self, node: &mut Program<'a>, _ctx: &mut TraverseCtx<'a>) {
|
||||
if self.options.remove_unnecessary_use_strict && node.source_type.is_module() {
|
||||
node.directives.drain_filter(|d| d.directive.as_str() == "use strict");
|
||||
@@ -271,7 +264,7 @@ impl<'a> Normalize {
|
||||
|
||||
fn fold_number_nan_to_nan(
|
||||
e: &StaticMemberExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let Expression::Identifier(ident) = &e.object else { return None };
|
||||
if ident.name != "Number" {
|
||||
@@ -280,7 +273,7 @@ impl<'a> Normalize {
|
||||
if e.property.name != "NaN" {
|
||||
return None;
|
||||
}
|
||||
if !Ctx::new(ctx).is_global_reference(ident) {
|
||||
if !ctx.is_global_reference(ident) {
|
||||
return None;
|
||||
}
|
||||
Some(ctx.ast.nan(ident.span))
|
||||
@@ -305,7 +298,7 @@ impl<'a> Normalize {
|
||||
/// `PC` or `PC_WITH_ARRAY` in <https://github.com/rollup/rollup/blob/v4.42.0/src/ast/nodes/shared/knownGlobals.ts>
|
||||
fn set_pure_or_no_side_effects_to_new_expr(
|
||||
new_expr: &mut NewExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) {
|
||||
if new_expr.pure {
|
||||
return;
|
||||
@@ -321,7 +314,6 @@ impl<'a> Normalize {
|
||||
return;
|
||||
}
|
||||
// callee is a global reference.
|
||||
let ctx = Ctx::new(ctx);
|
||||
let len = new_expr.arguments.len();
|
||||
|
||||
let (zero_arg_throws_error, one_arg_array_throws_error, one_arg_throws_error): (
|
||||
@@ -371,7 +363,7 @@ impl<'a> Normalize {
|
||||
| "TypeError" | "URIError" | "Number" | "Object" | "String" => (false, false, &[]),
|
||||
// RegExp needs special validation using the regex parser
|
||||
"RegExp" => {
|
||||
if Self::can_set_pure(ident, &ctx) && is_valid_regexp(&new_expr.arguments) {
|
||||
if Self::can_set_pure(ident, ctx) && is_valid_regexp(&new_expr.arguments) {
|
||||
new_expr.pure = true;
|
||||
}
|
||||
return;
|
||||
@@ -379,7 +371,7 @@ impl<'a> Normalize {
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !Self::can_set_pure(ident, &ctx) {
|
||||
if !Self::can_set_pure(ident, ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -409,7 +401,7 @@ impl<'a> Normalize {
|
||||
}
|
||||
}
|
||||
|
||||
fn can_set_pure(ident: &IdentifierReference<'a>, ctx: &Ctx<'a, '_>) -> bool {
|
||||
fn can_set_pure(ident: &IdentifierReference<'a>, ctx: &TraverseCtx<'a>) -> bool {
|
||||
ctx.is_global_reference(ident)
|
||||
// Throw is never pure.
|
||||
&& !matches!(ctx.parent(), Ancestor::ThrowStatementArgument(_))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::generated::ancestor::Ancestor;
|
||||
use oxc_allocator::{TakeIn, Vec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ast_visit::Visit;
|
||||
@@ -6,9 +7,8 @@ use oxc_ecmascript::{
|
||||
side_effects::MayHaveSideEffects,
|
||||
};
|
||||
use oxc_span::GetSpan;
|
||||
use oxc_traverse::Ancestor;
|
||||
|
||||
use crate::{ctx::Ctx, keep_var::KeepVar};
|
||||
use crate::{TraverseCtx, keep_var::KeepVar};
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -21,7 +21,7 @@ use super::PeepholeOptimizations;
|
||||
impl<'a> PeepholeOptimizations {
|
||||
/// Remove block from single line blocks
|
||||
/// `{ block } -> block`
|
||||
pub fn try_optimize_block(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn try_optimize_block(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Statement::BlockStatement(s) = stmt else { return };
|
||||
match s.body.len() {
|
||||
0 => {
|
||||
@@ -54,7 +54,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_fold_if(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn try_fold_if(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Statement::IfStatement(if_stmt) = stmt else { return };
|
||||
// Descend and remove `else` blocks first.
|
||||
match &mut if_stmt.alternate {
|
||||
@@ -123,7 +123,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_fold_for(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn try_fold_for(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Statement::ForStatement(for_stmt) = stmt else { return };
|
||||
if let Some(init) = &mut for_stmt.init
|
||||
&& let Some(init) = init.as_expression_mut()
|
||||
@@ -194,7 +194,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// ```js
|
||||
/// a: break a;
|
||||
/// ```
|
||||
pub fn try_fold_labeled(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn try_fold_labeled(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Statement::LabeledStatement(s) = stmt else { return };
|
||||
let id = s.label.name.as_str();
|
||||
|
||||
@@ -224,7 +224,7 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.state.changed = true;
|
||||
}
|
||||
|
||||
pub fn try_fold_expression_stmt(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn try_fold_expression_stmt(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Statement::ExpressionStatement(expr_stmt) = stmt else { return };
|
||||
// We need to check if it is in arrow function with `expression: true`.
|
||||
// This is the only scenario where we can't remove it even if `ExpressionStatement`.
|
||||
@@ -240,7 +240,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_fold_try(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn try_fold_try(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Statement::TryStatement(s) = stmt else { return };
|
||||
if let Some(handler) = &s.handler
|
||||
&& s.block.body.is_empty()
|
||||
@@ -276,7 +276,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
/// Try folding conditional expression (?:) if the condition results of the condition is known.
|
||||
pub fn try_fold_conditional_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn try_fold_conditional_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::ConditionalExpression(e) = expr else { return };
|
||||
let Some(v) = e.test.evaluate_value_to_boolean(ctx) else { return };
|
||||
ctx.state.changed = true;
|
||||
@@ -310,7 +310,7 @@ impl<'a> PeepholeOptimizations {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn remove_sequence_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn remove_sequence_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::SequenceExpression(e) = expr else { return };
|
||||
let should_keep_as_sequence_expr = e
|
||||
.expressions
|
||||
@@ -352,7 +352,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keep_track_of_pure_functions(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn keep_track_of_pure_functions(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
match stmt {
|
||||
Statement::FunctionDeclaration(f) => {
|
||||
if let Some(body) = &f.body {
|
||||
@@ -407,7 +407,7 @@ impl<'a> PeepholeOptimizations {
|
||||
body: &FunctionBody<'a>,
|
||||
r#async: bool,
|
||||
generator: bool,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if r#async || generator {
|
||||
return;
|
||||
@@ -428,7 +428,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_dead_code_call_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn remove_dead_code_call_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::CallExpression(e) = expr else { return };
|
||||
if let Expression::Identifier(ident) = &e.callee {
|
||||
let reference_id = ident.reference_id();
|
||||
@@ -456,7 +456,10 @@ impl<'a> PeepholeOptimizations {
|
||||
/// Example case: `let o = { f() { assert.ok(this !== o); } }; (true && o.f)(); (true && o.f)``;`
|
||||
///
|
||||
/// * `access_value` - The expression that may need to be kept as indirect reference (`foo.bar` in the example above)
|
||||
pub fn should_keep_indirect_access(access_value: &Expression<'a>, ctx: &Ctx<'a, '_>) -> bool {
|
||||
pub fn should_keep_indirect_access(
|
||||
access_value: &Expression<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
match ctx.parent() {
|
||||
Ancestor::CallExpressionCallee(_) | Ancestor::TaggedTemplateExpressionTag(_) => {
|
||||
match access_value {
|
||||
@@ -496,7 +499,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_dead_code_exit_class_body(body: &mut ClassBody<'a>, _ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn remove_dead_code_exit_class_body(body: &mut ClassBody<'a>, _ctx: &mut TraverseCtx<'a>) {
|
||||
body.body.retain(|e| !matches!(e, ClassElement::StaticBlock(s) if s.body.is_empty()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use super::PeepholeOptimizations;
|
||||
use crate::{CompressOptionsUnused, ctx::Ctx};
|
||||
use crate::{CompressOptionsUnused, TraverseCtx};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::constant_evaluation::{DetermineValueType, ValueType};
|
||||
|
||||
impl<'a> PeepholeOptimizations {
|
||||
fn can_remove_unused_declarators(ctx: &Ctx<'a, '_>) -> bool {
|
||||
fn can_remove_unused_declarators(ctx: &TraverseCtx<'a>) -> bool {
|
||||
ctx.state.options.unused != CompressOptionsUnused::Keep
|
||||
&& !Self::keep_top_level_var_in_script_mode(ctx)
|
||||
&& !ctx.scoping().root_scope_flags().contains_direct_eval()
|
||||
}
|
||||
|
||||
fn is_sync_iterator_expr(expr: &Expression<'a>, ctx: &Ctx<'a, '_>) -> bool {
|
||||
fn is_sync_iterator_expr(expr: &Expression<'a>, ctx: &TraverseCtx<'a>) -> bool {
|
||||
match expr {
|
||||
Expression::ArrayExpression(_)
|
||||
| Expression::StringLiteral(_)
|
||||
@@ -32,7 +32,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
pub fn should_remove_unused_declarator(
|
||||
decl: &VariableDeclarator<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
if !Self::can_remove_unused_declarators(ctx) {
|
||||
return false;
|
||||
@@ -67,7 +67,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
pub fn remove_unused_variable_declaration(
|
||||
mut stmt: Statement<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Statement<'a>> {
|
||||
let Statement::VariableDeclaration(var_decl) = &mut stmt else { return Some(stmt) };
|
||||
if !Self::can_remove_unused_declarators(ctx) {
|
||||
@@ -80,7 +80,7 @@ impl<'a> PeepholeOptimizations {
|
||||
Some(stmt)
|
||||
}
|
||||
|
||||
pub fn remove_unused_function_declaration(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn remove_unused_function_declaration(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Statement::FunctionDeclaration(f) = stmt else { return };
|
||||
if ctx.state.options.unused == CompressOptionsUnused::Keep {
|
||||
return;
|
||||
@@ -99,7 +99,7 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.state.changed = true;
|
||||
}
|
||||
|
||||
pub fn remove_unused_class_declaration(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn remove_unused_class_declaration(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Statement::ClassDeclaration(c) = stmt else { return };
|
||||
if ctx.state.options.unused == CompressOptionsUnused::Keep {
|
||||
return;
|
||||
@@ -128,7 +128,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
/// Do remove top level vars in script mode.
|
||||
pub fn keep_top_level_var_in_script_mode(ctx: &Ctx<'a, '_>) -> bool {
|
||||
pub fn keep_top_level_var_in_script_mode(ctx: &TraverseCtx<'a>) -> bool {
|
||||
ctx.scoping.current_scope_id() == ctx.scoping().root_scope_id()
|
||||
&& ctx.source_type().is_script()
|
||||
}
|
||||
@@ -156,7 +156,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// import 'a'
|
||||
/// import 'b'
|
||||
/// ```
|
||||
pub fn remove_unused_import_specifiers(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn remove_unused_import_specifiers(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if ctx.options().treeshake.invalid_import_side_effects
|
||||
|| ctx.state.options.unused == CompressOptionsUnused::Keep
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::iter;
|
||||
|
||||
use crate::{CompressOptionsUnused, ctx::Ctx};
|
||||
use crate::{CompressOptionsUnused, TraverseCtx};
|
||||
use oxc_allocator::{TakeIn, Vec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_compat::ESFeature;
|
||||
@@ -14,7 +14,7 @@ use super::PeepholeOptimizations;
|
||||
|
||||
impl<'a> PeepholeOptimizations {
|
||||
/// `SimplifyUnusedExpr`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L534>
|
||||
pub fn remove_unused_expression(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
pub fn remove_unused_expression(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
match e {
|
||||
Expression::ArrayExpression(_) => Self::remove_unused_array_expr(e, ctx),
|
||||
Expression::AssignmentExpression(_) => Self::remove_unused_assignment_expr(e, ctx),
|
||||
@@ -32,7 +32,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_unused_unary_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_unary_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::UnaryExpression(unary_expr) = e else { return false };
|
||||
match unary_expr.operator {
|
||||
UnaryOperator::Void | UnaryOperator::LogicalNot => {
|
||||
@@ -53,7 +53,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_unused_sequence_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_sequence_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::SequenceExpression(sequence_expr) = e else { return false };
|
||||
let old_len = sequence_expr.expressions.len();
|
||||
sequence_expr.expressions.retain_mut(|e| !Self::remove_unused_expression(e, ctx));
|
||||
@@ -63,7 +63,7 @@ impl<'a> PeepholeOptimizations {
|
||||
sequence_expr.expressions.is_empty()
|
||||
}
|
||||
|
||||
fn remove_unused_logical_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_logical_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
if !e.may_have_side_effects(ctx) {
|
||||
return true;
|
||||
}
|
||||
@@ -175,7 +175,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
// `([1,2,3, foo()])` -> `foo()`
|
||||
fn remove_unused_array_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_array_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::ArrayExpression(array_expr) = e else {
|
||||
return false;
|
||||
};
|
||||
@@ -225,7 +225,7 @@ impl<'a> PeepholeOptimizations {
|
||||
false
|
||||
}
|
||||
|
||||
fn remove_unused_new_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_new_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::NewExpression(new_expr) = e else { return false };
|
||||
if (new_expr.pure && ctx.annotations()) || ctx.manual_pure_functions(&new_expr.callee) {
|
||||
let mut exprs =
|
||||
@@ -245,7 +245,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
// "`${1}2${foo()}3`" -> "`${foo()}`"
|
||||
fn remove_unused_template_literal(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_template_literal(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::TemplateLiteral(temp_lit) = e else { return false };
|
||||
if temp_lit.expressions.is_empty() {
|
||||
return true;
|
||||
@@ -327,7 +327,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
// `({ 1: 1, [foo()]: bar() })` -> `foo(), bar()`
|
||||
fn remove_unused_object_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_object_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::ObjectExpression(object_expr) = e else {
|
||||
return false;
|
||||
};
|
||||
@@ -392,7 +392,7 @@ impl<'a> PeepholeOptimizations {
|
||||
false
|
||||
}
|
||||
|
||||
fn remove_unused_conditional_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_conditional_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
if !e.may_have_side_effects(ctx) {
|
||||
return true;
|
||||
}
|
||||
@@ -443,7 +443,7 @@ impl<'a> PeepholeOptimizations {
|
||||
false
|
||||
}
|
||||
|
||||
fn remove_unused_binary_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_binary_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::BinaryExpression(binary_expr) = e else {
|
||||
return false;
|
||||
};
|
||||
@@ -493,7 +493,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
/// returns whether the passed expression is a string
|
||||
fn fold_string_addition_chain(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn fold_string_addition_chain(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::BinaryExpression(binary_expr) = e else {
|
||||
return e.to_primitive(ctx).is_string() == Some(true);
|
||||
};
|
||||
@@ -536,7 +536,7 @@ impl<'a> PeepholeOptimizations {
|
||||
false
|
||||
}
|
||||
|
||||
fn remove_unused_call_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_call_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::CallExpression(call_expr) = e else { return false };
|
||||
|
||||
let is_pure = {
|
||||
@@ -572,7 +572,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
pub fn fold_arguments_into_needed_expressions(
|
||||
args: &mut Vec<'a, Argument<'a>>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Vec<'a, Expression<'a>> {
|
||||
ctx.ast.vec_from_iter(args.drain(..).filter_map(|arg| {
|
||||
let mut expr = match arg {
|
||||
@@ -586,7 +586,10 @@ impl<'a> PeepholeOptimizations {
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn remove_unused_assignment_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
pub fn remove_unused_assignment_expr(
|
||||
e: &mut Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
let Expression::AssignmentExpression(assign_expr) = e else { return false };
|
||||
if matches!(
|
||||
ctx.state.options.unused,
|
||||
@@ -627,7 +630,7 @@ impl<'a> PeepholeOptimizations {
|
||||
false
|
||||
}
|
||||
|
||||
fn remove_unused_class_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
|
||||
fn remove_unused_class_expr(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
||||
let Expression::ClassExpression(c) = e else { return false };
|
||||
if let Some(exprs) = Self::remove_unused_class(c, ctx) {
|
||||
if exprs.is_empty() {
|
||||
@@ -640,7 +643,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
pub fn remove_unused_class(
|
||||
c: &mut Class<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<Vec<'a, Expression<'a>>> {
|
||||
// TypeError `class C extends (() => {}) {}`
|
||||
if c.super_class
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::side_effects::MayHaveSideEffects;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -10,7 +10,7 @@ impl<'a> PeepholeOptimizations {
|
||||
///
|
||||
/// This function uses the private member usage collected during the main traverse
|
||||
/// to remove unused private fields and methods from the class body.
|
||||
pub fn remove_unused_private_members(body: &mut ClassBody<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn remove_unused_private_members(body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if ctx.current_scope_flags().contains_direct_eval() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::borrow::Cow;
|
||||
|
||||
use cow_utils::CowUtils;
|
||||
|
||||
use crate::generated::ancestor::Ancestor;
|
||||
use oxc_allocator::{Box, TakeIn};
|
||||
use oxc_ast::{NONE, ast::*};
|
||||
use oxc_compat::ESFeature;
|
||||
@@ -14,9 +15,8 @@ use oxc_regular_expression::{
|
||||
RegexUnsupportedPatterns, has_unsupported_regular_expression_pattern,
|
||||
};
|
||||
use oxc_span::SPAN;
|
||||
use oxc_traverse::Ancestor;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -25,7 +25,7 @@ type Arguments<'a> = oxc_allocator::Vec<'a, Argument<'a>>;
|
||||
/// Minimize With Known Methods
|
||||
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeReplaceKnownMethods.java>
|
||||
impl<'a> PeepholeOptimizations {
|
||||
pub fn replace_known_global_methods(node: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn replace_known_global_methods(node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::CallExpression(ce) = node else { return };
|
||||
|
||||
// Use constant evaluation for known method calls
|
||||
@@ -66,7 +66,7 @@ impl<'a> PeepholeOptimizations {
|
||||
span: Span,
|
||||
arguments: &mut Arguments<'a>,
|
||||
object: &Expression<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if !ctx.supports_feature(ESFeature::ES2016ExponentiationOperator) {
|
||||
return None;
|
||||
@@ -104,7 +104,7 @@ impl<'a> PeepholeOptimizations {
|
||||
arguments: &mut Arguments<'a>,
|
||||
name: &str,
|
||||
object: &Expression<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if !Self::validate_global_reference(object, "Array", ctx) {
|
||||
return None;
|
||||
@@ -120,7 +120,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
/// `[].concat(a).concat(b)` -> `[].concat(a, b)`
|
||||
/// `"".concat(a).concat(b)` -> `"".concat(a, b)`
|
||||
pub fn replace_concat_chain(node: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn replace_concat_chain(node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let original_span = if let Expression::CallExpression(root_call_expr) = node {
|
||||
root_call_expr.span
|
||||
} else {
|
||||
@@ -206,7 +206,7 @@ impl<'a> PeepholeOptimizations {
|
||||
span: Span,
|
||||
args: &mut Arguments<'a>,
|
||||
callee: &mut Expression<'a>,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
// let concat chaining reduction handle it first
|
||||
if let Ancestor::StaticMemberExpressionObject(parent_member) = ctx.parent()
|
||||
@@ -361,7 +361,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_known_property_access(node: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn replace_known_property_access(node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
// property access should be kept to keep `this` value
|
||||
if matches!(
|
||||
ctx.parent(),
|
||||
@@ -484,7 +484,7 @@ impl<'a> PeepholeOptimizations {
|
||||
fn try_fold_number_constants(
|
||||
name: &str,
|
||||
span: Span,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let num = |span: Span, n: f64| {
|
||||
ctx.ast.expression_numeric_literal(span, n, None, NumberBase::Decimal)
|
||||
@@ -550,7 +550,7 @@ impl<'a> PeepholeOptimizations {
|
||||
object: &mut Expression<'a>,
|
||||
property: u32,
|
||||
span: Span,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if object.may_have_side_effects(ctx) {
|
||||
return None;
|
||||
@@ -589,7 +589,11 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_global_reference(expr: &Expression<'a>, target: &str, ctx: &Ctx<'a, '_>) -> bool {
|
||||
fn validate_global_reference(
|
||||
expr: &Expression<'a>,
|
||||
target: &str,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
let Expression::Identifier(ident) = expr else { return false };
|
||||
ctx.is_global_reference(ident) && ident.name == target
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::iter::repeat_with;
|
||||
|
||||
use crate::generated::ancestor::Ancestor;
|
||||
use oxc_allocator::{CloneIn, TakeIn, Vec};
|
||||
use oxc_ast::{NONE, ast::*};
|
||||
use oxc_compat::ESFeature;
|
||||
@@ -14,9 +15,8 @@ use oxc_syntax::{
|
||||
number::NumberBase,
|
||||
operator::{BinaryOperator, UnaryOperator},
|
||||
};
|
||||
use oxc_traverse::Ancestor;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
@@ -25,7 +25,7 @@ use super::PeepholeOptimizations;
|
||||
/// with literals, and simplifying returns.
|
||||
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeSubstituteAlternateSyntax.java>
|
||||
impl<'a> PeepholeOptimizations {
|
||||
pub fn substitute_object_property(prop: &mut ObjectProperty<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_object_property(prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
// <https://tc39.es/ecma262/2024/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-propertydefinitionevaluation>
|
||||
if !prop.method
|
||||
&& let PropertyKey::StringLiteral(str) = &prop.key
|
||||
@@ -41,21 +41,21 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
pub fn substitute_assignment_target_property_property(
|
||||
prop: &mut AssignmentTargetPropertyProperty<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
Self::try_compress_property_key(&mut prop.name, &mut prop.computed, ctx);
|
||||
}
|
||||
|
||||
pub fn substitute_assignment_target_property(
|
||||
prop: &mut AssignmentTargetProperty<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
Self::try_compress_assignment_target_property(prop, ctx);
|
||||
}
|
||||
|
||||
pub fn try_compress_assignment_target_property(
|
||||
prop: &mut AssignmentTargetProperty<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// `a: a` -> `a`
|
||||
if let AssignmentTargetProperty::AssignmentTargetPropertyProperty(assign_target_prop_prop) =
|
||||
@@ -76,11 +76,14 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn substitute_binding_property(prop: &mut BindingProperty<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_binding_property(prop: &mut BindingProperty<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
Self::try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
|
||||
}
|
||||
|
||||
pub fn substitute_method_definition(prop: &mut MethodDefinition<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_method_definition(
|
||||
prop: &mut MethodDefinition<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let property_key_parent: ClassPropertyKeyParent = prop.into();
|
||||
// Only check for computed property restrictions if this is actually a computed property
|
||||
if prop.computed
|
||||
@@ -94,7 +97,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
pub fn substitute_property_definition(
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let property_key_parent: ClassPropertyKeyParent = prop.into();
|
||||
// Only check for computed property restrictions if this is actually a computed property
|
||||
@@ -107,7 +110,10 @@ impl<'a> PeepholeOptimizations {
|
||||
Self::try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
|
||||
}
|
||||
|
||||
pub fn substitute_accessor_property(prop: &mut AccessorProperty<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_accessor_property(
|
||||
prop: &mut AccessorProperty<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let property_key_parent: ClassPropertyKeyParent = prop.into();
|
||||
// Only check for computed property restrictions if this is actually a computed property
|
||||
if prop.computed
|
||||
@@ -119,29 +125,29 @@ impl<'a> PeepholeOptimizations {
|
||||
Self::try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
|
||||
}
|
||||
|
||||
pub fn substitute_for_statement(stmt: &mut ForStatement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_for_statement(stmt: &mut ForStatement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
Self::try_rewrite_arguments_copy_loop(stmt, ctx);
|
||||
}
|
||||
|
||||
pub fn substitute_variable_declaration(
|
||||
decl: &mut VariableDeclaration<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
for declarator in &mut decl.declarations {
|
||||
Self::compress_variable_declarator(declarator, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn substitute_call_expression(expr: &mut CallExpression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_call_expression(expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
Self::try_flatten_arguments(&mut expr.arguments, ctx);
|
||||
Self::try_rewrite_object_callee_indirect_call(expr, ctx);
|
||||
}
|
||||
|
||||
pub fn substitute_new_expression(expr: &mut NewExpression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_new_expression(expr: &mut NewExpression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
Self::try_flatten_arguments(&mut expr.arguments, ctx);
|
||||
}
|
||||
|
||||
pub fn substitute_chain_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_chain_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::ChainExpression(e) = expr else { return };
|
||||
Self::try_flatten_nested_chain_expression(e, ctx);
|
||||
Self::substitute_chain_call_expression(e, ctx);
|
||||
@@ -159,7 +165,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// `() => { return foo })` -> `() => foo`
|
||||
pub fn substitute_arrow_expression(
|
||||
arrow_expr: &mut ArrowFunctionExpression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if !arrow_expr.expression
|
||||
&& arrow_expr.body.directives.is_empty()
|
||||
@@ -186,7 +192,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// - `typeof foo.bar != "undefined"` -> `foo.bar !== undefined` (for any expression e.g.`typeof (foo + "")`)
|
||||
///
|
||||
/// Enabled by `compress.typeofs`
|
||||
pub fn substitute_typeof_undefined(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_typeof_undefined(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::BinaryExpression(e) = expr else { return };
|
||||
let Expression::UnaryExpression(unary_expr) = &e.left else { return };
|
||||
if !unary_expr.operator.is_typeof() {
|
||||
@@ -227,7 +233,7 @@ impl<'a> PeepholeOptimizations {
|
||||
///
|
||||
/// - `1 - +b` => `1 - b` (for other operators as well)
|
||||
/// - `+a - 1` => `a - 1` (for other operators as well)
|
||||
pub fn substitute_unary_plus(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_unary_plus(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::UnaryExpression(e) = expr else { return };
|
||||
if e.operator != UnaryOperator::UnaryPlus {
|
||||
return;
|
||||
@@ -293,7 +299,10 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
/// `a || (b || c);` -> `(a || b) || c;`
|
||||
pub fn substitute_rotate_logical_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_rotate_logical_expression(
|
||||
expr: &mut Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let Expression::LogicalExpression(e) = expr else { return };
|
||||
let Expression::LogicalExpression(right) = &e.right else { return };
|
||||
if right.operator != e.operator {
|
||||
@@ -318,7 +327,10 @@ impl<'a> PeepholeOptimizations {
|
||||
/// Rotate commutative operators to reduce parentheses:
|
||||
/// - `a * (b % c)` -> `b % c * a`
|
||||
/// - `a * (b / c)` -> `b / c * a`
|
||||
pub fn substitute_rotate_binary_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_rotate_binary_expression(
|
||||
expr: &mut Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let Expression::BinaryExpression(e) = expr else { return };
|
||||
|
||||
// Handle associative rotation
|
||||
@@ -390,7 +402,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// - If `foo` is an object, then `!!foo` is `true`. If `foo` is null, then `!!foo` is `false`.
|
||||
///
|
||||
/// This compression is safe for `document.all` because `typeof document.all` is not `'object'`.
|
||||
pub fn substitute_is_object_and_not_null(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_is_object_and_not_null(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::LogicalExpression(e) = expr else { return };
|
||||
let inversed = match e.operator {
|
||||
LogicalOperator::And => false,
|
||||
@@ -431,7 +443,7 @@ impl<'a> PeepholeOptimizations {
|
||||
left: &Expression<'a>,
|
||||
right: &Expression<'a>,
|
||||
span: Span,
|
||||
ctx: &Ctx<'a, '_>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
inversed: bool,
|
||||
) -> Option<Expression<'a>> {
|
||||
let pair = Self::commutative_pair(
|
||||
@@ -530,7 +542,7 @@ impl<'a> PeepholeOptimizations {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn substitute_loose_equals_undefined(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_loose_equals_undefined(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::BinaryExpression(e) = expr else { return };
|
||||
// `foo == void 0` -> `foo == null`, `foo == undefined` -> `foo == null`
|
||||
// `foo != void 0` -> `foo == null`, `foo == undefined` -> `foo == null`
|
||||
@@ -573,7 +585,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// for (var r = [], a = 1; a < arguments.length; a++)
|
||||
/// r[a - 1] = arguments[a];
|
||||
/// ```
|
||||
fn try_rewrite_arguments_copy_loop(for_stmt: &mut ForStatement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
fn try_rewrite_arguments_copy_loop(for_stmt: &mut ForStatement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum VerifyArrayArgResult {
|
||||
WithOffset,
|
||||
@@ -924,7 +936,7 @@ impl<'a> PeepholeOptimizations {
|
||||
///
|
||||
/// `return undefined` -> `return`
|
||||
/// `return void 0` -> `return`
|
||||
pub fn substitute_return_statement(stmt: &mut ReturnStatement<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_return_statement(stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Some(argument) = &stmt.argument else { return };
|
||||
if !match argument {
|
||||
Expression::Identifier(ident) => ctx.is_identifier_undefined(ident),
|
||||
@@ -943,7 +955,7 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.state.changed = true;
|
||||
}
|
||||
|
||||
fn compress_variable_declarator(decl: &mut VariableDeclarator<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
fn compress_variable_declarator(decl: &mut VariableDeclarator<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
// Destructuring Pattern has error throwing side effect.
|
||||
if matches!(
|
||||
decl.kind,
|
||||
@@ -967,7 +979,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// `Number(0)` -> `0`
|
||||
/// `String()` -> `''`
|
||||
/// `BigInt(1)` -> `1`
|
||||
pub fn substitute_simple_function_call(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_simple_function_call(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::CallExpression(e) = expr else { return };
|
||||
if e.optional || e.arguments.len() >= 2 {
|
||||
return;
|
||||
@@ -1041,7 +1053,10 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
/// Fold `Object` or `Array` constructor
|
||||
fn get_fold_constructor_name(callee: &Expression<'a>, ctx: &Ctx<'a, '_>) -> Option<&'a str> {
|
||||
fn get_fold_constructor_name(
|
||||
callee: &Expression<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Option<&'a str> {
|
||||
match callee {
|
||||
Expression::StaticMemberExpression(e) => {
|
||||
if !matches!(&e.object, Expression::Identifier(ident) if ident.name == "window") {
|
||||
@@ -1067,7 +1082,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// `window.Array()`, `new Array()`, `Array()` -> `[]`
|
||||
pub fn substitute_object_or_array_constructor(
|
||||
expr: &mut Expression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let callee = match expr {
|
||||
Expression::NewExpression(e) => &e.callee,
|
||||
@@ -1163,7 +1178,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// `new AggregateError()` -> `AggregateError()`
|
||||
/// `new Function()` -> `Function()`
|
||||
/// `new RegExp()` -> `RegExp()`
|
||||
pub fn substitute_global_new_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_global_new_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::NewExpression(e) = expr else { return };
|
||||
let Expression::Identifier(ident) = &e.callee else { return };
|
||||
let name = ident.name.as_str();
|
||||
@@ -1216,7 +1231,10 @@ impl<'a> PeepholeOptimizations {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn substitute_chain_call_expression(expr: &mut ChainExpression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_chain_call_expression(
|
||||
expr: &mut ChainExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if let ChainElement::CallExpression(call_expr) = &mut expr.expression {
|
||||
// `window.Object?.()` -> `Object?.()`
|
||||
if call_expr.arguments.is_empty()
|
||||
@@ -1237,7 +1255,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn substitute_template_literal(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_template_literal(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::TemplateLiteral(t) = expr else { return };
|
||||
let Some(val) = t.to_js_string(ctx) else { return };
|
||||
*expr = ctx.ast.expression_string_literal(t.span(), ctx.ast.atom_from_cow(&val), None);
|
||||
@@ -1248,7 +1266,7 @@ impl<'a> PeepholeOptimizations {
|
||||
fn try_compress_property_key(
|
||||
key: &mut PropertyKey<'a>,
|
||||
computed: &mut bool,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
match key {
|
||||
PropertyKey::NumericLiteral(_) => {
|
||||
@@ -1258,7 +1276,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
PropertyKey::StringLiteral(s) => {
|
||||
let value = s.value.as_str();
|
||||
if Ctx::is_identifier_name_patched(value) {
|
||||
if TraverseCtx::is_identifier_name_patched(value) {
|
||||
*computed = false;
|
||||
*key = PropertyKey::StaticIdentifier(
|
||||
ctx.ast.alloc_identifier_name(s.span, s.value),
|
||||
@@ -1266,7 +1284,7 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.state.changed = true;
|
||||
return;
|
||||
}
|
||||
if let Some(value) = Ctx::string_to_equivalent_number_value(value)
|
||||
if let Some(value) = TraverseCtx::string_to_equivalent_number_value(value)
|
||||
&& value >= 0.0
|
||||
{
|
||||
*computed = false;
|
||||
@@ -1289,7 +1307,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
// `foo(...[1,2,3])` -> `foo(1,2,3)`
|
||||
// `new Foo(...[1,2,3])` -> `new Foo(1,2,3)`
|
||||
fn try_flatten_arguments(args: &mut Vec<'a, Argument<'a>>, ctx: &mut Ctx<'a, '_>) {
|
||||
fn try_flatten_arguments(args: &mut Vec<'a, Argument<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||
let (new_size, should_fold) =
|
||||
args.iter().fold((0, false), |(mut new_size, mut should_fold), arg| {
|
||||
new_size += if let Argument::SpreadElement(spread_el) = arg {
|
||||
@@ -1346,7 +1364,10 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
/// Flatten nested chain expressions
|
||||
/// `(foo?.bar)?.baz` -> `foo?.bar?.baz`
|
||||
fn try_flatten_nested_chain_expression(expr: &mut ChainExpression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
fn try_flatten_nested_chain_expression(
|
||||
expr: &mut ChainExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
match &mut expr.expression {
|
||||
ChainElement::StaticMemberExpression(member) => {
|
||||
if let Expression::ChainExpression(chain) = member.object.without_parentheses_mut()
|
||||
@@ -1395,7 +1416,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// <https://tc39.es/ecma262/2025/multipage/fundamental-objects.html#sec-object-value>
|
||||
fn try_rewrite_object_callee_indirect_call(
|
||||
expr: &mut CallExpression<'a>,
|
||||
ctx: &mut Ctx<'a, '_>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let Expression::CallExpression(inner_call) = &mut expr.callee else { return };
|
||||
if inner_call.optional || inner_call.arguments.len() != 1 {
|
||||
@@ -1429,7 +1450,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// e.g. `var a = function f() {}` -> `var a = function () {}`
|
||||
///
|
||||
/// This compression is not safe if the code relies on `Function::name`.
|
||||
pub fn try_remove_name_from_functions(func: &mut Function<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn try_remove_name_from_functions(func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if ctx.options().keep_names.function {
|
||||
return;
|
||||
}
|
||||
@@ -1444,7 +1465,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// e.g. `var a = class C {}` -> `var a = class {}`
|
||||
///
|
||||
/// This compression is not safe if the code relies on `Class::name`.
|
||||
pub fn try_remove_name_from_classes(class: &mut Class<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn try_remove_name_from_classes(class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if ctx.options().keep_names.class {
|
||||
return;
|
||||
}
|
||||
@@ -1456,7 +1477,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
/// `new Int8Array(0)` -> `new Int8Array()` (also for other TypedArrays)
|
||||
pub fn substitute_typed_array_constructor(e: &mut NewExpression<'a>, ctx: &Ctx<'a, '_>) {
|
||||
pub fn substitute_typed_array_constructor(e: &mut NewExpression<'a>, ctx: &TraverseCtx<'a>) {
|
||||
let Expression::Identifier(ident) = &e.callee else { return };
|
||||
let name = ident.name.as_str();
|
||||
if !Self::is_typed_array_name(name) || !ctx.is_global_reference(ident) {
|
||||
@@ -1470,7 +1491,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
/// Transforms boolean expression `true` => `!0` `false` => `!1`.
|
||||
pub fn substitute_boolean(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_boolean(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::BooleanLiteral(lit) = expr else { return };
|
||||
let num = ctx.ast.expression_numeric_literal(
|
||||
lit.span,
|
||||
@@ -1483,7 +1504,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
/// Transforms long array expression with string literals to `"str1,str2".split(',')`
|
||||
pub fn substitute_array_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_array_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
// this threshold is chosen by hand by checking the minsize output
|
||||
const THRESHOLD: usize = 40;
|
||||
|
||||
@@ -1554,7 +1575,7 @@ impl<'a> PeepholeOptimizations {
|
||||
DELIMITERS.into_iter().find(|&delimiter| strings.clone().all(|s| !s.contains(delimiter)))
|
||||
}
|
||||
|
||||
pub fn substitute_catch_clause(catch: &mut CatchClause<'a>, ctx: &Ctx<'a, '_>) {
|
||||
pub fn substitute_catch_clause(catch: &mut CatchClause<'a>, ctx: &TraverseCtx<'a>) {
|
||||
if ctx.supports_feature(ESFeature::ES2019OptionalCatchBinding)
|
||||
&& let Some(param) = &catch.param
|
||||
&& let BindingPattern::BindingIdentifier(ident) = ¶m.pattern
|
||||
@@ -1585,7 +1606,7 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
/// Checks if the expression result is unused (i.e., in an expression statement context).
|
||||
fn is_expression_result_unused(ctx: &Ctx<'a, '_>) -> bool {
|
||||
fn is_expression_result_unused(ctx: &TraverseCtx<'a>) -> bool {
|
||||
matches!(ctx.parent(), Ancestor::ExpressionStatementExpression(_))
|
||||
}
|
||||
|
||||
@@ -1597,7 +1618,7 @@ impl<'a> PeepholeOptimizations {
|
||||
/// - Simplifies single-expression non-async arrow function IIFEs (e.g., `(() => foo())()` to `foo()`).
|
||||
/// - Converts arrow function IIFEs that return void or execute one expression
|
||||
/// (e.g., `(() => { foo() })()` or `(() => { return foo() })()`) into simpler expressions.
|
||||
pub fn substitute_iife_call(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
|
||||
pub fn substitute_iife_call(e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::CallExpression(call_expr) = e else { return };
|
||||
|
||||
if !call_expr.arguments.is_empty() || !call_expr.callee.is_function() {
|
||||
|
||||
@@ -15,7 +15,6 @@ pub struct SymbolValue<'a> {
|
||||
pub read_references_count: u32,
|
||||
pub write_references_count: u32,
|
||||
|
||||
#[expect(unused)]
|
||||
pub scope_id: ScopeId,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
use std::mem::transmute;
|
||||
|
||||
use oxc_data_structures::stack::NonEmptyStack;
|
||||
|
||||
use crate::generated::ancestor::{Ancestor, AncestorType};
|
||||
|
||||
const INITIAL_STACK_CAPACITY: usize = 64; // 64 entries = 1 KiB
|
||||
|
||||
/// Traverse ancestry context.
|
||||
///
|
||||
/// Contains a stack of `Ancestor`s, and provides methods to get parent/ancestor of current node.
|
||||
///
|
||||
/// `walk_*` methods push/pop `Ancestor`s to `stack` when entering/exiting nodes.
|
||||
///
|
||||
/// `Ancestor<'a, 't>` is an owned type.
|
||||
/// * `'a` is lifetime of AST nodes.
|
||||
/// * `'t` is lifetime of the `Ancestor` (derived from `&'t TraverseAncestry`).
|
||||
///
|
||||
/// `'t` is constrained in `parent`, `ancestor` and `ancestors` methods to only live as long as
|
||||
/// the `&'t TraverseAncestry` passed to the method.
|
||||
/// i.e. `Ancestor`s can only live as long as `enter_*` or `exit_*` method in which they're obtained,
|
||||
/// and cannot "escape" those methods.
|
||||
/// This is required for soundness. If an `Ancestor` could be retained longer, the references that
|
||||
/// can be got from it could alias a `&mut` reference to the same AST node.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// This type MUST NOT be mutable by consumer.
|
||||
///
|
||||
/// The safety scheme is entirely reliant on `stack` being in sync with the traversal,
|
||||
/// to prevent consumer from accessing fields of nodes which traversal has passed through,
|
||||
/// so as to not violate Rust's aliasing rules.
|
||||
/// If consumer could alter `stack` in any way, they could break the safety invariants and cause UB.
|
||||
///
|
||||
/// We prevent this in 3 ways:
|
||||
/// 1. `TraverseAncestry`'s `stack` field is private.
|
||||
/// 2. Public methods of `TraverseAncestry` provide no means for mutating `stack`.
|
||||
/// 3. Visitors receive a `&mut TraverseCtx`, but cannot overwrite its `ancestry` field because they:
|
||||
/// a. cannot create a new `TraverseAncestry`
|
||||
/// - `TraverseAncestry::new` and `TraverseCtx::new` are private.
|
||||
/// b. cannot obtain an owned `TraverseAncestry` from a `&TraverseAncestry`
|
||||
/// - `TraverseAncestry` is not `Clone`.
|
||||
///
|
||||
/// ## Soundness hole
|
||||
///
|
||||
/// Strictly speaking, there is still room to abuse the API and cause UB as follows:
|
||||
///
|
||||
/// * Initiate a 2nd traversal of a different AST inside a `Traverse` visitor method.
|
||||
/// * `mem::swap` the 2 x `&mut TraverseCtx`s from the 2 different traversals.
|
||||
///
|
||||
/// The 2 ASTs would have to be different, but borrowed for same lifetime, so I (@overlookmotel) don't
|
||||
/// think it's possible by this method to produce aliasing violations, or to over-extend AST node
|
||||
/// lifetimes to cause a use-after-free.
|
||||
/// But it *could* produce buffer underrun in `pop_stack`, when it tries to pop from a stack which
|
||||
/// is already empty.
|
||||
///
|
||||
/// In practice, this would be a completely bizarre thing to do, and would basically require you to
|
||||
/// write malicious code specifically designed to cause UB. So it's not a particularly real risk.
|
||||
///
|
||||
/// To close this hole and make the API 100% sound, we'd need branded lifetimes so that all
|
||||
/// `TraverseCtx`s have unique lifetimes, and so cannot be swapped for any other without
|
||||
/// the borrow-checker complaining.
|
||||
pub struct TraverseAncestry<'a> {
|
||||
stack: NonEmptyStack<Ancestor<'a, 'static>>,
|
||||
}
|
||||
|
||||
// Public methods
|
||||
impl<'a> TraverseAncestry<'a> {
|
||||
/// Get parent of current node.
|
||||
#[inline]
|
||||
pub fn parent<'t>(&'t self) -> Ancestor<'a, 't> {
|
||||
let ancestor = *self.stack.last();
|
||||
// Shrink `Ancestor`'s `'t` lifetime to lifetime of `&'t self`.
|
||||
// SAFETY: The `Ancestor` is guaranteed valid for `'t`. It is not possible to obtain
|
||||
// a `&mut` ref to any AST node which this `Ancestor` gives access to during `'t`.
|
||||
unsafe { transmute::<Ancestor<'a, '_>, Ancestor<'a, 't>>(ancestor) }
|
||||
}
|
||||
|
||||
/// Get ancestor of current node.
|
||||
///
|
||||
/// `level` is number of levels above parent.
|
||||
/// `ancestor(0)` is equivalent to `parent()` (but better to use `parent()` as it's more efficient).
|
||||
///
|
||||
/// If `level` is out of bounds (above `Program`), returns `Ancestor::None`.
|
||||
#[inline]
|
||||
pub fn ancestor<'t>(&'t self, level: usize) -> Ancestor<'a, 't> {
|
||||
// Behavior with different values:
|
||||
// `len = 1, level = 0` -> return `Ancestor::None` from else branch
|
||||
// `len = 1, level = 1` -> return `Ancestor::None` from else branch (out of bounds)
|
||||
// `len = 3, level = 0` -> return parent (index 2)
|
||||
// `len = 3, level = 1` -> return grandparent (index 1)
|
||||
// `len = 3, level = 2` -> return `Ancestor::None` from else branch
|
||||
// `len = 3, level = 3` -> return `Ancestor::None` from else branch (out of bounds)
|
||||
|
||||
// `self.stack.len()` is always at least 1, so `self.stack.len() - 1` cannot wrap around.
|
||||
// `level <= last_index` would also work here, but `level < last_index` avoids a read from memory
|
||||
// when that read would just get `Ancestor::None` anyway.
|
||||
let last_index = self.stack.len() - 1;
|
||||
if level < last_index {
|
||||
// SAFETY: We just checked that `level < last_index` so `last_index - level` cannot wrap around,
|
||||
// and `last_index - level` must be a valid index
|
||||
let ancestor = unsafe { *self.stack.get_unchecked(last_index - level) };
|
||||
|
||||
// Shrink `Ancestor`'s `'t` lifetime to lifetime of `&'t self`.
|
||||
// SAFETY: The `Ancestor` is guaranteed valid for `'t`. It is not possible to obtain
|
||||
// a `&mut` ref to any AST node which this `Ancestor` gives access to during `'t`.
|
||||
unsafe { transmute::<Ancestor<'a, '_>, Ancestor<'a, 't>>(ancestor) }
|
||||
} else {
|
||||
Ancestor::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get iterator over ancestors, starting with parent and working up.
|
||||
///
|
||||
/// Last `Ancestor` returned will be `Program`. `Ancestor::None` is not included in iteration.
|
||||
pub fn ancestors<'t>(&'t self) -> impl Iterator<Item = Ancestor<'a, 't>> {
|
||||
// SAFETY: Stack always has at least 1 entry
|
||||
let stack_without_first = unsafe { self.stack.get_unchecked(1..) };
|
||||
stack_without_first.iter().rev().map(|&ancestor| {
|
||||
// Shrink `Ancestor`'s `'t` lifetime to lifetime of `&'t self`.
|
||||
// SAFETY: The `Ancestor` is guaranteed valid for `'t`. It is not possible to obtain
|
||||
// a `&mut` ref to any AST node which this `Ancestor` gives access to during `'t`.
|
||||
unsafe { transmute::<Ancestor<'a, '_>, Ancestor<'a, 't>>(ancestor) }
|
||||
})
|
||||
}
|
||||
|
||||
/// Get depth in the AST.
|
||||
///
|
||||
/// Count includes current node. i.e. in `Program`, depth is 1.
|
||||
#[inline]
|
||||
pub fn ancestors_depth(&self) -> usize {
|
||||
self.stack.len()
|
||||
}
|
||||
}
|
||||
|
||||
// Methods used internally within crate.
|
||||
impl<'a> TraverseAncestry<'a> {
|
||||
/// Create new `TraverseAncestry`.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// This method must not be public outside this crate, or consumer could break safety invariants.
|
||||
pub(super) fn new() -> Self {
|
||||
Self { stack: NonEmptyStack::with_capacity(INITIAL_STACK_CAPACITY, Ancestor::None) }
|
||||
}
|
||||
|
||||
/// Push item onto ancestry stack.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// This method must not be public outside this crate, or consumer could break safety invariants.
|
||||
#[inline]
|
||||
#[must_use] // `PopToken` must be passed to `pop_stack` to pop this entry off the stack again
|
||||
pub(crate) fn push_stack(&mut self, ancestor: Ancestor<'a, 'static>) -> PopToken {
|
||||
self.stack.push(ancestor);
|
||||
|
||||
// Return `PopToken` which can be used to pop this entry off again
|
||||
PopToken(())
|
||||
}
|
||||
|
||||
/// Pop last item off ancestry stack.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// This method must not be public outside this crate, or consumer could break safety invariants.
|
||||
#[inline]
|
||||
#[expect(unused_variables, clippy::needless_pass_by_value)]
|
||||
pub(crate) fn pop_stack(&mut self, token: PopToken) {
|
||||
// SAFETY: `PopToken`s are only created in `push_stack`, so the fact that caller provides one
|
||||
// guarantees that a push has happened. This method consumes the token which guarantees another
|
||||
// pop hasn't occurred already corresponding to that push.
|
||||
// Therefore the stack cannot by empty.
|
||||
// The stack starts with 1 entry, so also it cannot be left empty after this pop.
|
||||
unsafe { self.stack.pop_unchecked() };
|
||||
}
|
||||
|
||||
/// Retag last item on ancestry stack.
|
||||
///
|
||||
/// i.e. Alter discriminant of `Ancestor` enum, without changing the "payload" it contains
|
||||
/// of pointer to the ancestor node.
|
||||
///
|
||||
/// This is purely a performance optimization. If the last item on stack already contains the
|
||||
/// correct pointer, then `ctx.retag_stack(AncestorType::ProgramBody)` is equivalent to:
|
||||
///
|
||||
/// ```nocompile
|
||||
/// ctx.pop_stack();
|
||||
/// ctx.push_stack(Ancestor::ProgramBody(ProgramWithoutBody(node_ptr)));
|
||||
/// ```
|
||||
///
|
||||
/// `retag_stack` is only a single 2-byte write operation.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// * Stack must have length of at least 2 (so we are not retagging dummy root `Ancestor`).
|
||||
/// * Last item on stack must contain pointer to type corresponding to provided `AncestorType`.
|
||||
///
|
||||
/// This method must not be public outside this crate, or consumer could break safety invariants.
|
||||
#[inline]
|
||||
#[expect(clippy::ptr_as_ptr, clippy::ref_as_ptr)]
|
||||
pub(crate) unsafe fn retag_stack(&mut self, ty: AncestorType) {
|
||||
debug_assert!(self.stack.len() >= 2);
|
||||
// SAFETY: Caller must uphold the safety invariants
|
||||
unsafe { *(self.stack.last_mut() as *mut _ as *mut AncestorType) = ty };
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero sized token which allows popping from stack. Used to ensure push and pop always correspond.
|
||||
/// Inner field is private to this module so can only be created by methods in this file.
|
||||
/// It is not `Clone` or `Copy`, so no way to obtain one except in this file.
|
||||
/// Only method which generates a `PopToken` is `push_stack`, and `pop_stack` consumes one,
|
||||
/// which guarantees you can't have more pops than pushes.
|
||||
pub struct PopToken(());
|
||||
@@ -0,0 +1,345 @@
|
||||
use oxc_ast::ast::{
|
||||
AssignmentTarget, BindingIdentifier, BindingPattern, Expression, IdentifierReference,
|
||||
SimpleAssignmentTarget,
|
||||
};
|
||||
use oxc_span::{SPAN, Span};
|
||||
use oxc_str::Ident;
|
||||
use oxc_syntax::{reference::ReferenceFlags, symbol::SymbolId};
|
||||
|
||||
use super::TraverseCtx;
|
||||
|
||||
use super::MaybeBoundIdentifier;
|
||||
|
||||
/// Info about a binding, from which one can create a `BindingIdentifier` or `IdentifierReference`s.
|
||||
///
|
||||
/// Typical usage:
|
||||
///
|
||||
/// ```rs
|
||||
/// // Generate a UID for a top-level var
|
||||
/// let binding = ctx.generate_uid_in_current_scope("foo", SymbolFlags::FunctionScopedVariable);
|
||||
///
|
||||
/// // Generate `IdentifierReference`s and insert them into AST
|
||||
/// some_node.id = binding.create_read_reference(ctx);
|
||||
/// some_other_node.id = binding.create_read_reference(ctx);
|
||||
///
|
||||
/// // Store details of the binding for later
|
||||
/// self.foo_binding = binding;
|
||||
///
|
||||
/// // Later on in `exit_program`
|
||||
/// let id = self.foo_binding.create_binding_identifier(ctx);
|
||||
/// // Insert `var <id> = something;` into `program.body`
|
||||
/// ```
|
||||
///
|
||||
/// Notes:
|
||||
///
|
||||
/// * `BoundIdentifier` is smaller than `BindingIdentifier`, so takes less memory when you store
|
||||
/// it for later use.
|
||||
/// * `BoundIdentifier` is `Clone` (unlike `BindingIdentifier`).
|
||||
/// * `BoundIdentifier` re-uses the same `Ident` for all `BindingIdentifier` / `IdentifierReference`s
|
||||
/// created from it.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BoundIdentifier<'a> {
|
||||
pub name: Ident<'a>,
|
||||
pub symbol_id: SymbolId,
|
||||
}
|
||||
|
||||
impl<'a> BoundIdentifier<'a> {
|
||||
/// Create `BoundIdentifier` for `name` and `symbol_id`
|
||||
pub fn new(name: Ident<'a>, symbol_id: SymbolId) -> Self {
|
||||
Self { name, symbol_id }
|
||||
}
|
||||
|
||||
/// Create `BoundIdentifier` from a `BindingIdentifier`
|
||||
pub fn from_binding_ident(ident: &BindingIdentifier<'a>) -> Self {
|
||||
Self { name: ident.name, symbol_id: ident.symbol_id() }
|
||||
}
|
||||
|
||||
/// Convert `BoundIdentifier` to `MaybeBoundIdentifier`
|
||||
pub fn to_maybe_bound_identifier(&self) -> MaybeBoundIdentifier<'a> {
|
||||
MaybeBoundIdentifier::new(self.name, Some(self.symbol_id))
|
||||
}
|
||||
|
||||
/// Create `BindingIdentifier` for this binding
|
||||
pub fn create_binding_identifier<State>(
|
||||
&self,
|
||||
ctx: &TraverseCtx<'a, State>,
|
||||
) -> BindingIdentifier<'a> {
|
||||
ctx.ast.binding_identifier_with_symbol_id(SPAN, self.name, self.symbol_id)
|
||||
}
|
||||
|
||||
/// Create `BindingPattern` for this binding
|
||||
pub fn create_binding_pattern<State>(
|
||||
&self,
|
||||
ctx: &TraverseCtx<'a, State>,
|
||||
) -> BindingPattern<'a> {
|
||||
let ident = self.create_binding_identifier(ctx);
|
||||
BindingPattern::BindingIdentifier(ctx.alloc(ident))
|
||||
}
|
||||
|
||||
// --- Read only ---
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is read from, with dummy `Span`
|
||||
pub fn create_read_reference<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_read_reference(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is read from, with dummy `Span`
|
||||
pub fn create_read_expression<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_read_expression(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is read from, with specified `Span`
|
||||
pub fn create_spanned_read_reference<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_reference(span, ReferenceFlags::Read, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is read from, with specified `Span`
|
||||
pub fn create_spanned_read_expression<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_expression(span, ReferenceFlags::Read, ctx)
|
||||
}
|
||||
|
||||
// --- Write only ---
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is written to, with dummy `Span`
|
||||
pub fn create_write_reference<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_write_reference(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is written to, with dummy `Span`
|
||||
pub fn create_write_expression<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_write_expression(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, which is written to, with dummy `Span`
|
||||
pub fn create_write_target<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_write_target(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, which is written to, with dummy `Span`
|
||||
pub fn create_write_simple_target<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_write_simple_target(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is written to, with specified `Span`
|
||||
pub fn create_spanned_write_reference<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_reference(span, ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is written to, with specified `Span`
|
||||
pub fn create_spanned_write_expression<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_expression(span, ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, which is written to, with specified `Span`
|
||||
pub fn create_spanned_write_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_target(span, ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, which is written to, with specified `Span`
|
||||
pub fn create_spanned_write_simple_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_simple_target(span, ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
// --- Read and write ---
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is read from + written to,
|
||||
/// with dummy `Span`
|
||||
pub fn create_read_write_reference<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_read_write_reference(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is read from + written to,
|
||||
/// with dummy `Span`
|
||||
pub fn create_read_write_expression<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_read_write_expression(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, which is read from + written to,
|
||||
/// with dummy `Span`
|
||||
pub fn create_read_write_target<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_read_write_target(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, which is read from + written to,
|
||||
/// with dummy `Span`
|
||||
pub fn create_read_write_simple_target<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_read_write_simple_target(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is read from + written to,
|
||||
/// with specified `Span`
|
||||
pub fn create_spanned_read_write_reference<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_reference(span, ReferenceFlags::Read | ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is read from + written to,
|
||||
/// with specified `Span`
|
||||
pub fn create_spanned_read_write_expression<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_expression(span, ReferenceFlags::Read | ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, which is read from + written to,
|
||||
/// with specified `Span`
|
||||
pub fn create_spanned_read_write_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_target(span, ReferenceFlags::Read | ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, which is read from + written to,
|
||||
/// with specified `Span`
|
||||
pub fn create_spanned_read_write_simple_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_simple_target(span, ReferenceFlags::Read | ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
// --- Specified ReferenceFlags ---
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, with specified `ReferenceFlags`
|
||||
pub fn create_reference<State>(
|
||||
&self,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_reference(SPAN, flags, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, with specified `ReferenceFlags`
|
||||
pub fn create_expression<State>(
|
||||
&self,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_expression(SPAN, flags, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, with specified `ReferenceFlags`
|
||||
pub fn create_target<State>(
|
||||
&self,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_target(SPAN, flags, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, with specified `ReferenceFlags`
|
||||
pub fn create_simple_target<State>(
|
||||
&self,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_simple_target(SPAN, flags, ctx)
|
||||
}
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, with specified `Span` and `ReferenceFlags`
|
||||
pub fn create_spanned_reference<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
ctx.create_bound_ident_reference(span, self.name, self.symbol_id, flags)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, with specified `Span` and `ReferenceFlags`
|
||||
pub fn create_spanned_expression<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
let ident = self.create_spanned_reference(span, flags, ctx);
|
||||
Expression::Identifier(ctx.alloc(ident))
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget::AssignmentTargetIdentifier` referencing this binding,
|
||||
/// with specified `Span` and `ReferenceFlags`
|
||||
pub fn create_spanned_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
let ident = self.create_spanned_reference(span, flags, ctx);
|
||||
AssignmentTarget::AssignmentTargetIdentifier(ctx.alloc(ident))
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget::AssignmentTargetIdentifier` referencing this binding,
|
||||
/// with specified `Span` and `ReferenceFlags`
|
||||
pub fn create_spanned_simple_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
let ident = self.create_spanned_reference(span, flags, ctx);
|
||||
SimpleAssignmentTarget::AssignmentTargetIdentifier(ctx.alloc(ident))
|
||||
}
|
||||
}
|
||||
+91
-40
@@ -1,51 +1,36 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
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, PropertyReadSideEffects, is_pure_function},
|
||||
side_effects::{
|
||||
MayHaveSideEffects, MayHaveSideEffectsContext, PropertyReadSideEffects, is_pure_function,
|
||||
},
|
||||
};
|
||||
use oxc_semantic::{IsGlobalReference, Scoping, SymbolId};
|
||||
use oxc_semantic::{IsGlobalReference, SymbolId};
|
||||
use oxc_span::format_atom;
|
||||
use oxc_syntax::{
|
||||
identifier::{is_identifier_part, is_identifier_start},
|
||||
reference::ReferenceId,
|
||||
scope::ScopeFlags,
|
||||
};
|
||||
use oxc_traverse::Ancestor;
|
||||
|
||||
use crate::{options::CompressOptions, state::MinifierState, symbol_value::SymbolValue};
|
||||
use oxc_compat::ESFeature;
|
||||
use crate::{
|
||||
generated::ancestor::Ancestor, options::CompressOptions, state::MinifierState,
|
||||
symbol_value::SymbolValue,
|
||||
};
|
||||
|
||||
pub type TraverseCtx<'a> = oxc_traverse::TraverseCtx<'a, MinifierState<'a>>;
|
||||
use super::TraverseCtx;
|
||||
|
||||
pub struct Ctx<'a, 'b>(&'b mut TraverseCtx<'a>);
|
||||
|
||||
impl<'a, 'b> Ctx<'a, 'b> {
|
||||
pub fn new(ctx: &'b mut TraverseCtx<'a>) -> Self {
|
||||
Self(ctx)
|
||||
}
|
||||
pub fn is_exact_int64(num: f64) -> bool {
|
||||
num.fract() == 0.0
|
||||
}
|
||||
|
||||
impl<'a> Deref for Ctx<'a, '_> {
|
||||
type Target = TraverseCtx<'a>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for Ctx<'a, '_> {
|
||||
fn deref_mut(&mut self) -> &mut TraverseCtx<'a> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> oxc_ecmascript::GlobalContext<'a> for Ctx<'a, '_> {
|
||||
impl<'a> GlobalContext<'a> for TraverseCtx<'a, MinifierState<'a>> {
|
||||
fn is_global_reference(&self, ident: &IdentifierReference<'_>) -> bool {
|
||||
ident.is_global_reference(self.0.scoping())
|
||||
ident.is_global_reference(self.scoping())
|
||||
}
|
||||
|
||||
fn get_constant_value_for_reference_id(
|
||||
@@ -62,7 +47,33 @@ impl<'a> oxc_ecmascript::GlobalContext<'a> for Ctx<'a, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> oxc_ecmascript::side_effects::MayHaveSideEffectsContext<'a> for Ctx<'a, '_> {
|
||||
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
|
||||
}
|
||||
@@ -84,23 +95,63 @@ impl<'a> oxc_ecmascript::side_effects::MayHaveSideEffectsContext<'a> for Ctx<'a,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ConstantEvaluationCtx<'a> for Ctx<'a, '_> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_exact_int64(num: f64) -> bool {
|
||||
num.fract() == 0.0
|
||||
impl<'a> ConstantEvaluationCtx<'a> for &TraverseCtx<'a, MinifierState<'a>> {
|
||||
fn ast(&self) -> AstBuilder<'a> {
|
||||
(*self).ast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Ctx<'a, '_> {
|
||||
pub fn scoping(&self) -> &Scoping {
|
||||
self.0.scoping()
|
||||
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.0.state.options
|
||||
&self.state.options
|
||||
}
|
||||
|
||||
/// Check if the target engines supports a feature.
|
||||
@@ -111,11 +162,11 @@ impl<'a> Ctx<'a, '_> {
|
||||
}
|
||||
|
||||
pub fn source_type(&self) -> SourceType {
|
||||
self.0.state.source_type
|
||||
self.state.source_type
|
||||
}
|
||||
|
||||
pub fn is_global_reference(&self, ident: &IdentifierReference<'a>) -> bool {
|
||||
ident.is_global_reference(self.0.scoping())
|
||||
ident.is_global_reference(self.scoping())
|
||||
}
|
||||
|
||||
pub fn eval_binary(&self, e: &BinaryExpression<'a>) -> Option<Expression<'a>> {
|
||||
@@ -0,0 +1,325 @@
|
||||
use oxc_ast::ast::{AssignmentTarget, Expression, IdentifierReference, SimpleAssignmentTarget};
|
||||
use oxc_span::{SPAN, Span};
|
||||
use oxc_str::Ident;
|
||||
use oxc_syntax::{reference::ReferenceFlags, symbol::SymbolId};
|
||||
|
||||
use super::TraverseCtx;
|
||||
|
||||
use super::BoundIdentifier;
|
||||
|
||||
/// A factory for generating `IdentifierReference`s.
|
||||
///
|
||||
/// Typical usage:
|
||||
///
|
||||
/// ```rs
|
||||
/// // Create `MaybeBoundIdentifier` from an existing `IdentifierReference`
|
||||
/// let binding = MaybeBoundIdentifier::from_identifier_reference(ident, ctx);
|
||||
///
|
||||
/// // Generate `IdentifierReference`s and insert them into AST
|
||||
/// assign_expr.left = binding.create_write_target(ctx);
|
||||
/// assign_expr.right = binding.create_read_expression(ctx);
|
||||
/// ```
|
||||
///
|
||||
/// Notes:
|
||||
///
|
||||
/// * The original `IdentifierReference` must also be used in the AST, or it'll be a dangling reference.
|
||||
/// * `MaybeBoundIdentifier` is smaller than `IdentifierReference`, so takes less memory when you store
|
||||
/// it for later use.
|
||||
/// * `MaybeBoundIdentifier` re-uses the same `Ident` for all `BindingIdentifier` / `IdentifierReference`s
|
||||
/// created from it.
|
||||
/// * `MaybeBoundIdentifier` looks up the `SymbolId` for the reference only once.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MaybeBoundIdentifier<'a> {
|
||||
pub name: Ident<'a>,
|
||||
pub symbol_id: Option<SymbolId>,
|
||||
}
|
||||
|
||||
impl<'a> MaybeBoundIdentifier<'a> {
|
||||
/// Create `MaybeBoundIdentifier` for `name` and `Option<SymbolId>`
|
||||
pub fn new(name: Ident<'a>, symbol_id: Option<SymbolId>) -> Self {
|
||||
Self { name, symbol_id }
|
||||
}
|
||||
|
||||
/// Create `MaybeBoundIdentifier` from an `IdentifierReference`
|
||||
pub fn from_identifier_reference<State>(
|
||||
ident: &IdentifierReference<'a>,
|
||||
ctx: &TraverseCtx<'a, State>,
|
||||
) -> Self {
|
||||
let symbol_id = ctx.scoping().get_reference(ident.reference_id()).symbol_id();
|
||||
Self { name: ident.name, symbol_id }
|
||||
}
|
||||
|
||||
/// Convert `MaybeBoundIdentifier` to `BoundIdentifier`.
|
||||
///
|
||||
/// Returns `None` if symbol is not bound.
|
||||
pub fn to_bound_identifier(&self) -> Option<BoundIdentifier<'a>> {
|
||||
self.symbol_id.map(|symbol_id| BoundIdentifier::new(self.name, symbol_id))
|
||||
}
|
||||
|
||||
// --- Read only ---
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is read from, with dummy `Span`
|
||||
pub fn create_read_reference<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_read_reference(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is read from, with dummy `Span`
|
||||
pub fn create_read_expression<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_read_expression(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is read from, with specified `Span`
|
||||
pub fn create_spanned_read_reference<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_reference(span, ReferenceFlags::Read, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is read from, with specified `Span`
|
||||
pub fn create_spanned_read_expression<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_expression(span, ReferenceFlags::Read, ctx)
|
||||
}
|
||||
|
||||
// --- Write only ---
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is written to, with dummy `Span`
|
||||
pub fn create_write_reference<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_write_reference(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is written to, with dummy `Span`
|
||||
pub fn create_write_expression<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_write_expression(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, which is written to, with dummy `Span`
|
||||
pub fn create_write_target<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_write_target(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, which is written to, with dummy `Span`
|
||||
pub fn create_write_simple_target<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_write_simple_target(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is written to, with specified `Span`
|
||||
pub fn create_spanned_write_reference<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_reference(span, ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is written to, with specified `Span`
|
||||
pub fn create_spanned_write_expression<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_expression(span, ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, which is written to, with specified `Span`
|
||||
pub fn create_spanned_write_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_target(span, ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, which is written to, with specified `Span`
|
||||
pub fn create_spanned_write_simple_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_simple_target(span, ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
// --- Read and write ---
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is read from + written to,
|
||||
/// with dummy `Span`
|
||||
pub fn create_read_write_reference<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_read_write_reference(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is read from + written to,
|
||||
/// with dummy `Span`
|
||||
pub fn create_read_write_expression<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_read_write_expression(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, which is read from + written to,
|
||||
/// with dummy `Span`
|
||||
pub fn create_read_write_target<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_read_write_target(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, which is read from + written to,
|
||||
/// with dummy `Span`
|
||||
pub fn create_read_write_simple_target<State>(
|
||||
&self,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_read_write_simple_target(SPAN, ctx)
|
||||
}
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, which is read from + written to,
|
||||
/// with specified `Span`
|
||||
pub fn create_spanned_read_write_reference<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_reference(span, ReferenceFlags::Read | ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, which is read from + written to,
|
||||
/// with specified `Span`
|
||||
pub fn create_spanned_read_write_expression<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_expression(span, ReferenceFlags::Read | ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, which is read from + written to,
|
||||
/// with specified `Span`
|
||||
pub fn create_spanned_read_write_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_target(span, ReferenceFlags::Read | ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, which is read from + written to,
|
||||
/// with specified `Span`
|
||||
pub fn create_spanned_read_write_simple_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_simple_target(span, ReferenceFlags::Read | ReferenceFlags::Write, ctx)
|
||||
}
|
||||
|
||||
// --- Specified ReferenceFlags ---
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, with specified `ReferenceFlags`
|
||||
pub fn create_reference<State>(
|
||||
&self,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
self.create_spanned_reference(SPAN, flags, ctx)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, with specified `ReferenceFlags`
|
||||
pub fn create_expression<State>(
|
||||
&self,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
self.create_spanned_expression(SPAN, flags, ctx)
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget` referencing this binding, with specified `ReferenceFlags`
|
||||
pub fn create_target<State>(
|
||||
&self,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
self.create_spanned_target(SPAN, flags, ctx)
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget` referencing this binding, with specified `ReferenceFlags`
|
||||
pub fn create_simple_target<State>(
|
||||
&self,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
self.create_spanned_simple_target(SPAN, flags, ctx)
|
||||
}
|
||||
|
||||
/// Create `IdentifierReference` referencing this binding, with specified `Span` and `ReferenceFlags`
|
||||
pub fn create_spanned_reference<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> IdentifierReference<'a> {
|
||||
ctx.create_ident_reference(span, self.name, self.symbol_id, flags)
|
||||
}
|
||||
|
||||
/// Create `Expression::Identifier` referencing this binding, with specified `Span` and `ReferenceFlags`
|
||||
pub fn create_spanned_expression<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> Expression<'a> {
|
||||
let ident = self.create_spanned_reference(span, flags, ctx);
|
||||
Expression::Identifier(ctx.alloc(ident))
|
||||
}
|
||||
|
||||
/// Create `AssignmentTarget::AssignmentTargetIdentifier` referencing this binding,
|
||||
/// with specified `Span` and `ReferenceFlags`
|
||||
pub fn create_spanned_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
let ident = self.create_spanned_reference(span, flags, ctx);
|
||||
AssignmentTarget::AssignmentTargetIdentifier(ctx.alloc(ident))
|
||||
}
|
||||
|
||||
/// Create `SimpleAssignmentTarget::AssignmentTargetIdentifier` referencing this binding,
|
||||
/// with specified `Span` and `ReferenceFlags`
|
||||
pub fn create_spanned_simple_target<State>(
|
||||
&self,
|
||||
span: Span,
|
||||
flags: ReferenceFlags,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
let ident = self.create_spanned_reference(span, flags, ctx);
|
||||
SimpleAssignmentTarget::AssignmentTargetIdentifier(ctx.alloc(ident))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,717 @@
|
||||
use oxc_allocator::{Allocator, Box as ArenaBox, Vec as ArenaVec};
|
||||
use oxc_ast::{
|
||||
AstBuilder,
|
||||
ast::{Expression, IdentifierReference, Statement},
|
||||
};
|
||||
use oxc_semantic::Scoping;
|
||||
use oxc_span::Span;
|
||||
use oxc_str::Ident;
|
||||
use oxc_syntax::{
|
||||
reference::{ReferenceFlags, ReferenceId},
|
||||
scope::{ScopeFlags, ScopeId},
|
||||
symbol::{SymbolFlags, SymbolId},
|
||||
};
|
||||
use oxc_traverse::ast_operations::{GatherNodeParts, get_var_name_from_node};
|
||||
|
||||
use crate::{
|
||||
generated::ancestor::{Ancestor, AncestorType},
|
||||
state::MinifierState,
|
||||
};
|
||||
|
||||
mod ancestry;
|
||||
mod bound_identifier;
|
||||
mod ecma_context;
|
||||
mod maybe_bound_identifier;
|
||||
mod reusable;
|
||||
mod scopes_collector;
|
||||
mod scoping;
|
||||
mod uid;
|
||||
use ancestry::PopToken;
|
||||
pub use ancestry::TraverseAncestry;
|
||||
pub use bound_identifier::BoundIdentifier;
|
||||
pub use maybe_bound_identifier::MaybeBoundIdentifier;
|
||||
pub use reusable::ReusableTraverseCtx;
|
||||
pub use scoping::TraverseScoping;
|
||||
|
||||
pub type MinifierTraverseCtx<'a> = TraverseCtx<'a, MinifierState<'a>>;
|
||||
pub type ReusableMinifierTraverseCtx<'a> = ReusableTraverseCtx<'a, MinifierState<'a>>;
|
||||
|
||||
/// Traverse context.
|
||||
///
|
||||
/// Passed to all AST visitor functions.
|
||||
///
|
||||
/// Provides ability to:
|
||||
/// * Query parent/ancestor of current node via [`parent`], [`ancestor`], [`ancestors`].
|
||||
/// * Get scopes tree and symbols table via [`scoping`] and [`scoping_mut`],
|
||||
/// [`ancestor_scopes`].
|
||||
/// * Create AST nodes via AST builder [`ast`].
|
||||
/// * Allocate into arena via [`alloc`].
|
||||
///
|
||||
/// # Namespaced APIs
|
||||
///
|
||||
/// All APIs are provided via 2 routes:
|
||||
///
|
||||
/// 1. Directly on `TraverseCtx`.
|
||||
/// 2. Via "namespaces".
|
||||
///
|
||||
/// | Direct | Namespaced |
|
||||
/// |--------------------------|----------------------------------|
|
||||
/// | `ctx.parent()` | `ctx.ancestry.parent()` |
|
||||
/// | `ctx.current_scope_id()` | `ctx.scoping.current_scope_id()` |
|
||||
/// | `ctx.alloc(thing)` | `ctx.ast.alloc(thing)` |
|
||||
///
|
||||
/// Purpose of the "namespaces" is to support if you want to mutate scope tree or symbol table
|
||||
/// while holding an `&Ancestor`, or AST nodes obtained from an `&Ancestor`.
|
||||
///
|
||||
/// For example, this will not compile because it attempts to borrow `ctx`
|
||||
/// immutably and mutably at same time:
|
||||
///
|
||||
/// ```nocompile
|
||||
/// use oxc_ast::ast::*;
|
||||
/// use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
||||
///
|
||||
/// struct MyTransform;
|
||||
/// impl<'a> Traverse<'a> for MyTransform {
|
||||
/// fn enter_unary_expression(&mut self, unary_expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
/// // `right` is ultimately borrowed from `ctx`
|
||||
/// let right = match ctx.parent() {
|
||||
/// Ancestor::BinaryExpressionLeft(bin_expr) => bin_expr.right(),
|
||||
/// _ => return,
|
||||
/// };
|
||||
///
|
||||
/// // Won't compile! `ctx.scopes_mut()` attempts to mut borrow `ctx`
|
||||
/// // while it's already borrowed by `right`.
|
||||
/// let scope_tree_mut = ctx.scopes_mut();
|
||||
///
|
||||
/// // Use `right` later on
|
||||
/// dbg!(right);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// You can fix this by using the "namespaced" methods instead.
|
||||
/// This works because you can borrow `ctx.ancestry` and `ctx.scoping` simultaneously:
|
||||
///
|
||||
/// ```
|
||||
/// use oxc_ast::ast::*;
|
||||
/// use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
||||
///
|
||||
/// struct MyTransform;
|
||||
/// impl<'a> Traverse<'a, ()> for MyTransform {
|
||||
/// fn enter_unary_expression(&mut self, unary_expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a, ()>) {
|
||||
/// let right = match ctx.ancestry.parent() {
|
||||
/// Ancestor::BinaryExpressionLeft(bin_expr) => bin_expr.right(),
|
||||
/// _ => return,
|
||||
/// };
|
||||
///
|
||||
/// let scoping_mut = ctx.scoping.scoping_mut();
|
||||
///
|
||||
/// dbg!(right);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`parent`]: `TraverseCtx::parent`
|
||||
/// [`ancestor`]: `TraverseCtx::ancestor`
|
||||
/// [`ancestors`]: `TraverseCtx::ancestors`
|
||||
/// [`scoping`]: `TraverseCtx::scoping`
|
||||
/// [`scoping_mut`]: `TraverseCtx::scoping_mut`
|
||||
/// [`ancestor_scopes`]: `TraverseCtx::ancestor_scopes`
|
||||
/// [`ast`]: `TraverseCtx::ast`
|
||||
/// [`alloc`]: `TraverseCtx::alloc`
|
||||
pub struct TraverseCtx<'a, State> {
|
||||
pub state: State,
|
||||
pub ancestry: TraverseAncestry<'a>,
|
||||
pub scoping: TraverseScoping<'a>,
|
||||
pub ast: AstBuilder<'a>,
|
||||
}
|
||||
|
||||
// Public methods
|
||||
impl<'a, State> TraverseCtx<'a, State> {
|
||||
/// Allocate a node in the arena.
|
||||
///
|
||||
/// Returns a [`Box<'a, T>`](ArenaBox).
|
||||
///
|
||||
/// Shortcut for `ctx.ast.alloc`.
|
||||
#[inline]
|
||||
pub fn alloc<T>(&self, node: T) -> ArenaBox<'a, T> {
|
||||
self.ast.alloc(node)
|
||||
}
|
||||
|
||||
/// Get parent of current node.
|
||||
///
|
||||
/// Shortcut for `ctx.ancestry.parent`.
|
||||
#[inline]
|
||||
pub fn parent<'t>(&'t self) -> Ancestor<'a, 't> {
|
||||
self.ancestry.parent()
|
||||
}
|
||||
|
||||
/// Get ancestor of current node.
|
||||
///
|
||||
/// `level` is number of levels above parent.
|
||||
/// `ancestor(0)` is equivalent to `parent()` (but better to use `parent()` as it's more efficient).
|
||||
///
|
||||
/// If `level` is out of bounds (above `Program`), returns `Ancestor::None`.
|
||||
///
|
||||
/// Shortcut for `ctx.ancestry.ancestor`.
|
||||
#[inline]
|
||||
pub fn ancestor<'t>(&'t self, level: usize) -> Ancestor<'a, 't> {
|
||||
self.ancestry.ancestor(level)
|
||||
}
|
||||
|
||||
/// Get iterator over ancestors, starting with parent and working up.
|
||||
///
|
||||
/// Last `Ancestor` returned will be `Program`. `Ancestor::None` is not included in iteration.
|
||||
///
|
||||
/// Shortcut for `ctx.ancestry.ancestors`.
|
||||
#[inline]
|
||||
pub fn ancestors<'t>(&'t self) -> impl Iterator<Item = Ancestor<'a, 't>> {
|
||||
self.ancestry.ancestors()
|
||||
}
|
||||
|
||||
/// Get depth in the AST.
|
||||
///
|
||||
/// Count includes current node. i.e. in `Program`, depth is 1.
|
||||
///
|
||||
/// Shortcut for `self.ancestry.ancestors_depth`.
|
||||
#[inline]
|
||||
pub fn ancestors_depth(&self) -> usize {
|
||||
self.ancestry.ancestors_depth()
|
||||
}
|
||||
|
||||
/// Get current scope ID.
|
||||
///
|
||||
/// Shortcut for `ctx.scoping.current_scope_id`.
|
||||
#[inline]
|
||||
pub fn current_scope_id(&self) -> ScopeId {
|
||||
self.scoping.current_scope_id()
|
||||
}
|
||||
|
||||
/// Get current var hoisting scope ID.
|
||||
///
|
||||
/// Shortcut for `ctx.scoping.current_hoist_scope_id`.
|
||||
#[inline]
|
||||
pub fn current_hoist_scope_id(&self) -> ScopeId {
|
||||
self.scoping.current_hoist_scope_id()
|
||||
}
|
||||
|
||||
/// Get current block scope ID.
|
||||
///
|
||||
/// Shortcut for `ctx.scoping.current_block_scope_id`.
|
||||
#[inline]
|
||||
pub fn current_block_scope_id(&self) -> ScopeId {
|
||||
self.scoping.current_block_scope_id()
|
||||
}
|
||||
|
||||
/// Get current scope flags.
|
||||
///
|
||||
/// Shortcut for `ctx.scoping.current_scope_flags`.
|
||||
#[inline]
|
||||
pub fn current_scope_flags(&self) -> ScopeFlags {
|
||||
self.scoping.current_scope_flags()
|
||||
}
|
||||
|
||||
/// Get scopes tree.
|
||||
///
|
||||
/// Shortcut for `ctx.scoping.scopes`.
|
||||
#[inline]
|
||||
pub fn scoping(&self) -> &Scoping {
|
||||
self.scoping.scoping()
|
||||
}
|
||||
|
||||
/// Get mutable scopes tree.
|
||||
///
|
||||
/// Shortcut for `ctx.scoping.scopes_mut`.
|
||||
#[inline]
|
||||
pub fn scoping_mut(&mut self) -> &mut Scoping {
|
||||
self.scoping.scoping_mut()
|
||||
}
|
||||
|
||||
/// Get iterator over scopes, starting with current scope and working up.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.parent_scopes`.
|
||||
#[inline]
|
||||
pub fn ancestor_scopes(&self) -> impl Iterator<Item = ScopeId> + '_ {
|
||||
self.scoping.ancestor_scopes()
|
||||
}
|
||||
|
||||
/// Create new scope as child of provided scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.create_child_scope`.
|
||||
#[inline]
|
||||
pub fn create_child_scope(&mut self, parent_id: ScopeId, flags: ScopeFlags) -> ScopeId {
|
||||
self.scoping.create_child_scope(parent_id, flags)
|
||||
}
|
||||
|
||||
/// Create new scope as child of current scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.create_child_scope_of_current`.
|
||||
#[inline]
|
||||
pub fn create_child_scope_of_current(&mut self, flags: ScopeFlags) -> ScopeId {
|
||||
self.scoping.create_child_scope_of_current(flags)
|
||||
}
|
||||
|
||||
/// Insert a scope into scope tree below a statement.
|
||||
///
|
||||
/// Statement must be in current scope.
|
||||
/// New scope is created as child of current scope.
|
||||
/// All child scopes of the statement are reassigned to be children of the new scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.insert_scope_below_statement`.
|
||||
#[inline]
|
||||
pub fn insert_scope_below_statement(&mut self, stmt: &Statement, flags: ScopeFlags) -> ScopeId {
|
||||
self.scoping.insert_scope_below_statement(stmt, flags)
|
||||
}
|
||||
|
||||
/// Insert a scope into scope tree below a statement.
|
||||
///
|
||||
/// Statement must be in provided scope.
|
||||
/// New scope is created as child of the provided scope.
|
||||
/// All child scopes of the statement are reassigned to be children of the new scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.insert_scope_below_statement_from_scope_id`.
|
||||
#[inline]
|
||||
pub fn insert_scope_below_statement_from_scope_id(
|
||||
&mut self,
|
||||
stmt: &Statement,
|
||||
scope_id: ScopeId,
|
||||
flags: ScopeFlags,
|
||||
) -> ScopeId {
|
||||
self.scoping.insert_scope_below_statement_from_scope_id(stmt, scope_id, flags)
|
||||
}
|
||||
|
||||
/// Insert a scope into scope tree below an expression.
|
||||
///
|
||||
/// Expression must be in current scope.
|
||||
/// New scope is created as child of current scope.
|
||||
/// All child scopes of the expression are reassigned to be children of the new scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.insert_scope_below_expression`.
|
||||
#[inline]
|
||||
pub fn insert_scope_below_expression(
|
||||
&mut self,
|
||||
expr: &Expression,
|
||||
flags: ScopeFlags,
|
||||
) -> ScopeId {
|
||||
self.scoping.insert_scope_below_expression(expr, flags)
|
||||
}
|
||||
|
||||
/// Insert a scope into scope tree below a `Vec` of statements.
|
||||
///
|
||||
/// Statements must be in current scope.
|
||||
/// New scope is created as child of current scope.
|
||||
/// All child scopes of the statement are reassigned to be children of the new scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.insert_scope_below_statements`.
|
||||
pub fn insert_scope_below_statements(
|
||||
&mut self,
|
||||
stmts: &ArenaVec<Statement>,
|
||||
flags: ScopeFlags,
|
||||
) -> ScopeId {
|
||||
self.scoping.insert_scope_below_statements(stmts, flags)
|
||||
}
|
||||
|
||||
/// Insert a scope between a parent and a child scope.
|
||||
///
|
||||
/// For example, given the following scopes
|
||||
/// ```ts
|
||||
/// parentScope1: {
|
||||
/// childScope: { }
|
||||
/// childScope2: { }
|
||||
/// }
|
||||
/// ```
|
||||
/// and calling this function with `parentScope1` and `childScope`,
|
||||
/// the resulting scopes will be:
|
||||
/// ```ts
|
||||
/// parentScope1: {
|
||||
/// newScope: {
|
||||
/// childScope: { }
|
||||
/// }
|
||||
/// childScope2: { }
|
||||
/// }
|
||||
/// ```
|
||||
/// This is a shortcut for `ctx.scoping.insert_scope_between`.
|
||||
pub fn insert_scope_between(
|
||||
&mut self,
|
||||
parent_id: ScopeId,
|
||||
child_id: ScopeId,
|
||||
flags: ScopeFlags,
|
||||
) -> ScopeId {
|
||||
self.scoping.insert_scope_between(parent_id, child_id, flags)
|
||||
}
|
||||
|
||||
/// Remove scope for an expression from the scope chain.
|
||||
///
|
||||
/// Delete the scope and set parent of its child scopes to its parent scope.
|
||||
/// e.g.:
|
||||
/// * Starting scopes parentage `A -> B`, `B -> C`, `B -> D`.
|
||||
/// * Remove scope `B` from chain.
|
||||
/// * End result: scopes `A -> C`, `A -> D`.
|
||||
///
|
||||
/// Use this when removing an expression which owns a scope, without removing its children.
|
||||
/// For example when unwrapping `(() => foo)()` to just `foo`.
|
||||
/// `foo` here could be an expression which itself contains scopes.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.remove_scope_for_expression`.
|
||||
pub fn remove_scope_for_expression(&mut self, scope_id: ScopeId, expr: &Expression) {
|
||||
self.scoping.remove_scope_for_expression(scope_id, expr);
|
||||
}
|
||||
|
||||
/// Generate binding.
|
||||
///
|
||||
/// Creates a symbol with the provided name and flags and adds it to the specified scope.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.generate_binding`.
|
||||
pub fn generate_binding(
|
||||
&mut self,
|
||||
name: Ident<'a>,
|
||||
scope_id: ScopeId,
|
||||
flags: SymbolFlags,
|
||||
) -> BoundIdentifier<'a> {
|
||||
self.scoping.generate_binding(name, scope_id, flags)
|
||||
}
|
||||
|
||||
/// Generate binding in current scope.
|
||||
///
|
||||
/// Creates a symbol with the provided name and flags and adds it to the current scope.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.generate_binding_in_current_scope`.
|
||||
pub fn generate_binding_in_current_scope(
|
||||
&mut self,
|
||||
name: Ident<'a>,
|
||||
flags: SymbolFlags,
|
||||
) -> BoundIdentifier<'a> {
|
||||
self.scoping.generate_binding_in_current_scope(name, flags)
|
||||
}
|
||||
|
||||
/// Generate UID var name.
|
||||
///
|
||||
/// Finds a unique variable name which does clash with any other variables used in the program.
|
||||
///
|
||||
/// See [`TraverseScoping::generate_uid_name`] for important information on how UIDs are generated.
|
||||
/// There are some potential "gotchas".
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.generate_uid_name`.
|
||||
pub fn generate_uid_name(&mut self, name: &str) -> Ident<'a> {
|
||||
self.scoping.generate_uid_name(name, self.ast.allocator)
|
||||
}
|
||||
|
||||
/// Generate UID.
|
||||
///
|
||||
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
|
||||
/// on how UIDs are generated. There are some potential "gotchas".
|
||||
#[inline]
|
||||
pub fn generate_uid(
|
||||
&mut self,
|
||||
name: &str,
|
||||
scope_id: ScopeId,
|
||||
flags: SymbolFlags,
|
||||
) -> BoundIdentifier<'a> {
|
||||
// Get name for UID
|
||||
let name = self.generate_uid_name(name);
|
||||
let symbol_id = self.scoping.add_binding(name, scope_id, flags);
|
||||
BoundIdentifier::new(name, symbol_id)
|
||||
}
|
||||
|
||||
/// Generate UID in current scope.
|
||||
///
|
||||
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
|
||||
/// on how UIDs are generated. There are some potential "gotchas".
|
||||
#[inline]
|
||||
pub fn generate_uid_in_current_scope(
|
||||
&mut self,
|
||||
name: &str,
|
||||
flags: SymbolFlags,
|
||||
) -> BoundIdentifier<'a> {
|
||||
self.generate_uid(name, self.current_scope_id(), flags)
|
||||
}
|
||||
|
||||
/// Generate UID in root scope.
|
||||
///
|
||||
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
|
||||
/// on how UIDs are generated. There are some potential "gotchas".
|
||||
#[inline]
|
||||
pub fn generate_uid_in_root_scope(
|
||||
&mut self,
|
||||
name: &str,
|
||||
flags: SymbolFlags,
|
||||
) -> BoundIdentifier<'a> {
|
||||
self.generate_uid(name, self.scoping().root_scope_id(), flags)
|
||||
}
|
||||
|
||||
/// Generate UID based on node.
|
||||
///
|
||||
/// Recursively gathers the identifying names of a node, and joins them with `$`.
|
||||
///
|
||||
/// Based on Babel's `scope.generateUidBasedOnNode` logic.
|
||||
/// <https://github.com/babel/babel/blob/419644f27c5c59deb19e71aaabd417a3bc5483ca/packages/babel-traverse/src/scope/index.ts#L543>
|
||||
#[inline]
|
||||
pub fn generate_uid_based_on_node<N: GatherNodeParts<'a>>(
|
||||
&mut self,
|
||||
node: &N,
|
||||
scope_id: ScopeId,
|
||||
flags: SymbolFlags,
|
||||
) -> BoundIdentifier<'a> {
|
||||
let name = get_var_name_from_node(node);
|
||||
self.generate_uid(&name, scope_id, flags)
|
||||
}
|
||||
|
||||
/// Generate UID in current scope based on node.
|
||||
///
|
||||
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
|
||||
/// on how UIDs are generated. There are some potential "gotchas".
|
||||
#[inline]
|
||||
pub fn generate_uid_in_current_scope_based_on_node<N: GatherNodeParts<'a>>(
|
||||
&mut self,
|
||||
node: &N,
|
||||
flags: SymbolFlags,
|
||||
) -> BoundIdentifier<'a> {
|
||||
self.generate_uid_based_on_node(node, self.current_scope_id(), flags)
|
||||
}
|
||||
|
||||
/// Generate UID in current hoist scope.
|
||||
///
|
||||
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
|
||||
/// on how UIDs are generated. There are some potential "gotchas".
|
||||
#[inline]
|
||||
pub fn generate_uid_in_current_hoist_scope(&mut self, name: &str) -> BoundIdentifier<'a> {
|
||||
self.generate_uid(name, self.current_hoist_scope_id(), SymbolFlags::FunctionScopedVariable)
|
||||
}
|
||||
|
||||
/// Generate UID in current hoist scope based on node.
|
||||
///
|
||||
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
|
||||
/// on how UIDs are generated. There are some potential "gotchas".
|
||||
#[inline]
|
||||
pub fn generate_uid_in_current_hoist_scope_based_on_node<N: GatherNodeParts<'a>>(
|
||||
&mut self,
|
||||
node: &N,
|
||||
) -> BoundIdentifier<'a> {
|
||||
let name = get_var_name_from_node(node);
|
||||
self.generate_uid_in_current_hoist_scope(&name)
|
||||
}
|
||||
|
||||
/// Create a reference bound to a `SymbolId`.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.create_bound_reference`.
|
||||
#[inline]
|
||||
pub fn create_bound_reference(
|
||||
&mut self,
|
||||
symbol_id: SymbolId,
|
||||
flags: ReferenceFlags,
|
||||
) -> ReferenceId {
|
||||
self.scoping.create_bound_reference(symbol_id, flags)
|
||||
}
|
||||
|
||||
/// Create an `IdentifierReference` bound to a `SymbolId`.
|
||||
pub fn create_bound_ident_reference(
|
||||
&mut self,
|
||||
span: Span,
|
||||
name: Ident<'a>,
|
||||
symbol_id: SymbolId,
|
||||
flags: ReferenceFlags,
|
||||
) -> IdentifierReference<'a> {
|
||||
let reference_id = self.create_bound_reference(symbol_id, flags);
|
||||
self.ast.identifier_reference_with_reference_id(span, name, reference_id)
|
||||
}
|
||||
|
||||
/// Create an `Expression::Identifier` bound to a `SymbolId`.
|
||||
pub fn create_bound_ident_expr(
|
||||
&mut self,
|
||||
span: Span,
|
||||
name: Ident<'a>,
|
||||
symbol_id: SymbolId,
|
||||
flags: ReferenceFlags,
|
||||
) -> Expression<'a> {
|
||||
let ident = self.create_bound_ident_reference(span, name, symbol_id, flags);
|
||||
Expression::Identifier(self.ast.alloc(ident))
|
||||
}
|
||||
|
||||
/// Create an unbound reference.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.create_unbound_reference`.
|
||||
#[inline]
|
||||
pub fn create_unbound_reference(
|
||||
&mut self,
|
||||
name: Ident<'_>,
|
||||
flags: ReferenceFlags,
|
||||
) -> ReferenceId {
|
||||
self.scoping.create_unbound_reference(name, flags)
|
||||
}
|
||||
|
||||
/// Create an unbound `IdentifierReference`.
|
||||
pub fn create_unbound_ident_reference(
|
||||
&mut self,
|
||||
span: Span,
|
||||
name: Ident<'a>,
|
||||
flags: ReferenceFlags,
|
||||
) -> IdentifierReference<'a> {
|
||||
let reference_id = self.create_unbound_reference(name, flags);
|
||||
self.ast.identifier_reference_with_reference_id(span, name, reference_id)
|
||||
}
|
||||
|
||||
/// Create an unbound `Expression::Identifier`.
|
||||
pub fn create_unbound_ident_expr(
|
||||
&mut self,
|
||||
span: Span,
|
||||
name: Ident<'a>,
|
||||
flags: ReferenceFlags,
|
||||
) -> Expression<'a> {
|
||||
let ident = self.create_unbound_ident_reference(span, name, flags);
|
||||
Expression::Identifier(self.ast.alloc(ident))
|
||||
}
|
||||
|
||||
/// Create a reference optionally bound to a `SymbolId`.
|
||||
///
|
||||
/// If you know if there's a `SymbolId` or not, prefer `TraverseCtx::create_bound_reference`
|
||||
/// or `TraverseCtx::create_unbound_reference`.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.create_reference`.
|
||||
#[inline]
|
||||
pub fn create_reference(
|
||||
&mut self,
|
||||
name: Ident<'_>,
|
||||
symbol_id: Option<SymbolId>,
|
||||
flags: ReferenceFlags,
|
||||
) -> ReferenceId {
|
||||
self.scoping.create_reference(name, symbol_id, flags)
|
||||
}
|
||||
|
||||
/// Create an `IdentifierReference` optionally bound to a `SymbolId`.
|
||||
///
|
||||
/// If you know if there's a `SymbolId` or not, prefer `TraverseCtx::create_bound_ident_reference`
|
||||
/// or `TraverseCtx::create_unbound_ident_reference`.
|
||||
pub fn create_ident_reference(
|
||||
&mut self,
|
||||
span: Span,
|
||||
name: Ident<'a>,
|
||||
symbol_id: Option<SymbolId>,
|
||||
flags: ReferenceFlags,
|
||||
) -> IdentifierReference<'a> {
|
||||
if let Some(symbol_id) = symbol_id {
|
||||
self.create_bound_ident_reference(span, name, symbol_id, flags)
|
||||
} else {
|
||||
self.create_unbound_ident_reference(span, name, flags)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an `Expression::Identifier` optionally bound to a `SymbolId`.
|
||||
///
|
||||
/// If you know if there's a `SymbolId` or not, prefer `TraverseCtx::create_bound_ident_expr`
|
||||
/// or `TraverseCtx::create_unbound_ident_expr`.
|
||||
pub fn create_ident_expr(
|
||||
&mut self,
|
||||
span: Span,
|
||||
name: Ident<'a>,
|
||||
symbol_id: Option<SymbolId>,
|
||||
flags: ReferenceFlags,
|
||||
) -> Expression<'a> {
|
||||
if let Some(symbol_id) = symbol_id {
|
||||
self.create_bound_ident_expr(span, name, symbol_id, flags)
|
||||
} else {
|
||||
self.create_unbound_ident_expr(span, name, flags)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create reference in current scope, looking up binding for `name`,
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.create_reference_in_current_scope`.
|
||||
#[inline]
|
||||
pub fn create_reference_in_current_scope(
|
||||
&mut self,
|
||||
name: Ident<'_>,
|
||||
flags: ReferenceFlags,
|
||||
) -> ReferenceId {
|
||||
self.scoping.create_reference_in_current_scope(name, flags)
|
||||
}
|
||||
|
||||
/// Delete a reference.
|
||||
///
|
||||
/// Provided `name` must match `reference_id`.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.delete_reference`.
|
||||
pub fn delete_reference(&mut self, reference_id: ReferenceId, name: Ident<'_>) {
|
||||
self.scoping.delete_reference(reference_id, name);
|
||||
}
|
||||
|
||||
/// Delete reference for an `IdentifierReference`.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.delete_reference_for_identifier`.
|
||||
pub fn delete_reference_for_identifier(&mut self, ident: &IdentifierReference) {
|
||||
self.scoping.delete_reference_for_identifier(ident);
|
||||
}
|
||||
}
|
||||
|
||||
// Methods used internally within crate
|
||||
impl<'a, State> TraverseCtx<'a, State> {
|
||||
/// Create new traversal context.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// This function must not be public to maintain soundness of [`TraverseAncestry`].
|
||||
pub(crate) fn new(state: State, scoping: Scoping, allocator: &'a Allocator) -> Self {
|
||||
let ancestry = TraverseAncestry::new();
|
||||
let scoping = TraverseScoping::new(scoping);
|
||||
let ast = AstBuilder::new(allocator);
|
||||
Self { state, ancestry, scoping, ast }
|
||||
}
|
||||
|
||||
/// Shortcut for `self.ancestry.push_stack`, to make `walk_*` methods less verbose.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// This method must not be public outside this crate, or consumer could break safety invariants.
|
||||
#[inline]
|
||||
pub(crate) fn push_stack(&mut self, ancestor: Ancestor<'a, 'static>) -> PopToken {
|
||||
self.ancestry.push_stack(ancestor)
|
||||
}
|
||||
|
||||
/// Shortcut for `self.ancestry.pop_stack`, to make `walk_*` methods less verbose.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// See safety constraints of `TraverseAncestry.pop_stack`.
|
||||
/// This method must not be public outside this crate, or consumer could break safety invariants.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn pop_stack(&mut self, token: PopToken) {
|
||||
self.ancestry.pop_stack(token);
|
||||
}
|
||||
|
||||
/// Shortcut for `self.ancestry.retag_stack`, to make `walk_*` methods less verbose.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// See safety constraints of `TraverseAncestry.retag_stack`.
|
||||
/// This method must not be public outside this crate, or consumer could break safety invariants.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn retag_stack(&mut self, ty: AncestorType) {
|
||||
// SAFETY: Caller muct uphold safety constraints
|
||||
unsafe { self.ancestry.retag_stack(ty) };
|
||||
}
|
||||
|
||||
/// Shortcut for `ctx.scoping.set_current_scope_id`, to make `walk_*` methods less verbose.
|
||||
#[inline]
|
||||
pub(crate) fn set_current_scope_id(&mut self, scope_id: ScopeId) {
|
||||
self.scoping.set_current_scope_id(scope_id);
|
||||
}
|
||||
|
||||
/// Shortcut for `ctx.scoping.set_current_hoist_scope_id`, to make `walk_*` methods less verbose.
|
||||
#[inline]
|
||||
pub(crate) fn set_current_hoist_scope_id(&mut self, scope_id: ScopeId) {
|
||||
self.scoping.set_current_hoist_scope_id(scope_id);
|
||||
}
|
||||
|
||||
/// Shortcut for `ctx.scoping.set_current_block_scope_id`, to make `walk_*` methods less verbose.
|
||||
#[inline]
|
||||
pub(crate) fn set_current_block_scope_id(&mut self, scope_id: ScopeId) {
|
||||
self.scoping.set_current_block_scope_id(scope_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_semantic::Scoping;
|
||||
|
||||
use super::TraverseCtx;
|
||||
|
||||
/// Wrapper around [`TraverseCtx`], allowing its reuse.
|
||||
///
|
||||
/// We cannot expose ability to obtain an owned [`TraverseCtx`], as it's then possible to circumvent
|
||||
/// the safety invariants of [`TraverseAncestry`].
|
||||
///
|
||||
/// This wrapper type can safely be passed to user code as only ways it can be used are to:
|
||||
///
|
||||
/// * Call `traverse_mut_with_ctx`, which maintains safety invariants.
|
||||
/// * Unwrap it to [`Scoping`] and / or `State`, which discards the sensitive [`TraverseAncestry`] in the process.
|
||||
///
|
||||
/// [`TraverseAncestry`]: super::TraverseAncestry
|
||||
#[repr(transparent)]
|
||||
pub struct ReusableTraverseCtx<'a, State>(TraverseCtx<'a, State>);
|
||||
|
||||
// Public methods
|
||||
impl<'a, State> ReusableTraverseCtx<'a, State> {
|
||||
/// Create new [`ReusableTraverseCtx`].
|
||||
pub fn new(state: State, scoping: Scoping, allocator: &'a Allocator) -> Self {
|
||||
Self(TraverseCtx::new(state, scoping, allocator))
|
||||
}
|
||||
|
||||
/// Get a reference to the user state.
|
||||
pub fn state(&self) -> &State {
|
||||
&self.0.state
|
||||
}
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
impl<'a, State> ReusableTraverseCtx<'a, State> {
|
||||
/// Mutably borrow [`TraverseCtx`] from a [`ReusableTraverseCtx`].
|
||||
#[inline] // because this function is a no-op at run time
|
||||
pub(crate) fn get_mut(&mut self) -> &mut TraverseCtx<'a, State> {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,391 @@
|
||||
use std::str;
|
||||
|
||||
use oxc_allocator::{Allocator, Vec as ArenaVec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ast_visit::Visit;
|
||||
use oxc_semantic::{NodeId, Reference, Scoping};
|
||||
use oxc_span::{Ident, SPAN};
|
||||
use oxc_syntax::{
|
||||
reference::{ReferenceFlags, ReferenceId},
|
||||
scope::{ScopeFlags, ScopeId},
|
||||
symbol::{SymbolFlags, SymbolId},
|
||||
};
|
||||
|
||||
use super::{
|
||||
bound_identifier::BoundIdentifier, scopes_collector::ChildScopeCollector, uid::UidGenerator,
|
||||
};
|
||||
|
||||
/// Traverse scope context.
|
||||
///
|
||||
/// Contains the scope tree and symbols table, and provides methods to access them.
|
||||
///
|
||||
/// `current_scope_id` is the ID of current scope during traversal.
|
||||
/// `walk_*` functions update this field when entering/exiting a scope.
|
||||
pub struct TraverseScoping<'a> {
|
||||
scoping: Scoping,
|
||||
uid_generator: Option<UidGenerator<'a>>,
|
||||
current_scope_id: ScopeId,
|
||||
current_hoist_scope_id: ScopeId,
|
||||
current_block_scope_id: ScopeId,
|
||||
}
|
||||
|
||||
// Public methods
|
||||
impl<'a> TraverseScoping<'a> {
|
||||
/// Get current scope ID
|
||||
#[inline]
|
||||
pub fn current_scope_id(&self) -> ScopeId {
|
||||
self.current_scope_id
|
||||
}
|
||||
|
||||
/// Get current var hoisting scope ID
|
||||
#[inline]
|
||||
pub(crate) fn current_hoist_scope_id(&self) -> ScopeId {
|
||||
self.current_hoist_scope_id
|
||||
}
|
||||
|
||||
/// Get current block scope ID
|
||||
#[inline]
|
||||
pub(crate) fn current_block_scope_id(&self) -> ScopeId {
|
||||
self.current_block_scope_id
|
||||
}
|
||||
|
||||
/// Get current scope flags
|
||||
#[inline]
|
||||
pub fn current_scope_flags(&self) -> ScopeFlags {
|
||||
self.scoping.scope_flags(self.current_scope_id)
|
||||
}
|
||||
|
||||
/// Get scopes tree
|
||||
#[inline]
|
||||
pub fn scoping(&self) -> &Scoping {
|
||||
&self.scoping
|
||||
}
|
||||
|
||||
/// Get mutable scopes tree
|
||||
#[inline]
|
||||
pub fn scoping_mut(&mut self) -> &mut Scoping {
|
||||
&mut self.scoping
|
||||
}
|
||||
|
||||
/// Get iterator over scopes, starting with current scope and working up
|
||||
pub fn ancestor_scopes(&self) -> impl Iterator<Item = ScopeId> + '_ {
|
||||
self.scoping.scope_ancestors(self.current_scope_id)
|
||||
}
|
||||
|
||||
/// Create new scope as child of provided scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
pub fn create_child_scope(&mut self, parent_id: ScopeId, flags: ScopeFlags) -> ScopeId {
|
||||
let flags = self.scoping.get_new_scope_flags(flags, parent_id);
|
||||
self.scoping.add_scope(Some(parent_id), NodeId::DUMMY, flags)
|
||||
}
|
||||
|
||||
/// Create new scope as child of current scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
pub fn create_child_scope_of_current(&mut self, flags: ScopeFlags) -> ScopeId {
|
||||
self.create_child_scope(self.current_scope_id, flags)
|
||||
}
|
||||
|
||||
/// Insert a scope into scope tree below a statement.
|
||||
///
|
||||
/// Statement must be in current scope.
|
||||
/// New scope is created as child of current scope.
|
||||
/// All child scopes of the statement are reassigned to be children of the new scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
pub fn insert_scope_below_statement(&mut self, stmt: &Statement, flags: ScopeFlags) -> ScopeId {
|
||||
self.insert_scope_below_statement_from_scope_id(stmt, self.current_scope_id, flags)
|
||||
}
|
||||
|
||||
/// Insert a scope into scope tree below a statement.
|
||||
///
|
||||
/// Statement must be in provided scope.
|
||||
/// New scope is created as child of the provided scope.
|
||||
/// All child scopes of the statement are reassigned to be children of the new scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
pub fn insert_scope_below_statement_from_scope_id(
|
||||
&mut self,
|
||||
stmt: &Statement,
|
||||
scope_id: ScopeId,
|
||||
flags: ScopeFlags,
|
||||
) -> ScopeId {
|
||||
let mut collector = ChildScopeCollector::new();
|
||||
collector.visit_statement(stmt);
|
||||
self.insert_scope_below(scope_id, &collector.scope_ids, flags)
|
||||
}
|
||||
|
||||
/// Insert a scope into scope tree below an expression.
|
||||
///
|
||||
/// Expression must be in current scope.
|
||||
/// New scope is created as child of current scope.
|
||||
/// All child scopes of the expression are reassigned to be children of the new scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
pub fn insert_scope_below_expression(
|
||||
&mut self,
|
||||
expr: &Expression,
|
||||
flags: ScopeFlags,
|
||||
) -> ScopeId {
|
||||
let mut collector = ChildScopeCollector::new();
|
||||
collector.visit_expression(expr);
|
||||
self.insert_scope_below(self.current_scope_id, &collector.scope_ids, flags)
|
||||
}
|
||||
|
||||
/// Insert a scope into scope tree below a `Vec` of statements.
|
||||
///
|
||||
/// Statements must be in current scope.
|
||||
/// New scope is created as child of current scope.
|
||||
/// All child scopes of the statement are reassigned to be children of the new scope.
|
||||
///
|
||||
/// `flags` provided are amended to inherit from parent scope's flags.
|
||||
pub fn insert_scope_below_statements(
|
||||
&mut self,
|
||||
stmts: &ArenaVec<Statement>,
|
||||
flags: ScopeFlags,
|
||||
) -> ScopeId {
|
||||
let mut collector = ChildScopeCollector::new();
|
||||
collector.visit_statements(stmts);
|
||||
self.insert_scope_below(self.current_scope_id, &collector.scope_ids, flags)
|
||||
}
|
||||
|
||||
fn insert_scope_below(
|
||||
&mut self,
|
||||
scope_id: ScopeId,
|
||||
child_scope_ids: &[ScopeId],
|
||||
flags: ScopeFlags,
|
||||
) -> ScopeId {
|
||||
// Create new scope as child of parent
|
||||
let new_scope_id = self.create_child_scope(scope_id, flags);
|
||||
|
||||
// Set scopes as children of new scope instead
|
||||
for &child_id in child_scope_ids {
|
||||
self.scoping.set_scope_parent_id(child_id, Some(new_scope_id));
|
||||
}
|
||||
|
||||
new_scope_id
|
||||
}
|
||||
|
||||
/// Insert a scope between a parent and a child scope.
|
||||
///
|
||||
/// For example, given the following scopes
|
||||
/// ```ts
|
||||
/// parentScope1: {
|
||||
/// childScope: { }
|
||||
/// childScope2: { }
|
||||
/// }
|
||||
/// ```
|
||||
/// and calling this function with `parentScope1` and `childScope`,
|
||||
/// the resulting scopes will be:
|
||||
/// ```ts
|
||||
/// parentScope1: {
|
||||
/// newScope: {
|
||||
/// childScope: { }
|
||||
/// }
|
||||
/// childScope2: { }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn insert_scope_between(
|
||||
&mut self,
|
||||
parent_id: ScopeId,
|
||||
child_id: ScopeId,
|
||||
flags: ScopeFlags,
|
||||
) -> ScopeId {
|
||||
let scope_id = self.create_child_scope(parent_id, flags);
|
||||
|
||||
debug_assert_eq!(
|
||||
self.scoping.scope_parent_id(child_id),
|
||||
Some(parent_id),
|
||||
"Child scope must be a child of parent scope"
|
||||
);
|
||||
|
||||
self.scoping.set_scope_parent_id(child_id, Some(scope_id));
|
||||
scope_id
|
||||
}
|
||||
|
||||
/// Remove scope for an expression from the scope chain.
|
||||
///
|
||||
/// Delete the scope and set parent of its child scopes to its parent scope.
|
||||
/// e.g.:
|
||||
/// * Starting scopes parentage `A -> B`, `B -> C`, `B -> D`.
|
||||
/// * Remove scope `B` from chain.
|
||||
/// * End result: scopes `A -> C`, `A -> D`.
|
||||
///
|
||||
/// Use this when removing an expression which owns a scope, without removing its children.
|
||||
/// For example when unwrapping `(() => foo)()` to just `foo`.
|
||||
/// `foo` here could be an expression which itself contains scopes.
|
||||
pub fn remove_scope_for_expression(&mut self, scope_id: ScopeId, expr: &Expression) {
|
||||
let mut collector = ChildScopeCollector::new();
|
||||
collector.visit_expression(expr);
|
||||
|
||||
let child_ids = collector.scope_ids;
|
||||
if !child_ids.is_empty() {
|
||||
let parent_id = self.scoping.scope_parent_id(scope_id);
|
||||
for child_id in child_ids {
|
||||
self.scoping.set_scope_parent_id(child_id, parent_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add binding to `ScopeTree` and `SymbolTable`.
|
||||
#[inline]
|
||||
pub(crate) fn add_binding(
|
||||
&mut self,
|
||||
name: Ident<'_>,
|
||||
scope_id: ScopeId,
|
||||
flags: SymbolFlags,
|
||||
) -> SymbolId {
|
||||
let symbol_id = self.scoping.create_symbol(SPAN, name, flags, scope_id, NodeId::DUMMY);
|
||||
self.scoping.add_binding(scope_id, name, symbol_id);
|
||||
|
||||
symbol_id
|
||||
}
|
||||
|
||||
/// Generate binding.
|
||||
///
|
||||
/// Creates a symbol with the provided name and flags and adds it to the specified scope.
|
||||
pub fn generate_binding(
|
||||
&mut self,
|
||||
name: Ident<'a>,
|
||||
scope_id: ScopeId,
|
||||
flags: SymbolFlags,
|
||||
) -> BoundIdentifier<'a> {
|
||||
let symbol_id = self.add_binding(name, scope_id, flags);
|
||||
BoundIdentifier::new(name, symbol_id)
|
||||
}
|
||||
|
||||
/// Generate binding in current scope.
|
||||
///
|
||||
/// Creates a symbol with the provided name and flags and adds it to the current scope.
|
||||
pub fn generate_binding_in_current_scope(
|
||||
&mut self,
|
||||
name: Ident<'a>,
|
||||
flags: SymbolFlags,
|
||||
) -> BoundIdentifier<'a> {
|
||||
self.generate_binding(name, self.current_scope_id, flags)
|
||||
}
|
||||
|
||||
/// Generate UID var name.
|
||||
///
|
||||
/// Finds a unique variable name which does clash with any other variables used in the program.
|
||||
///
|
||||
/// Caller must ensure `name` is a valid JS identifier, after a `_` is prepended on start.
|
||||
/// The fact that a `_` will be prepended on start means providing an empty string or a string
|
||||
/// starting with a digit (0-9) is fine.
|
||||
///
|
||||
/// See comments on `UidGenerator` for further details.
|
||||
pub fn generate_uid_name(&mut self, name: &str, allocator: &'a Allocator) -> Ident<'a> {
|
||||
// If `uid_generator` is not already populated, initialize it
|
||||
let uid_generator =
|
||||
self.uid_generator.get_or_insert_with(|| UidGenerator::new(&self.scoping, allocator));
|
||||
// Generate unique name
|
||||
uid_generator.create(name)
|
||||
}
|
||||
|
||||
/// Create a reference bound to a `SymbolId`
|
||||
pub fn create_bound_reference(
|
||||
&mut self,
|
||||
symbol_id: SymbolId,
|
||||
flags: ReferenceFlags,
|
||||
) -> ReferenceId {
|
||||
let reference =
|
||||
Reference::new_with_symbol_id(NodeId::DUMMY, symbol_id, self.current_scope_id, flags);
|
||||
let reference_id = self.scoping.create_reference(reference);
|
||||
self.scoping.add_resolved_reference(symbol_id, reference_id);
|
||||
reference_id
|
||||
}
|
||||
|
||||
/// Create an unbound reference
|
||||
pub fn create_unbound_reference(
|
||||
&mut self,
|
||||
name: Ident<'_>,
|
||||
flags: ReferenceFlags,
|
||||
) -> ReferenceId {
|
||||
let reference = Reference::new(NodeId::DUMMY, self.current_scope_id, flags);
|
||||
let reference_id = self.scoping.create_reference(reference);
|
||||
self.scoping.add_root_unresolved_reference(name, reference_id);
|
||||
reference_id
|
||||
}
|
||||
|
||||
/// Create a reference optionally bound to a `SymbolId`.
|
||||
///
|
||||
/// If you know if there's a `SymbolId` or not, prefer `TraverseCtx::create_bound_reference`
|
||||
/// or `TraverseCtx::create_unbound_reference`.
|
||||
pub fn create_reference(
|
||||
&mut self,
|
||||
name: Ident<'_>,
|
||||
symbol_id: Option<SymbolId>,
|
||||
flags: ReferenceFlags,
|
||||
) -> ReferenceId {
|
||||
if let Some(symbol_id) = symbol_id {
|
||||
self.create_bound_reference(symbol_id, flags)
|
||||
} else {
|
||||
self.create_unbound_reference(name, flags)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create reference in current scope, looking up binding for `name`
|
||||
pub fn create_reference_in_current_scope(
|
||||
&mut self,
|
||||
name: Ident<'_>,
|
||||
flags: ReferenceFlags,
|
||||
) -> ReferenceId {
|
||||
let symbol_id = self.scoping.find_binding(self.current_scope_id, name);
|
||||
self.create_reference(name, symbol_id, flags)
|
||||
}
|
||||
|
||||
/// Delete a reference.
|
||||
///
|
||||
/// Provided `name` must match `reference_id`.
|
||||
pub fn delete_reference(&mut self, reference_id: ReferenceId, name: Ident<'_>) {
|
||||
let symbol_id = self.scoping.get_reference(reference_id).symbol_id();
|
||||
if let Some(symbol_id) = symbol_id {
|
||||
self.scoping.delete_resolved_reference(symbol_id, reference_id);
|
||||
} else {
|
||||
self.scoping.delete_root_unresolved_reference(name, reference_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete reference for an `IdentifierReference`.
|
||||
pub fn delete_reference_for_identifier(&mut self, ident: &IdentifierReference) {
|
||||
self.delete_reference(ident.reference_id(), ident.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Methods used internally within crate
|
||||
impl TraverseScoping<'_> {
|
||||
/// Create new `TraverseScoping`
|
||||
pub(super) fn new(scoping: Scoping) -> Self {
|
||||
Self {
|
||||
scoping,
|
||||
uid_generator: None,
|
||||
// Dummy values. Both immediately overwritten in `walk_program`.
|
||||
current_scope_id: ScopeId::new(0),
|
||||
current_hoist_scope_id: ScopeId::new(0),
|
||||
current_block_scope_id: ScopeId::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set current scope ID
|
||||
#[inline]
|
||||
pub(crate) fn set_current_scope_id(&mut self, scope_id: ScopeId) {
|
||||
self.current_scope_id = scope_id;
|
||||
}
|
||||
|
||||
/// Set current hoist scope ID
|
||||
#[inline]
|
||||
pub(crate) fn set_current_hoist_scope_id(&mut self, scope_id: ScopeId) {
|
||||
self.current_hoist_scope_id = scope_id;
|
||||
}
|
||||
|
||||
/// Set current block scope ID
|
||||
#[inline]
|
||||
pub(crate) fn set_current_block_scope_id(&mut self, scope_id: ScopeId) {
|
||||
self.current_block_scope_id = scope_id;
|
||||
}
|
||||
|
||||
pub fn delete_typescript_bindings(&mut self) {
|
||||
self.scoping.delete_typescript_bindings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
use std::str;
|
||||
|
||||
use itoa::Buffer as ItoaBuffer;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use oxc_allocator::{Allocator, StringBuilder as ArenaStringBuilder};
|
||||
use oxc_semantic::Scoping;
|
||||
use oxc_str::Ident;
|
||||
|
||||
/// Unique identifier generator.
|
||||
///
|
||||
/// When initialized with [`UidGenerator::new`], creates a catalog of all symbols and unresolved references
|
||||
/// in the AST which begin with `_`.
|
||||
///
|
||||
/// [`UidGenerator::create`] uses that catalog to generate a unique identifier which does not clash with
|
||||
/// any existing name.
|
||||
///
|
||||
/// Such UIDs are based on the base name provided. They start with `_` and end with digits if required to
|
||||
/// maintain uniqueness. e.g. given base name of `foo`, UIDs will be `_foo`, `_foo2`, `_foo3` etc.
|
||||
///
|
||||
/// Roughly based on Babel's `scope.generateUid` logic, but with some differences (see below).
|
||||
/// <https://github.com/babel/babel/blob/3b1a3c0be9df65140260a316c1a21adcf948645d/packages/babel-traverse/src/scope/index.ts#L501-L523>
|
||||
///
|
||||
/// # Algorithm
|
||||
///
|
||||
/// UIDs are generated in series for each "base" name.
|
||||
/// "Base" name is the provided name with `_`s trimmed from the start, and digits trimmed from the end.
|
||||
///
|
||||
/// During cataloging of existing symbols, for each base name it's recorded:
|
||||
///
|
||||
/// 1. Largest number of leading `_`s.
|
||||
/// 2. Largest numeric postfix for that base name.
|
||||
///
|
||||
/// UIDs are generated for that base name with that number of leading underscores, and with ascending
|
||||
/// numeric postfix.
|
||||
///
|
||||
/// | Existing symbols | Generated UIDs |
|
||||
/// |------------------|---------------------------------|
|
||||
/// | (none) | `_foo`, `_foo2`, `_foo3` |
|
||||
/// | `_foo` | `_foo2`, `_foo3`, `_foo4` |
|
||||
/// | `_foo3` | `_foo4`, `_foo5`, `_foo6` |
|
||||
/// | `__foo` | `__foo2`, `__foo3`, `__foo4` |
|
||||
/// | `___foo5` | `___foo6`, `___foo7`, `___foo8` |
|
||||
/// | `_foo8`, `__foo` | `__foo2`, `__foo3`, `__foo4` |
|
||||
///
|
||||
/// This algorithm requires at most 1 hashmap lookup and 1 hashmap insert per UID generated.
|
||||
///
|
||||
/// # Differences from Babel
|
||||
///
|
||||
/// This implementation aims to replicate Babel's behavior, but differs from Babel
|
||||
/// in the following ways:
|
||||
///
|
||||
/// 1. Does not check that name provided as "base" for the UID is a valid JS identifier name.
|
||||
/// In most cases, we're creating a UID based on an existing variable name, in which case
|
||||
/// this check is redundant.
|
||||
/// Caller must ensure `name` is a valid JS identifier, after a `_` is prepended on start.
|
||||
/// The fact that a `_` will be prepended on start means providing an empty string or a string
|
||||
/// starting with a digit (0-9) is fine.
|
||||
///
|
||||
/// 2. Does not convert to camel case.
|
||||
/// This seems unimportant.
|
||||
///
|
||||
/// 3. Does not check var name against list of globals or "contextVariables"
|
||||
/// (which Babel does in `hasBinding`).
|
||||
/// No globals or "contextVariables" start with `_` anyway, so no need for this check.
|
||||
///
|
||||
/// 4. Does not check this name is unique if used as a named statement label,
|
||||
/// only that it's unique as an identifier.
|
||||
///
|
||||
/// 5. Uses a slightly different algorithm for generating names (see above).
|
||||
/// The resulting UIDs are similar enough to Babel's algorithm to fail only 1 of Babel's tests.
|
||||
///
|
||||
/// # Potential improvements
|
||||
///
|
||||
/// TODO(improve-on-babel):
|
||||
///
|
||||
/// UID generation is fairly expensive, because of the amount of string hashing required.
|
||||
///
|
||||
/// [`UidGenerator::new`] iterates through every binding and unresolved reference in the entire AST,
|
||||
/// and builds a hashmap of symbols which could clash with UIDs.
|
||||
/// Once that's built, [`UidGenerator::create`] has to do at a hashmap lookup when generating each UID.
|
||||
/// Hashing strings is a fairly expensive operation.
|
||||
///
|
||||
/// We could improve this in one of 3 ways:
|
||||
///
|
||||
/// ## 1. Build the hashmap in `SemanticBuilder`
|
||||
///
|
||||
/// Instead of iterating through all symbols again here.
|
||||
///
|
||||
/// ## 2. Use a simpler algorithm
|
||||
///
|
||||
/// * During initial semantic pass, check for any existing identifiers starting with `_`.
|
||||
/// * Calculate what is the highest postfix number on `_...` identifiers (e.g. `_foo1`, `_bar8`).
|
||||
/// * Store that highest number in a counter which is global across the whole program.
|
||||
/// * When creating a UID, increment the counter, and make the UID `_<name><counter>`.
|
||||
///
|
||||
/// i.e. if source contains identifiers `_foo1` and `_bar15`, create UIDs named `_qux16`,
|
||||
/// `_temp17` etc. They'll all be unique within the program.
|
||||
///
|
||||
/// Minimal cost in semantic, and generating UIDs extremely cheap.
|
||||
///
|
||||
/// The resulting UIDs would still be fairly readable.
|
||||
///
|
||||
/// This is a different method from Babel, and unfortunately produces UID names
|
||||
/// which differ from Babel for some of its test cases.
|
||||
///
|
||||
/// ## 3. Even simpler algorithm, but produces hard-to-read code
|
||||
///
|
||||
/// If output is being minified anyway, use a method which produces less debuggable output,
|
||||
/// but is even simpler:
|
||||
///
|
||||
/// * During initial semantic pass, check for any existing identifiers starting with `_`.
|
||||
/// * Find the highest number of leading `_`s for any existing symbol.
|
||||
/// * Generate UIDs with a counter starting at 0, prefixed with number of `_`s one greater than
|
||||
/// what was found in AST.
|
||||
///
|
||||
/// i.e. if source contains identifiers `_foo` and `__bar`, create UIDs names `___0`, `___1`,
|
||||
/// `___2` etc. They'll all be unique within the program.
|
||||
pub struct UidGenerator<'a> {
|
||||
names: FxHashMap<&'a str, UidName>,
|
||||
allocator: &'a Allocator,
|
||||
}
|
||||
|
||||
/// Details of next UID for a base name.
|
||||
//
|
||||
// `#[repr(align(8))]` on 64-bit platforms so can fit in a single register.
|
||||
#[cfg_attr(target_pointer_width = "64", repr(align(8)))]
|
||||
#[derive(Clone, Copy)]
|
||||
struct UidName {
|
||||
/// Digits appended to end of name.
|
||||
/// When generating a UID, increment this field and use that as the postfix.
|
||||
/// This field is never 0, so postfix will be at least 2.
|
||||
postfix: u32,
|
||||
/// Number of underscores to prefix name with.
|
||||
underscore_count: u32,
|
||||
}
|
||||
|
||||
impl<'a> UidGenerator<'a> {
|
||||
/// Create [`UidGenerator`].
|
||||
pub(super) fn new(scoping: &Scoping, allocator: &'a Allocator) -> Self {
|
||||
let mut generator = Self { names: FxHashMap::default(), allocator };
|
||||
|
||||
for name in scoping.symbol_names() {
|
||||
generator.add(name);
|
||||
}
|
||||
for name in scoping.root_unresolved_references().keys() {
|
||||
generator.add(name.as_str());
|
||||
}
|
||||
|
||||
generator
|
||||
}
|
||||
|
||||
/// Add a record to [`UidGenerator`].
|
||||
fn add(&mut self, name: &str) {
|
||||
// If `name` does not start with `_`, exit
|
||||
if name.as_bytes().first() != Some(&b'_') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trim off underscores from start of `name`
|
||||
let original_len = name.len();
|
||||
// SAFETY: We just check first byte of `name` is `_`
|
||||
let name = unsafe { name.get_unchecked(1..) };
|
||||
let mut name = name.trim_start_matches('_');
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
let underscore_count = (original_len - name.len()) as u32;
|
||||
let mut uid_name = UidName { underscore_count, postfix: 1 };
|
||||
|
||||
// Find digits on end of `name`
|
||||
let last_non_digit_index = name.as_bytes().iter().rposition(|&b| !b.is_ascii_digit());
|
||||
let parts = match last_non_digit_index {
|
||||
Some(last_non_digit_index) => {
|
||||
if last_non_digit_index == name.len() - 1 {
|
||||
// No digits on end
|
||||
None
|
||||
} else {
|
||||
// Name ends with digits
|
||||
let digit_index = last_non_digit_index + 1;
|
||||
debug_assert!(name.as_bytes().get(digit_index).is_some_and(u8::is_ascii_digit));
|
||||
// SAFETY: There's an ASCII digit at `digit_index`, so slicing `name` at that index
|
||||
// is guaranteed to yield 2 valid UTF-8 strings. `digit_index` cannot be out of bounds.
|
||||
unsafe {
|
||||
let without_digits = name.get_unchecked(..digit_index);
|
||||
let digits = name.get_unchecked(digit_index..);
|
||||
Some((without_digits, digits))
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if name.is_empty() {
|
||||
// Name consists purely of `_`s e.g. `_` or `___`
|
||||
None
|
||||
} else {
|
||||
// Name consists of `_`s followed by digits e.g. `_123`
|
||||
Some(("", name))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((without_digits, digits)) = parts {
|
||||
const U32_MAX_LEN: usize = "4294967295".len(); // 4294967295 = u32::MAX
|
||||
// SAFETY: `digits` cannot be empty
|
||||
let first_digit = unsafe { *digits.as_bytes().get_unchecked(0) };
|
||||
if first_digit == b'0' || digits.len() > U32_MAX_LEN {
|
||||
// We don't create UIDs with postfix starting with 0, or greater than `u32::MAX`,
|
||||
// so can ignore this - can't clash
|
||||
return;
|
||||
}
|
||||
if let Ok(n) = digits.parse::<u32>() {
|
||||
if n == 1 {
|
||||
// We don't create UIDs with postfix of 1, so can ignore this - can't clash
|
||||
return;
|
||||
}
|
||||
name = without_digits;
|
||||
uid_name.postfix = n;
|
||||
} else {
|
||||
// Digits represent a number greater than `u32::MAX`.
|
||||
// We don't create UIDs with postfix over `u32::MAX` so can ignore this - can't clash.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Unfortunately can't use `Entry` API here because `name` doesn't have required lifetime `'a`,
|
||||
// because it comes from `Semantic`'s arena, not the AST arena
|
||||
if let Some(existing_uid_name) = self.names.get_mut(name) {
|
||||
if uid_name.underscore_count > existing_uid_name.underscore_count
|
||||
|| (uid_name.underscore_count == existing_uid_name.underscore_count
|
||||
&& uid_name.postfix > existing_uid_name.postfix)
|
||||
{
|
||||
*existing_uid_name = uid_name;
|
||||
}
|
||||
} else {
|
||||
let name = self.allocator.alloc_str(name);
|
||||
self.names.insert(name, uid_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a unique identifier.
|
||||
///
|
||||
/// The UID returned will be added to the list of used identifiers, so this method will never
|
||||
/// return the same UID twice.
|
||||
///
|
||||
/// Caller must ensure `name` is a valid JS identifier, after a `_` is prepended on start.
|
||||
/// The fact that a `_` will be prepended on start means providing an empty string or a string
|
||||
/// starting with a digit (0-9) is fine.
|
||||
///
|
||||
/// Please see docs for [`UidGenerator`] for further info.
|
||||
pub(super) fn create(&mut self, name: &str) -> Ident<'a> {
|
||||
// Get the base name, with `_`s trimmed from start, and digits trimmed from end.
|
||||
// i.e. `__foo123` -> `foo`.
|
||||
// Equivalent to `name.trim_start_matches('_').trim_end_matches(|c: char| c.is_ascii_digit())`
|
||||
// but more efficient as operates on bytes not chars
|
||||
let mut bytes = name.as_bytes();
|
||||
while bytes.first() == Some(&b'_') {
|
||||
bytes = &bytes[1..];
|
||||
}
|
||||
while matches!(bytes.last(), Some(b) if b.is_ascii_digit()) {
|
||||
bytes = &bytes[0..bytes.len() - 1];
|
||||
}
|
||||
// SAFETY: We started with a valid UTF8 `&str` and have only trimmed off ASCII characters,
|
||||
// so remainder must still be valid UTF8
|
||||
let base = unsafe { str::from_utf8_unchecked(bytes) };
|
||||
|
||||
// Generate UID.
|
||||
// Unfortunately can't use `Entry` API here as `name` doesn't have required lifetime `'a`.
|
||||
if let Some(uid_name) = self.names.get_mut(base) {
|
||||
// AST contains identifier(s) with this base already.
|
||||
// Get next postfix.
|
||||
if uid_name.postfix < u32::MAX {
|
||||
// Increment `postfix`
|
||||
uid_name.postfix += 1;
|
||||
} else {
|
||||
// Identifier `_<base>4294967295` was already used.
|
||||
// Can't increment `postfix` as it would wrap around, so increment `underscore_count` instead.
|
||||
// It shouldn't be possible for `underscore_count` to be `u32::MAX` too, because that
|
||||
// would require an identifier comprising `u32::MAX` x underscores in source text.
|
||||
// That's theoretically possible, but source text is limited to `u32::MAX` bytes,
|
||||
// so it'd be the entirety of the source text. Therefore `postfix` would be 1.
|
||||
uid_name.underscore_count += 1;
|
||||
uid_name.postfix = 2;
|
||||
}
|
||||
|
||||
// Format UID `_<base><postfix>`.
|
||||
// If `underscore_count > 1`, add further underscores to the start.
|
||||
let mut buffer = ItoaBuffer::new();
|
||||
let digits = buffer.format(uid_name.postfix);
|
||||
|
||||
if uid_name.underscore_count == 1 {
|
||||
Ident::from_strs_array_in(["_", base, digits], self.allocator)
|
||||
} else {
|
||||
let mut uid = ArenaStringBuilder::with_capacity_in(
|
||||
uid_name.underscore_count as usize + base.len() + digits.len(),
|
||||
self.allocator,
|
||||
);
|
||||
uid.push_ascii_byte_repeat(b'_', uid_name.underscore_count as usize);
|
||||
uid.push_str(base);
|
||||
uid.push_str(digits);
|
||||
Ident::from(uid)
|
||||
}
|
||||
} else {
|
||||
let uid = Ident::from_strs_array_in(["_", base], self.allocator);
|
||||
// SAFETY: String starts with `_`, so trimming off that byte leaves a valid UTF-8 string
|
||||
let base = unsafe { uid.as_str().get_unchecked(1..) };
|
||||
self.names.insert(base, UidName { underscore_count: 1, postfix: 1 });
|
||||
uid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn uids() {
|
||||
// (&[ initial, ... ], &[ (name, expected_uid), ... ])
|
||||
#[expect(clippy::type_complexity)]
|
||||
let cases: &[(&[&str], &[(&str, &str)])] = &[
|
||||
(&[], &[("foo", "_foo"), ("foo", "_foo2"), ("foo", "_foo3")]),
|
||||
(
|
||||
&["foo", "foo0", "foo1", "foo2", "foo10", "_bar"],
|
||||
&[("foo", "_foo"), ("foo", "_foo2"), ("foo", "_foo3")],
|
||||
),
|
||||
(
|
||||
&["_foo0", "_foo1", "__foo0", "____foo1", "_foo01", "_foo012345", "_foo000000"],
|
||||
&[("foo", "_foo"), ("foo", "_foo2"), ("foo", "_foo3")],
|
||||
),
|
||||
(&[], &[("_foo", "_foo"), ("__foo", "_foo2"), ("_____foo", "_foo3")]),
|
||||
(&[], &[("_foo123", "_foo"), ("__foo456", "_foo2"), ("_____foo789", "_foo3")]),
|
||||
(&["_foo"], &[("foo", "_foo2"), ("foo", "_foo3"), ("foo", "_foo4")]),
|
||||
(&["_foo3"], &[("foo", "_foo4"), ("foo", "_foo5"), ("foo", "_foo6")]),
|
||||
(&["__foo"], &[("foo", "__foo2"), ("foo", "__foo3"), ("foo", "__foo4")]),
|
||||
(&["__foo8"], &[("foo", "__foo9"), ("foo", "__foo10"), ("foo", "__foo11")]),
|
||||
(&["_foo999", "____foo"], &[("foo", "____foo2"), ("foo", "____foo3"), ("foo", "____foo4")]),
|
||||
(
|
||||
&["_foo4294967293"],
|
||||
&[
|
||||
("foo", "_foo4294967294"),
|
||||
("foo", "_foo4294967295"),
|
||||
("foo", "__foo2"),
|
||||
("foo", "__foo3"),
|
||||
],
|
||||
),
|
||||
(
|
||||
&["___foo4294967293"],
|
||||
&[
|
||||
("foo", "___foo4294967294"),
|
||||
("foo", "___foo4294967295"),
|
||||
("foo", "____foo2"),
|
||||
("foo", "____foo3"),
|
||||
],
|
||||
),
|
||||
(&[], &[("_", "_"), ("_", "_2"), ("_", "_3")]),
|
||||
(
|
||||
&["_0", "_1", "__0", "____1", "_01", "_012345", "_000000"],
|
||||
&[("_", "_"), ("_", "_2"), ("_", "_3")],
|
||||
),
|
||||
(&[], &[("___", "_"), ("_____", "_2"), ("_____", "_3")]),
|
||||
(&["_"], &[("_", "_2"), ("_", "_3"), ("_", "_4")]),
|
||||
(&["_4"], &[("_", "_5"), ("_", "_6"), ("_", "_7")]),
|
||||
(&["___"], &[("_", "___2"), ("_", "___3"), ("_", "___4")]),
|
||||
(&["___99"], &[("_", "___100"), ("_", "___101"), ("_", "___102")]),
|
||||
(&["_"], &[("_123", "_2"), ("__456", "_3"), ("___789", "_4")]),
|
||||
];
|
||||
|
||||
let allocator = Allocator::default();
|
||||
for &(used_names, created) in cases {
|
||||
let mut generator = UidGenerator { names: FxHashMap::default(), allocator: &allocator };
|
||||
for &used_name in used_names {
|
||||
generator.add(used_name);
|
||||
}
|
||||
|
||||
for &(name, uid) in created {
|
||||
assert_eq!(generator.create(name), uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//! Generator for minifier-local traverse runtime.
|
||||
//!
|
||||
//! Generates 2 files in `oxc_minifier` crate:
|
||||
//! * `traverse.rs` - `MinifierTraverse` trait with `enter_*` / `exit_*` methods.
|
||||
//! * `walk.rs` - Unsafe `walk_*` functions for AST traversal.
|
||||
|
||||
use super::traverse::{self, TraverseTraitConfig};
|
||||
use crate::{
|
||||
Codegen, Generator, MINIFIER_CRATE_PATH,
|
||||
output::{Output, output_path},
|
||||
schema::Schema,
|
||||
};
|
||||
|
||||
use super::define_generator;
|
||||
|
||||
pub struct MinifierTraverseGenerator;
|
||||
|
||||
define_generator!(MinifierTraverseGenerator);
|
||||
|
||||
impl Generator for MinifierTraverseGenerator {
|
||||
fn generate_many(&self, schema: &Schema, _codegen: &Codegen) -> Vec<Output> {
|
||||
let config = TraverseTraitConfig::minifier();
|
||||
vec![
|
||||
Output::Rust {
|
||||
path: output_path(MINIFIER_CRATE_PATH, "traverse.rs"),
|
||||
tokens: traverse::generate_traverse_trait(schema, &config),
|
||||
},
|
||||
Output::Rust {
|
||||
path: output_path(MINIFIER_CRATE_PATH, "walk.rs"),
|
||||
tokens: traverse::generate_walk_minifier(schema),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ mod ast_kind;
|
||||
mod estree_visit;
|
||||
mod formatter;
|
||||
mod get_id;
|
||||
mod minifier_traverse;
|
||||
#[cfg(feature = "generate-js")]
|
||||
mod oxlint_envs;
|
||||
#[cfg(feature = "generate-js")]
|
||||
@@ -31,6 +32,7 @@ pub use ast_kind::AstKindGenerator;
|
||||
pub use estree_visit::ESTreeVisitGenerator;
|
||||
pub use formatter::{FormatterAstNodesGenerator, FormatterFormatGenerator};
|
||||
pub use get_id::GetIdGenerator;
|
||||
pub use minifier_traverse::MinifierTraverseGenerator;
|
||||
#[cfg(feature = "generate-js")]
|
||||
pub use oxlint_envs::OxlintEnvsGenerator;
|
||||
#[cfg(feature = "generate-js")]
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
//! * `walk.rs` - Unsafe `walk_*` functions for AST traversal.
|
||||
//! * `ancestor.rs` - Ancestor tracking types and offset constants.
|
||||
|
||||
mod ancestor;
|
||||
mod walk;
|
||||
pub(super) mod ancestor;
|
||||
pub(super) mod walk;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
@@ -27,14 +27,15 @@ define_generator!(TraverseGenerator);
|
||||
|
||||
impl Generator for TraverseGenerator {
|
||||
fn generate_many(&self, schema: &Schema, _codegen: &Codegen) -> Vec<Output> {
|
||||
let config = TraverseTraitConfig::traverse();
|
||||
vec![
|
||||
Output::Rust {
|
||||
path: output_path(TRAVERSE_CRATE_PATH, "traverse.rs"),
|
||||
tokens: generate_traverse_trait(schema),
|
||||
tokens: generate_traverse_trait(schema, &config),
|
||||
},
|
||||
Output::Rust {
|
||||
path: output_path(TRAVERSE_CRATE_PATH, "walk.rs"),
|
||||
tokens: walk::generate_walk(schema),
|
||||
tokens: generate_walk_traverse(schema),
|
||||
},
|
||||
Output::Rust {
|
||||
path: output_path(TRAVERSE_CRATE_PATH, "ancestor.rs"),
|
||||
@@ -44,8 +45,50 @@ impl Generator for TraverseGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn generate_walk_traverse(schema: &Schema) -> TokenStream {
|
||||
walk::generate_walk(schema, &walk::WalkConfig::traverse())
|
||||
}
|
||||
|
||||
pub(super) fn generate_walk_minifier(schema: &Schema) -> TokenStream {
|
||||
walk::generate_walk(schema, &walk::WalkConfig::minifier())
|
||||
}
|
||||
|
||||
pub(super) struct TraverseTraitConfig {
|
||||
pub trait_ident: syn::Ident,
|
||||
pub trait_generics: TokenStream,
|
||||
pub ctx_ty: TokenStream,
|
||||
pub ctx_use: TokenStream,
|
||||
}
|
||||
|
||||
impl TraverseTraitConfig {
|
||||
pub fn traverse() -> Self {
|
||||
Self {
|
||||
trait_ident: quote::format_ident!("Traverse"),
|
||||
trait_generics: quote! { <'a, State> },
|
||||
ctx_ty: quote! { TraverseCtx<'a, State> },
|
||||
ctx_use: quote! { use crate::TraverseCtx; },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn minifier() -> Self {
|
||||
Self {
|
||||
trait_ident: quote::format_ident!("Traverse"),
|
||||
trait_generics: quote! { <'a> },
|
||||
ctx_ty: quote! { TraverseCtx<'a> },
|
||||
ctx_use: quote! { use crate::TraverseCtx; },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate `Traverse` trait with `enter_*` / `exit_*` methods for each visited type.
|
||||
fn generate_traverse_trait(schema: &Schema) -> TokenStream {
|
||||
pub(super) fn generate_traverse_trait(
|
||||
schema: &Schema,
|
||||
config: &TraverseTraitConfig,
|
||||
) -> TokenStream {
|
||||
let ctx_ty = &config.ctx_ty;
|
||||
let trait_ident = &config.trait_ident;
|
||||
let trait_generics = &config.trait_generics;
|
||||
let ctx_use = &config.ctx_use;
|
||||
let mut methods = quote!();
|
||||
|
||||
for type_def in &schema.types {
|
||||
@@ -66,9 +109,9 @@ fn generate_traverse_trait(schema: &Schema) -> TokenStream {
|
||||
methods.extend(quote! {
|
||||
///@@line_break
|
||||
#[inline]
|
||||
fn #enter_ident(&mut self, node: &mut #ty, ctx: &mut TraverseCtx<'a, State>) {}
|
||||
fn #enter_ident(&mut self, node: &mut #ty, ctx: &mut #ctx_ty) {}
|
||||
#[inline]
|
||||
fn #exit_ident(&mut self, node: &mut #ty, ctx: &mut TraverseCtx<'a, State>) {}
|
||||
fn #exit_ident(&mut self, node: &mut #ty, ctx: &mut #ctx_ty) {}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,9 +119,9 @@ fn generate_traverse_trait(schema: &Schema) -> TokenStream {
|
||||
methods.extend(quote! {
|
||||
///@@line_break
|
||||
#[inline]
|
||||
fn enter_statements(&mut self, node: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a, State>) {}
|
||||
fn enter_statements(&mut self, node: &mut Vec<'a, Statement<'a>>, ctx: &mut #ctx_ty) {}
|
||||
#[inline]
|
||||
fn exit_statements(&mut self, node: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a, State>) {}
|
||||
fn exit_statements(&mut self, node: &mut Vec<'a, Statement<'a>>, ctx: &mut #ctx_ty) {}
|
||||
});
|
||||
|
||||
quote! {
|
||||
@@ -86,11 +129,11 @@ fn generate_traverse_trait(schema: &Schema) -> TokenStream {
|
||||
use oxc_ast::ast::*;
|
||||
|
||||
///@@line_break
|
||||
use crate::TraverseCtx;
|
||||
#ctx_use
|
||||
|
||||
///@@line_break
|
||||
#[expect(unused_variables)]
|
||||
pub trait Traverse<'a, State> {
|
||||
pub trait #trait_ident #trait_generics {
|
||||
#methods
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,45 @@ use super::{
|
||||
traverse_snake_name,
|
||||
};
|
||||
|
||||
pub(super) struct WalkConfig {
|
||||
pub use_code: TokenStream,
|
||||
pub trait_bound: TokenStream,
|
||||
pub ctx_ty: TokenStream,
|
||||
pub has_state: bool,
|
||||
}
|
||||
|
||||
impl WalkConfig {
|
||||
pub fn traverse() -> Self {
|
||||
Self {
|
||||
use_code: quote! { use crate::{ancestor::{self, AncestorType}, Ancestor, Traverse, TraverseCtx}; },
|
||||
trait_bound: quote! { Tr: Traverse<'a, State> },
|
||||
ctx_ty: quote! { TraverseCtx<'a, State> },
|
||||
has_state: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn minifier() -> Self {
|
||||
Self {
|
||||
use_code: quote! {
|
||||
use crate::{
|
||||
generated::ancestor::{self, Ancestor, AncestorType},
|
||||
Traverse,
|
||||
TraverseCtx,
|
||||
};
|
||||
},
|
||||
trait_bound: quote! { Tr: Traverse<'a> },
|
||||
ctx_ty: quote! { TraverseCtx<'a> },
|
||||
has_state: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate all walk functions.
|
||||
pub fn generate_walk(schema: &Schema) -> TokenStream {
|
||||
pub(super) fn generate_walk(schema: &Schema, config: &WalkConfig) -> TokenStream {
|
||||
let mut walk_methods = quote!();
|
||||
let use_code = &config.use_code;
|
||||
let ctx_ty = &config.ctx_ty;
|
||||
let walk_generics = walk_generics_tokens(config);
|
||||
|
||||
for type_def in &schema.types {
|
||||
if !is_ast_type_with_visitor(type_def, schema) {
|
||||
@@ -30,10 +66,10 @@ pub fn generate_walk(schema: &Schema) -> TokenStream {
|
||||
}
|
||||
match type_def {
|
||||
TypeDef::Struct(struct_def) => {
|
||||
walk_methods.extend(generate_walk_for_struct(struct_def, schema));
|
||||
walk_methods.extend(generate_walk_for_struct(struct_def, schema, config));
|
||||
}
|
||||
TypeDef::Enum(enum_def) => {
|
||||
walk_methods.extend(generate_walk_for_enum(enum_def, schema));
|
||||
walk_methods.extend(generate_walk_for_enum(enum_def, schema, config));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -59,7 +95,7 @@ pub fn generate_walk(schema: &Schema) -> TokenStream {
|
||||
use oxc_syntax::scope::ScopeId;
|
||||
|
||||
///@@line_break
|
||||
use crate::{ancestor::{self, AncestorType}, Ancestor, Traverse, TraverseCtx};
|
||||
#use_code
|
||||
|
||||
///@@line_break
|
||||
/// Walk AST with `Traverse` impl.
|
||||
@@ -69,10 +105,10 @@ pub fn generate_walk(schema: &Schema) -> TokenStream {
|
||||
/// (`Program<'a>`).
|
||||
/// * `ctx` must contain a `TraverseAncestry<'a>` with single `Ancestor::None` on its stack.
|
||||
#[inline]
|
||||
pub unsafe fn walk_ast<'a, State, Tr: Traverse<'a, State>>(
|
||||
pub unsafe fn walk_ast<#walk_generics>(
|
||||
traverser: &mut Tr,
|
||||
program: *mut Program<'a>,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
ctx: &mut #ctx_ty,
|
||||
) {
|
||||
walk_program(traverser, program, ctx);
|
||||
}
|
||||
@@ -80,10 +116,10 @@ pub fn generate_walk(schema: &Schema) -> TokenStream {
|
||||
#walk_methods
|
||||
|
||||
///@@line_break
|
||||
unsafe fn walk_statements<'a, State, Tr: Traverse<'a, State>>(
|
||||
unsafe fn walk_statements<#walk_generics>(
|
||||
traverser: &mut Tr,
|
||||
stmts: *mut Vec<'a, Statement<'a>>,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
ctx: &mut #ctx_ty,
|
||||
) {
|
||||
traverser.enter_statements(&mut *stmts, ctx);
|
||||
for stmt in &mut *stmts {
|
||||
@@ -94,8 +130,23 @@ pub fn generate_walk(schema: &Schema) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_generics_tokens(config: &WalkConfig) -> TokenStream {
|
||||
let trait_bound = &config.trait_bound;
|
||||
if config.has_state {
|
||||
quote! { 'a, State, #trait_bound }
|
||||
} else {
|
||||
quote! { 'a, #trait_bound }
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate walk function for a struct type.
|
||||
fn generate_walk_for_struct(struct_def: &StructDef, schema: &Schema) -> TokenStream {
|
||||
fn generate_walk_for_struct(
|
||||
struct_def: &StructDef,
|
||||
schema: &Schema,
|
||||
config: &WalkConfig,
|
||||
) -> TokenStream {
|
||||
let walk_generics = walk_generics_tokens(config);
|
||||
let ctx_ty = &config.ctx_ty;
|
||||
let visitor_names = struct_def.visit.visitor_names.as_ref().unwrap();
|
||||
let snake_name = traverse_snake_name(visitor_names);
|
||||
let walk_fn_name = format_ident!("walk_{snake_name}");
|
||||
@@ -195,10 +246,10 @@ fn generate_walk_for_struct(struct_def: &StructDef, schema: &Schema) -> TokenStr
|
||||
|
||||
quote! {
|
||||
///@@line_break
|
||||
unsafe fn #walk_fn_name<'a, State, Tr: Traverse<'a, State>>(
|
||||
unsafe fn #walk_fn_name<#walk_generics>(
|
||||
traverser: &mut Tr,
|
||||
node: *mut #struct_ty,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
ctx: &mut #ctx_ty,
|
||||
) {
|
||||
traverser.#enter_fn_name(&mut *node, ctx);
|
||||
#fields_code
|
||||
@@ -472,7 +523,9 @@ fn generate_field_walk_option_with_retag(
|
||||
}
|
||||
|
||||
/// Generate walk function for an enum type.
|
||||
fn generate_walk_for_enum(enum_def: &EnumDef, schema: &Schema) -> TokenStream {
|
||||
fn generate_walk_for_enum(enum_def: &EnumDef, schema: &Schema, config: &WalkConfig) -> TokenStream {
|
||||
let walk_generics = walk_generics_tokens(config);
|
||||
let ctx_ty = &config.ctx_ty;
|
||||
let visitor_names = enum_def.visit.visitor_names.as_ref().unwrap();
|
||||
let snake_name = traverse_snake_name(visitor_names);
|
||||
let walk_fn_name = format_ident!("walk_{snake_name}");
|
||||
@@ -528,10 +581,10 @@ fn generate_walk_for_enum(enum_def: &EnumDef, schema: &Schema) -> TokenStream {
|
||||
|
||||
quote! {
|
||||
///@@line_break
|
||||
unsafe fn #walk_fn_name<'a, State, Tr: Traverse<'a, State>>(
|
||||
unsafe fn #walk_fn_name<#walk_generics>(
|
||||
traverser: &mut Tr,
|
||||
node: *mut #enum_ty,
|
||||
ctx: &mut TraverseCtx<'a, State>,
|
||||
ctx: &mut #ctx_ty,
|
||||
) {
|
||||
traverser.#enter_fn_name(&mut *node, ctx);
|
||||
match &mut *node {
|
||||
|
||||
@@ -253,6 +253,9 @@ const AST_MACROS_CRATE_PATH: &str = "crates/oxc_ast_macros";
|
||||
/// Path to `oxc_traverse` crate
|
||||
const TRAVERSE_CRATE_PATH: &str = "crates/oxc_traverse";
|
||||
|
||||
/// Path to `oxc_minifier` crate
|
||||
const MINIFIER_CRATE_PATH: &str = "crates/oxc_minifier";
|
||||
|
||||
/// Path to write TS type definitions to
|
||||
const TYPESCRIPT_DEFINITIONS_PATH: &str = "npm/oxc-types/types.d.ts";
|
||||
|
||||
@@ -300,6 +303,7 @@ const GENERATORS: &[&(dyn Generator + Sync)] = &[
|
||||
&generators::FormatterFormatGenerator,
|
||||
&generators::FormatterAstNodesGenerator,
|
||||
&generators::TraverseGenerator,
|
||||
&generators::MinifierTraverseGenerator,
|
||||
];
|
||||
|
||||
/// Attributes on structs and enums (not including those defined by derives/generators)
|
||||
|
||||
Reference in new issue
Block a user