Skip to main content

sqry_core/graph/unified/node/
kind.rs

1//! `NodeKind` enumeration for the unified graph architecture.
2//!
3//! This module defines `NodeKind`, which categorizes all code entities
4//! that can be represented as nodes in the graph.
5//!
6//! # Design (, Appendix A1)
7//!
8//! The enumeration covers:
9//! - **Core symbols**: Functions, methods, classes, interfaces, traits
10//! - **Declarations**: Variables, constants, types, modules
11//! - **Structural**: Call sites, components, services
12//! - **Domain-specific**: Resources, endpoints, handlers
13
14use std::fmt;
15
16use serde::{Deserialize, Serialize};
17
18/// Enumeration of code entity types that can be represented as graph nodes.
19///
20/// Each variant represents a distinct category of code symbol. The categorization
21/// is language-agnostic to support cross-language analysis.
22///
23/// # Serialization
24///
25/// All variants serialize to their lowercase name for JSON interoperability:
26/// - `Function` → `"function"`
27/// - `CallSite` → `"call_site"`
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum NodeKind {
31    // ==================== Core Symbols ====================
32    /// A standalone function (not a method).
33    Function,
34
35    /// A method belonging to a class, struct, or trait.
36    Method,
37
38    /// A class definition (OOP languages).
39    Class,
40
41    /// An interface definition (TypeScript, Go, Java, etc.).
42    Interface,
43
44    /// A trait definition (Rust, Scala, etc.).
45    Trait,
46
47    /// A module or namespace declaration.
48    Module,
49
50    // ==================== Declarations ====================
51    /// A variable binding (let, var, const in some languages).
52    Variable,
53
54    /// A constant value (const, static const, final).
55    Constant,
56
57    /// A type alias or typedef.
58    Type,
59
60    /// A struct definition.
61    Struct,
62
63    /// An enum definition.
64    Enum,
65
66    /// An enum variant.
67    EnumVariant,
68
69    /// A macro definition.
70    Macro,
71
72    /// A function parameter.
73    Parameter,
74
75    /// A class property or struct field.
76    Property,
77
78    // ==================== Structural ====================
79    /// A call site (location where a call occurs).
80    ///
81    /// Call sites are used for fine-grained call graph analysis,
82    /// allowing multiple edges from the same function.
83    CallSite,
84
85    /// An import statement or declaration.
86    Import,
87
88    /// An export statement or re-export.
89    Export,
90
91    // ==================== Styles (CSS/SCSS/Less) ====================
92    /// A style rule (selector block).
93    StyleRule,
94
95    /// A style at-rule (@media, @keyframes, etc.).
96    StyleAtRule,
97
98    /// A style variable ($var or --var).
99    StyleVariable,
100
101    // ==================== Rust-Specific ====================
102    /// A lifetime parameter pseudo-symbol.
103    ///
104    /// Lifetime nodes enable `LifetimeConstraint` edges to have proper
105    /// source/target node IDs in the graph. Each lifetime parameter
106    /// (e.g., `'a`, `'b`, `'static`) in a function/struct signature
107    /// becomes a node.
108    ///
109    /// Qualified name format: `{owner}::'a` (e.g., `foo::'a` for `fn foo<'a>`)
110    /// Special case: `'static` is represented as `::static`
111    Lifetime,
112
113    // ==================== Domain-Specific ====================
114    /// A component (React, Vue, Angular, etc.).
115    Component,
116
117    /// A service class or service function.
118    Service,
119
120    /// A resource (REST, GraphQL, etc.).
121    Resource,
122
123    /// An API endpoint handler.
124    Endpoint,
125
126    /// A test function or test case.
127    Test,
128
129    // ==================== JVM Classpath (Track C) ====================
130    /// Type parameter declaration (e.g., `T` in `class Foo<T>`).
131    TypeParameter,
132
133    /// Annotation type or usage (e.g., `@Override`, `@RequestMapping`).
134    Annotation,
135
136    /// Annotation element value (e.g., `value = "/api"` in `@RequestMapping`).
137    AnnotationValue,
138
139    /// Lambda or method reference target (e.g., `String::toUpperCase`).
140    LambdaTarget,
141
142    /// Java 9+ module declaration (from `module-info.java`).
143    JavaModule,
144
145    /// Enum constant (e.g., `MONDAY` in `enum DayOfWeek`).
146    EnumConstant,
147
148    // ==================== Extensibility ====================
149    /// A custom or language-specific node kind.
150    ///
151    /// Used for plugin-defined node types that don't fit other categories.
152    /// The string identifier provides semantic meaning.
153    #[serde(other)]
154    Other,
155}
156
157impl NodeKind {
158    /// Returns `true` if this is a callable entity (function, method).
159    #[inline]
160    #[must_use]
161    pub const fn is_callable(self) -> bool {
162        matches!(self, Self::Function | Self::Method | Self::Macro)
163    }
164
165    /// Returns `true` if this is a type definition.
166    #[inline]
167    #[must_use]
168    pub const fn is_type_definition(self) -> bool {
169        matches!(
170            self,
171            Self::Class | Self::Interface | Self::Trait | Self::Struct | Self::Enum | Self::Type
172        )
173    }
174
175    /// Returns `true` if this is a container that can have members.
176    #[inline]
177    #[must_use]
178    pub const fn is_container(self) -> bool {
179        matches!(
180            self,
181            Self::Class
182                | Self::Interface
183                | Self::Trait
184                | Self::Struct
185                | Self::Module
186                | Self::Enum
187                | Self::StyleRule
188                | Self::StyleAtRule
189                | Self::JavaModule
190        )
191    }
192
193    /// Returns `true` if this represents an import/export boundary.
194    #[inline]
195    #[must_use]
196    pub const fn is_boundary(self) -> bool {
197        matches!(self, Self::Import | Self::Export)
198    }
199
200    /// Returns `true` if this is a Rust lifetime pseudo-symbol.
201    #[inline]
202    #[must_use]
203    pub const fn is_lifetime(self) -> bool {
204        matches!(self, Self::Lifetime)
205    }
206
207    /// Returns the canonical string representation for serialization.
208    #[must_use]
209    pub const fn as_str(self) -> &'static str {
210        match self {
211            Self::Function => "function",
212            Self::Method => "method",
213            Self::Class => "class",
214            Self::Interface => "interface",
215            Self::Trait => "trait",
216            Self::Module => "module",
217            Self::Variable => "variable",
218            Self::Constant => "constant",
219            Self::Type => "type",
220            Self::Struct => "struct",
221            Self::Enum => "enum",
222            Self::EnumVariant => "enum_variant",
223            Self::Macro => "macro",
224            Self::Parameter => "parameter",
225            Self::Property => "property",
226            Self::CallSite => "call_site",
227            Self::Import => "import",
228            Self::Export => "export",
229            Self::StyleRule => "style_rule",
230            Self::StyleAtRule => "style_at_rule",
231            Self::StyleVariable => "style_variable",
232            Self::Lifetime => "lifetime",
233            Self::Component => "component",
234            Self::Service => "service",
235            Self::Resource => "resource",
236            Self::Endpoint => "endpoint",
237            Self::Test => "test",
238            Self::TypeParameter => "type_parameter",
239            Self::Annotation => "annotation",
240            Self::AnnotationValue => "annotation_value",
241            Self::LambdaTarget => "lambda_target",
242            Self::JavaModule => "java_module",
243            Self::EnumConstant => "enum_constant",
244            Self::Other => "other",
245        }
246    }
247
248    /// Parses a string into a `NodeKind`.
249    ///
250    /// Returns `None` if the string doesn't match any known kind.
251    #[must_use]
252    pub fn parse(s: &str) -> Option<Self> {
253        match s {
254            "function" => Some(Self::Function),
255            "method" => Some(Self::Method),
256            "class" => Some(Self::Class),
257            "interface" => Some(Self::Interface),
258            "trait" => Some(Self::Trait),
259            "module" => Some(Self::Module),
260            "variable" => Some(Self::Variable),
261            "constant" => Some(Self::Constant),
262            "type" => Some(Self::Type),
263            "struct" => Some(Self::Struct),
264            "enum" => Some(Self::Enum),
265            "enum_variant" => Some(Self::EnumVariant),
266            "macro" => Some(Self::Macro),
267            "parameter" => Some(Self::Parameter),
268            "property" => Some(Self::Property),
269            "call_site" => Some(Self::CallSite),
270            "import" => Some(Self::Import),
271            "export" => Some(Self::Export),
272            "style_rule" => Some(Self::StyleRule),
273            "style_at_rule" => Some(Self::StyleAtRule),
274            "style_variable" => Some(Self::StyleVariable),
275            "lifetime" => Some(Self::Lifetime),
276            "component" => Some(Self::Component),
277            "service" => Some(Self::Service),
278            "resource" => Some(Self::Resource),
279            "endpoint" => Some(Self::Endpoint),
280            "test" => Some(Self::Test),
281            "type_parameter" => Some(Self::TypeParameter),
282            "annotation" => Some(Self::Annotation),
283            "annotation_value" => Some(Self::AnnotationValue),
284            "lambda_target" => Some(Self::LambdaTarget),
285            "java_module" => Some(Self::JavaModule),
286            "enum_constant" => Some(Self::EnumConstant),
287            "other" => Some(Self::Other),
288            _ => None,
289        }
290    }
291}
292
293impl fmt::Display for NodeKind {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        f.write_str(self.as_str())
296    }
297}
298
299impl Default for NodeKind {
300    /// Returns `NodeKind::Function` as the default (most common node type).
301    fn default() -> Self {
302        Self::Function
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn test_node_kind_as_str() {
312        assert_eq!(NodeKind::Function.as_str(), "function");
313        assert_eq!(NodeKind::Method.as_str(), "method");
314        assert_eq!(NodeKind::Class.as_str(), "class");
315        assert_eq!(NodeKind::CallSite.as_str(), "call_site");
316        assert_eq!(NodeKind::EnumVariant.as_str(), "enum_variant");
317    }
318
319    #[test]
320    fn test_node_kind_parse() {
321        assert_eq!(NodeKind::parse("function"), Some(NodeKind::Function));
322        assert_eq!(NodeKind::parse("call_site"), Some(NodeKind::CallSite));
323        assert_eq!(NodeKind::parse("unknown"), None);
324    }
325
326    #[test]
327    fn test_node_kind_display() {
328        assert_eq!(format!("{}", NodeKind::Function), "function");
329        assert_eq!(format!("{}", NodeKind::EnumVariant), "enum_variant");
330    }
331
332    #[test]
333    fn test_is_callable() {
334        assert!(NodeKind::Function.is_callable());
335        assert!(NodeKind::Method.is_callable());
336        assert!(NodeKind::Macro.is_callable());
337        assert!(!NodeKind::Class.is_callable());
338        assert!(!NodeKind::Variable.is_callable());
339    }
340
341    #[test]
342    fn test_is_type_definition() {
343        assert!(NodeKind::Class.is_type_definition());
344        assert!(NodeKind::Interface.is_type_definition());
345        assert!(NodeKind::Trait.is_type_definition());
346        assert!(NodeKind::Struct.is_type_definition());
347        assert!(NodeKind::Enum.is_type_definition());
348        assert!(NodeKind::Type.is_type_definition());
349        assert!(!NodeKind::Function.is_type_definition());
350        assert!(!NodeKind::Variable.is_type_definition());
351    }
352
353    #[test]
354    fn test_is_container() {
355        assert!(NodeKind::Class.is_container());
356        assert!(NodeKind::Module.is_container());
357        assert!(NodeKind::Struct.is_container());
358        assert!(!NodeKind::Function.is_container());
359        assert!(!NodeKind::Variable.is_container());
360    }
361
362    #[test]
363    fn test_is_boundary() {
364        assert!(NodeKind::Import.is_boundary());
365        assert!(NodeKind::Export.is_boundary());
366        assert!(!NodeKind::Function.is_boundary());
367    }
368
369    #[test]
370    fn test_is_lifetime() {
371        assert!(NodeKind::Lifetime.is_lifetime());
372        assert!(!NodeKind::Function.is_lifetime());
373        assert!(!NodeKind::Variable.is_lifetime());
374    }
375
376    #[test]
377    fn test_lifetime_serialization() {
378        assert_eq!(NodeKind::Lifetime.as_str(), "lifetime");
379        assert_eq!(NodeKind::parse("lifetime"), Some(NodeKind::Lifetime));
380
381        // JSON roundtrip
382        let json = serde_json::to_string(&NodeKind::Lifetime).unwrap();
383        assert_eq!(json, "\"lifetime\"");
384        let deserialized: NodeKind = serde_json::from_str(&json).unwrap();
385        assert_eq!(deserialized, NodeKind::Lifetime);
386    }
387
388    #[test]
389    fn test_default() {
390        assert_eq!(NodeKind::default(), NodeKind::Function);
391    }
392
393    #[test]
394    fn test_serde_roundtrip() {
395        let kinds = [
396            NodeKind::Function,
397            NodeKind::Method,
398            NodeKind::Class,
399            NodeKind::CallSite,
400            NodeKind::EnumVariant,
401        ];
402
403        for kind in kinds {
404            // JSON roundtrip
405            let json = serde_json::to_string(&kind).unwrap();
406            let deserialized: NodeKind = serde_json::from_str(&json).unwrap();
407            assert_eq!(kind, deserialized);
408
409            // Postcard roundtrip
410            let bytes = postcard::to_allocvec(&kind).unwrap();
411            let deserialized: NodeKind = postcard::from_bytes(&bytes).unwrap();
412            assert_eq!(kind, deserialized);
413        }
414    }
415
416    #[test]
417    fn test_json_format() {
418        // Verify snake_case serialization
419        assert_eq!(
420            serde_json::to_string(&NodeKind::Function).unwrap(),
421            "\"function\""
422        );
423        assert_eq!(
424            serde_json::to_string(&NodeKind::CallSite).unwrap(),
425            "\"call_site\""
426        );
427        assert_eq!(
428            serde_json::to_string(&NodeKind::EnumVariant).unwrap(),
429            "\"enum_variant\""
430        );
431    }
432
433    #[test]
434    fn test_hash() {
435        use std::collections::HashSet;
436
437        let mut set = HashSet::new();
438        set.insert(NodeKind::Function);
439        set.insert(NodeKind::Method);
440        set.insert(NodeKind::Class);
441
442        assert!(set.contains(&NodeKind::Function));
443        assert!(!set.contains(&NodeKind::Variable));
444        assert_eq!(set.len(), 3);
445    }
446
447    #[test]
448    #[allow(clippy::clone_on_copy)] // Intentionally testing Clone trait
449    fn test_copy_clone() {
450        let kind = NodeKind::Function;
451        let copied = kind;
452        let cloned = kind.clone();
453
454        assert_eq!(kind, copied);
455        assert_eq!(kind, cloned);
456    }
457}