Skip to main content

codemem_engine/index/
symbol.rs

1//! Symbol and Reference types extracted from source code via tree-sitter.
2
3use serde::{Deserialize, Serialize};
4
5/// A function/method parameter.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Parameter {
8    /// Parameter name.
9    pub name: String,
10    /// Type annotation, if present.
11    pub type_annotation: Option<String>,
12    /// Default value expression, if present.
13    pub default_value: Option<String>,
14}
15
16/// A code symbol extracted from source.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Symbol {
19    /// Simple name (e.g., "add").
20    pub name: String,
21    /// Fully qualified name (e.g., "module::Struct::method").
22    pub qualified_name: String,
23    /// What kind of symbol this is.
24    pub kind: SymbolKind,
25    /// Full signature text (up to the opening brace or the whole item for short items).
26    pub signature: String,
27    /// Visibility of the symbol.
28    pub visibility: Visibility,
29    /// File path where the symbol is defined.
30    pub file_path: String,
31    /// 0-based starting line number.
32    pub line_start: usize,
33    /// 0-based ending line number.
34    pub line_end: usize,
35    /// Documentation comment, if any (e.g., `///` or `//!` in Rust).
36    pub doc_comment: Option<String>,
37    /// Qualified name of the parent symbol (e.g., struct name for a method).
38    pub parent: Option<String>,
39    /// Extracted parameters for functions/methods.
40    #[serde(default)]
41    pub parameters: Vec<Parameter>,
42    /// Return type annotation, if present.
43    #[serde(default)]
44    pub return_type: Option<String>,
45    /// Whether this is an async function/method.
46    #[serde(default)]
47    pub is_async: bool,
48    /// Attributes, decorators, or annotations (e.g., `#[derive(Debug)]`, `@Override`).
49    #[serde(default)]
50    pub attributes: Vec<String>,
51    /// Error/exception types this symbol can throw.
52    #[serde(default)]
53    pub throws: Vec<String>,
54    /// Generic type parameters (e.g., `<T: Display>`).
55    #[serde(default)]
56    pub generic_params: Option<String>,
57    /// Whether this is an abstract method (trait/interface method without body).
58    #[serde(default)]
59    pub is_abstract: bool,
60}
61
62/// The kind of a code symbol.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
64pub enum SymbolKind {
65    Function,
66    Method,
67    Class,
68    Struct,
69    Enum,
70    Interface, // trait in Rust
71    Type,      // type alias
72    Constant,
73    Module,
74    Test,
75    Field,       // struct/class field
76    Constructor, // __init__, constructor, new
77}
78
79impl std::fmt::Display for SymbolKind {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            Self::Function => write!(f, "function"),
83            Self::Method => write!(f, "method"),
84            Self::Class => write!(f, "class"),
85            Self::Struct => write!(f, "struct"),
86            Self::Enum => write!(f, "enum"),
87            Self::Interface => write!(f, "interface"),
88            Self::Type => write!(f, "type"),
89            Self::Constant => write!(f, "constant"),
90            Self::Module => write!(f, "module"),
91            Self::Test => write!(f, "test"),
92            Self::Field => write!(f, "field"),
93            Self::Constructor => write!(f, "constructor"),
94        }
95    }
96}
97
98/// Visibility of a symbol.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
100pub enum Visibility {
101    Public,
102    Private,
103    Crate,     // pub(crate) in Rust
104    Protected, // for languages that have it
105}
106
107impl std::fmt::Display for Visibility {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        match self {
110            Self::Public => write!(f, "public"),
111            Self::Private => write!(f, "private"),
112            Self::Crate => write!(f, "crate"),
113            Self::Protected => write!(f, "protected"),
114        }
115    }
116}
117
118/// A reference from one symbol/location to another symbol.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct Reference {
121    /// Qualified name of the symbol that contains this reference.
122    pub source_qualified_name: String,
123    /// Name of the referenced target (may be unresolved / simple name).
124    pub target_name: String,
125    /// What kind of reference this is.
126    pub kind: ReferenceKind,
127    /// File path where the reference occurs.
128    pub file_path: String,
129    /// 0-based line number of the reference.
130    pub line: usize,
131}
132
133/// The kind of a reference between symbols.
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
135pub enum ReferenceKind {
136    Call,
137    Callback,
138    Import,
139    Inherits,
140    Implements,
141    TypeUsage,
142}
143
144impl std::str::FromStr for SymbolKind {
145    type Err = String;
146
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        match s {
149            "function" => Ok(SymbolKind::Function),
150            "method" => Ok(SymbolKind::Method),
151            "class" => Ok(SymbolKind::Class),
152            "struct" => Ok(SymbolKind::Struct),
153            "enum" => Ok(SymbolKind::Enum),
154            "interface" => Ok(SymbolKind::Interface),
155            "type" => Ok(SymbolKind::Type),
156            "constant" => Ok(SymbolKind::Constant),
157            "module" => Ok(SymbolKind::Module),
158            "test" => Ok(SymbolKind::Test),
159            "field" => Ok(SymbolKind::Field),
160            "constructor" => Ok(SymbolKind::Constructor),
161            _ => Err(format!("Unknown SymbolKind: {s}")),
162        }
163    }
164}
165
166/// Parse a `SymbolKind` from its `Display` string (e.g. `"function"`, `"enum_variant"`).
167pub fn symbol_kind_from_str(s: &str) -> Option<SymbolKind> {
168    s.parse().ok()
169}
170
171/// Parse a `Visibility` from its `Display` string.
172pub fn visibility_from_str(s: &str) -> Visibility {
173    match s {
174        "public" => Visibility::Public,
175        "crate" => Visibility::Crate,
176        "protected" => Visibility::Protected,
177        _ => Visibility::Private,
178    }
179}
180
181/// Lossy fallback: map a `NodeKind` back to a `SymbolKind`.
182/// Several `SymbolKind` variants collapse into the same `NodeKind`, so this
183/// mapping is not lossless (e.g. `NodeKind::Constant` could be Field/Constant).
184/// Prefer `symbol_kind_from_str` with the stored
185/// `"symbol_kind"` payload field for lossless round-trips.
186fn symbol_kind_from_node_kind(nk: &codemem_core::NodeKind) -> SymbolKind {
187    match nk {
188        codemem_core::NodeKind::Function => SymbolKind::Function,
189        codemem_core::NodeKind::Method => SymbolKind::Method,
190        codemem_core::NodeKind::Class => SymbolKind::Class,
191        codemem_core::NodeKind::Interface | codemem_core::NodeKind::Trait => SymbolKind::Interface,
192        codemem_core::NodeKind::Type => SymbolKind::Type,
193        codemem_core::NodeKind::Constant => SymbolKind::Constant,
194        codemem_core::NodeKind::Module => SymbolKind::Module,
195        codemem_core::NodeKind::Test => SymbolKind::Test,
196        codemem_core::NodeKind::Enum => SymbolKind::Enum,
197        codemem_core::NodeKind::Field | codemem_core::NodeKind::Property => SymbolKind::Field,
198        _ => SymbolKind::Function, // safe fallback for non-symbol node kinds
199    }
200}
201
202/// Reconstruct a `Symbol` from a persisted `sym:*` `GraphNode`.
203///
204/// Returns `None` if the node ID doesn't start with `"sym:"` or required
205/// payload fields are missing.
206pub fn symbol_from_graph_node(node: &codemem_core::GraphNode) -> Option<Symbol> {
207    let qualified_name = node.id.strip_prefix("sym:")?.to_string();
208
209    // Lossless kind from payload, lossy fallback from NodeKind
210    let kind = node
211        .payload
212        .get("symbol_kind")
213        .and_then(|v| v.as_str())
214        .and_then(symbol_kind_from_str)
215        .unwrap_or_else(|| symbol_kind_from_node_kind(&node.kind));
216
217    let file_path = node
218        .payload
219        .get("file_path")
220        .and_then(|v| v.as_str())
221        .unwrap_or("")
222        .to_string();
223
224    let signature = node
225        .payload
226        .get("signature")
227        .and_then(|v| v.as_str())
228        .unwrap_or("")
229        .to_string();
230
231    let visibility = node
232        .payload
233        .get("visibility")
234        .and_then(|v| v.as_str())
235        .map(visibility_from_str)
236        .unwrap_or(Visibility::Private);
237
238    let line_start = node
239        .payload
240        .get("line_start")
241        .and_then(|v| v.as_u64())
242        .unwrap_or(0) as usize;
243
244    let line_end = node
245        .payload
246        .get("line_end")
247        .and_then(|v| v.as_u64())
248        .unwrap_or(0) as usize;
249
250    let doc_comment = node
251        .payload
252        .get("doc_comment")
253        .and_then(|v| v.as_str())
254        .map(|s| s.to_string());
255
256    // Derive simple name from qualified name (last segment after "::")
257    let name = qualified_name
258        .rsplit("::")
259        .next()
260        .unwrap_or(&qualified_name)
261        .to_string();
262
263    let parameters: Vec<Parameter> = node
264        .payload
265        .get("parameters")
266        .and_then(|v| serde_json::from_value(v.clone()).ok())
267        .unwrap_or_default();
268
269    let return_type = node
270        .payload
271        .get("return_type")
272        .and_then(|v| v.as_str())
273        .map(|s| s.to_string());
274
275    let is_async = node
276        .payload
277        .get("is_async")
278        .and_then(|v| v.as_bool())
279        .unwrap_or(false);
280
281    let attributes: Vec<String> = node
282        .payload
283        .get("attributes")
284        .and_then(|v| serde_json::from_value(v.clone()).ok())
285        .unwrap_or_default();
286
287    let throws: Vec<String> = node
288        .payload
289        .get("throws")
290        .and_then(|v| serde_json::from_value(v.clone()).ok())
291        .unwrap_or_default();
292
293    let generic_params = node
294        .payload
295        .get("generic_params")
296        .and_then(|v| v.as_str())
297        .map(|s| s.to_string());
298
299    let is_abstract = node
300        .payload
301        .get("is_abstract")
302        .and_then(|v| v.as_bool())
303        .unwrap_or(false);
304
305    let parent = node
306        .payload
307        .get("parent")
308        .and_then(|v| v.as_str())
309        .map(|s| s.to_string());
310
311    Some(Symbol {
312        name,
313        qualified_name,
314        kind,
315        signature,
316        visibility,
317        file_path,
318        line_start,
319        line_end,
320        doc_comment,
321        parent,
322        parameters,
323        return_type,
324        is_async,
325        attributes,
326        throws,
327        generic_params,
328        is_abstract,
329    })
330}
331
332impl From<SymbolKind> for codemem_core::NodeKind {
333    fn from(kind: SymbolKind) -> Self {
334        match kind {
335            SymbolKind::Function => codemem_core::NodeKind::Function,
336            SymbolKind::Method => codemem_core::NodeKind::Method,
337            SymbolKind::Class => codemem_core::NodeKind::Class,
338            SymbolKind::Struct => codemem_core::NodeKind::Class,
339            SymbolKind::Enum => codemem_core::NodeKind::Enum,
340            SymbolKind::Interface => codemem_core::NodeKind::Interface,
341            SymbolKind::Type => codemem_core::NodeKind::Type,
342            SymbolKind::Constant => codemem_core::NodeKind::Constant,
343            SymbolKind::Module => codemem_core::NodeKind::Module,
344            SymbolKind::Test => codemem_core::NodeKind::Test,
345            SymbolKind::Field => codemem_core::NodeKind::Field,
346            SymbolKind::Constructor => codemem_core::NodeKind::Method,
347        }
348    }
349}
350
351impl std::fmt::Display for ReferenceKind {
352    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
353        match self {
354            Self::Call => write!(f, "call"),
355            Self::Callback => write!(f, "callback"),
356            Self::Import => write!(f, "import"),
357            Self::Inherits => write!(f, "inherits"),
358            Self::Implements => write!(f, "implements"),
359            Self::TypeUsage => write!(f, "type_usage"),
360        }
361    }
362}