refactor(traverse)!: generate_uid return a BoundIdentifier (#6294)

Closes #5020.

`TraverseCtx::generate_uid` and associated methods return a `BoundIdentifier`, containing both symbol ID and name. This reduces boilerplate code for the caller and avoids and unnecessary lookup to get the name.

Also add a couple of helper methods to `BoundIdentifier` to reduce boilerplate further.
This commit is contained in:
overlookmotel 2024-10-05 16:00:50 +00:00
parent 7b62966813
commit 409dffc8ab
14 changed files with 111 additions and 214 deletions

View file

@ -18,8 +18,7 @@ use oxc_allocator::Vec;
use oxc_ast::{ast::*, NONE};
use oxc_data_structures::stack::SparseStack;
use oxc_span::SPAN;
use oxc_syntax::symbol::SymbolId;
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};
use crate::TransformCtx;
@ -68,15 +67,14 @@ impl<'a> VarDeclarationsStore<'a> {
}
/// Add a `VariableDeclarator` to be inserted at top of current enclosing statement block,
/// given `name` and `symbol_id`.
/// given a `BoundIdentifier`.
pub fn insert(
&self,
name: Atom<'a>,
symbol_id: SymbolId,
binding: &BoundIdentifier<'a>,
init: Option<Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
let ident = BindingIdentifier::new_with_symbol_id(SPAN, name, symbol_id);
let ident = binding.create_binding_identifier();
let ident = ctx.ast.binding_pattern_kind_from_binding_identifier(ident);
let ident = ctx.ast.binding_pattern(ident, NONE, false);
self.insert_binding_pattern(ident, init, ctx);

View file

@ -284,13 +284,7 @@ impl<'a> ArrowFunctions<'a> {
) && !scope_flags.contains(ScopeFlags::Arrow)
})
.unwrap();
BoundIdentifier::new_uid(
"this",
target_scope_id,
SymbolFlags::FunctionScopedVariable,
ctx,
)
ctx.generate_uid("this", target_scope_id, SymbolFlags::FunctionScopedVariable)
});
Some(this_var.create_spanned_read_reference(span, ctx))
}

View file

@ -290,23 +290,18 @@ impl<'a, 'ctx> ExponentiationOperator<'a, 'ctx> {
_ => "ref",
};
let symbol_id =
ctx.generate_uid_in_current_scope(name, SymbolFlags::FunctionScopedVariable);
let symbol_name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
let binding = ctx.generate_uid_in_current_scope(name, SymbolFlags::FunctionScopedVariable);
// var _name;
self.ctx.var_declarations.insert(symbol_name.clone(), symbol_id, None, ctx);
self.ctx.var_declarations.insert(&binding, None, ctx);
let ident =
ctx.create_bound_reference_id(SPAN, symbol_name, symbol_id, ReferenceFlags::Read);
// let ident = self.create_new_var_with_expression(&expr);
// Add new reference `_name = name` to nodes
let left = ctx.ast.simple_assignment_target_from_identifier_reference(
ctx.clone_identifier_reference(&ident, ReferenceFlags::Write),
binding.create_write_reference(ctx),
);
let op = AssignmentOperator::Assign;
nodes.push(ctx.ast.expression_assignment(SPAN, op, AssignmentTarget::from(left), expr));
ctx.ast.expression_from_identifier_reference(ident)
ctx.ast.expression_from_identifier_reference(binding.create_read_reference(ctx))
}
}

View file

@ -32,7 +32,7 @@
//! * Babel plugin implementation: <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-optional-catch-binding>
//! * Optional catch binding TC39 proposal: <https://github.com/tc39/proposal-optional-catch-binding>
use oxc_ast::{ast::*, NONE};
use oxc_ast::ast::*;
use oxc_semantic::SymbolFlags;
use oxc_span::SPAN;
use oxc_traverse::{Traverse, TraverseCtx};
@ -53,17 +53,12 @@ impl<'a> Traverse<'a> for OptionalCatchBinding {
return;
}
let block_scope_id = clause.body.scope_id.get().unwrap();
let symbol_id = ctx.generate_uid(
let binding = ctx.generate_uid(
"unused",
block_scope_id,
clause.body.scope_id.get().unwrap(),
SymbolFlags::CatchVariable | SymbolFlags::FunctionScopedVariable,
);
let name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
let binding_identifier = BindingIdentifier::new_with_symbol_id(SPAN, name, symbol_id);
let binding_pattern_kind =
ctx.ast.binding_pattern_kind_from_binding_identifier(binding_identifier);
let binding_pattern = ctx.ast.binding_pattern(binding_pattern_kind, NONE, false);
let binding_pattern = binding.create_binding_pattern(ctx);
let param = ctx.ast.catch_parameter(SPAN, binding_pattern);
clause.param = Some(param);
}

