use std::{
cell::{Cell, RefCell},
mem,
};
use rustc_hash::FxHashMap;
use oxc_allocator::Address;
use oxc_ast::{AstKind, ast::*};
use oxc_ast_visit::Visit;
#[cfg(feature = "cfg")]
use oxc_cfg::{
ControlFlowGraphBuilder, CtxCursor, CtxFlags, EdgeType, ErrorEdgeKind, InstructionKind,
IterationInstructionKind, ReturnInstructionKind,
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::{SourceType, Span};
use oxc_str::{Ident, IdentHashMap};
use oxc_syntax::{
node::{NodeFlags, NodeId},
reference::{Reference, ReferenceFlags, ReferenceId},
scope::{ScopeFlags, ScopeId},
symbol::{SymbolFlags, SymbolId},
};
use crate::{
Semantic,
binder::{Binder, ModuleInstanceState},
checker,
class::ClassTableBuilder,
diagnostics::redeclaration,
label::UnusedLabels,
node::AstNodes,
scoping::{Bindings, Scoping},
stats::Stats,
unresolved_stack::UnresolvedReferences,
};
#[cfg(feature = "jsdoc")]
use oxc_jsdoc::JSDocBuilder;
#[cfg(feature = "cfg")]
macro_rules! control_flow {
($self:ident, |$cfg:tt| $body:expr) => {
if let Some($cfg) = &mut $self.cfg { $body } else { Default::default() }
};
}
#[cfg(not(feature = "cfg"))]
macro_rules! control_flow {
($self:ident, |$cfg:tt| $body:expr) => {{
#[expect(clippy::unused_unit)]
{
let _ = $self; ()
}
}};
}
pub struct SemanticBuilder<'a> {
pub(crate) source_text: &'a str,
pub(crate) source_type: SourceType,
pub(crate) errors: RefCell<Vec<OxcDiagnostic>>,
pub(crate) current_node_id: NodeId,
pub(crate) current_node_flags: NodeFlags,
pub(crate) current_scope_id: ScopeId,
pub(crate) current_function_node_id: NodeId,
pub(crate) module_instance_state_cache: FxHashMap<Address, ModuleInstanceState>,
current_reference_flags: ReferenceFlags,
pub(crate) hoisting_variables: FxHashMap<ScopeId, IdentHashMap<'a, SymbolId>>,
pub(crate) nodes: AstNodes<'a>,
pub(crate) scoping: Scoping,
pub(crate) unresolved_references: UnresolvedReferences<'a>,
unresolved_references_checkpoint: usize,
unused_labels: UnusedLabels<'a>,
#[cfg(feature = "jsdoc")]
jsdoc: JSDocBuilder<'a>,
stats: Option<Stats>,
excess_capacity: f64,
enum_eval: bool,
check_syntax_error: bool,
#[cfg(feature = "cfg")]
pub(crate) cfg: Option<ControlFlowGraphBuilder<'a>>,
#[cfg(not(feature = "cfg"))]
#[expect(unused)]
pub(crate) cfg: (),
pub(crate) class_table_builder: ClassTableBuilder<'a>,
#[cfg(feature = "cfg")]
ast_node_records: Vec<NodeId>,
}
pub struct SemanticBuilderReturn<'a> {
pub semantic: Semantic<'a>,
pub errors: Vec<OxcDiagnostic>,
}
impl Default for SemanticBuilder<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'a> SemanticBuilder<'a> {
pub fn new() -> Self {
let scoping = Scoping::default();
let current_scope_id = scoping.root_scope_id();
Self {
source_text: "",
source_type: SourceType::default(),
errors: RefCell::new(vec![]),
current_node_id: NodeId::new(0),
current_node_flags: NodeFlags::empty(),
current_reference_flags: ReferenceFlags::empty(),
current_scope_id,
current_function_node_id: NodeId::ROOT,
module_instance_state_cache: FxHashMap::default(),
nodes: AstNodes::default(),
hoisting_variables: FxHashMap::default(),
scoping,
unresolved_references: UnresolvedReferences::new(),
unresolved_references_checkpoint: 0,
unused_labels: UnusedLabels::default(),
#[cfg(feature = "jsdoc")]
jsdoc: JSDocBuilder::default(),
stats: None,
excess_capacity: 0.0,
enum_eval: false,
check_syntax_error: false,
#[cfg(feature = "cfg")]
cfg: None,
#[cfg(not(feature = "cfg"))]
cfg: (),
class_table_builder: ClassTableBuilder::new(),
#[cfg(feature = "cfg")]
ast_node_records: Vec::new(),
}
}
#[must_use]
pub fn with_check_syntax_error(mut self, yes: bool) -> Self {
self.check_syntax_error = yes;
self
}
#[must_use]
pub fn with_enum_eval(mut self, yes: bool) -> Self {
self.enum_eval = yes;
self
}
#[must_use]
#[cfg(feature = "cfg")]
pub fn with_cfg(mut self, cfg: bool) -> Self {
self.cfg = if cfg { Some(ControlFlowGraphBuilder::default()) } else { None };
self
}
#[cfg(not(feature = "cfg"))]
#[must_use]
pub fn with_cfg(self, _cfg: bool) -> Self {
self
}
#[must_use]
pub fn with_stats(mut self, stats: Stats) -> Self {
self.stats = Some(stats);
self
}
#[must_use]
pub fn with_excess_capacity(mut self, excess_capacity: f64) -> Self {
self.excess_capacity = excess_capacity;
self
}
pub fn build(mut self, program: &'a Program<'a>) -> SemanticBuilderReturn<'a> {
self.source_text = program.source_text;
self.source_type = program.source_type;
#[cfg(feature = "jsdoc")]
{
self.jsdoc = JSDocBuilder::new(self.source_text, &program.comments);
}
#[cfg_attr(not(debug_assertions), expect(unused_variables))]
let (stats, check_stats) = if let Some(stats) = self.stats {
(stats, None)
} else {
let stats = Stats::count(program);
let stats_with_excess = stats.increase_by(self.excess_capacity);
(stats_with_excess, Some(stats))
};
self.nodes.reserve(stats.nodes as usize);
self.scoping.reserve(
stats.symbols as usize,
stats.references as usize,
stats.scopes as usize,
);
self.visit_program(program);
#[cfg(debug_assertions)]
if let Some(stats) = check_stats {
#[expect(clippy::cast_possible_truncation)]
let actual_stats = Stats::new(
self.nodes.len() as u32,
self.scoping.scopes_len() as u32,
self.scoping.symbols_len() as u32,
self.scoping.references.len() as u32,
);
stats.assert_accurate(actual_stats);
}
#[cfg(feature = "jsdoc")]
let jsdoc = {
let result = self.jsdoc.build();
crate::jsdoc::JSDocFinder::new(result.attached, result.not_attached)
};
#[cfg(debug_assertions)]
self.unused_labels.assert_empty();
let semantic = Semantic {
source_text: self.source_text,
source_type: self.source_type,
comments: &program.comments,
irregular_whitespaces: [].into(),
nodes: self.nodes,
scoping: self.scoping,
classes: self.class_table_builder.build(),
#[cfg(feature = "jsdoc")]
jsdoc,
unused_labels: self.unused_labels.labels,
#[cfg(feature = "cfg")]
cfg: self.cfg.map(ControlFlowGraphBuilder::build),
#[cfg(not(feature = "cfg"))]
cfg: (),
};
SemanticBuilderReturn { semantic, errors: self.errors.into_inner() }
}
pub(crate) fn error(&self, error: OxcDiagnostic) {
self.errors.borrow_mut().push(error);
}
pub(crate) fn in_declare_scope(&self) -> bool {
self.source_type.is_typescript_definition()
|| self
.scoping
.scope_ancestors(self.current_scope_id)
.any(|scope_id| self.scoping.scope_flags(scope_id).is_ts_module_block())
}
fn create_ast_node(&mut self, kind: AstKind<'a>) {
#[cfg(not(feature = "jsdoc"))]
let flags = self.current_node_flags;
#[cfg(feature = "jsdoc")]
let mut flags = self.current_node_flags;
#[cfg(feature = "jsdoc")]
if self.jsdoc.retrieve_attached_jsdoc(&kind) {
flags |= NodeFlags::JSDoc;
}
self.current_node_id = self.nodes.add_node(
kind,
self.current_scope_id,
self.current_node_id,
#[cfg(feature = "cfg")]
control_flow!(self, |cfg| cfg.current_node_ix),
flags,
);
self.record_ast_node();
}
#[inline]
fn pop_ast_node(&mut self) {
self.current_node_id = self.nodes.parent_id(self.current_node_id);
}
#[inline]
#[cfg(feature = "cfg")]
fn record_ast_nodes(&mut self) {
if self.cfg.is_some() {
self.ast_node_records.push(NodeId::DUMMY);
}
}
#[inline]
#[cfg(feature = "cfg")]
fn retrieve_recorded_ast_node(&mut self) -> Option<NodeId> {
if self.cfg.is_some() {
Some(self.ast_node_records.pop().expect("there is no ast node record to stop."))
} else {
None
}
}
#[inline]
#[cfg(feature = "cfg")]
fn record_ast_node(&mut self) {
if self.cfg.is_some()
&& let Some(record) = self.ast_node_records.last_mut()
&& *record == NodeId::DUMMY
{
*record = self.current_node_id;
}
}
#[inline]
#[cfg(not(feature = "cfg"))]
#[expect(clippy::unused_self, clippy::needless_pass_by_ref_mut)]
fn record_ast_node(&mut self) {}
#[inline]
pub(crate) fn current_scope_flags(&self) -> ScopeFlags {
self.scoping.scope_flags(self.current_scope_id)
}
#[inline]
pub(crate) fn strict_mode(&self) -> bool {
self.current_scope_flags().is_strict_mode()
}
pub(crate) fn declare_symbol_on_scope(
&mut self,
span: Span,
name: Ident<'a>,
scope_id: ScopeId,
includes: SymbolFlags,
excludes: SymbolFlags,
) -> SymbolId {
if let Some(symbol_id) = self.check_redeclaration(scope_id, span, name, excludes) {
self.add_redeclare_variable(symbol_id, includes, span);
self.scoping.union_symbol_flag(symbol_id, includes);
return symbol_id;
}
let symbol_id =
self.scoping.create_symbol(span, name, includes, scope_id, self.current_node_id);
self.scoping.add_binding(scope_id, name, symbol_id);
symbol_id
}
pub(crate) fn declare_symbol(
&mut self,
span: Span,
name: Ident<'a>,
includes: SymbolFlags,
excludes: SymbolFlags,
) -> SymbolId {
self.declare_symbol_on_scope(span, name, self.current_scope_id, includes, excludes)
}
pub(crate) fn check_redeclaration(
&self,
scope_id: ScopeId,
span: Span,
name: Ident<'_>,
excludes: SymbolFlags,
) -> Option<SymbolId> {
let symbol_id = self.scoping.get_binding(scope_id, name).or_else(|| {
self.hoisting_variables.get(&scope_id).and_then(|symbols| symbols.get(&name).copied())
})?;
if self.scoping.symbol_flags(symbol_id).is_function()
&& self
.nodes
.kind(self.scoping.symbol_declaration(symbol_id))
.as_function()
.is_some_and(Function::is_expression)
{
return None;
}
let flags = self.scoping.symbol_flags(symbol_id);
if flags.intersects(excludes) {
let symbol_span = self.scoping.symbol_span(symbol_id);
self.error(redeclaration(&name, symbol_span, span));
}
Some(symbol_id)
}
#[inline]
pub(crate) fn declare_reference(
&mut self,
name: Ident<'a>,
reference: Reference,
) -> ReferenceId {
let reference_id = self.scoping.create_reference(reference);
self.unresolved_references.push(name, reference_id);
reference_id
}
pub(crate) fn declare_shadow_symbol(
&mut self,
name: Ident<'a>,
span: Span,
scope_id: ScopeId,
includes: SymbolFlags,
) -> SymbolId {
let symbol_id = self.scoping.create_symbol(
span,
name,
includes,
self.current_scope_id,
self.current_node_id,
);
self.scoping.insert_binding(scope_id, name, symbol_id);
symbol_id
}
fn resolve_all_references(&mut self) {
let refs = self.unresolved_references.take();
for (name, reference_id) in refs {
if !self.walk_up_resolve_reference(name, reference_id) {
self.scoping.add_root_unresolved_reference(name, reference_id);
}
}
}
#[expect(clippy::inline_always, reason = "Hot path — called for every reference resolution")]
#[inline(always)]
fn walk_up_resolve_reference(&mut self, name: Ident<'a>, reference_id: ReferenceId) -> bool {
let mut scope_id = Some(self.scoping.references[reference_id].scope_id());
while let Some(sid) = scope_id {
if let Some(symbol_id) = self.scoping.get_binding(sid, name)
&& self.try_resolve_reference(reference_id, symbol_id)
{
return true;
}
scope_id = self.scoping.scope_parent_id(sid);
}
false
}
fn try_resolve_reference(&mut self, reference_id: ReferenceId, symbol_id: SymbolId) -> bool {
let symbol_flags = self.scoping.symbol_flags(symbol_id);
let reference = &mut self.scoping.references[reference_id];
let flags = reference.flags_mut();
let can_resolve = if flags.is_namespace()
&& !flags.is_value()
&& !flags.is_value_as_type()
&& !symbol_flags.can_be_referenced_as_namespace()
{
false
} else {
(flags.is_value() && symbol_flags.can_be_referenced_by_value())
|| (flags.is_type() && symbol_flags.can_be_referenced_by_type())
|| (flags.is_value_as_type() && symbol_flags.can_be_referenced_by_value_as_type())
};
if !can_resolve {
return false;
}
if symbol_flags.is_value() && flags.is_value() {
*flags -= ReferenceFlags::Type;
} else {
*flags = ReferenceFlags::Type;
}
reference.set_symbol_id(symbol_id);
self.scoping.add_resolved_reference(symbol_id, reference_id);
true
}
fn resolve_references_for_current_scope(&mut self) {
let checkpoint = self.unresolved_references_checkpoint;
let refs = self.unresolved_references.slice_from(checkpoint).to_vec();
if refs.is_empty() {
return;
}
self.unresolved_references.truncate(checkpoint);
for (name, reference_id) in refs {
if !self.walk_up_resolve_reference(name, reference_id) {
self.unresolved_references.push(name, reference_id);
}
}
}
pub(crate) fn add_redeclare_variable(
&mut self,
symbol_id: SymbolId,
flags: SymbolFlags,
span: Span,
) {
self.scoping.add_symbol_redeclaration(symbol_id, flags, self.current_node_id, span);
}
}
impl<'a> Visit<'a> for SemanticBuilder<'a> {
fn enter_scope(&mut self, flags: ScopeFlags, scope_id: &Cell<Option<ScopeId>>) {
let parent_scope_id = self.current_scope_id;
let flags = self.scoping.get_new_scope_flags(flags, parent_scope_id);
self.current_scope_id =
self.scoping.add_scope(Some(parent_scope_id), self.current_node_id, flags);
scope_id.set(Some(self.current_scope_id));
}
fn leave_scope(&mut self) {
let parent_id = self.scoping.scope_parent_id(self.current_scope_id);
debug_assert!(parent_id.is_some());
if let Some(parent_id) = parent_id {
if self.scoping.scope_flags(self.current_scope_id).contains_direct_eval() {
self.scoping.scope_flags_mut(parent_id).insert(ScopeFlags::DirectEval);
}
self.current_scope_id = parent_id;
}
}
fn enter_node(&mut self, kind: AstKind<'a>) {
self.create_ast_node(kind);
}
#[expect(
clippy::inline_always,
reason = "enables compile-time match elimination in checker::check"
)]
#[inline(always)]
fn leave_node(&mut self, kind: AstKind<'a>) {
if self.check_syntax_error {
checker::check(kind, self);
}
self.pop_ast_node();
}
fn visit_program(&mut self, program: &Program<'a>) {
let kind = AstKind::Program(self.alloc(program));
#[cfg(feature = "cfg")]
let error_harness = control_flow!(self, |cfg| {
let error_harness = cfg.attach_error_harness(ErrorEdgeKind::Implicit);
let _program_basic_block = cfg.new_basic_block_normal();
error_harness
});
self.current_node_id = self.nodes.add_program_node(
kind,
self.current_scope_id,
#[cfg(feature = "cfg")]
control_flow!(self, |cfg| cfg.current_node_ix),
self.current_node_flags,
);
let mut flags = ScopeFlags::Top;
if self.source_type.is_strict() || program.has_use_strict_directive() {
flags |= ScopeFlags::StrictMode;
}
self.current_scope_id = self.scoping.add_scope(None, self.current_node_id, flags);
program.scope_id.set(Some(self.current_scope_id));
if let Some(hashbang) = &program.hashbang {
self.visit_hashbang(hashbang);
}
for directive in &program.directives {
self.visit_directive(directive);
}
self.visit_statements(&program.body);
control_flow!(self, |cfg| cfg.release_error_harness(error_harness));
self.resolve_all_references();
self.leave_node(kind);
debug_assert!(self.current_function_node_id == NodeId::ROOT);
}
fn visit_break_statement(&mut self, stmt: &BreakStatement<'a>) {
let kind = AstKind::BreakStatement(self.alloc(stmt));
self.enter_node(kind);
if let Some(label) = &stmt.label {
self.unused_labels.reference(label.name);
}
#[cfg(feature = "cfg")]
let node_id = self.current_node_id;
if let Some(break_target) = &stmt.label {
self.visit_label_identifier(break_target);
}
control_flow!(self, |cfg| cfg
.append_break(node_id, stmt.label.as_ref().map(|it| it.name.as_str())));
self.leave_node(kind);
}
fn visit_class(&mut self, class: &Class<'a>) {
let kind = AstKind::Class(self.alloc(class));
self.enter_node(kind);
self.current_node_flags |= NodeFlags::Class;
if class.is_declaration() {
class.bind(self);
}
self.visit_decorators(&class.decorators);
self.enter_scope(ScopeFlags::StrictMode, &class.scope_id);
if class.is_expression() {
class.bind(self);
}
if let Some(id) = &class.id {
self.visit_binding_identifier(id);
}
if let Some(type_parameters) = &class.type_parameters {
self.visit_ts_type_parameter_declaration(type_parameters);
}
if let Some(super_class) = &class.super_class {
self.visit_expression(super_class);
}
if let Some(super_type_parameters) = &class.super_type_arguments {
self.visit_ts_type_parameter_instantiation(super_type_parameters);
}
self.visit_ts_class_implements_list(&class.implements);
self.visit_class_body(&class.body);
self.leave_scope();
self.leave_node(kind);
self.current_node_flags -= NodeFlags::Class;
self.class_table_builder.pop_class();
}
fn visit_block_statement(&mut self, it: &BlockStatement<'a>) {
let kind = AstKind::BlockStatement(self.alloc(it));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
let parent_scope_id = self.current_scope_id;
self.enter_scope(ScopeFlags::empty(), &it.scope_id);
if self.scoping.scope_flags(parent_scope_id).is_catch_clause() {
let parent_bindings = self.scoping.cell.with_dependent_mut(|allocator, cell| {
if cell.bindings[parent_scope_id].is_empty() {
None
} else {
let mut parent_bindings = Bindings::new_in(allocator);
mem::swap(&mut cell.bindings[parent_scope_id], &mut parent_bindings);
let parent_symbol_ids = parent_bindings.values().copied().collect::<Vec<_>>();
cell.bindings[self.current_scope_id] = parent_bindings;
Some(parent_symbol_ids)
}
});
if let Some(parent_bindings) = parent_bindings {
for symbol_id in parent_bindings {
self.scoping.set_symbol_scope_id(symbol_id, self.current_scope_id);
}
}
}
self.visit_statements(&it.body);
self.leave_scope();
self.leave_node(kind);
}
fn visit_continue_statement(&mut self, stmt: &ContinueStatement<'a>) {
let kind = AstKind::ContinueStatement(self.alloc(stmt));
self.enter_node(kind);
if let Some(label) = &stmt.label {
self.unused_labels.reference(label.name);
}
#[cfg(feature = "cfg")]
let node_id = self.current_node_id;
if let Some(continue_target) = &stmt.label {
self.visit_label_identifier(continue_target);
}
control_flow!(self, |cfg| cfg
.append_continue(node_id, stmt.label.as_ref().map(|it| it.name.as_str())));
self.leave_node(kind);
}
fn visit_do_while_statement(&mut self, stmt: &DoWhileStatement<'a>) {
let kind = AstKind::DoWhileStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
#[cfg(feature = "cfg")]
let (before_do_while_stmt_graph_ix, start_body_graph_ix) = control_flow!(self, |cfg| {
let before_do_while_stmt_graph_ix = cfg.current_node_ix;
let start_body_graph_ix = cfg.new_basic_block_normal();
cfg.ctx(None).default().allow_break().allow_continue();
(before_do_while_stmt_graph_ix, start_body_graph_ix)
});
self.visit_statement(&stmt.body);
#[cfg(feature = "cfg")]
let (after_body_graph_ix, start_of_condition_graph_ix) = control_flow!(self, |cfg| {
let after_body_graph_ix = cfg.current_node_ix;
let start_of_condition_graph_ix = cfg.new_basic_block_normal();
(after_body_graph_ix, start_of_condition_graph_ix)
});
#[cfg(feature = "cfg")]
self.record_ast_nodes();
self.visit_expression(&stmt.test);
#[cfg(feature = "cfg")]
let test_node_id = self.retrieve_recorded_ast_node();
control_flow!(self, |cfg| {
cfg.append_condition_to(start_of_condition_graph_ix, test_node_id);
let end_of_condition_graph_ix = cfg.current_node_ix;
let end_do_while_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_do_while_stmt_graph_ix, start_body_graph_ix, EdgeType::Normal);
cfg.add_edge(after_body_graph_ix, start_of_condition_graph_ix, EdgeType::Normal);
cfg.add_edge(end_of_condition_graph_ix, end_do_while_graph_ix, EdgeType::Normal);
cfg.add_edge(end_of_condition_graph_ix, start_body_graph_ix, EdgeType::Backedge);
cfg.ctx(None)
.mark_break(end_do_while_graph_ix)
.mark_continue(start_of_condition_graph_ix)
.resolve_with_upper_label();
});
self.leave_node(kind);
}
fn visit_logical_expression(&mut self, expr: &LogicalExpression<'a>) {
let kind = AstKind::LogicalExpression(self.alloc(expr));
self.enter_node(kind);
#[cfg(feature = "cfg")]
let (before_logical_graph_ix, start_of_condition_graph_ix) = control_flow!(self, |cfg| {
let before_logical_graph_ix = cfg.current_node_ix;
let start_of_condition_graph_ix = cfg.new_basic_block_normal();
(before_logical_graph_ix, start_of_condition_graph_ix)
});
#[cfg(feature = "cfg")]
self.record_ast_nodes();
self.visit_expression(&expr.left);
#[cfg(feature = "cfg")]
let left_node_id = self.retrieve_recorded_ast_node();
#[cfg(feature = "cfg")]
let (left_expr_end_ix, right_expr_start_ix) = control_flow!(self, |cfg| {
cfg.append_condition_to(start_of_condition_graph_ix, left_node_id);
let left_expr_end_ix = cfg.current_node_ix;
let right_expr_start_ix = cfg.new_basic_block_normal();
(left_expr_end_ix, right_expr_start_ix)
});
self.visit_expression(&expr.right);
control_flow!(self, |cfg| {
let right_expr_end_ix = cfg.current_node_ix;
let after_logical_expr_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_logical_graph_ix, start_of_condition_graph_ix, EdgeType::Normal);
cfg.add_edge(left_expr_end_ix, right_expr_start_ix, EdgeType::Normal);
cfg.add_edge(left_expr_end_ix, after_logical_expr_ix, EdgeType::Normal);
cfg.add_edge(right_expr_end_ix, after_logical_expr_ix, EdgeType::Normal);
});
self.leave_node(kind);
}
fn visit_assignment_expression(&mut self, expr: &AssignmentExpression<'a>) {
let kind = AstKind::AssignmentExpression(self.alloc(expr));
self.enter_node(kind);
if !expr.operator.is_assign() {
self.current_reference_flags = ReferenceFlags::read_write();
}
#[cfg(feature = "cfg")]
let (before_assignment_graph_ix, start_of_condition_graph_ix) =
control_flow!(self, |cfg| {
if expr.operator.is_logical() {
let before_assignment_graph_ix = cfg.current_node_ix;
let start_of_condition_graph_ix = cfg.new_basic_block_normal();
(Some(before_assignment_graph_ix), Some(start_of_condition_graph_ix))
} else {
(None, None)
}
});
#[cfg(feature = "cfg")]
if expr.operator.is_logical() {
self.record_ast_nodes();
}
self.visit_assignment_target(&expr.left);
#[cfg(feature = "cfg")]
let target_node_id =
if expr.operator.is_logical() { self.retrieve_recorded_ast_node() } else { None };
#[cfg(feature = "cfg")]
let cfg_ixs = control_flow!(self, |cfg| {
if expr.operator.is_logical() {
if let Some(condition_ix) = start_of_condition_graph_ix {
cfg.append_condition_to(condition_ix, target_node_id);
}
let target_end_ix = cfg.current_node_ix;
let expr_start_ix = cfg.new_basic_block_normal();
Some((target_end_ix, expr_start_ix))
} else {
None
}
});
self.visit_expression(&expr.right);
control_flow!(self, |cfg| {
if let Some((target_end_ix, expr_start_ix)) = cfg_ixs {
let expr_end_ix = cfg.current_node_ix;
let after_assignment_ix = cfg.new_basic_block_normal();
if let Some((before_ix, condition_ix)) =
before_assignment_graph_ix.zip(start_of_condition_graph_ix)
{
cfg.add_edge(before_ix, condition_ix, EdgeType::Normal);
}
cfg.add_edge(target_end_ix, expr_start_ix, EdgeType::Normal);
cfg.add_edge(target_end_ix, after_assignment_ix, EdgeType::Normal);
cfg.add_edge(expr_end_ix, after_assignment_ix, EdgeType::Normal);
}
});
self.leave_node(kind);
}
fn visit_conditional_expression(&mut self, expr: &ConditionalExpression<'a>) {
let kind = AstKind::ConditionalExpression(self.alloc(expr));
self.enter_node(kind);
#[cfg(feature = "cfg")]
let (before_conditional_graph_ix, start_of_condition_graph_ix) =
control_flow!(self, |cfg| {
let before_conditional_graph_ix = cfg.current_node_ix;
let start_of_condition_graph_ix = cfg.new_basic_block_normal();
(before_conditional_graph_ix, start_of_condition_graph_ix)
});
#[cfg(feature = "cfg")]
self.record_ast_nodes();
let saved_flags = self.current_reference_flags;
self.current_reference_flags -= ReferenceFlags::MemberWriteTarget;
self.visit_expression(&expr.test);
self.current_reference_flags = saved_flags;
#[cfg(feature = "cfg")]
let test_node_id = self.retrieve_recorded_ast_node();
#[cfg(feature = "cfg")]
let (after_condition_graph_ix, before_consequent_expr_graph_ix) =
control_flow!(self, |cfg| {
cfg.append_condition_to(start_of_condition_graph_ix, test_node_id);
let after_condition_graph_ix = cfg.current_node_ix;
let before_consequent_expr_graph_ix = cfg.new_basic_block_normal();
(after_condition_graph_ix, before_consequent_expr_graph_ix)
});
self.visit_expression(&expr.consequent);
#[cfg(feature = "cfg")]
let (after_consequent_expr_graph_ix, start_alternate_graph_ix) =
control_flow!(self, |cfg| {
let after_consequent_expr_graph_ix = cfg.current_node_ix;
let start_alternate_graph_ix = cfg.new_basic_block_normal();
(after_consequent_expr_graph_ix, start_alternate_graph_ix)
});
self.visit_expression(&expr.alternate);
control_flow!(self, |cfg| {
let after_alternate_graph_ix = cfg.current_node_ix;
let after_conditional_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(
before_conditional_graph_ix,
start_of_condition_graph_ix,
EdgeType::Normal,
);
cfg.add_edge(
after_consequent_expr_graph_ix,
after_conditional_graph_ix,
EdgeType::Normal,
);
cfg.add_edge(after_condition_graph_ix, before_consequent_expr_graph_ix, EdgeType::Jump);
cfg.add_edge(after_condition_graph_ix, start_alternate_graph_ix, EdgeType::Normal);
cfg.add_edge(after_alternate_graph_ix, after_conditional_graph_ix, EdgeType::Normal);
});
self.leave_node(kind);
}
fn visit_for_statement(&mut self, stmt: &ForStatement<'a>) {
let kind = AstKind::ForStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.enter_scope(ScopeFlags::empty(), &stmt.scope_id);
if let Some(init) = &stmt.init {
self.visit_for_statement_init(init);
}
#[cfg(feature = "cfg")]
let (before_for_graph_ix, test_graph_ix) = control_flow!(self, |cfg| {
let before_for_graph_ix = cfg.current_node_ix;
let test_graph_ix = cfg.new_basic_block_normal();
(before_for_graph_ix, test_graph_ix)
});
if let Some(test) = &stmt.test {
#[cfg(feature = "cfg")]
self.record_ast_nodes();
self.visit_expression(test);
#[cfg(feature = "cfg")]
let test_node_id = self.retrieve_recorded_ast_node();
control_flow!(self, |cfg| cfg.append_condition_to(test_graph_ix, test_node_id));
}
#[cfg(feature = "cfg")]
let (after_test_graph_ix, update_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal()));
if let Some(update) = &stmt.update {
self.visit_expression(update);
}
#[cfg(feature = "cfg")]
let before_body_graph_ix = control_flow!(self, |cfg| {
let before_body_graph_ix = cfg.new_basic_block_normal();
cfg.ctx(None).default().allow_break().allow_continue();
before_body_graph_ix
});
self.visit_statement(&stmt.body);
control_flow!(self, |cfg| {
let after_body_graph_ix = cfg.current_node_ix;
let after_for_stmt = cfg.new_basic_block_normal();
cfg.add_edge(before_for_graph_ix, test_graph_ix, EdgeType::Normal);
cfg.add_edge(after_test_graph_ix, before_body_graph_ix, EdgeType::Jump);
cfg.add_edge(after_body_graph_ix, update_graph_ix, EdgeType::Backedge);
cfg.add_edge(update_graph_ix, test_graph_ix, EdgeType::Backedge);
cfg.add_edge(after_test_graph_ix, after_for_stmt, EdgeType::Normal);
cfg.ctx(None)
.mark_break(after_for_stmt)
.mark_continue(update_graph_ix)
.resolve_with_upper_label();
});
self.leave_scope();
self.leave_node(kind);
}
fn visit_for_in_statement(&mut self, stmt: &ForInStatement<'a>) {
let kind = AstKind::ForInStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.enter_scope(ScopeFlags::empty(), &stmt.scope_id);
self.visit_for_statement_left(&stmt.left);
#[cfg(feature = "cfg")]
let (before_for_stmt_graph_ix, start_prepare_cond_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal(),));
#[cfg(feature = "cfg")]
self.record_ast_nodes();
self.visit_expression(&stmt.right);
#[cfg(feature = "cfg")]
let right_node_id = self.retrieve_recorded_ast_node();
#[cfg(feature = "cfg")]
let (end_of_prepare_cond_graph_ix, iteration_graph_ix, body_graph_ix) =
control_flow!(self, |cfg| {
let end_of_prepare_cond_graph_ix = cfg.current_node_ix;
let iteration_graph_ix = cfg.new_basic_block_normal();
cfg.append_iteration(right_node_id, IterationInstructionKind::In);
let body_graph_ix = cfg.new_basic_block_normal();
cfg.ctx(None).default().allow_break().allow_continue();
(end_of_prepare_cond_graph_ix, iteration_graph_ix, body_graph_ix)
});
self.visit_statement(&stmt.body);
control_flow!(self, |cfg| {
let end_of_body_graph_ix = cfg.current_node_ix;
let after_for_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_for_stmt_graph_ix, start_prepare_cond_graph_ix, EdgeType::Normal);
cfg.add_edge(end_of_prepare_cond_graph_ix, iteration_graph_ix, EdgeType::Normal);
cfg.add_edge(iteration_graph_ix, body_graph_ix, EdgeType::Jump);
cfg.add_edge(end_of_body_graph_ix, iteration_graph_ix, EdgeType::Backedge);
cfg.add_edge(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal);
cfg.ctx(None)
.mark_break(after_for_graph_ix)
.mark_continue(iteration_graph_ix)
.resolve_with_upper_label();
});
self.leave_scope();
self.leave_node(kind);
}
fn visit_for_of_statement(&mut self, stmt: &ForOfStatement<'a>) {
let kind = AstKind::ForOfStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.enter_scope(ScopeFlags::empty(), &stmt.scope_id);
self.visit_for_statement_left(&stmt.left);
#[cfg(feature = "cfg")]
let (before_for_stmt_graph_ix, start_prepare_cond_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal()));
#[cfg(feature = "cfg")]
self.record_ast_nodes();
self.visit_expression(&stmt.right);
#[cfg(feature = "cfg")]
let right_node_id = self.retrieve_recorded_ast_node();
#[cfg(feature = "cfg")]
let (end_of_prepare_cond_graph_ix, iteration_graph_ix, body_graph_ix) =
control_flow!(self, |cfg| {
let end_of_prepare_cond_graph_ix = cfg.current_node_ix;
let iteration_graph_ix = cfg.new_basic_block_normal();
cfg.append_iteration(right_node_id, IterationInstructionKind::Of);
let body_graph_ix = cfg.new_basic_block_normal();
cfg.ctx(None).default().allow_break().allow_continue();
(end_of_prepare_cond_graph_ix, iteration_graph_ix, body_graph_ix)
});
self.visit_statement(&stmt.body);
control_flow!(self, |cfg| {
let end_of_body_graph_ix = cfg.current_node_ix;
let after_for_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_for_stmt_graph_ix, start_prepare_cond_graph_ix, EdgeType::Normal);
cfg.add_edge(end_of_prepare_cond_graph_ix, iteration_graph_ix, EdgeType::Normal);
cfg.add_edge(iteration_graph_ix, body_graph_ix, EdgeType::Jump);
cfg.add_edge(end_of_body_graph_ix, iteration_graph_ix, EdgeType::Backedge);
cfg.add_edge(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal);
cfg.ctx(None)
.mark_break(after_for_graph_ix)
.mark_continue(iteration_graph_ix)
.resolve_with_upper_label();
});
self.leave_scope();
self.leave_node(kind);
}
fn visit_if_statement(&mut self, stmt: &IfStatement<'a>) {
let kind = AstKind::IfStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
#[cfg(feature = "cfg")]
let (before_if_stmt_graph_ix, start_of_condition_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal(),));
#[cfg(feature = "cfg")]
self.record_ast_nodes();
self.visit_expression(&stmt.test);
#[cfg(feature = "cfg")]
let test_node_id = self.retrieve_recorded_ast_node();
#[cfg(feature = "cfg")]
let (after_test_graph_ix, before_consequent_stmt_graph_ix) = control_flow!(self, |cfg| {
cfg.append_condition_to(start_of_condition_graph_ix, test_node_id);
(cfg.current_node_ix, cfg.new_basic_block_normal())
});
self.visit_statement(&stmt.consequent);
#[cfg(feature = "cfg")]
let after_consequent_stmt_graph_ix = control_flow!(self, |cfg| cfg.current_node_ix);
#[cfg(feature = "cfg")]
let else_graph_ix = if let Some(alternate) = &stmt.alternate {
let else_graph_ix = control_flow!(self, |cfg| cfg.new_basic_block_normal());
self.visit_statement(alternate);
control_flow!(self, |cfg| Some((else_graph_ix, cfg.current_node_ix)))
} else {
None
};
#[cfg(not(feature = "cfg"))]
if let Some(alternate) = &stmt.alternate {
self.visit_statement(alternate);
}
control_flow!(self, |cfg| {
let after_if_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_if_stmt_graph_ix, start_of_condition_graph_ix, EdgeType::Normal);
cfg.add_edge(after_test_graph_ix, before_consequent_stmt_graph_ix, EdgeType::Jump);
cfg.add_edge(after_consequent_stmt_graph_ix, after_if_graph_ix, EdgeType::Normal);
if let Some((start_of_alternate_stmt_graph_ix, after_alternate_stmt_graph_ix)) =
else_graph_ix
{
cfg.add_edge(after_test_graph_ix, start_of_alternate_stmt_graph_ix, EdgeType::Jump);
cfg.add_edge(after_alternate_stmt_graph_ix, after_if_graph_ix, EdgeType::Normal);
} else {
cfg.add_edge(after_test_graph_ix, after_if_graph_ix, EdgeType::Jump);
}
});
self.leave_node(kind);
}
fn visit_labeled_statement(&mut self, stmt: &LabeledStatement<'a>) {
let kind = AstKind::LabeledStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.unused_labels.add(stmt.label.name, self.current_node_id);
#[cfg(feature = "cfg")]
let label = &stmt.label.name;
control_flow!(self, |cfg| {
let ctx = cfg.ctx(Some(label.as_str())).default().allow_break();
if stmt.body.is_iteration_statement() {
ctx.allow_continue();
}
});
self.visit_label_identifier(&stmt.label);
self.visit_statement(&stmt.body);
control_flow!(self, |cfg| {
let after_body_graph_ix = cfg.current_node_ix;
let after_labeled_stmt_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(after_body_graph_ix, after_labeled_stmt_graph_ix, EdgeType::Normal);
cfg.ctx(Some(label.as_str())).mark_break(after_labeled_stmt_graph_ix).resolve();
});
self.unused_labels.mark_unused();
self.leave_node(kind);
}
fn visit_return_statement(&mut self, stmt: &ReturnStatement<'a>) {
let kind = AstKind::ReturnStatement(self.alloc(stmt));
self.enter_node(kind);
#[cfg(feature = "cfg")]
let node_id = self.current_node_id;
#[cfg(feature = "cfg")]
let ret_kind = if let Some(arg) = &stmt.argument {
self.visit_expression(arg);
ReturnInstructionKind::NotImplicitUndefined
} else {
ReturnInstructionKind::ImplicitUndefined
};
#[cfg(not(feature = "cfg"))]
if let Some(arg) = &stmt.argument {
self.visit_expression(arg);
}
control_flow!(self, |cfg| {
cfg.push_return(ret_kind, Some(node_id));
cfg.append_unreachable();
});
self.leave_node(kind);
}
fn visit_switch_statement(&mut self, stmt: &SwitchStatement<'a>) {
let kind = AstKind::SwitchStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.visit_expression(&stmt.discriminant);
self.enter_scope(ScopeFlags::empty(), &stmt.scope_id);
#[cfg(feature = "cfg")]
let discriminant_graph_ix = control_flow!(self, |cfg| {
let discriminant_graph_ix = cfg.current_node_ix;
cfg.ctx(None).default().allow_break();
discriminant_graph_ix
});
#[cfg(feature = "cfg")]
let mut switch_case_graph_spans = vec![];
#[cfg(feature = "cfg")]
let mut have_default_case = false;
for case in &stmt.cases {
#[cfg(feature = "cfg")]
let before_case_graph_ix = control_flow!(self, |cfg| cfg.new_basic_block_normal());
self.visit_switch_case(case);
#[cfg(feature = "cfg")]
if case.is_default_case() {
have_default_case = true;
}
control_flow!(self, |cfg| switch_case_graph_spans
.push((before_case_graph_ix, cfg.current_node_ix)));
}
control_flow!(self, |cfg| {
for i in 0..switch_case_graph_spans.len() {
let case_graph_span = switch_case_graph_spans[i];
for y in switch_case_graph_spans.iter().skip(i + 1) {
cfg.add_edge(case_graph_span.0, y.0, EdgeType::Normal);
}
if switch_case_graph_spans.len() > i + 1 {
let (_, end_of_switch_case) = switch_case_graph_spans[i];
let (next_switch_statement_condition, _) = switch_case_graph_spans[i + 1];
cfg.add_edge(
end_of_switch_case,
next_switch_statement_condition,
EdgeType::Normal,
);
}
cfg.add_edge(discriminant_graph_ix, case_graph_span.0, EdgeType::Normal);
}
let end_of_switch_case_statement = cfg.new_basic_block_normal();
if let Some(last) = switch_case_graph_spans.last() {
cfg.add_edge(last.1, end_of_switch_case_statement, EdgeType::Normal);
}
if !have_default_case {
cfg.add_edge(discriminant_graph_ix, end_of_switch_case_statement, EdgeType::Normal);
}
cfg.ctx(None).mark_break(end_of_switch_case_statement).resolve();
});
self.leave_scope();
self.leave_node(kind);
}
fn visit_switch_case(&mut self, case: &SwitchCase<'a>) {
let kind = AstKind::SwitchCase(self.alloc(case));
self.enter_node(kind);
if let Some(expr) = &case.test {
#[cfg(feature = "cfg")]
let condition_block_ix = control_flow!(self, |cfg| cfg.current_node_ix);
#[cfg(feature = "cfg")]
self.record_ast_nodes();
self.visit_expression(expr);
#[cfg(feature = "cfg")]
let test_node_id = self.retrieve_recorded_ast_node();
control_flow!(self, |cfg| cfg.append_condition_to(condition_block_ix, test_node_id));
}
control_flow!(self, |cfg| {
let after_test_graph_ix = cfg.current_node_ix;
let statements_in_switch_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(after_test_graph_ix, statements_in_switch_graph_ix, EdgeType::Jump);
});
self.visit_statements(&case.consequent);
self.leave_node(kind);
}
fn visit_throw_statement(&mut self, stmt: &ThrowStatement<'a>) {
let kind = AstKind::ThrowStatement(self.alloc(stmt));
self.enter_node(kind);
#[cfg(feature = "cfg")]
let node_id = self.current_node_id;
self.visit_expression(&stmt.argument);
control_flow!(self, |cfg| cfg.append_throw(node_id));
self.leave_node(kind);
}
fn visit_try_statement(&mut self, stmt: &TryStatement<'a>) {
let kind = AstKind::TryStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
#[cfg(feature = "cfg")]
let (
before_try_statement_graph_ix,
error_harness,
before_finalizer_graph_ix,
before_try_block_graph_ix,
) = control_flow!(self, |cfg| {
let before_try_statement_graph_ix = cfg.current_node_ix;
let error_harness =
stmt.handler.as_ref().map(|_| cfg.attach_error_harness(ErrorEdgeKind::Explicit));
let before_finalizer_graph_ix = stmt.finalizer.as_ref().map(|_| cfg.attach_finalizer());
let before_try_block_graph_ix = cfg.new_basic_block_normal();
(
before_try_statement_graph_ix,
error_harness,
before_finalizer_graph_ix,
before_try_block_graph_ix,
)
});
self.visit_block_statement(&stmt.block);
#[cfg(feature = "cfg")]
let after_try_block_graph_ix = control_flow!(self, |cfg| cfg.current_node_ix);
#[cfg(feature = "cfg")]
let catch_block_end_ix = if let Some(handler) = &stmt.handler {
control_flow!(self, |cfg| {
let Some(error_harness) = error_harness else {
unreachable!("we always create an error harness if we have a catch block.");
};
cfg.release_error_harness(error_harness);
let catch_block_start_ix = cfg.new_basic_block_normal();
cfg.add_edge(error_harness, catch_block_start_ix, EdgeType::Normal);
});
self.visit_catch_clause(handler);
control_flow!(self, |cfg| Some(cfg.current_node_ix))
} else {
None
};
#[cfg(not(feature = "cfg"))]
if let Some(handler) = &stmt.handler {
self.visit_catch_clause(handler);
}
#[cfg(feature = "cfg")]
let finally_block_end_ix = if let Some(finalizer) = &stmt.finalizer {
control_flow!(self, |cfg| {
let Some(before_finalizer_graph_ix) = before_finalizer_graph_ix else {
unreachable!("we always create a finalizer when there is a finally block.");
};
cfg.release_finalizer(before_finalizer_graph_ix);
let start_finally_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_finalizer_graph_ix, start_finally_graph_ix, EdgeType::Normal);
});
self.visit_block_statement(finalizer);
control_flow!(self, |cfg| Some(cfg.current_node_ix))
} else {
None
};
#[cfg(not(feature = "cfg"))]
if let Some(finalizer) = &stmt.finalizer {
self.visit_block_statement(finalizer);
}
control_flow!(self, |cfg| {
cfg.add_edge(
before_try_statement_graph_ix,
before_try_block_graph_ix,
EdgeType::Normal,
);
if let Some(catch_block_end_ix) = catch_block_end_ix
&& finally_block_end_ix.is_none()
{
let after_try_statement_block_ix = cfg.new_basic_block_normal();
cfg.add_edge(
after_try_block_graph_ix,
after_try_statement_block_ix,
EdgeType::Normal,
);
cfg.add_edge(catch_block_end_ix, after_try_statement_block_ix, EdgeType::Normal);
}
if let Some(finally_block_end_ix) = finally_block_end_ix {
let after_try_statement_block_ix = cfg.new_basic_block_normal();
if catch_block_end_ix.is_some() {
cfg.add_edge(
finally_block_end_ix,
after_try_statement_block_ix,
EdgeType::Normal,
);
} else {
cfg.add_edge(
finally_block_end_ix,
after_try_statement_block_ix,
if cfg.basic_block(after_try_block_graph_ix).is_unreachable() {
EdgeType::Unreachable
} else {
EdgeType::Join
},
);
}
}
});
self.leave_node(kind);
}
fn visit_while_statement(&mut self, stmt: &WhileStatement<'a>) {
let kind = AstKind::WhileStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
#[cfg(feature = "cfg")]
let (before_while_stmt_graph_ix, condition_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal()));
#[cfg(feature = "cfg")]
self.record_ast_nodes();
self.visit_expression(&stmt.test);
#[cfg(feature = "cfg")]
let test_node_id = self.retrieve_recorded_ast_node();
#[cfg(feature = "cfg")]
let body_graph_ix = control_flow!(self, |cfg| {
cfg.append_condition_to(condition_graph_ix, test_node_id);
let body_graph_ix = cfg.new_basic_block_normal();
cfg.ctx(None).default().allow_break().allow_continue();
body_graph_ix
});
self.visit_statement(&stmt.body);
control_flow!(self, |cfg| {
let after_body_graph_ix = cfg.current_node_ix;
let after_while_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_while_stmt_graph_ix, condition_graph_ix, EdgeType::Normal);
cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Jump);
cfg.add_edge(after_body_graph_ix, condition_graph_ix, EdgeType::Backedge);
cfg.add_edge(condition_graph_ix, after_while_graph_ix, EdgeType::Normal);
cfg.ctx(None)
.mark_break(after_while_graph_ix)
.mark_continue(condition_graph_ix)
.resolve_with_upper_label();
});
self.leave_node(kind);
}
fn visit_with_statement(&mut self, stmt: &WithStatement<'a>) {
let kind = AstKind::WithStatement(self.alloc(stmt));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
#[cfg(feature = "cfg")]
let (before_with_stmt_graph_ix, condition_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal()));
self.visit_expression(&stmt.object);
self.enter_scope(ScopeFlags::With, &stmt.scope_id);
#[cfg(feature = "cfg")]
let body_graph_ix = control_flow!(self, |cfg| cfg.new_basic_block_normal());
self.visit_statement(&stmt.body);
control_flow!(self, |cfg| {
let after_body_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_with_stmt_graph_ix, condition_graph_ix, EdgeType::Normal);
cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Normal);
cfg.add_edge(body_graph_ix, after_body_graph_ix, EdgeType::Normal);
cfg.add_edge(condition_graph_ix, after_body_graph_ix, EdgeType::Normal);
});
self.leave_scope();
self.leave_node(kind);
}
fn visit_function(&mut self, func: &Function<'a>, flags: ScopeFlags) {
#[cfg(feature = "cfg")]
let (before_function_graph_ix, error_harness, function_graph_ix) =
control_flow!(self, |cfg| {
let before_function_graph_ix = cfg.current_node_ix;
cfg.push_finalization_stack();
let error_harness = cfg.attach_error_harness(ErrorEdgeKind::Implicit);
let function_graph_ix = cfg.new_basic_block_function();
cfg.ctx(None).new_function();
(before_function_graph_ix, error_harness, function_graph_ix)
});
let kind = AstKind::Function(self.alloc(func));
self.enter_node(kind);
let parent_function_node_id = self.current_function_node_id;
self.current_function_node_id = self.current_node_id;
if func.is_declaration() {
func.bind(self);
}
self.enter_scope(
{
let mut flags = flags;
if func.has_use_strict_directive() {
flags |= ScopeFlags::StrictMode;
}
flags
},
&func.scope_id,
);
if func.is_expression() {
func.bind(self);
}
if let Some(id) = &func.id {
self.visit_binding_identifier(id);
}
control_flow!(self, |cfg| cfg.add_edge(
before_function_graph_ix,
function_graph_ix,
EdgeType::NewFunction
));
let saved_checkpoint = self.unresolved_references_checkpoint;
self.unresolved_references_checkpoint = self.unresolved_references.checkpoint();
if let Some(type_parameters) = &func.type_parameters {
self.visit_ts_type_parameter_declaration(type_parameters);
}
if let Some(this_param) = &func.this_param {
self.visit_ts_this_parameter(this_param);
}
self.visit_formal_parameters(&func.params);
if let Some(return_type) = &func.return_type {
self.visit_ts_type_annotation(return_type);
}
if func.params.has_parameter() || func.return_type.is_some() {
self.resolve_references_for_current_scope();
}
self.unresolved_references_checkpoint = saved_checkpoint;
if let Some(body) = &func.body {
self.visit_function_body(body);
}
control_flow!(self, |cfg| {
let c = cfg.current_basic_block();
if !matches!(
c.instructions().last().map(|inst| &inst.kind),
Some(InstructionKind::Unreachable)
) {
cfg.push_implicit_return();
}
cfg.ctx(None).resolve_expect(CtxFlags::FUNCTION);
cfg.release_error_harness(error_harness);
cfg.pop_finalization_stack();
let after_function_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_function_graph_ix, after_function_graph_ix, EdgeType::Normal);
});
self.leave_scope();
self.leave_node(kind);
self.current_function_node_id = parent_function_node_id;
}
fn visit_arrow_function_expression(&mut self, expr: &ArrowFunctionExpression<'a>) {
#[cfg(feature = "cfg")]
let (current_node_ix, error_harness, function_graph_ix) = control_flow!(self, |cfg| {
let current_node_ix = cfg.current_node_ix;
cfg.push_finalization_stack();
let error_harness = cfg.attach_error_harness(ErrorEdgeKind::Implicit);
let function_graph_ix = cfg.new_basic_block_function();
cfg.ctx(None).new_function();
(current_node_ix, error_harness, function_graph_ix)
});
let kind = AstKind::ArrowFunctionExpression(self.alloc(expr));
self.enter_node(kind);
self.enter_scope(
{
let mut flags = ScopeFlags::Function | ScopeFlags::Arrow;
if expr.has_use_strict_directive() {
flags |= ScopeFlags::StrictMode;
}
flags
},
&expr.scope_id,
);
let saved_checkpoint = self.unresolved_references_checkpoint;
self.unresolved_references_checkpoint = self.unresolved_references.checkpoint();
if let Some(parameters) = &expr.type_parameters {
self.visit_ts_type_parameter_declaration(parameters);
}
self.visit_formal_parameters(&expr.params);
control_flow!(self, |cfg| cfg.add_edge(
current_node_ix,
function_graph_ix,
EdgeType::NewFunction
));
if let Some(return_type) = &expr.return_type {
self.visit_ts_type_annotation(return_type);
}
if expr.params.has_parameter() || expr.return_type.is_some() {
self.resolve_references_for_current_scope();
}
self.unresolved_references_checkpoint = saved_checkpoint;
self.visit_function_body(&expr.body);
control_flow!(self, |cfg| {
let c = cfg.current_basic_block();
if !matches!(
c.instructions().last().map(|inst| &inst.kind),
Some(InstructionKind::Unreachable)
) {
cfg.push_implicit_return();
}
cfg.ctx(None).resolve_expect(CtxFlags::FUNCTION);
cfg.release_error_harness(error_harness);
cfg.pop_finalization_stack();
cfg.current_node_ix = current_node_ix;
});
self.leave_scope();
self.leave_node(kind);
}
fn visit_update_expression(&mut self, it: &UpdateExpression<'a>) {
let kind = AstKind::UpdateExpression(self.alloc(it));
self.enter_node(kind);
self.current_reference_flags = ReferenceFlags::read_write();
self.visit_simple_assignment_target(&it.argument);
self.leave_node(kind);
}
fn visit_unary_expression(&mut self, it: &UnaryExpression<'a>) {
let kind = AstKind::UnaryExpression(self.alloc(it));
self.enter_node(kind);
if it.operator == UnaryOperator::Delete && it.argument.is_member_expression() {
self.current_reference_flags = ReferenceFlags::Write;
}
self.visit_expression(&it.argument);
self.leave_node(kind);
}
fn visit_member_expression(&mut self, it: &MemberExpression<'a>) {
if self.current_reference_flags.is_write() {
self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::MemberWriteTarget;
} else {
self.current_reference_flags -= ReferenceFlags::Write;
}
match it {
MemberExpression::ComputedMemberExpression(it) => {
self.visit_computed_member_expression(it);
}
MemberExpression::StaticMemberExpression(it) => self.visit_static_member_expression(it),
MemberExpression::PrivateFieldExpression(it) => self.visit_private_field_expression(it),
}
self.current_reference_flags = ReferenceFlags::empty();
}
fn visit_simple_assignment_target(&mut self, it: &SimpleAssignmentTarget<'a>) {
if !self.current_reference_flags.is_write() {
self.current_reference_flags = ReferenceFlags::Write;
}
match it {
SimpleAssignmentTarget::AssignmentTargetIdentifier(it) => {
self.visit_identifier_reference(it);
}
SimpleAssignmentTarget::TSAsExpression(it) => {
self.visit_ts_as_expression(it);
}
SimpleAssignmentTarget::TSSatisfiesExpression(it) => {
self.visit_ts_satisfies_expression(it);
}
SimpleAssignmentTarget::TSNonNullExpression(it) => {
self.visit_ts_non_null_expression(it);
}
SimpleAssignmentTarget::TSTypeAssertion(it) => {
self.visit_ts_type_assertion(it);
}
match_member_expression!(SimpleAssignmentTarget) => {
self.visit_member_expression(it.to_member_expression());
}
}
}
fn visit_assignment_target_property_identifier(
&mut self,
it: &AssignmentTargetPropertyIdentifier<'a>,
) {
let kind = AstKind::AssignmentTargetPropertyIdentifier(self.alloc(it));
self.enter_node(kind);
self.current_reference_flags = ReferenceFlags::Write;
self.visit_identifier_reference(&it.binding);
if let Some(init) = &it.init {
self.visit_expression(init);
}
self.leave_node(kind);
}
fn visit_export_default_declaration_kind(&mut self, it: &ExportDefaultDeclarationKind<'a>) {
match it {
ExportDefaultDeclarationKind::FunctionDeclaration(it) => {
let flags = ScopeFlags::Function;
self.visit_function(it, flags);
}
ExportDefaultDeclarationKind::ClassDeclaration(it) => self.visit_class(it),
ExportDefaultDeclarationKind::TSInterfaceDeclaration(it) => {
self.visit_ts_interface_declaration(it);
}
ExportDefaultDeclarationKind::Identifier(it) => {
self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type;
self.visit_identifier_reference(it);
}
match_expression!(ExportDefaultDeclarationKind) => {
self.visit_expression(it.to_expression());
}
}
}
fn visit_export_named_declaration(&mut self, it: &ExportNamedDeclaration<'a>) {
let kind = AstKind::ExportNamedDeclaration(self.alloc(it));
self.enter_node(kind);
self.visit_span(&it.span);
if let Some(declaration) = &it.declaration {
self.visit_declaration(declaration);
}
if let Some(source) = &it.source {
self.visit_string_literal(source);
self.visit_export_specifiers(&it.specifiers);
} else {
for specifier in &it.specifiers {
if it.export_kind.is_type() || specifier.export_kind.is_type() {
self.current_reference_flags = ReferenceFlags::Type;
} else {
self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type;
}
self.visit_export_specifier(specifier);
}
}
if let Some(with_clause) = &it.with_clause {
self.visit_with_clause(with_clause);
}
self.leave_node(kind);
}
fn visit_export_specifier(&mut self, it: &ExportSpecifier<'a>) {
let kind = AstKind::ExportSpecifier(self.alloc(it));
self.enter_node(kind);
self.visit_span(&it.span);
self.current_node_flags |= NodeFlags::ExportSpecifier;
self.visit_module_export_name(&it.local);
self.visit_module_export_name(&it.exported);
self.current_node_flags -= NodeFlags::ExportSpecifier;
self.leave_node(kind);
}
fn visit_ts_export_assignment(&mut self, it: &TSExportAssignment<'a>) {
let kind = AstKind::TSExportAssignment(self.alloc(it));
self.enter_node(kind);
self.visit_span(&it.span);
if it.expression.is_identifier_reference() {
self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type;
}
self.visit_expression(&it.expression);
self.leave_node(kind);
}
fn visit_catch_parameter(&mut self, param: &CatchParameter<'a>) {
let kind = AstKind::CatchParameter(self.alloc(param));
self.enter_node(kind);
param.bind(self);
let saved_checkpoint = self.unresolved_references_checkpoint;
self.unresolved_references_checkpoint = self.unresolved_references.checkpoint();
self.visit_span(¶m.span);
self.visit_binding_pattern(¶m.pattern);
if let Some(type_annotation) = ¶m.type_annotation {
self.visit_ts_type_annotation(type_annotation);
}
self.resolve_references_for_current_scope();
self.unresolved_references_checkpoint = saved_checkpoint;
self.leave_node(kind);
}
fn visit_ts_type_query(&mut self, ty: &TSTypeQuery<'a>) {
let kind = AstKind::TSTypeQuery(self.alloc(ty));
self.enter_node(kind);
self.current_reference_flags = ReferenceFlags::ValueAsType;
self.visit_span(&ty.span);
self.visit_ts_type_query_expr_name(&ty.expr_name);
if let Some(type_arguments) = &ty.type_arguments {
self.visit_ts_type_parameter_instantiation(type_arguments);
}
self.current_reference_flags = ReferenceFlags::empty();
self.leave_node(kind);
}
fn visit_ts_property_signature(&mut self, sig: &TSPropertySignature<'a>) {
let kind = AstKind::TSPropertySignature(self.alloc(sig));
self.enter_node(kind);
if sig.key.is_expression() {
self.current_reference_flags = ReferenceFlags::ValueAsType;
}
self.visit_span(&sig.span);
self.visit_property_key(&sig.key);
if let Some(type_annotation) = &sig.type_annotation {
self.visit_ts_type_annotation(type_annotation);
}
self.current_reference_flags = ReferenceFlags::empty();
self.leave_node(kind);
}
fn visit_import_specifier(&mut self, specifier: &ImportSpecifier<'a>) {
let kind = AstKind::ImportSpecifier(self.alloc(specifier));
self.enter_node(kind);
specifier.bind(self);
self.visit_span(&specifier.span);
self.visit_module_export_name(&specifier.imported);
self.visit_binding_identifier(&specifier.local);
self.leave_node(kind);
}
fn visit_import_default_specifier(&mut self, specifier: &ImportDefaultSpecifier<'a>) {
let kind = AstKind::ImportDefaultSpecifier(self.alloc(specifier));
self.enter_node(kind);
specifier.bind(self);
self.visit_span(&specifier.span);
self.visit_binding_identifier(&specifier.local);
self.leave_node(kind);
}
fn visit_import_namespace_specifier(&mut self, specifier: &ImportNamespaceSpecifier<'a>) {
let kind = AstKind::ImportNamespaceSpecifier(self.alloc(specifier));
self.enter_node(kind);
specifier.bind(self);
self.visit_span(&specifier.span);
self.visit_binding_identifier(&specifier.local);
self.leave_node(kind);
}
fn visit_ts_import_equals_declaration(&mut self, decl: &TSImportEqualsDeclaration<'a>) {
let kind = AstKind::TSImportEqualsDeclaration(self.alloc(decl));
self.enter_node(kind);
decl.bind(self);
self.visit_span(&decl.span);
self.visit_binding_identifier(&decl.id);
self.visit_ts_module_reference(&decl.module_reference);
self.leave_node(kind);
}
fn visit_variable_declarator(&mut self, decl: &VariableDeclarator<'a>) {
let kind = AstKind::VariableDeclarator(self.alloc(decl));
self.enter_node(kind);
decl.bind(self);
self.visit_span(&decl.span);
self.visit_binding_pattern(&decl.id);
if let Some(type_annotation) = &decl.type_annotation {
self.visit_ts_type_annotation(type_annotation);
}
if let Some(init) = &decl.init {
self.visit_expression(init);
}
self.leave_node(kind);
}
fn visit_class_body(&mut self, body: &ClassBody<'a>) {
let kind = AstKind::ClassBody(self.alloc(body));
self.enter_node(kind);
self.class_table_builder.declare_class_body(body, self.current_node_id, &self.nodes);
self.visit_span(&body.span);
self.visit_class_elements(&body.body);
self.leave_node(kind);
}
fn visit_private_identifier(&mut self, ident: &PrivateIdentifier<'a>) {
let kind = AstKind::PrivateIdentifier(self.alloc(ident));
self.enter_node(kind);
self.class_table_builder.add_private_identifier_reference(
ident,
self.current_node_id,
&self.nodes,
);
self.visit_span(&ident.span);
self.leave_node(kind);
}
fn visit_binding_rest_element(&mut self, element: &BindingRestElement<'a>) {
let kind = AstKind::BindingRestElement(self.alloc(element));
self.enter_node(kind);
element.bind(self);
self.visit_span(&element.span);
self.visit_binding_pattern(&element.argument);
self.leave_node(kind);
}
fn visit_formal_parameter(&mut self, param: &FormalParameter<'a>) {
let kind = AstKind::FormalParameter(self.alloc(param));
self.enter_node(kind);
param.bind(self);
self.visit_span(¶m.span);
self.visit_decorators(¶m.decorators);
self.visit_binding_pattern(¶m.pattern);
if let Some(type_annotation) = ¶m.type_annotation {
self.visit_ts_type_annotation(type_annotation);
}
if let Some(initializer) = ¶m.initializer {
self.visit_expression(initializer);
}
self.leave_node(kind);
}
fn visit_formal_parameter_rest(&mut self, param: &FormalParameterRest<'a>) {
let kind = AstKind::FormalParameterRest(self.alloc(param));
self.enter_node(kind);
param.bind(self);
self.visit_span(¶m.span);
self.visit_decorators(¶m.decorators);
self.visit_binding_rest_element(¶m.rest);
if let Some(type_annotation) = ¶m.type_annotation {
self.visit_ts_type_annotation(type_annotation);
}
self.leave_node(kind);
}
fn visit_ts_module_declaration(&mut self, decl: &TSModuleDeclaration<'a>) {
let kind = AstKind::TSModuleDeclaration(self.alloc(decl));
self.enter_node(kind);
decl.bind(self);
self.visit_span(&decl.span);
self.visit_ts_module_declaration_name(&decl.id);
self.enter_scope(
{
let mut flags = ScopeFlags::TsModuleBlock;
if decl.body.as_ref().is_some_and(TSModuleDeclarationBody::has_use_strict_directive)
{
flags |= ScopeFlags::StrictMode;
}
flags
},
&decl.scope_id,
);
if let Some(body) = &decl.body {
self.visit_ts_module_declaration_body(body);
}
self.leave_scope();
self.leave_node(kind);
}
fn visit_ts_type_alias_declaration(&mut self, decl: &TSTypeAliasDeclaration<'a>) {
let kind = AstKind::TSTypeAliasDeclaration(self.alloc(decl));
self.enter_node(kind);
decl.bind(self);
self.visit_span(&decl.span);
self.visit_binding_identifier(&decl.id);
self.enter_scope(ScopeFlags::empty(), &decl.scope_id);
if let Some(type_parameters) = &decl.type_parameters {
self.visit_ts_type_parameter_declaration(type_parameters);
}
self.visit_ts_type(&decl.type_annotation);
self.leave_scope();
self.leave_node(kind);
}
fn visit_ts_interface_declaration(&mut self, decl: &TSInterfaceDeclaration<'a>) {
let kind = AstKind::TSInterfaceDeclaration(self.alloc(decl));
self.enter_node(kind);
decl.bind(self);
self.visit_span(&decl.span);
self.visit_binding_identifier(&decl.id);
self.enter_scope(ScopeFlags::empty(), &decl.scope_id);
if let Some(type_parameters) = &decl.type_parameters {
self.visit_ts_type_parameter_declaration(type_parameters);
}
self.visit_ts_interface_heritages(&decl.extends);
self.visit_ts_interface_body(&decl.body);
self.leave_scope();
self.leave_node(kind);
}
fn visit_ts_enum_declaration(&mut self, decl: &TSEnumDeclaration<'a>) {
let kind = AstKind::TSEnumDeclaration(self.alloc(decl));
self.enter_node(kind);
decl.bind(self);
self.visit_span(&decl.span);
self.visit_binding_identifier(&decl.id);
self.visit_ts_enum_body(&decl.body);
if self.enum_eval {
crate::ts_enum::eval::evaluate_enum_members(decl, &mut self.scoping);
}
self.leave_node(kind);
}
fn visit_ts_enum_member(&mut self, member: &TSEnumMember<'a>) {
let kind = AstKind::TSEnumMember(self.alloc(member));
self.enter_node(kind);
member.bind(self);
self.visit_span(&member.span);
self.visit_ts_enum_member_name(&member.id);
if let Some(initializer) = &member.initializer {
self.visit_expression(initializer);
}
self.leave_node(kind);
}
fn visit_ts_type_parameter(&mut self, param: &TSTypeParameter<'a>) {
let kind = AstKind::TSTypeParameter(self.alloc(param));
self.enter_node(kind);
param.bind(self);
self.visit_span(¶m.span);
self.visit_binding_identifier(¶m.name);
if let Some(constraint) = ¶m.constraint {
self.visit_ts_type(constraint);
}
if let Some(default) = ¶m.default {
self.visit_ts_type(default);
}
self.leave_node(kind);
}
fn visit_ts_mapped_type(&mut self, it: &TSMappedType<'a>) {
let kind = AstKind::TSMappedType(self.alloc(it));
self.enter_node(kind);
self.enter_scope(ScopeFlags::empty(), &it.scope_id);
it.bind(self);
self.visit_span(&it.span);
self.visit_binding_identifier(&it.key);
self.visit_ts_type(&it.constraint);
if let Some(name_type) = &it.name_type {
self.visit_ts_type(name_type);
}
if let Some(type_annotation) = &it.type_annotation {
self.visit_ts_type(type_annotation);
}
self.leave_scope();
self.leave_node(kind);
}
fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) {
let kind = AstKind::IdentifierReference(self.alloc(ident));
self.enter_node(kind);
self.reference_identifier(ident);
self.visit_span(&ident.span);
self.leave_node(kind);
}
fn visit_yield_expression(&mut self, expr: &YieldExpression<'a>) {
let kind = AstKind::YieldExpression(self.alloc(expr));
self.enter_node(kind);
*self.nodes.flags_mut(self.current_function_node_id) |= NodeFlags::HasYield;
self.visit_span(&expr.span);
if let Some(argument) = &expr.argument {
self.visit_expression(argument);
}
self.leave_node(kind);
}
fn visit_call_expression(&mut self, expr: &CallExpression<'a>) {
let kind = AstKind::CallExpression(self.alloc(expr));
self.enter_node(kind);
if !expr.optional && expr.callee.is_specific_id("eval") {
self.scoping.scope_flags_mut(self.current_scope_id).insert(ScopeFlags::DirectEval);
}
self.visit_span(&expr.span);
self.visit_expression(&expr.callee);
if let Some(type_arguments) = &expr.type_arguments {
self.visit_ts_type_parameter_instantiation(type_arguments);
}
self.visit_arguments(&expr.arguments);
self.leave_node(kind);
}
fn visit_ts_interface_heritage(&mut self, heritage: &TSInterfaceHeritage<'a>) {
let kind = AstKind::TSInterfaceHeritage(self.alloc(heritage));
self.enter_node(kind);
self.current_reference_flags = ReferenceFlags::Type;
self.visit_span(&heritage.span);
self.visit_expression(&heritage.expression);
if let Some(type_arguments) = &heritage.type_arguments {
self.visit_ts_type_parameter_instantiation(type_arguments);
}
self.leave_node(kind);
}
fn visit_ts_class_implements(&mut self, implements: &TSClassImplements<'a>) {
let kind = AstKind::TSClassImplements(self.alloc(implements));
self.enter_node(kind);
self.current_reference_flags = ReferenceFlags::Type;
self.visit_span(&implements.span);
self.visit_ts_type_name(&implements.expression);
if let Some(type_arguments) = &implements.type_arguments {
self.visit_ts_type_parameter_instantiation(type_arguments);
}
self.leave_node(kind);
}
fn visit_ts_type_reference(&mut self, ty: &TSTypeReference<'a>) {
let kind = AstKind::TSTypeReference(self.alloc(ty));
self.enter_node(kind);
self.current_reference_flags = ReferenceFlags::Type;
self.visit_span(&ty.span);
self.visit_ts_type_name(&ty.type_name);
if let Some(type_arguments) = &ty.type_arguments {
self.visit_ts_type_parameter_instantiation(type_arguments);
}
self.leave_node(kind);
}
fn visit_ts_qualified_name(&mut self, name: &TSQualifiedName<'a>) {
let kind = AstKind::TSQualifiedName(self.alloc(name));
self.enter_node(kind);
self.visit_span(&name.span);
if self.current_reference_flags.is_empty() {
self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Namespace;
} else {
self.current_reference_flags |= ReferenceFlags::Namespace;
}
self.visit_ts_type_name(&name.left);
self.visit_identifier_name(&name.right);
self.leave_node(kind);
}
fn visit_expression_statement(&mut self, it: &ExpressionStatement<'a>) {
let kind = AstKind::ExpressionStatement(self.alloc(it));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.visit_span(&it.span);
self.visit_expression(&it.expression);
self.leave_node(kind);
}
fn visit_variable_declaration(&mut self, it: &VariableDeclaration<'a>) {
let kind = AstKind::VariableDeclaration(self.alloc(it));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.visit_span(&it.span);
self.visit_variable_declarators(&it.declarations);
self.leave_node(kind);
}
fn visit_empty_statement(&mut self, it: &EmptyStatement) {
let kind = AstKind::EmptyStatement(self.alloc(it));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.visit_span(&it.span);
self.leave_node(kind);
}
fn visit_debugger_statement(&mut self, it: &DebuggerStatement) {
let kind = AstKind::DebuggerStatement(self.alloc(it));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.visit_span(&it.span);
self.leave_node(kind);
}
fn visit_export_default_declaration(&mut self, it: &ExportDefaultDeclaration<'a>) {
let kind = AstKind::ExportDefaultDeclaration(self.alloc(it));
self.enter_node(kind);
control_flow!(self, |cfg| cfg.enter_statement(self.current_node_id));
self.visit_span(&it.span);
self.visit_export_default_declaration_kind(&it.declaration);
self.leave_node(kind);
}
}
impl<'a> SemanticBuilder<'a> {
#[inline]
fn reference_identifier(&mut self, ident: &IdentifierReference<'a>) {
let flags = self.resolve_reference_usages();
let reference = Reference::new(self.current_node_id, self.current_scope_id, flags);
let reference_id = self.declare_reference(ident.name, reference);
ident.reference_id.set(Some(reference_id));
}
#[inline]
fn resolve_reference_usages(&mut self) -> ReferenceFlags {
if self.current_reference_flags.is_empty() {
ReferenceFlags::Read
} else {
mem::take(&mut self.current_reference_flags)
}
}
}