diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 7d79bca75..f1b9b7e25 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -30,6 +30,9 @@ impl<'a> Program<'a> { pub fn is_empty(&self) -> bool { self.body.is_empty() && self.directives.is_empty() } + pub fn is_strict(&self) -> bool { + self.directives.iter().any(|d| d.directive == "use strict") + } } /// Expression @@ -1477,6 +1480,12 @@ impl<'a> Function<'a> { pub fn is_declaration(&self) -> bool { 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)] diff --git a/crates/oxc_ast/src/visit.rs b/crates/oxc_ast/src/visit.rs index 0aa39c1d5..af1ccc19b 100644 --- a/crates/oxc_ast/src/visit.rs +++ b/crates/oxc_ast/src/visit.rs @@ -6,6 +6,7 @@ use oxc_allocator::Vec; use oxc_span::Span; +use oxc_syntax::scope::ScopeFlags; #[allow(clippy::wildcard_imports)] use crate::{ast::*, ast_kind::AstKind}; @@ -14,14 +15,22 @@ use crate::{ast::*, ast_kind::AstKind}; pub trait Visit<'a>: Sized { fn enter_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>) { let kind = AstKind::Program(program); 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 { self.visit_directive(directive); } self.visit_statements(&program.body); + self.leave_scope(); self.leave_node(kind); } @@ -66,7 +75,9 @@ pub trait Visit<'a>: Sized { fn visit_block_statement(&mut self, stmt: &'a BlockStatement<'a>) { let kind = AstKind::BlockStatement(stmt); self.enter_node(kind); + self.enter_scope(ScopeFlags::empty()); self.visit_statements(&stmt.body); + self.leave_scope(); self.leave_node(kind); } @@ -118,6 +129,11 @@ pub trait Visit<'a>: Sized { fn visit_for_statement(&mut self, stmt: &'a ForStatement<'a>) { let kind = AstKind::ForStatement(stmt); 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 { self.visit_for_statement_init(init); } @@ -128,6 +144,9 @@ pub trait Visit<'a>: Sized { self.visit_expression(update); } self.visit_statement(&stmt.body); + if is_lexical_declaration { + self.leave_scope(); + } self.leave_node(kind); } @@ -149,8 +168,15 @@ pub trait Visit<'a>: Sized { fn visit_for_in_statement(&mut self, stmt: &'a ForInStatement<'a>) { let kind = AstKind::ForInStatement(stmt); 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_expression(&stmt.right); + if is_lexical_declaration { + self.leave_scope(); + } self.visit_statement(&stmt.body); self.leave_node(kind); } @@ -158,9 +184,16 @@ pub trait Visit<'a>: Sized { fn visit_for_of_statement(&mut self, stmt: &'a ForOfStatement<'a>) { let kind = AstKind::ForOfStatement(stmt); 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_expression(&stmt.right); self.visit_statement(&stmt.body); + if is_lexical_declaration { + self.leave_scope(); + } self.leave_node(kind); } @@ -207,10 +240,12 @@ pub trait Visit<'a>: Sized { fn visit_switch_statement(&mut self, stmt: &'a SwitchStatement<'a>) { let kind = AstKind::SwitchStatement(stmt); self.enter_node(kind); + self.enter_scope(ScopeFlags::empty()); self.visit_expression(&stmt.discriminant); for case in &stmt.cases { self.visit_switch_case(case); } + self.leave_scope(); self.leave_node(kind); } @@ -247,10 +282,12 @@ pub trait Visit<'a>: Sized { fn visit_catch_clause(&mut self, clause: &'a CatchClause<'a>) { let kind = AstKind::CatchClause(clause); self.enter_node(kind); + self.enter_scope(ScopeFlags::empty()); if let Some(param) = &clause.param { self.visit_binding_pattern(param); } self.visit_statements(&clause.body.body); + self.leave_scope(); self.leave_node(kind); } @@ -307,12 +344,18 @@ pub trait Visit<'a>: Sized { /* ---------- Function ---------- */ - fn visit_function(&mut self, func: &'a Function<'a>) { + fn visit_function(&mut self, func: &'a Function<'a>, flags: Option) { let kind = AstKind::Function(func); self.enter_node(kind); + // todo: visit binding identifier with includes, excludes if let Some(ident) = &func.id { 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); if let Some(body) = &func.body { self.visit_function_body(body); @@ -323,6 +366,7 @@ pub trait Visit<'a>: Sized { if let Some(annotation) = &func.return_type { self.visit_ts_type_annotation(annotation); } + self.leave_scope(); self.leave_node(kind); } @@ -376,6 +420,16 @@ pub trait Visit<'a>: Sized { } let kind = AstKind::Class(class); 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 { self.visit_binding_identifier(id); } @@ -390,6 +444,9 @@ pub trait Visit<'a>: Sized { self.visit_ts_type_parameter_instantiation(super_parameters); } self.visit_class_body(&class.body); + if is_class_expr { + self.leave_scope(); + } self.leave_node(kind); } @@ -425,7 +482,9 @@ pub trait Visit<'a>: Sized { fn visit_static_block(&mut self, block: &'a StaticBlock<'a>) { let kind = AstKind::StaticBlock(block); self.enter_node(kind); + self.enter_scope(ScopeFlags::ClassStaticBlock); self.visit_statements(&block.body); + self.leave_scope(); self.leave_node(kind); } @@ -435,8 +494,14 @@ pub trait Visit<'a>: Sized { for decorator in &def.decorators { 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_function(&def.value); + self.visit_function(&def.value, Some(flags)); self.leave_node(kind); } @@ -491,9 +556,12 @@ pub trait Visit<'a>: Sized { Expression::BinaryExpression(expr) => self.visit_binary_expression(expr), Expression::CallExpression(expr) => self.visit_call_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::FunctionExpression(expr) => self.visit_function(expr), + Expression::FunctionExpression(expr) => self.visit_function(expr, None), Expression::ImportExpression(expr) => self.visit_import_expression(expr), Expression::LogicalExpression(expr) => self.visit_logical_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>) { let kind = AstKind::ArrowExpression(expr); self.enter_node(kind); + self.enter_scope(ScopeFlags::Function | ScopeFlags::Arrow); self.visit_formal_parameters(&expr.params); self.visit_function_body(&expr.body); if let Some(parameters) = &expr.type_parameters { self.visit_ts_type_parameter_declaration(parameters); } + self.leave_scope(); self.leave_node(kind); } @@ -1233,7 +1303,7 @@ pub trait Visit<'a>: Sized { match &decl.declaration { ExportDefaultDeclarationKind::Expression(expr) => self.visit_expression(expr), ExportDefaultDeclarationKind::FunctionDeclaration(func) => { - self.visit_function(func); + self.visit_function(func, None); } 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>) { let kind = AstKind::TSEnumBody(body); self.enter_node(kind); + self.enter_scope(ScopeFlags::empty()); for member in &body.members { self.visit_enum_member(member); } + self.leave_scope(); self.leave_node(kind); } @@ -1277,8 +1349,11 @@ pub trait Visit<'a>: Sized { fn visit_declaration(&mut self, decl: &'a Declaration<'a>) { match decl { Declaration::VariableDeclaration(decl) => self.visit_variable_declaration(decl), - Declaration::FunctionDeclaration(func) => self.visit_function(func), - Declaration::ClassDeclaration(class) => self.visit_class(class), + Declaration::FunctionDeclaration(func) => self.visit_function(func, None), + Declaration::ClassDeclaration(class) => { + debug_assert_eq!(class.r#type, ClassType::ClassDeclaration); + self.visit_class(class); + } Declaration::UsingDeclaration(decl) => self.visit_using_declaration(decl), Declaration::TSModuleDeclaration(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>) { let kind = AstKind::TSModuleBlock(block); self.enter_node(kind); + self.enter_scope(ScopeFlags::TsModuleBlock); self.visit_statements(&block.body); + self.leave_scope(); self.leave_node(kind); }