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 => 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        _ => SymbolKind::Function, // safe fallback for non-symbol node kinds
196    }
197}
198
199/// Reconstruct a `Symbol` from a persisted `sym:*` `GraphNode`.
200///
201/// Returns `None` if the node ID doesn't start with `"sym:"` or required
202/// payload fields are missing.
203pub fn symbol_from_graph_node(node: &codemem_core::GraphNode) -> Option<Symbol> {
204    let qualified_name = node.id.strip_prefix("sym:")?.to_string();
205
206    // Lossless kind from payload, lossy fallback from NodeKind
207    let kind = node
208        .payload
209        .get("symbol_kind")
210        .and_then(|v| v.as_str())
211        .and_then(symbol_kind_from_str)
212        .unwrap_or_else(|| symbol_kind_from_node_kind(&node.kind));
213
214    let file_path = node
215        .payload
216        .get("file_path")
217        .and_then(|v| v.as_str())
218        .unwrap_or("")
219        .to_string();
220
221    let signature = node
222        .payload
223        .get("signature")
224        .and_then(|v| v.as_str())
225        .unwrap_or("")
226        .to_string();
227
228    let visibility = node
229        .payload
230        .get("visibility")
231        .and_then(|v| v.as_str())
232        .map(visibility_from_str)
233        .unwrap_or(Visibility::Private);
234
235    let line_start = node
236        .payload
237        .get("line_start")
238        .and_then(|v| v.as_u64())
239        .unwrap_or(0) as usize;
240
241    let line_end = node
242        .payload
243        .get("line_end")
244        .and_then(|v| v.as_u64())
245        .unwrap_or(0) as usize;
246
247    let doc_comment = node
248        .payload
249        .get("doc_comment")
250        .and_then(|v| v.as_str())
251        .map(|s| s.to_string());
252
253    // Derive simple name from qualified name (last segment after "::")
254    let name = qualified_name
255        .rsplit("::")
256        .next()
257        .unwrap_or(&qualified_name)
258        .to_string();
259
260    let parameters: Vec<Parameter> = node
261        .payload
262        .get("parameters")
263        .and_then(|v| serde_json::from_value(v.clone()).ok())
264        .unwrap_or_default();
265
266    let return_type = node
267        .payload
268        .get("return_type")
269        .and_then(|v| v.as_str())
270        .map(|s| s.to_string());
271
272    let is_async = node
273        .payload
274        .get("is_async")
275        .and_then(|v| v.as_bool())
276        .unwrap_or(false);
277
278    let attributes: Vec<String> = node
279        .payload
280        .get("attributes")
281        .and_then(|v| serde_json::from_value(v.clone()).ok())
282        .unwrap_or_default();
283
284    let throws: Vec<String> = node
285        .payload
286        .get("throws")
287        .and_then(|v| serde_json::from_value(v.clone()).ok())
288        .unwrap_or_default();
289
290    let generic_params = node
291        .payload
292        .get("generic_params")
293        .and_then(|v| v.as_str())
294        .map(|s| s.to_string());
295
296    let is_abstract = node
297        .payload
298        .get("is_abstract")
299        .and_then(|v| v.as_bool())
300        .unwrap_or(false);
301
302    let parent = node
303        .payload
304        .get("parent")
305        .and_then(|v| v.as_str())
306        .map(|s| s.to_string());
307
308    Some(Symbol {
309        name,
310        qualified_name,
311        kind,
312        signature,
313        visibility,
314        file_path,
315        line_start,
316        line_end,
317        doc_comment,
318        parent,
319        parameters,
320        return_type,
321        is_async,
322        attributes,
323        throws,
324        generic_params,
325        is_abstract,
326    })
327}
328
329impl From<SymbolKind> for codemem_core::NodeKind {
330    fn from(kind: SymbolKind) -> Self {
331        match kind {
332            SymbolKind::Function => codemem_core::NodeKind::Function,
333            SymbolKind::Method => codemem_core::NodeKind::Method,
334            SymbolKind::Class => codemem_core::NodeKind::Class,
335            SymbolKind::Struct => codemem_core::NodeKind::Class,
336            SymbolKind::Enum => codemem_core::NodeKind::Class,
337            SymbolKind::Interface => codemem_core::NodeKind::Interface,
338            SymbolKind::Type => codemem_core::NodeKind::Type,
339            SymbolKind::Constant => codemem_core::NodeKind::Constant,
340            SymbolKind::Module => codemem_core::NodeKind::Module,
341            SymbolKind::Test => codemem_core::NodeKind::Test,
342            SymbolKind::Field => codemem_core::NodeKind::Constant,
343            SymbolKind::Constructor => codemem_core::NodeKind::Method,
344        }
345    }
346}
347
348impl std::fmt::Display for ReferenceKind {
349    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350        match self {
351            Self::Call => write!(f, "call"),
352            Self::Import => write!(f, "import"),
353            Self::Inherits => write!(f, "inherits"),
354            Self::Implements => write!(f, "implements"),
355            Self::TypeUsage => write!(f, "type_usage"),
356        }
357    }
358}