swc_ecma_minifier/
program_data.rs

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/// Analyzed info of a whole program we are working on.
37#[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        /// `false` if it's only used.
55        const DECLARED                  = 1 << 1;
56        /// `true` if the enclosing function defines this variable as a parameter.
57        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        /// The variable itself is assigned after reference.
62        const REASSIGNED                = 1 << 6;
63        const HAS_PROPERTY_ACCESS       = 1 << 7;
64        const EXPORTED                  = 1 << 8;
65        /// True if used **above** the declaration or in init. (Not eval order).
66        const USED_ABOVE_DECL           = 1 << 9;
67        /// `true` if it's declared by function parameters or variables declared in
68        /// a closest function and used only within it and not used by child
69        /// functions.
70        const IS_FN_LOCAL               = 1 << 10;
71        const USED_IN_NON_CHILD_FN      = 1 << 11;
72        /// `true` if all its assign happens in the same function scope it's defined
73        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        /// `a` in `foo(a)` or `foo({ a })`.
80        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        /// Is the variable declared in top level?
85        const IS_TOP_LEVEL              = 1 << 22;
86        const USED_RECURSIVELY          = 1 << 23;
87
88        /// `a` in `foo(<a />)`
89        const USED_AS_JSX_CALLEE       = 1 << 24;
90
91        /// The variable is declared without initializer.
92        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    /// The number of direct reference to this identifier.
107    pub(crate) ref_count: u32,
108    pub(crate) declared_count: u32,
109
110    /// The number of assign and initialization to this identifier.
111    pub(crate) assign_count: u32,
112
113    /// The number of direct and indirect reference to this identifier.
114    /// ## Things to note
115    ///
116    /// - Update is counted as usage, but assign is not
117    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`. This should be renamed, but it will be done with another
127    /// PR. (because it's hard to review)
128    infects_to: Vec<Access>,
129    /// Only **string** properties.
130    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    /// The variable itself or a property of it is modified.
159    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            // trace!("merge({:?},{}{:?})", kind, id.0, id.1);
231            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 it is inited in some other child scope and also inited in current
251                        // scope
252                        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                            // If it is referred outside child scope, it will
258                            // be marked as var_initialized false
259                            e.get_mut().flags.insert(VarUsageInfoFlags::VAR_INITIALIZED);
260                        }
261                    } else {
262                        // If it is inited in some other child scope, but referenced in
263                        // current child scope
264                        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 a var is registered at a parent scope, it means that it's delcared before
307                    // usages.
308                    //
309                    // e.get_mut().used_above_decl |= var_info.used_above_decl;
310
311                    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 it is inited in some child scope, but referenced in current scope
376        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        // if cfg!(feature = "debug") {
458        //     debug!(has_init = has_init, "declare_decl(`{}`)", i);
459        // }
460
461        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        // assigned or declared before this declaration
467        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        // This is not delcared yet, so this is the first declaration.
484        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        // not a VarDecl, thus always inited
512        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    /// This should be used only for conditionals pass.
668    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                // TODO
693                (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        // We treat `window` and `global` as resolved
748        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}