rusty_react_flow/analyzer/
typescript.rs1use std::fs;
2use std::path::Path;
3use swc_common::{sync::Lrc, SourceMap, FileName};
4use swc_ecma_parser::{lexer::Lexer, Parser as SwcParser, StringInput, Syntax, TsSyntax};
5use swc_ecma_ast::*;
6
7use crate::models::{FileAnalysis, ImportInfo, ExportInfo};
8
9pub fn is_typescript_file(path: &Path) -> bool {
11 if let Some(ext) = path.extension() {
12 let ext_str = ext.to_string_lossy().to_lowercase();
13 return ext_str == "ts" || ext_str == "tsx" || ext_str == "js" || ext_str == "jsx";
14 }
15 false
16}
17
18pub fn analyze_file(file_path: &Path) -> Option<FileAnalysis> {
20 let cm: Lrc<SourceMap> = Default::default();
21
22 let src = match fs::read_to_string(file_path) {
23 Ok(content) => content,
24 Err(e) => {
25 eprintln!("Failed to read file {}: {}", file_path.display(), e);
26 return None;
27 }
28 };
29
30 let fm = cm.new_source_file(FileName::Real(file_path.to_path_buf()).into(), src);
31
32 let is_tsx = if let Some(ext) = file_path.extension() {
33 let ext_str = ext.to_string_lossy().to_lowercase();
34 ext_str == "tsx" || ext_str == "jsx"
35 } else {
36 false
37 };
38
39 let syntax = Syntax::Typescript(TsSyntax {
40 tsx: is_tsx,
41 decorators: true,
42 dts: false,
43 no_early_errors: false,
44 disallow_ambiguous_jsx_like: false,
45 });
46
47 let lexer = Lexer::new(syntax, Default::default(), StringInput::from(&*fm), None);
48 let mut parser = SwcParser::new_from(lexer);
49
50 let module = match parser.parse_module() {
51 Ok(module) => module,
52 Err(e) => {
53 eprintln!("Failed to parse {}: {:?}", file_path.display(), e);
54 return None;
55 }
56 };
57
58 let mut imports = Vec::new();
59 let mut exports = Vec::new();
60
61 extract_imports(&module, &mut imports);
63
64 extract_exports(&module, &mut exports);
66
67 Some(FileAnalysis {
68 file_path: file_path.to_string_lossy().to_string(),
69 imports,
70 exports,
71 })
72}
73
74fn extract_imports(module: &Module, imports: &mut Vec<ImportInfo>) {
76 for item in &module.body {
77 if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = item {
78 let from = import.src.value.to_string();
79 for specifier in &import.specifiers {
80 match specifier {
81 ImportSpecifier::Named(named) => {
82 let name = named.local.sym.to_string();
83 imports.push(ImportInfo {
84 name,
85 source: from.clone(),
86 kind: "named".to_string(),
87 });
88 }
89 ImportSpecifier::Default(default) => {
90 imports.push(ImportInfo {
91 name: default.local.sym.to_string(),
92 source: from.clone(),
93 kind: "default".to_string(),
94 });
95 }
96 ImportSpecifier::Namespace(ns) => {
97 imports.push(ImportInfo {
98 name: ns.local.sym.to_string(),
99 source: from.clone(),
100 kind: "namespace".to_string(),
101 });
102 }
103 }
104 }
105 }
106 }
107}
108
109fn extract_exports(module: &Module, exports: &mut Vec<ExportInfo>) {
111 for item in &module.body {
112 match item {
113 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
114 for spec in &named.specifiers {
115 if let ExportSpecifier::Named(named_spec) = spec {
116 let name = if let Some(exported) = &named_spec.exported {
117 match exported {
118 ModuleExportName::Ident(id) => id.sym.to_string(),
119 ModuleExportName::Str(s) => s.value.to_string(),
120 }
121 } else {
122 named_spec.orig.atom().to_string()
123 };
124 exports.push(ExportInfo {
125 name,
126 kind: "named".to_string(),
127 });
128 }
129 }
130 }
131 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl)) => match &decl.decl {
132 Decl::Var(var_decl) => {
133 for decl in &var_decl.decls {
134 if let Pat::Ident(ident) = &decl.name {
135 exports.push(ExportInfo {
136 name: ident.id.sym.to_string(),
137 kind: "variable".to_string(),
138 });
139 }
140 }
141 }
142 Decl::Fn(f) => {
143 exports.push(ExportInfo {
144 name: f.ident.sym.to_string(),
145 kind: "function".to_string(),
146 });
147 }
148 Decl::Class(c) => {
149 exports.push(ExportInfo {
150 name: c.ident.sym.to_string(),
151 kind: "class".to_string(),
152 });
153 }
154 Decl::TsInterface(i) => {
155 exports.push(ExportInfo {
156 name: i.id.sym.to_string(),
157 kind: "interface".to_string(),
158 });
159 }
160 _ => {}
161 },
162 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(_)) => {
163 exports.push(ExportInfo {
164 name: "<expression>".to_string(),
165 kind: "default".to_string(),
166 });
167 }
168 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(decl)) => {
169 match &decl.decl {
170 DefaultDecl::Class(c) => {
171 let name = c
172 .ident
173 .as_ref()
174 .map(|id| id.sym.to_string())
175 .unwrap_or_else(|| "<anonymous>".to_string());
176 exports.push(ExportInfo {
177 name,
178 kind: "default-class".to_string(),
179 });
180 }
181 DefaultDecl::Fn(f) => {
182 let name = f
183 .ident
184 .as_ref()
185 .map(|id| id.sym.to_string())
186 .unwrap_or_else(|| "<anonymous>".to_string());
187 exports.push(ExportInfo {
188 name,
189 kind: "default-function".to_string(),
190 });
191 }
192 DefaultDecl::TsInterfaceDecl(_) => {
193 exports.push(ExportInfo {
194 name: "<interface>".to_string(),
195 kind: "default".to_string(),
196 });
197 }
198 }
199 },
200 _ => {}
201 }
202 }
203}