1use std::collections::hash_map::Entry;
2
3use indexmap::IndexSet;
4use rustc_hash::{FxBuildHasher, FxHashMap};
5use swc_atoms::Wtf8Atom;
6use swc_common::SyntaxContext;
7use swc_ecma_ast::*;
8use swc_ecma_usage_analyzer::{
9 alias::{Access, AccessKind},
10 analyzer::{
11 analyze_with_custom_storage,
12 storage::{ScopeDataLike, Storage, VarDataLike},
13 Ctx, ScopeKind, UsageAnalyzer,
14 },
15 marks::Marks,
16 util::is_global_var_with_pure_property_access,
17};
18use swc_ecma_utils::{Merge, Type, Value};
19use swc_ecma_visit::VisitWith;
20
21pub(crate) fn analyze<N>(n: &N, marks: Option<Marks>, collect_property_atoms: bool) -> ProgramData
22where
23 N: VisitWith<UsageAnalyzer<ProgramData>>,
24{
25 let data = if collect_property_atoms {
26 ProgramData {
27 property_atoms: Some(Vec::with_capacity(128)),
28 ..Default::default()
29 }
30 } else {
31 ProgramData::default()
32 };
33 analyze_with_custom_storage(data, n, marks)
34}
35
36#[derive(Debug, Default)]
38pub(crate) struct ProgramData {
39 pub(crate) vars: FxHashMap<Id, Box<VarUsageInfo>>,
40
41 pub(crate) top: ScopeData,
42
43 pub(crate) scopes: FxHashMap<SyntaxContext, ScopeData>,
44
45 initialized_vars: IndexSet<Id, FxBuildHasher>,
46
47 pub(crate) property_atoms: Option<Vec<Wtf8Atom>>,
48}
49
50bitflags::bitflags! {
51 #[derive(Debug, Clone, Copy)]
52 pub(crate) struct VarUsageInfoFlags: u32 {
53 const INLINE_PREVENTED = 1 << 0;
54 const DECLARED = 1 << 1;
56 const DECLARED_AS_FN_PARAM = 1 << 2;
58 const DECLARED_AS_FN_DECL = 1 << 3;
59 const DECLARED_AS_FN_EXPR = 1 << 4;
60 const DECLARED_AS_FOR_INIT = 1 << 5;
61 const REASSIGNED = 1 << 6;
63 const HAS_PROPERTY_ACCESS = 1 << 7;
64 const EXPORTED = 1 << 8;
65 const USED_ABOVE_DECL = 1 << 9;
67 const IS_FN_LOCAL = 1 << 10;
71 const USED_IN_NON_CHILD_FN = 1 << 11;
72 const ASSIGNED_FN_LOCAL = 1 << 12;
74 const EXECUTED_MULTIPLE_TIME = 1 << 13;
75 const USED_IN_COND = 1 << 14;
76 const VAR_INITIALIZED = 1 << 15;
77 const DECLARED_AS_CATCH_PARAM = 1 << 16;
78 const NO_SIDE_EFFECT_FOR_MEMBER_ACCESS = 1 << 17;
79 const USED_AS_REF = 1 << 18;
81 const USED_AS_ARG = 1 << 19;
82 const INDEXED_WITH_DYNAMIC_KEY = 1 << 20;
83 const PURE_FN = 1 << 21;
84 const IS_TOP_LEVEL = 1 << 22;
86 const USED_RECURSIVELY = 1 << 23;
87
88 const USED_AS_JSX_CALLEE = 1 << 24;
90
91 const LAZY_INIT = 1 << 25;
93 }
94
95 #[derive(Debug, Default, Clone, Copy)]
96 pub(crate) struct ScopeData: u8 {
97 const HAS_WITH_STMT = 1 << 0;
98 const HAS_EVAL_CALL = 1 << 1;
99 const USED_ARGUMENTS = 1 << 2;
100 }
101}
102
103#[derive(Debug, Clone)]
104pub(crate) struct VarUsageInfo {
105 pub(crate) flags: VarUsageInfoFlags,
106 pub(crate) ref_count: u32,
108 pub(crate) declared_count: u32,
109
110 pub(crate) assign_count: u32,
112
113 pub(crate) usage_count: u32,
118
119 pub(crate) property_mutation_count: u32,
120
121 pub(crate) var_kind: Option<VarDeclKind>,
122 pub(crate) merged_var_type: Option<Value<Type>>,
123
124 pub(crate) callee_count: u32,
125
126 infects_to: Vec<Access>,
129 pub(crate) accessed_props: FxHashMap<Wtf8Atom, u32>,
131}
132
133impl Default for VarUsageInfo {
134 fn default() -> Self {
135 const DEFAULT_FLAGS: VarUsageInfoFlags =
136 VarUsageInfoFlags::ASSIGNED_FN_LOCAL.union(VarUsageInfoFlags::IS_FN_LOCAL);
137 Self {
138 flags: DEFAULT_FLAGS,
139 ref_count: Default::default(),
140 declared_count: Default::default(),
141 assign_count: Default::default(),
142 usage_count: Default::default(),
143 property_mutation_count: Default::default(),
144 var_kind: Default::default(),
145 merged_var_type: Default::default(),
146 callee_count: Default::default(),
147 infects_to: Default::default(),
148 accessed_props: Default::default(),
149 }
150 }
151}
152
153impl VarUsageInfo {
154 pub(crate) fn is_infected(&self) -> bool {
155 !self.infects_to.is_empty()
156 }
157
158 pub(crate) fn mutated(&self) -> bool {
160 self.assign_count > 1 || self.property_mutation_count > 0
161 }
162
163 pub(crate) fn can_inline_fn_once(&self) -> bool {
164 (self.callee_count > 0
165 || !self
166 .flags
167 .contains(VarUsageInfoFlags::EXECUTED_MULTIPLE_TIME)
168 && (self.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL)
169 || !self.flags.contains(VarUsageInfoFlags::USED_IN_NON_CHILD_FN)))
170 && !self.flags.contains(VarUsageInfoFlags::USED_AS_JSX_CALLEE)
171 && !(self.flags.contains(
172 VarUsageInfoFlags::USED_RECURSIVELY.union(VarUsageInfoFlags::HAS_PROPERTY_ACCESS),
173 ) && self.property_mutation_count != 0)
174 }
175
176 fn initialized(&self) -> bool {
177 self.flags.intersects(
178 VarUsageInfoFlags::VAR_INITIALIZED
179 .union(VarUsageInfoFlags::DECLARED_AS_FN_PARAM)
180 .union(VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM),
181 )
182 }
183}
184
185impl Storage for ProgramData {
186 type ScopeData = ScopeData;
187 type VarData = VarUsageInfo;
188
189 fn new(collect_prop_atom: bool) -> Self {
190 if collect_prop_atom {
191 ProgramData {
192 property_atoms: Some(Vec::with_capacity(128)),
193 ..Default::default()
194 }
195 } else {
196 ProgramData::default()
197 }
198 }
199
200 #[inline(always)]
201 fn need_collect_prop_atom(&self) -> bool {
202 self.property_atoms.is_some()
203 }
204
205 fn scope(&mut self, ctxt: swc_common::SyntaxContext) -> &mut Self::ScopeData {
206 self.scopes.entry(ctxt).or_default()
207 }
208
209 fn top_scope(&mut self) -> &mut Self::ScopeData {
210 &mut self.top
211 }
212
213 fn var_or_default(&mut self, id: Id) -> &mut Self::VarData {
214 self.vars.entry(id).or_default()
215 }
216
217 fn merge(&mut self, kind: ScopeKind, child: Self) {
218 self.scopes.reserve(child.scopes.len());
219
220 for (ctxt, scope) in child.scopes {
221 let to = self.scopes.entry(ctxt).or_default();
222 self.top.merge(scope, true);
223
224 to.merge(scope, false);
225 }
226
227 self.vars.reserve(child.vars.len());
228
229 for (id, mut var_info) in child.vars {
230 let inited = self.initialized_vars.contains(&id);
232 match self.vars.entry(id) {
233 Entry::Occupied(mut e) => {
234 if var_info.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) {
235 e.get_mut()
236 .flags
237 .insert(VarUsageInfoFlags::INLINE_PREVENTED);
238 }
239 let var_assigned = var_info.assign_count > 0
240 || (var_info.flags.contains(VarUsageInfoFlags::VAR_INITIALIZED)
241 && !e.get().flags.contains(VarUsageInfoFlags::VAR_INITIALIZED));
242
243 if var_info.assign_count > 0 {
244 if e.get().initialized() {
245 e.get_mut().flags.insert(VarUsageInfoFlags::REASSIGNED);
246 }
247 }
248
249 if var_info.flags.contains(VarUsageInfoFlags::VAR_INITIALIZED) {
250 if e.get().flags.contains(VarUsageInfoFlags::VAR_INITIALIZED)
253 || e.get().ref_count > 0
254 {
255 e.get_mut().flags.insert(VarUsageInfoFlags::REASSIGNED);
256 } else {
257 e.get_mut().flags.insert(VarUsageInfoFlags::VAR_INITIALIZED);
260 }
261 } else {
262 if !inited
265 && e.get().flags.contains(VarUsageInfoFlags::VAR_INITIALIZED)
266 && var_info.ref_count > 0
267 {
268 e.get_mut().flags.remove(VarUsageInfoFlags::VAR_INITIALIZED);
269 e.get_mut().flags.insert(VarUsageInfoFlags::REASSIGNED);
270 }
271 }
272
273 e.get_mut().merged_var_type.merge(var_info.merged_var_type);
274
275 e.get_mut().ref_count += var_info.ref_count;
276 e.get_mut().property_mutation_count |= var_info.property_mutation_count;
277 e.get_mut().declared_count += var_info.declared_count;
278 e.get_mut().assign_count += var_info.assign_count;
279 e.get_mut().usage_count += var_info.usage_count;
280 e.get_mut().infects_to.extend(var_info.infects_to);
281 e.get_mut().callee_count += var_info.callee_count;
282
283 for (k, v) in var_info.accessed_props {
284 *e.get_mut().accessed_props.entry(k).or_default() += v;
285 }
286
287 let var_info_flags = var_info.flags;
288 let e_flags = &mut e.get_mut().flags;
289 *e_flags |= var_info_flags & VarUsageInfoFlags::REASSIGNED;
290 *e_flags |= var_info_flags & VarUsageInfoFlags::HAS_PROPERTY_ACCESS;
291 *e_flags |= var_info_flags & VarUsageInfoFlags::EXPORTED;
292 *e_flags |= var_info_flags & VarUsageInfoFlags::DECLARED;
293 *e_flags |= var_info_flags & VarUsageInfoFlags::DECLARED_AS_FN_PARAM;
294 *e_flags |= var_info_flags & VarUsageInfoFlags::DECLARED_AS_FN_DECL;
295 *e_flags |= var_info_flags & VarUsageInfoFlags::DECLARED_AS_FN_EXPR;
296 *e_flags |= var_info_flags & VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM;
297 *e_flags |= var_info_flags & VarUsageInfoFlags::EXECUTED_MULTIPLE_TIME;
298 *e_flags |= var_info_flags & VarUsageInfoFlags::USED_IN_COND;
299 *e_flags |= var_info_flags & VarUsageInfoFlags::USED_AS_ARG;
300 *e_flags |= var_info_flags & VarUsageInfoFlags::USED_AS_REF;
301 *e_flags |= var_info_flags & VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY;
302 *e_flags |= var_info_flags & VarUsageInfoFlags::PURE_FN;
303 *e_flags |= var_info_flags & VarUsageInfoFlags::USED_RECURSIVELY;
304 *e_flags |= var_info_flags & VarUsageInfoFlags::USED_IN_NON_CHILD_FN;
305
306 if !var_info_flags.contains(VarUsageInfoFlags::NO_SIDE_EFFECT_FOR_MEMBER_ACCESS)
312 {
313 e_flags.remove(VarUsageInfoFlags::NO_SIDE_EFFECT_FOR_MEMBER_ACCESS);
314 }
315 if !var_info_flags.contains(VarUsageInfoFlags::IS_FN_LOCAL) {
316 e_flags.remove(VarUsageInfoFlags::IS_FN_LOCAL);
317 }
318 if !var_info_flags.contains(VarUsageInfoFlags::ASSIGNED_FN_LOCAL) {
319 e_flags.remove(VarUsageInfoFlags::ASSIGNED_FN_LOCAL);
320 }
321
322 match kind {
323 ScopeKind::Fn => {
324 e_flags.remove(VarUsageInfoFlags::IS_FN_LOCAL);
325 if !var_info_flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) {
326 e_flags.insert(VarUsageInfoFlags::USED_IN_NON_CHILD_FN);
327 }
328 if var_assigned {
329 e_flags.remove(VarUsageInfoFlags::ASSIGNED_FN_LOCAL);
330 }
331 }
332 ScopeKind::Block => {
333 if e_flags.contains(VarUsageInfoFlags::USED_IN_NON_CHILD_FN) {
334 e_flags.remove(VarUsageInfoFlags::IS_FN_LOCAL);
335 e_flags.insert(VarUsageInfoFlags::USED_IN_NON_CHILD_FN);
336 }
337 }
338 }
339 }
340 Entry::Vacant(e) => {
341 match kind {
342 ScopeKind::Fn => {
343 if !var_info.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) {
344 var_info
345 .flags
346 .insert(VarUsageInfoFlags::USED_IN_NON_CHILD_FN);
347 }
348 }
349 ScopeKind::Block => {}
350 }
351 e.insert(var_info);
352 }
353 }
354 }
355
356 if let Some(property_atoms) = self.property_atoms.as_mut() {
357 property_atoms.extend(child.property_atoms.unwrap());
358 }
359 }
360
361 fn report_usage(&mut self, ctx: Ctx, i: Id) {
362 let inited = self.initialized_vars.contains(&i);
363
364 let e = self.vars.entry(i).or_insert_with(|| {
365 let mut default = VarUsageInfo::default();
366 default.flags.insert(VarUsageInfoFlags::USED_ABOVE_DECL);
367 Box::new(default)
368 });
369
370 if ctx.is_id_ref() {
371 e.flags.insert(VarUsageInfoFlags::USED_AS_REF);
372 }
373 e.ref_count += 1;
374 e.usage_count += 1;
375 if !inited && e.flags.contains(VarUsageInfoFlags::VAR_INITIALIZED) {
377 e.flags.insert(VarUsageInfoFlags::REASSIGNED);
378 e.flags.remove(VarUsageInfoFlags::VAR_INITIALIZED);
379 }
380 if ctx.inline_prevented() {
381 e.flags.insert(VarUsageInfoFlags::INLINE_PREVENTED);
382 }
383 if ctx.executed_multiple_time() {
384 e.flags.insert(VarUsageInfoFlags::EXECUTED_MULTIPLE_TIME);
385 }
386 if ctx.in_cond() {
387 e.flags.insert(VarUsageInfoFlags::USED_IN_COND);
388 }
389 }
390
391 fn report_assign(&mut self, ctx: Ctx, i: Id, is_op: bool, ty: Value<Type>) {
392 let e = self.vars.entry(i.clone()).or_default();
393
394 let inited = self.initialized_vars.contains(&i);
395
396 if e.assign_count > 0 || e.initialized() {
397 e.flags.insert(VarUsageInfoFlags::REASSIGNED);
398 }
399
400 e.merged_var_type.merge(Some(ty));
401 e.assign_count += 1;
402
403 if !is_op {
404 self.initialized_vars.insert(i.clone());
405 if e.ref_count == 1 && e.var_kind != Some(VarDeclKind::Const) && !inited {
406 e.flags.insert(VarUsageInfoFlags::VAR_INITIALIZED);
407 } else {
408 e.flags.insert(VarUsageInfoFlags::REASSIGNED);
409 }
410
411 if e.ref_count == 1 && e.flags.contains(VarUsageInfoFlags::USED_ABOVE_DECL) {
412 e.flags.remove(VarUsageInfoFlags::USED_ABOVE_DECL);
413 }
414
415 e.usage_count = e.usage_count.saturating_sub(1);
416 }
417
418 let mut to_visit: IndexSet<Id, FxBuildHasher> =
419 IndexSet::from_iter(e.infects_to.iter().cloned().map(|i| i.0));
420
421 let mut idx = 0;
422
423 while idx < to_visit.len() {
424 let curr = &to_visit[idx];
425
426 if let Some(usage) = self.vars.get_mut(curr) {
427 if ctx.inline_prevented() {
428 usage.flags.insert(VarUsageInfoFlags::INLINE_PREVENTED);
429 }
430 if ctx.executed_multiple_time() {
431 usage
432 .flags
433 .insert(VarUsageInfoFlags::EXECUTED_MULTIPLE_TIME);
434 }
435 if ctx.in_cond() {
436 usage.flags.insert(VarUsageInfoFlags::USED_IN_COND);
437 }
438
439 if is_op {
440 usage.usage_count += 1;
441 }
442
443 to_visit.extend(usage.infects_to.iter().cloned().map(|i| i.0))
444 }
445
446 idx += 1;
447 }
448 }
449
450 fn declare_decl(
451 &mut self,
452 ctx: Ctx,
453 i: &Ident,
454 init_type: Option<Value<Type>>,
455 kind: Option<VarDeclKind>,
456 ) -> &mut VarUsageInfo {
457 let v = self.vars.entry(i.to_id()).or_default();
462 if ctx.is_top_level() {
463 v.flags |= VarUsageInfoFlags::IS_TOP_LEVEL;
464 }
465
466 if init_type.is_some() {
468 if v.flags
469 .intersects(VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::VAR_INITIALIZED))
470 || v.assign_count > 0
471 {
472 #[cfg(feature = "debug")]
473 {
474 tracing::trace!("declare_decl(`{}`): Already declared", i);
475 }
476
477 v.flags |= VarUsageInfoFlags::REASSIGNED;
478 }
479
480 v.assign_count += 1;
481 }
482
483 if !v.flags.contains(VarUsageInfoFlags::DECLARED) {
485 v.var_kind = kind;
486 if ctx.in_decl_with_no_side_effect_for_member_access() {
487 v.flags
488 .insert(VarUsageInfoFlags::NO_SIDE_EFFECT_FOR_MEMBER_ACCESS);
489 } else {
490 v.flags
491 .remove(VarUsageInfoFlags::NO_SIDE_EFFECT_FOR_MEMBER_ACCESS);
492 }
493 }
494
495 if v.flags.contains(VarUsageInfoFlags::USED_IN_NON_CHILD_FN) {
496 v.flags.remove(VarUsageInfoFlags::IS_FN_LOCAL);
497 }
498
499 if init_type.is_some() {
500 v.flags.insert(VarUsageInfoFlags::VAR_INITIALIZED);
501 }
502
503 if ctx.in_pat_of_param() {
504 v.merged_var_type = Some(Value::Unknown);
505 } else {
506 v.merged_var_type.merge(init_type);
507 }
508
509 v.declared_count += 1;
510 v.flags |= VarUsageInfoFlags::DECLARED;
511 if init_type.is_some() || kind.is_none() {
513 self.initialized_vars.insert(i.to_id());
514 }
515 if ctx.in_catch_param() {
516 v.flags |= VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM;
517 }
518
519 v
520 }
521
522 fn get_initialized_cnt(&self) -> usize {
523 self.initialized_vars.len()
524 }
525
526 fn truncate_initialized_cnt(&mut self, len: usize) {
527 self.initialized_vars.truncate(len)
528 }
529
530 fn mark_property_mutation(&mut self, id: Id) {
531 let e = self.vars.entry(id).or_default();
532 e.property_mutation_count += 1;
533
534 let to_mark_mutate = e
535 .infects_to
536 .iter()
537 .filter(|(_, kind)| *kind == AccessKind::Reference)
538 .map(|(id, _)| id.clone())
539 .collect::<Vec<_>>();
540
541 for other in to_mark_mutate {
542 let other = self.vars.entry(other).or_default();
543
544 other.property_mutation_count += 1;
545 }
546 }
547
548 fn add_property_atom(&mut self, atom: Wtf8Atom) {
549 if let Some(atoms) = self.property_atoms.as_mut() {
550 atoms.push(atom);
551 }
552 }
553
554 fn get_var_data(&self, id: Id) -> Option<&Self::VarData> {
555 self.vars.get(&id).map(|v| v.as_ref())
556 }
557}
558
559impl ScopeDataLike for ScopeData {
560 fn add_declared_symbol(&mut self, _: &Ident) {}
561
562 fn merge(&mut self, other: Self, _: bool) {
563 *self |= other & Self::HAS_WITH_STMT;
564 *self |= other & Self::HAS_EVAL_CALL;
565 *self |= other & Self::USED_ARGUMENTS;
566 }
567
568 fn mark_used_arguments(&mut self) {
569 *self |= Self::USED_ARGUMENTS;
570 }
571
572 fn mark_eval_called(&mut self) {
573 *self |= Self::HAS_EVAL_CALL;
574 }
575
576 fn mark_with_stmt(&mut self) {
577 *self |= Self::HAS_WITH_STMT;
578 }
579}
580
581impl VarDataLike for VarUsageInfo {
582 fn mark_declared_as_fn_param(&mut self) {
583 self.flags.insert(VarUsageInfoFlags::DECLARED_AS_FN_PARAM);
584 }
585
586 fn mark_as_lazy_init(&mut self) {
587 self.flags.insert(VarUsageInfoFlags::LAZY_INIT);
588 }
589
590 fn mark_declared_as_fn_decl(&mut self) {
591 self.flags.insert(VarUsageInfoFlags::DECLARED_AS_FN_DECL);
592 }
593
594 fn mark_declared_as_fn_expr(&mut self) {
595 self.flags.insert(VarUsageInfoFlags::DECLARED_AS_FN_EXPR);
596 }
597
598 fn mark_declared_as_for_init(&mut self) {
599 self.flags.insert(VarUsageInfoFlags::DECLARED_AS_FOR_INIT);
600 }
601
602 fn mark_has_property_access(&mut self) {
603 self.flags.insert(VarUsageInfoFlags::HAS_PROPERTY_ACCESS);
604 }
605
606 fn mark_used_as_callee(&mut self) {
607 self.callee_count += 1;
608 }
609
610 fn mark_used_as_arg(&mut self) {
611 self.flags.insert(VarUsageInfoFlags::USED_AS_REF);
612 self.flags.insert(VarUsageInfoFlags::USED_AS_ARG);
613 }
614
615 fn mark_indexed_with_dynamic_key(&mut self) {
616 self.flags
617 .insert(VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY);
618 }
619
620 fn add_accessed_property(&mut self, name: swc_atoms::Wtf8Atom) {
621 *self.accessed_props.entry(name).or_default() += 1;
622 }
623
624 fn mark_used_as_ref(&mut self) {
625 self.flags.insert(VarUsageInfoFlags::USED_AS_REF);
626 }
627
628 fn add_infects_to(&mut self, other: Access) {
629 self.infects_to.push(other);
630 }
631
632 fn prevent_inline(&mut self) {
633 self.flags.insert(VarUsageInfoFlags::INLINE_PREVENTED);
634 }
635
636 fn mark_as_exported(&mut self) {
637 self.flags.insert(VarUsageInfoFlags::EXPORTED);
638 }
639
640 fn mark_initialized_with_safe_value(&mut self) {
641 self.flags
642 .insert(VarUsageInfoFlags::NO_SIDE_EFFECT_FOR_MEMBER_ACCESS);
643 }
644
645 fn mark_as_pure_fn(&mut self) {
646 self.flags.insert(VarUsageInfoFlags::PURE_FN);
647 }
648
649 fn mark_used_above_decl(&mut self) {
650 self.flags.insert(VarUsageInfoFlags::USED_ABOVE_DECL);
651 }
652
653 fn mark_used_recursively(&mut self) {
654 self.flags.insert(VarUsageInfoFlags::USED_RECURSIVELY);
655 }
656
657 fn is_declared(&self) -> bool {
658 self.flags.contains(VarUsageInfoFlags::DECLARED)
659 }
660
661 fn mark_used_as_jsx_callee(&mut self) {
662 self.flags.insert(VarUsageInfoFlags::USED_AS_JSX_CALLEE);
663 }
664}
665
666impl ProgramData {
667 pub(crate) fn contains_unresolved(&self, e: &Expr) -> bool {
669 match e {
670 Expr::Ident(i) => self.ident_is_unresolved(i),
671
672 Expr::Member(MemberExpr { obj, prop, .. }) => {
673 if self.contains_unresolved(obj) {
674 return true;
675 }
676
677 if let MemberProp::Computed(prop) = prop {
678 if self.contains_unresolved(&prop.expr) {
679 return true;
680 }
681 }
682
683 false
684 }
685 Expr::Bin(BinExpr { left, right, .. }) => {
686 self.contains_unresolved(left) || self.contains_unresolved(right)
687 }
688 Expr::Unary(UnaryExpr { arg, .. }) => self.contains_unresolved(arg),
689 Expr::Update(UpdateExpr { arg, .. }) => self.contains_unresolved(arg),
690 Expr::Seq(SeqExpr { exprs, .. }) => exprs.iter().any(|e| self.contains_unresolved(e)),
691 Expr::Assign(AssignExpr { left, right, .. }) => {
692 (match left {
694 AssignTarget::Simple(left) => {
695 self.simple_assign_target_contains_unresolved(left)
696 }
697 AssignTarget::Pat(_) => false,
698 #[cfg(swc_ast_unknown)]
699 _ => panic!("unable to access unknown nodes"),
700 }) || self.contains_unresolved(right)
701 }
702 Expr::Cond(CondExpr {
703 test, cons, alt, ..
704 }) => {
705 self.contains_unresolved(test)
706 || self.contains_unresolved(cons)
707 || self.contains_unresolved(alt)
708 }
709 Expr::New(NewExpr { args, .. }) => args.iter().flatten().any(|arg| match arg.spread {
710 Some(..) => self.contains_unresolved(&arg.expr),
711 None => false,
712 }),
713 Expr::Yield(YieldExpr { arg, .. }) => {
714 matches!(arg, Some(arg) if self.contains_unresolved(arg))
715 }
716 Expr::Tpl(Tpl { exprs, .. }) => exprs.iter().any(|e| self.contains_unresolved(e)),
717 Expr::Paren(ParenExpr { expr, .. }) => self.contains_unresolved(expr),
718 Expr::Await(AwaitExpr { arg, .. }) => self.contains_unresolved(arg),
719 Expr::Array(ArrayLit { elems, .. }) => elems.iter().any(|elem| match elem {
720 Some(elem) => self.contains_unresolved(&elem.expr),
721 None => false,
722 }),
723
724 Expr::Call(CallExpr {
725 callee: Callee::Expr(callee),
726 args,
727 ..
728 }) => {
729 if self.contains_unresolved(callee) {
730 return true;
731 }
732
733 if args.iter().any(|arg| self.contains_unresolved(&arg.expr)) {
734 return true;
735 }
736
737 false
738 }
739
740 Expr::OptChain(o) => self.opt_chain_expr_contains_unresolved(o),
741
742 _ => false,
743 }
744 }
745
746 pub(crate) fn ident_is_unresolved(&self, i: &Ident) -> bool {
747 if is_global_var_with_pure_property_access(&i.sym)
749 || matches!(&*i.sym, "arguments" | "window" | "global")
750 {
751 return false;
752 }
753
754 if let Some(v) = self.vars.get(&i.to_id()) {
755 return !v.flags.contains(VarUsageInfoFlags::DECLARED);
756 }
757
758 true
759 }
760
761 fn opt_chain_expr_contains_unresolved(&self, o: &OptChainExpr) -> bool {
762 match &*o.base {
763 OptChainBase::Member(me) => self.member_expr_contains_unresolved(me),
764 OptChainBase::Call(OptCall { callee, args, .. }) => {
765 if self.contains_unresolved(callee) {
766 return true;
767 }
768
769 if args.iter().any(|arg| self.contains_unresolved(&arg.expr)) {
770 return true;
771 }
772
773 false
774 }
775 #[cfg(swc_ast_unknown)]
776 _ => panic!("unable to access unknown nodes"),
777 }
778 }
779
780 fn member_expr_contains_unresolved(&self, n: &MemberExpr) -> bool {
781 if self.contains_unresolved(&n.obj) {
782 return true;
783 }
784
785 if let MemberProp::Computed(prop) = &n.prop {
786 if self.contains_unresolved(&prop.expr) {
787 return true;
788 }
789 }
790
791 false
792 }
793
794 fn simple_assign_target_contains_unresolved(&self, n: &SimpleAssignTarget) -> bool {
795 match n {
796 SimpleAssignTarget::Ident(i) => self.ident_is_unresolved(&i.id),
797 SimpleAssignTarget::Member(me) => self.member_expr_contains_unresolved(me),
798 SimpleAssignTarget::SuperProp(n) => {
799 if let SuperProp::Computed(prop) = &n.prop {
800 if self.contains_unresolved(&prop.expr) {
801 return true;
802 }
803 }
804
805 false
806 }
807 SimpleAssignTarget::Paren(n) => self.contains_unresolved(&n.expr),
808 SimpleAssignTarget::OptChain(n) => self.opt_chain_expr_contains_unresolved(n),
809 SimpleAssignTarget::TsAs(..)
810 | SimpleAssignTarget::TsSatisfies(..)
811 | SimpleAssignTarget::TsNonNull(..)
812 | SimpleAssignTarget::TsTypeAssertion(..)
813 | SimpleAssignTarget::TsInstantiation(..) => false,
814 SimpleAssignTarget::Invalid(..) => true,
815 #[cfg(swc_ast_unknown)]
816 _ => panic!("unable to access unknown nodes"),
817 }
818 }
819}