diff --git a/crates/oxc_linter/src/rules/eslint/no_class_assign.rs b/crates/oxc_linter/src/rules/eslint/no_class_assign.rs index bac8d08c5..58d52cfee 100644 --- a/crates/oxc_linter/src/rules/eslint/no_class_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_class_assign.rs @@ -42,8 +42,7 @@ impl Rule for NoClassAssign { fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) { let symbol_table = ctx.semantic().symbols(); if symbol_table.get_flag(symbol_id).is_class() { - for reference_id in symbol_table.get_resolved_references(symbol_id) { - let reference = symbol_table.get_reference(*reference_id); + for reference in symbol_table.get_resolved_references(symbol_id) { if reference.is_write() { ctx.diagnostic(NoClassAssignDiagnostic( symbol_table.get_name(symbol_id).clone(), diff --git a/crates/oxc_linter/src/rules/eslint/no_const_assign.rs b/crates/oxc_linter/src/rules/eslint/no_const_assign.rs index 259486203..072cb7593 100644 --- a/crates/oxc_linter/src/rules/eslint/no_const_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_const_assign.rs @@ -41,8 +41,7 @@ impl Rule for NoConstAssign { fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) { let symbol_table = ctx.semantic().symbols(); if symbol_table.get_flag(symbol_id).is_const_variable() { - for reference_id in symbol_table.get_resolved_references(symbol_id) { - let reference = symbol_table.get_reference(*reference_id); + for reference in symbol_table.get_resolved_references(symbol_id) { if reference.is_write() { ctx.diagnostic(NoConstAssignDiagnostic( symbol_table.get_name(symbol_id).clone(), diff --git a/crates/oxc_linter/src/rules/eslint/no_ex_assign.rs b/crates/oxc_linter/src/rules/eslint/no_ex_assign.rs index b51f42b24..7c8943adf 100644 --- a/crates/oxc_linter/src/rules/eslint/no_ex_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_ex_assign.rs @@ -43,8 +43,7 @@ impl Rule for NoExAssign { fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) { let symbol_table = ctx.semantic().symbols(); if symbol_table.get_flag(symbol_id).is_catch_variable() { - for reference_id in symbol_table.get_resolved_references(symbol_id) { - let reference = symbol_table.get_reference(*reference_id); + for reference in symbol_table.get_resolved_references(symbol_id) { if reference.is_write() { ctx.diagnostic(NoExAssignDiagnostic( reference.span(), diff --git a/crates/oxc_linter/src/rules/eslint/no_func_assign.rs b/crates/oxc_linter/src/rules/eslint/no_func_assign.rs index c265d457b..7c86fb16a 100644 --- a/crates/oxc_linter/src/rules/eslint/no_func_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_func_assign.rs @@ -39,8 +39,7 @@ impl Rule for NoFuncAssign { let symbol_table = ctx.semantic().symbols(); let decl = symbol_table.get_declaration(symbol_id); if let AstKind::Function(_) = ctx.nodes().kind(decl) { - for reference_id in symbol_table.get_resolved_references(symbol_id) { - let reference = symbol_table.get_reference(*reference_id); + for reference in symbol_table.get_resolved_references(symbol_id) { if reference.is_write() { ctx.diagnostic(NoFuncAssignDiagnostic( symbol_table.get_name(symbol_id).clone(), diff --git a/crates/oxc_linter/src/rules/eslint/no_import_assign.rs b/crates/oxc_linter/src/rules/eslint/no_import_assign.rs index 22ec60883..94c0cae04 100644 --- a/crates/oxc_linter/src/rules/eslint/no_import_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_import_assign.rs @@ -46,8 +46,7 @@ impl Rule for NoImportAssign { fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) { let symbol_table = ctx.semantic().symbols(); if symbol_table.get_flag(symbol_id).is_import_binding() { - for reference_id in symbol_table.get_resolved_references(symbol_id) { - let reference = symbol_table.get_reference(*reference_id); + for reference in symbol_table.get_resolved_references(symbol_id) { if reference.is_write() { ctx.diagnostic(NoImportAssignDiagnostic(reference.span())); } diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow_restricted_names.rs b/crates/oxc_linter/src/rules/eslint/no_shadow_restricted_names.rs index 3fb6d30f0..a07c2608d 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow_restricted_names.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow_restricted_names.rs @@ -42,8 +42,7 @@ fn safely_shadows_undefined(symbol_id: SymbolId, ctx: &LintContext<'_>) -> bool let symbol_table = ctx.semantic().symbols(); if symbol_table.get_name(symbol_id).as_str() == "undefined" { let mut no_assign = true; - for reference_id in symbol_table.get_resolved_references(symbol_id) { - let reference = symbol_table.get_reference(*reference_id); + for reference in symbol_table.get_resolved_references(symbol_id) { if reference.is_write() { no_assign = false; } diff --git a/crates/oxc_minifier/src/mangler/mod.rs b/crates/oxc_minifier/src/mangler/mod.rs index 1729b356c..e10d2706f 100644 --- a/crates/oxc_minifier/src/mangler/mod.rs +++ b/crates/oxc_minifier/src/mangler/mod.rs @@ -226,7 +226,8 @@ impl<'a> ManglerBuilder<'a> { } let index = *slot; frequencies[index].slot = *slot; - frequencies[index].frequency += symbol_table.get_resolved_references(symbol_id).len(); + frequencies[index].frequency += + symbol_table.get_resolved_reference_ids(symbol_id).len(); frequencies[index].symbol_ids.push(symbol_id); } frequencies.sort_by_key(|x| (std::cmp::Reverse(x.frequency))); diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index b48050a48..2833beb92 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -99,10 +99,23 @@ impl<'a> Semantic<'a> { self.scopes().root_unresolved_references().contains_key(&id.name) } + /// Find which scope a symbol is declared in pub fn symbol_scope(&self, symbol_id: SymbolId) -> ScopeId { self.symbols.get_scope_id(symbol_id) } + /// Get all resolved references for a symbol + pub fn symbol_references( + &'a self, + symbol_id: SymbolId, + ) -> impl Iterator + '_ { + self.symbols.get_resolved_references(symbol_id) + } + + pub fn symbol_declaration(&self, symbol_id: SymbolId) -> AstNodeId { + self.symbols.get_declaration(symbol_id) + } + pub fn is_reference_to_global_variable(&self, ident: &IdentifierReference) -> bool { self.scopes().root_unresolved_references().contains_key(&ident.name) } @@ -111,36 +124,68 @@ impl<'a> Semantic<'a> { #[cfg(test)] mod tests { use oxc_allocator::Allocator; - use oxc_ast::AstKind; - use oxc_span::SourceType; + use oxc_ast::{ast::VariableDeclarationKind, AstKind}; + use oxc_span::{Atom, SourceType}; - use crate::SemanticBuilder; + use super::*; + + /// Create a [`Semantic`] from source code, assuming there are no syntax/semantic errors. + fn get_semantic<'s, 'a: 's>( + allocator: &'a Allocator, + source: &'s str, + source_type: SourceType, + ) -> Semantic<'s> { + let parse = oxc_parser::Parser::new(allocator, source, source_type).parse(); + assert!(parse.errors.is_empty()); + let program = allocator.alloc(parse.program); + let semantic = SemanticBuilder::new(source, source_type).build(program); + assert!(semantic.errors.is_empty()); + semantic.semantic + } + + #[test] + fn test_symbols() { + let source = " + let a; + function foo(a) { + return a + 1; + } + let b = a + foo(1);"; + let allocator = Allocator::default(); + let semantic = get_semantic(&allocator, source, SourceType::default()); + + let top_level_a = semantic + .scopes() + .get_binding(semantic.scopes().root_scope_id(), &Atom::from("a")) + .unwrap(); + + let decl = semantic.symbol_declaration(top_level_a); + match semantic.nodes().get_node(decl).kind() { + AstKind::VariableDeclarator(decl) => { + assert_eq!(decl.kind, VariableDeclarationKind::Let); + } + kind => panic!("Expected VariableDeclarator for 'let', got {kind:?}"), + } + + let references = semantic.symbol_references(top_level_a); + assert_eq!(references.count(), 1); + } #[test] fn test_is_global() { let source = " - var a = 0; - function foo() { - a += 1; - } + var a = 0; + function foo() { + a += 1; + } - var b = a + 2; - "; + var b = a + 2; + "; let allocator = Allocator::default(); - let source_type = SourceType::default(); - let parse = - oxc_parser::Parser::new(&allocator, source, oxc_span::SourceType::default()).parse(); - assert!(parse.errors.is_empty()); - let program = allocator.alloc(parse.program); - - { - let semantic = SemanticBuilder::new(source, source_type).build(program); - assert!(semantic.errors.is_empty()); - let semantic = semantic.semantic; - for node in semantic.nodes().iter() { - if let AstKind::IdentifierReference(id) = node.kind() { - assert!(!semantic.is_reference_to_global_variable(id)); - } + let semantic = get_semantic(&allocator, source, SourceType::default()); + for node in semantic.nodes().iter() { + if let AstKind::IdentifierReference(id) = node.kind() { + assert!(!semantic.is_reference_to_global_variable(id)); } } } diff --git a/crates/oxc_semantic/src/symbol.rs b/crates/oxc_semantic/src/symbol.rs index baf98ba5e..fb6c4c447 100644 --- a/crates/oxc_semantic/src/symbol.rs +++ b/crates/oxc_semantic/src/symbol.rs @@ -92,7 +92,16 @@ impl SymbolTable { self.references[reference_id].symbol_id().is_none() } - pub fn get_resolved_references(&self, symbol_id: SymbolId) -> &Vec { + pub fn get_resolved_reference_ids(&self, symbol_id: SymbolId) -> &Vec { &self.resolved_references[symbol_id] } + + pub fn get_resolved_references( + &self, + symbol_id: SymbolId, + ) -> impl Iterator + '_ { + self.resolved_references[symbol_id] + .iter() + .map(|reference_id| &self.references[*reference_id]) + } }