1use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use std::hash::{Hash, Hasher};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
15#[serde(rename_all = "snake_case")]
16pub enum SymbolKind {
17 Function,
19 Method,
21 Class,
23 Struct,
25 Interface,
27 Trait,
29 Enum,
31 Module,
33 Variable,
35 Constant,
37 Parameter,
39 Field,
41 Import,
43 Export,
45 EnumVariant,
47 TypeAlias,
49 Unknown,
51}
52
53impl SymbolKind {
54 pub fn from_ast_kind(kind: &str) -> Self {
59 match kind {
60 "function_item" | "function_definition" | "function_declaration" | "function_expression" | "arrow_function" | "decorated_definition" => Self::Function,
68
69 "method_definition" | "method_declaration" | "method" | "singleton_method" | "constructor_declaration" => Self::Method,
76
77 "impl_item" | "class_definition" | "class_declaration" | "class_specifier" | "class" => Self::Class,
84
85 "struct_item" | "struct_specifier" | "struct_declaration" => Self::Struct,
90
91 "interface_declaration" | "protocol_declaration" => Self::Interface,
95
96 "trait_item" | "trait_declaration" => Self::Trait,
100
101 "enum_item" | "enum_declaration" | "enum_specifier" => Self::Enum,
106
107 "mod_item" | "module" | "namespace_definition" | "namespace_declaration" => Self::Module,
113
114 "static_item" | "variable_declaration" | "lexical_declaration" => Self::Variable,
119
120 "const_item" => Self::Constant,
123
124 "type_item" | "type_alias_declaration" | "type_declaration" => Self::TypeAlias,
129
130 _ => Self::Unknown,
131 }
132 }
133
134 pub fn display_name(&self) -> &'static str {
136 match self {
137 Self::Function => "function",
138 Self::Method => "method",
139 Self::Class => "class",
140 Self::Struct => "struct",
141 Self::Interface => "interface",
142 Self::Trait => "trait",
143 Self::Enum => "enum",
144 Self::Module => "module",
145 Self::Variable => "variable",
146 Self::Constant => "constant",
147 Self::Parameter => "parameter",
148 Self::Field => "field",
149 Self::Import => "import",
150 Self::Export => "export",
151 Self::EnumVariant => "enum variant",
152 Self::TypeAlias => "type alias",
153 Self::Unknown => "unknown",
154 }
155 }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Default)]
160#[serde(rename_all = "snake_case")]
161pub enum Visibility {
162 Public,
164 #[default]
166 Private,
167 Protected,
169 Internal,
171}
172
173impl Visibility {
174 pub fn from_keywords(text: &str) -> Self {
176 let lower = text.to_lowercase();
177 if lower.contains("pub ") || lower.contains("public ") || lower.contains("export ") {
178 Self::Public
179 } else if lower.contains("protected ") {
180 Self::Protected
181 } else if lower.contains("internal ") || lower.contains("package ") {
182 Self::Internal
183 } else {
184 Self::Private
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
191#[serde(rename_all = "snake_case")]
192pub enum ReferenceKind {
193 Call,
195 Read,
197 Write,
199 Import,
201 TypeReference,
203 Inheritance,
205 Instantiation,
207 Unknown,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
217pub struct SymbolId {
218 pub file_path: String,
220 pub name: String,
222 pub kind: SymbolKind,
224 pub start_line: usize,
226 pub start_col: usize,
228}
229
230impl SymbolId {
231 pub fn new(
233 file_path: impl Into<String>,
234 name: impl Into<String>,
235 kind: SymbolKind,
236 start_line: usize,
237 start_col: usize,
238 ) -> Self {
239 Self {
240 file_path: file_path.into(),
241 name: name.into(),
242 kind,
243 start_line,
244 start_col,
245 }
246 }
247
248 pub fn to_storage_id(&self) -> String {
250 format!(
251 "{}:{}:{}:{}",
252 self.file_path, self.name, self.start_line, self.start_col
253 )
254 }
255
256 pub fn from_storage_id(id: &str) -> Option<Self> {
258 let parts: Vec<&str> = id.rsplitn(4, ':').collect();
259 if parts.len() != 4 {
260 return None;
261 }
262 let start_col = parts[0].parse().ok()?;
264 let start_line = parts[1].parse().ok()?;
265 let name = parts[2].to_string();
266 let file_path = parts[3].to_string();
267
268 Some(Self {
269 file_path,
270 name,
271 kind: SymbolKind::Unknown, start_line,
273 start_col,
274 })
275 }
276}
277
278impl PartialEq for SymbolId {
279 fn eq(&self, other: &Self) -> bool {
280 self.file_path == other.file_path
281 && self.name == other.name
282 && self.start_line == other.start_line
283 && self.start_col == other.start_col
284 }
285}
286
287impl Eq for SymbolId {}
288
289impl Hash for SymbolId {
290 fn hash<H: Hasher>(&self, state: &mut H) {
291 self.file_path.hash(state);
292 self.name.hash(state);
293 self.start_line.hash(state);
294 self.start_col.hash(state);
295 }
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
303pub struct Definition {
304 pub symbol_id: SymbolId,
306 pub root_path: Option<String>,
308 pub project: Option<String>,
310 pub end_line: usize,
312 pub end_col: usize,
314 pub signature: String,
316 pub doc_comment: Option<String>,
318 pub visibility: Visibility,
320 pub parent_id: Option<String>,
322 pub indexed_at: i64,
324}
325
326impl Definition {
327 pub fn to_storage_id(&self) -> String {
329 format!(
330 "def:{}:{}:{}",
331 self.symbol_id.file_path, self.symbol_id.name, self.symbol_id.start_line
332 )
333 }
334
335 pub fn file_path(&self) -> &str {
337 &self.symbol_id.file_path
338 }
339
340 pub fn name(&self) -> &str {
342 &self.symbol_id.name
343 }
344
345 pub fn kind(&self) -> SymbolKind {
347 self.symbol_id.kind
348 }
349
350 pub fn start_line(&self) -> usize {
352 self.symbol_id.start_line
353 }
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
358pub struct Reference {
359 pub file_path: String,
361 pub root_path: Option<String>,
363 pub project: Option<String>,
365 pub start_line: usize,
367 pub end_line: usize,
369 pub start_col: usize,
371 pub end_col: usize,
373 pub target_symbol_id: String,
375 pub reference_kind: ReferenceKind,
377 pub indexed_at: i64,
379}
380
381impl Reference {
382 pub fn to_storage_id(&self) -> String {
384 format!(
385 "ref:{}:{}:{}",
386 self.file_path, self.start_line, self.start_col
387 )
388 }
389}
390
391#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
393pub struct CallEdge {
394 pub caller_id: String,
396 pub callee_id: String,
398 pub call_site_file: String,
400 pub call_site_line: usize,
402 pub call_site_col: usize,
404}
405
406#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
408#[serde(rename_all = "snake_case")]
409pub enum PrecisionLevel {
410 High,
412 Medium,
414 Low,
416}
417
418impl PrecisionLevel {
419 pub fn description(&self) -> &'static str {
421 match self {
422 Self::High => "high (stack-graphs)",
423 Self::Medium => "medium (AST-based)",
424 Self::Low => "low (text-based)",
425 }
426 }
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
435pub struct DefinitionResult {
436 pub file_path: String,
438 pub name: String,
440 pub kind: SymbolKind,
442 pub start_line: usize,
444 pub end_line: usize,
446 pub start_col: usize,
448 pub end_col: usize,
450 pub signature: String,
452 pub doc_comment: Option<String>,
454}
455
456impl From<&Definition> for DefinitionResult {
457 fn from(def: &Definition) -> Self {
458 Self {
459 file_path: def.symbol_id.file_path.clone(),
460 name: def.symbol_id.name.clone(),
461 kind: def.symbol_id.kind,
462 start_line: def.symbol_id.start_line,
463 end_line: def.end_line,
464 start_col: def.symbol_id.start_col,
465 end_col: def.end_col,
466 signature: def.signature.clone(),
467 doc_comment: def.doc_comment.clone(),
468 }
469 }
470}
471
472#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
474pub struct ReferenceResult {
475 pub file_path: String,
477 pub start_line: usize,
479 pub end_line: usize,
481 pub start_col: usize,
483 pub end_col: usize,
485 pub reference_kind: ReferenceKind,
487 pub preview: Option<String>,
489}
490
491impl From<&Reference> for ReferenceResult {
492 fn from(r: &Reference) -> Self {
493 Self {
494 file_path: r.file_path.clone(),
495 start_line: r.start_line,
496 end_line: r.end_line,
497 start_col: r.start_col,
498 end_col: r.end_col,
499 reference_kind: r.reference_kind,
500 preview: None,
501 }
502 }
503}
504
505#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
507pub struct CallGraphNode {
508 pub name: String,
510 pub kind: SymbolKind,
512 pub file_path: String,
514 pub line: usize,
516 pub children: Vec<CallGraphNode>,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
522pub struct SymbolInfo {
523 pub name: String,
525 pub kind: SymbolKind,
527 pub file_path: String,
529 pub start_line: usize,
531 pub end_line: usize,
533 pub signature: String,
535}
536
537#[cfg(test)]
542mod tests {
543 use super::*;
544
545 #[test]
546 fn test_symbol_kind_from_ast_kind() {
547 assert_eq!(SymbolKind::from_ast_kind("function_item"), SymbolKind::Function);
548 assert_eq!(SymbolKind::from_ast_kind("class_definition"), SymbolKind::Class);
549 assert_eq!(SymbolKind::from_ast_kind("method_definition"), SymbolKind::Method);
550 assert_eq!(SymbolKind::from_ast_kind("unknown_node"), SymbolKind::Unknown);
551 }
552
553 #[test]
554 fn test_symbol_kind_display_name() {
555 assert_eq!(SymbolKind::Function.display_name(), "function");
556 assert_eq!(SymbolKind::Class.display_name(), "class");
557 assert_eq!(SymbolKind::Unknown.display_name(), "unknown");
558 }
559
560 #[test]
561 fn test_visibility_from_keywords() {
562 assert_eq!(Visibility::from_keywords("pub fn foo"), Visibility::Public);
563 assert_eq!(Visibility::from_keywords("public void bar"), Visibility::Public);
564 assert_eq!(Visibility::from_keywords("protected int x"), Visibility::Protected);
565 assert_eq!(Visibility::from_keywords("fn private_func"), Visibility::Private);
566 }
567
568 #[test]
569 fn test_symbol_id_equality() {
570 let id1 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 0);
571 let id2 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 0);
572 let id3 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 20, 0);
573
574 assert_eq!(id1, id2);
575 assert_ne!(id1, id3);
576 }
577
578 #[test]
579 fn test_symbol_id_hash() {
580 use std::collections::HashSet;
581
582 let id1 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 0);
583 let id2 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 0);
584
585 let mut set = HashSet::new();
586 set.insert(id1);
587 assert!(set.contains(&id2));
588 }
589
590 #[test]
591 fn test_symbol_id_storage_id() {
592 let id = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 5);
593 let storage_id = id.to_storage_id();
594 assert_eq!(storage_id, "src/main.rs:foo:10:5");
595 }
596
597 #[test]
598 fn test_definition_storage_id() {
599 let def = Definition {
600 symbol_id: SymbolId::new("src/lib.rs", "MyClass", SymbolKind::Class, 15, 0),
601 root_path: Some("/project".to_string()),
602 project: Some("test".to_string()),
603 end_line: 50,
604 end_col: 1,
605 signature: "class MyClass".to_string(),
606 doc_comment: None,
607 visibility: Visibility::Public,
608 parent_id: None,
609 indexed_at: 12345,
610 };
611
612 assert_eq!(def.to_storage_id(), "def:src/lib.rs:MyClass:15");
613 assert_eq!(def.file_path(), "src/lib.rs");
614 assert_eq!(def.name(), "MyClass");
615 assert_eq!(def.kind(), SymbolKind::Class);
616 }
617
618 #[test]
619 fn test_reference_storage_id() {
620 let reference = Reference {
621 file_path: "src/consumer.rs".to_string(),
622 root_path: None,
623 project: None,
624 start_line: 25,
625 end_line: 25,
626 start_col: 10,
627 end_col: 20,
628 target_symbol_id: "def:src/lib.rs:foo:10".to_string(),
629 reference_kind: ReferenceKind::Call,
630 indexed_at: 12345,
631 };
632
633 assert_eq!(reference.to_storage_id(), "ref:src/consumer.rs:25:10");
634 }
635
636 #[test]
637 fn test_precision_level_description() {
638 assert_eq!(PrecisionLevel::High.description(), "high (stack-graphs)");
639 assert_eq!(PrecisionLevel::Medium.description(), "medium (AST-based)");
640 assert_eq!(PrecisionLevel::Low.description(), "low (text-based)");
641 }
642
643 #[test]
644 fn test_definition_result_from_definition() {
645 let def = Definition {
646 symbol_id: SymbolId::new("src/lib.rs", "my_func", SymbolKind::Function, 10, 0),
647 root_path: None,
648 project: None,
649 end_line: 20,
650 end_col: 1,
651 signature: "fn my_func()".to_string(),
652 doc_comment: Some("Does stuff".to_string()),
653 visibility: Visibility::Public,
654 parent_id: None,
655 indexed_at: 0,
656 };
657
658 let result = DefinitionResult::from(&def);
659 assert_eq!(result.file_path, "src/lib.rs");
660 assert_eq!(result.name, "my_func");
661 assert_eq!(result.kind, SymbolKind::Function);
662 assert_eq!(result.start_line, 10);
663 assert_eq!(result.end_line, 20);
664 assert_eq!(result.doc_comment, Some("Does stuff".to_string()));
665 }
666
667 #[test]
668 fn test_serialization() {
669 let id = SymbolId::new("src/main.rs", "test", SymbolKind::Function, 1, 0);
670 let json = serde_json::to_string(&id).unwrap();
671 let deserialized: SymbolId = serde_json::from_str(&json).unwrap();
672 assert_eq!(id, deserialized);
673 }
674
675 #[test]
676 fn test_reference_kind_serialization() {
677 let kind = ReferenceKind::Call;
678 let json = serde_json::to_string(&kind).unwrap();
679 assert_eq!(json, "\"call\"");
680
681 let deserialized: ReferenceKind = serde_json::from_str(&json).unwrap();
682 assert_eq!(deserialized, ReferenceKind::Call);
683 }
684}