1use crate::ast::{DeclKind, Expr, Param, Prop, Stmt, StmtKind, SwitchCase};
47use rustc_hash::{FxHashMap, FxHashSet};
48
49pub type SlotTable = FxHashMap<String, u16>;
51
52#[derive(Default)]
54pub struct Plan {
55 pub table: SlotTable,
57 pub numeric: FxHashSet<String>,
60 pub consts: FxHashSet<String>,
65}
66
67fn is_number_literal(e: &Expr) -> bool {
70 match e {
71 Expr::Number(_) => true,
72 Expr::Unary(crate::ast::UnOp::Neg, inner) => matches!(**inner, Expr::Number(_)),
73 _ => false,
74 }
75}
76
77pub fn plan(params: &[Param], body: &[Stmt], top_level: bool) -> Plan {
82 if !chunk_is_eligible(body) {
83 return Plan::default();
84 }
85 let mut escaping = FxHashSet::default();
88 for s in body {
89 collect_escaping_stmt(s, &mut escaping);
90 }
91 let mut p = Planner {
92 candidates: SlotTable::default(),
93 rejected: escaping,
94 numeric: FxHashSet::default(),
95 consts: FxHashSet::default(),
96 top_level,
97 next: 0,
98 };
99 for name in param_names(params) {
103 p.declare(&name);
104 }
105 for s in body {
106 p.walk_stmt(s);
107 }
108 for name in p.rejected {
109 p.candidates.remove(&name);
110 p.numeric.remove(&name);
111 p.consts.remove(&name);
112 }
113 Plan {
114 table: p.candidates,
115 numeric: p.numeric,
116 consts: p.consts,
117 }
118}
119
120pub fn param_names(params: &[Param]) -> Vec<String> {
123 params
124 .iter()
125 .filter(|p| !p.rest)
126 .filter_map(|p| match &p.pattern {
127 Expr::Ident(n) => Some(n.clone()),
128 _ => None,
129 })
130 .collect()
131}
132
133fn chunk_is_eligible(body: &[Stmt]) -> bool {
140 !mentions_eval_stmts(body) && body.iter().all(stmt_slot_safe)
141}
142
143fn mentions_eval_stmts(body: &[Stmt]) -> bool {
146 let mut names = FxHashSet::default();
147 for s in body {
148 collect_all_idents_stmt(s, &mut names);
149 }
150 names.contains("eval")
151}
152
153fn stmt_slot_safe(s: &Stmt) -> bool {
156 match &s.kind {
157 StmtKind::Try {
160 block,
161 handler,
162 finalizer,
163 } => {
164 block.iter().all(stmt_slot_safe)
165 && handler
166 .as_ref()
167 .map_or(true, |(_, b)| b.iter().all(stmt_slot_safe))
168 && finalizer
169 .as_ref()
170 .map_or(true, |b| b.iter().all(stmt_slot_safe))
171 }
172 StmtKind::Expr(e) | StmtKind::Throw(e) => expr_slot_safe(e),
173 StmtKind::Return(e) => e.as_ref().map_or(true, expr_slot_safe),
174 StmtKind::Decl { decls, .. } => decls
175 .iter()
176 .all(|d| d.init.as_ref().map_or(true, expr_slot_safe)),
177 StmtKind::Block(body) => body.iter().all(stmt_slot_safe),
178 StmtKind::If { test, cons, alt } => {
179 expr_slot_safe(test)
180 && stmt_slot_safe(cons)
181 && alt.as_deref().map_or(true, stmt_slot_safe)
182 }
183 StmtKind::While { test, body } | StmtKind::DoWhile { body, test } => {
184 expr_slot_safe(test) && stmt_slot_safe(body)
185 }
186 StmtKind::For {
187 init,
188 test,
189 update,
190 body,
191 } => {
192 init.as_deref().map_or(true, stmt_slot_safe)
193 && test.as_ref().map_or(true, expr_slot_safe)
194 && update.as_ref().map_or(true, expr_slot_safe)
195 && stmt_slot_safe(body)
196 }
197 StmtKind::ForOf {
198 target,
199 iter,
200 body,
201 is_await,
202 ..
203 } => !*is_await && expr_slot_safe(target) && expr_slot_safe(iter) && stmt_slot_safe(body),
204 StmtKind::ForIn {
205 target,
206 object,
207 body,
208 ..
209 } => expr_slot_safe(target) && expr_slot_safe(object) && stmt_slot_safe(body),
210 StmtKind::Switch { disc, cases } => {
211 expr_slot_safe(disc)
212 && cases.iter().all(|c: &SwitchCase| {
213 c.test.as_ref().map_or(true, expr_slot_safe)
214 && c.body.iter().all(stmt_slot_safe)
215 })
216 }
217 StmtKind::Labeled { body, .. } => stmt_slot_safe(body),
218 StmtKind::Break(_) | StmtKind::Continue(_) | StmtKind::Empty => true,
219 StmtKind::FuncDecl { .. } | StmtKind::ClassDecl(_) => true,
223 }
224}
225
226fn expr_slot_safe(e: &Expr) -> bool {
227 let all = |xs: &[Expr]| xs.iter().all(expr_slot_safe);
228 match e {
229 Expr::Yield { .. } | Expr::Await(_) => false,
230 Expr::Unary(_, inner) | Expr::Spread(inner) => expr_slot_safe(inner),
233 Expr::Template { exprs, .. } => all(exprs),
234 Expr::TaggedTemplate { tag, exprs, .. } => expr_slot_safe(tag) && all(exprs),
235 Expr::Array(items) | Expr::Sequence(items) => all(items),
236 Expr::Object(props) => props.iter().all(|p| match p {
237 Prop::KeyValue { key, value, .. } => expr_slot_safe(key) && expr_slot_safe(value),
238 Prop::Spread(x) => expr_slot_safe(x),
239 Prop::Accessor { .. } => true,
242 }),
243 Expr::Logical(_, l, r) | Expr::Binary(_, l, r) => expr_slot_safe(l) && expr_slot_safe(r),
244 Expr::Conditional { test, cons, alt } => {
245 expr_slot_safe(test) && expr_slot_safe(cons) && expr_slot_safe(alt)
246 }
247 Expr::Assign { target, value } => expr_slot_safe(target) && expr_slot_safe(value),
248 Expr::Update { target, .. } => expr_slot_safe(target),
249 Expr::Call { func, args, .. } | Expr::New { callee: func, args } => {
250 expr_slot_safe(func) && all(args)
251 }
252 Expr::Member { object, .. } => expr_slot_safe(object),
253 Expr::Index { object, index, .. } => expr_slot_safe(object) && expr_slot_safe(index),
254 Expr::Function { .. } | Expr::Class(_) => true,
256 _ => true,
257 }
258}
259
260struct Planner {
261 candidates: SlotTable,
262 rejected: FxHashSet<String>,
263 numeric: FxHashSet<String>,
268 consts: FxHashSet<String>,
273 top_level: bool,
274 next: u16,
275}
276
277impl Planner {
278 fn declare(&mut self, name: &str) {
281 if self.rejected.contains(name) {
282 return;
283 }
284 if self.candidates.contains_key(name) {
285 self.reject(name);
286 return;
287 }
288 if self.next == u16::MAX {
291 self.reject(name);
292 return;
293 }
294 self.candidates.insert(name.to_string(), self.next);
295 self.next += 1;
296 }
297
298 fn reject(&mut self, name: &str) {
299 self.rejected.insert(name.to_string());
300 }
301
302 fn mention(&mut self, name: &str) {
305 if !self.candidates.contains_key(name) {
306 self.reject(name);
307 }
308 }
309
310 fn declare_target(&mut self, target: &Expr, kind: Option<DeclKind>) {
312 self.declare_target_init(target, kind, None)
313 }
314
315 fn declare_target_init(&mut self, target: &Expr, kind: Option<DeclKind>, init: Option<&Expr>) {
318 let Expr::Ident(n) = target else {
319 self.reject_names_in(target);
322 return;
323 };
324 match kind {
325 Some(DeclKind::Var) if self.top_level => self.reject(n),
327 Some(k) => {
328 self.declare(n);
329 if init.is_some_and(is_number_literal) {
330 self.numeric.insert(n.clone());
331 }
332 if k == DeclKind::Const {
333 self.consts.insert(n.clone());
334 }
335 }
336 None => self.mention(n),
339 }
340 }
341
342 fn reject_names_in(&mut self, e: &Expr) {
343 let mut names = Vec::new();
344 collect_idents(e, &mut names);
345 for n in names {
346 self.reject(&n);
347 }
348 }
349
350 fn walk_stmt(&mut self, s: &Stmt) {
351 match &s.kind {
352 StmtKind::Decl { kind, decls } => {
353 for d in decls {
354 if let Some(init) = &d.init {
356 self.walk_expr(init);
357 }
358 match &d.init {
359 None => self.reject_names_in(&d.target),
363 Some(init) => self.declare_target_init(&d.target, Some(*kind), Some(init)),
364 }
365 }
366 }
367 StmtKind::Expr(e) | StmtKind::Throw(e) => self.walk_expr(e),
368 StmtKind::Return(e) => {
369 if let Some(e) = e {
370 self.walk_expr(e);
371 }
372 }
373 StmtKind::Block(body) => {
374 for s in body {
375 self.walk_stmt(s);
376 }
377 }
378 StmtKind::If { test, cons, alt } => {
379 self.walk_expr(test);
380 self.walk_stmt(cons);
381 if let Some(alt) = alt {
382 self.walk_stmt(alt);
383 }
384 }
385 StmtKind::While { test, body } => {
386 self.walk_expr(test);
387 self.walk_stmt(body);
388 }
389 StmtKind::DoWhile { body, test } => {
390 self.walk_stmt(body);
391 self.walk_expr(test);
392 }
393 StmtKind::For {
394 init,
395 test,
396 update,
397 body,
398 } => {
399 if let Some(init) = init {
400 self.walk_stmt(init);
401 }
402 if let Some(test) = test {
403 self.walk_expr(test);
404 }
405 self.walk_stmt(body);
406 if let Some(update) = update {
407 self.walk_expr(update);
408 }
409 }
410 StmtKind::ForOf {
411 decl_kind,
412 target,
413 iter,
414 body,
415 ..
416 } => {
417 self.walk_expr(iter);
418 self.declare_target(target, *decl_kind);
419 self.walk_stmt(body);
420 }
421 StmtKind::ForIn {
422 decl_kind,
423 target,
424 object,
425 body,
426 } => {
427 self.walk_expr(object);
428 self.declare_target(target, *decl_kind);
429 self.walk_stmt(body);
430 }
431 StmtKind::Switch { disc, cases } => {
432 self.walk_expr(disc);
433 for c in cases {
434 if let Some(t) = &c.test {
435 self.walk_expr(t);
436 }
437 for s in &c.body {
438 self.walk_stmt(s);
439 }
440 }
441 }
442 StmtKind::Labeled { body, .. } => self.walk_stmt(body),
443 StmtKind::Try { .. } | StmtKind::FuncDecl { .. } | StmtKind::ClassDecl(_) => {}
445 StmtKind::Break(_) | StmtKind::Continue(_) | StmtKind::Empty => {}
446 }
447 }
448
449 fn walk_expr(&mut self, e: &Expr) {
450 match e {
451 Expr::Ident(n) => self.mention(n),
452 Expr::Assign { target, value } => {
455 self.walk_expr(value);
456 match &**target {
457 Expr::Ident(n) => {
460 self.numeric.remove(n);
461 self.mention(n);
462 }
463 other => self.walk_expr(other),
464 }
465 }
466 Expr::Update { target, .. } => self.walk_expr(target),
467 Expr::Unary(crate::ast::UnOp::Delete, inner) => {
469 if let Expr::Ident(n) = &**inner {
470 self.reject(n);
471 } else {
472 self.walk_expr(inner);
473 }
474 }
475 Expr::Unary(_, inner) | Expr::Spread(inner) | Expr::Await(inner) => {
476 self.walk_expr(inner)
477 }
478 Expr::Yield { arg: Some(a), .. } => self.walk_expr(a),
479 Expr::Template { exprs, .. } => self.walk_all(exprs),
480 Expr::TaggedTemplate { tag, exprs, .. } => {
481 self.walk_expr(tag);
482 self.walk_all(exprs);
483 }
484 Expr::Array(items) | Expr::Sequence(items) => self.walk_all(items),
485 Expr::Object(props) => {
486 for p in props {
487 match p {
488 Prop::KeyValue { key, value, .. } => {
489 self.walk_expr(key);
490 self.walk_expr(value);
491 }
492 Prop::Spread(x) => self.walk_expr(x),
493 Prop::Accessor { key, func, .. } => {
494 self.walk_expr(key);
495 self.walk_expr(func);
496 }
497 }
498 }
499 }
500 Expr::Logical(_, l, r) | Expr::Binary(_, l, r) => {
501 self.walk_expr(l);
502 self.walk_expr(r);
503 }
504 Expr::Conditional { test, cons, alt } => {
505 self.walk_expr(test);
506 self.walk_expr(cons);
507 self.walk_expr(alt);
508 }
509 Expr::Call { func, args, .. } | Expr::New { callee: func, args } => {
510 self.walk_expr(func);
511 self.walk_all(args);
512 }
513 Expr::Member { object, .. } => self.walk_expr(object),
514 Expr::Index { object, index, .. } => {
515 self.walk_expr(object);
516 self.walk_expr(index);
517 }
518 Expr::Function { .. } | Expr::Class(_) => {}
521 _ => {}
522 }
523 }
524
525 fn walk_all(&mut self, items: &[Expr]) {
526 for e in items {
527 self.walk_expr(e);
528 }
529 }
530}
531
532fn collect_escaping_stmt(s: &Stmt, out: &mut FxHashSet<String>) {
541 match &s.kind {
542 StmtKind::FuncDecl { name, .. } => {
544 out.insert(name.clone());
545 collect_all_idents_stmt(s, out);
546 }
547 StmtKind::ClassDecl(c) => {
548 if let Some(n) = &c.name {
549 out.insert(n.clone());
550 }
551 collect_all_idents_stmt(s, out);
552 }
553 StmtKind::Try {
554 block,
555 handler,
556 finalizer,
557 } => {
558 for st in block {
559 collect_all_idents_stmt(st, out);
560 }
561 if let Some((bind, body)) = handler {
562 if let Some(p) = bind {
563 collect_all_idents_expr(p, out);
564 }
565 for st in body {
566 collect_all_idents_stmt(st, out);
567 }
568 }
569 if let Some(body) = finalizer {
570 for st in body {
571 collect_all_idents_stmt(st, out);
572 }
573 }
574 }
575 StmtKind::Expr(e) | StmtKind::Throw(e) => collect_escaping_expr(e, out),
576 StmtKind::Return(e) => {
577 if let Some(e) = e {
578 collect_escaping_expr(e, out);
579 }
580 }
581 StmtKind::Decl { decls, .. } => {
582 for d in decls {
583 if let Some(init) = &d.init {
584 collect_escaping_expr(init, out);
585 }
586 }
587 }
588 StmtKind::Block(body) => {
589 for st in body {
590 collect_escaping_stmt(st, out);
591 }
592 }
593 StmtKind::If { test, cons, alt } => {
594 collect_escaping_expr(test, out);
595 collect_escaping_stmt(cons, out);
596 if let Some(alt) = alt {
597 collect_escaping_stmt(alt, out);
598 }
599 }
600 StmtKind::While { test, body } | StmtKind::DoWhile { body, test } => {
601 collect_escaping_expr(test, out);
602 collect_escaping_stmt(body, out);
603 }
604 StmtKind::For {
605 init,
606 test,
607 update,
608 body,
609 } => {
610 if let Some(init) = init {
611 collect_escaping_stmt(init, out);
612 }
613 if let Some(test) = test {
614 collect_escaping_expr(test, out);
615 }
616 if let Some(update) = update {
617 collect_escaping_expr(update, out);
618 }
619 collect_escaping_stmt(body, out);
620 }
621 StmtKind::ForOf {
622 target, iter, body, ..
623 } => {
624 collect_escaping_expr(target, out);
625 collect_escaping_expr(iter, out);
626 collect_escaping_stmt(body, out);
627 }
628 StmtKind::ForIn {
629 target,
630 object,
631 body,
632 ..
633 } => {
634 collect_escaping_expr(target, out);
635 collect_escaping_expr(object, out);
636 collect_escaping_stmt(body, out);
637 }
638 StmtKind::Switch { disc, cases } => {
639 collect_escaping_expr(disc, out);
640 for c in cases {
641 if let Some(t) = &c.test {
642 collect_escaping_expr(t, out);
643 }
644 for st in &c.body {
645 collect_escaping_stmt(st, out);
646 }
647 }
648 }
649 StmtKind::Labeled { body, .. } => collect_escaping_stmt(body, out),
650 StmtKind::Break(_) | StmtKind::Continue(_) | StmtKind::Empty => {}
651 }
652}
653
654fn collect_escaping_expr(e: &Expr, out: &mut FxHashSet<String>) {
655 match e {
656 Expr::Function { .. } | Expr::Class(_) => collect_all_idents_expr(e, out),
659 Expr::Ident(_) | Expr::Null | Expr::Undefined | Expr::True | Expr::False => {}
660 Expr::Unary(_, x) | Expr::Spread(x) | Expr::Await(x) | Expr::Member { object: x, .. } => {
661 collect_escaping_expr(x, out)
662 }
663 Expr::Yield { arg: Some(a), .. } => collect_escaping_expr(a, out),
664 Expr::Template { exprs, .. } => exprs.iter().for_each(|x| collect_escaping_expr(x, out)),
665 Expr::TaggedTemplate { tag, exprs, .. } => {
666 collect_escaping_expr(tag, out);
667 exprs.iter().for_each(|x| collect_escaping_expr(x, out));
668 }
669 Expr::Array(items) | Expr::Sequence(items) => {
670 items.iter().for_each(|x| collect_escaping_expr(x, out))
671 }
672 Expr::Object(props) => {
673 for p in props {
674 match p {
675 Prop::KeyValue { key, value, .. } => {
676 collect_escaping_expr(key, out);
677 collect_escaping_expr(value, out);
678 }
679 Prop::Spread(x) => collect_escaping_expr(x, out),
680 Prop::Accessor { key, func, .. } => {
681 collect_escaping_expr(key, out);
682 collect_all_idents_expr(func, out);
683 }
684 }
685 }
686 }
687 Expr::Logical(_, l, r) | Expr::Binary(_, l, r) => {
688 collect_escaping_expr(l, out);
689 collect_escaping_expr(r, out);
690 }
691 Expr::Conditional { test, cons, alt } => {
692 collect_escaping_expr(test, out);
693 collect_escaping_expr(cons, out);
694 collect_escaping_expr(alt, out);
695 }
696 Expr::Assign { target, value } => {
697 collect_escaping_expr(target, out);
698 collect_escaping_expr(value, out);
699 }
700 Expr::Update { target, .. } => collect_escaping_expr(target, out),
701 Expr::Call { func, args, .. } | Expr::New { callee: func, args } => {
702 collect_escaping_expr(func, out);
703 args.iter().for_each(|x| collect_escaping_expr(x, out));
704 }
705 Expr::Index { object, index, .. } => {
706 collect_escaping_expr(object, out);
707 collect_escaping_expr(index, out);
708 }
709 _ => {}
710 }
711}
712
713fn collect_all_idents_stmt(s: &Stmt, out: &mut FxHashSet<String>) {
715 match &s.kind {
716 StmtKind::FuncDecl { params, body, .. } => {
717 for p in params {
718 collect_all_idents_expr(&p.pattern, out);
719 if let Some(d) = &p.default {
720 collect_all_idents_expr(d, out);
721 }
722 }
723 body.iter().for_each(|st| collect_all_idents_stmt(st, out));
724 }
725 StmtKind::ClassDecl(c) => collect_class_idents(c, out),
726 StmtKind::Expr(e) | StmtKind::Throw(e) => collect_all_idents_expr(e, out),
727 StmtKind::Return(e) => {
728 if let Some(e) = e {
729 collect_all_idents_expr(e, out);
730 }
731 }
732 StmtKind::Decl { decls, .. } => {
733 for d in decls {
734 collect_all_idents_expr(&d.target, out);
735 if let Some(init) = &d.init {
736 collect_all_idents_expr(init, out);
737 }
738 }
739 }
740 StmtKind::Block(body) => body.iter().for_each(|st| collect_all_idents_stmt(st, out)),
741 StmtKind::If { test, cons, alt } => {
742 collect_all_idents_expr(test, out);
743 collect_all_idents_stmt(cons, out);
744 if let Some(alt) = alt {
745 collect_all_idents_stmt(alt, out);
746 }
747 }
748 StmtKind::While { test, body } | StmtKind::DoWhile { body, test } => {
749 collect_all_idents_expr(test, out);
750 collect_all_idents_stmt(body, out);
751 }
752 StmtKind::For {
753 init,
754 test,
755 update,
756 body,
757 } => {
758 if let Some(init) = init {
759 collect_all_idents_stmt(init, out);
760 }
761 if let Some(test) = test {
762 collect_all_idents_expr(test, out);
763 }
764 if let Some(update) = update {
765 collect_all_idents_expr(update, out);
766 }
767 collect_all_idents_stmt(body, out);
768 }
769 StmtKind::ForOf {
770 target, iter, body, ..
771 } => {
772 collect_all_idents_expr(target, out);
773 collect_all_idents_expr(iter, out);
774 collect_all_idents_stmt(body, out);
775 }
776 StmtKind::ForIn {
777 target,
778 object,
779 body,
780 ..
781 } => {
782 collect_all_idents_expr(target, out);
783 collect_all_idents_expr(object, out);
784 collect_all_idents_stmt(body, out);
785 }
786 StmtKind::Switch { disc, cases } => {
787 collect_all_idents_expr(disc, out);
788 for c in cases {
789 if let Some(t) = &c.test {
790 collect_all_idents_expr(t, out);
791 }
792 c.body
793 .iter()
794 .for_each(|st| collect_all_idents_stmt(st, out));
795 }
796 }
797 StmtKind::Labeled { body, .. } => collect_all_idents_stmt(body, out),
798 StmtKind::Try {
799 block,
800 handler,
801 finalizer,
802 } => {
803 block.iter().for_each(|st| collect_all_idents_stmt(st, out));
804 if let Some((bind, body)) = handler {
805 if let Some(p) = bind {
806 collect_all_idents_expr(p, out);
807 }
808 body.iter().for_each(|st| collect_all_idents_stmt(st, out));
809 }
810 if let Some(body) = finalizer {
811 body.iter().for_each(|st| collect_all_idents_stmt(st, out));
812 }
813 }
814 StmtKind::Break(_) | StmtKind::Continue(_) | StmtKind::Empty => {}
815 }
816}
817
818fn collect_class_idents(c: &crate::ast::ClassNode, out: &mut FxHashSet<String>) {
819 if let Some(p) = &c.parent {
820 collect_all_idents_expr(p, out);
821 }
822 for m in &c.members {
823 collect_all_idents_expr(&m.key, out);
824 for p in &m.params {
825 collect_all_idents_expr(&p.pattern, out);
826 if let Some(d) = &p.default {
827 collect_all_idents_expr(d, out);
828 }
829 }
830 m.body
831 .iter()
832 .for_each(|st| collect_all_idents_stmt(st, out));
833 if let Some(init) = &m.field_init {
834 collect_all_idents_expr(init, out);
835 }
836 }
837}
838
839fn collect_all_idents_expr(e: &Expr, out: &mut FxHashSet<String>) {
840 let all = |xs: &[Expr], out: &mut FxHashSet<String>| {
841 xs.iter().for_each(|x| collect_all_idents_expr(x, out))
842 };
843 match e {
844 Expr::Ident(n) => {
845 out.insert(n.clone());
846 }
847 Expr::Class(c) => collect_class_idents(c, out),
848 Expr::Function { params, body, .. } => {
849 for p in params {
850 collect_all_idents_expr(&p.pattern, out);
851 if let Some(d) = &p.default {
852 collect_all_idents_expr(d, out);
853 }
854 }
855 match body {
856 crate::ast::FnBody::Block(stmts) => {
857 stmts.iter().for_each(|st| collect_all_idents_stmt(st, out))
858 }
859 crate::ast::FnBody::Expr(x) => collect_all_idents_expr(x, out),
860 }
861 }
862 Expr::Unary(_, x) | Expr::Spread(x) | Expr::Await(x) | Expr::Member { object: x, .. } => {
863 collect_all_idents_expr(x, out)
864 }
865 Expr::Yield { arg: Some(a), .. } => collect_all_idents_expr(a, out),
866 Expr::Template { exprs, .. } => all(exprs, out),
867 Expr::TaggedTemplate { tag, exprs, .. } => {
868 collect_all_idents_expr(tag, out);
869 all(exprs, out);
870 }
871 Expr::Array(items) | Expr::Sequence(items) => all(items, out),
872 Expr::Object(props) => {
873 for p in props {
874 match p {
875 Prop::KeyValue { key, value, .. } => {
876 collect_all_idents_expr(key, out);
877 collect_all_idents_expr(value, out);
878 }
879 Prop::Spread(x) => collect_all_idents_expr(x, out),
880 Prop::Accessor { key, func, .. } => {
881 collect_all_idents_expr(key, out);
882 collect_all_idents_expr(func, out);
883 }
884 }
885 }
886 }
887 Expr::Logical(_, l, r) | Expr::Binary(_, l, r) => {
888 collect_all_idents_expr(l, out);
889 collect_all_idents_expr(r, out);
890 }
891 Expr::Conditional { test, cons, alt } => {
892 collect_all_idents_expr(test, out);
893 collect_all_idents_expr(cons, out);
894 collect_all_idents_expr(alt, out);
895 }
896 Expr::Assign { target, value } => {
897 collect_all_idents_expr(target, out);
898 collect_all_idents_expr(value, out);
899 }
900 Expr::Update { target, .. } => collect_all_idents_expr(target, out),
901 Expr::Call { func, args, .. } | Expr::New { callee: func, args } => {
902 collect_all_idents_expr(func, out);
903 all(args, out);
904 }
905 Expr::Index { object, index, .. } => {
906 collect_all_idents_expr(object, out);
907 collect_all_idents_expr(index, out);
908 }
909 _ => {}
910 }
911}
912
913fn collect_idents(e: &Expr, out: &mut Vec<String>) {
915 match e {
916 Expr::Ident(n) => out.push(n.clone()),
917 Expr::Array(items) | Expr::Sequence(items) => {
918 for x in items {
919 collect_idents(x, out);
920 }
921 }
922 Expr::Object(props) => {
923 for p in props {
924 match p {
925 Prop::KeyValue { value, .. } => collect_idents(value, out),
926 Prop::Spread(x) => collect_idents(x, out),
927 Prop::Accessor { .. } => {}
928 }
929 }
930 }
931 Expr::Spread(inner) => collect_idents(inner, out),
932 Expr::Assign { target, .. } => collect_idents(target, out),
933 _ => {}
934 }
935}