mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32:10 +00:00
feat(ast): enter/leave scopes in Visit
This commit is contained in:
parent
90828c4d39
commit
6c1388df55
2 changed files with 93 additions and 7 deletions
|
|
@ -30,6 +30,9 @@ impl<'a> Program<'a> {
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.body.is_empty() && self.directives.is_empty()
|
self.body.is_empty() && self.directives.is_empty()
|
||||||
}
|
}
|
||||||
|
pub fn is_strict(&self) -> bool {
|
||||||
|
self.directives.iter().any(|d| d.directive == "use strict")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expression
|
/// Expression
|
||||||
|
|
@ -1477,6 +1480,12 @@ impl<'a> Function<'a> {
|
||||||
pub fn is_declaration(&self) -> bool {
|
pub fn is_declaration(&self) -> bool {
|
||||||
matches!(self.r#type, FunctionType::FunctionDeclaration | FunctionType::TSDeclareFunction)
|
matches!(self.r#type, FunctionType::FunctionDeclaration | FunctionType::TSDeclareFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_strict(&self) -> bool {
|
||||||
|
self.body.as_ref().is_some_and(|body| {
|
||||||
|
body.directives.iter().any(|directive| directive.directive == "use strict")
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
use oxc_allocator::Vec;
|
use oxc_allocator::Vec;
|
||||||
use oxc_span::Span;
|
use oxc_span::Span;
|
||||||
|
use oxc_syntax::scope::ScopeFlags;
|
||||||
|
|
||||||
#[allow(clippy::wildcard_imports)]
|
#[allow(clippy::wildcard_imports)]
|
||||||
use crate::{ast::*, ast_kind::AstKind};
|
use crate::{ast::*, ast_kind::AstKind};
|
||||||
|
|
@ -14,14 +15,22 @@ use crate::{ast::*, ast_kind::AstKind};
|
||||||
pub trait Visit<'a>: Sized {
|
pub trait Visit<'a>: Sized {
|
||||||
fn enter_node(&mut self, _kind: AstKind<'a>) {}
|
fn enter_node(&mut self, _kind: AstKind<'a>) {}
|
||||||
fn leave_node(&mut self, _kind: AstKind<'a>) {}
|
fn leave_node(&mut self, _kind: AstKind<'a>) {}
|
||||||
|
fn enter_scope(&mut self, _flags: ScopeFlags) {}
|
||||||
|
fn leave_scope(&mut self) {}
|
||||||
|
|
||||||
fn visit_program(&mut self, program: &'a Program<'a>) {
|
fn visit_program(&mut self, program: &'a Program<'a>) {
|
||||||
let kind = AstKind::Program(program);
|
let kind = AstKind::Program(program);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
let mut flags = ScopeFlags::Top;
|
||||||
|
if program.is_strict() {
|
||||||
|
flags |= ScopeFlags::StrictMode;
|
||||||
|
}
|
||||||
|
self.enter_scope(flags);
|
||||||
for directive in &program.directives {
|
for directive in &program.directives {
|
||||||
self.visit_directive(directive);
|
self.visit_directive(directive);
|
||||||
}
|
}
|
||||||
self.visit_statements(&program.body);
|
self.visit_statements(&program.body);
|
||||||
|
self.leave_scope();
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,7 +75,9 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_block_statement(&mut self, stmt: &'a BlockStatement<'a>) {
|
fn visit_block_statement(&mut self, stmt: &'a BlockStatement<'a>) {
|
||||||
let kind = AstKind::BlockStatement(stmt);
|
let kind = AstKind::BlockStatement(stmt);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
self.enter_scope(ScopeFlags::empty());
|
||||||
self.visit_statements(&stmt.body);
|
self.visit_statements(&stmt.body);
|
||||||
|
self.leave_scope();
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,6 +129,11 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_for_statement(&mut self, stmt: &'a ForStatement<'a>) {
|
fn visit_for_statement(&mut self, stmt: &'a ForStatement<'a>) {
|
||||||
let kind = AstKind::ForStatement(stmt);
|
let kind = AstKind::ForStatement(stmt);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
let is_lexical_declaration =
|
||||||
|
stmt.init.as_ref().is_some_and(ForStatementInit::is_lexical_declaration);
|
||||||
|
if is_lexical_declaration {
|
||||||
|
self.enter_scope(ScopeFlags::empty());
|
||||||
|
}
|
||||||
if let Some(init) = &stmt.init {
|
if let Some(init) = &stmt.init {
|
||||||
self.visit_for_statement_init(init);
|
self.visit_for_statement_init(init);
|
||||||
}
|
}
|
||||||
|
|
@ -128,6 +144,9 @@ pub trait Visit<'a>: Sized {
|
||||||
self.visit_expression(update);
|
self.visit_expression(update);
|
||||||
}
|
}
|
||||||
self.visit_statement(&stmt.body);
|
self.visit_statement(&stmt.body);
|
||||||
|
if is_lexical_declaration {
|
||||||
|
self.leave_scope();
|
||||||
|
}
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,8 +168,15 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_for_in_statement(&mut self, stmt: &'a ForInStatement<'a>) {
|
fn visit_for_in_statement(&mut self, stmt: &'a ForInStatement<'a>) {
|
||||||
let kind = AstKind::ForInStatement(stmt);
|
let kind = AstKind::ForInStatement(stmt);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
let is_lexical_declaration = stmt.left.is_lexical_declaration();
|
||||||
|
if is_lexical_declaration {
|
||||||
|
self.enter_scope(ScopeFlags::empty());
|
||||||
|
}
|
||||||
self.visit_for_statement_left(&stmt.left);
|
self.visit_for_statement_left(&stmt.left);
|
||||||
self.visit_expression(&stmt.right);
|
self.visit_expression(&stmt.right);
|
||||||
|
if is_lexical_declaration {
|
||||||
|
self.leave_scope();
|
||||||
|
}
|
||||||
self.visit_statement(&stmt.body);
|
self.visit_statement(&stmt.body);
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
@ -158,9 +184,16 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_for_of_statement(&mut self, stmt: &'a ForOfStatement<'a>) {
|
fn visit_for_of_statement(&mut self, stmt: &'a ForOfStatement<'a>) {
|
||||||
let kind = AstKind::ForOfStatement(stmt);
|
let kind = AstKind::ForOfStatement(stmt);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
let is_lexical_declaration = stmt.left.is_lexical_declaration();
|
||||||
|
if is_lexical_declaration {
|
||||||
|
self.enter_scope(ScopeFlags::empty());
|
||||||
|
}
|
||||||
self.visit_for_statement_left(&stmt.left);
|
self.visit_for_statement_left(&stmt.left);
|
||||||
self.visit_expression(&stmt.right);
|
self.visit_expression(&stmt.right);
|
||||||
self.visit_statement(&stmt.body);
|
self.visit_statement(&stmt.body);
|
||||||
|
if is_lexical_declaration {
|
||||||
|
self.leave_scope();
|
||||||
|
}
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,10 +240,12 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_switch_statement(&mut self, stmt: &'a SwitchStatement<'a>) {
|
fn visit_switch_statement(&mut self, stmt: &'a SwitchStatement<'a>) {
|
||||||
let kind = AstKind::SwitchStatement(stmt);
|
let kind = AstKind::SwitchStatement(stmt);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
self.enter_scope(ScopeFlags::empty());
|
||||||
self.visit_expression(&stmt.discriminant);
|
self.visit_expression(&stmt.discriminant);
|
||||||
for case in &stmt.cases {
|
for case in &stmt.cases {
|
||||||
self.visit_switch_case(case);
|
self.visit_switch_case(case);
|
||||||
}
|
}
|
||||||
|
self.leave_scope();
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,10 +282,12 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_catch_clause(&mut self, clause: &'a CatchClause<'a>) {
|
fn visit_catch_clause(&mut self, clause: &'a CatchClause<'a>) {
|
||||||
let kind = AstKind::CatchClause(clause);
|
let kind = AstKind::CatchClause(clause);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
self.enter_scope(ScopeFlags::empty());
|
||||||
if let Some(param) = &clause.param {
|
if let Some(param) = &clause.param {
|
||||||
self.visit_binding_pattern(param);
|
self.visit_binding_pattern(param);
|
||||||
}
|
}
|
||||||
self.visit_statements(&clause.body.body);
|
self.visit_statements(&clause.body.body);
|
||||||
|
self.leave_scope();
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,12 +344,18 @@ pub trait Visit<'a>: Sized {
|
||||||
|
|
||||||
/* ---------- Function ---------- */
|
/* ---------- Function ---------- */
|
||||||
|
|
||||||
fn visit_function(&mut self, func: &'a Function<'a>) {
|
fn visit_function(&mut self, func: &'a Function<'a>, flags: Option<ScopeFlags>) {
|
||||||
let kind = AstKind::Function(func);
|
let kind = AstKind::Function(func);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
// todo: visit binding identifier with includes, excludes
|
||||||
if let Some(ident) = &func.id {
|
if let Some(ident) = &func.id {
|
||||||
self.visit_binding_identifier(ident);
|
self.visit_binding_identifier(ident);
|
||||||
}
|
}
|
||||||
|
let mut flags = flags.unwrap_or(ScopeFlags::empty()) | ScopeFlags::Function;
|
||||||
|
if func.is_strict() {
|
||||||
|
flags |= ScopeFlags::StrictMode;
|
||||||
|
}
|
||||||
|
self.enter_scope(flags);
|
||||||
self.visit_formal_parameters(&func.params);
|
self.visit_formal_parameters(&func.params);
|
||||||
if let Some(body) = &func.body {
|
if let Some(body) = &func.body {
|
||||||
self.visit_function_body(body);
|
self.visit_function_body(body);
|
||||||
|
|
@ -323,6 +366,7 @@ pub trait Visit<'a>: Sized {
|
||||||
if let Some(annotation) = &func.return_type {
|
if let Some(annotation) = &func.return_type {
|
||||||
self.visit_ts_type_annotation(annotation);
|
self.visit_ts_type_annotation(annotation);
|
||||||
}
|
}
|
||||||
|
self.leave_scope();
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,6 +420,16 @@ pub trait Visit<'a>: Sized {
|
||||||
}
|
}
|
||||||
let kind = AstKind::Class(class);
|
let kind = AstKind::Class(class);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
|
||||||
|
// FIXME(don): Should we enter a scope when visiting class declarations?
|
||||||
|
let is_class_expr = class.r#type == ClassType::ClassExpression;
|
||||||
|
if is_class_expr {
|
||||||
|
// Class expressions create a temporary scope with the class name as its only variable
|
||||||
|
// E.g., `let c = class A { foo() { console.log(A) } }`
|
||||||
|
self.enter_scope(ScopeFlags::empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// self.enter_scope(ScopeFlags::Class);
|
||||||
if let Some(id) = &class.id {
|
if let Some(id) = &class.id {
|
||||||
self.visit_binding_identifier(id);
|
self.visit_binding_identifier(id);
|
||||||
}
|
}
|
||||||
|
|
@ -390,6 +444,9 @@ pub trait Visit<'a>: Sized {
|
||||||
self.visit_ts_type_parameter_instantiation(super_parameters);
|
self.visit_ts_type_parameter_instantiation(super_parameters);
|
||||||
}
|
}
|
||||||
self.visit_class_body(&class.body);
|
self.visit_class_body(&class.body);
|
||||||
|
if is_class_expr {
|
||||||
|
self.leave_scope();
|
||||||
|
}
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -425,7 +482,9 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_static_block(&mut self, block: &'a StaticBlock<'a>) {
|
fn visit_static_block(&mut self, block: &'a StaticBlock<'a>) {
|
||||||
let kind = AstKind::StaticBlock(block);
|
let kind = AstKind::StaticBlock(block);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
self.enter_scope(ScopeFlags::ClassStaticBlock);
|
||||||
self.visit_statements(&block.body);
|
self.visit_statements(&block.body);
|
||||||
|
self.leave_scope();
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,8 +494,14 @@ pub trait Visit<'a>: Sized {
|
||||||
for decorator in &def.decorators {
|
for decorator in &def.decorators {
|
||||||
self.visit_decorator(decorator);
|
self.visit_decorator(decorator);
|
||||||
}
|
}
|
||||||
|
let flags = match def.kind {
|
||||||
|
MethodDefinitionKind::Get => ScopeFlags::GetAccessor,
|
||||||
|
MethodDefinitionKind::Set => ScopeFlags::SetAccessor,
|
||||||
|
MethodDefinitionKind::Constructor => ScopeFlags::Constructor,
|
||||||
|
MethodDefinitionKind::Method => ScopeFlags::empty(),
|
||||||
|
};
|
||||||
self.visit_property_key(&def.key);
|
self.visit_property_key(&def.key);
|
||||||
self.visit_function(&def.value);
|
self.visit_function(&def.value, Some(flags));
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -491,9 +556,12 @@ pub trait Visit<'a>: Sized {
|
||||||
Expression::BinaryExpression(expr) => self.visit_binary_expression(expr),
|
Expression::BinaryExpression(expr) => self.visit_binary_expression(expr),
|
||||||
Expression::CallExpression(expr) => self.visit_call_expression(expr),
|
Expression::CallExpression(expr) => self.visit_call_expression(expr),
|
||||||
Expression::ChainExpression(expr) => self.visit_chain_expression(expr),
|
Expression::ChainExpression(expr) => self.visit_chain_expression(expr),
|
||||||
Expression::ClassExpression(expr) => self.visit_class(expr),
|
Expression::ClassExpression(expr) => {
|
||||||
|
debug_assert_eq!(expr.r#type, ClassType::ClassExpression);
|
||||||
|
self.visit_class(expr);
|
||||||
|
}
|
||||||
Expression::ConditionalExpression(expr) => self.visit_conditional_expression(expr),
|
Expression::ConditionalExpression(expr) => self.visit_conditional_expression(expr),
|
||||||
Expression::FunctionExpression(expr) => self.visit_function(expr),
|
Expression::FunctionExpression(expr) => self.visit_function(expr, None),
|
||||||
Expression::ImportExpression(expr) => self.visit_import_expression(expr),
|
Expression::ImportExpression(expr) => self.visit_import_expression(expr),
|
||||||
Expression::LogicalExpression(expr) => self.visit_logical_expression(expr),
|
Expression::LogicalExpression(expr) => self.visit_logical_expression(expr),
|
||||||
Expression::MemberExpression(expr) => self.visit_member_expression(expr),
|
Expression::MemberExpression(expr) => self.visit_member_expression(expr),
|
||||||
|
|
@ -592,11 +660,13 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_arrow_expression(&mut self, expr: &'a ArrowExpression<'a>) {
|
fn visit_arrow_expression(&mut self, expr: &'a ArrowExpression<'a>) {
|
||||||
let kind = AstKind::ArrowExpression(expr);
|
let kind = AstKind::ArrowExpression(expr);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
self.enter_scope(ScopeFlags::Function | ScopeFlags::Arrow);
|
||||||
self.visit_formal_parameters(&expr.params);
|
self.visit_formal_parameters(&expr.params);
|
||||||
self.visit_function_body(&expr.body);
|
self.visit_function_body(&expr.body);
|
||||||
if let Some(parameters) = &expr.type_parameters {
|
if let Some(parameters) = &expr.type_parameters {
|
||||||
self.visit_ts_type_parameter_declaration(parameters);
|
self.visit_ts_type_parameter_declaration(parameters);
|
||||||
}
|
}
|
||||||
|
self.leave_scope();
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1233,7 +1303,7 @@ pub trait Visit<'a>: Sized {
|
||||||
match &decl.declaration {
|
match &decl.declaration {
|
||||||
ExportDefaultDeclarationKind::Expression(expr) => self.visit_expression(expr),
|
ExportDefaultDeclarationKind::Expression(expr) => self.visit_expression(expr),
|
||||||
ExportDefaultDeclarationKind::FunctionDeclaration(func) => {
|
ExportDefaultDeclarationKind::FunctionDeclaration(func) => {
|
||||||
self.visit_function(func);
|
self.visit_function(func, None);
|
||||||
}
|
}
|
||||||
ExportDefaultDeclarationKind::ClassDeclaration(class) => self.visit_class(class),
|
ExportDefaultDeclarationKind::ClassDeclaration(class) => self.visit_class(class),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -1260,9 +1330,11 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_enum_body(&mut self, body: &'a TSEnumBody<'a>) {
|
fn visit_enum_body(&mut self, body: &'a TSEnumBody<'a>) {
|
||||||
let kind = AstKind::TSEnumBody(body);
|
let kind = AstKind::TSEnumBody(body);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
self.enter_scope(ScopeFlags::empty());
|
||||||
for member in &body.members {
|
for member in &body.members {
|
||||||
self.visit_enum_member(member);
|
self.visit_enum_member(member);
|
||||||
}
|
}
|
||||||
|
self.leave_scope();
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1277,8 +1349,11 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_declaration(&mut self, decl: &'a Declaration<'a>) {
|
fn visit_declaration(&mut self, decl: &'a Declaration<'a>) {
|
||||||
match decl {
|
match decl {
|
||||||
Declaration::VariableDeclaration(decl) => self.visit_variable_declaration(decl),
|
Declaration::VariableDeclaration(decl) => self.visit_variable_declaration(decl),
|
||||||
Declaration::FunctionDeclaration(func) => self.visit_function(func),
|
Declaration::FunctionDeclaration(func) => self.visit_function(func, None),
|
||||||
Declaration::ClassDeclaration(class) => self.visit_class(class),
|
Declaration::ClassDeclaration(class) => {
|
||||||
|
debug_assert_eq!(class.r#type, ClassType::ClassDeclaration);
|
||||||
|
self.visit_class(class);
|
||||||
|
}
|
||||||
Declaration::UsingDeclaration(decl) => self.visit_using_declaration(decl),
|
Declaration::UsingDeclaration(decl) => self.visit_using_declaration(decl),
|
||||||
Declaration::TSModuleDeclaration(module) => {
|
Declaration::TSModuleDeclaration(module) => {
|
||||||
self.visit_ts_module_declaration(module);
|
self.visit_ts_module_declaration(module);
|
||||||
|
|
@ -1322,7 +1397,9 @@ pub trait Visit<'a>: Sized {
|
||||||
fn visit_ts_module_block(&mut self, block: &'a TSModuleBlock<'a>) {
|
fn visit_ts_module_block(&mut self, block: &'a TSModuleBlock<'a>) {
|
||||||
let kind = AstKind::TSModuleBlock(block);
|
let kind = AstKind::TSModuleBlock(block);
|
||||||
self.enter_node(kind);
|
self.enter_node(kind);
|
||||||
|
self.enter_scope(ScopeFlags::TsModuleBlock);
|
||||||
self.visit_statements(&block.body);
|
self.visit_statements(&block.body);
|
||||||
|
self.leave_scope();
|
||||||
self.leave_node(kind);
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue