mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
fix(semantic): resolve references to the incorrect symbol (#4280)
close: #3799 related: #3863
This commit is contained in:
parent
af4dc0112e
commit
1108f2a700
23 changed files with 198 additions and 72 deletions
|
|
@ -348,6 +348,7 @@ impl<'a> AstKind<'a> {
|
|||
Self::TSConditionalType(_) => "TSConditionalType".into(),
|
||||
Self::TSMappedType(_) => "TSMappedType".into(),
|
||||
Self::TSConstructSignatureDeclaration(_) => "TSConstructSignatureDeclaration".into(),
|
||||
Self::TSModuleReference(_) => "TSModuleReference".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ReferenceId>,
|
||||
reference_id_list: impl Iterator<Item = ReferenceId>,
|
||||
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<ReferenceId>,
|
||||
reference_id_list: impl Iterator<Item = ReferenceId>,
|
||||
jest_reference_id_list: &Vec<(ReferenceId, Span)>,
|
||||
seen_jest_set_timeout: &mut bool,
|
||||
id_to_jest_node_map: &HashMap<AstNodeId, &PossibleJestNode<'a, '_>>,
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ReferenceId> {
|
||||
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::<Vec<ReferenceId>>()
|
||||
.collect::<Vec<(ReferenceId, ReferenceFlag)>>()
|
||||
}
|
||||
|
||||
/// join name of the expression. e.g.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
type Bindings = FxIndexMap<CompactStr, SymbolId>;
|
||||
pub(crate) type UnresolvedReferences = FxHashMap<CompactStr, Vec<ReferenceId>>;
|
||||
pub(crate) type UnresolvedReference = (ReferenceId, ReferenceFlag);
|
||||
pub(crate) type UnresolvedReferences = FxHashMap<CompactStr, Vec<UnresolvedReference>>;
|
||||
|
||||
/// Scope Tree
|
||||
///
|
||||
|
|
@ -90,6 +92,12 @@ impl ScopeTree {
|
|||
&self.root_unresolved_references
|
||||
}
|
||||
|
||||
pub fn root_unresolved_references_ids(
|
||||
&self,
|
||||
) -> impl Iterator<Item = impl Iterator<Item = ReferenceId> + '_> + '_ {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T extends number>(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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue