rusty_react_flow/analyzer/
typescript.rs

1use 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
9/// 파일이 TypeScript/JavaScript 파일인지 확인합니다.
10pub 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
18/// TypeScript/JavaScript 파일을 분석합니다.
19pub 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    // Import 정보 추출
62    extract_imports(&module, &mut imports);
63
64    // Export 정보 추출
65    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
74/// 모듈에서 import 정보를 추출합니다.
75fn 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
109/// 모듈에서 export 정보를 추출합니다.
110fn 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}