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