1use std::collections::HashMap;
2
3use crate::walker::{Language, SourceFile};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
6pub enum NodeKind {
7 File,
8 Function,
9 Class,
10 Variable,
11 Type,
12 Module,
13 Author,
14}
15
16impl NodeKind {
17 pub fn as_str(&self) -> &'static str {
18 match self {
19 NodeKind::File => "File",
20 NodeKind::Function => "Function",
21 NodeKind::Class => "Class",
22 NodeKind::Variable => "Variable",
23 NodeKind::Type => "Type",
24 NodeKind::Module => "Module",
25 NodeKind::Author => "Author",
26 }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
31#[serde(rename_all = "UPPERCASE")]
32pub enum EdgeKind {
33 Calls,
34 Imports,
35 Inherits,
36 Exports,
37 CoChanges,
38 Owns,
39 DependsOn,
40}
41
42impl EdgeKind {
43 pub fn as_str(&self) -> &'static str {
44 match self {
45 EdgeKind::Calls => "CALLS",
46 EdgeKind::Imports => "IMPORTS",
47 EdgeKind::Inherits => "INHERITS",
48 EdgeKind::Exports => "EXPORTS",
49 EdgeKind::CoChanges => "CO_CHANGES",
50 EdgeKind::Owns => "OWNS",
51 EdgeKind::DependsOn => "DEPENDS_ON",
52 }
53 }
54}
55
56#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
57pub struct NodeDef {
58 pub id: String,
59 pub kind: NodeKind,
60 pub name: String,
61 pub path: String,
62 pub line_start: u32,
63 pub line_end: u32,
64 #[serde(default)]
65 pub metadata: serde_json::Value,
66}
67
68impl Default for NodeDef {
69 fn default() -> Self {
70 Self {
71 id: String::new(),
72 kind: NodeKind::File,
73 name: String::new(),
74 path: String::new(),
75 line_start: 0,
76 line_end: 0,
77 metadata: serde_json::Value::Null,
78 }
79 }
80}
81
82#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
83pub struct EdgeDef {
84 pub src: String,
85 pub dst: String,
86 pub kind: EdgeKind,
87 #[serde(default = "default_edge_weight")]
88 pub weight: f64,
89 #[serde(default = "default_edge_weight")]
90 pub confidence: f64,
91}
92
93impl Default for EdgeDef {
94 fn default() -> Self {
95 Self {
96 src: String::new(),
97 dst: String::new(),
98 kind: EdgeKind::Calls,
99 weight: 1.0,
100 confidence: 1.0,
101 }
102 }
103}
104
105fn default_edge_weight() -> f64 {
106 1.0
107}
108
109#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
110pub struct ParseResult {
111 pub nodes: Vec<NodeDef>,
112 pub edges: Vec<EdgeDef>,
113}
114
115impl ParseResult {
116 pub fn new() -> Self {
117 Self {
118 nodes: Vec::new(),
119 edges: Vec::new(),
120 }
121 }
122}
123
124impl Default for ParseResult {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130pub trait LanguageParser: Send + Sync {
131 fn extensions(&self) -> &[&str];
132 fn extract(&self, file: &SourceFile) -> anyhow::Result<ParseResult>;
133}
134
135pub struct ParserRegistry {
136 parsers: HashMap<Language, Box<dyn LanguageParser>>,
137}
138
139impl ParserRegistry {
140 pub fn new() -> Self {
141 let mut parsers: HashMap<Language, Box<dyn LanguageParser>> = HashMap::new();
142
143 parsers.insert(
144 Language::TypeScript,
145 Box::new(super::parsers::ts::TypeScriptParser::new()),
146 );
147 parsers.insert(
148 Language::JavaScript,
149 Box::new(super::parsers::ts::TypeScriptParser::new()),
150 );
151 parsers.insert(
152 Language::Python,
153 Box::new(super::parsers::py::PythonParser::new()),
154 );
155 parsers.insert(
156 Language::Rust,
157 Box::new(super::parsers::rust::RustParser::new()),
158 );
159 parsers.insert(
160 Language::Go,
161 Box::new(super::parsers::go::GoParser::new()),
162 );
163 parsers.insert(
164 Language::Java,
165 Box::new(super::parsers::java::JavaParser::new()),
166 );
167 parsers.insert(
168 Language::CSharp,
169 Box::new(super::parsers::java::JavaParser::new()),
170 );
171 parsers.insert(
172 Language::Php,
173 Box::new(super::parsers::php::PhpParser::new()),
174 );
175
176 Self { parsers }
177 }
178
179 pub fn parse(&self, file: &SourceFile) -> anyhow::Result<ParseResult> {
180 if let Some(parser) = self.parsers.get(&file.language) {
181 parser.extract(file)
182 } else {
183 Ok(ParseResult::new())
184 }
185 }
186
187 pub fn parse_all(&self, files: &[SourceFile]) -> Vec<ParseResult> {
188 use rayon::prelude::*;
189 files
190 .par_iter()
191 .map(|file| self.parse(file).unwrap_or_else(|e| {
192 tracing::warn!("Parse error in {}: {}", file.relative_path, e);
193 ParseResult::new()
194 }))
195 .collect()
196 }
197}
198
199impl Default for ParserRegistry {
200 fn default() -> Self {
201 Self::new()
202 }
203}