1pub mod ids;
9pub mod output;
10
11use std::path::PathBuf;
12
13pub use ids::{DataNodeId, FileId, IdGenerator, SnapshotId, SymbolId};
14pub use output::{ClassEntry, FuncEntry, InspectOutput, ObjectEntry};
15
16use crate::language::LangId;
17use serde::Serialize;
18
19#[derive(Debug, Clone, Serialize)]
20pub struct UnresolvedImport {
21 pub import_specifier: String,
22 pub alias: Option<String>,
23 pub symbol: Option<String>,
24 pub star: bool,
25 pub range: SourceRange,
26}
27
28#[derive(Debug, Clone, Serialize)]
29pub struct UnresolvedReference {
30 pub name: String,
31 pub range: SourceRange,
32}
33
34#[derive(Debug, Clone, Serialize)]
35pub struct FileExtraction {
36 pub path: PathBuf,
37 pub lang: LangId,
38 pub symbols: Vec<Symbol>,
39 pub imports: Vec<UnresolvedImport>,
40 pub references: Vec<UnresolvedReference>,
41 pub diagnostics: Vec<crate::error::Diagnostic>,
42 pub ast_node_count: usize,
44 #[cfg(feature = "metacall-deploy")]
45 pub call_sites: Vec<crate::deploy::scanner::CallSite>,
46 #[cfg(feature = "dataflow")]
47 pub data_nodes: Vec<DataNode>,
48 #[cfg(feature = "dataflow")]
49 pub flow_edges: Vec<FlowEdge>,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
53pub struct LineColumn {
54 pub line: usize,
55 pub column: usize,
56}
57
58#[derive(Debug, Clone, Serialize)]
59pub struct SourceRange {
60 pub byte_start: usize,
61 pub byte_end: usize,
62 pub start: LineColumn,
63 pub end: LineColumn,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
67#[non_exhaustive]
68pub enum SymbolKind {
69 Function,
70 Method,
71 Class,
72 Struct,
73 Interface,
74 Trait,
75 Enum,
76 Object,
77 Constant,
78 Static,
79 Module,
80 Namespace,
81 TypeAlias,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
85#[non_exhaustive]
86pub enum Visibility {
87 Public,
88 Private,
89}
90
91#[derive(Debug, Clone, Serialize)]
92pub struct Symbol {
93 pub id: SymbolId,
94 pub name: String,
95 pub kind: SymbolKind,
96 pub language: LangId,
97 pub file_path: PathBuf,
98 pub source_range: SourceRange,
99 pub visibility: Option<Visibility>,
100 pub signature: Option<String>,
101 pub docstring: Option<String>,
102 pub is_async: bool,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
107#[non_exhaustive]
108pub enum DataScope {
109 Local,
110 Parameter,
111 Member,
112 Closure,
113 Temporary,
114}
115
116impl DataScope {
117 pub fn as_str(self) -> &'static str {
118 match self {
119 DataScope::Local => "local",
120 DataScope::Parameter => "parameter",
121 DataScope::Member => "member",
122 DataScope::Closure => "closure",
123 DataScope::Temporary => "temporary",
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize)]
130pub struct DataNode {
131 pub id: DataNodeId,
132 pub symbol_id: Option<SymbolId>,
133 pub name: Option<String>,
134 pub scope: DataScope,
135 pub type_hint: Option<String>,
136 pub source_range: SourceRange,
137}
138
139#[derive(Debug, Clone, Serialize)]
141pub struct FlowEdge {
142 pub source: DataNodeId,
143 pub target: DataNodeId,
144 pub kind: FlowKind,
145 pub confidence: f32,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
150#[non_exhaustive]
151pub enum FlowKind {
152 DefUse,
153 Argument,
154 Return,
155 FieldAccess,
156}
157
158impl FlowKind {
159 pub fn as_str(self) -> &'static str {
160 match self {
161 FlowKind::DefUse => "def_use",
162 FlowKind::Argument => "argument",
163 FlowKind::Return => "return",
164 FlowKind::FieldAccess => "field_access",
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use serde_json;
173
174 fn sample_source_range() -> SourceRange {
175 SourceRange {
176 byte_start: 0,
177 byte_end: 10,
178 start: LineColumn { line: 1, column: 0 },
179 end: LineColumn {
180 line: 1,
181 column: 10,
182 },
183 }
184 }
185
186 #[test]
187 fn symbol_construction_all_fields() {
188 let sym = Symbol {
189 id: SymbolId::new(42).unwrap(),
190 name: "my_func".into(),
191 kind: SymbolKind::Function,
192 language: LangId::Rust,
193 file_path: PathBuf::from("src/main.rs"),
194 source_range: sample_source_range(),
195 visibility: Some(Visibility::Public),
196 signature: Some("fn my_func() -> bool".into()),
197 docstring: Some("does a thing".into()),
198 is_async: true,
199 };
200 assert_eq!(sym.id, SymbolId::new(42).unwrap());
201 assert_eq!(sym.name, "my_func");
202 assert!(matches!(sym.kind, SymbolKind::Function));
203 assert_eq!(sym.language, LangId::Rust);
204 assert_eq!(sym.file_path, PathBuf::from("src/main.rs"));
205 assert_eq!(sym.visibility, Some(Visibility::Public));
206 assert_eq!(sym.signature.as_deref(), Some("fn my_func() -> bool"));
207 assert_eq!(sym.docstring.as_deref(), Some("does a thing"));
208 assert!(sym.is_async);
209 }
210
211 #[test]
212 fn symbol_with_optional_fields_none() {
213 let sym = Symbol {
214 id: SymbolId::new(1).unwrap(),
215 name: "x".into(),
216 kind: SymbolKind::Constant,
217 language: LangId::Python,
218 file_path: PathBuf::from("a.py"),
219 source_range: sample_source_range(),
220 visibility: None,
221 signature: None,
222 docstring: None,
223 is_async: false,
224 };
225 assert!(sym.visibility.is_none());
226 assert!(sym.signature.is_none());
227 assert!(sym.docstring.is_none());
228 assert!(!sym.is_async);
229 }
230
231 #[test]
232 fn source_range_fields() {
233 let sr = sample_source_range();
234 assert_eq!(sr.byte_start, 0);
235 assert_eq!(sr.byte_end, 10);
236 assert_eq!(sr.start, LineColumn { line: 1, column: 0 });
237 assert_eq!(
238 sr.end,
239 LineColumn {
240 line: 1,
241 column: 10
242 }
243 );
244 }
245
246 #[test]
247 fn line_column_zero_indexed() {
248 let lc = LineColumn { line: 0, column: 0 };
249 assert_eq!(lc.line, 0);
250 assert_eq!(lc.column, 0);
251 }
252
253 #[test]
254 fn visibility_serialization() {
255 assert_eq!(
256 serde_json::to_string(&Visibility::Public).unwrap(),
257 "\"Public\""
258 );
259 assert_eq!(
260 serde_json::to_string(&Visibility::Private).unwrap(),
261 "\"Private\""
262 );
263 }
264
265 #[test]
266 fn symbol_kind_all_variants_serialize() {
267 let variants: Vec<SymbolKind> = vec![
268 SymbolKind::Function,
269 SymbolKind::Method,
270 SymbolKind::Class,
271 SymbolKind::Struct,
272 SymbolKind::Interface,
273 SymbolKind::Trait,
274 SymbolKind::Enum,
275 SymbolKind::Object,
276 SymbolKind::Constant,
277 SymbolKind::Static,
278 SymbolKind::Module,
279 SymbolKind::Namespace,
280 SymbolKind::TypeAlias,
281 ];
282 for v in &variants {
283 let json = serde_json::to_string(v).unwrap();
284 assert!(
285 json.starts_with('"') && json.ends_with('"'),
286 "expected a JSON string, got: {json}"
287 );
288 assert!(
289 json.len() > 2,
290 "expected non-empty variant name, got: {json}"
291 );
292 }
293 }
294
295 #[test]
296 fn symbol_serde_roundtrip() {
297 let sym = Symbol {
298 id: SymbolId::new(7).unwrap(),
299 name: "roundtrip_fn".into(),
300 kind: SymbolKind::Method,
301 language: LangId::Go,
302 file_path: PathBuf::from("main.go"),
303 source_range: sample_source_range(),
304 visibility: Some(Visibility::Private),
305 signature: Some("func (t T) roundtripFn()".into()),
306 docstring: Some("doc".into()),
307 is_async: false,
308 };
309 let json = serde_json::to_string(&sym).unwrap();
310 let val: serde_json::Value = serde_json::from_str(&json).unwrap();
311 assert_eq!(val["name"], "roundtrip_fn");
312 assert_eq!(val["kind"], "Method");
313 assert_eq!(val["is_async"], false);
314 }
315
316 #[test]
317 fn data_scope_as_str_all_variants() {
318 assert_eq!(DataScope::Local.as_str(), "local");
319 assert_eq!(DataScope::Parameter.as_str(), "parameter");
320 assert_eq!(DataScope::Member.as_str(), "member");
321 assert_eq!(DataScope::Closure.as_str(), "closure");
322 assert_eq!(DataScope::Temporary.as_str(), "temporary");
323 }
324
325 #[test]
326 fn data_scope_serialization() {
327 assert_eq!(
328 serde_json::to_string(&DataScope::Local).unwrap(),
329 "\"Local\""
330 );
331 assert_eq!(
332 serde_json::to_string(&DataScope::Parameter).unwrap(),
333 "\"Parameter\""
334 );
335 }
336
337 #[test]
338 fn flow_kind_as_str_all_variants() {
339 assert_eq!(FlowKind::DefUse.as_str(), "def_use");
340 assert_eq!(FlowKind::Argument.as_str(), "argument");
341 assert_eq!(FlowKind::Return.as_str(), "return");
342 assert_eq!(FlowKind::FieldAccess.as_str(), "field_access");
343 }
344
345 #[test]
346 fn flow_kind_serialization() {
347 assert_eq!(
348 serde_json::to_string(&FlowKind::DefUse).unwrap(),
349 "\"DefUse\""
350 );
351 assert_eq!(
352 serde_json::to_string(&FlowKind::Argument).unwrap(),
353 "\"Argument\""
354 );
355 assert_eq!(
356 serde_json::to_string(&FlowKind::Return).unwrap(),
357 "\"Return\""
358 );
359 assert_eq!(
360 serde_json::to_string(&FlowKind::FieldAccess).unwrap(),
361 "\"FieldAccess\""
362 );
363 }
364
365 #[test]
366 fn data_node_construction_and_serde() {
367 let data = DataNode {
368 id: DataNodeId::new(10).unwrap(),
369 symbol_id: Some(SymbolId::new(5).unwrap()),
370 name: Some("x".into()),
371 scope: DataScope::Local,
372 type_hint: Some("int".into()),
373 source_range: sample_source_range(),
374 };
375 let json = serde_json::to_string(&data).unwrap();
376 let val: serde_json::Value = serde_json::from_str(&json).unwrap();
377 assert_eq!(val["name"], "x");
378 assert_eq!(val["scope"], "Local");
379 assert_eq!(val["type_hint"], "int");
380 }
381
382 #[test]
383 fn data_node_optional_fields_none() {
384 let data = DataNode {
385 id: DataNodeId::new(1).unwrap(),
386 symbol_id: None,
387 name: None,
388 scope: DataScope::Temporary,
389 type_hint: None,
390 source_range: sample_source_range(),
391 };
392 assert!(data.symbol_id.is_none());
393 assert!(data.name.is_none());
394 assert!(data.type_hint.is_none());
395 }
396
397 #[test]
398 fn flow_edge_serde() {
399 let edge = FlowEdge {
400 source: DataNodeId::new(1).unwrap(),
401 target: DataNodeId::new(2).unwrap(),
402 kind: FlowKind::Argument,
403 confidence: 0.85,
404 };
405 let json = serde_json::to_string(&edge).unwrap();
406 let val: serde_json::Value = serde_json::from_str(&json).unwrap();
407 assert_eq!(val["source"], 1);
408 assert_eq!(val["target"], 2);
409 assert_eq!(val["kind"], "Argument");
410 assert_eq!(val["confidence"], 0.85);
411 }
412
413 #[test]
414 fn data_node_id_serde_roundtrip() {
415 let original = DataNodeId::new(99).unwrap();
416 let json = serde_json::to_string(&original).unwrap();
417 let roundtrip: DataNodeId = serde_json::from_str(&json).unwrap();
418 assert_eq!(original, roundtrip);
419 }
420}