morph_cli/core/ast/
graph.rs1use std::collections::{HashMap, HashSet};
2use std::path::{Path, PathBuf};
3use crate::core::ast::parser::parse_file;
4use crate::utils::path::resolve_relative_import;
5use swc_ecma_ast::*;
6
7#[derive(Debug, Default, Clone)]
8pub struct ImportGraph {
9 imports: HashMap<PathBuf, HashSet<PathBuf>>,
11 dependents: HashMap<PathBuf, HashSet<PathBuf>>,
13 exports: HashMap<PathBuf, HashSet<String>>,
15}
16
17impl ImportGraph {
18 pub fn new() -> Self {
19 Self::default()
20 }
21
22 pub fn analyze_file(&mut self, path: &Path) -> anyhow::Result<()> {
23 let parsed = parse_file(path)?;
24 let mut local_imports = HashSet::new();
25 let mut local_exports = HashSet::new();
26
27 for item in &parsed.module.body {
28 match item {
29 ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
30 let src = import.src.value.to_string();
31 if src.starts_with('.') {
32 if let Some(resolved) = resolve_relative_import(path, &src) {
33 local_imports.insert(resolved);
34 }
35 }
36 }
37 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
38 for spec in &export.specifiers {
39 match spec {
40 ExportSpecifier::Named(named) => {
41 let name = match &named.orig {
42 ModuleExportName::Ident(id) => id.sym.to_string(),
43 ModuleExportName::Str(s) => s.value.to_string(),
44 };
45 local_exports.insert(name);
46 }
47 _ => {}
48 }
49 }
50 if let Some(src) = &export.src {
51 let src_val = src.value.to_string();
52 if src_val.starts_with('.') {
53 if let Some(resolved) = resolve_relative_import(path, &src_val) {
54 local_imports.insert(resolved);
55 }
56 }
57 }
58 }
59 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => {
60 match &export.decl {
61 Decl::Fn(f) => { local_exports.insert(f.ident.sym.to_string()); }
62 Decl::Class(c) => { local_exports.insert(c.ident.sym.to_string()); }
63 Decl::Var(v) => {
64 for decl in &v.decls {
65 if let Pat::Ident(id) = &decl.name {
66 local_exports.insert(id.id.sym.to_string());
67 }
68 }
69 }
70 _ => {}
71 }
72 }
73 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => {
74 local_exports.insert("default".to_string());
75 match &export.decl {
76 DefaultDecl::Fn(f) => {
77 if let Some(id) = &f.ident {
78 local_exports.insert(id.sym.to_string());
79 }
80 }
81 DefaultDecl::Class(c) => {
82 if let Some(id) = &c.ident {
83 local_exports.insert(id.sym.to_string());
84 }
85 }
86 _ => {}
87 }
88 }
89 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(_)) => {
90 local_exports.insert("default".to_string());
91 }
92 _ => {}
93 }
94 }
95
96 let path_buf = path.to_path_buf();
97 for imported in &local_imports {
98 self.dependents.entry(imported.clone()).or_default().insert(path_buf.clone());
99 }
100 self.imports.insert(path_buf.clone(), local_imports);
101 self.exports.insert(path_buf, local_exports);
102
103 Ok(())
104 }
105
106 pub fn get_imports(&self, path: &Path) -> Option<&HashSet<PathBuf>> {
107 self.imports.get(path)
108 }
109
110 pub fn get_dependents(&self, path: &Path) -> Option<&HashSet<PathBuf>> {
111 self.dependents.get(path)
112 }
113
114 pub fn get_exports(&self, path: &Path) -> Option<&HashSet<String>> {
115 self.exports.get(path)
116 }
117}