1use blake3::Hasher;
8use serde::{Deserialize, Serialize};
9use std::path::{Path, PathBuf};
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct NodeId([u8; 16]);
14
15impl NodeId {
16 pub fn new(repo_id: &str, file_path: &Path, span: &Span, kind: &NodeKind) -> Self {
18 let mut hasher = Hasher::new();
19 hasher.update(repo_id.as_bytes());
20 hasher.update(file_path.to_string_lossy().as_bytes());
21 hasher.update(&span.start_byte.to_le_bytes());
22 hasher.update(&span.end_byte.to_le_bytes());
23 hasher.update(format!("{kind:?}").as_bytes());
24
25 let hash = hasher.finalize();
26 let mut id = [0u8; 16];
27 id.copy_from_slice(&hash.as_bytes()[..16]);
28 Self(id)
29 }
30
31 pub fn to_hex(&self) -> String {
33 hex::encode(self.0)
34 }
35}
36
37impl std::fmt::Debug for NodeId {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(f, "NodeId({})", &self.to_hex()[..8])
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case")]
46pub enum NodeKind {
47 Module,
49 Class,
51 Function,
53 Method,
55 Parameter,
57 Variable,
59 Call,
61 Import,
63 Literal,
65 Route,
67 SqlQuery,
69 Event,
71 Unknown,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
77#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
78pub enum EdgeKind {
79 Calls,
81 Reads,
83 Writes,
85 Imports,
87 Emits,
89 RoutesTo,
91 Raises,
93 Extends,
95 Implements,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
101pub struct Span {
102 pub start_byte: usize,
104 pub end_byte: usize,
106 pub start_line: usize,
108 pub end_line: usize,
110 pub start_column: usize,
112 pub end_column: usize,
114}
115
116impl Span {
117 pub fn new(
119 start_byte: usize,
120 end_byte: usize,
121 start_line: usize,
122 end_line: usize,
123 start_column: usize,
124 end_column: usize,
125 ) -> Self {
126 Self {
127 start_byte,
128 end_byte,
129 start_line,
130 end_line,
131 start_column,
132 end_column,
133 }
134 }
135
136 pub fn from_node(node: &tree_sitter::Node) -> Self {
138 let start_pos = node.start_position();
139 let end_pos = node.end_position();
140
141 Self {
142 start_byte: node.start_byte(),
143 end_byte: node.end_byte(),
144 start_line: start_pos.row + 1, end_line: end_pos.row + 1,
146 start_column: start_pos.column + 1,
147 end_column: end_pos.column + 1,
148 }
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
154#[serde(rename_all = "lowercase")]
155pub enum Language {
156 JavaScript,
158 TypeScript,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct Node {
165 pub id: NodeId,
167 pub kind: NodeKind,
169 pub name: String,
171 pub lang: Language,
173 pub file: PathBuf,
175 pub span: Span,
177 pub signature: Option<String>,
179 pub metadata: serde_json::Value,
181}
182
183impl Node {
184 pub fn new(
186 repo_id: &str,
187 kind: NodeKind,
188 name: String,
189 lang: Language,
190 file: PathBuf,
191 span: Span,
192 ) -> Self {
193 let id = NodeId::new(repo_id, &file, &span, &kind);
194 Self {
195 id,
196 kind,
197 name,
198 lang,
199 file,
200 span,
201 signature: None,
202 metadata: serde_json::Value::Null,
203 }
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
209pub struct Edge {
210 pub source: NodeId,
212 pub target: NodeId,
214 pub kind: EdgeKind,
216}
217
218impl Edge {
219 pub fn new(source: NodeId, target: NodeId, kind: EdgeKind) -> Self {
221 Self {
222 source,
223 target,
224 kind,
225 }
226 }
227}