morph_cli/core/ast/
semantic.rs1use std::collections::{HashMap, HashSet};
2use swc_ecma_ast::*;
3use swc_ecma_visit::{Visit, VisitWith};
4
5#[derive(Debug, Default, Clone)]
6pub struct SemanticModel {
7 pub imports: HashMap<String, ImportInfo>,
8 pub exports: HashSet<String>,
9 pub declarations: HashSet<String>,
10 pub usages: HashSet<String>,
11 pub collisions: HashSet<String>,
12}
13
14#[derive(Debug, Clone)]
15pub struct ImportInfo {
16 pub local_name: String,
17 pub original_name: Option<String>,
18 pub source: String,
19 pub is_default: bool,
20}
21
22impl SemanticModel {
23 pub fn new(module: &Module) -> Self {
24 let mut analyzer = SemanticAnalyzer::default();
25 module.visit_with(&mut analyzer);
26 analyzer.model
27 }
28
29 pub fn get_unused_imports(&self) -> Vec<String> {
30 self.imports
31 .keys()
32 .filter(|name| !self.usages.contains(*name))
33 .cloned()
34 .collect()
35 }
36
37 pub fn has_collision(&self, name: &str) -> bool {
38 self.declarations.contains(name) || self.imports.contains_key(name)
39 }
40}
41
42#[derive(Default)]
43struct SemanticAnalyzer {
44 model: SemanticModel,
45 in_export: bool,
46}
47
48impl SemanticAnalyzer {
49 fn declare(&mut self, name: String) {
50 if !self.model.declarations.insert(name.clone()) {
51 self.model.collisions.insert(name);
52 }
53 }
54}
55
56impl Visit for SemanticAnalyzer {
57 fn visit_import_decl(&mut self, import: &ImportDecl) {
58 let source = import.src.value.to_string();
59 for specifier in &import.specifiers {
60 match specifier {
61 ImportSpecifier::Default(def) => {
62 let name = def.local.sym.to_string();
63 self.model.imports.insert(
64 name.clone(),
65 ImportInfo {
66 local_name: name.clone(),
67 original_name: None,
68 source: source.clone(),
69 is_default: true,
70 },
71 );
72 self.declare(name);
73 }
74 ImportSpecifier::Named(named) => {
75 let local = named.local.sym.to_string();
76 let original = named.imported.as_ref().map(|ext| match ext {
77 ModuleExportName::Ident(id) => id.sym.to_string(),
78 ModuleExportName::Str(s) => s.value.to_string(),
79 });
80 self.model.imports.insert(
81 local.clone(),
82 ImportInfo {
83 local_name: local.clone(),
84 original_name: original,
85 source: source.clone(),
86 is_default: false,
87 },
88 );
89 self.declare(local);
90 }
91 ImportSpecifier::Namespace(ns) => {
92 let name = ns.local.sym.to_string();
93 self.model.imports.insert(
94 name.clone(),
95 ImportInfo {
96 local_name: name.clone(),
97 original_name: None,
98 source: source.clone(),
99 is_default: false,
100 },
101 );
102 self.declare(name);
103 }
104 }
105 }
106 }
108
109 fn visit_export_decl(&mut self, export: &ExportDecl) {
110 self.in_export = true;
111 export.visit_children_with(self);
112 self.in_export = false;
113 }
114
115 fn visit_named_export(&mut self, export: &NamedExport) {
116 for spec in &export.specifiers {
117 if let ExportSpecifier::Named(named) = spec {
118 let name = match &named.orig {
119 ModuleExportName::Ident(id) => id.sym.to_string(),
120 ModuleExportName::Str(s) => s.value.to_string(),
121 };
122 self.model.exports.insert(name.clone());
123 self.model.usages.insert(name);
124 }
125 }
126 export.visit_children_with(self);
127 }
128
129 fn visit_var_declarator(&mut self, var: &VarDeclarator) {
130 if let Pat::Ident(ident) = &var.name {
131 let name = ident.id.sym.to_string();
132 self.declare(name.clone());
133 if self.in_export {
134 self.model.exports.insert(name);
135 }
136 }
137 var.visit_children_with(self);
139 }
140
141 fn visit_fn_decl(&mut self, func: &FnDecl) {
142 let name = func.ident.sym.to_string();
143 self.declare(name.clone());
144 if self.in_export {
145 self.model.exports.insert(name);
146 }
147 func.visit_children_with(self);
148 }
149
150 fn visit_class_decl(&mut self, class: &ClassDecl) {
151 let name = class.ident.sym.to_string();
152 self.declare(name.clone());
153 if self.in_export {
154 self.model.exports.insert(name);
155 }
156 class.visit_children_with(self);
157 }
158
159 fn visit_ident(&mut self, ident: &Ident) {
160 self.model.usages.insert(ident.sym.to_string());
161 ident.visit_children_with(self);
162 }
163}