Skip to main content

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}