1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use swc_atoms::JsWord;
use swc_common::collections::{AHashMap, AHashSet};
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};

#[derive(Debug, Default)]
pub(crate) struct ImportMap {
    /// Map from module name to (module path, exported symbol)
    imports: AHashMap<Id, (JsWord, JsWord)>,

    namespace_imports: AHashMap<Id, JsWord>,

    imported_modules: AHashSet<JsWord>,
}

impl ImportMap {
    pub fn is_module_imported(&mut self, module: &JsWord) -> bool {
        self.imported_modules.contains(module)
    }

    /// Returns true if `e` is an import of `orig_name` from `module`.
    pub fn is_import(&self, e: &Expr, module: &str, orig_name: &str) -> bool {
        match e {
            Expr::Ident(i) => {
                if let Some((i_src, i_sym)) = self.imports.get(&i.to_id()) {
                    i_src == module && i_sym == orig_name
                } else {
                    false
                }
            }

            Expr::Member(MemberExpr {
                obj: box Expr::Ident(obj),
                prop: MemberProp::Ident(prop),
                ..
            }) => {
                if let Some(obj_src) = self.namespace_imports.get(&obj.to_id()) {
                    obj_src == module && prop.sym == *orig_name
                } else {
                    false
                }
            }

            _ => false,
        }
    }

    pub fn analyze(m: &Module) -> Self {
        let mut data = ImportMap::default();

        m.visit_with(&mut Analyzer { data: &mut data });

        data
    }
}

struct Analyzer<'a> {
    data: &'a mut ImportMap,
}

impl Visit for Analyzer<'_> {
    noop_visit_type!();

    fn visit_import_decl(&mut self, import: &ImportDecl) {
        self.data.imported_modules.insert(import.src.value.clone());

        for s in &import.specifiers {
            let (local, orig_sym) = match s {
                ImportSpecifier::Named(ImportNamedSpecifier {
                    local, imported, ..
                }) => match imported {
                    Some(imported) => (local.to_id(), orig_name(imported)),
                    _ => (local.to_id(), local.sym.clone()),
                },
                ImportSpecifier::Default(s) => (s.local.to_id(), "default".into()),
                ImportSpecifier::Namespace(s) => {
                    self.data
                        .namespace_imports
                        .insert(s.local.to_id(), import.src.value.clone());
                    continue;
                }
            };

            self.data
                .imports
                .insert(local, (import.src.value.clone(), orig_sym));
        }
    }
}

fn orig_name(n: &ModuleExportName) -> JsWord {
    match n {
        ModuleExportName::Ident(v) => v.sym.clone(),
        ModuleExportName::Str(v) => v.value.clone(),
    }
}