hotspots_core/language/function_body.rs
1//! Language-agnostic function body representation
2
3use swc_ecma_ast::BlockStmt;
4
5/// Language-agnostic function body
6///
7/// This enum wraps language-specific AST representations of function bodies,
8/// allowing the rest of the codebase to work with different languages uniformly.
9#[derive(Debug, Clone)]
10pub enum FunctionBody {
11 /// ECMAScript (TypeScript/JavaScript) function body
12 ///
13 /// Wraps SWC's BlockStmt which represents the body of a function,
14 /// method, arrow function, etc.
15 ECMAScript(BlockStmt),
16
17 /// Go function body
18 ///
19 /// Contains the tree-sitter node ID for the block and the source code.
20 /// We store the source because tree-sitter nodes are tied to the tree lifetime.
21 Go {
22 /// The tree-sitter node ID for the function body block
23 body_node: usize,
24 /// The source code (needed to reconstruct the tree)
25 source: String,
26 },
27
28 /// Java function body
29 ///
30 /// Contains the tree-sitter node ID for the block and the source code.
31 /// We store the source because tree-sitter nodes are tied to the tree lifetime.
32 Java {
33 /// The tree-sitter node ID for the function body block
34 body_node: usize,
35 /// The source code (needed to reconstruct the tree)
36 source: String,
37 },
38
39 /// Python function body
40 ///
41 /// Contains the tree-sitter node ID for the block and the source code.
42 /// We store the source because tree-sitter nodes are tied to the tree lifetime.
43 Python {
44 /// The tree-sitter node ID for the function body block
45 body_node: usize,
46 /// The source code (needed to reconstruct the tree)
47 source: String,
48 },
49
50 /// Rust function body
51 ///
52 /// Contains the source code for the function body.
53 /// We re-parse on demand using syn when building CFG or extracting metrics.
54 Rust {
55 /// The source code for the entire function (signature + body)
56 source: String,
57 },
58
59 /// C# function body
60 ///
61 /// Contains the tree-sitter node ID for the block and the source code.
62 CSharp {
63 /// The tree-sitter node ID for the function body block
64 body_node: usize,
65 /// The source code (needed to reconstruct the tree)
66 source: String,
67 },
68
69 /// C function body
70 ///
71 /// Contains the tree-sitter node ID for the compound_statement and the source code.
72 C {
73 /// The tree-sitter node ID for the function body compound_statement
74 body_node: usize,
75 /// The source code (needed to reconstruct the tree)
76 source: String,
77 },
78}
79
80impl FunctionBody {
81 /// Create an ECMAScript function body
82 pub fn ecmascript(block: BlockStmt) -> Self {
83 FunctionBody::ECMAScript(block)
84 }
85
86 /// Check if this is an ECMAScript function body
87 pub fn is_ecmascript(&self) -> bool {
88 matches!(self, FunctionBody::ECMAScript(_))
89 }
90
91 /// Check if this is a Go function body
92 pub fn is_go(&self) -> bool {
93 matches!(self, FunctionBody::Go { .. })
94 }
95
96 /// Check if this is a Java function body
97 pub fn is_java(&self) -> bool {
98 matches!(self, FunctionBody::Java { .. })
99 }
100
101 /// Check if this is a Python function body
102 pub fn is_python(&self) -> bool {
103 matches!(self, FunctionBody::Python { .. })
104 }
105
106 /// Check if this is a Rust function body
107 pub fn is_rust(&self) -> bool {
108 matches!(self, FunctionBody::Rust { .. })
109 }
110
111 /// Check if this is a C# function body
112 pub fn is_csharp(&self) -> bool {
113 matches!(self, FunctionBody::CSharp { .. })
114 }
115
116 /// Check if this is a C function body
117 pub fn is_c(&self) -> bool {
118 matches!(self, FunctionBody::C { .. })
119 }
120
121 /// Get the ECMAScript body, if this is one
122 ///
123 /// # Panics
124 ///
125 /// Panics if this is not an ECMAScript body. Use `is_ecmascript()` to check first,
126 /// or use pattern matching instead.
127 pub fn as_ecmascript(&self) -> &BlockStmt {
128 match self {
129 FunctionBody::ECMAScript(block) => block,
130 _ => panic!("FunctionBody is not ECMAScript"),
131 }
132 }
133
134 /// Get a mutable reference to the ECMAScript body, if this is one
135 ///
136 /// # Panics
137 ///
138 /// Panics if this is not an ECMAScript body.
139 pub fn as_ecmascript_mut(&mut self) -> &mut BlockStmt {
140 match self {
141 FunctionBody::ECMAScript(block) => block,
142 _ => panic!("FunctionBody is not ECMAScript"),
143 }
144 }
145
146 /// Get the Go body node ID and source, if this is a Go function
147 ///
148 /// # Panics
149 ///
150 /// Panics if this is not a Go body. Use `is_go()` to check first.
151 pub fn as_go(&self) -> (usize, &str) {
152 match self {
153 FunctionBody::Go { body_node, source } => (*body_node, source.as_str()),
154 _ => panic!("FunctionBody is not Go"),
155 }
156 }
157
158 /// Get the Java body node ID and source, if this is a Java function
159 ///
160 /// # Panics
161 ///
162 /// Panics if this is not a Java body. Use `is_java()` to check first.
163 pub fn as_java(&self) -> (usize, &str) {
164 match self {
165 FunctionBody::Java { body_node, source } => (*body_node, source.as_str()),
166 _ => panic!("FunctionBody is not Java"),
167 }
168 }
169
170 /// Get the Python body node ID and source, if this is a Python function
171 ///
172 /// # Panics
173 ///
174 /// Panics if this is not a Python body. Use `is_python()` to check first.
175 pub fn as_python(&self) -> (usize, &str) {
176 match self {
177 FunctionBody::Python { body_node, source } => (*body_node, source.as_str()),
178 _ => panic!("FunctionBody is not Python"),
179 }
180 }
181
182 /// Get the Rust source, if this is a Rust function
183 ///
184 /// # Panics
185 ///
186 /// Panics if this is not a Rust body. Use `is_rust()` to check first.
187 pub fn as_rust(&self) -> &str {
188 match self {
189 FunctionBody::Rust { source } => source.as_str(),
190 _ => panic!("FunctionBody is not Rust"),
191 }
192 }
193
194 /// Get the C# body node ID and source, if this is a C# function
195 ///
196 /// # Panics
197 ///
198 /// Panics if this is not a C# body. Use `is_csharp()` to check first.
199 pub fn as_csharp(&self) -> (usize, &str) {
200 match self {
201 FunctionBody::CSharp { body_node, source } => (*body_node, source.as_str()),
202 _ => panic!("FunctionBody is not CSharp"),
203 }
204 }
205
206 /// Get the C body node ID and source, if this is a C function
207 ///
208 /// # Panics
209 ///
210 /// Panics if this is not a C body. Use `is_c()` to check first.
211 pub fn as_c(&self) -> (usize, &str) {
212 match self {
213 FunctionBody::C { body_node, source } => (*body_node, source.as_str()),
214 _ => panic!("FunctionBody is not C"),
215 }
216 }
217}
218
219// Implement From for easy conversion
220impl From<BlockStmt> for FunctionBody {
221 fn from(block: BlockStmt) -> Self {
222 FunctionBody::ECMAScript(block)
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use swc_common::DUMMY_SP;
230
231 fn make_test_block() -> BlockStmt {
232 BlockStmt {
233 span: DUMMY_SP,
234 ctxt: Default::default(),
235 stmts: vec![],
236 }
237 }
238
239 #[test]
240 fn test_create_ecmascript() {
241 let block = make_test_block();
242 let body = FunctionBody::ecmascript(block.clone());
243 assert!(body.is_ecmascript());
244 }
245
246 #[test]
247 fn test_from_block_stmt() {
248 let block = make_test_block();
249 let body: FunctionBody = block.clone().into();
250 assert!(body.is_ecmascript());
251 }
252
253 #[test]
254 fn test_as_ecmascript() {
255 let block = make_test_block();
256 let body = FunctionBody::ecmascript(block.clone());
257 let retrieved = body.as_ecmascript();
258 assert_eq!(retrieved.stmts.len(), 0);
259 }
260
261 #[test]
262 fn test_as_ecmascript_mut() {
263 let block = make_test_block();
264 let mut body = FunctionBody::ecmascript(block);
265 let retrieved = body.as_ecmascript_mut();
266 assert_eq!(retrieved.stmts.len(), 0);
267 }
268
269 #[test]
270 #[should_panic(expected = "FunctionBody is not ECMAScript")]
271 fn test_as_ecmascript_panics_on_wrong_type() {
272 // This test will be relevant when we add other language variants
273 // For now, it's not possible to create a non-ECMAScript body
274 // so we can't test the panic path yet
275 let block = make_test_block();
276 let body = FunctionBody::ecmascript(block);
277 let _ = body.as_ecmascript(); // This won't panic
278
279 // Force a panic to make the test pass for now
280 // When we add Go/Rust variants, replace this with:
281 // let body = FunctionBody::Go(...);
282 // let _ = body.as_ecmascript();
283 panic!("FunctionBody is not ECMAScript");
284 }
285}