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    // ==================== Go Concurrency (T2.4) ====================
149    /// A statically-resolvable channel symbol (Go).
150    ///
151    /// Represents the alias-class of channel values traced by the
152    /// sqry-lang-go Phase 1 alias resolver (see
153    /// `docs/development/go-channels-and-generic-instantiation/02_DESIGN.md` §4.1).
154    /// Each `Channel` node is the *target* of one or more `ChannelPeer` edges
155    /// emitted from send / receive / close operation sites. There is one
156    /// `Channel` node per alias-class, not one per operation.
157    ///
158    /// Qualified-name format:
159    ///   * named-local:  `{package}.{containing_function}.{var_name}`
160    ///   * parameter:    `{package}.{function}.{param_name}`
161    ///   * struct field: `{package}.{struct_name}.{field_name}`
162    ///
163    /// Inserted immediately before `Other` (which carries `#[serde(other)]`
164    /// and must remain the last variant). This shifts `Other`'s positional
165    /// postcard discriminant, which is why the snapshot format bumps V13 to
166    /// V14 with a frozen `NodeKindV13` wire mirror (see persistence §6.1).
167    Channel,
168
169    // ==================== Extensibility ====================
170    /// A custom or language-specific node kind.
171    ///
172    /// Used for plugin-defined node types that don't fit other categories.
173    /// The string identifier provides semantic meaning.
174    #[serde(other)]
175    Other,
176}
177
178impl NodeKind {
179    /// Returns `true` if this is a callable entity (function, method).
180    #[inline]
181    #[must_use]
182    pub const fn is_callable(self) -> bool {
183        matches!(self, Self::Function | Self::Method | Self::Macro)
184    }
185
186    /// Returns `true` if this is a type definition.
187    #[inline]
188    #[must_use]
189    pub const fn is_type_definition(self) -> bool {
190        matches!(
191            self,
192            Self::Class | Self::Interface | Self::Trait | Self::Struct | Self::Enum | Self::Type
193        )
194    }
195
196    /// Returns `true` if this is a container that can have members.
197    #[inline]
198    #[must_use]
199    pub const fn is_container(self) -> bool {
200        matches!(
201            self,
202            Self::Class
203                | Self::Interface
204                | Self::Trait
205                | Self::Struct
206                | Self::Module
207                | Self::Enum
208                | Self::StyleRule
209                | Self::StyleAtRule
210                | Self::JavaModule
211        )
212    }
213
214    /// Returns `true` if this represents an import/export boundary.
215    #[inline]
216    #[must_use]
217    pub const fn is_boundary(self) -> bool {
218        matches!(self, Self::Import | Self::Export)
219    }
220
221    /// Returns `true` if this is a Rust lifetime pseudo-symbol.
222    #[inline]
223    #[must_use]
224    pub const fn is_lifetime(self) -> bool {
225        matches!(self, Self::Lifetime)
226    }
227
228    /// Returns the canonical string representation for serialization.
229    #[must_use]
230    pub const fn as_str(self) -> &'static str {
231        match self {
232            Self::Function => "function",
233            Self::Method => "method",
234            Self::Class => "class",
235            Self::Interface => "interface",
236            Self::Trait => "trait",
237            Self::Module => "module",
238            Self::Variable => "variable",
239            Self::Constant => "constant",
240            Self::Type => "type",
241            Self::Struct => "struct",
242            Self::Enum => "enum",
243            Self::EnumVariant => "enum_variant",
244            Self::Macro => "macro",
245            Self::Parameter => "parameter",
246            Self::Property => "property",
247            Self::CallSite => "call_site",
248            Self::Import => "import",
249            Self::Export => "export",
250            Self::StyleRule => "style_rule",
251            Self::StyleAtRule => "style_at_rule",
252            Self::StyleVariable => "style_variable",
253            Self::Lifetime => "lifetime",
254            Self::Component => "component",
255            Self::Service => "service",
256            Self::Resource => "resource",
257            Self::Endpoint => "endpoint",
258            Self::Test => "test",
259            Self::TypeParameter => "type_parameter",
260            Self::Annotation => "annotation",
261            Self::AnnotationValue => "annotation_value",
262            Self::LambdaTarget => "lambda_target",
263            Self::JavaModule => "java_module",
264            Self::EnumConstant => "enum_constant",
265            Self::Channel => "channel",
266            Self::Other => "other",
267        }
268    }
269
270    /// Parses a string into a `NodeKind`.
271    ///
272    /// Returns `None` if the string doesn't match any known kind.
273    #[must_use]
274    pub fn parse(s: &str) -> Option<Self> {
275        match s {
276            "function" => Some(Self::Function),
277            "method" => Some(Self::Method),
278            "class" => Some(Self::Class),
279            "interface" => Some(Self::Interface),
280            "trait" => Some(Self::Trait),
281            "module" => Some(Self::Module),
282            "variable" => Some(Self::Variable),
283            "constant" => Some(Self::Constant),
284            "type" => Some(Self::Type),
285            "struct" => Some(Self::Struct),
286            "enum" => Some(Self::Enum),
287            "enum_variant" => Some(Self::EnumVariant),
288            "macro" => Some(Self::Macro),
289            "parameter" => Some(Self::Parameter),
290            "property" => Some(Self::Property),
291            "call_site" => Some(Self::CallSite),
292            "import" => Some(Self::Import),
293            "export" => Some(Self::Export),
294            "style_rule" => Some(Self::StyleRule),
295            "style_at_rule" => Some(Self::StyleAtRule),
296            "style_variable" => Some(Self::StyleVariable),
297            "lifetime" => Some(Self::Lifetime),
298            "component" => Some(Self::Component),
299            "service" => Some(Self::Service),
300            "resource" => Some(Self::Resource),
301            "endpoint" => Some(Self::Endpoint),
302            "test" => Some(Self::Test),
303            "type_parameter" => Some(Self::TypeParameter),
304            "annotation" => Some(Self::Annotation),
305            "annotation_value" => Some(Self::AnnotationValue),
306            "lambda_target" => Some(Self::LambdaTarget),
307            "java_module" => Some(Self::JavaModule),
308            "enum_constant" => Some(Self::EnumConstant),
309            "channel" => Some(Self::Channel),
310            "other" => Some(Self::Other),
311            _ => None,
312        }
313    }
314}
315
316impl fmt::Display for NodeKind {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        f.write_str(self.as_str())
319    }
320}
321
322impl Default for NodeKind {
323    /// Returns `NodeKind::Function` as the default (most common node type).
324    fn default() -> Self {
325        Self::Function
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    #[test]
334    fn test_node_kind_as_str() {
335        assert_eq!(NodeKind::Function.as_str(), "function");
336        assert_eq!(NodeKind::Method.as_str(), "method");
337        assert_eq!(NodeKind::Class.as_str(), "class");
338        assert_eq!(NodeKind::CallSite.as_str(), "call_site");
339        assert_eq!(NodeKind::EnumVariant.as_str(), "enum_variant");
340    }
341
342    #[test]
343    fn test_node_kind_parse() {
344        assert_eq!(NodeKind::parse("function"), Some(NodeKind::Function));
345        assert_eq!(NodeKind::parse("call_site"), Some(NodeKind::CallSite));
346        assert_eq!(NodeKind::parse("unknown"), None);
347    }
348
349    #[test]
350    fn test_node_kind_display() {
351        assert_eq!(format!("{}", NodeKind::Function), "function");
352        assert_eq!(format!("{}", NodeKind::EnumVariant), "enum_variant");
353    }
354
355    #[test]
356    fn test_is_callable() {
357        assert!(NodeKind::Function.is_callable());
358        assert!(NodeKind::Method.is_callable());
359        assert!(NodeKind::Macro.is_callable());
360        assert!(!NodeKind::Class.is_callable());
361        assert!(!NodeKind::Variable.is_callable());
362    }
363
364    #[test]
365    fn test_is_type_definition() {
366        assert!(NodeKind::Class.is_type_definition());
367        assert!(NodeKind::Interface.is_type_definition());
368        assert!(NodeKind::Trait.is_type_definition());
369        assert!(NodeKind::Struct.is_type_definition());
370        assert!(NodeKind::Enum.is_type_definition());
371        assert!(NodeKind::Type.is_type_definition());
372        assert!(!NodeKind::Function.is_type_definition());
373        assert!(!NodeKind::Variable.is_type_definition());
374    }
375
376    #[test]
377    fn test_is_container() {
378        assert!(NodeKind::Class.is_container());
379        assert!(NodeKind::Module.is_container());
380        assert!(NodeKind::Struct.is_container());
381        assert!(!NodeKind::Function.is_container());
382        assert!(!NodeKind::Variable.is_container());
383    }
384
385    #[test]
386    fn test_is_boundary() {
387        assert!(NodeKind::Import.is_boundary());
388        assert!(NodeKind::Export.is_boundary());
389        assert!(!NodeKind::Function.is_boundary());
390    }
391
392    #[test]
393    fn test_is_lifetime() {
394        assert!(NodeKind::Lifetime.is_lifetime());
395        assert!(!NodeKind::Function.is_lifetime());
396        assert!(!NodeKind::Variable.is_lifetime());
397    }
398
399    #[test]
400    fn test_lifetime_serialization() {
401        assert_eq!(NodeKind::Lifetime.as_str(), "lifetime");
402        assert_eq!(NodeKind::parse("lifetime"), Some(NodeKind::Lifetime));
403
404        // JSON roundtrip
405        let json = serde_json::to_string(&NodeKind::Lifetime).unwrap();
406        assert_eq!(json, "\"lifetime\"");
407        let deserialized: NodeKind = serde_json::from_str(&json).unwrap();
408        assert_eq!(deserialized, NodeKind::Lifetime);
409    }
410
411    #[test]
412    fn test_default() {
413        assert_eq!(NodeKind::default(), NodeKind::Function);
414    }
415
416    #[test]
417    fn test_serde_roundtrip() {
418        let kinds = [
419            NodeKind::Function,
420            NodeKind::Method,
421            NodeKind::Class,
422            NodeKind::CallSite,
423            NodeKind::EnumVariant,
424        ];
425
426        for kind in kinds {
427            // JSON roundtrip
428            let json = serde_json::to_string(&kind).unwrap();
429            let deserialized: NodeKind = serde_json::from_str(&json).unwrap();
430            assert_eq!(kind, deserialized);
431
432            // Postcard roundtrip
433            let bytes = postcard::to_allocvec(&kind).unwrap();
434            let deserialized: NodeKind = postcard::from_bytes(&bytes).unwrap();
435            assert_eq!(kind, deserialized);
436        }
437    }
438
439    #[test]
440    fn test_json_format() {
441        // Verify snake_case serialization
442        assert_eq!(
443            serde_json::to_string(&NodeKind::Function).unwrap(),
444            "\"function\""
445        );
446        assert_eq!(
447            serde_json::to_string(&NodeKind::CallSite).unwrap(),
448            "\"call_site\""
449        );
450        assert_eq!(
451            serde_json::to_string(&NodeKind::EnumVariant).unwrap(),
452            "\"enum_variant\""
453        );
454    }
455
456    #[test]
457    fn test_hash() {
458        use std::collections::HashSet;
459
460        let mut set = HashSet::new();
461        set.insert(NodeKind::Function);
462        set.insert(NodeKind::Method);
463        set.insert(NodeKind::Class);
464
465        assert!(set.contains(&NodeKind::Function));
466        assert!(!set.contains(&NodeKind::Variable));
467        assert_eq!(set.len(), 3);
468    }
469
470    #[test]
471    #[allow(clippy::clone_on_copy)] // Intentionally testing Clone trait
472    fn test_copy_clone() {
473        let kind = NodeKind::Function;
474        let copied = kind;
475        let cloned = kind.clone();
476
477        assert_eq!(kind, copied);
478        assert_eq!(kind, cloned);
479    }
480}