use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use crate::core::ast::parser::parse_file;
use crate::utils::path::resolve_relative_import;
use swc_ecma_ast::*;
#[derive(Debug, Default, Clone)]
pub struct ImportGraph {
imports: HashMap<PathBuf, HashSet<PathBuf>>,
dependents: HashMap<PathBuf, HashSet<PathBuf>>,
exports: HashMap<PathBuf, HashSet<String>>,
}
impl ImportGraph {
pub fn new() -> Self {
Self::default()
}
pub fn analyze_file(&mut self, path: &Path) -> anyhow::Result<()> {
let parsed = parse_file(path)?;
let mut local_imports = HashSet::new();
let mut local_exports = HashSet::new();
for item in &parsed.module.body {
match item {
ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
let src = import.src.value.to_string();
if src.starts_with('.') {
if let Some(resolved) = resolve_relative_import(path, &src) {
local_imports.insert(resolved);
}
}
}
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
for spec in &export.specifiers {
match spec {
ExportSpecifier::Named(named) => {
let name = match &named.orig {
ModuleExportName::Ident(id) => id.sym.to_string(),
ModuleExportName::Str(s) => s.value.to_string(),
};
local_exports.insert(name);
}
_ => {}
}
}
if let Some(src) = &export.src {
let src_val = src.value.to_string();
if src_val.starts_with('.') {
if let Some(resolved) = resolve_relative_import(path, &src_val) {
local_imports.insert(resolved);
}
}
}
}
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => {
match &export.decl {
Decl::Fn(f) => { local_exports.insert(f.ident.sym.to_string()); }
Decl::Class(c) => { local_exports.insert(c.ident.sym.to_string()); }
Decl::Var(v) => {
for decl in &v.decls {
if let Pat::Ident(id) = &decl.name {
local_exports.insert(id.id.sym.to_string());
}
}
}
_ => {}
}
}
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => {
local_exports.insert("default".to_string());
match &export.decl {
DefaultDecl::Fn(f) => {
if let Some(id) = &f.ident {
local_exports.insert(id.sym.to_string());
}
}
DefaultDecl::Class(c) => {
if let Some(id) = &c.ident {
local_exports.insert(id.sym.to_string());
}
}
_ => {}
}
}
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(_)) => {
local_exports.insert("default".to_string());
}
_ => {}
}
}
let path_buf = path.to_path_buf();
for imported in &local_imports {
self.dependents.entry(imported.clone()).or_default().insert(path_buf.clone());
}
self.imports.insert(path_buf.clone(), local_imports);
self.exports.insert(path_buf, local_exports);
Ok(())
}
pub fn get_imports(&self, path: &Path) -> Option<&HashSet<PathBuf>> {
self.imports.get(path)
}
pub fn get_dependents(&self, path: &Path) -> Option<&HashSet<PathBuf>> {
self.dependents.get(path)
}
pub fn get_exports(&self, path: &Path) -> Option<&HashSet<String>> {
self.exports.get(path)
}
}