diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index f1b9b7e25..3865ee5da 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -30,8 +30,11 @@ 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") + self.source_type.is_module() + || self.source_type.always_strict() + || self.directives.iter().any(|d| d.directive == "use strict") } } diff --git a/crates/oxc_ast/src/visit.rs b/crates/oxc_ast/src/visit.rs index 7f65f79e0..3e206ae1b 100644 --- a/crates/oxc_ast/src/visit.rs +++ b/crates/oxc_ast/src/visit.rs @@ -1560,6 +1560,7 @@ pub trait Visit<'a>: Sized { fn visit_ts_type_parameter(&mut self, ty: &'a TSTypeParameter<'a>) { let kind = AstKind::TSTypeParameter(ty); + self.enter_scope(ScopeFlags::empty()); self.enter_node(kind); if let Some(constraint) = &ty.constraint { self.visit_ts_type(constraint); @@ -1569,6 +1570,7 @@ pub trait Visit<'a>: Sized { self.visit_ts_type(default); } self.leave_node(kind); + self.leave_scope(); } fn visit_ts_type_parameter_instantiation(&mut self, ty: &'a TSTypeParameterInstantiation<'a>) { diff --git a/crates/oxc_linter/src/snapshots/no_redeclare.snap b/crates/oxc_linter/src/snapshots/no_redeclare.snap index 5492a9d10..0a4747968 100644 --- a/crates/oxc_linter/src/snapshots/no_redeclare.snap +++ b/crates/oxc_linter/src/snapshots/no_redeclare.snap @@ -1,6 +1,5 @@ --- source: crates/oxc_linter/src/tester.rs -assertion_line: 105 expression: no_redeclare --- ⚠ eslint(no-redeclare): 'b' is already defined. @@ -151,12 +150,4 @@ expression: no_redeclare · ╰── 'a' is already defined. ╰──── - ⚠ eslint(no-redeclare): 'a' is already defined. - ╭─[no_redeclare.tsx:1:1] - 1 │ for (var a, a;;); - · ┬ ┬ - · │ ╰── It can not be redeclare here. - · ╰── 'a' is already defined. - ╰──── - diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 4525f52d1..22c0b31b1 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -93,7 +93,7 @@ pub struct SemanticBuilderReturn<'a> { impl<'a> SemanticBuilder<'a> { pub fn new(source_text: &'a str, source_type: SourceType) -> Self { - let scope = ScopeTree::new(source_type); + let scope = ScopeTree::default(); let current_scope_id = scope.root_scope_id(); let trivias = Rc::new(TriviasMap::default()); @@ -212,35 +212,6 @@ impl<'a> SemanticBuilder<'a> { } } - fn try_enter_scope(&mut self, kind: AstKind<'a>) { - fn is_strict(directives: &[Directive]) -> bool { - directives.iter().any(|d| d.directive == "use strict") - } - if let Some(flags) = ScopeTree::scope_flags_from_ast_kind(kind) { - self.enter_scope(flags); - } - let strict_mode = match kind { - AstKind::Program(program) => is_strict(&program.directives), - AstKind::Function(func) => { - func.body.as_ref().is_some_and(|body| is_strict(&body.directives)) - } - _ => false, - }; - if strict_mode { - *self.scope.get_flags_mut(self.current_scope_id) = - self.scope.get_flags(self.current_scope_id).with_strict_mode(true); - } - } - - fn try_leave_scope(&mut self, kind: AstKind<'a>) { - if ScopeTree::scope_flags_from_ast_kind(kind).is_some() - || matches!(kind, AstKind::Program(_)) - { - self.resolve_references_for_current_scope(); - self.leave_scope(); - } - } - pub fn strict_mode(&self) -> bool { self.scope.get_flags(self.current_scope_id).is_strict_mode() || self.current_node_flags.contains(NodeFlags::Class) @@ -309,7 +280,6 @@ impl<'a> SemanticBuilder<'a> { return symbol_id; } - // let includes = includes | self.current_symbol_flags; let symbol_id = self.symbols.create_symbol(span, name.clone(), includes, self.current_scope_id); if includes.is_variable() { @@ -370,37 +340,6 @@ impl<'a> SemanticBuilder<'a> { symbol_id } - pub fn enter_scope(&mut self, flags: ScopeFlags) { - let mut flags = flags; - // Inherit strict mode for functions - // https://tc39.es/ecma262/#sec-strict-mode-code - let mut strict_mode = self.scope.root_flags().is_strict_mode(); - let parent_scope_id = self.current_scope_id; - let parent_scope_flags = self.scope.get_flags(parent_scope_id); - - if !strict_mode && parent_scope_flags.is_function() && parent_scope_flags.is_strict_mode() { - strict_mode = true; - } - - // inherit flags for non-function scopes - if !flags.contains(ScopeFlags::Function) { - flags |= parent_scope_flags & ScopeFlags::Modifiers; - }; - - if strict_mode { - flags |= ScopeFlags::StrictMode; - } - - self.current_scope_id = self.scope.add_scope(Some(self.current_scope_id), flags); - } - - pub fn leave_scope(&mut self) { - self.resolve_references_for_current_scope(); - if let Some(parent_id) = self.scope.get_parent_id(self.current_scope_id) { - self.current_scope_id = parent_id; - } - } - fn resolve_references_for_current_scope(&mut self) { let all_references = self .scope @@ -440,15 +379,48 @@ impl<'a> SemanticBuilder<'a> { } impl<'a> Visit<'a> for SemanticBuilder<'a> { - // Setup all the context for the binder, - // the order is important here. + fn enter_scope(&mut self, flags: ScopeFlags) { + let parent_scope_id = + if flags.contains(ScopeFlags::Top) { None } else { Some(self.current_scope_id) }; + + let mut flags = flags; + // Inherit strict mode for functions + // https://tc39.es/ecma262/#sec-strict-mode-code + if let Some(parent_scope_id) = parent_scope_id { + let mut strict_mode = self.scope.root_flags().is_strict_mode(); + let parent_scope_flags = self.scope.get_flags(parent_scope_id); + + if !strict_mode + && parent_scope_flags.is_function() + && parent_scope_flags.is_strict_mode() + { + strict_mode = true; + } + + // inherit flags for non-function scopes + if !flags.contains(ScopeFlags::Function) { + flags |= parent_scope_flags & ScopeFlags::Modifiers; + }; + + if strict_mode { + flags |= ScopeFlags::StrictMode; + } + } + + self.current_scope_id = self.scope.add_scope(parent_scope_id, flags); + } + + fn leave_scope(&mut self) { + self.resolve_references_for_current_scope(); + if let Some(parent_id) = self.scope.get_parent_id(self.current_scope_id) { + self.current_scope_id = parent_id; + } + } + + // Setup all the context for the binder. + // The order is important here. fn enter_node(&mut self, kind: AstKind<'a>) { - // create new self.scope.current_scope_id - self.try_enter_scope(kind); - - // create new self.current_node_id self.create_ast_node(kind); - self.enter_kind(kind); } @@ -460,7 +432,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { } self.leave_kind(kind); self.pop_ast_node(); - self.try_leave_scope(kind); } } diff --git a/crates/oxc_semantic/src/scope.rs b/crates/oxc_semantic/src/scope.rs index 05c0b2a67..10c6c4741 100644 --- a/crates/oxc_semantic/src/scope.rs +++ b/crates/oxc_semantic/src/scope.rs @@ -1,9 +1,8 @@ use std::hash::BuildHasherDefault; use indexmap::IndexMap; -use oxc_ast::{ast::ClassType, AstKind}; use oxc_index::IndexVec; -use oxc_span::{Atom, SourceType}; +use oxc_span::Atom; pub use oxc_syntax::scope::{ScopeFlags, ScopeId}; use rustc_hash::{FxHashMap, FxHasher}; @@ -26,14 +25,6 @@ pub struct ScopeTree { } impl ScopeTree { - pub fn new(source_type: SourceType) -> Self { - let mut scope_tree = Self::default(); - let scope_flags = ScopeFlags::Top - .with_strict_mode(source_type.is_module() || source_type.always_strict()); - scope_tree.add_scope(None, scope_flags); - scope_tree - } - pub fn len(&self) -> usize { self.parent_ids.len() } @@ -134,28 +125,3 @@ impl ScopeTree { &mut self.unresolved_references[scope_id] } } - -impl ScopeTree { - pub fn scope_flags_from_ast_kind(kind: AstKind) -> Option { - match kind { - AstKind::Function(_) => Some(ScopeFlags::Function), - AstKind::ArrowExpression(_) => Some(ScopeFlags::Function | ScopeFlags::Arrow), - AstKind::StaticBlock(_) => Some(ScopeFlags::ClassStaticBlock), - AstKind::TSModuleBlock(_) => Some(ScopeFlags::TsModuleBlock), - AstKind::Class(class) if matches!(class.r#type, ClassType::ClassExpression) => { - // Class expression creates a temporary scope with the class name as its only variable - // E.g., `let c = class A { foo() { console.log(A) } }` - Some(ScopeFlags::empty()) - } - AstKind::BlockStatement(_) - | AstKind::CatchClause(_) - | AstKind::ForStatement(_) - | AstKind::ForInStatement(_) - | AstKind::ForOfStatement(_) - | AstKind::TSTypeParameter(_) - | AstKind::TSEnumBody(_) - | AstKind::SwitchStatement(_) => Some(ScopeFlags::empty()), - _ => None, - } - } -}