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