1use serde::{Deserialize, Serialize};
32use std::path::PathBuf;
33
34#[derive(Clone, Debug)]
35pub enum Ast {
36 Rust(RustAst),
37 Python(PythonAst),
38 TypeScript(TypeScriptAst),
39 Unknown,
40}
41
42#[derive(Clone, Debug, PartialEq, Eq, Copy)]
44pub enum JsLanguageVariant {
45 JavaScript,
46 TypeScript,
47 Jsx,
48 Tsx,
49}
50
51impl JsLanguageVariant {
52 pub fn from_extension(ext: &str) -> Option<Self> {
54 match ext {
55 "js" | "mjs" | "cjs" => Some(JsLanguageVariant::JavaScript),
56 "jsx" => Some(JsLanguageVariant::Jsx),
57 "ts" | "mts" | "cts" => Some(JsLanguageVariant::TypeScript),
58 "tsx" => Some(JsLanguageVariant::Tsx),
59 _ => None,
60 }
61 }
62
63 pub fn has_jsx(&self) -> bool {
65 matches!(self, JsLanguageVariant::Jsx | JsLanguageVariant::Tsx)
66 }
67
68 pub fn has_types(&self) -> bool {
70 matches!(self, JsLanguageVariant::TypeScript | JsLanguageVariant::Tsx)
71 }
72}
73
74#[derive(Clone)]
76pub struct TypeScriptAst {
77 pub tree: tree_sitter::Tree,
79 pub path: PathBuf,
81 pub source: String,
83 pub language_variant: JsLanguageVariant,
85}
86
87impl std::fmt::Debug for TypeScriptAst {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_struct("TypeScriptAst")
90 .field("path", &self.path)
91 .field("language_variant", &self.language_variant)
92 .field("source_len", &self.source.len())
93 .finish()
94 }
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct ClassDef {
105 pub name: String,
106 pub base_classes: Vec<String>,
107 pub methods: Vec<MethodDef>,
108 pub is_abstract: bool,
109 pub decorators: Vec<String>,
110 pub line: usize,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct MethodDef {
119 pub name: String,
120 pub is_abstract: bool,
121 pub decorators: Vec<String>,
122 pub overrides_base: bool,
123 pub line: usize,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ModuleScopeAnalysis {
132 pub assignments: Vec<Assignment>,
133 pub singleton_instances: Vec<SingletonInstance>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct Assignment {
138 pub name: String,
139 pub value: Expression,
140 pub scope: Scope,
141 pub line: usize,
142}
143
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub enum Scope {
146 Module,
147 Class,
148 Function,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub enum Expression {
153 ClassInstantiation {
154 class_name: String,
155 args: Vec<String>,
156 },
157 FunctionCall {
158 function_name: String,
159 args: Vec<String>,
160 },
161 ClassReference {
162 class_name: String,
163 },
164 Literal {
165 value: String,
166 },
167 Other,
168}
169
170impl Expression {
171 pub fn is_class_instantiation(&self) -> bool {
172 matches!(self, Expression::ClassInstantiation { .. })
173 }
174
175 pub fn is_class_reference(&self) -> bool {
176 matches!(self, Expression::ClassReference { .. })
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct SingletonInstance {
182 pub variable_name: String,
183 pub class_name: String,
184 pub line: usize,
185}
186
187#[derive(Clone, Debug)]
188pub struct RustAst {
189 pub file: syn::File,
190 pub path: PathBuf,
191 pub source: String,
192}
193
194#[derive(Clone, Debug)]
196pub struct PythonAst {
197 pub path: PathBuf,
198 pub source: String,
199}
200
201#[derive(Clone, Debug)]
202pub struct AstNode {
203 pub kind: NodeKind,
204 pub name: Option<String>,
205 pub line: usize,
206 pub children: Vec<AstNode>,
207}
208
209#[derive(Clone, Debug, PartialEq)]
210pub enum NodeKind {
211 Function,
212 Method,
213 Class,
214 Module,
215 If,
216 While,
217 For,
218 Match,
219 Try,
220 Block,
221}
222
223impl Ast {
224 pub fn transform<F>(self, f: F) -> Self
225 where
226 F: Fn(Self) -> Self,
227 {
228 f(self)
229 }
230
231 pub fn map_functions<F, T>(&self, f: F) -> Vec<T>
232 where
233 F: Fn(&AstNode) -> Option<T>,
234 {
235 let nodes = self.extract_nodes();
236 nodes
237 .iter()
238 .filter(|n| matches!(n.kind, NodeKind::Function | NodeKind::Method))
239 .filter_map(f)
240 .collect()
241 }
242
243 pub fn extract_nodes(&self) -> Vec<AstNode> {
244 match self {
245 Ast::Rust(_) => self.extract_rust_nodes(),
246 Ast::Python(_) => self.extract_python_nodes(),
247 Ast::TypeScript(_) => self.extract_typescript_nodes(),
248 Ast::Unknown => vec![],
249 }
250 }
251
252 fn extract_rust_nodes(&self) -> Vec<AstNode> {
253 vec![]
254 }
255
256 fn extract_python_nodes(&self) -> Vec<AstNode> {
257 vec![]
258 }
259
260 fn extract_typescript_nodes(&self) -> Vec<AstNode> {
261 vec![]
263 }
264
265 pub fn count_branches(&self) -> usize {
266 self.extract_nodes()
267 .iter()
268 .filter(|n| {
269 matches!(
270 n.kind,
271 NodeKind::If | NodeKind::While | NodeKind::For | NodeKind::Match
272 )
273 })
274 .count()
275 }
276}
277
278pub fn combine_asts(asts: Vec<Ast>) -> Vec<Ast> {
279 asts
280}
281
282pub fn filter_ast<F>(ast: Ast, predicate: F) -> Option<Ast>
283where
284 F: Fn(&Ast) -> bool,
285{
286 if predicate(&ast) {
287 Some(ast)
288 } else {
289 None
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn test_map_functions_extracts_functions() {
299 let ast = Ast::Unknown;
300 let results = ast.map_functions(|node| {
301 if matches!(node.kind, NodeKind::Function | NodeKind::Method) {
302 Some(node.name.clone().unwrap_or_else(|| "anonymous".to_string()))
303 } else {
304 None
305 }
306 });
307
308 assert_eq!(results.len(), 0);
310 }
311
312 #[test]
313 fn test_ast_transform() {
314 let ast = Ast::Unknown;
315 let transformed = ast.clone().transform(|a| {
316 a
318 });
319
320 assert!(matches!(transformed, Ast::Unknown));
321 }
322
323 #[test]
324 fn test_count_branches() {
325 let ast = Ast::Unknown;
326 let count = ast.count_branches();
327 assert_eq!(count, 0);
328 }
329
330 #[test]
331 fn test_extract_nodes_unknown() {
332 let ast = Ast::Unknown;
333 let nodes = ast.extract_nodes();
334 assert_eq!(nodes.len(), 0);
335 }
336
337 #[test]
338 fn test_combine_asts() {
339 let asts = vec![Ast::Unknown, Ast::Unknown];
340 let combined = combine_asts(asts);
341 assert_eq!(combined.len(), 2);
342 }
343
344 #[test]
345 fn test_filter_ast_matches() {
346 let ast = Ast::Unknown;
347 let filtered = filter_ast(ast, |a| matches!(a, Ast::Unknown));
348 assert!(filtered.is_some());
349 }
350
351 #[test]
352 fn test_filter_ast_no_match() {
353 let ast = Ast::Unknown;
354 let filtered = filter_ast(ast, |a| matches!(a, Ast::Rust(_)));
355 assert!(filtered.is_none());
356 }
357
358 #[test]
359 fn test_ast_node_creation() {
360 let node = AstNode {
361 kind: NodeKind::Function,
362 name: Some("test_func".to_string()),
363 line: 10,
364 children: vec![],
365 };
366
367 assert_eq!(node.kind, NodeKind::Function);
368 assert_eq!(node.name, Some("test_func".to_string()));
369 assert_eq!(node.line, 10);
370 assert_eq!(node.children.len(), 0);
371 }
372
373 #[test]
374 fn test_map_functions_filters_correctly() {
375 let nodes = [
377 AstNode {
378 kind: NodeKind::Function,
379 name: Some("func1".to_string()),
380 line: 1,
381 children: vec![],
382 },
383 AstNode {
384 kind: NodeKind::If,
385 name: None,
386 line: 2,
387 children: vec![],
388 },
389 AstNode {
390 kind: NodeKind::Method,
391 name: Some("method1".to_string()),
392 line: 3,
393 children: vec![],
394 },
395 ];
396
397 let function_nodes: Vec<_> = nodes
400 .iter()
401 .filter(|n| matches!(n.kind, NodeKind::Function | NodeKind::Method))
402 .collect();
403
404 assert_eq!(function_nodes.len(), 2);
405 }
406
407 #[test]
408 fn test_extract_rust_nodes() {
409 let ast = Ast::Rust(RustAst {
410 file: syn::File {
411 shebang: None,
412 attrs: vec![],
413 items: vec![],
414 },
415 path: PathBuf::from("test.rs"),
416 source: String::new(),
417 });
418
419 let nodes = ast.extract_rust_nodes();
421 assert_eq!(nodes.len(), 0);
422 }
423
424 #[test]
425 fn test_extract_python_nodes() {
426 let ast = Ast::Unknown;
428 let nodes = ast.extract_python_nodes();
429 assert_eq!(nodes.len(), 0);
430 }
431
432 #[test]
433 fn test_extract_nodes_rust() {
434 let ast = Ast::Rust(RustAst {
435 file: syn::File {
436 shebang: None,
437 attrs: vec![],
438 items: vec![],
439 },
440 path: PathBuf::from("test.rs"),
441 source: String::new(),
442 });
443
444 let nodes = ast.extract_nodes();
445 assert_eq!(nodes.len(), 0); }
447
448 #[test]
449 fn test_extract_nodes_python() {
450 let ast = Ast::Unknown;
452 let nodes = ast.extract_nodes();
453 assert_eq!(nodes.len(), 0);
454 }
455
456 #[test]
457 fn test_ast_node_with_children() {
458 let child1 = AstNode {
459 kind: NodeKind::Block,
460 name: None,
461 line: 5,
462 children: vec![],
463 };
464
465 let child2 = AstNode {
466 kind: NodeKind::If,
467 name: None,
468 line: 6,
469 children: vec![],
470 };
471
472 let parent = AstNode {
473 kind: NodeKind::Function,
474 name: Some("parent_func".to_string()),
475 line: 4,
476 children: vec![child1, child2],
477 };
478
479 assert_eq!(parent.children.len(), 2);
480 assert_eq!(parent.children[0].kind, NodeKind::Block);
481 assert_eq!(parent.children[1].kind, NodeKind::If);
482 }
483
484 #[test]
485 fn test_node_kind_equality() {
486 assert_eq!(NodeKind::Function, NodeKind::Function);
487 assert_ne!(NodeKind::Function, NodeKind::Method);
488 assert_ne!(NodeKind::If, NodeKind::While);
489 }
490
491 #[test]
492 fn test_count_branches_with_different_node_types() {
493 let branch_kinds = vec![
495 NodeKind::If,
496 NodeKind::While,
497 NodeKind::For,
498 NodeKind::Match,
499 ];
500
501 let non_branch_kinds = vec![
502 NodeKind::Function,
503 NodeKind::Method,
504 NodeKind::Class,
505 NodeKind::Module,
506 NodeKind::Try,
507 NodeKind::Block,
508 ];
509
510 for kind in branch_kinds {
511 assert!(
512 matches!(
513 kind,
514 NodeKind::If | NodeKind::While | NodeKind::For | NodeKind::Match
515 ),
516 "Expected {kind:?} to be a branch node"
517 );
518 }
519
520 for kind in non_branch_kinds {
521 assert!(
522 !matches!(
523 kind,
524 NodeKind::If | NodeKind::While | NodeKind::For | NodeKind::Match
525 ),
526 "Expected {kind:?} to not be a branch node"
527 );
528 }
529 }
530
531 #[test]
532 fn test_transform_preserves_type() {
533 let rust_ast = Ast::Rust(RustAst {
534 file: syn::File {
535 shebang: None,
536 attrs: vec![],
537 items: vec![],
538 },
539 path: PathBuf::from("test.rs"),
540 source: String::new(),
541 });
542
543 let transformed = rust_ast.transform(|a| a);
544 assert!(matches!(transformed, Ast::Rust(_)));
545 }
546
547 #[test]
548 fn test_combine_asts_preserves_order() {
549 let ast1 = Ast::Unknown;
550 let ast2 = Ast::Unknown;
551 let ast3 = Ast::Unknown;
552
553 let combined = combine_asts(vec![ast1, ast2, ast3]);
554 assert_eq!(combined.len(), 3);
555 }
556
557 #[test]
558 fn test_combine_asts_empty() {
559 let combined = combine_asts(vec![]);
560 assert_eq!(combined.len(), 0);
561 }
562
563 #[test]
564 fn test_filter_ast_with_multiple_predicates() {
565 let ast = Ast::Unknown;
566
567 let result1 = filter_ast(ast.clone(), |_| true);
569 assert!(result1.is_some());
570
571 let result2 = filter_ast(ast.clone(), |_| false);
573 assert!(result2.is_none());
574
575 let result3 = filter_ast(ast.clone(), |a| matches!(a, Ast::Unknown));
577 assert!(result3.is_some());
578 }
579}