use itertools::Itertools;
use oxc::allocator::FromIn;
use oxc::ast::AstType;
use oxc::ast::ast::{AssignmentTarget, JSXMemberExpression, Str};
use oxc::{
allocator::{self, Dummy as _, IntoIn, TakeIn},
ast::{
NONE,
ast::{self, BindingPattern, Expression, SimpleAssignmentTarget, Statement},
match_member_expression,
},
ast_visit::{VisitMut, walk_mut},
semantic::ScopeFlags,
span::{SPAN, Span},
};
use oxc_str::CompactStr;
use rolldown_common::{ConcatenateWrappedModuleKind, SymbolRef, ThisExprReplaceKind, WrapKind};
use rolldown_ecmascript::ToSourceString;
use rolldown_ecmascript_utils::{ExpressionExt, JsxExt, JsxMemberExpressionObjectExt};
use crate::hmr::utils::HmrAstBuilder;
use crate::module_finalizers::{KeepNameId, TraverseState};
use super::ScopeHoistingFinalizer;
impl<'ast> VisitMut<'ast> for ScopeHoistingFinalizer<'_, 'ast> {
fn enter_scope(
&mut self,
flags: oxc::semantic::ScopeFlags,
_scope_id: &std::cell::Cell<Option<oxc::semantic::ScopeId>>,
) {
self.scope_stack.push(flags);
self.state.set(
TraverseState::TopLevel,
self.scope_stack.iter().rev().all(|flag| flag.is_block() || flag.is_top()),
);
self
.state
.set(TraverseState::IsRootLevel, self.scope_stack.iter().rev().all(|flag| flag.is_top()));
}
fn leave_scope(&mut self) {
self.scope_stack.pop();
self.state.set(
TraverseState::TopLevel,
self.scope_stack.iter().rev().all(|flag| flag.is_block() || flag.is_top()),
);
self
.state
.set(TraverseState::IsRootLevel, self.scope_stack.iter().rev().all(|flag| flag.is_top()));
}
fn visit_if_statement(&mut self, it: &mut ast::IfStatement<'ast>) {
let kind = AstType::IfStatement;
self.enter_node(kind);
self.visit_span(&mut it.span);
let pre = self.state;
self.state.insert(TraverseState::SmartInlineConst);
self.visit_expression(&mut it.test);
self.state = pre;
self.visit_statement(&mut it.consequent);
if let Some(alternate) = &mut it.alternate {
self.visit_statement(alternate);
}
self.leave_node(kind);
}
fn visit_conditional_expression(&mut self, it: &mut ast::ConditionalExpression<'ast>) {
let kind = AstType::ConditionalExpression;
self.enter_node(kind);
self.visit_span(&mut it.span);
let pre = self.state;
self.state.insert(TraverseState::SmartInlineConst);
self.visit_expression(&mut it.test);
self.state = pre;
self.visit_expression(&mut it.consequent);
self.visit_expression(&mut it.alternate);
self.leave_node(kind);
}
fn visit_logical_expression(&mut self, it: &mut ast::LogicalExpression<'ast>) {
let pre = self.state;
self.state.insert(TraverseState::SmartInlineConst);
walk_mut::walk_logical_expression(self, it);
self.state = pre;
}
fn visit_program(&mut self, program: &mut ast::Program<'ast>) {
program.hashbang.take();
program.directives.clear();
let last_import_stmt_idx = self.remove_unused_top_level_stmt(program);
if self.ctx.options.is_dev_mode_enabled() {
let hmr_header = if self.ctx.runtime.id() == self.ctx.module.idx {
vec![]
} else {
self.generate_hmr_header()
};
program.body.splice(last_import_stmt_idx..last_import_stmt_idx, hmr_header);
}
let included_wrap_kind = self
.ctx
.linking_info
.wrapper_stmt_info
.is_some_and(|idx| self.ctx.linking_info.stmt_info_included.has_bit(idx))
.then_some(self.ctx.linking_info.wrap_kind());
self.needs_hosted_top_level_binding = matches!(included_wrap_kind, Some(WrapKind::Esm));
let declaration_of_module_namespace_object =
self.generate_declaration_of_module_namespace_object();
let mut shimmed_exports =
self.ctx.linking_info.shimmed_missing_exports.iter().collect::<Vec<_>>();
shimmed_exports.sort_unstable_by_key(|(name, _)| name.as_str());
shimmed_exports.into_iter().for_each(|(_name, symbol_ref)| {
debug_assert!(!self.ctx.stmt_infos.declared_stmts_by_symbol(symbol_ref).is_empty());
let is_included: bool = self
.ctx
.stmt_infos
.declared_stmts_by_symbol(symbol_ref)
.iter()
.any(|id| self.ctx.linking_info.stmt_info_included.has_bit(*id));
if is_included {
let canonical_name = self.canonical_name_for(*symbol_ref);
program.body.push(self.snippet.var_decl_stmt(canonical_name, self.snippet.void_zero()));
}
});
self.enter_scope(
{
let mut flags = ScopeFlags::Top;
if program.source_type.is_strict() || program.has_use_strict_directive() {
flags |= ScopeFlags::StrictMode;
}
flags
},
&program.scope_id,
);
walk_mut::walk_program(self, program);
self.leave_scope();
self.insert_keep_name_statements(&mut program.body);
self.keep_name_statement_to_insert.clear();
match included_wrap_kind {
Some(WrapKind::Cjs) => {
let wrap_ref_name = self.canonical_name_for(self.ctx.linking_info.wrapper_ref.unwrap());
let commonjs_ref = if self.ctx.options.profiler_names {
self.canonical_ref_for_runtime("__commonJS")
} else {
self.canonical_ref_for_runtime("__commonJSMin")
};
let (commonjs_ref_expr, _) = self.finalized_expr_for_symbol_ref(commonjs_ref, false, false);
let mut stmts_inside_closure = allocator::Vec::new_in(self.alloc);
stmts_inside_closure.append(&mut program.body);
program.body.push(self.snippet.commonjs_wrapper_stmt(
wrap_ref_name,
commonjs_ref_expr,
stmts_inside_closure,
self.ctx.module.ast_usage,
self.ctx.options.profiler_names,
&self.ctx.module.stable_id,
self.ctx.linking_info.is_tla_or_contains_tla_dependency,
));
}
Some(WrapKind::Esm) => {
let is_concatenated_wrapped_module = !matches!(
self.ctx.linking_info.concatenated_wrapped_module_kind,
ConcatenateWrappedModuleKind::None
);
let old_body = program.body.take_in(self.alloc);
let mut fn_stmts = allocator::Vec::new_in(self.alloc);
let mut stmts_inside_closure = allocator::Vec::new_in(self.alloc);
old_body.into_iter().for_each(|mut stmt| match &mut stmt {
ast::Statement::FunctionDeclaration(_) => {
fn_stmts.push(stmt);
}
ast::match_module_declaration!(Statement) => {
if stmt.is_typescript_syntax() {
unreachable!(
"At this point, typescript module declarations should have been removed or transformed"
)
}
program.body.push(stmt);
}
_ => {
stmts_inside_closure.push(stmt);
}
});
if is_concatenated_wrapped_module {
self.rendered_concatenated_wrapped_module_parts.hoisted_functions_or_module_ns_decl =
declaration_of_module_namespace_object
.iter()
.chain(fn_stmts.iter())
.map(rolldown_ecmascript::ToSourceString::to_source_string)
.collect_vec();
self.rendered_concatenated_wrapped_module_parts.hoisted_vars = self
.top_level_var_bindings
.iter()
.map(|var_name| CompactStr::new(var_name))
.collect_vec();
} else {
program.body.extend(declaration_of_module_namespace_object);
program.body.extend(fn_stmts);
}
if !is_concatenated_wrapped_module && !self.top_level_var_bindings.is_empty() {
let builder = self.builder();
let decorations = self.top_level_var_bindings.iter().map(|var_name| {
builder.variable_declarator(
SPAN,
ast::VariableDeclarationKind::Var,
builder.binding_pattern_binding_identifier(SPAN, *var_name),
NONE,
None,
false,
)
});
program.body.push(Statement::VariableDeclaration(builder.alloc_variable_declaration(
SPAN,
ast::VariableDeclarationKind::Var,
builder.vec_from_iter(decorations),
false,
)));
}
if matches!(
self.ctx.linking_info.concatenated_wrapped_module_kind,
ConcatenateWrappedModuleKind::Inner
) {
program.body.extend(stmts_inside_closure);
return;
}
let esm_ref = if self.ctx.options.profiler_names {
self.canonical_ref_for_runtime("__esm")
} else {
self.canonical_ref_for_runtime("__esmMin")
};
let (esm_ref_expr, _) = self.finalized_expr_for_symbol_ref(esm_ref, false, false);
let wrap_ref_name = self.canonical_name_for(self.ctx.linking_info.wrapper_ref.unwrap());
if matches!(
self.ctx.linking_info.concatenated_wrapped_module_kind,
ConcatenateWrappedModuleKind::Root
) {
self.rendered_concatenated_wrapped_module_parts.rendered_esm_runtime_expr =
Some(self.builder().expression_statement(SPAN, esm_ref_expr).to_source_string());
self.rendered_concatenated_wrapped_module_parts.wrap_ref_name =
Some(CompactStr::new(wrap_ref_name));
program.body.extend(stmts_inside_closure);
return;
}
program.body.push(self.snippet.esm_wrapper_stmt(
wrap_ref_name,
esm_ref_expr,
stmts_inside_closure,
self.ctx.options.profiler_names,
self.ctx.options.optimization.is_pife_for_module_wrappers_enabled(),
&self.ctx.module.stable_id,
self.ctx.linking_info.is_tla_or_contains_tla_dependency,
));
}
Some(WrapKind::None) => {}
None => {
program.body.splice(0..0, declaration_of_module_namespace_object);
}
}
if self.json_module_inlined_prop.is_some() {
program.body.drain_filter(|item| matches!(item, ast::Statement::EmptyStatement(_)));
}
}
fn visit_binding_identifier(&mut self, ident: &mut ast::BindingIdentifier<'ast>) {
if let Some(symbol_id) = ident.symbol_id.get() {
let symbol_ref: SymbolRef = (self.ctx.idx, symbol_id).into();
let canonical_ref = self.ctx.symbol_db.canonical_ref_for(symbol_ref);
let symbol = self.ctx.symbol_db.get(canonical_ref);
assert!(symbol.namespace_alias.is_none());
let canonical_name = self.canonical_name_for(symbol_ref);
if ident.name != canonical_name {
ident.name = self.snippet.atom(canonical_name).into();
}
ident.symbol_id.get_mut().take();
} else {
}
}
fn visit_statement(&mut self, it: &mut ast::Statement<'ast>) {
_ = self.try_inline_json_module_prop(it);
if !self.ctx.options.drop_labels.is_empty() {
if let ast::Statement::LabeledStatement(stmt) = it {
if self.ctx.options.drop_labels.contains(stmt.label.name.as_str()) {
*it = self.snippet.builder.statement_empty(stmt.span);
}
}
}
walk_mut::walk_statement(self, it);
if self.state.contains(TraverseState::TopLevel)
&& self.needs_hosted_top_level_binding
&& let ast::Statement::VariableDeclaration(decl) = it
{
if let Some((expr, bindings)) =
self.var_declaration_to_expr_seq_and_bindings(decl, self.state)
{
self.top_level_var_bindings.extend(bindings);
*it = ast::Statement::ExpressionStatement(
self.builder().alloc_expression_statement(SPAN, expr),
);
}
}
}
fn visit_statements(&mut self, it: &mut allocator::Vec<'ast, ast::Statement<'ast>>) {
let previous_stmt_index = self.cur_stmt_index;
let previous_keep_name_statement = std::mem::take(&mut self.keep_name_statement_to_insert);
for (i, stmt) in it.iter_mut().enumerate() {
self.cur_stmt_index = i;
self.visit_statement(stmt);
}
self.insert_keep_name_statements(it);
self.cur_stmt_index = previous_stmt_index;
self.keep_name_statement_to_insert = previous_keep_name_statement;
}
fn visit_identifier_reference(&mut self, ident: &mut ast::IdentifierReference) {
debug_assert!(
self.is_global_identifier_reference(ident) || ident.reference_id.get().is_none(),
"{} doesn't get processed in {}",
ident.name,
self.ctx.module.repr_name
);
}
fn visit_expression(&mut self, expr: &mut ast::Expression<'ast>) {
if self.ctx.options.keep_names && self.ctx.runtime.id() != self.ctx.idx {
match expr {
ast::Expression::ClassExpression(class_expression) => {
if let Some(id) = class_expression.id.as_ref() {
if let Some(element) = self.keep_name_helper_for_class(
id.symbol_id.get().map(KeepNameId::SymbolId),
&class_expression.body,
) {
class_expression.body.body.insert(0, element);
}
}
}
ast::Expression::FunctionExpression(fn_expression) => {
if let Some(id) = fn_expression.id.as_mut() {
if let Some(symbol_id) = id.symbol_id.get() {
let keep_name_id = KeepNameId::SymbolId(symbol_id);
if let Some((_insert_position, original_name, _)) =
self.process_fn(Some(keep_name_id), Some(keep_name_id))
{
let symbol_ref: SymbolRef = (self.ctx.idx, symbol_id).into();
let canonical_name = self.canonical_name_for(symbol_ref);
if id.name != canonical_name {
id.name = self.snippet.atom(canonical_name).into();
}
id.symbol_id.get_mut().take();
let fn_expr = expr.take_in(self.alloc);
let name_ref = self.canonical_ref_for_runtime("__name");
let (finalized_callee, _) =
self.finalized_expr_for_symbol_ref(name_ref, false, false);
*expr =
self.snippet.keep_name_call_expr(&original_name, fn_expr, finalized_callee, true);
}
}
}
}
_ => {}
}
}
match expr {
ast::Expression::CallExpression(call_expr) => {
self.rewrite_hot_accept_call_deps(call_expr);
if let Some(new_expr) = self.try_rewrite_global_require_call(call_expr) {
*expr = new_expr;
} else if let Some(ident_ref) = call_expr.callee.as_identifier_mut() {
let is_empty_function = ident_ref
.reference_id
.get()
.and_then(|ref_id| self.scope.scoping().get_reference(ref_id).symbol_id())
.map(|id| {
let symbol_ref = self.ctx.symbol_db.canonical_ref_for((self.ctx.idx, id).into());
symbol_ref.is_side_effect_free_function(self.ctx.symbol_db, self.ctx.modules)
&& symbol_ref.is_not_reassigned(self.ctx.symbol_db) == Some(true)
})
.unwrap_or(false);
if is_empty_function {
call_expr.pure = true;
} else if let Some(new_expr) = self.try_rewrite_identifier_reference_expr(ident_ref, true)
{
call_expr.callee = new_expr;
}
}
}
ast::Expression::ImportExpression(import_expr) => {
if let Some(new_expr) = self.try_rewrite_inline_dynamic_import_expr(import_expr) {
*expr = new_expr;
}
if self.try_rewrite_import_expression(expr) {
return;
}
}
ast::Expression::NewExpression(new_expr) => {
self.handle_new_url_with_string_literal_and_import_meta_url(new_expr);
}
ast::Expression::Identifier(ident_ref) => {
if let Some(new_expr) = self.try_rewrite_identifier_reference_expr(ident_ref, false) {
*expr = new_expr;
}
}
ast::Expression::ThisExpression(this_expr) => {
if let Some(kind) = self.ctx.module.ecma_view.this_expr_replace_map.get(&this_expr.span) {
match kind {
ThisExprReplaceKind::Exports => {
*expr = self.snippet.builder.expression_identifier(SPAN, "exports");
}
ThisExprReplaceKind::Context if self.ctx.options.context.is_empty() => {
*expr = self.snippet.void_zero();
}
ThisExprReplaceKind::Context => {
*expr = self.snippet.builder.expression_identifier(
SPAN,
Str::from_in(self.ctx.options.context.as_str(), self.alloc),
);
}
}
}
}
ast::Expression::MetaProperty(meta) => {
if !self.ctx.options.format.keep_esm_import_export_syntax()
&& meta.meta.name == "import"
&& meta.property.name == "meta"
{
*expr = self.snippet.builder.expression_object(SPAN, self.snippet.builder.vec());
}
}
ast::Expression::ChainExpression(_) => {
if self.ctx.has_enum_inlining
&& let Some(new_expr) = self.try_inline_enum_access(expr)
{
*expr = new_expr;
self.rewrite_import_meta_hot(expr);
walk_mut::walk_expression(self, expr);
return;
}
let ast::Expression::ChainExpression(chain_expr) = expr else { unreachable!() };
let chain_span = chain_expr.span;
if let Some(new_expr) = chain_expr
.expression
.as_member_expression_mut()
.and_then(|expr| self.try_rewrite_member_expr(expr))
{
if has_optional_member_access(&new_expr) {
match new_expr {
ast::Expression::StaticMemberExpression(member) => {
*expr = self
.snippet
.builder
.expression_chain(chain_span, ast::ChainElement::StaticMemberExpression(member));
}
ast::Expression::ComputedMemberExpression(member) => {
*expr = self.snippet.builder.expression_chain(
chain_span,
ast::ChainElement::ComputedMemberExpression(member),
);
}
_ => {
*expr = new_expr;
}
}
} else {
*expr = new_expr;
}
}
}
_ => {
if self.ctx.has_enum_inlining {
if let Some(new_expr) = self.try_inline_enum_access(expr) {
*expr = new_expr;
self.rewrite_import_meta_hot(expr);
walk_mut::walk_expression(self, expr);
return;
}
}
if let Some(new_expr) =
expr.as_member_expression().and_then(|expr| self.try_rewrite_member_expr(expr))
{
*expr = new_expr;
if self.ctx.has_enum_inlining {
if let Some(inlined) = self.try_inline_enum_access(expr) {
*expr = inlined;
}
}
}
}
}
self.rewrite_import_meta_hot(expr);
walk_mut::walk_expression(self, expr);
}
fn visit_jsx_element_name(&mut self, it: &mut ast::JSXElementName<'ast>) {
match it {
ast::JSXElementName::Identifier(ident) => {
walk_mut::walk_jsx_identifier(self, ident);
}
ast::JSXElementName::IdentifierReference(identifier_reference) => {
if let Some(new_expr) =
self.try_rewrite_identifier_reference_expr(identifier_reference, false)
{
match new_expr {
Expression::Identifier(ident_ref) => {
*it = ast::JSXElementName::IdentifierReference(ident_ref);
}
Expression::StaticMemberExpression(member_expr) => {
*it = ast::JSXElementName::MemberExpression(oxc::allocator::Box::new_in(
JSXMemberExpression::from_ast(member_expr.unbox(), self.alloc).unwrap(),
self.alloc,
));
}
Expression::ThisExpression(this_expr) => {
*it = ast::JSXElementName::ThisExpression(this_expr);
}
_ => {
unreachable!(
"Should always rewrite to Identifier, StaticMemberExpression, or ThisExpression for JsxElementName::IdentifierReference"
)
}
}
}
}
ast::JSXElementName::NamespacedName(jsx_namespace_name) => {
walk_mut::walk_jsx_namespaced_name(self, jsx_namespace_name);
}
ast::JSXElementName::MemberExpression(jsx_member_expression) => {
if let Some(ident) = jsx_member_expression.get_identifier() {
if let Some(new_expr) = self.try_rewrite_identifier_reference_expr(ident, false) {
match new_expr {
Expression::Identifier(ident_ref) => {
jsx_member_expression.object.rewrite_ident_reference(
ast::JSXMemberExpressionObject::IdentifierReference(ident_ref),
);
}
Expression::StaticMemberExpression(member_expr) => {
jsx_member_expression.object.rewrite_ident_reference(
ast::JSXMemberExpressionObject::MemberExpression(oxc::allocator::Box::new_in(
JSXMemberExpression::from_ast(member_expr.unbox(), self.alloc).unwrap(),
self.alloc,
)),
);
}
Expression::ThisExpression(this_expr) => {
*it = ast::JSXElementName::ThisExpression(this_expr);
}
_ => {
unreachable!(
"Should always rewrite to Identifier for JsxMemberExpression::get_identifier()"
)
}
}
}
}
}
ast::JSXElementName::ThisExpression(this_expression) => {
walk_mut::walk_this_expression(self, this_expression);
}
}
}
fn visit_member_expression(&mut self, expr: &mut ast::MemberExpression<'ast>) {
if let Some(new_expr) = self.try_rewrite_member_expr(expr) {
match new_expr {
match_member_expression!(Expression) => {
*expr = new_expr.into_member_expression();
}
_ => {
unreachable!("Always rewrite to MemberExpression for nested MemberExpression")
}
}
} else {
walk_mut::walk_member_expression(self, expr);
}
}
fn visit_object_property(&mut self, prop: &mut ast::ObjectProperty<'ast>) {
if prop.shorthand {
if let ast::Expression::Identifier(id_ref) = &mut prop.value {
match self.generate_finalized_expr_for_reference(id_ref, false) {
Some(expr) => {
prop.value = expr;
prop.shorthand = false;
}
None => {
id_ref.reference_id.get_mut().take();
}
}
}
}
walk_mut::walk_object_property(self, prop);
}
fn visit_object_pattern(&mut self, pat: &mut ast::ObjectPattern<'ast>) {
self.rewrite_object_pat_shorthand(pat);
walk_mut::walk_object_pattern(self, pat);
}
fn visit_assignment_target_property(
&mut self,
property: &mut ast::AssignmentTargetProperty<'ast>,
) {
if let ast::AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(prop) = property {
if let Some(target) =
self.generate_finalized_simple_assignment_target_for_reference(&prop.binding)
{
*property = ast::AssignmentTargetProperty::AssignmentTargetPropertyProperty(
ast::AssignmentTargetPropertyProperty {
name: ast::PropertyKey::StaticIdentifier(
self.snippet.id_name(&prop.binding.name, prop.span).into_in(self.alloc),
),
binding: if let Some(init) = prop.init.take() {
ast::AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(
ast::AssignmentTargetWithDefault {
binding: ast::AssignmentTarget::from(target),
init,
span: Span::default(),
..ast::AssignmentTargetWithDefault::dummy(self.alloc)
}
.into_in(self.alloc),
)
} else {
ast::AssignmentTargetMaybeDefault::from(target)
},
span: Span::default(),
computed: false,
..ast::AssignmentTargetPropertyProperty::dummy(self.alloc)
}
.into_in(self.alloc),
);
} else {
prop.binding.reference_id.get_mut().take();
}
}
walk_mut::walk_assignment_target_property(self, property);
}
fn visit_simple_assignment_target(&mut self, target: &mut SimpleAssignmentTarget<'ast>) {
self.rewrite_simple_assignment_target(target);
walk_mut::walk_simple_assignment_target(self, target);
}
fn visit_assignment_expression(&mut self, it: &mut ast::AssignmentExpression<'ast>) {
if let AssignmentTarget::AssignmentTargetIdentifier(id) = &mut it.left {
self.process_keep_name_for_expression(
id.reference_id.get().map(KeepNameId::ReferenceId),
&mut it.right,
);
}
walk_mut::walk_assignment_expression(self, it);
}
fn visit_for_statement_init(&mut self, it: &mut ast::ForStatementInit<'ast>) {
walk_mut::walk_for_statement_init(self, it);
if self.state.contains(TraverseState::TopLevel)
&& self.needs_hosted_top_level_binding
&& let ast::ForStatementInit::VariableDeclaration(decl) = it
{
if let Some((expr, bindings)) =
self.var_declaration_to_expr_seq_and_bindings(decl, self.state)
{
self.top_level_var_bindings.extend(bindings);
*it = ast::ForStatementInit::from(expr);
}
}
}
fn visit_declaration(&mut self, it: &mut ast::Declaration<'ast>) {
match it {
ast::Declaration::VariableDeclaration(decl) => {
for decl in &mut decl.declarations {
let (BindingPattern::BindingIdentifier(id), Some(init)) = (&decl.id, decl.init.as_mut())
else {
continue;
};
self.process_keep_name_for_expression(id.symbol_id.get().map(KeepNameId::SymbolId), init);
}
}
ast::Declaration::FunctionDeclaration(decl) => {
let keep_name_id =
decl.id.as_ref().and_then(|id| id.symbol_id.get().map(KeepNameId::SymbolId));
if let Some((insert_position, original_name, new_name)) =
self.process_fn(keep_name_id, keep_name_id)
{
self.keep_name_statement_to_insert.push((insert_position, original_name, new_name));
}
}
ast::Declaration::ClassDeclaration(decl) => {
if let Some(element) = self.keep_name_helper_for_class(
decl.id.as_ref().and_then(|id| id.symbol_id.get().map(KeepNameId::SymbolId)),
&decl.body,
) {
decl.body.body.insert(0, element);
}
if let Some(new_decl) = self.get_transformed_class_decl(decl) {
*it = new_decl;
if let ast::Declaration::VariableDeclaration(var_decl) = it {
if let Some(declarator) = var_decl.declarations.first_mut() {
if let Some(ast::Expression::ClassExpression(class_expr)) = &mut declarator.init {
if let Some(id) = &mut class_expr.id {
id.symbol_id.get_mut().take();
}
}
}
}
}
}
ast::Declaration::TSTypeAliasDeclaration(_)
| ast::Declaration::TSInterfaceDeclaration(_)
| ast::Declaration::TSEnumDeclaration(_)
| ast::Declaration::TSModuleDeclaration(_)
| ast::Declaration::TSImportEqualsDeclaration(_)
| ast::Declaration::TSGlobalDeclaration(_) => unreachable!(),
}
walk_mut::walk_declaration(self, it);
}
}
fn has_optional_member_access(expr: &Expression) -> bool {
let mut cur = expr;
loop {
match cur {
Expression::StaticMemberExpression(e) => {
if e.optional {
return true;
}
cur = &e.object;
}
Expression::ComputedMemberExpression(e) => {
if e.optional {
return true;
}
cur = &e.object;
}
_ => return false,
}
}
}