From 73bb61c81587eb12156a20e48b67bdf78c320d40 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 31 Oct 2023 22:16:57 -0400 Subject: [PATCH] feat: ability to go to a symbol dep's definition (#314) --- src/type_tracer/analyzer.rs | 24 +++ src/type_tracer/cross_module.rs | 184 +++++++++++++++++----- src/type_tracer/mod.rs | 5 +- tests/specs/type_tracing/Namespaces02.txt | 180 +++++++++++++++++++++ 4 files changed, 355 insertions(+), 38 deletions(-) create mode 100644 tests/specs/type_tracing/Namespaces02.txt diff --git a/src/type_tracer/analyzer.rs b/src/type_tracer/analyzer.rs index 46ee687a4..e3e4d11a4 100644 --- a/src/type_tracer/analyzer.rs +++ b/src/type_tracer/analyzer.rs @@ -32,6 +32,7 @@ use super::collections::LockableRefCell; use super::cross_module; use super::cross_module::Definition; use super::ImportedExports; +use super::ResolvedSymbolDepEntry; use super::TypeTraceDiagnostic; use super::TypeTraceDiagnosticKind; use super::TypeTraceHandler; @@ -94,6 +95,20 @@ impl RootSymbol { &|specifier| self.get_module_from_specifier(specifier), ) } + + pub fn resolve_symbol_dep<'a>( + &'a self, + module_graph: &ModuleGraph, + module: ModuleSymbolRef<'a>, + dep: &'a SymbolDep, + ) -> Vec> { + super::cross_module::resolve_symbol_dep( + module_graph, + module, + dep, + &|specifier| self.get_module_from_specifier(specifier), + ) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -446,6 +461,15 @@ pub struct UniqueSymbolId { pub symbol_id: SymbolId, } +impl UniqueSymbolId { + pub fn new(module_id: ModuleId, symbol_id: SymbolId) -> Self { + Self { + module_id, + symbol_id, + } + } +} + #[derive(Debug, Clone, Copy)] pub enum ModuleSymbolRef<'a> { Json(&'a JsonModuleSymbol), diff --git a/src/type_tracer/cross_module.rs b/src/type_tracer/cross_module.rs index 0a51a76fd..ec5c4308a 100644 --- a/src/type_tracer/cross_module.rs +++ b/src/type_tracer/cross_module.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; -use anyhow::Result; use deno_ast::SourceRange; use indexmap::IndexMap; @@ -10,6 +9,7 @@ use crate::ModuleGraph; use crate::ModuleSpecifier; use super::analyzer::SymbolDeclKind; +use super::analyzer::SymbolDep; use super::FileDep; use super::FileDepName; use super::ModuleSymbolRef; @@ -33,6 +33,13 @@ pub struct Definition<'a> { } impl<'a> Definition<'a> { + pub fn unique_id(&self) -> UniqueSymbolId { + UniqueSymbolId { + module_id: self.module.module_id(), + symbol_id: self.symbol.symbol_id(), + } + } + pub fn range(&self) -> &SourceRange { &self.symbol_decl.range } @@ -112,31 +119,14 @@ fn go_to_definitions_internal<'a>( } } SymbolDeclKind::QualifiedTarget(target_id, parts) => { - if let Some(symbol_id) = - module.esm().unwrap().symbol_id_from_swc(target_id) - { - if let Ok(results) = resolve_qualified_name( - module_graph, - module, - symbol_id, - parts, - specifier_to_module, - ) { - for (specifier, symbol_id) in results { - if let Some(module_symbol) = specifier_to_module(&specifier) { - if let Some(symbol) = module_symbol.symbol(symbol_id) { - definitions.extend(go_to_definitions_internal( - module_graph, - module_symbol, - symbol, - visited_symbols, - specifier_to_module, - )); - } - } - } - } - } + definitions.extend(go_to_id_and_parts_definitions( + module_graph, + module, + target_id, + parts, + specifier_to_module, + visited_symbols, + )); } SymbolDeclKind::FileRef(file_ref) => match &file_ref.name { FileDepName::Star => { @@ -179,13 +169,135 @@ fn go_to_definitions_internal<'a>( definitions } +/// A resolved `SymbolDep`. +pub enum ResolvedSymbolDepEntry<'a> { + /// The definition of the symbol dep. + Definition(Definition<'a>), + /// If the symbol dep was an import type with no property access. + /// + /// Ex. `type MyType = typeof import("./my_module.ts");` + ImportType(ModuleSymbolRef<'a>), +} + +pub fn resolve_symbol_dep<'a>( + module_graph: &ModuleGraph, + module: ModuleSymbolRef<'a>, + dep: &SymbolDep, + specifier_to_module: &impl Fn(&ModuleSpecifier) -> Option>, +) -> Vec> { + match &dep { + SymbolDep::Id(id) => { + if let Some(symbol) = module.esm().and_then(|m| m.symbol_from_swc(id)) { + go_to_definitions(module_graph, module, symbol, specifier_to_module) + .into_iter() + .map(ResolvedSymbolDepEntry::Definition) + .collect() + } else { + Vec::new() + } + } + SymbolDep::QualifiedId(id, parts) => go_to_id_and_parts_definitions( + module_graph, + module, + id, + parts, + specifier_to_module, + &mut Default::default(), + ) + .into_iter() + .map(ResolvedSymbolDepEntry::Definition) + .collect(), + SymbolDep::ImportType(import_specifier, parts) => { + let maybe_dep_specifier = module_graph.resolve_dependency( + import_specifier, + module.specifier(), + /* prefer types */ true, + ); + let Some(module_symbol) = + maybe_dep_specifier.as_ref().and_then(specifier_to_module) + else { + return Vec::new(); + }; + if parts.is_empty() { + // an ImportType includes default exports + vec![ResolvedSymbolDepEntry::ImportType(module_symbol)] + } else { + let symbols = resolve_qualified_export_name( + module_graph, + module_symbol, + &parts[0], + &parts[1..], + specifier_to_module, + ); + let mut visited_symbols = HashSet::new(); + let mut definitions = Vec::new(); + for (specifier, symbol_id) in symbols { + if let Some(module) = specifier_to_module(&specifier) { + if let Some(symbol) = module.esm().and_then(|m| m.symbol(symbol_id)) + { + definitions.extend( + go_to_definitions_internal( + module_graph, + module, + symbol, + &mut visited_symbols, + specifier_to_module, + ) + .into_iter() + .map(ResolvedSymbolDepEntry::Definition), + ); + } + } + } + definitions + } + } + } +} + +fn go_to_id_and_parts_definitions<'a>( + module_graph: &ModuleGraph, + module: ModuleSymbolRef<'a>, + target_id: &deno_ast::swc::ast::Id, + parts: &[String], + specifier_to_module: &impl Fn(&ModuleSpecifier) -> Option>, + visited_symbols: &mut HashSet, +) -> Vec> { + let mut definitions = Vec::new(); + if let Some(symbol_id) = + module.esm().and_then(|m| m.symbol_id_from_swc(target_id)) + { + let results = resolve_qualified_name( + module_graph, + module, + symbol_id, + parts, + specifier_to_module, + ); + for (specifier, symbol_id) in results { + if let Some(module_symbol) = specifier_to_module(&specifier) { + if let Some(symbol) = module_symbol.symbol(symbol_id) { + definitions.extend(go_to_definitions_internal( + module_graph, + module_symbol, + symbol, + visited_symbols, + specifier_to_module, + )); + } + } + } + } + definitions +} + pub fn resolve_qualified_export_name<'a>( graph: &ModuleGraph, module_symbol: ModuleSymbolRef<'a>, export_name: &str, parts: &[String], specifier_to_module: &impl Fn(&ModuleSpecifier) -> Option>, -) -> Result> { +) -> Vec<(ModuleSpecifier, SymbolId)> { resolve_qualified_export_name_internal( graph, module_symbol, @@ -203,7 +315,7 @@ fn resolve_qualified_export_name_internal<'a>( parts: &[String], visited_symbols: &mut HashSet, specifier_to_module: &impl Fn(&ModuleSpecifier) -> Option>, -) -> Result> { +) -> Vec<(ModuleSpecifier, SymbolId)> { let exports = exports_and_re_exports(graph, module_symbol, specifier_to_module); if let Some((module, symbol_id)) = exports.get(export_name) { @@ -216,7 +328,7 @@ fn resolve_qualified_export_name_internal<'a>( specifier_to_module, ) } else { - Ok(Vec::new()) + Vec::new() } } @@ -226,7 +338,7 @@ pub fn resolve_qualified_name<'a>( symbol_id: SymbolId, parts: &[String], specifier_to_module: &impl Fn(&ModuleSpecifier) -> Option>, -) -> Result> { +) -> Vec<(ModuleSpecifier, SymbolId)> { resolve_qualified_name_internal( graph, module_symbol, @@ -244,9 +356,9 @@ fn resolve_qualified_name_internal<'a>( parts: &[String], visited_symbols: &mut HashSet, specifier_to_module: &impl Fn(&ModuleSpecifier) -> Option>, -) -> Result> { +) -> Vec<(ModuleSpecifier, SymbolId)> { if parts.is_empty() { - return Ok(vec![(module_symbol.specifier().clone(), symbol_id)]); + return vec![(module_symbol.specifier().clone(), symbol_id)]; } let mut result = Vec::new(); @@ -272,7 +384,7 @@ fn resolve_qualified_name_internal<'a>( &parts[1..], visited_symbols, specifier_to_module, - )?); + )); } } DefinitionKind::ExportStar(file_dep) => { @@ -290,15 +402,15 @@ fn resolve_qualified_name_internal<'a>( &parts[1..], visited_symbols, specifier_to_module, - )?); + )); } } } } } - Ok(result) + result } - None => Ok(Vec::new()), + None => Vec::new(), } } diff --git a/src/type_tracer/mod.rs b/src/type_tracer/mod.rs index 436f591f4..c93ecf65f 100644 --- a/src/type_tracer/mod.rs +++ b/src/type_tracer/mod.rs @@ -34,6 +34,7 @@ pub use self::analyzer::SymbolNodeRef; pub use self::analyzer::UniqueSymbolId; pub use self::cross_module::Definition; pub use self::cross_module::DefinitionKind; +pub use self::cross_module::ResolvedSymbolDepEntry; mod analyzer; mod collections; @@ -376,7 +377,7 @@ fn trace_module( &|specifier| { context.analyzer.get_or_analyze(specifier).ok().flatten() }, - )?); + )); } } SymbolDep::ImportType(import_specifier, parts) => { @@ -405,7 +406,7 @@ fn trace_module( &|specifier| { context.analyzer.get_or_analyze(specifier).ok().flatten() }, - )?); + )); } } } diff --git a/tests/specs/type_tracing/Namespaces02.txt b/tests/specs/type_tracing/Namespaces02.txt new file mode 100644 index 000000000..5e465f663 --- /dev/null +++ b/tests/specs/type_tracing/Namespaces02.txt @@ -0,0 +1,180 @@ +# mod.ts +export namespace RootNs { + export namespace NestedNs { + export enum Foo { + } + } + + export enum Foo { + } +} + +# output +file:///mod.ts: EsmModuleSymbol { + module_id: ModuleId( + 0, + ), + specifier: "file:///mod.ts", + child_decls: { + 0, + }, + exports: { + "RootNs": 0, + }, + re_exports: [], + swc_id_to_symbol_id: { + ( + "RootNs", + #2, + ): 0, + ( + "NestedNs", + #3, + ): 1, + ( + "Foo", + #4, + ): 2, + ( + "Foo", + #3, + ): 3, + }, + symbols: { + 0: Symbol { + symbol_id: 0, + is_public: true, + decls: [ + SymbolDecl { + range: SourceRange { + start: SourcePos( + 0, + ), + end: SourcePos( + 114, + ), + }, + kind: Definition( + SymbolNode( + "", + ), + ), + }, + ], + deps: { + Id( + ( + "NestedNs", + #3, + ), + ), + Id( + ( + "Foo", + #3, + ), + ), + }, + child_decls: { + 1, + 3, + }, + exports: { + "NestedNs": 1, + "Foo": 3, + }, + }, + 1: Symbol { + symbol_id: 1, + is_public: true, + decls: [ + SymbolDecl { + range: SourceRange { + start: SourcePos( + 28, + ), + end: SourcePos( + 87, + ), + }, + kind: Definition( + SymbolNode( + "", + ), + ), + }, + ], + deps: { + Id( + ( + "Foo", + #4, + ), + ), + }, + child_decls: { + 2, + }, + exports: { + "Foo": 2, + }, + }, + 2: Symbol { + symbol_id: 2, + is_public: true, + decls: [ + SymbolDecl { + range: SourceRange { + start: SourcePos( + 60, + ), + end: SourcePos( + 83, + ), + }, + kind: Definition( + SymbolNode( + "", + ), + ), + }, + ], + deps: {}, + child_decls: {}, + exports: {}, + }, + 3: Symbol { + symbol_id: 3, + is_public: true, + decls: [ + SymbolDecl { + range: SourceRange { + start: SourcePos( + 91, + ), + end: SourcePos( + 112, + ), + }, + kind: Definition( + SymbolNode( + "", + ), + ), + }, + ], + deps: {}, + child_decls: {}, + exports: {}, + }, + }, + traced_re_exports: {}, + traced_referrers: {}, +} +== export definitions == +[RootNs]: file:///mod.ts:0..114 + export namespace RootNs { + export namespace NestedNs { + ... + } + }