View file

@ -148,21 +148,16 @@ impl<'a, 'ctx> NullishCoalescingOperator<'a, 'ctx> {
ctx: &mut TraverseCtx<'a>,
) -> (BindingPattern<'a>, IdentifierReference<'a>) {
// Add `var name` to scope
let symbol_id = ctx.generate_uid_based_on_node(
let binding = ctx.generate_uid_based_on_node(
expr,
current_scope_id,
SymbolFlags::FunctionScopedVariable,
);
let symbol_name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
// var _name;
let binding_identifier =
BindingIdentifier::new_with_symbol_id(SPAN, symbol_name.clone(), symbol_id);
let id = ctx.ast.binding_pattern_kind_from_binding_identifier(binding_identifier);
let id = ctx.ast.binding_pattern(id, NONE, false);
let reference =
ctx.create_bound_reference_id(SPAN, symbol_name, symbol_id, ReferenceFlags::Read);
let id = binding.create_binding_pattern(ctx);
let reference = binding.create_read_reference(ctx);
(id, reference)
}

View file

@ -342,13 +342,11 @@ impl<'a, 'ctx> LogicalAssignmentOperators<'a, 'ctx> {
return None;
}
let symbol_id = ctx
.generate_uid_in_current_scope_based_on_node(expr, SymbolFlags::FunctionScopedVariable);
let name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
// var _name;
self.ctx.var_declarations.insert(name.clone(), symbol_id, None, ctx);
let binding = ctx
.generate_uid_in_current_scope_based_on_node(expr, SymbolFlags::FunctionScopedVariable);
self.ctx.var_declarations.insert(&binding, None, ctx);
Some(BoundIdentifier::new(name, symbol_id))
Some(binding)
}
}

View file

@ -195,13 +195,11 @@ impl<'a, 'ctx> AutomaticScriptBindings<'a, 'ctx> {
front: bool,
ctx: &mut TraverseCtx<'a>,
) -> BoundIdentifier<'a> {
let symbol_id =
let binding =
ctx.generate_uid_in_root_scope(variable_name, SymbolFlags::FunctionScopedVariable);
let variable_name = ctx.ast.atom(&ctx.symbols().names[symbol_id]);
let import = NamedImport::new(variable_name.clone(), None, symbol_id);
let import = NamedImport::new(binding.name.clone(), None, binding.symbol_id);
self.ctx.module_imports.add_import(source, import, front);
BoundIdentifier { name: variable_name, symbol_id }
binding
}
}
@ -297,12 +295,11 @@ impl<'a, 'ctx> AutomaticModuleBindings<'a, 'ctx> {
source: Atom<'a>,
ctx: &mut TraverseCtx<'a>,
) -> BoundIdentifier<'a> {
let symbol_id = ctx.generate_uid_in_root_scope(name, SymbolFlags::Import);
let local = ctx.ast.atom(&ctx.symbols().names[symbol_id]);
let import = NamedImport::new(Atom::from(name), Some(local.clone()), symbol_id);
let binding = ctx.generate_uid_in_root_scope(name, SymbolFlags::Import);
let import =
NamedImport::new(Atom::from(name), Some(binding.name.clone()), binding.symbol_id);
self.ctx.module_imports.add_import(source, import, false);
BoundIdentifier { name: local, symbol_id }
binding
}
}

View file

