arbor_core/
node.rs

1//! Code node representation.
2//!
3//! A CodeNode is our abstraction over raw AST nodes. It captures
4//! the semantically meaningful parts of code: what it is, where it lives,
5//! and enough metadata to be useful for graph construction.
6
7use serde::{Deserialize, Serialize};
8use std::hash::{Hash, Hasher};
9
10/// The kind of code entity this node represents.
11///
12/// We intentionally keep this list focused on the entities that matter
13/// for understanding code structure. Helper nodes like expressions
14/// or statements are filtered out during extraction.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum NodeKind {
18    /// A standalone function (not attached to a class).
19    Function,
20    /// A method inside a class or impl block.
21    Method,
22    /// A class definition.
23    Class,
24    /// An interface, protocol, or trait.
25    Interface,
26    /// A struct (Rust, Go).
27    Struct,
28    /// An enum definition.
29    Enum,
30    /// A module-level variable.
31    Variable,
32    /// A constant or static value.
33    Constant,
34    /// A type alias.
35    TypeAlias,
36    /// The file/module itself as a container.
37    Module,
38    /// An import statement.
39    Import,
40    /// An export declaration.
41    Export,
42}
43
44impl std::fmt::Display for NodeKind {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        let s = match self {
47            Self::Function => "function",
48            Self::Method => "method",
49            Self::Class => "class",
50            Self::Interface => "interface",
51            Self::Struct => "struct",
52            Self::Enum => "enum",
53            Self::Variable => "variable",
54            Self::Constant => "constant",
55            Self::TypeAlias => "type_alias",
56            Self::Module => "module",
57            Self::Import => "import",
58            Self::Export => "export",
59        };
60        write!(f, "{}", s)
61    }
62}
63
64/// Visibility of a code entity.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
66#[serde(rename_all = "snake_case")]
67pub enum Visibility {
68    #[default]
69    Private,
70    Public,
71    Protected,
72    /// Rust's pub(crate) or similar restricted visibility.
73    Internal,
74}
75
76/// A code entity extracted from source.
77///
78/// This is the core data type that flows through Arbor. It's designed
79/// to be language-agnostic while still capturing the structure we need.
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct CodeNode {
82    /// Unique identifier, derived from file path + qualified name + kind.
83    pub id: String,
84
85    /// The simple name (e.g., "validate_user").
86    pub name: String,
87
88    /// Fully qualified name including parent scope (e.g., "UserService.validate_user").
89    pub qualified_name: String,
90
91    /// What kind of entity this is.
92    pub kind: NodeKind,
93
94    /// Path to the source file, relative to project root.
95    pub file: String,
96
97    /// Starting line (1-indexed, like editors show).
98    pub line_start: u32,
99
100    /// Ending line (inclusive).
101    pub line_end: u32,
102
103    /// Column of the name identifier.
104    pub column: u32,
105
106    /// Function/method signature if applicable.
107    pub signature: Option<String>,
108
109    /// Visibility modifier.
110    pub visibility: Visibility,
111
112    /// Whether this is async.
113    pub is_async: bool,
114
115    /// Whether this is static/class-level.
116    pub is_static: bool,
117
118    /// Whether this is exported (TS/ES modules).
119    pub is_exported: bool,
120
121    /// Docstring or leading comment.
122    pub docstring: Option<String>,
123
124    /// Byte offset range in source for incremental updates.
125    pub byte_start: u32,
126    pub byte_end: u32,
127
128    /// Entities this node references (call targets, type refs, etc).
129    /// These are names, not IDs - resolution happens in the graph crate.
130    pub references: Vec<String>,
131}
132
133impl CodeNode {
134    /// Creates a deterministic ID for this node.
135    ///
136    /// The ID is a hash of (file, qualified_name, kind) so the same
137    /// entity always gets the same ID across parses.
138    pub fn compute_id(file: &str, qualified_name: &str, kind: NodeKind) -> String {
139        use std::collections::hash_map::DefaultHasher;
140
141        let mut hasher = DefaultHasher::new();
142        file.hash(&mut hasher);
143        qualified_name.hash(&mut hasher);
144        kind.hash(&mut hasher);
145
146        format!("{:016x}", hasher.finish())
147    }
148
149    /// Creates a new node and automatically computes its ID.
150    pub fn new(
151        name: impl Into<String>,
152        qualified_name: impl Into<String>,
153        kind: NodeKind,
154        file: impl Into<String>,
155    ) -> Self {
156        let name = name.into();
157        let qualified_name = qualified_name.into();
158        let file = file.into();
159        let id = Self::compute_id(&file, &qualified_name, kind);
160
161        Self {
162            id,
163            name,
164            qualified_name,
165            kind,
166            file,
167            line_start: 0,
168            line_end: 0,
169            column: 0,
170            signature: None,
171            visibility: Visibility::default(),
172            is_async: false,
173            is_static: false,
174            is_exported: false,
175            docstring: None,
176            byte_start: 0,
177            byte_end: 0,
178            references: Vec::new(),
179        }
180    }
181
182    /// Builder pattern: set line range.
183    pub fn with_lines(mut self, start: u32, end: u32) -> Self {
184        self.line_start = start;
185        self.line_end = end;
186        self
187    }
188
189    /// Builder pattern: set byte range.
190    pub fn with_bytes(mut self, start: u32, end: u32) -> Self {
191        self.byte_start = start;
192        self.byte_end = end;
193        self
194    }
195
196    /// Builder pattern: set column.
197    pub fn with_column(mut self, column: u32) -> Self {
198        self.column = column;
199        self
200    }
201
202    /// Builder pattern: set signature.
203    pub fn with_signature(mut self, sig: impl Into<String>) -> Self {
204        self.signature = Some(sig.into());
205        self
206    }
207
208    /// Builder pattern: set visibility.
209    pub fn with_visibility(mut self, vis: Visibility) -> Self {
210        self.visibility = vis;
211        self
212    }
213
214    /// Builder pattern: mark as async.
215    pub fn as_async(mut self) -> Self {
216        self.is_async = true;
217        self
218    }
219
220    /// Builder pattern: mark as static.
221    pub fn as_static(mut self) -> Self {
222        self.is_static = true;
223        self
224    }
225
226    /// Builder pattern: mark as exported.
227    pub fn as_exported(mut self) -> Self {
228        self.is_exported = true;
229        self
230    }
231
232    /// Builder pattern: add references.
233    pub fn with_references(mut self, refs: Vec<String>) -> Self {
234        self.references = refs;
235        self
236    }
237}
238
239impl PartialEq for CodeNode {
240    fn eq(&self, other: &Self) -> bool {
241        self.id == other.id
242    }
243}
244
245impl Eq for CodeNode {}
246
247impl Hash for CodeNode {
248    fn hash<H: Hasher>(&self, state: &mut H) {
249        self.id.hash(state);
250    }
251}