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    Import,
138    Inherits,
139    Implements,
140    TypeUsage,
141}
142
143impl std::str::FromStr for SymbolKind {
144    type Err = String;
145
146    fn from_str(s: &str) -> Result<Self, Self::Err> {
147        match s {
148            "function" => Ok(SymbolKind::Function),
149            "method" => Ok(SymbolKind::Method),
150            "class" => Ok(SymbolKind::Class),
151            "struct" => Ok(SymbolKind::Struct),
152            "enum" => Ok(SymbolKind::Enum),
153            "interface" => Ok(SymbolKind::Interface),
154            "type" => Ok(SymbolKind::Type),
155            "constant" => Ok(SymbolKind::Constant),
156            "module" => Ok(SymbolKind::Module),
157            "test" => Ok(SymbolKind::Test),
158            "field" => Ok(SymbolKind::Field),
159            "constructor" => Ok(SymbolKind::Constructor),
160            _ => Err(format!("Unknown SymbolKind: {s}")),
161        }
162    }
163}
164
165/// Parse a `SymbolKind` from its `Display` string (e.g. `"function"`, `"enum_variant"`).
166pub fn symbol_kind_from_str(s: &str) -> Option<SymbolKind> {
167    s.parse().ok()
168}
169
170/// Parse a `Visibility` from its `Display` string.
171pub fn visibility_from_str(s: &str) -> Visibility {
172    match s {
173        "public" => Visibility::Public,
174        "crate" => Visibility::Crate,
175        "protected" => Visibility::Protected,
176        _ => Visibility::Private,
177    }
178}
179
180/// Lossy fallback: map a `NodeKind` back to a `SymbolKind`.
181/// Several `SymbolKind` variants collapse into the same `NodeKind`, so this
182/// mapping is not lossless (e.g. `NodeKind::Constant` could be Field/Constant).
183/// Prefer `symbol_kind_from_str` with the stored
184/// `"symbol_kind"` payload field for lossless round-trips.
185fn symbol_kind_from_node_kind(nk: &codemem_core::NodeKind) -> SymbolKind {
186    match nk {
187        codemem_core::NodeKind::Function => SymbolKind::Function,
188        codemem_core::NodeKind::Method => SymbolKind::Method,
189        codemem_core::NodeKind::Class => SymbolKind::Class,
190        codemem_core::NodeKind::Interface | codemem_core::NodeKind::Trait => SymbolKind::Interface,
191        codemem_core::NodeKind::Type => SymbolKind::Type,
192        codemem_core::NodeKind::Constant => SymbolKind::Constant,
193        codemem_core::NodeKind::Module => SymbolKind::Module,
194        codemem_core::NodeKind::Test => SymbolKind::Test,
195        codemem_core::NodeKind::Enum => SymbolKind::Enum,
196        codemem_core::NodeKind::Field | codemem_core::NodeKind::Property => SymbolKind::Field,
197        _ => SymbolKind::Function, // safe fallback for non-symbol node kinds
198    }
199}
200
201/// Reconstruct a `Symbol` from a persisted `sym:*` `GraphNode`.
202///
203/// Returns `None` if the node ID doesn't start with `"sym:"` or required
204/// payload fields are missing.
205pub fn symbol_from_graph_node(node: &codemem_core::GraphNode) -> Option<Symbol> {
206    let qualified_name = node.id.strip_prefix("sym:")?.to_string();
207
208    // Lossless kind from payload, lossy fallback from NodeKind
209    let kind = node
210        .payload
211        .get("symbol_kind")
212        .and_then(|v| v.as_str())
213        .and_then(symbol_kind_from_str)
214        .unwrap_or_else(|| symbol_kind_from_node_kind(&node.kind));
215
216    let file_path = node
217        .payload
218        .get("file_path")
219        .and_then(|v| v.as_str())
220        .unwrap_or("")
221        .to_string();
222
223    let signature = node
224        .payload
225        .get("signature")
226        .and_then(|v| v.as_str())
227        .unwrap_or("")
228        .to_string();
229
230    let visibility = node
231        .payload
232        .get("visibility")
233        .and_then(|v| v.as_str())
234        .map(visibility_from_str)
235        .unwrap_or(Visibility::Private);
236
237    let line_start = node
238        .payload
239        .get("line_start")
240        .and_then(|v| v.as_u64())
241        .unwrap_or(0) as usize;
242
243    let line_end = node
244        .payload
245        .get("line_end")
246        .and_then(|v| v.as_u64())
247        .unwrap_or(0) as usize;
248
249    let doc_comment = node
250        .payload
251        .get("doc_comment")
252        .and_then(|v| v.as_str())
253        .map(|s| s.to_string());
254
255    // Derive simple name from qualified name (last segment after "::")
256    let name = qualified_name
257        .rsplit("::")
258        .next()
259        .unwrap_or(&qualified_name)
260        .to_string();
261
262    let parameters: Vec<Parameter> = node
263        .payload
264        .get("parameters")
265        .and_then(|v| serde_json::from_value(v.clone()).ok())
266        .unwrap_or_default();
267
268    let return_type = node
269        .payload
270        .get("return_type")
271        .and_then(|v| v.as_str())
272        .map(|s| s.to_string());
273
274    let is_async = node
275        .payload
276        .get("is_async")
277        .and_then(|v| v.as_bool())
278        .unwrap_or(false);
279
280    let attributes: Vec<String> = node
281        .payload
282        .get("attributes")
283        .and_then(|v| serde_json::from_value(v.clone()).ok())
284        .unwrap_or_default();
285
286    let throws: Vec<String> = node
287        .payload
288        .get("throws")
289        .and_then(|v| serde_json::from_value(v.clone()).ok())
290        .unwrap_or_default();
291
292    let generic_params = node
293        .payload
294        .get("generic_params")
295        .and_then(|v| v.as_str())
296        .map(|s| s.to_string());
297
298    let is_abstract = node
299        .payload
300        .get("is_abstract")
301        .and_then(|v| v.as_bool())
302        .unwrap_or(false);
303
304    let parent = node
305        .payload
306        .get("parent")
307        .and_then(|v| v.as_str())
308        .map(|s| s.to_string());
309
310    Some(Symbol {
311        name,
312        qualified_name,
313        kind,
314        signature,
315        visibility,
316        file_path,
317        line_start,
318        line_end,
319        doc_comment,
320        parent,
321        parameters,
322        return_type,
323        is_async,
324        attributes,
325        throws,
326        generic_params,
327        is_abstract,
328    })
329}
330
331impl From<SymbolKind> for codemem_core::NodeKind {
332    fn from(kind: SymbolKind) -> Self {
333        match kind {
334            SymbolKind::Function => codemem_core::NodeKind::Function,
335            SymbolKind::Method => codemem_core::NodeKind::Method,
336            SymbolKind::Class => codemem_core::NodeKind::Class,
337            SymbolKind::Struct => codemem_core::NodeKind::Class,
338            SymbolKind::Enum => codemem_core::NodeKind::Enum,
339            SymbolKind::Interface => codemem_core::NodeKind::Interface,
340            SymbolKind::Type => codemem_core::NodeKind::Type,
341            SymbolKind::Constant => codemem_core::NodeKind::Constant,
342            SymbolKind::Module => codemem_core::NodeKind::Module,
343            SymbolKind::Test => codemem_core::NodeKind::Test,
344            SymbolKind::Field => codemem_core::NodeKind::Field,
345            SymbolKind::Constructor => codemem_core::NodeKind::Method,
346        }
347    }
348}
349
350impl std::fmt::Display for ReferenceKind {
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352        match self {
353            Self::Call => write!(f, "call"),
354            Self::Import => write!(f, "import"),
355            Self::Inherits => write!(f, "inherits"),
356            Self::Implements => write!(f, "implements"),
357            Self::TypeUsage => write!(f, "type_usage"),
358        }
359    }
360}