mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
use enter / leave scope from ast visit in SemanticBuilder
This commit is contained in:
parent
dd875402e7
commit
0fcad27515
5 changed files with 48 additions and 115 deletions
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
╰────
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ScopeFlags> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue