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