1#![warn(missing_docs)]
2
3use nargo_types::{Result, Span};
8use serde::{Deserialize, Serialize};
9
10use crate::expr::JsExpr;
11use crate::types::{MAX_ARRAY_LENGTH, MAX_RECURSION_DEPTH, MAX_STRING_LENGTH, IRError, Trivia};
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub enum JsStmt {
16 Expr(JsExpr, #[serde(default)] Span, #[serde(default)] Trivia),
18 VariableDecl {
20 kind: String,
22 id: String,
24 init: Option<JsExpr>,
26 #[serde(default)]
28 span: Span,
29 #[serde(default)]
31 trivia: Trivia,
32 },
33 Import {
35 source: String,
37 specifiers: Vec<String>,
39 #[serde(default)]
41 span: Span,
42 #[serde(default)]
44 trivia: Trivia,
45 },
46 Export {
48 declaration: Box<JsStmt>,
50 #[serde(default)]
52 span: Span,
53 #[serde(default)]
55 trivia: Trivia,
56 },
57 ExportAll {
59 source: String,
61 #[serde(default)]
63 span: Span,
64 #[serde(default)]
66 trivia: Trivia,
67 },
68 ExportNamed {
70 source: Option<String>,
72 specifiers: Vec<String>,
74 #[serde(default)]
76 span: Span,
77 #[serde(default)]
79 trivia: Trivia,
80 },
81 FunctionDecl {
83 id: String,
85 params: Vec<String>,
87 body: Vec<JsStmt>,
89 #[serde(default)]
91 is_async: bool,
92 #[serde(default)]
94 span: Span,
95 #[serde(default)]
97 trivia: Trivia,
98 },
99 Return(Option<JsExpr>, #[serde(default)] Span, #[serde(default)] Trivia),
101 If {
103 test: JsExpr,
105 consequent: Box<JsStmt>,
107 alternate: Option<Box<JsStmt>>,
109 #[serde(default)]
111 span: Span,
112 #[serde(default)]
114 trivia: Trivia,
115 },
116 While {
118 test: JsExpr,
120 body: Box<JsStmt>,
122 #[serde(default)]
124 span: Span,
125 #[serde(default)]
127 trivia: Trivia,
128 },
129 For {
131 init: Option<Box<JsStmt>>,
133 test: Option<JsExpr>,
135 update: Option<JsExpr>,
137 body: Box<JsStmt>,
139 #[serde(default)]
141 span: Span,
142 #[serde(default)]
144 trivia: Trivia,
145 },
146 ForIn {
148 left: Box<JsStmt>,
150 right: JsExpr,
152 body: Box<JsStmt>,
154 #[serde(default)]
156 span: Span,
157 #[serde(default)]
159 trivia: Trivia,
160 },
161 ForOf {
163 left: Box<JsStmt>,
165 right: JsExpr,
167 body: Box<JsStmt>,
169 #[serde(default)]
171 span: Span,
172 #[serde(default)]
174 trivia: Trivia,
175 },
176 Try {
178 block: Box<JsStmt>,
180 handler: Option<(String, Box<JsStmt>)>,
182 finalizer: Option<Box<JsStmt>>,
184 #[serde(default)]
186 span: Span,
187 #[serde(default)]
189 trivia: Trivia,
190 },
191 Switch {
193 discriminant: JsExpr,
195 cases: Vec<(Option<JsExpr>, Vec<JsStmt>)>,
197 #[serde(default)]
199 span: Span,
200 #[serde(default)]
202 trivia: Trivia,
203 },
204 Throw(JsExpr, #[serde(default)] Span, #[serde(default)] Trivia),
206 Block(Vec<JsStmt>, #[serde(default)] Span, #[serde(default)] Trivia),
208 Break(#[serde(default)] Span, #[serde(default)] Trivia),
210 Continue(#[serde(default)] Span, #[serde(default)] Trivia),
212 Other(String, #[serde(default)] Span, #[serde(default)] Trivia),
214}
215
216impl JsStmt {
217 pub fn span(&self) -> Span {
219 match self {
220 JsStmt::Expr(_, span, _) => *span,
221 JsStmt::VariableDecl { span, .. } => *span,
222 JsStmt::Import { span, .. } => *span,
223 JsStmt::Export { span, .. } => *span,
224 JsStmt::ExportAll { span, .. } => *span,
225 JsStmt::ExportNamed { span, .. } => *span,
226 JsStmt::FunctionDecl { span, .. } => *span,
227 JsStmt::Return(_, span, _) => *span,
228 JsStmt::If { span, .. } => *span,
229 JsStmt::While { span, .. } => *span,
230 JsStmt::For { span, .. } => *span,
231 JsStmt::ForIn { span, .. } => *span,
232 JsStmt::ForOf { span, .. } => *span,
233 JsStmt::Try { span, .. } => *span,
234 JsStmt::Switch { span, .. } => *span,
235 JsStmt::Throw(_, span, _) => *span,
236 JsStmt::Block(_, span, _) => *span,
237 JsStmt::Break(span, _) => *span,
238 JsStmt::Continue(span, _) => *span,
239 JsStmt::Other(_, span, _) => *span,
240 }
241 }
242
243 pub fn trivia(&self) -> &Trivia {
245 match self {
246 JsStmt::Expr(_, _, t) => t,
247 JsStmt::VariableDecl { trivia, .. } => trivia,
248 JsStmt::Import { trivia, .. } => trivia,
249 JsStmt::Export { trivia, .. } => trivia,
250 JsStmt::ExportAll { trivia, .. } => trivia,
251 JsStmt::ExportNamed { trivia, .. } => trivia,
252 JsStmt::FunctionDecl { trivia, .. } => trivia,
253 JsStmt::Return(_, _, t) => t,
254 JsStmt::If { trivia, .. } => trivia,
255 JsStmt::While { trivia, .. } => trivia,
256 JsStmt::For { trivia, .. } => trivia,
257 JsStmt::ForIn { trivia, .. } => trivia,
258 JsStmt::ForOf { trivia, .. } => trivia,
259 JsStmt::Try { trivia, .. } => trivia,
260 JsStmt::Switch { trivia, .. } => trivia,
261 JsStmt::Throw(_, _, t) => t,
262 JsStmt::Block(_, _, t) => t,
263 JsStmt::Break(_, t) => t,
264 JsStmt::Continue(_, t) => t,
265 JsStmt::Other(_, _, t) => t,
266 }
267 }
268
269 pub fn validate(&self, depth: usize) -> Result<()> {
271 if depth > MAX_RECURSION_DEPTH {
272 return Err(IRError::CircularReference("Statement recursion depth exceeded".to_string()).into());
273 }
274
275 match self {
276 JsStmt::Expr(expr, _, _) => expr.validate(depth + 1),
277 JsStmt::VariableDecl { id, init, .. } => {
278 if id.len() > MAX_STRING_LENGTH {
279 return Err(IRError::SizeLimitExceeded("Variable name length exceeded".to_string()).into());
280 }
281 if let Some(init_expr) = init {
282 init_expr.validate(depth + 1)?;
283 }
284 Ok(())
285 }
286 JsStmt::Import { source, specifiers, .. } => {
287 if source.len() > MAX_STRING_LENGTH {
288 return Err(IRError::SizeLimitExceeded("Import source length exceeded".to_string()).into());
289 }
290 if specifiers.len() > MAX_ARRAY_LENGTH {
291 return Err(IRError::SizeLimitExceeded("Import specifiers length exceeded".to_string()).into());
292 }
293 for specifier in specifiers {
294 if specifier.len() > MAX_STRING_LENGTH {
295 return Err(IRError::SizeLimitExceeded("Import specifier length exceeded".to_string()).into());
296 }
297 }
298 Ok(())
299 }
300 JsStmt::Export { declaration, .. } => declaration.validate(depth + 1),
301 JsStmt::ExportAll { source, .. } => {
302 if source.len() > MAX_STRING_LENGTH {
303 return Err(IRError::SizeLimitExceeded("Export all source length exceeded".to_string()).into());
304 }
305 Ok(())
306 }
307 JsStmt::ExportNamed { source, specifiers, .. } => {
308 if let Some(source_str) = source {
309 if source_str.len() > MAX_STRING_LENGTH {
310 return Err(IRError::SizeLimitExceeded("Export named source length exceeded".to_string()).into());
311 }
312 }
313 if specifiers.len() > MAX_ARRAY_LENGTH {
314 return Err(IRError::SizeLimitExceeded("Export named specifiers length exceeded".to_string()).into());
315 }
316 for specifier in specifiers {
317 if specifier.len() > MAX_STRING_LENGTH {
318 return Err(IRError::SizeLimitExceeded("Export specifier length exceeded".to_string()).into());
319 }
320 }
321 Ok(())
322 }
323 JsStmt::FunctionDecl { id, params, body, .. } => {
324 if id.len() > MAX_STRING_LENGTH {
325 return Err(IRError::SizeLimitExceeded("Function name length exceeded".to_string()).into());
326 }
327 if params.len() > MAX_ARRAY_LENGTH {
328 return Err(IRError::SizeLimitExceeded("Function parameters length exceeded".to_string()).into());
329 }
330 for param in params {
331 if param.len() > MAX_STRING_LENGTH {
332 return Err(IRError::SizeLimitExceeded("Parameter name length exceeded".to_string()).into());
333 }
334 }
335 if body.len() > MAX_ARRAY_LENGTH {
336 return Err(IRError::SizeLimitExceeded("Function body length exceeded".to_string()).into());
337 }
338 for stmt in body {
339 stmt.validate(depth + 1)?;
340 }
341 Ok(())
342 }
343 JsStmt::Return(expr, _, _) => {
344 if let Some(expr) = expr {
345 expr.validate(depth + 1)?;
346 }
347 Ok(())
348 }
349 JsStmt::If { test, consequent, alternate, .. } => {
350 test.validate(depth + 1)?;
351 consequent.validate(depth + 1)?;
352 if let Some(alt) = alternate {
353 alt.validate(depth + 1)?;
354 }
355 Ok(())
356 }
357 JsStmt::While { test, body, .. } => {
358 test.validate(depth + 1)?;
359 body.validate(depth + 1)
360 }
361 JsStmt::For { init, test, update, body, .. } => {
362 if let Some(init_stmt) = init {
363 init_stmt.validate(depth + 1)?;
364 }
365 if let Some(test_expr) = test {
366 test_expr.validate(depth + 1)?;
367 }
368 if let Some(update_expr) = update {
369 update_expr.validate(depth + 1)?;
370 }
371 body.validate(depth + 1)
372 }
373 JsStmt::ForIn { left, right, body, .. } => {
374 left.validate(depth + 1)?;
375 right.validate(depth + 1)?;
376 body.validate(depth + 1)
377 }
378 JsStmt::ForOf { left, right, body, .. } => {
379 left.validate(depth + 1)?;
380 right.validate(depth + 1)?;
381 body.validate(depth + 1)
382 }
383 JsStmt::Try { block, handler, finalizer, .. } => {
384 block.validate(depth + 1)?;
385 if let Some((catch_id, catch_body)) = handler {
386 if catch_id.len() > MAX_STRING_LENGTH {
387 return Err(IRError::SizeLimitExceeded("Catch parameter name length exceeded".to_string()).into());
388 }
389 catch_body.validate(depth + 1)?;
390 }
391 if let Some(finally_body) = finalizer {
392 finally_body.validate(depth + 1)?;
393 }
394 Ok(())
395 }
396 JsStmt::Switch { discriminant, cases, .. } => {
397 discriminant.validate(depth + 1)?;
398 if cases.len() > MAX_ARRAY_LENGTH {
399 return Err(IRError::SizeLimitExceeded("Switch cases length exceeded".to_string()).into());
400 }
401 for (test, stmts) in cases {
402 if let Some(test_expr) = test {
403 test_expr.validate(depth + 1)?;
404 }
405 if stmts.len() > MAX_ARRAY_LENGTH {
406 return Err(IRError::SizeLimitExceeded("Switch case statements length exceeded".to_string()).into());
407 }
408 for stmt in stmts {
409 stmt.validate(depth + 1)?;
410 }
411 }
412 Ok(())
413 }
414 JsStmt::Throw(expr, _, _) => expr.validate(depth + 1),
415 JsStmt::Block(stmts, _, _) => {
416 if stmts.len() > MAX_ARRAY_LENGTH {
417 return Err(IRError::SizeLimitExceeded("Block statements length exceeded".to_string()).into());
418 }
419 for stmt in stmts {
420 stmt.validate(depth + 1)?;
421 }
422 Ok(())
423 }
424 JsStmt::Break(_, _) => Ok(()),
425 JsStmt::Continue(_, _) => Ok(()),
426 JsStmt::Other(code, _, _) => {
427 if code.len() > MAX_STRING_LENGTH {
428 return Err(IRError::SizeLimitExceeded("Other statement code length exceeded".to_string()).into());
429 }
430 Ok(())
431 }
432 }
433 }
434
435 pub fn optimize(&mut self) {
437 crate::optimizer::StmtOptimizer::optimize(self);
438 }
439
440 pub fn is_empty(&self) -> bool {
442 match self {
443 JsStmt::Block(stmts, _, _) => stmts.is_empty(),
444 _ => false,
445 }
446 }
447}
448
449pub trait JsStmtVisitor<R> {
451 fn visit_expr(&mut self, expr: &JsExpr, span: &Span, trivia: &Trivia) -> R;
453 fn visit_variable_decl(&mut self, kind: &String, id: &String, init: &Option<JsExpr>, span: &Span, trivia: &Trivia) -> R;
455 fn visit_import(&mut self, source: &String, specifiers: &Vec<String>, span: &Span, trivia: &Trivia) -> R;
457 fn visit_export(&mut self, declaration: &JsStmt, span: &Span, trivia: &Trivia) -> R;
459 fn visit_export_all(&mut self, source: &String, span: &Span, trivia: &Trivia) -> R;
461 fn visit_export_named(&mut self, source: &Option<String>, specifiers: &Vec<String>, span: &Span, trivia: &Trivia) -> R;
463 fn visit_function_decl(&mut self, id: &String, params: &Vec<String>, body: &Vec<JsStmt>, is_async: bool, span: &Span, trivia: &Trivia) -> R;
465 fn visit_return(&mut self, expr: &Option<JsExpr>, span: &Span, trivia: &Trivia) -> R;
467 fn visit_if(&mut self, test: &JsExpr, consequent: &JsStmt, alternate: &Option<Box<JsStmt>>, span: &Span, trivia: &Trivia) -> R;
469 fn visit_while(&mut self, test: &JsExpr, body: &JsStmt, span: &Span, trivia: &Trivia) -> R;
471 fn visit_for(&mut self, init: &Option<Box<JsStmt>>, test: &Option<JsExpr>, update: &Option<JsExpr>, body: &JsStmt, span: &Span, trivia: &Trivia) -> R;
473 fn visit_for_in(&mut self, left: &JsStmt, right: &JsExpr, body: &JsStmt, span: &Span, trivia: &Trivia) -> R;
475 fn visit_for_of(&mut self, left: &JsStmt, right: &JsExpr, body: &JsStmt, span: &Span, trivia: &Trivia) -> R;
477 fn visit_try(&mut self, block: &JsStmt, handler: &Option<(String, Box<JsStmt>)>, finalizer: &Option<Box<JsStmt>>, span: &Span, trivia: &Trivia) -> R;
479 fn visit_switch(&mut self, discriminant: &JsExpr, cases: &Vec<(Option<JsExpr>, Vec<JsStmt>)>, span: &Span, trivia: &Trivia) -> R;
481 fn visit_throw(&mut self, expr: &JsExpr, span: &Span, trivia: &Trivia) -> R;
483 fn visit_block(&mut self, stmts: &Vec<JsStmt>, span: &Span, trivia: &Trivia) -> R;
485 fn visit_break(&mut self, span: &Span, trivia: &Trivia) -> R;
487 fn visit_continue(&mut self, span: &Span, trivia: &Trivia) -> R;
489 fn visit_other(&mut self, code: &String, span: &Span, trivia: &Trivia) -> R;
491}
492
493impl JsStmt {
494 pub fn accept<R>(&self, visitor: &mut dyn JsStmtVisitor<R>) -> R {
496 match self {
497 JsStmt::Expr(expr, span, trivia) => visitor.visit_expr(expr, span, trivia),
498 JsStmt::VariableDecl { kind, id, init, span, trivia } => visitor.visit_variable_decl(kind, id, init, span, trivia),
499 JsStmt::Import { source, specifiers, span, trivia } => visitor.visit_import(source, specifiers, span, trivia),
500 JsStmt::Export { declaration, span, trivia } => visitor.visit_export(declaration, span, trivia),
501 JsStmt::ExportAll { source, span, trivia } => visitor.visit_export_all(source, span, trivia),
502 JsStmt::ExportNamed { source, specifiers, span, trivia } => visitor.visit_export_named(source, specifiers, span, trivia),
503 JsStmt::FunctionDecl { id, params, body, is_async, span, trivia } => visitor.visit_function_decl(id, params, body, *is_async, span, trivia),
504 JsStmt::Return(expr, span, trivia) => visitor.visit_return(expr, span, trivia),
505 JsStmt::If { test, consequent, alternate, span, trivia } => visitor.visit_if(test, consequent, alternate, span, trivia),
506 JsStmt::While { test, body, span, trivia } => visitor.visit_while(test, body, span, trivia),
507 JsStmt::For { init, test, update, body, span, trivia } => visitor.visit_for(init, test, update, body, span, trivia),
508 JsStmt::ForIn { left, right, body, span, trivia } => visitor.visit_for_in(left, right, body, span, trivia),
509 JsStmt::ForOf { left, right, body, span, trivia } => visitor.visit_for_of(left, right, body, span, trivia),
510 JsStmt::Try { block, handler, finalizer, span, trivia } => visitor.visit_try(block, handler, finalizer, span, trivia),
511 JsStmt::Switch { discriminant, cases, span, trivia } => visitor.visit_switch(discriminant, cases, span, trivia),
512 JsStmt::Throw(expr, span, trivia) => visitor.visit_throw(expr, span, trivia),
513 JsStmt::Block(stmts, span, trivia) => visitor.visit_block(stmts, span, trivia),
514 JsStmt::Break(span, trivia) => visitor.visit_break(span, trivia),
515 JsStmt::Continue(span, trivia) => visitor.visit_continue(span, trivia),
516 JsStmt::Other(code, span, trivia) => visitor.visit_other(code, span, trivia),
517 }
518 }
519}