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 Medium,
412 Low,
414}
415
416impl PrecisionLevel {
417 pub fn description(&self) -> &'static str {
419 match self {
420 Self::Medium => "medium (AST-based)",
421 Self::Low => "low (text-based)",
422 }
423 }
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
432pub struct DefinitionResult {
433 pub file_path: String,
435 pub name: String,
437 pub kind: SymbolKind,
439 pub start_line: usize,
441 pub end_line: usize,
443 pub start_col: usize,
445 pub end_col: usize,
447 pub signature: String,
449 pub doc_comment: Option<String>,
451}
452
453impl From<&Definition> for DefinitionResult {
454 fn from(def: &Definition) -> Self {
455 Self {
456 file_path: def.symbol_id.file_path.clone(),
457 name: def.symbol_id.name.clone(),
458 kind: def.symbol_id.kind,
459 start_line: def.symbol_id.start_line,
460 end_line: def.end_line,
461 start_col: def.symbol_id.start_col,
462 end_col: def.end_col,
463 signature: def.signature.clone(),
464 doc_comment: def.doc_comment.clone(),
465 }
466 }
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
471pub struct ReferenceResult {
472 pub file_path: String,
474 pub start_line: usize,
476 pub end_line: usize,
478 pub start_col: usize,
480 pub end_col: usize,
482 pub reference_kind: ReferenceKind,
484 pub preview: Option<String>,
486}
487
488impl From<&Reference> for ReferenceResult {
489 fn from(r: &Reference) -> Self {
490 Self {
491 file_path: r.file_path.clone(),
492 start_line: r.start_line,
493 end_line: r.end_line,
494 start_col: r.start_col,
495 end_col: r.end_col,
496 reference_kind: r.reference_kind,
497 preview: None,
498 }
499 }
500}
501
502#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
504pub struct CallGraphNode {
505 pub name: String,
507 pub kind: SymbolKind,
509 pub file_path: String,
511 pub line: usize,
513 pub children: Vec<CallGraphNode>,
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
519pub struct SymbolInfo {
520 pub name: String,
522 pub kind: SymbolKind,
524 pub file_path: String,
526 pub start_line: usize,
528 pub end_line: usize,
530 pub signature: String,
532}
533
534#[cfg(test)]
539mod tests {
540 use super::*;
541
542 #[test]
543 fn test_symbol_kind_from_ast_kind() {
544 assert_eq!(
545 SymbolKind::from_ast_kind("function_item"),
546 SymbolKind::Function
547 );
548 assert_eq!(
549 SymbolKind::from_ast_kind("class_definition"),
550 SymbolKind::Class
551 );
552 assert_eq!(
553 SymbolKind::from_ast_kind("method_definition"),
554 SymbolKind::Method
555 );
556 assert_eq!(
557 SymbolKind::from_ast_kind("unknown_node"),
558 SymbolKind::Unknown
559 );
560 }
561
562 #[test]
563 fn test_symbol_kind_display_name() {
564 assert_eq!(SymbolKind::Function.display_name(), "function");
565 assert_eq!(SymbolKind::Class.display_name(), "class");
566 assert_eq!(SymbolKind::Unknown.display_name(), "unknown");
567 }
568
569 #[test]
570 fn test_visibility_from_keywords() {
571 assert_eq!(Visibility::from_keywords("pub fn foo"), Visibility::Public);
572 assert_eq!(
573 Visibility::from_keywords("public void bar"),
574 Visibility::Public
575 );
576 assert_eq!(
577 Visibility::from_keywords("protected int x"),
578 Visibility::Protected
579 );
580 assert_eq!(
581 Visibility::from_keywords("fn private_func"),
582 Visibility::Private
583 );
584 }
585
586 #[test]
587 fn test_symbol_id_equality() {
588 let id1 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 0);
589 let id2 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 0);
590 let id3 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 20, 0);
591
592 assert_eq!(id1, id2);
593 assert_ne!(id1, id3);
594 }
595
596 #[test]
597 fn test_symbol_id_hash() {
598 use std::collections::HashSet;
599
600 let id1 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 0);
601 let id2 = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 0);
602
603 let mut set = HashSet::new();
604 set.insert(id1);
605 assert!(set.contains(&id2));
606 }
607
608 #[test]
609 fn test_symbol_id_storage_id() {
610 let id = SymbolId::new("src/main.rs", "foo", SymbolKind::Function, 10, 5);
611 let storage_id = id.to_storage_id();
612 assert_eq!(storage_id, "src/main.rs:foo:10:5");
613 }
614
615 #[test]
616 fn test_definition_storage_id() {
617 let def = Definition {
618 symbol_id: SymbolId::new("src/lib.rs", "MyClass", SymbolKind::Class, 15, 0),
619 root_path: Some("/project".to_string()),
620 project: Some("test".to_string()),
621 end_line: 50,
622 end_col: 1,
623 signature: "class MyClass".to_string(),
624 doc_comment: None,
625 visibility: Visibility::Public,
626 parent_id: None,
627 indexed_at: 12345,
628 };
629
630 assert_eq!(def.to_storage_id(), "def:src/lib.rs:MyClass:15");
631 assert_eq!(def.file_path(), "src/lib.rs");
632 assert_eq!(def.name(), "MyClass");
633 assert_eq!(def.kind(), SymbolKind::Class);
634 }
635
636 #[test]
637 fn test_reference_storage_id() {
638 let reference = Reference {
639 file_path: "src/consumer.rs".to_string(),
640 root_path: None,
641 project: None,
642 start_line: 25,
643 end_line: 25,
644 start_col: 10,
645 end_col: 20,
646 target_symbol_id: "def:src/lib.rs:foo:10".to_string(),
647 reference_kind: ReferenceKind::Call,
648 indexed_at: 12345,
649 };
650
651 assert_eq!(reference.to_storage_id(), "ref:src/consumer.rs:25:10");
652 }
653
654 #[test]
655 fn test_precision_level_description() {
656 assert_eq!(PrecisionLevel::Medium.description(), "medium (AST-based)");
657 assert_eq!(PrecisionLevel::Low.description(), "low (text-based)");
658 }
659
660 #[test]
661 fn test_definition_result_from_definition() {
662 let def = Definition {
663 symbol_id: SymbolId::new("src/lib.rs", "my_func", SymbolKind::Function, 10, 0),
664 root_path: None,
665 project: None,
666 end_line: 20,
667 end_col: 1,
668 signature: "fn my_func()".to_string(),
669 doc_comment: Some("Does stuff".to_string()),
670 visibility: Visibility::Public,
671 parent_id: None,
672 indexed_at: 0,
673 };
674
675 let result = DefinitionResult::from(&def);
676 assert_eq!(result.file_path, "src/lib.rs");
677 assert_eq!(result.name, "my_func");
678 assert_eq!(result.kind, SymbolKind::Function);
679 assert_eq!(result.start_line, 10);
680 assert_eq!(result.end_line, 20);
681 assert_eq!(result.doc_comment, Some("Does stuff".to_string()));
682 }
683
684 #[test]
685 fn test_serialization() {
686 let id = SymbolId::new("src/main.rs", "test", SymbolKind::Function, 1, 0);
687 let json = serde_json::to_string(&id).unwrap();
688 let deserialized: SymbolId = serde_json::from_str(&json).unwrap();
689 assert_eq!(id, deserialized);
690 }
691
692 #[test]
693 fn test_reference_kind_serialization() {
694 let kind = ReferenceKind::Call;
695 let json = serde_json::to_string(&kind).unwrap();
696 assert_eq!(json, "\"call\"");
697
698 let deserialized: ReferenceKind = serde_json::from_str(&json).unwrap();
699 assert_eq!(deserialized, ReferenceKind::Call);
700 }
701}