mago_codex/signature.rs
1use serde::Deserialize;
2use serde::Serialize;
3
4use mago_atom::Atom;
5
6/// Represents a signature node for a definition (function, class, method, constant, etc.).
7///
8/// This structure forms a hierarchical tree where top-level symbols (classes, functions)
9/// can have children (methods, properties within classes).
10///
11/// Unlike Hakana which separates `signature_hash` and `body_hash`, we use a single `hash`
12/// field that covers everything. This is more conservative but simpler, and aligns with
13/// the requirements for taint analysis where any change requires re-analysis.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct DefSignatureNode {
16 /// The name of the symbol (e.g., "Foo" for class Foo, "bar" for method bar)
17 pub name: Atom,
18
19 /// Whether this node represents a function or method
20 pub is_function: bool,
21
22 /// Whether this node represents a constant
23 pub is_constant: bool,
24
25 /// Starting byte offset in the source file
26 pub start_offset: u32,
27
28 /// Ending byte offset in the source file
29 pub end_offset: u32,
30
31 /// Starting line number (1-indexed)
32 pub start_line: u32,
33
34 /// Ending line number (1-indexed)
35 pub end_line: u32,
36
37 /// Starting column (0-indexed)
38 pub start_column: u16,
39
40 /// Ending column (0-indexed)
41 pub end_column: u16,
42
43 /// Nested symbols (e.g., methods and properties within a class)
44 pub children: Vec<DefSignatureNode>,
45
46 /// Position-insensitive fingerprint hash covering the entire definition.
47 /// Any change to signature, body, modifiers, or attributes will change this hash.
48 pub hash: u64,
49}
50
51impl DefSignatureNode {
52 /// Creates a new `DefSignatureNode` with the given parameters.
53 #[inline]
54 #[allow(clippy::too_many_arguments)]
55 #[must_use]
56 pub fn new(
57 name: Atom,
58 is_function: bool,
59 is_constant: bool,
60 start_offset: u32,
61 end_offset: u32,
62 start_line: u32,
63 end_line: u32,
64 start_column: u16,
65 end_column: u16,
66 hash: u64,
67 ) -> Self {
68 Self {
69 name,
70 is_function,
71 is_constant,
72 start_offset,
73 end_offset,
74 start_line,
75 end_line,
76 start_column,
77 end_column,
78 children: Vec::new(),
79 hash,
80 }
81 }
82
83 /// Adds a child node to this definition.
84 #[inline]
85 pub fn add_child(&mut self, child: DefSignatureNode) {
86 self.children.push(child);
87 }
88
89 /// Returns a reference to the children of this node.
90 #[inline]
91 #[must_use]
92 pub fn children(&self) -> &[DefSignatureNode] {
93 &self.children
94 }
95
96 /// Returns a mutable reference to the children of this node.
97 #[inline]
98 pub fn children_mut(&mut self) -> &mut Vec<DefSignatureNode> {
99 &mut self.children
100 }
101}
102
103/// Represents the signature of an entire file.
104///
105/// This contains all top-level definitions (classes, interfaces, traits, enums,
106/// functions, constants) in the file as a flat vector. Nested definitions
107/// (methods, properties) are stored within the `children` of their parent nodes.
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
109pub struct FileSignature {
110 pub hash: u64,
111 pub ast_nodes: Vec<DefSignatureNode>,
112}
113
114impl FileSignature {
115 /// Creates a new empty `FileSignature`.
116 #[inline]
117 #[must_use]
118 pub fn new(hash: u64) -> Self {
119 Self { hash, ast_nodes: Vec::new() }
120 }
121
122 /// Adds a top-level definition node to this file signature.
123 #[inline]
124 pub fn add_node(&mut self, node: DefSignatureNode) {
125 self.ast_nodes.push(node);
126 }
127
128 /// Returns a reference to the top-level nodes.
129 #[inline]
130 #[must_use]
131 pub fn nodes(&self) -> &[DefSignatureNode] {
132 &self.ast_nodes
133 }
134
135 /// Returns a mutable reference to the top-level nodes.
136 #[inline]
137 pub fn nodes_mut(&mut self) -> &mut Vec<DefSignatureNode> {
138 &mut self.ast_nodes
139 }
140
141 /// Returns true if this file signature has no nodes.
142 #[inline]
143 #[must_use]
144 pub fn is_empty(&self) -> bool {
145 self.ast_nodes.is_empty()
146 }
147
148 /// Returns the number of top-level nodes.
149 #[inline]
150 #[must_use]
151 pub fn len(&self) -> usize {
152 self.ast_nodes.len()
153 }
154}