1extern crate swc_ecma_parser;
2
3use crate::language::javascript::parse::ContextPath::Constant;
4use crate::language::{InternalCallGraph, python, TextRange};
5use crate::language::{Report, ReportItem, ReportTriggerableFunctions};
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8use std::env::var;
9use swc_common::sync::Lrc;
10use swc_common::{
11 errors::{ColorConfig, Handler},
12 FileName, FilePathMapping, SourceMap,
13};
14use swc_common::source_map::Pos;
15use swc_ecma_ast as ast;
16use swc_ecma_ast::{BlockStmtOrExpr, Callee, Decl, Expr, FnDecl, ForHead, Ident, ImportSpecifier, Lit, MemberProp, ModuleDecl, ModuleItem, Pat, PatOrExpr, PropName, Stmt};
17use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax};
18use crate::language::ChidoriStaticAnalysisError;
19use crate::language::ContextPath;
20
21fn remove_hash_and_numbers(input: &str) -> String {
22 match input.find('#') {
23 Some(index) => input[..index].to_string(),
24 None => input.to_string(),
25 }
26}
27
28#[derive(Default)]
40pub struct ASTWalkContext {
41 pub context_stack_references: Vec<Vec<ContextPath>>,
42 pub context_stack: Vec<ContextPath>,
43 pub locals: HashSet<String>,
44 pub local_contexts: Vec<HashSet<String>>,
45 pub globals: HashSet<String>,
46}
47
48impl ASTWalkContext {
49 fn new() -> Self {
50 Self {
51 context_stack_references: vec![],
52 context_stack: vec![],
53 locals: HashSet::new(),
54 local_contexts: vec![],
55 globals: HashSet::new(),
56 }
57 }
58
59 fn var_exists(&self, name: &str) -> bool {
60 if self.globals.contains(name) {
61 return true;
62 }
63 if self.locals.contains(name) {
64 return true;
65 }
66 for local_context in self.local_contexts.iter().rev() {
67 if local_context.contains(name) {
68 return true;
69 }
70 }
71 return false;
72 }
73
74 fn new_local_context(&mut self) {
75 self.local_contexts.push(self.locals.clone());
76 }
77
78 fn insert_local(&mut self, ident: &ast::Ident) {
79 let name = remove_hash_and_numbers(&ident.to_string());
80 self.locals.insert(name);
81 }
82
83 fn pop_local_context(&mut self) {
84 self.locals.clear();
86
87 self.locals.extend(
90 self.local_contexts
91 .iter()
92 .flat_map(|context| context.iter().cloned()),
93 );
94
95 self.local_contexts.pop();
96 }
97
98 fn enter_anonymous_function(&mut self) -> usize {
99 self.context_stack.push(ContextPath::InAnonFunction);
100 self.context_stack.len()
103 }
104
105 fn enter_statement_function(&mut self, name: &ast::Ident, range: TextRange) -> usize {
106 let name = remove_hash_and_numbers(&name.to_string());
107 self.context_stack.push(ContextPath::InFunction(name, range));
108 self.context_stack.len()
109 }
110
111 fn enter_params(&mut self) -> usize {
112 self.context_stack.push(ContextPath::FunctionArguments);
113 self.context_stack.len()
114 }
115
116 fn enter_decorator_expression(&mut self, idx: &usize) -> usize {
117 self.context_stack
118 .push(ContextPath::InFunctionDecorator(idx.clone()));
119 self.context_stack.len()
120 }
121
122 fn enter_call_expression(&mut self) -> usize {
123 self.context_stack.push(ContextPath::InCallExpression);
124 self.context_stack.len()
125 }
126
127 fn encounter_constant(&mut self, lit: &ast::Lit) {
128 match lit {
129 Lit::Str(ast::Str { value, .. }) => {
130 self.context_stack
131 .push(ContextPath::Constant(value.to_string()));
132 }
135 Lit::Bool(_) => {}
136 Lit::Null(_) => {}
137 Lit::Num(_) => {}
138 Lit::BigInt(_) => {}
139 Lit::Regex(_) => {}
140 Lit::JSXText(_) => {}
141 }
142 }
143
144 fn encounter_named_reference(&mut self, name: &ast::Ident) {
145 let name = remove_hash_and_numbers(&name.to_string());
146 let mut var_exists = false;
147 if self.globals.contains(&name.to_string()) {
148 var_exists = true;
149 }
150 if self.locals.contains(&name.to_string()) {
151 var_exists = true;
152 }
153 for local_context in self.local_contexts.iter().rev() {
154 if local_context.contains(&name.to_string()) {
155 var_exists = true;
156 }
157 }
158
159 if var_exists {
160 self.context_stack
162 .push(ContextPath::IdentifierReferredTo{
163 name: name.to_string(),
164 in_scope: true,
165 exposed: self.local_contexts.len() == 0
166 });
167 } else {
168 if self.context_stack.contains(&ContextPath::AssignmentToStatement) {
169 self.locals.insert(name.to_string());
170 }
171 if self.context_stack.contains(&ContextPath::FunctionArguments) {
175 self.locals.insert(name.to_string());
176 self.context_stack
177 .push(ContextPath::IdentifierReferredTo{
178 name: name.to_string(),
179 in_scope: false,
180 exposed: self.local_contexts.len() == 0
181 });
182 return;
183 }
184 self.context_stack
185 .push(ContextPath::IdentifierReferredTo{
186 name: name.to_string(),
187 in_scope: false,
188 exposed: self.local_contexts.len() == 0
189 });
190 }
191 }
192
193 fn enter_assignment_to_statement(&mut self) -> usize {
194 self.context_stack.push(ContextPath::AssignmentToStatement);
195 self.context_stack.len()
196 }
197
198 fn enter_assignment_from_statement(&mut self) -> usize {
199 self.context_stack
200 .push(ContextPath::AssignmentFromStatement);
201 self.context_stack.len()
202 }
203
204 fn enter_attr(&mut self, name: &ast::Ident) -> usize {
205 let name = remove_hash_and_numbers(&name.to_string());
206 self.context_stack
207 .push(ContextPath::Attribute(name.to_string()));
208 self.context_stack.len()
209 }
210
211 fn pop_until(&mut self, size: usize) {
212 self.context_stack_references
213 .push(self.context_stack.clone());
214 while self.context_stack.len() >= size {
215 self.context_stack.pop();
216 }
217 }
218}
219
220fn traverse_module(module: ModuleItem, machine: &mut ASTWalkContext) {
221 match module {
222 ModuleItem::ModuleDecl(mod_decl) => match mod_decl {
223 ModuleDecl::Import(ast::ImportDecl { specifiers, .. }) => {
224 for specifier in specifiers {
225 match specifier {
226 ImportSpecifier::Named(ast::ImportNamedSpecifier { local, .. }) => {
227 let name = remove_hash_and_numbers(&local.to_string());
228 machine.globals.insert(name);
229 }
230 ImportSpecifier::Default(ast::ImportDefaultSpecifier { local, .. }) => {
231 let name = remove_hash_and_numbers(&local.to_string());
232 machine.globals.insert(name);
233 }
234 ImportSpecifier::Namespace(ast::ImportStarAsSpecifier {
235 local, ..
236 }) => {
237 let name = remove_hash_and_numbers(&local.to_string());
238 machine.globals.insert(name);
239 }
240 }
241 }
242 }
243 ModuleDecl::ExportDecl(ast::ExportDecl { .. }) => {}
244 ModuleDecl::ExportNamed(ast::NamedExport { .. }) => {}
245 ModuleDecl::ExportDefaultDecl(ast::ExportDefaultDecl { .. }) => {}
246 ModuleDecl::ExportDefaultExpr(ast::ExportDefaultExpr { .. }) => {}
247 ModuleDecl::ExportAll(ast::ExportAll { .. }) => {}
248 ModuleDecl::TsImportEquals(_) => {}
249 ModuleDecl::TsExportAssignment(_) => {}
250 ModuleDecl::TsNamespaceExport(_) => {}
251 },
252 ModuleItem::Stmt(stmt) => {
253 let v = vec![stmt];
254 traverse_stmts(v.as_slice(), machine);
255 }
256 }
257}
258
259fn traverse_prop_name(prop_name: &PropName, machine: &mut ASTWalkContext) {
260 match prop_name {
261 PropName::Ident(name) => {
262 }
264 PropName::Str(_) => {}
265 PropName::Num(_) => {}
266 PropName::Computed(_) => {}
267 PropName::BigInt(_) => {}
268 }
269}
270
271fn traverse_pat(pat: &ast::Pat, machine: &mut ASTWalkContext) {
272 match pat {
273 Pat::Ident(ast::BindingIdent { id, .. }) => {
274 machine.encounter_named_reference(id);
275 }
276 Pat::Array(ast::ArrayPat { elems, .. }) => {
277 for elem in elems {
278 if let Some(elem) = elem {
279 traverse_pat(elem, machine);
280 }
281 }
282 }
283 Pat::Rest(ast::RestPat { arg, .. }) => {
284 traverse_pat(arg, machine);
285 }
286 Pat::Object(ast::ObjectPat { props, .. }) => {
287 for prop in props {
288 match prop {
289 ast::ObjectPatProp::KeyValue(ast::KeyValuePatProp { key, value, .. }) => {
290 traverse_prop_name(key, machine);
292 traverse_pat(value, machine);
294 }
295 ast::ObjectPatProp::Assign(ast::AssignPatProp { key, value, .. }) => {
296 machine.encounter_named_reference(key);
297 if let Some(value) = value {
298 traverse_expr(value, machine);
299 }
300 }
301 ast::ObjectPatProp::Rest(ast::RestPat { arg, .. }) => {
302 traverse_pat(arg, machine);
303 }
304 }
305 }
306 }
307 Pat::Assign(ast::AssignPat { left, right, .. }) => {
308 traverse_pat(left, machine);
309 traverse_expr(right, machine);
310 }
311 Pat::Invalid(ast::Invalid { .. }) => {}
312 Pat::Expr(expr) => {
313 traverse_expr(expr, machine);
314 }
315 }
316}
317
318fn traverse_expr(expr: &ast::Expr, machine: &mut ASTWalkContext) {
319 match expr {
320 Expr::This(ast::ThisExpr { .. }) => {}
321 Expr::Array(ast::ArrayLit { elems, .. }) => {}
322 Expr::Object(ast::ObjectLit { props, .. }) => {}
323 Expr::Fn(ast::FnExpr { .. }) => {}
324 Expr::Unary(ast::UnaryExpr { arg, .. }) => {
325 traverse_expr(arg, machine);
326 }
327 Expr::Update(ast::UpdateExpr { arg, .. }) => {
328 traverse_expr(arg, machine);
329 }
330 Expr::Bin(ast::BinExpr { left, right, .. }) => {
331 traverse_expr(left, machine);
332 traverse_expr(right, machine);
333 }
334 Expr::Assign(ast::AssignExpr {
335 op, left, right, ..
336 }) => {
337 let idx = machine.enter_assignment_to_statement();
338 match left {
339 PatOrExpr::Expr(expr) => {
340 traverse_expr(expr, machine);
341 }
342 PatOrExpr::Pat(pat) => {
343 traverse_pat(pat, machine);
344 }
345 }
346 machine.pop_until(idx);
347 let idx = machine.enter_assignment_from_statement();
348 traverse_expr(right, machine);
349 machine.pop_until(idx);
350 }
351 Expr::Member(ast::MemberExpr { obj, prop, .. }) => {
352 match prop {
353 MemberProp::Ident(id) => {
354 machine.enter_attr(id);
355 },
356 MemberProp::PrivateName(ast::PrivateName { id, .. }) => {
357 machine.enter_attr(id);
358 }
359 MemberProp::Computed(ast::ComputedPropName { expr, .. }) => {
360 }
362 };
363 traverse_expr(&obj, machine);
364 }
366 Expr::SuperProp(ast::SuperPropExpr { .. }) => {}
367 Expr::Cond(ast::CondExpr {
368 test, cons, alt, ..
369 }) => {
370 traverse_expr(&test, machine);
371 traverse_expr(&cons, machine);
372 traverse_expr(&alt, machine);
373 }
374 Expr::Call(ast::CallExpr { args, callee, .. }) => {
375 let idx = machine.enter_call_expression();
376 match callee {
377 Callee::Super(_) => {}
378 Callee::Import(_) => {}
379 Callee::Expr(expr) => {
380 traverse_expr(expr, machine);
381 }
382 }
383
384 for arg in args {
385 traverse_expr(&arg.expr, machine);
386 }
387 machine.pop_until(idx);
388 }
389 Expr::New(ast::NewExpr { callee, args, .. }) => {
390 let idx = machine.enter_call_expression();
391 traverse_expr(&callee, machine);
392 if let Some(args) = args {
393 for arg in args {
394 traverse_expr(&arg.expr, machine);
395 }
396 }
397 machine.pop_until(idx);
398 }
399 Expr::Seq(ast::SeqExpr { exprs, .. }) => {
400 for expr in exprs {
401 traverse_expr(expr, machine);
402 }
403 }
404 Expr::Ident(id) => {
405 machine.encounter_named_reference(id);
406 }
407 Expr::Lit(lit) => {
408 machine.encounter_constant(lit);
409 }
410 Expr::Tpl(ast::Tpl { exprs, .. }) => {
411 for expr in exprs {
412 traverse_expr(&expr, machine);
413 }
414 }
415 Expr::TaggedTpl(ast::TaggedTpl { tag, .. }) => {
416 traverse_expr(&tag, machine);
417 }
418 Expr::Arrow(ast::ArrowExpr { params, body, .. }) => {
419 let idx = machine.enter_anonymous_function();
420 machine.new_local_context();
421 let params_idx = machine.enter_params();
422 for param in params {
423 traverse_pat(param, machine);
424 }
425 machine.pop_until(params_idx);
426 match **body {
427 BlockStmtOrExpr::Expr(ref expr) => {
428 traverse_expr(expr, machine);
429 }
430 BlockStmtOrExpr::BlockStmt(ref block) => {
431 traverse_stmts(&block.stmts, machine);
432 }
433 }
434 machine.pop_local_context();
435 machine.pop_until(idx);
436 }
437 Expr::Class(ast::ClassExpr { class, .. }) => {
438 }
440 Expr::Yield(ast::YieldExpr { arg, .. }) => {
441 if let Some(arg) = arg {
442 traverse_expr(arg, machine);
443 }
444 }
445 Expr::MetaProp(ast::MetaPropExpr { .. }) => {}
446 Expr::Await(ast::AwaitExpr { arg, .. }) => {
447 traverse_expr(arg, machine);
448 }
449 Expr::Paren(ast::ParenExpr { expr, .. }) => {
450 traverse_expr(&expr, machine);
451 }
452 Expr::JSXMember(ast::JSXMemberExpr { .. }) => {}
453 Expr::JSXNamespacedName(ast::JSXNamespacedName { .. }) => {}
454 Expr::JSXEmpty(ast::JSXEmptyExpr { .. }) => {}
455 Expr::JSXElement(el) => {}
456 Expr::JSXFragment(ast::JSXFragment { .. }) => {}
457 Expr::TsTypeAssertion(ast::TsTypeAssertion { .. }) => {}
458 Expr::TsConstAssertion(ast::TsConstAssertion { .. }) => {}
459 Expr::TsNonNull(ast::TsNonNullExpr { .. }) => {}
460 Expr::TsAs(ast::TsAsExpr { .. }) => {}
461 Expr::TsInstantiation(ast::TsInstantiation { .. }) => {}
462 Expr::TsSatisfies(ast::TsSatisfiesExpr { .. }) => {}
463 Expr::PrivateName(ast::PrivateName { .. }) => {}
464 Expr::OptChain(ast::OptChainExpr { .. }) => {}
465 Expr::Invalid(ast::Invalid { .. }) => {}
466 }
467}
468fn traverse_stmt(stmt: &Stmt, machine: &mut ASTWalkContext) {
469 match stmt {
470 Stmt::Expr(expr_stmt) => {
471 traverse_expr(&*expr_stmt.expr, machine);
472 }
473 Stmt::Block(block_stmt) => {
474 traverse_stmts(&block_stmt.stmts, machine);
475 }
476 Stmt::Empty(_) => {}
477 Stmt::Debugger(ast::DebuggerStmt { .. }) => {}
478 Stmt::With(ast::WithStmt { obj, body, .. }) => {
479 traverse_expr(&obj, machine);
480 traverse_stmt(&body, machine);
481 }
482 Stmt::Return(ast::ReturnStmt { arg, .. }) => {
483 if let Some(arg) = arg {
484 traverse_expr(&arg, machine);
485 }
486 }
487 Stmt::Labeled(ast::LabeledStmt { body, .. }) => {
488 traverse_stmt(&body, machine);
489 }
490 Stmt::Break(ast::BreakStmt { .. }) => {}
491 Stmt::Continue(ast::ContinueStmt { .. }) => {}
492 Stmt::If(ast::IfStmt {
493 test, cons, alt, ..
494 }) => {
495 traverse_expr(&test, machine);
496 traverse_stmt(&cons, machine);
497 if let Some(alt) = alt {
498 traverse_stmt(&alt, machine);
499 }
500 }
501 Stmt::Switch(ast::SwitchStmt {
502 discriminant,
503 cases,
504 ..
505 }) => {
506 traverse_expr(&discriminant, machine);
507 for case in cases {
508 if let Some(test) = &case.test {
509 traverse_expr(&test, machine);
510 }
511 traverse_stmts(&case.cons, machine);
512 }
513 }
514 Stmt::Throw(ast::ThrowStmt { arg, .. }) => {
515 traverse_expr(&arg, machine);
516 }
517 Stmt::Try(x) => {
518 let ast::TryStmt {
519 block,
520 handler,
521 finalizer,
522 ..
523 } = &**x;
524 traverse_stmts(&block.stmts, machine);
525 }
526 Stmt::While(ast::WhileStmt { test, body, .. }) => {
527 machine.new_local_context();
528 traverse_expr(&test, machine);
529 traverse_stmt(&body, machine);
530 machine.pop_local_context();
531 }
532 Stmt::DoWhile(ast::DoWhileStmt { test, body, .. }) => {
533 machine.new_local_context();
534 traverse_expr(&test, machine);
535 traverse_stmt(&body, machine);
536 machine.pop_local_context();
537 }
538 Stmt::For(ast::ForStmt {
539 init,
540 test,
541 body,
542 update,
543 ..
544 }) => {
545 machine.new_local_context();
546 if let Some(init) = init {
547 match init {
548 ast::VarDeclOrExpr::VarDecl(x) => {
549 let ast::VarDecl { decls, .. } = &**x;
550 for decl in decls {
551 let idx = machine.enter_assignment_to_statement();
552 traverse_pat(&decl.name, machine);
553 machine.pop_until(idx);
554 if let Some(init) = &decl.init {
555 traverse_expr(&init, machine);
556 }
557 }
558 }
559 ast::VarDeclOrExpr::Expr(expr) => {
560 traverse_expr(&expr, machine);
561 }
562 }
563 }
564 if let Some(test) = test {
565 traverse_expr(&test, machine);
566 }
567 if let Some(update) = update {
568 traverse_expr(&update, machine);
569 }
570 traverse_stmt(&body, machine);
571 machine.pop_local_context();
572 }
573 Stmt::ForIn(ast::ForInStmt {
574 left, right, body, ..
575 }) => {
576 machine.new_local_context();
577 traverse_for_statement(machine, left);
578 traverse_expr(&right, machine);
579 traverse_stmt(&body, machine);
580 machine.pop_local_context();
581 }
582 Stmt::ForOf(ast::ForOfStmt {
583 left, right, body, ..
584 }) => {
585 machine.new_local_context();
586 traverse_for_statement(machine, left);
587 traverse_expr(&right, machine);
588 traverse_stmt(&body, machine);
589 machine.pop_local_context();
590 }
591 Stmt::Decl(decl) => match decl {
592 Decl::Class(_) => {}
593 Decl::Fn(ast::FnDecl {
594 ident, function, ..
595 }) => {
596 machine.insert_local(ident);
597 let ast::Function { params, body, span, .. } = &**function;
598 let idx = machine.enter_statement_function(ident, TextRange {
599 start: span.lo.to_usize(),
600 end: span.hi.to_usize(),
601 });
602 let params_idx = machine.enter_params();
603 for param in params {
604 traverse_pat(¶m.pat, machine);
605 }
606 machine.pop_until(params_idx);
607 if let Some(body) = body {
608 traverse_stmts(&body.stmts, machine);
609 }
610 machine.pop_until(idx);
611 }
612 Decl::Var(v) => {
613 let ast::VarDecl { decls, .. } = &**v;
614 for decl in decls {
615 let idx = machine.enter_assignment_to_statement();
616 traverse_pat(&decl.name, machine);
617 machine.pop_until(idx);
618 if let Some(init) = &decl.init {
619 let idx = machine.enter_assignment_from_statement();
620 traverse_expr(&init, machine);
621 machine.pop_until(idx);
622 }
623 }
624 }
625 Decl::Using(v) => {
626 let ast::UsingDecl { decls, .. } = &**v;
627 for decl in decls {
628 let idx = machine.enter_assignment_to_statement();
629 traverse_pat(&decl.name, machine);
630 machine.pop_until(idx);
631 if let Some(init) = &decl.init {
632 traverse_expr(&init, machine);
633 }
634 }
635 }
636 Decl::TsInterface(_) => {}
637 Decl::TsTypeAlias(_) => {}
638 Decl::TsEnum(_) => {}
639 Decl::TsModule(_) => {}
640 },
641 }
642}
643
644fn traverse_for_statement(machine: &mut ASTWalkContext, left: &ForHead) {
645 match left {
646 ForHead::VarDecl(x) => {
647 let ast::VarDecl { decls, .. } = &**x;
648 for decl in decls {
649 let idx = machine.enter_assignment_to_statement();
650 traverse_pat(&decl.name, machine);
651 machine.pop_until(idx);
652 if let Some(init) = &decl.init {
653 traverse_expr(&init, machine);
654 }
655 }
656 }
657 ForHead::UsingDecl(x) => {
658 let ast::UsingDecl { decls, .. } = &**x;
659 for decl in decls {
660 let idx = machine.enter_assignment_to_statement();
661 traverse_pat(&decl.name, machine);
662 machine.pop_until(idx);
663 if let Some(init) = &decl.init {
664 traverse_expr(&init, machine);
665 }
666 }
667 }
668 ForHead::Pat(x) => {
669 traverse_pat(&x, machine);
670 }
671 }
672}
673
674fn traverse_stmts(stmts: &[Stmt], machine: &mut ASTWalkContext) {
675 for stmt in stmts {
676 traverse_stmt(stmt, machine);
677 }
678}
679
680pub fn extract_dependencies_js(source: &str) -> Result<Vec<Vec<ContextPath>>, ChidoriStaticAnalysisError> {
681 let mut machine = ASTWalkContext::new();
682 let cm: Lrc<SourceMap> = Default::default();
683 let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(cm.clone()));
684 let fm = cm.new_source_file(FileName::Custom("test.js".into()), source.to_string());
685
686 let parse_module = |syntax: Syntax| {
687 let lexer = Lexer::new(
688 syntax,
689 Default::default(),
690 StringInput::from(&*fm),
691 None,
692 );
693
694 let mut parser = Parser::new_from(lexer);
695
696 for e in parser.take_errors() {
697 e.into_diagnostic(&handler).emit();
698 }
699
700 parser.parse_module()
701 };
702
703 let module = parse_module(Syntax::Es(Default::default()))
704 .or_else(|_| parse_module(Syntax::Typescript(Default::default())))
705 .map_err(|mut e| {
706 e
708 });
710
711 match module {
712 Ok(module) => {
713 for item in module.body {
714 traverse_module(item, &mut machine);
715 }
716 Ok(machine.context_stack_references)
717 },
718 Err(e) => {
719 Err(ChidoriStaticAnalysisError::ParseError {
720 msg: format!("{:?}", e),
721 offset: 0,
722 source_path: "".to_string(),
723 source_code: "".to_string(),
724 })
725
726 }
727 }
728}
729
730
731pub fn build_report(context_paths: &Vec<Vec<ContextPath>>) -> Report {
732 let mut exposed_values = HashMap::new();
733 let mut depended_values = HashMap::new();
734 let mut triggerable_functions = HashMap::new();
735 for context_path in context_paths {
736 let mut encountered = vec![];
737
738 let mut in_function: Option<&String> = None;
739 let mut in_call_expression = false;
740 let mut attribute_path = vec![];
741
742 for (idx, context_path_unit) in context_path.iter().enumerate() {
743 encountered.push(context_path_unit);
744
745 if let ContextPath::InFunction(name, _) = context_path_unit {
747 in_function = Some(name);
748 if !triggerable_functions.contains_key(name) {
749 triggerable_functions
750 .entry(name.clone())
751 .or_insert_with(|| ReportTriggerableFunctions {
752 arguments: vec![],
753 emit_event: vec![],
754 trigger_on: vec![],
755 });
756 }
757 }
758
759 if let Some(in_function_name) = in_function {
760 if let ContextPath::InCallExpression = context_path_unit {
761 in_call_expression = true;
762 }
763
764 if in_call_expression {
765 if let ContextPath::Attribute(attr) = context_path_unit {
766 attribute_path.push(attr);
767 }
768 }
769
770 if context_path.len() - 1 == idx {
771 let mut x = triggerable_functions
772 .entry(in_function_name.clone())
773 .or_insert_with(|| ReportTriggerableFunctions {
774
775 arguments: vec![],
776 emit_event: vec![], trigger_on: vec![],
778 });
779
780 if attribute_path == vec![&"emitAs".to_string()] {
781 if let ContextPath::Constant(const_name) = encountered[idx] {
782 x.emit_event.push(const_name.clone());
783 }
784 }
785
786 if attribute_path == vec![&"onEvent".to_string()] {
787 if let ContextPath::Constant(const_name) = encountered[idx] {
788 x.trigger_on.push(const_name.clone());
789 }
790 }
791 }
792 }
793
794 if let ContextPath::IdentifierReferredTo{name: identifier, exposed, in_scope: false} = context_path_unit {
796 if encountered.iter().any(|x| matches!(x, ContextPath::InFunction(_, _)))
799 && encountered.iter().any(|x| matches!(x, ContextPath::FunctionArguments))
800 {
801 for context_path_unit in &encountered {
802 if let ContextPath::InFunction(function_name, _) = context_path_unit {
803 let mut x = triggerable_functions
804 .entry(function_name.clone())
805 .or_insert_with(|| ReportTriggerableFunctions {
806 arguments: vec![],
807 emit_event: vec![], trigger_on: vec![],
809 });
810 x.arguments.push(identifier.clone());
811 }
812 }
813 continue;
814 }
815
816 if encountered
818 .iter()
819 .find(|x| matches!(x, ContextPath::InFunction(_, _)))
820 .is_none()
821 &&
822 encountered
823 .iter()
824 .find(|x| matches!(x, ContextPath::InAnonFunction))
825 .is_none()
826 && *exposed
827
828 {
829 if encountered.contains(&&ContextPath::AssignmentToStatement) {
830 exposed_values.insert(
831 identifier.clone(),
832 ReportItem {
833 },
835 );
836 continue;
837 }
838 }
839
840 if !encountered.contains(&&ContextPath::AssignmentToStatement) && !encountered.contains(&&ContextPath::FunctionArguments) {
841 depended_values.insert(
842 identifier.clone(),
843 ReportItem {
844 },
846 );
847 continue;
848 }
849 }
850 }
851 }
852
853 let js_built_ins: HashSet<&str> = [
854 "Deno", "Chidori", "Array", "ArrayBuffer", "Boolean", "DataView", "Date", "Error", "EvalError", "Float32Array",
855 "Float64Array", "Function", "Generator", "GeneratorFunction", "Infinity", "Int8Array",
856 "Int16Array", "Int32Array", "InternalError", "Intl", "JSON", "Map", "Math", "NaN",
857 "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect",
858 "RegExp", "Set", "SharedArrayBuffer", "String", "Symbol", "SyntaxError", "TypeError",
859 "URIError", "Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array", "WeakMap",
860 "WeakSet", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", "escape",
861 "eval", "isFinite", "isNaN", "parseFloat", "parseInt", "unescape", "uneval", "setTimeout", "setInterval", "console", "process"
862 ].iter().cloned().collect();
863
864 depended_values.retain(|value,_ | !js_built_ins.contains(value.as_str()));
865
866 Report {
867 internal_call_graph: InternalCallGraph {
868 graph: Default::default(),
869 },
870 cell_exposed_values: exposed_values,
871 cell_depended_values: depended_values,
872 triggerable_functions: triggerable_functions,
873 }
874}
875
876#[cfg(test)]
877mod tests {
878 use super::*;
879 use crate::language::python::parse::extract_dependencies_python;
880 use crate::language::{Report, ReportItem, ReportTriggerableFunctions};
881 use indoc::indoc;
882
883 #[test]
884 fn test_extraction_of_ch_statements() {
885 let js_source = indoc! { r#"
886 import * as ch from "@1kbirds/chidori";
887
888 ch.prompt.configure("default", ch.llm({model: "openai"}))
889 "#};
890 let context_stack_references = extract_dependencies_js(js_source).unwrap();
891 insta::assert_yaml_snapshot!(context_stack_references);
892 }
893
894 #[test]
895 fn test_assignment_to_value() {
896 let js_source = indoc! { r#"
897 const x = 1
898 "#};
899 let context_stack_references = extract_dependencies_js(js_source).unwrap();
900 insta::with_settings!({
901 description => js_source,
902 omit_expression => true
903 }, {
904 insta::assert_yaml_snapshot!(context_stack_references);
905 });
906 }
907
908 #[test]
909 fn test_nothing_extracted_with_no_ch_references() {
910 let js_source = indoc! { r#"
911 function createDockerfile() {
912 return prompt("prompts/create_dockerfile")
913 }
914 "#};
915 let context_stack_references = extract_dependencies_js(js_source).unwrap();
916 insta::with_settings!({
917 description => js_source,
918 omit_expression => true
919 }, {
920 insta::assert_yaml_snapshot!(context_stack_references);
921 });
922 }
923
924 #[test]
925 fn test_handling_react_hook_style_refrence() {
926 let js_source = indoc! { r#"
927 function createDockerfile() {
928 useHook(() => {
929 ch.prompt("demo");
930 }, [otherFunction]);
931 return prompt("prompts/create_dockerfile")
932 }
933 "#};
934 let context_stack_references = extract_dependencies_js(js_source).unwrap();
935
936 insta::with_settings!({
937 description => js_source,
938 omit_expression => true
939 }, {
940 insta::assert_yaml_snapshot!(context_stack_references);
941 });
942
943 }
944
945 #[test]
946 fn test_function_decorator_ch_annotation() {
947 let js_source = indoc! { r#"
948 function migrationAgent() {
949 ch.register();
950 ch.set("bar", 1);
951 }
952 "#};
953 let context_stack_references = extract_dependencies_js(js_source).unwrap();
954 insta::with_settings!({
955 description => js_source,
956 omit_expression => true
957 }, {
958 insta::assert_yaml_snapshot!(context_stack_references);
959 });
960 }
961
962 #[test]
963 fn test_function_decorator_ch_annotation_with_internal_ch_and_emit() {
964 let js_source = indoc! { r#"
965 function dispatch_agent(ev) {
966 ch.onEvent("new_file")
967 ch.emitAs("file_created")
968 ch.emitAs("file_created", "multiple", "args")
969 ch.set("file_path", ev.file_path)
970 }
971 "#};
972 let context_stack_references = extract_dependencies_js(js_source).unwrap();
973
974 insta::with_settings!({
975 description => js_source,
976 omit_expression => true
977 }, {
978 insta::assert_yaml_snapshot!(context_stack_references);
979 });
980
981 }
982
983 #[test]
984 fn test_ch_reference_internal_to_function() {
985 let js_source = indoc! { r#"
986 function evaluate_agent(ev) {
987 ch.set("file_path", ev.file_path)
988 migration_agent()
989 }
990 "#};
991 let context_stack_references = extract_dependencies_js(js_source).unwrap();
992
993 insta::with_settings!({
994 description => js_source,
995 omit_expression => true
996 }, {
997 insta::assert_yaml_snapshot!(context_stack_references);
998 });
999
1000 }
1001
1002 #[test]
1003 fn test_ch_function_decoration_referring_to_another_function() {
1004 let js_source = indoc! { r#"
1005 function setupPipeline(x) {
1006 ch.p(create_dockerfile)
1007 return x
1008 }
1009 "#};
1010 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1011
1012 insta::with_settings!({
1013 description => js_source,
1014 omit_expression => true
1015 }, {
1016 insta::assert_yaml_snapshot!(context_stack_references);
1017 });
1018
1019 }
1020
1021 #[test]
1022 fn test_ch_function_with_arguments() {
1023 let js_source = indoc! { r#"
1024 function subtract(a, b) {
1025 return a - b;
1026 }
1027
1028 // Example usage
1029 const v = subtract(x, 5);
1030 "#};
1031 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1032
1033 insta::with_settings!({
1034 description => js_source,
1035 omit_expression => true
1036 }, {
1037 insta::assert_yaml_snapshot!(context_stack_references);
1038 });
1039
1040 }
1041
1042 #[test]
1043 fn test_pipe_function_composition() {
1044 let js_source = indoc! { r#"
1045 function main() {
1046 bar() | foo() | baz()
1047 }
1048 "#};
1049 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1050 insta::with_settings!({
1051 description => js_source,
1052 omit_expression => true
1053 }, {
1054 insta::assert_yaml_snapshot!(context_stack_references);
1055 });
1056 }
1057
1058 #[test]
1059 fn test_report_generation() {
1060 let js_source = indoc! { r#"
1061 function testing() {
1062 ch.onEvent("new_file");
1063 ch.emitAs("file_created");
1064 const x = 2 + y;
1065 return x
1066 }
1067 "#};
1068 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1069 insta::with_settings!({
1070 description => js_source,
1071 omit_expression => true
1072 }, {
1073 insta::assert_yaml_snapshot!(context_stack_references);
1074 });
1075 let result = build_report(&context_stack_references);
1076 insta::with_settings!({
1077 description => js_source,
1078 omit_expression => true
1079 }, {
1080 insta::assert_yaml_snapshot!(result);
1081 });
1082 }
1083
1084 #[test]
1085 fn test_report_for_simple_function() {
1086 let js_source = indoc! { r#"
1087 function testing(x) {
1088 return x
1089 }
1090 "#};
1091 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1092 insta::with_settings!({
1093 description => js_source,
1094 omit_expression => true
1095 }, {
1096 insta::assert_yaml_snapshot!(context_stack_references);
1097 });
1098 let result = build_report(&context_stack_references);
1099 insta::with_settings!({
1100 description => js_source,
1101 omit_expression => true
1102 }, {
1103 insta::assert_yaml_snapshot!(result);
1104 });
1105 }
1106
1107 #[test]
1108 fn test_report_generation_deno_test_framework() {
1109 let js_source = indoc! { r#"
1110 import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts";
1111
1112 Deno.test("addition test", async () => {
1113 const result = await add_two(2);
1114 console.log(result);
1115 assertEquals(result, 4);
1116 });
1117 "#};
1118 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1119 insta::with_settings!({
1120 description => js_source,
1121 omit_expression => true
1122 }, {
1123 insta::assert_yaml_snapshot!(context_stack_references);
1124 });
1125 ;
1126 let result = build_report(&context_stack_references);
1127 let report = Report {
1128 internal_call_graph: InternalCallGraph {
1129 graph: Default::default(),
1130 },
1131 cell_exposed_values: {
1132 let mut map = std::collections::HashMap::new();
1133 map
1134 },
1135 cell_depended_values: {
1136 let mut map = std::collections::HashMap::new();
1137 map.insert("add_two".to_string(), ReportItem {});
1138 map
1139 },
1140 triggerable_functions: {
1141 let mut map = std::collections::HashMap::new();
1142 map
1143 },
1144 };
1145 assert_eq!(result, report);
1146 }
1147
1148 #[test]
1149 fn test_report_generation_with_import() {
1150 let js_source = indoc! { r#"
1151import { random } from "random"
1152
1153function fun_name() {
1154 const w = function_that_doesnt_exist()
1155 const v = 5
1156 return v
1157}
1158
1159x = random.randint(0, 10)
1160"#};
1161 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1162 insta::with_settings!({
1163 description => js_source,
1164 omit_expression => true
1165 }, {
1166 insta::assert_yaml_snapshot!(context_stack_references);
1167 });
1168 ;
1169 let result = build_report(&context_stack_references);
1170 let report = Report {
1171
1172 internal_call_graph: InternalCallGraph {
1173 graph: Default::default(),
1174 },
1175 cell_exposed_values: {
1176 let mut map = std::collections::HashMap::new();
1177 map.insert("x".to_string(), ReportItem {});
1178 map
1179 },
1180 cell_depended_values: {
1181 let mut map = std::collections::HashMap::new();
1182 map.insert("function_that_doesnt_exist".to_string(), ReportItem {});
1183 map
1184 },
1185 triggerable_functions: {
1186 let mut map = std::collections::HashMap::new();
1187 map.insert(
1188 "fun_name".to_string(),
1189 ReportTriggerableFunctions {
1190
1191 arguments: vec![],
1192 emit_event: vec![],
1193 trigger_on: vec![],
1194 },
1195 );
1196 map
1197 },
1198 };
1199 assert_eq!(result, report);
1200 }
1201
1202 #[test]
1203 fn test_report_generation_with_object_destructuring() {
1204 let js_source = indoc! { r#"
1205 const { a, b } = someObject;
1206 const { c: renamed } = anotherObject;
1207
1208 function processValues({ x, y }) {
1209 return x + y;
1210 }
1211
1212 const result = processValues({ x: a, y: b });
1213 "#};
1214 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1215 insta::with_settings!({
1216 description => js_source,
1217 omit_expression => true
1218 }, {
1219 insta::assert_yaml_snapshot!(context_stack_references);
1220 });
1221
1222 let result = build_report(&context_stack_references);
1223 insta::with_settings!({
1224 sort_maps => true,
1225 description => js_source,
1226 omit_expression => true
1227 }, {
1228 insta::assert_yaml_snapshot!(result);
1229 });
1230 }
1231}
1232
1233#[cfg(test)]
1234mod destructuring_tests {
1235 use super::*;
1236 use indoc::indoc;
1237
1238 #[test]
1239 fn test_basic_object_destructuring() {
1240 let js_source = indoc! { r#"
1241 const { a, b } = { a: 1, b: 2 };
1242 "#};
1243 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1244 insta::with_settings!({
1245 sort_maps => true,
1246 description => js_source,
1247 omit_expression => true
1248 }, {
1249 insta::assert_yaml_snapshot!(context_stack_references);
1250 });
1251
1252 let result = build_report(&context_stack_references);
1253 insta::with_settings!({
1254 sort_maps => true,
1255 description => js_source,
1256 omit_expression => true
1257 }, {
1258 insta::assert_yaml_snapshot!(result);
1259 });
1260
1261 result;
1262 }
1263
1264 #[test]
1265 fn test_object_destructuring_with_renaming_and_default() {
1266 let js_source = indoc! { r#"
1267 const { c: renamed, d = 'default' } = { c: 3 };
1268 "#};
1269 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1270 insta::with_settings!({
1271 sort_maps => true,
1272 description => js_source,
1273 omit_expression => true
1274 }, {
1275 insta::assert_yaml_snapshot!(context_stack_references);
1276 });
1277
1278 let result = build_report(&context_stack_references);
1279 insta::with_settings!({
1280 sort_maps => true,
1281 description => js_source,
1282 omit_expression => true
1283 }, {
1284 insta::assert_yaml_snapshot!(result);
1285 });
1286
1287 result;
1288 }
1289
1290 #[test]
1291 fn test_nested_object_destructuring() {
1292 let js_source = indoc! { r#"
1293 const { e: { f } } = { e: { f: 4 } };
1294 "#};
1295 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1296 insta::with_settings!({
1297 sort_maps => true,
1298 description => js_source,
1299 omit_expression => true
1300 }, {
1301 insta::assert_yaml_snapshot!(context_stack_references);
1302 });
1303
1304 let result = build_report(&context_stack_references);
1305 insta::with_settings!({
1306 sort_maps => true,
1307 description => js_source,
1308 omit_expression => true
1309 }, {
1310 insta::assert_yaml_snapshot!(result);
1311 });
1312
1313 result;
1314 }
1315
1316 #[test]
1317 fn test_basic_array_destructuring() {
1318 let js_source = indoc! { r#"
1319 const [g, h] = [5, 6];
1320 "#};
1321 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1322 insta::with_settings!({
1323 sort_maps => true,
1324 description => js_source,
1325 omit_expression => true
1326 }, {
1327 insta::assert_yaml_snapshot!(context_stack_references);
1328 });
1329
1330 let result = build_report(&context_stack_references);
1331 insta::with_settings!({
1332 sort_maps => true,
1333 description => js_source,
1334 omit_expression => true
1335 }, {
1336 insta::assert_yaml_snapshot!(result);
1337 });
1338
1339 result;
1340 }
1341
1342 #[test]
1343 fn test_array_destructuring_with_gaps() {
1344 let js_source = indoc! { r#"
1345 const [i, , j] = [7, 8, 9];
1346 "#};
1347 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1348 insta::with_settings!({
1349 sort_maps => true,
1350 description => js_source,
1351 omit_expression => true
1352 }, {
1353 insta::assert_yaml_snapshot!(context_stack_references);
1354 });
1355
1356 let result = build_report(&context_stack_references);
1357 insta::with_settings!({
1358 sort_maps => true,
1359 description => js_source,
1360 omit_expression => true
1361 }, {
1362 insta::assert_yaml_snapshot!(result);
1363 });
1364
1365 result;
1366 }
1367
1368 #[test]
1369 fn test_array_destructuring_with_rest() {
1370 let js_source = indoc! { r#"
1371 const [k, ...rest] = [10, 11, 12];
1372 "#};
1373 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1374 insta::with_settings!({
1375 sort_maps => true,
1376 description => js_source,
1377 omit_expression => true
1378 }, {
1379 insta::assert_yaml_snapshot!(context_stack_references);
1380 });
1381
1382 let result = build_report(&context_stack_references);
1383 insta::with_settings!({
1384 sort_maps => true,
1385 description => js_source,
1386 omit_expression => true
1387 }, {
1388 insta::assert_yaml_snapshot!(result);
1389 });
1390
1391 result;
1392 }
1393
1394 #[test]
1395 fn test_mixed_destructuring() {
1396 let js_source = indoc! { r#"
1397 const { l, m: [n, o] } = { l: 13, m: [14, 15] };
1398 "#};
1399 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1400 insta::with_settings!({
1401 sort_maps => true,
1402 description => js_source,
1403 omit_expression => true
1404 }, {
1405 insta::assert_yaml_snapshot!(context_stack_references);
1406 });
1407
1408 let result = build_report(&context_stack_references);
1409 insta::with_settings!({
1410 sort_maps => true,
1411 description => js_source,
1412 omit_expression => true
1413 }, {
1414 insta::assert_yaml_snapshot!(result);
1415 });
1416
1417 result;
1418 }
1419
1420 #[test]
1421 fn test_destructuring_in_function_parameters() {
1422 let js_source = indoc! { r#"
1423 function printPerson({ name, age = 30 }) {
1424 console.log(`${name} is ${age} years old`);
1425 }
1426 "#};
1427 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1428 insta::with_settings!({
1429 sort_maps => true,
1430 description => js_source,
1431 omit_expression => true
1432 }, {
1433 insta::assert_yaml_snapshot!(context_stack_references);
1434 });
1435
1436 let result = build_report(&context_stack_references);
1437 insta::with_settings!({
1438 sort_maps => true,
1439 description => js_source,
1440 omit_expression => true
1441 }, {
1442 insta::assert_yaml_snapshot!(result);
1443 });
1444
1445 result;
1446 }
1447
1448 #[test]
1449 fn test_destructuring_with_computed_property_names() {
1450 let js_source = indoc! { r#"
1451 const key = 'p';
1452 const { [key]: value } = { p: 16 };
1453 "#};
1454 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1455 insta::with_settings!({
1456 sort_maps => true,
1457 description => js_source,
1458 omit_expression => true
1459 }, {
1460 insta::assert_yaml_snapshot!(context_stack_references);
1461 });
1462
1463 let result = build_report(&context_stack_references);
1464 insta::with_settings!({
1465 sort_maps => true,
1466 description => js_source,
1467 omit_expression => true
1468 }, {
1469 insta::assert_yaml_snapshot!(result);
1470 });
1471
1472 result;
1473 }
1474
1475 #[test]
1476 fn test_complex_nested_destructuring() {
1477 let js_source = indoc! { r#"
1478 const { q: { r: [s, { t }] } } = { q: { r: [17, { t: 18 }] } };
1479 "#};
1480 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1481 insta::with_settings!({
1482 sort_maps => true,
1483 description => js_source,
1484 omit_expression => true
1485 }, {
1486 insta::assert_yaml_snapshot!(context_stack_references);
1487 });
1488
1489 let result = build_report(&context_stack_references);
1490 insta::with_settings!({
1491 sort_maps => true,
1492 description => js_source,
1493 omit_expression => true
1494 }, {
1495 insta::assert_yaml_snapshot!(result);
1496 });
1497
1498 result;
1499 }
1500
1501 #[test]
1502 fn test_object_destructuring_with_rest_properties() {
1503 let js_source = indoc! { r#"
1504 const { u, ...others } = { u: 19, v: 20, w: 21 };
1505 "#};
1506 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1507 insta::with_settings!({
1508 sort_maps => true,
1509 description => js_source,
1510 omit_expression => true
1511 }, {
1512 insta::assert_yaml_snapshot!(context_stack_references);
1513 });
1514
1515 let result = build_report(&context_stack_references);
1516 insta::with_settings!({
1517 sort_maps => true,
1518 description => js_source,
1519 omit_expression => true
1520 }, {
1521 insta::assert_yaml_snapshot!(result);
1522 });
1523
1524 result;
1525 }
1526
1527 #[test]
1528 fn test_destructuring_in_for_of_loops() {
1529 let js_source = indoc! { r#"
1530 const items = [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }];
1531 for (const { id, name } of items) {
1532 console.log(`${id}: ${name}`);
1533 }
1534 "#};
1535 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1536 insta::with_settings!({
1537 sort_maps => true,
1538 description => js_source,
1539 omit_expression => true
1540 }, {
1541 insta::assert_yaml_snapshot!(context_stack_references);
1542 });
1543
1544 let result = build_report(&context_stack_references);
1545 insta::with_settings!({
1546 sort_maps => true,
1547 description => js_source,
1548 omit_expression => true
1549 }, {
1550 insta::assert_yaml_snapshot!(result);
1551 });
1552
1553 result;
1554 }
1555
1556 #[test]
1557 fn test_destructuring_with_alias_and_default_value() {
1558 let js_source = indoc! { r#"
1559 const { x: newX = 100 } = {};
1560 "#};
1561 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1562 insta::with_settings!({
1563 sort_maps => true,
1564 description => js_source,
1565 omit_expression => true
1566 }, {
1567 insta::assert_yaml_snapshot!(context_stack_references);
1568 });
1569
1570 let result = build_report(&context_stack_references);
1571 insta::with_settings!({
1572 sort_maps => true,
1573 description => js_source,
1574 omit_expression => true
1575 }, {
1576 insta::assert_yaml_snapshot!(result);
1577 });
1578
1579 result;
1580 }
1581
1582 #[test]
1583 fn test_array_destructuring_with_default_values() {
1584 let js_source = indoc! { r#"
1585 const [y = 200, z = 300] = [22];
1586 "#};
1587 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1588 insta::with_settings!({
1589 sort_maps => true,
1590 description => js_source,
1591 omit_expression => true
1592 }, {
1593 insta::assert_yaml_snapshot!(context_stack_references);
1594 });
1595
1596 let result = build_report(&context_stack_references);
1597 insta::with_settings!({
1598 sort_maps => true,
1599 description => js_source,
1600 omit_expression => true
1601 }, {
1602 insta::assert_yaml_snapshot!(result);
1603 });
1604
1605 result;
1606 }
1607
1608 #[test]
1609 fn test_destructuring_returned_arrays() {
1610 let js_source = indoc! { r#"
1611 function returnArray() {
1612 return [1, 2, 3];
1613 }
1614 const [first, ...restArray] = returnArray();
1615 "#};
1616 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1617 insta::with_settings!({
1618 sort_maps => true,
1619 description => js_source,
1620 omit_expression => true
1621 }, {
1622 insta::assert_yaml_snapshot!(context_stack_references);
1623 });
1624
1625 let result = build_report(&context_stack_references);
1626 insta::with_settings!({
1627 sort_maps => true,
1628 description => js_source,
1629 omit_expression => true
1630 }, {
1631 insta::assert_yaml_snapshot!(result);
1632 });
1633
1634 result;
1635 }
1636
1637 #[test]
1638 fn test_variable_swapping_with_destructuring() {
1639 let js_source = indoc! { r#"
1640 let aa = 'first', bb = 'second';
1641 [aa, bb] = [bb, aa];
1642 "#};
1643 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1644 insta::with_settings!({
1645 sort_maps => true,
1646 description => js_source,
1647 omit_expression => true
1648 }, {
1649 insta::assert_yaml_snapshot!(context_stack_references);
1650 });
1651
1652 let result = build_report(&context_stack_references);
1653 insta::with_settings!({
1654 sort_maps => true,
1655 description => js_source,
1656 omit_expression => true
1657 }, {
1658 insta::assert_yaml_snapshot!(result);
1659 });
1660
1661 result;
1662 }
1663}
1664
1665#[cfg(test)]
1666mod for_loop_tests {
1667 use super::*;
1668 use indoc::indoc;
1669
1670 #[test]
1671 fn test_traditional_for_loop() {
1672 let js_source = indoc! { r#"
1673 for (let i = 0; i < 5; i++) {
1674 console.log(i);
1675 }
1676 "#};
1677 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1678 insta::with_settings!({
1679 sort_maps => true,
1680 description => js_source,
1681 omit_expression => true
1682 }, {
1683 insta::assert_yaml_snapshot!(context_stack_references);
1684 });
1685
1686 let result = build_report(&context_stack_references);
1687 insta::with_settings!({
1688 sort_maps => true,
1689 description => js_source,
1690 omit_expression => true
1691 }, {
1692 insta::assert_yaml_snapshot!(result);
1693 });
1694
1695 result;
1696 }
1697
1698 #[test]
1699 fn test_for_of_loop_with_array() {
1700 let js_source = indoc! { r#"
1701 const array = [1, 2, 3, 4, 5];
1702 for (const item of array) {
1703 console.log(item);
1704 }
1705 "#};
1706 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1707 insta::with_settings!({
1708 sort_maps => true,
1709 description => js_source,
1710 omit_expression => true
1711 }, {
1712 insta::assert_yaml_snapshot!(context_stack_references);
1713 });
1714
1715 let result = build_report(&context_stack_references);
1716 insta::with_settings!({
1717 sort_maps => true,
1718 description => js_source,
1719 omit_expression => true
1720 }, {
1721 insta::assert_yaml_snapshot!(result);
1722 });
1723
1724 result;
1725 }
1726
1727 #[test]
1728 fn test_for_in_loop_with_object() {
1729 let js_source = indoc! { r#"
1730 const obj = { a: 1, b: 2, c: 3 };
1731 for (const key in obj) {
1732 console.log(key, obj[key]);
1733 }
1734 "#};
1735 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1736 insta::with_settings!({
1737 sort_maps => true,
1738 description => js_source,
1739 omit_expression => true
1740 }, {
1741 insta::assert_yaml_snapshot!(context_stack_references);
1742 });
1743
1744 let result = build_report(&context_stack_references);
1745 insta::with_settings!({
1746 sort_maps => true,
1747 description => js_source,
1748 omit_expression => true
1749 }, {
1750 insta::assert_yaml_snapshot!(result);
1751 });
1752
1753 result;
1754 }
1755
1756 #[test]
1757 fn test_for_loop_with_multiple_declarations() {
1758 let js_source = indoc! { r#"
1759 for (let i = 0, j = 10; i < j; i++, j--) {
1760 console.log(i, j);
1761 }
1762 "#};
1763 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1764 insta::with_settings!({
1765 sort_maps => true,
1766 description => js_source,
1767 omit_expression => true
1768 }, {
1769 insta::assert_yaml_snapshot!(context_stack_references);
1770 });
1771
1772 let result = build_report(&context_stack_references);
1773 insta::with_settings!({
1774 sort_maps => true,
1775 description => js_source,
1776 omit_expression => true
1777 }, {
1778 insta::assert_yaml_snapshot!(result);
1779 });
1780
1781 result;
1782 }
1783
1784 #[test]
1785 fn test_for_loop_with_break_statement() {
1786 let js_source = indoc! { r#"
1787 for (let i = 0; i < 10; i++) {
1788 if (i === 5) break;
1789 console.log(i);
1790 }
1791 "#};
1792 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1793 insta::with_settings!({
1794 sort_maps => true,
1795 description => js_source,
1796 omit_expression => true
1797 }, {
1798 insta::assert_yaml_snapshot!(context_stack_references);
1799 });
1800
1801 let result = build_report(&context_stack_references);
1802 insta::with_settings!({
1803 sort_maps => true,
1804 description => js_source,
1805 omit_expression => true
1806 }, {
1807 insta::assert_yaml_snapshot!(result);
1808 });
1809
1810 result;
1811 }
1812
1813 #[test]
1814 fn test_for_loop_with_continue_statement() {
1815 let js_source = indoc! { r#"
1816 for (let i = 0; i < 10; i++) {
1817 if (i % 2 === 0) continue;
1818 console.log(i);
1819 }
1820 "#};
1821 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1822 insta::with_settings!({
1823 sort_maps => true,
1824 description => js_source,
1825 omit_expression => true
1826 }, {
1827 insta::assert_yaml_snapshot!(context_stack_references);
1828 });
1829
1830 let result = build_report(&context_stack_references);
1831 insta::with_settings!({
1832 sort_maps => true,
1833 description => js_source,
1834 omit_expression => true
1835 }, {
1836 insta::assert_yaml_snapshot!(result);
1837 });
1838
1839 result;
1840 }
1841
1842 #[test]
1843 fn test_nested_for_loops() {
1844 let js_source = indoc! { r#"
1845 for (let i = 0; i < 3; i++) {
1846 for (let j = 0; j < 3; j++) {
1847 console.log(i, j);
1848 }
1849 }
1850 "#};
1851 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1852 insta::with_settings!({
1853 sort_maps => true,
1854 description => js_source,
1855 omit_expression => true
1856 }, {
1857 insta::assert_yaml_snapshot!(context_stack_references);
1858 });
1859
1860 let result = build_report(&context_stack_references);
1861 insta::with_settings!({
1862 sort_maps => true,
1863 description => js_source,
1864 omit_expression => true
1865 }, {
1866 insta::assert_yaml_snapshot!(result);
1867 });
1868
1869 result;
1870 }
1871
1872 #[test]
1873 fn test_for_loop_with_let_declaration() {
1874 let js_source = indoc! { r#"
1875 for (let i = 0; i < 5; i++) {
1876 let x = i * 2;
1877 console.log(x);
1878 }
1879 "#};
1880 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1881 insta::with_settings!({
1882 sort_maps => true,
1883 description => js_source,
1884 omit_expression => true
1885 }, {
1886 insta::assert_yaml_snapshot!(context_stack_references);
1887 });
1888
1889 let result = build_report(&context_stack_references);
1890 insta::with_settings!({
1891 sort_maps => true,
1892 description => js_source,
1893 omit_expression => true
1894 }, {
1895 insta::assert_yaml_snapshot!(result);
1896 });
1897
1898 result;
1899 }
1900
1901 #[test]
1902 fn test_for_loop_with_const_declaration() {
1903 let js_source = indoc! { r#"
1904 for (let i = 0; i < 5; i++) {
1905 const y = i * 2;
1906 console.log(y);
1907 }
1908 "#};
1909 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1910 insta::with_settings!({
1911 sort_maps => true,
1912 description => js_source,
1913 omit_expression => true
1914 }, {
1915 insta::assert_yaml_snapshot!(context_stack_references);
1916 });
1917
1918 let result = build_report(&context_stack_references);
1919 insta::with_settings!({
1920 sort_maps => true,
1921 description => js_source,
1922 omit_expression => true
1923 }, {
1924 insta::assert_yaml_snapshot!(result);
1925 });
1926
1927 result;
1928 }
1929
1930 #[test]
1931 fn test_for_loop_with_var_declaration() {
1932 let js_source = indoc! { r#"
1933 for (var i = 0; i < 5; i++) {
1934 var z = i * 2;
1935 console.log(z);
1936 }
1937 "#};
1938 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1939 insta::with_settings!({
1940 sort_maps => true,
1941 description => js_source,
1942 omit_expression => true
1943 }, {
1944 insta::assert_yaml_snapshot!(context_stack_references);
1945 });
1946
1947 let result = build_report(&context_stack_references);
1948 insta::with_settings!({
1949 sort_maps => true,
1950 description => js_source,
1951 omit_expression => true
1952 }, {
1953 insta::assert_yaml_snapshot!(result);
1954 });
1955
1956 result;
1957 }
1958
1959 #[test]
1960 fn test_for_of_loop_with_destructuring() {
1961 let js_source = indoc! { r#"
1962 const pairs = [[1, 'one'], [2, 'two'], [3, 'three']];
1963 for (const [num, word] of pairs) {
1964 console.log(num, word);
1965 }
1966 "#};
1967 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1968 insta::with_settings!({
1969 sort_maps => true,
1970 description => js_source,
1971 omit_expression => true
1972 }, {
1973 insta::assert_yaml_snapshot!(context_stack_references);
1974 });
1975
1976 let result = build_report(&context_stack_references);
1977 insta::with_settings!({
1978 sort_maps => true,
1979 description => js_source,
1980 omit_expression => true
1981 }, {
1982 insta::assert_yaml_snapshot!(result);
1983 });
1984
1985 result;
1986 }
1987
1988 #[test]
1989 fn test_for_of_loop_with_object_destructuring() {
1990 let js_source = indoc! { r#"
1991 const pairs = [{a: 1, b:2}];
1992 for (const {a, b} of pairs) {
1993 console.log(a, b);
1994 }
1995 "#};
1996 let context_stack_references = extract_dependencies_js(js_source).unwrap();
1997 insta::with_settings!({
1998 sort_maps => true,
1999 description => js_source,
2000 omit_expression => true
2001 }, {
2002 insta::assert_yaml_snapshot!(context_stack_references);
2003 });
2004
2005 let result = build_report(&context_stack_references);
2006 insta::with_settings!({
2007 sort_maps => true,
2008 description => js_source,
2009 omit_expression => true
2010 }, {
2011 insta::assert_yaml_snapshot!(result);
2012 });
2013
2014 result;
2015 }
2016}
2017
2018
2019
2020#[cfg(test)]
2021mod anonymous_function_tests {
2022 use super::*;
2023 use indoc::indoc;
2024
2025 #[test]
2026 fn test_basic_anonymous_function() {
2027 let js_source = indoc! { r#"
2028 const func = function(x, y) {
2029 return x + y;
2030 };
2031 func(1, 2);
2032 "#};
2033 let context_stack_references = extract_dependencies_js(js_source).unwrap();
2034 let result = build_report(&context_stack_references);
2035
2036 assert!(result.cell_depended_values.is_empty(), "Anonymous function parameters should not be depended upon");
2037 assert!(result.cell_exposed_values.contains_key("func"), "The function should be exposed");
2038 }
2039
2040 #[test]
2041 fn test_arrow_function() {
2042 let js_source = indoc! { r#"
2043 const arrowFunc = (a, b) => a * b;
2044 arrowFunc(3, 4);
2045 "#};
2046 let context_stack_references = extract_dependencies_js(js_source).unwrap();
2047 let result = build_report(&context_stack_references);
2048
2049 assert!(result.cell_depended_values.is_empty(), "Arrow function parameters should not be depended upon");
2050 assert!(result.cell_exposed_values.contains_key("arrowFunc"), "The arrow function should be exposed");
2051 }
2052
2053 #[test]
2054 fn test_iife() {
2055 let js_source = indoc! { r#"
2056 (function(x) {
2057 console.log(x);
2058 })(5);
2059 "#};
2060 let context_stack_references = extract_dependencies_js(js_source).unwrap();
2061 let result = build_report(&context_stack_references);
2062
2063 assert!(result.cell_depended_values.is_empty(), "IIFE parameters should not be depended upon");
2064 }
2065
2066 #[test]
2067 fn test_function_as_argument() {
2068 let js_source = indoc! { r#"
2069 function higherOrder(callback) {
2070 callback(10);
2071 }
2072 higherOrder(function(num) {
2073 return num * 2;
2074 });
2075 "#};
2076 let context_stack_references = extract_dependencies_js(js_source).unwrap();
2077 let result = build_report(&context_stack_references);
2078
2079 assert!(!result.cell_depended_values.contains_key("num"), "Callback parameter should not be depended upon");
2080 assert!(result.triggerable_functions.contains_key("higherOrder"), "The higher-order function should be exposed");
2081 }
2082
2083 #[test]
2084 fn test_nested_anonymous_functions() {
2085 let js_source = indoc! { r#"
2086 const nestedFunc = function(x) {
2087 return function(y) {
2088 return x + y;
2089 };
2090 };
2091 nestedFunc(5)(3);
2092 "#};
2093 let context_stack_references = extract_dependencies_js(js_source).unwrap();
2094 let result = build_report(&context_stack_references);
2095
2096 assert!(result.cell_depended_values.is_empty(), "Nested anonymous function parameters should not be depended upon");
2097 assert!(result.cell_exposed_values.contains_key("nestedFunc"), "The outer function should be exposed");
2098 }
2099
2100 #[test]
2101 fn test_anonymous_function_with_destructuring() {
2102 let js_source = indoc! { r#"
2103 const destructuringFunc = function({ a, b }) {
2104 return a + b;
2105 };
2106 destructuringFunc({ a: 1, b: 2 });
2107 "#};
2108 let context_stack_references = extract_dependencies_js(js_source).unwrap();
2109 let result = build_report(&context_stack_references);
2110
2111 assert!(result.cell_depended_values.is_empty(), "Destructured parameters should not be depended upon");
2112 assert!(result.cell_exposed_values.contains_key("destructuringFunc"), "The function should be exposed");
2113 }
2114
2115 #[test]
2116 fn test_anonymous_function_in_object() {
2117 let js_source = indoc! { r#"
2118 const obj = {
2119 method: function(x) {
2120 return x * x;
2121 }
2122 };
2123 obj.method(4);
2124 "#};
2125 let context_stack_references = extract_dependencies_js(js_source).unwrap();
2126 let result = build_report(&context_stack_references);
2127
2128 assert!(result.cell_depended_values.is_empty(), "Object method parameters should not be depended upon");
2129 assert!(result.cell_exposed_values.contains_key("obj"), "The object should be exposed");
2130 }
2131}