diff --git a/crates/oxc_ast/src/ast_kind_impl.rs b/crates/oxc_ast/src/ast_kind_impl.rs index bcf1b56ba3dec..11387e4d68d29 100644 --- a/crates/oxc_ast/src/ast_kind_impl.rs +++ b/crates/oxc_ast/src/ast_kind_impl.rs @@ -348,6 +348,7 @@ impl<'a> AstKind<'a> { Self::TSConditionalType(_) => "TSConditionalType".into(), Self::TSMappedType(_) => "TSMappedType".into(), Self::TSConstructSignatureDeclaration(_) => "TSConstructSignatureDeclaration".into(), + Self::TSModuleReference(_) => "TSModuleReference".into(), } } } diff --git a/crates/oxc_ast/src/generated/ast_kind.rs b/crates/oxc_ast/src/generated/ast_kind.rs index 91eed09b2b849..d89ca1e512e3d 100644 --- a/crates/oxc_ast/src/generated/ast_kind.rs +++ b/crates/oxc_ast/src/generated/ast_kind.rs @@ -152,6 +152,7 @@ pub enum AstType { TSSatisfiesExpression, TSTypeAssertion, TSImportEqualsDeclaration, + TSModuleReference, TSExternalModuleReference, TSNonNullExpression, Decorator, @@ -321,6 +322,7 @@ pub enum AstKind<'a> { TSSatisfiesExpression(&'a TSSatisfiesExpression<'a>), TSTypeAssertion(&'a TSTypeAssertion<'a>), TSImportEqualsDeclaration(&'a TSImportEqualsDeclaration<'a>), + TSModuleReference(&'a TSModuleReference<'a>), TSExternalModuleReference(&'a TSExternalModuleReference<'a>), TSNonNullExpression(&'a TSNonNullExpression<'a>), Decorator(&'a Decorator<'a>), @@ -491,6 +493,7 @@ impl<'a> GetSpan for AstKind<'a> { Self::TSSatisfiesExpression(it) => it.span(), Self::TSTypeAssertion(it) => it.span(), Self::TSImportEqualsDeclaration(it) => it.span(), + Self::TSModuleReference(it) => it.span(), Self::TSExternalModuleReference(it) => it.span(), Self::TSNonNullExpression(it) => it.span(), Self::Decorator(it) => it.span(), diff --git a/crates/oxc_ast/src/generated/visit.rs b/crates/oxc_ast/src/generated/visit.rs index 5abd35de2e343..b953314fc94e6 100644 --- a/crates/oxc_ast/src/generated/visit.rs +++ b/crates/oxc_ast/src/generated/visit.rs @@ -3939,6 +3939,8 @@ pub mod walk { #[inline] pub fn walk_ts_module_reference<'a, V: Visit<'a>>(visitor: &mut V, it: &TSModuleReference<'a>) { + let kind = AstKind::TSModuleReference(visitor.alloc(it)); + visitor.enter_node(kind); match it { TSModuleReference::ExternalModuleReference(it) => { visitor.visit_ts_external_module_reference(it) @@ -3947,6 +3949,7 @@ pub mod walk { visitor.visit_ts_type_name(it.to_ts_type_name()) } } + visitor.leave_node(kind); } #[inline] diff --git a/crates/oxc_ast/src/generated/visit_mut.rs b/crates/oxc_ast/src/generated/visit_mut.rs index 8fa4d4d67d6d7..d0961c65f275d 100644 --- a/crates/oxc_ast/src/generated/visit_mut.rs +++ b/crates/oxc_ast/src/generated/visit_mut.rs @@ -4166,6 +4166,8 @@ pub mod walk_mut { visitor: &mut V, it: &mut TSModuleReference<'a>, ) { + let kind = AstType::TSModuleReference; + visitor.enter_node(kind); match it { TSModuleReference::ExternalModuleReference(it) => { visitor.visit_ts_external_module_reference(it) @@ -4174,6 +4176,7 @@ pub mod walk_mut { visitor.visit_ts_type_name(it.to_ts_type_name_mut()) } } + visitor.leave_node(kind); } #[inline] diff --git a/crates/oxc_linter/src/rules/eslint/no_global_assign.rs b/crates/oxc_linter/src/rules/eslint/no_global_assign.rs index a8b3710bc5b32..6d502f324d0f2 100644 --- a/crates/oxc_linter/src/rules/eslint/no_global_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_global_assign.rs @@ -61,8 +61,8 @@ impl Rule for NoGlobalAssign { fn run_once(&self, ctx: &LintContext) { let symbol_table = ctx.symbols(); - for reference_id_list in ctx.scopes().root_unresolved_references().values() { - for &reference_id in reference_id_list { + for reference_id_list in ctx.scopes().root_unresolved_references_ids() { + for reference_id in reference_id_list { let reference = symbol_table.get_reference(reference_id); if reference.is_write() { let name = reference.name(); 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 898638ce12ee9..7d10ec08beb0b 100644 --- a/crates/oxc_linter/src/rules/eslint/no_import_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_import_assign.rs @@ -52,7 +52,7 @@ const REFLECT_MUTATION_METHODS: phf::Set<&'static str> = 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() { + if symbol_table.get_flag(symbol_id).is_import() { let kind = ctx.nodes().kind(symbol_table.get_declaration(symbol_id)); let is_namespace_specifier = matches!(kind, AstKind::ImportNamespaceSpecifier(_)); for reference in symbol_table.get_resolved_references(symbol_id) { diff --git a/crates/oxc_linter/src/rules/eslint/no_undef.rs b/crates/oxc_linter/src/rules/eslint/no_undef.rs index 4315a53557a4a..75826fe9df3d6 100644 --- a/crates/oxc_linter/src/rules/eslint/no_undef.rs +++ b/crates/oxc_linter/src/rules/eslint/no_undef.rs @@ -49,8 +49,8 @@ impl Rule for NoUndef { fn run_once(&self, ctx: &LintContext) { let symbol_table = ctx.symbols(); - for reference_id_list in ctx.scopes().root_unresolved_references().values() { - for &reference_id in reference_id_list { + for reference_id_list in ctx.scopes().root_unresolved_references_ids() { + for reference_id in reference_id_list { let reference = symbol_table.get_reference(reference_id); let name = reference.name(); diff --git a/crates/oxc_linter/src/rules/jest/no_confusing_set_timeout.rs b/crates/oxc_linter/src/rules/jest/no_confusing_set_timeout.rs index af0941d7dbb1d..dcb26371ec757 100644 --- a/crates/oxc_linter/src/rules/jest/no_confusing_set_timeout.rs +++ b/crates/oxc_linter/src/rules/jest/no_confusing_set_timeout.rs @@ -90,15 +90,19 @@ impl Rule for NoConfusingSetTimeout { let mut jest_reference_id_list: Vec<(ReferenceId, Span)> = vec![]; let mut seen_jest_set_timeout = false; - for reference_ids in scopes.root_unresolved_references().values() { + for reference_ids in scopes.root_unresolved_references_ids() { collect_jest_reference_id(reference_ids, &mut jest_reference_id_list, ctx); } for reference_ids in &symbol_table.resolved_references { - collect_jest_reference_id(reference_ids, &mut jest_reference_id_list, ctx); + collect_jest_reference_id( + reference_ids.iter().copied(), + &mut jest_reference_id_list, + ctx, + ); } - for reference_id_list in scopes.root_unresolved_references().values() { + for reference_id_list in scopes.root_unresolved_references_ids() { handle_jest_set_time_out( ctx, reference_id_list, @@ -111,7 +115,7 @@ impl Rule for NoConfusingSetTimeout { for reference_id_list in &symbol_table.resolved_references { handle_jest_set_time_out( ctx, - reference_id_list, + reference_id_list.iter().copied(), &jest_reference_id_list, &mut seen_jest_set_timeout, &id_to_jest_node_map, @@ -121,7 +125,7 @@ impl Rule for NoConfusingSetTimeout { } fn collect_jest_reference_id( - reference_id_list: &Vec, + reference_id_list: impl Iterator, jest_reference_list: &mut Vec<(ReferenceId, Span)>, ctx: &LintContext, ) { @@ -129,7 +133,7 @@ fn collect_jest_reference_id( let nodes = ctx.nodes(); for reference_id in reference_id_list { - let reference = symbol_table.get_reference(*reference_id); + let reference = symbol_table.get_reference(reference_id); if !is_jest_call(reference.name()) { continue; } @@ -139,13 +143,13 @@ fn collect_jest_reference_id( let AstKind::MemberExpression(member_expr) = parent_node.kind() else { continue; }; - jest_reference_list.push((*reference_id, member_expr.span())); + jest_reference_list.push((reference_id, member_expr.span())); } } fn handle_jest_set_time_out<'a>( ctx: &LintContext<'a>, - reference_id_list: &Vec, + reference_id_list: impl Iterator, jest_reference_id_list: &Vec<(ReferenceId, Span)>, seen_jest_set_timeout: &mut bool, id_to_jest_node_map: &HashMap>, @@ -154,7 +158,7 @@ fn handle_jest_set_time_out<'a>( let scopes = ctx.scopes(); let symbol_table = ctx.symbols(); - for &reference_id in reference_id_list { + for reference_id in reference_id_list { let reference = symbol_table.get_reference(reference_id); let Some(parent_node) = nodes.parent_node(reference.node_id()) else { diff --git a/crates/oxc_linter/src/rules/jest/no_jasmine_globals.rs b/crates/oxc_linter/src/rules/jest/no_jasmine_globals.rs index 3ed3ff259c053..e0f73bf31d068 100644 --- a/crates/oxc_linter/src/rules/jest/no_jasmine_globals.rs +++ b/crates/oxc_linter/src/rules/jest/no_jasmine_globals.rs @@ -53,7 +53,7 @@ impl Rule for NoJasmineGlobals { .filter(|(key, _)| NON_JASMINE_PROPERTY_NAMES.contains(&key.as_str())); for (name, reference_ids) in jasmine_references { - for &reference_id in reference_ids { + for &(reference_id, _) in reference_ids { let reference = symbol_table.get_reference(reference_id); if let Some((error, help)) = get_non_jasmine_property_messages(name) { ctx.diagnostic(no_jasmine_globals_diagnostic(error, help, reference.span())); diff --git a/crates/oxc_linter/src/rules/jest/no_mocks_import.rs b/crates/oxc_linter/src/rules/jest/no_mocks_import.rs index ecdbc2a2bba02..a27e4fb725020 100644 --- a/crates/oxc_linter/src/rules/jest/no_mocks_import.rs +++ b/crates/oxc_linter/src/rules/jest/no_mocks_import.rs @@ -48,7 +48,7 @@ impl Rule for NoMocksImport { return; }; - for reference_id in require_reference_ids { + for (reference_id, _) in require_reference_ids { let reference = ctx.symbols().get_reference(*reference_id); let Some(parent) = ctx.nodes().parent_node(reference.node_id()) else { return; diff --git a/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs b/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs index 4be6dd539e167..b8faa966a1e91 100644 --- a/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs +++ b/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs @@ -48,7 +48,7 @@ impl Rule for NoDuplicateHead { } let flag = symbols.get_flag(symbol_id); - if !flag.is_import_binding() { + if !flag.is_import() { return; } diff --git a/crates/oxc_linter/src/utils/jest.rs b/crates/oxc_linter/src/utils/jest.rs index 090bb1092f973..10d33bbfc27b8 100644 --- a/crates/oxc_linter/src/utils/jest.rs +++ b/crates/oxc_linter/src/utils/jest.rs @@ -7,7 +7,7 @@ use oxc_ast::{ }, AstKind, }; -use oxc_semantic::{AstNode, ReferenceId}; +use oxc_semantic::{AstNode, ReferenceFlag, ReferenceId}; use phf::phf_set; use crate::LintContext; @@ -159,7 +159,7 @@ pub fn collect_possible_jest_call_node<'a, 'b>( collect_ids_referenced_to_global(ctx) .iter() // set the original of global test function to None - .map(|id| (*id, None)), + .map(|(id, _)| (*id, None)), ); } @@ -198,7 +198,7 @@ fn collect_ids_referenced_to_import<'a>( .resolved_references .iter_enumerated() .filter_map(|(symbol_id, reference_ids)| { - if ctx.symbols().get_flag(symbol_id).is_import_binding() { + if ctx.symbols().get_flag(symbol_id).is_import() { let id = ctx.symbols().get_declaration(symbol_id); let Some(AstKind::ImportDeclaration(import_decl)) = ctx.nodes().parent_kind(id) else { @@ -236,13 +236,13 @@ fn find_original_name<'a>(import_decl: &'a ImportDeclaration<'a>, name: &str) -> }) } -fn collect_ids_referenced_to_global(ctx: &LintContext) -> Vec { +fn collect_ids_referenced_to_global(ctx: &LintContext) -> Vec<(ReferenceId, ReferenceFlag)> { ctx.scopes() .root_unresolved_references() .iter() .filter(|(name, _)| JEST_METHOD_NAMES.contains(name.as_str())) .flat_map(|(_, reference_ids)| reference_ids.clone()) - .collect::>() + .collect::>() } /// join name of the expression. e.g. diff --git a/crates/oxc_semantic/src/binder.rs b/crates/oxc_semantic/src/binder.rs index b268a913cd054..5ed04d9ec9c09 100644 --- a/crates/oxc_semantic/src/binder.rs +++ b/crates/oxc_semantic/src/binder.rs @@ -251,11 +251,24 @@ impl<'a> Binder for CatchParameter<'a> { } } -fn declare_symbol_for_import_specifier(ident: &BindingIdentifier, builder: &mut SemanticBuilder) { +fn declare_symbol_for_import_specifier( + ident: &BindingIdentifier, + is_type: bool, + builder: &mut SemanticBuilder, +) { + let includes = if is_type + || builder.nodes.parent_kind(builder.current_node_id).is_some_and( + |decl| matches!(decl, AstKind::ImportDeclaration(decl) if decl.import_kind.is_type()), + ) { + SymbolFlags::TypeImport + } else { + SymbolFlags::Import + }; + let symbol_id = builder.declare_symbol( ident.span, &ident.name, - SymbolFlags::ImportBinding, + includes, SymbolFlags::ImportBindingExcludes, ); ident.symbol_id.set(Some(symbol_id)); @@ -263,25 +276,25 @@ fn declare_symbol_for_import_specifier(ident: &BindingIdentifier, builder: &mut impl<'a> Binder for ImportSpecifier<'a> { fn bind(&self, builder: &mut SemanticBuilder) { - declare_symbol_for_import_specifier(&self.local, builder); + declare_symbol_for_import_specifier(&self.local, self.import_kind.is_type(), builder); } } impl<'a> Binder for ImportDefaultSpecifier<'a> { fn bind(&self, builder: &mut SemanticBuilder) { - declare_symbol_for_import_specifier(&self.local, builder); + declare_symbol_for_import_specifier(&self.local, false, builder); } } impl<'a> Binder for ImportNamespaceSpecifier<'a> { fn bind(&self, builder: &mut SemanticBuilder) { - declare_symbol_for_import_specifier(&self.local, builder); + declare_symbol_for_import_specifier(&self.local, false, builder); } } impl<'a> Binder for TSImportEqualsDeclaration<'a> { fn bind(&self, builder: &mut SemanticBuilder) { - declare_symbol_for_import_specifier(&self.id, builder); + declare_symbol_for_import_specifier(&self.id, false, builder); } } diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index f3a6c72ffab81..723333d7f53b5 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -353,12 +353,13 @@ impl<'a> SemanticBuilder<'a> { /// # Panics pub fn declare_reference(&mut self, reference: Reference) -> ReferenceId { let reference_name = reference.name().clone(); + let reference_flag = *reference.flag(); let reference_id = self.symbols.create_reference(reference); self.unresolved_references[self.current_scope_depth] .entry(reference_name) .or_default() - .push(reference_id); + .push((reference_id, reference_flag)); reference_id } @@ -385,19 +386,39 @@ impl<'a> SemanticBuilder<'a> { let parent_refs = iter.nth(self.current_scope_depth - 1).unwrap(); let current_refs = iter.next().unwrap(); - let bindings = self.scope.get_bindings(self.current_scope_id); - for (name, reference_ids) in current_refs.drain() { + for (name, mut references) in current_refs.drain() { // Try to resolve a reference. // If unresolved, transfer it to parent scope's unresolved references. + let bindings = self.scope.get_bindings(self.current_scope_id); if let Some(symbol_id) = bindings.get(&name).copied() { - for reference_id in &reference_ids { - self.symbols.references[*reference_id].set_symbol_id(symbol_id); + let symbol_flag = self.symbols.get_flag(symbol_id); + + let resolved_references: &mut Vec<_> = + self.symbols.resolved_references[symbol_id].as_mut(); + // Reserve space for all references to avoid reallocations. + resolved_references.reserve(references.len()); + + references.retain(|(id, flag)| { + if flag.is_type() && symbol_flag.is_type() + || flag.is_value() && symbol_flag.is_value() + { + self.symbols.references[*id].set_symbol_id(symbol_id); + resolved_references.push(*id); + false + } else { + true + } + }); + + if references.is_empty() { + continue; } - self.symbols.resolved_references[symbol_id].extend(reference_ids); - } else if let Some(parent_reference_ids) = parent_refs.get_mut(&name) { - parent_reference_ids.extend(reference_ids); + } + + if let Some(parent_reference_ids) = parent_refs.get_mut(&name) { + parent_reference_ids.extend(references); } else { - parent_refs.insert(name, reference_ids); + parent_refs.insert(name, references); } } } @@ -1692,7 +1713,19 @@ impl<'a> SemanticBuilder<'a> { self.current_reference_flag = ReferenceFlag::Type; } AstKind::TSTypeName(_) => { - self.current_reference_flag = ReferenceFlag::Type; + match self.nodes.parent_kind(self.current_node_id) { + Some(AstKind::TSModuleReference(_)) => { + // import A = a; + self.current_reference_flag = ReferenceFlag::Read; + } + Some(AstKind::TSQualifiedName(_)) => { + // import A = a.b + // ^^^ Keep the current reference flag + } + _ => { + self.current_reference_flag = ReferenceFlag::Type; + } + } } AstKind::IdentifierReference(ident) => { self.reference_identifier(ident); diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index 52376d4ff4974..f812232ee27f1 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -273,7 +273,7 @@ mod tests { (SourceType::default(), "let a, b; b = a = 1", ReferenceFlag::read_write()), (SourceType::default(), "let a, b; b = (a = 1)", ReferenceFlag::read_write()), (SourceType::default(), "let a, b, c; b = c = a", ReferenceFlag::read()), - // sequences return last value in sequence + // sequences return last read_write in sequence (SourceType::default(), "let a, b; b = (0, a++)", ReferenceFlag::read_write()), // loops ( @@ -300,7 +300,7 @@ mod tests { "let a, b; if (b == a) { true } else { false }", ReferenceFlag::read(), ), - // identifiers not in last value are also considered a read (at + // identifiers not in last read_write are also considered a read (at // least, or now) (SourceType::default(), "let a, b; b = (a, 0)", ReferenceFlag::read()), (SourceType::default(), "let a, b; b = (--a, 0)", ReferenceFlag::read_write()), diff --git a/crates/oxc_semantic/src/reference.rs b/crates/oxc_semantic/src/reference.rs index 145f3494b0856..c32678bbf497b 100644 --- a/crates/oxc_semantic/src/reference.rs +++ b/crates/oxc_semantic/src/reference.rs @@ -63,6 +63,10 @@ impl Reference { &mut self.flag } + pub fn flag(&self) -> &ReferenceFlag { + &self.flag + } + /// Returns `true` if the identifier value was read. This is not mutually /// exclusive with [`#is_write`] pub fn is_read(&self) -> bool { diff --git a/crates/oxc_semantic/src/scope.rs b/crates/oxc_semantic/src/scope.rs index d1ac3d4c8ae96..379c457542158 100644 --- a/crates/oxc_semantic/src/scope.rs +++ b/crates/oxc_semantic/src/scope.rs @@ -3,15 +3,17 @@ use std::hash::BuildHasherDefault; use indexmap::IndexMap; use oxc_index::IndexVec; use oxc_span::CompactStr; +use oxc_syntax::reference::{ReferenceFlag, ReferenceId}; pub use oxc_syntax::scope::{ScopeFlags, ScopeId}; use rustc_hash::{FxHashMap, FxHasher}; -use crate::{reference::ReferenceId, symbol::SymbolId, AstNodeId}; +use crate::{symbol::SymbolId, AstNodeId}; type FxIndexMap = IndexMap>; type Bindings = FxIndexMap; -pub(crate) type UnresolvedReferences = FxHashMap>; +pub(crate) type UnresolvedReference = (ReferenceId, ReferenceFlag); +pub(crate) type UnresolvedReferences = FxHashMap>; /// Scope Tree /// @@ -90,6 +92,12 @@ impl ScopeTree { &self.root_unresolved_references } + pub fn root_unresolved_references_ids( + &self, + ) -> impl Iterator + '_> + '_ { + self.root_unresolved_references.values().map(|v| v.iter().map(|(id, _)| *id)) + } + pub fn get_flags(&self, scope_id: ScopeId) -> ScopeFlags { self.flags[scope_id] } @@ -140,8 +148,12 @@ impl ScopeTree { self.get_binding(self.root_scope_id(), name) } - pub fn add_root_unresolved_reference(&mut self, name: CompactStr, reference_id: ReferenceId) { - self.root_unresolved_references.entry(name).or_default().push(reference_id); + pub fn add_root_unresolved_reference( + &mut self, + name: CompactStr, + reference: UnresolvedReference, + ) { + self.root_unresolved_references.entry(name).or_default().push(reference); } pub fn has_binding(&self, scope_id: ScopeId, name: &str) -> bool { diff --git a/crates/oxc_semantic/tests/integration/modules.rs b/crates/oxc_semantic/tests/integration/modules.rs index 98b6f8cd51cc2..f221e94bdbbf6 100644 --- a/crates/oxc_semantic/tests/integration/modules.rs +++ b/crates/oxc_semantic/tests/integration/modules.rs @@ -180,7 +180,7 @@ fn test_exports_in_namespace() { return foo(); } export const x = 2 - } + } ", ); test.has_some_symbol("bar").is_exported().test(); @@ -207,11 +207,11 @@ fn test_export_in_invalid_scope() { fn test_import_assignment() { SemanticTester::ts("import Foo = require('./foo')") .has_root_symbol("Foo") - .contains_flags(SymbolFlags::ImportBinding) + .contains_flags(SymbolFlags::Import) .test(); SemanticTester::ts("import { Foo } from './foo'; import Baz = Foo.Bar.Baz") .has_root_symbol("Baz") - .contains_flags(SymbolFlags::ImportBinding) + .contains_flags(SymbolFlags::Import) .test(); } diff --git a/crates/oxc_semantic/tests/integration/symbols.rs b/crates/oxc_semantic/tests/integration/symbols.rs index 3d0517ca03305..b879758c9c11f 100644 --- a/crates/oxc_semantic/tests/integration/symbols.rs +++ b/crates/oxc_semantic/tests/integration/symbols.rs @@ -237,3 +237,45 @@ fn test_ts_infer_type() { // type C is not referenced tester.has_symbol("C").has_number_of_references(0).test(); } + +#[test] +fn test_value_used_as_type() { + // Type annotations (or any type reference) do not resolve to value symbols + SemanticTester::ts( + " + const x = 1; + function foo(a: x) { } + ", + ) + .has_root_symbol("x") + .intersects_flags(SymbolFlags::Value) + .has_number_of_references(0) + .test(); + + // T is a value that gets shadowed by a type. When `T` is referenced within + // a value context, the root `const T` should be the symbol recoreded in the + // reference. + let tester = SemanticTester::ts( + " +const T = 1; +function foo(a: T) { + return a + T; +} +", + ); + + tester.has_root_symbol("T").has_number_of_reads(1).test(); +} + +#[test] +fn test_type_used_as_value() { + SemanticTester::ts( + " + type T = number; + let x = T; + ", + ) + .has_some_symbol("T") + .has_number_of_reads(0) + .test(); +} diff --git a/crates/oxc_syntax/src/reference.rs b/crates/oxc_syntax/src/reference.rs index c4c67f1194f94..95a893886c0de 100644 --- a/crates/oxc_syntax/src/reference.rs +++ b/crates/oxc_syntax/src/reference.rs @@ -32,7 +32,7 @@ export type ReferenceFlag = { Read: 0b1, Write: 0b10, Type: 0b100, - ReadWrite: 0b11 + Value: 0b11 } "#; @@ -45,7 +45,7 @@ bitflags! { const Write = 1 << 1; // Used in type definitions. const Type = 1 << 2; - const ReadWrite = Self::Read.bits() | Self::Write.bits(); + const Value = Self::Read.bits() | Self::Write.bits(); } } @@ -59,7 +59,7 @@ impl ReferenceFlag { } pub const fn read_write() -> Self { - Self::ReadWrite + Self::Value } /// The identifier is read from. It may also be written to. @@ -83,12 +83,16 @@ impl ReferenceFlag { } /// The identifier is both read from and written to, e.g `a += 1`. - pub const fn is_read_write(&self) -> bool { - self.contains(Self::ReadWrite) + pub fn is_read_write(&self) -> bool { + self.contains(Self::Read | Self::Write) } /// The identifier is used in a type definition. pub const fn is_type(&self) -> bool { self.contains(Self::Type) } + + pub const fn is_value(&self) -> bool { + self.intersects(Self::Value) + } } diff --git a/crates/oxc_syntax/src/symbol.rs b/crates/oxc_syntax/src/symbol.rs index dc659da48dd4e..7f782011c0377 100644 --- a/crates/oxc_syntax/src/symbol.rs +++ b/crates/oxc_syntax/src/symbol.rs @@ -46,25 +46,26 @@ bitflags! { const Class = 1 << 5; const CatchVariable = 1 << 6; // try {} catch(catch_variable) {} const Function = 1 << 7; - const ImportBinding = 1 << 8; // Imported ESM binding + const Import = 1 << 8; // Imported ESM binding + const TypeImport = 1 << 9; // Imported ESM type-only binding // Type specific symbol flags - const TypeAlias = 1 << 9; - const Interface = 1 << 10; - const RegularEnum = 1 << 11; - const ConstEnum = 1 << 12; - const EnumMember = 1 << 13; - const TypeLiteral = 1 << 14; - const TypeParameter = 1 << 15; - const NameSpaceModule = 1 << 16; - const ValueModule = 1 << 17; + const TypeAlias = 1 << 10; + const Interface = 1 << 11; + const RegularEnum = 1 << 12; + const ConstEnum = 1 << 13; + const EnumMember = 1 << 14; + const TypeLiteral = 1 << 15; + const TypeParameter = 1 << 16; + const NameSpaceModule = 1 << 17; + const ValueModule = 1 << 18; // In a dts file or there is a declare flag - const Ambient = 1 << 18; + const Ambient = 1 << 19; const Enum = Self::ConstEnum.bits() | Self::RegularEnum.bits(); const Variable = Self::FunctionScopedVariable.bits() | Self::BlockScopedVariable.bits(); - const Value = Self::Variable.bits() | Self::Class.bits() | Self::Enum.bits() | Self::ValueModule.bits(); - const Type = Self::Class.bits() | Self::Interface.bits() | Self::Enum.bits() | Self::TypeLiteral.bits() | Self::TypeParameter.bits() | Self::TypeAlias.bits(); + const Value = Self::Variable.bits() | Self::Class.bits() | Self::Enum.bits() | Self::EnumMember.bits() | Self::ValueModule.bits(); + const Type = Self::Class.bits() | Self::Interface.bits() | Self::Enum.bits() | Self::EnumMember.bits() | Self::TypeLiteral.bits() | Self::TypeParameter.bits() | Self::TypeAlias.bits(); /// Variables can be redeclared, but can not redeclare a block-scoped declaration with the /// same name, or any other value that is not a variable, e.g. ValueModule or Class @@ -75,7 +76,7 @@ bitflags! { const BlockScopedVariableExcludes = Self::Value.bits(); const ClassExcludes = (Self::Value.bits() | Self::TypeAlias.bits()) & !Self::ValueModule.bits() ; - const ImportBindingExcludes = Self::ImportBinding.bits(); + const ImportBindingExcludes = Self::Import.bits() | Self::TypeImport.bits(); // Type specific excludes const TypeAliasExcludes = Self::Type.bits(); const InterfaceExcludes = Self::Type.bits() & !(Self::Interface.bits() | Self::Class.bits()); @@ -94,7 +95,11 @@ impl SymbolFlags { } pub fn is_type(&self) -> bool { - !self.intersects(Self::Value) + self.intersects(Self::Type | Self::TypeImport | Self::Import) + } + + pub fn is_value(&self) -> bool { + self.intersects(Self::Value | Self::Import | Self::Function) } pub fn is_const_variable(&self) -> bool { @@ -121,7 +126,7 @@ impl SymbolFlags { self.contains(Self::Export) } - pub fn is_import_binding(&self) -> bool { - self.contains(Self::ImportBinding) + pub fn is_import(&self) -> bool { + self.intersects(Self::Import | Self::TypeImport) } } diff --git a/crates/oxc_traverse/src/context/scoping.rs b/crates/oxc_traverse/src/context/scoping.rs index 30482793a4d11..f2c862e8cec74 100644 --- a/crates/oxc_traverse/src/context/scoping.rs +++ b/crates/oxc_traverse/src/context/scoping.rs @@ -299,7 +299,7 @@ impl TraverseScoping { ) -> ReferenceId { let reference = Reference::new(SPAN, name.clone(), AstNodeId::DUMMY, flag); let reference_id = self.symbols.create_reference(reference); - self.scopes.add_root_unresolved_reference(name, reference_id); + self.scopes.add_root_unresolved_reference(name, (reference_id, flag)); reference_id } diff --git a/tasks/ast_codegen/src/generators/ast_kind.rs b/tasks/ast_codegen/src/generators/ast_kind.rs index a836b5cd1cde3..4af92ccf2e502 100644 --- a/tasks/ast_codegen/src/generators/ast_kind.rs +++ b/tasks/ast_codegen/src/generators/ast_kind.rs @@ -8,7 +8,7 @@ use super::generated_header; pub struct AstKindGenerator; -pub const BLACK_LIST: [&str; 66] = [ +pub const BLACK_LIST: [&str; 65] = [ "Expression", "ObjectPropertyKind", "TemplateElement", @@ -62,7 +62,6 @@ pub const BLACK_LIST: [&str; 66] = [ "TSImportAttributeName", "TSFunctionType", "TSConstructorType", - "TSModuleReference", "TSExportAssignment", "TSNamespaceExportDeclaration", "JSDocNullableType",