@ -220,14 +220,12 @@ impl<'a, 'ctx> ReactJsxSource<'a, 'ctx> {
Some(decl)
}
fn get_filename_var(&mut self, ctx: &mut TraverseCtx<'a>) -> BoundIdentifier<'a> {
fn get_filename_var(&mut self, ctx: &mut TraverseCtx<'a>) -> &BoundIdentifier<'a> {
if self.filename_var.is_none() {
self.filename_var = Some(BoundIdentifier::new_uid_in_root_scope(
FILE_NAME_VAR,
SymbolFlags::FunctionScopedVariable,
ctx,
));
self.filename_var = Some(
ctx.generate_uid_in_root_scope(FILE_NAME_VAR, SymbolFlags::FunctionScopedVariable),
);
}
self.filename_var.as_ref().unwrap().clone()
self.filename_var.as_ref().unwrap()
}
}

View file

@ -467,10 +467,9 @@ impl<'a, 'ctx> ReactRefresh<'a, 'ctx> {
reference_flags: ReferenceFlags,
ctx: &mut TraverseCtx<'a>,
) -> AssignmentTarget<'a> {
let symbol_id = ctx.generate_uid_in_root_scope("c", SymbolFlags::FunctionScopedVariable);
self.registrations.push((symbol_id, persistent_id));
let name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
let ident = ctx.create_bound_reference_id(SPAN, name, symbol_id, reference_flags);
let binding = ctx.generate_uid_in_root_scope("c", SymbolFlags::FunctionScopedVariable);
self.registrations.push((binding.symbol_id, persistent_id));
let ident = binding.create_reference(reference_flags, ctx);
let ident = ctx.ast.simple_assignment_target_from_identifier_reference(ident);
ctx.ast.assignment_target_simple(ident)
}
@ -683,12 +682,8 @@ impl<'a, 'ctx> ReactRefresh<'a, 'ctx> {
.find(|scope_id| ctx.scopes().get_flags(*scope_id).is_var())
.unwrap_or_else(|| ctx.current_scope_id());
let symbol_id = ctx.generate_uid("s", target_scope_id, SymbolFlags::FunctionScopedVariable);
let symbol_name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
let binding_identifier =
BindingIdentifier::new_with_symbol_id(SPAN, symbol_name.clone(), symbol_id);
let binding = ctx.generate_uid("s", target_scope_id, SymbolFlags::FunctionScopedVariable);
let binding_identifier = binding.create_binding_identifier();
// _s();
let call_expression = ctx.ast.statement_expression(

View file

@ -158,8 +158,8 @@ impl<'a, 'ctx> TypeScriptNamespace<'a, 'ctx> {
// Reuse `TSModuleDeclaration`'s scope in transformed function
let scope_id = decl.scope_id.get().unwrap();
let symbol_id = ctx.generate_uid(&real_name, scope_id, SymbolFlags::FunctionScopedVariable);
let name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
let binding = ctx.generate_uid(&real_name, scope_id, SymbolFlags::FunctionScopedVariable);
let name = binding.name;
let directives;
let namespace_top_level;

View file

@ -1,10 +1,9 @@
use oxc_ast::ast::{BindingIdentifier, IdentifierReference};
use oxc_span::{Atom, Span, SPAN};
use oxc_syntax::{
reference::ReferenceFlags,
scope::ScopeId,
symbol::{SymbolFlags, SymbolId},
use oxc_ast::{
ast::{BindingIdentifier, BindingPattern, IdentifierReference},
NONE,
};
use oxc_span::{Atom, Span, SPAN};
use oxc_syntax::{reference::ReferenceFlags, symbol::SymbolId};
use crate::TraverseCtx;
@ -47,44 +46,18 @@ impl<'a> BoundIdentifier<'a> {
Self { name, symbol_id }
}
/// Create `BoundIdentifier` for new binding in specified scope
pub fn new_uid(
name: &str,
scope_id: ScopeId,
flags: SymbolFlags,
ctx: &mut TraverseCtx<'a>,
) -> Self {
let symbol_id = ctx.generate_uid(name, scope_id, flags);
let name = ctx.ast.atom(&ctx.symbols().names[symbol_id]);
Self { name, symbol_id }
}
/// Create `BoundIdentifier` for new binding in root scope
pub fn new_uid_in_root_scope(
name: &str,
flags: SymbolFlags,
ctx: &mut TraverseCtx<'a>,
) -> Self {
let scope_id = ctx.scopes().root_scope_id();
Self::new_uid(name, scope_id, flags, ctx)
}
/// Create `BoundIdentifier` for new binding in current scope
#[allow(unused)]
pub fn new_uid_in_current_scope(
name: &str,
flags: SymbolFlags,
ctx: &mut TraverseCtx<'a>,
) -> Self {
let scope_id = ctx.current_scope_id();
Self::new_uid(name, scope_id, flags, ctx)
}
/// Create `BindingIdentifier` for this binding
pub fn create_binding_identifier(&self) -> BindingIdentifier<'a> {
BindingIdentifier::new_with_symbol_id(SPAN, self.name.clone(), self.symbol_id)
}
/// Create `BindingPattern` for this binding
pub fn create_binding_pattern(&self, ctx: &mut TraverseCtx<'a>) -> BindingPattern<'a> {
let ident = self.create_binding_identifier();
let binding_pattern_kind = ctx.ast.binding_pattern_kind_from_binding_identifier(ident);
ctx.ast.binding_pattern(binding_pattern_kind, NONE, false)
}
/// Create `IdentifierReference` referencing this binding, which is read from, with dummy `Span`
pub fn create_read_reference(&self, ctx: &mut TraverseCtx<'a>) -> IdentifierReference<'a> {
self.create_spanned_read_reference(SPAN, ctx)
@ -134,6 +107,15 @@ impl<'a> BoundIdentifier<'a> {
self.create_spanned_reference(span, ReferenceFlags::Read | ReferenceFlags::Write, ctx)
}
/// Create `IdentifierReference` referencing this binding, with specified `ReferenceFlags`
pub fn create_reference(
&self,
flags: ReferenceFlags,
ctx: &mut TraverseCtx<'a>,
) -> IdentifierReference<'a> {
self.create_spanned_reference(SPAN, flags, ctx)
}
/// Create `IdentifierReference` referencing this binding, with specified `Span` and `ReferenceFlags`
pub fn create_spanned_reference(
&self,

View file

@ -3,8 +3,8 @@ use oxc_ast::{
ast::{Expression, IdentifierReference, Statement},
AstBuilder,
};
use oxc_semantic::{ScopeTree, SymbolTable};
use oxc_span::{Atom, CompactStr, Span};
use oxc_semantic::{NodeId, ScopeTree, SymbolTable};
use oxc_span::{Atom, CompactStr, Span, SPAN};
use oxc_syntax::{
reference::{ReferenceFlags, ReferenceId},
scope::{ScopeFlags, ScopeId},
@ -15,11 +15,13 @@ use crate::ancestor::{Ancestor, AncestorType};
mod ancestry;
mod ast_operations;
use ast_operations::GatherNodeParts;
mod bound_identifier;
use ancestry::PopToken;
pub use ancestry::TraverseAncestry;
pub use bound_identifier::BoundIdentifier;
mod identifier;
use identifier::to_identifier;
mod scoping;
pub use scoping::TraverseScoping;
@ -299,49 +301,73 @@ impl<'a> TraverseCtx<'a> {
///
/// See also comments on [`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`.
#[inline]
pub fn generate_uid(&mut self, name: &str, scope_id: ScopeId, flags: SymbolFlags) -> SymbolId {
self.scoping.generate_uid(name, scope_id, flags)
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 name_atom = self.ast.atom(&name);
// Add binding to scope
let symbol_id =
self.symbols_mut().create_symbol(SPAN, name.clone(), flags, scope_id, NodeId::DUMMY);
self.scopes_mut().add_binding(scope_id, name, symbol_id);
BoundIdentifier::new(name_atom, 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".
///
/// This is a shortcut for `ctx.scoping.generate_uid_in_current_scope`.
#[inline]
pub fn generate_uid_in_current_scope(&mut self, name: &str, flags: SymbolFlags) -> SymbolId {
self.scoping.generate_uid_in_current_scope(name, flags)
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".
///
/// This is a shortcut for `ctx.scoping.generate_uid_in_root_scope`.
#[inline]
pub fn generate_uid_in_root_scope(&mut self, name: &str, flags: SymbolFlags) -> SymbolId {
self.scoping.generate_uid_in_root_scope(name, flags)
pub fn generate_uid_in_root_scope(
&mut self,
name: &str,
flags: SymbolFlags,
) -> BoundIdentifier<'a> {
self.generate_uid(name, self.scopes().root_scope_id(), flags)
}
/// Generate UID based on node.
///
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
/// on how UIDs are generated. There are some potential "gotchas".
/// Recursively gathers the identifying names of a node, and joins them with `$`.
///
/// This is a shortcut for `ctx.scoping.generate_uid_based_on_node`.
/// 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(
&mut self,
node: &Expression<'a>,
scope_id: ScopeId,
flags: SymbolFlags,
) -> SymbolId {
self.scoping.generate_uid_based_on_node(node, scope_id, flags)
) -> BoundIdentifier<'a> {
let mut parts = String::new();
node.gather(&mut |part| {
if !parts.is_empty() {
parts.push('$');
}
parts.push_str(part);
});
let name = if parts.is_empty() { "ref" } else { parts.trim_start_matches('_') };
self.generate_uid(&to_identifier(name.get(..20).unwrap_or(name)), scope_id, flags)
}
/// Generate UID in current scope based on node.
@ -355,8 +381,8 @@ impl<'a> TraverseCtx<'a> {
&mut self,
node: &Expression<'a>,
flags: SymbolFlags,
) -> SymbolId {
self.scoping.generate_uid_in_current_scope_based_on_node(node, flags)
) -> BoundIdentifier<'a> {
self.generate_uid_based_on_node(node, self.current_scope_id(), flags)
}
/// Create a reference bound to a `SymbolId`.

View file

@ -7,14 +7,13 @@ use rustc_hash::FxHashSet;
#[allow(clippy::wildcard_imports)]
use oxc_ast::{ast::*, visit::Visit};
use oxc_semantic::{NodeId, Reference, ScopeTree, SymbolTable};
use oxc_span::{Atom, CompactStr, Span, SPAN};
use oxc_span::{Atom, CompactStr, Span};
use oxc_syntax::{
reference::{ReferenceFlags, ReferenceId},
scope::{ScopeFlags, ScopeId},
symbol::{SymbolFlags, SymbolId},
symbol::SymbolId,
};
use super::{ast_operations::GatherNodeParts, identifier::to_identifier};
use crate::scopes_collector::ChildScopeCollector;
/// Traverse scope context.
@ -229,81 +228,6 @@ impl TraverseScoping {
uid
}
/// Generate UID in provided scope.
///
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
/// on how UIDs are generated. There are some potential "gotchas".
pub fn generate_uid(&mut self, name: &str, scope_id: ScopeId, flags: SymbolFlags) -> SymbolId {
// Get name for UID
let name = self.generate_uid_name(name);
// Add binding to scope
let symbol_id =
self.symbols.create_symbol(SPAN, name.clone(), flags, scope_id, NodeId::DUMMY);
self.scopes.add_binding(scope_id, name, symbol_id);
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".
pub fn generate_uid_in_current_scope(&mut self, name: &str, flags: SymbolFlags) -> SymbolId {
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".
pub fn generate_uid_in_root_scope(&mut self, name: &str, flags: SymbolFlags) -> SymbolId {
self.generate_uid(name, self.scopes.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>
///
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
/// on how UIDs are generated. There are some potential "gotchas".
pub fn generate_uid_based_on_node<'a, T>(
&mut self,
node: &T,
scope_id: ScopeId,
flags: SymbolFlags,
) -> SymbolId
where
T: GatherNodeParts<'a>,
{
let mut parts = String::new();
node.gather(&mut |part| {
if !parts.is_empty() {
parts.push('$');
}
parts.push_str(part);
});
let name = if parts.is_empty() { "ref" } else { parts.trim_start_matches('_') };
self.generate_uid(&to_identifier(name.get(..20).unwrap_or(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".
pub fn generate_uid_in_current_scope_based_on_node<'a, T>(
&mut self,
node: &T,
flags: SymbolFlags,
) -> SymbolId
where
T: GatherNodeParts<'a>,
{
self.generate_uid_based_on_node(node, self.current_scope_id, flags)
}
/// Create a reference bound to a `SymbolId`
pub fn create_bound_reference(
&mut self,

View file

@ -1645,7 +1645,7 @@ x Output mismatch
* regression/4403/input.js
Reference flags mismatch for "_ref":
after transform: ReferenceId(3): ReferenceFlags(Write)
after transform: ReferenceId(2): ReferenceFlags(Write)
rebuilt : ReferenceId(0): ReferenceFlags(Read | Write)