turbopack_ecmascript/analyzer/
imports.rs

1use std::{collections::BTreeMap, fmt::Display};
2
3use once_cell::sync::Lazy;
4use rustc_hash::{FxHashMap, FxHashSet};
5use swc_core::{
6    common::{BytePos, Span, Spanned, comments::Comments, source_map::SmallPos},
7    ecma::{
8        ast::*,
9        atoms::{Atom, atom},
10        utils::find_pat_ids,
11        visit::{Visit, VisitWith},
12    },
13};
14use turbo_rcstr::RcStr;
15use turbo_tasks::{FxIndexMap, FxIndexSet, ResolvedVc};
16use turbopack_core::{issue::IssueSource, source::Source};
17
18use super::{JsValue, ModuleValue, top_level_await::has_top_level_await};
19use crate::{
20    SpecifiedModuleType,
21    analyzer::{ConstantValue, ObjectPart},
22    tree_shake::{PartId, find_turbopack_part_id_in_asserts},
23};
24
25#[turbo_tasks::value(serialization = "auto_for_input")]
26#[derive(Default, Debug, Clone, Hash)]
27pub struct ImportAnnotations {
28    // TODO store this in more structured way
29    #[turbo_tasks(trace_ignore)]
30    map: BTreeMap<Atom, Atom>,
31}
32
33/// Enables a specified transition for the annotated import
34static ANNOTATION_TRANSITION: Lazy<Atom> =
35    Lazy::new(|| crate::annotations::ANNOTATION_TRANSITION.into());
36
37/// Changes the chunking type for the annotated import
38static ANNOTATION_CHUNKING_TYPE: Lazy<Atom> =
39    Lazy::new(|| crate::annotations::ANNOTATION_CHUNKING_TYPE.into());
40
41/// Changes the type of the resolved module (only "json" is supported currently)
42static ATTRIBUTE_MODULE_TYPE: Lazy<Atom> = Lazy::new(|| "type".into());
43
44impl ImportAnnotations {
45    pub fn parse(with: Option<&ObjectLit>) -> ImportAnnotations {
46        let Some(with) = with else {
47            return ImportAnnotations::default();
48        };
49
50        let mut map = BTreeMap::new();
51
52        // The `with` clause is way more restrictive than `ObjectLit`, it only allows
53        // string -> value and value can only be a string.
54        // We just ignore everything else here till the SWC ast is more restrictive.
55        for (key, value) in with.props.iter().filter_map(|prop| {
56            let kv = prop.as_prop()?.as_key_value()?;
57
58            let Lit::Str(str) = kv.value.as_lit()? else {
59                return None;
60            };
61
62            Some((&kv.key, str))
63        }) {
64            let key = match key {
65                PropName::Ident(ident) => ident.sym.as_str(),
66                PropName::Str(str) => str.value.as_str(),
67                // the rest are invalid, ignore for now till SWC ast is correct
68                _ => continue,
69            };
70
71            map.insert(key.into(), value.value.as_str().into());
72        }
73
74        ImportAnnotations { map }
75    }
76
77    pub fn parse_dynamic(with: &JsValue) -> Option<ImportAnnotations> {
78        let mut map = BTreeMap::new();
79
80        let JsValue::Object { parts, .. } = with else {
81            return None;
82        };
83
84        for part in parts.iter() {
85            let ObjectPart::KeyValue(key, value) = part else {
86                continue;
87            };
88            let (
89                JsValue::Constant(ConstantValue::Str(key)),
90                JsValue::Constant(ConstantValue::Str(value)),
91            ) = (key, value)
92            else {
93                continue;
94            };
95
96            map.insert(key.as_str().into(), value.as_str().into());
97        }
98
99        Some(ImportAnnotations { map })
100    }
101
102    /// Returns the content on the transition annotation
103    pub fn transition(&self) -> Option<&str> {
104        self.get(&ANNOTATION_TRANSITION)
105    }
106
107    /// Returns the content on the chunking-type annotation
108    pub fn chunking_type(&self) -> Option<&str> {
109        self.get(&ANNOTATION_CHUNKING_TYPE)
110    }
111
112    /// Returns the content on the type attribute
113    pub fn module_type(&self) -> Option<&str> {
114        self.get(&ATTRIBUTE_MODULE_TYPE)
115    }
116
117    pub fn get(&self, key: &Atom) -> Option<&str> {
118        self.map.get(key).map(|w| w.as_str())
119    }
120}
121
122impl Display for ImportAnnotations {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        let mut it = self.map.iter();
125        if let Some((k, v)) = it.next() {
126            write!(f, "{{ {k}: {v}")?
127        } else {
128            return f.write_str("{}");
129        };
130        for (k, v) in it {
131            write!(f, ", {k}: {v}")?
132        }
133        f.write_str(" }")
134    }
135}
136
137#[derive(Debug)]
138pub(crate) enum Reexport {
139    Star,
140    Namespace { exported: Atom },
141    Named { imported: Atom, exported: Atom },
142}
143
144/// The storage for all kinds of imports.
145///
146/// Note that when it's initialized by calling `analyze`, it only contains ESM
147/// import/exports.
148#[derive(Default, Debug)]
149pub(crate) struct ImportMap {
150    /// Map from identifier to (index in references, exported symbol)
151    imports: FxIndexMap<Id, (usize, Atom)>,
152
153    /// Map from identifier to index in references
154    namespace_imports: FxIndexMap<Id, usize>,
155
156    /// List of (index in references, imported symbol, exported symbol)
157    reexports: Vec<(usize, Reexport)>,
158
159    /// Ordered list of imported symbols
160    references: FxIndexSet<ImportMapReference>,
161
162    /// True, when the module has imports
163    has_imports: bool,
164
165    /// True, when the module has exports
166    has_exports: bool,
167
168    /// True if the module is an ESM module due to top-level await.
169    has_top_level_await: bool,
170
171    /// Locations of [webpack-style "magic comments"][magic] that override import behaviors.
172    ///
173    /// Most commonly, these are `/* webpackIgnore: true */` comments. See [ImportAttributes] for
174    /// full details.
175    ///
176    /// [magic]: https://webpack.js.org/api/module-methods/#magic-comments
177    attributes: FxHashMap<BytePos, ImportAttributes>,
178
179    /// The module specifiers of star imports that are accessed dynamically and should be imported
180    /// as a whole.
181    full_star_imports: FxHashSet<Atom>,
182
183    pub(crate) exports: FxHashMap<RcStr, Id>,
184}
185
186/// Represents a collection of [webpack-style "magic comments"][magic] that override import
187/// behaviors.
188///
189/// [magic]: https://webpack.js.org/api/module-methods/#magic-comments
190#[derive(Debug)]
191pub struct ImportAttributes {
192    /// Should we ignore this import expression when bundling? If so, the import expression will be
193    /// left as-is in Turbopack's output.
194    ///
195    /// This is set by using either a `webpackIgnore` or `turbopackIgnore` comment.
196    ///
197    /// Example:
198    /// ```js
199    /// const a = import(/* webpackIgnore: true */ "a");
200    /// const b = import(/* turbopackIgnore: true */ "b");
201    /// ```
202    pub ignore: bool,
203}
204
205impl ImportAttributes {
206    pub const fn empty() -> Self {
207        ImportAttributes { ignore: false }
208    }
209
210    pub fn empty_ref() -> &'static Self {
211        // use `Self::empty` here as `Default::default` isn't const
212        static DEFAULT_VALUE: ImportAttributes = ImportAttributes::empty();
213        &DEFAULT_VALUE
214    }
215}
216
217impl Default for ImportAttributes {
218    fn default() -> Self {
219        ImportAttributes::empty()
220    }
221}
222
223impl Default for &ImportAttributes {
224    fn default() -> Self {
225        ImportAttributes::empty_ref()
226    }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Hash)]
230pub(crate) enum ImportedSymbol {
231    ModuleEvaluation,
232    Symbol(Atom),
233    Exports,
234    Part(u32),
235    PartEvaluation(u32),
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, Hash)]
239pub(crate) struct ImportMapReference {
240    pub module_path: Atom,
241    pub imported_symbol: ImportedSymbol,
242    pub annotations: ImportAnnotations,
243    pub issue_source: Option<IssueSource>,
244}
245
246impl ImportMap {
247    pub fn is_esm(&self, specified_type: SpecifiedModuleType) -> bool {
248        if self.has_exports {
249            return true;
250        }
251
252        match specified_type {
253            SpecifiedModuleType::Automatic => {
254                self.has_exports || self.has_imports || self.has_top_level_await
255            }
256            SpecifiedModuleType::CommonJs => false,
257            SpecifiedModuleType::EcmaScript => true,
258        }
259    }
260
261    pub fn get_import(&self, id: &Id) -> Option<JsValue> {
262        if let Some((i, i_sym)) = self.imports.get(id) {
263            let r = &self.references[*i];
264            return Some(JsValue::member(
265                Box::new(JsValue::Module(ModuleValue {
266                    module: r.module_path.clone(),
267                    annotations: r.annotations.clone(),
268                })),
269                Box::new(i_sym.clone().into()),
270            ));
271        }
272        if let Some(i) = self.namespace_imports.get(id) {
273            let r = &self.references[*i];
274            return Some(JsValue::Module(ModuleValue {
275                module: r.module_path.clone(),
276                annotations: r.annotations.clone(),
277            }));
278        }
279        None
280    }
281
282    pub fn get_attributes(&self, span: Span) -> &ImportAttributes {
283        self.attributes.get(&span.lo).unwrap_or_default()
284    }
285
286    // TODO this could return &str instead of String to avoid cloning
287    pub fn get_binding(&self, id: &Id) -> Option<(usize, Option<RcStr>)> {
288        if let Some((i, i_sym)) = self.imports.get(id) {
289            return Some((*i, Some(i_sym.as_str().into())));
290        }
291        if let Some(i) = self.namespace_imports.get(id) {
292            return Some((*i, None));
293        }
294        None
295    }
296
297    pub fn references(&self) -> impl ExactSizeIterator<Item = &ImportMapReference> {
298        self.references.iter()
299    }
300
301    pub fn reexports(&self) -> impl ExactSizeIterator<Item = (usize, &Reexport)> {
302        self.reexports.iter().map(|(i, r)| (*i, r))
303    }
304
305    /// Analyze ES import
306    pub(super) fn analyze(
307        m: &Program,
308        source: Option<ResolvedVc<Box<dyn Source>>>,
309        comments: Option<&dyn Comments>,
310    ) -> Self {
311        let mut data = ImportMap::default();
312
313        // We have to analyze imports first to determine if a star import is dynamic.
314        // We can't do this in the visitor because import may (and likely) comes before usages, and
315        // a method invoked after visitor will not work because we need to preserve the import
316        // order.
317
318        if let Program::Module(m) = m {
319            let mut candidates = FxIndexMap::default();
320
321            // Imports are hoisted to the top of the module.
322            // So we have to collect all imports first.
323            m.body.iter().for_each(|stmt| {
324                if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = stmt {
325                    for s in &import.specifiers {
326                        if let ImportSpecifier::Namespace(s) = s {
327                            candidates.insert(s.local.to_id(), import.src.value.clone());
328                        }
329                    }
330                }
331            });
332
333            let mut analyzer = StarImportAnalyzer {
334                candidates,
335                full_star_imports: &mut data.full_star_imports,
336            };
337            m.visit_with(&mut analyzer);
338        }
339
340        let mut analyzer = Analyzer {
341            data: &mut data,
342            source,
343            comments,
344        };
345        m.visit_with(&mut analyzer);
346
347        data
348    }
349
350    pub(crate) fn should_import_all(&self, esm_reference_index: usize) -> bool {
351        let r = &self.references[esm_reference_index];
352
353        self.full_star_imports.contains(&r.module_path)
354    }
355}
356
357struct StarImportAnalyzer<'a> {
358    /// The local identifiers of the star imports
359    candidates: FxIndexMap<Id, Atom>,
360    full_star_imports: &'a mut FxHashSet<Atom>,
361}
362
363impl Visit for StarImportAnalyzer<'_> {
364    fn visit_expr(&mut self, node: &Expr) {
365        if let Expr::Ident(i) = node {
366            if let Some(module_path) = self.candidates.get(&i.to_id()) {
367                self.full_star_imports.insert(module_path.clone());
368                return;
369            }
370        }
371
372        node.visit_children_with(self);
373    }
374
375    fn visit_import_decl(&mut self, _: &ImportDecl) {}
376
377    fn visit_member_expr(&mut self, node: &MemberExpr) {
378        match &node.prop {
379            MemberProp::Ident(..) | MemberProp::PrivateName(..) => {
380                if node.obj.is_ident() {
381                    return;
382                }
383                // We can skip `visit_expr(obj)` because it's not a dynamic access
384                node.obj.visit_children_with(self);
385            }
386            MemberProp::Computed(..) => {
387                node.obj.visit_with(self);
388                node.prop.visit_with(self);
389            }
390        }
391    }
392
393    fn visit_pat(&mut self, pat: &Pat) {
394        if let Pat::Ident(i) = pat {
395            if let Some(module_path) = self.candidates.get(&i.to_id()) {
396                self.full_star_imports.insert(module_path.clone());
397                return;
398            }
399        }
400
401        pat.visit_children_with(self);
402    }
403
404    fn visit_simple_assign_target(&mut self, node: &SimpleAssignTarget) {
405        if let SimpleAssignTarget::Ident(i) = node {
406            if let Some(module_path) = self.candidates.get(&i.to_id()) {
407                self.full_star_imports.insert(module_path.clone());
408                return;
409            }
410        }
411
412        node.visit_children_with(self);
413    }
414}
415
416struct Analyzer<'a> {
417    data: &'a mut ImportMap,
418    source: Option<ResolvedVc<Box<dyn Source>>>,
419    comments: Option<&'a dyn Comments>,
420}
421
422impl Analyzer<'_> {
423    fn ensure_reference(
424        &mut self,
425        span: Span,
426        module_path: Atom,
427        imported_symbol: ImportedSymbol,
428        annotations: ImportAnnotations,
429    ) -> Option<usize> {
430        let issue_source = self
431            .source
432            .map(|s| IssueSource::from_swc_offsets(s, span.lo.to_u32(), span.hi.to_u32()));
433
434        let r = ImportMapReference {
435            module_path,
436            imported_symbol,
437            issue_source,
438            annotations,
439        };
440        if let Some(i) = self.data.references.get_index_of(&r) {
441            Some(i)
442        } else {
443            let i = self.data.references.len();
444            self.data.references.insert(r);
445            Some(i)
446        }
447    }
448}
449
450fn to_word(name: &ModuleExportName) -> Atom {
451    match name {
452        ModuleExportName::Ident(ident) => ident.sym.clone(),
453        ModuleExportName::Str(str) => str.value.clone(),
454    }
455}
456
457impl Visit for Analyzer<'_> {
458    fn visit_import_decl(&mut self, import: &ImportDecl) {
459        self.data.has_imports = true;
460
461        let annotations = ImportAnnotations::parse(import.with.as_deref());
462
463        let internal_symbol = parse_with(import.with.as_deref());
464
465        if internal_symbol.is_none() {
466            self.ensure_reference(
467                import.span,
468                import.src.value.clone(),
469                ImportedSymbol::ModuleEvaluation,
470                annotations.clone(),
471            );
472        }
473
474        for s in &import.specifiers {
475            let symbol = internal_symbol
476                .clone()
477                .unwrap_or_else(|| get_import_symbol_from_import(s));
478            let i = self.ensure_reference(
479                import.span,
480                import.src.value.clone(),
481                symbol,
482                annotations.clone(),
483            );
484            let i = match i {
485                Some(v) => v,
486                None => continue,
487            };
488
489            let (local, orig_sym) = match s {
490                ImportSpecifier::Named(ImportNamedSpecifier {
491                    local, imported, ..
492                }) => match imported {
493                    Some(imported) => (local.to_id(), orig_name(imported)),
494                    _ => (local.to_id(), local.sym.clone()),
495                },
496                ImportSpecifier::Default(s) => (s.local.to_id(), "default".into()),
497                ImportSpecifier::Namespace(s) => {
498                    self.data.namespace_imports.insert(s.local.to_id(), i);
499                    continue;
500                }
501            };
502
503            self.data.imports.insert(local, (i, orig_sym));
504        }
505        if import.specifiers.is_empty() {
506            if let Some(internal_symbol) = internal_symbol {
507                self.ensure_reference(
508                    import.span,
509                    import.src.value.clone(),
510                    internal_symbol,
511                    annotations,
512                );
513            }
514        }
515    }
516
517    fn visit_export_all(&mut self, export: &ExportAll) {
518        self.data.has_exports = true;
519
520        let annotations = ImportAnnotations::parse(export.with.as_deref());
521
522        self.ensure_reference(
523            export.span,
524            export.src.value.clone(),
525            ImportedSymbol::ModuleEvaluation,
526            annotations.clone(),
527        );
528        let symbol = parse_with(export.with.as_deref());
529
530        let i = self.ensure_reference(
531            export.span,
532            export.src.value.clone(),
533            symbol.unwrap_or(ImportedSymbol::Exports),
534            annotations,
535        );
536        if let Some(i) = i {
537            self.data.reexports.push((i, Reexport::Star));
538        }
539    }
540
541    fn visit_named_export(&mut self, export: &NamedExport) {
542        self.data.has_exports = true;
543
544        let Some(ref src) = export.src else {
545            return;
546        };
547
548        let annotations = ImportAnnotations::parse(export.with.as_deref());
549
550        let internal_symbol = parse_with(export.with.as_deref());
551
552        if internal_symbol.is_none() || export.specifiers.is_empty() {
553            self.ensure_reference(
554                export.span,
555                src.value.clone(),
556                ImportedSymbol::ModuleEvaluation,
557                annotations.clone(),
558            );
559        }
560
561        for spec in export.specifiers.iter() {
562            let symbol = internal_symbol
563                .clone()
564                .unwrap_or_else(|| get_import_symbol_from_export(spec));
565
566            let i =
567                self.ensure_reference(export.span, src.value.clone(), symbol, annotations.clone());
568            let i = match i {
569                Some(v) => v,
570                None => continue,
571            };
572
573            match spec {
574                ExportSpecifier::Namespace(n) => {
575                    self.data.reexports.push((
576                        i,
577                        Reexport::Namespace {
578                            exported: to_word(&n.name),
579                        },
580                    ));
581                }
582                ExportSpecifier::Default(d) => {
583                    self.data.reexports.push((
584                        i,
585                        Reexport::Named {
586                            imported: atom!("default"),
587                            exported: d.exported.sym.clone(),
588                        },
589                    ));
590                }
591                ExportSpecifier::Named(n) => {
592                    self.data.reexports.push((
593                        i,
594                        Reexport::Named {
595                            imported: to_word(&n.orig),
596                            exported: to_word(n.exported.as_ref().unwrap_or(&n.orig)),
597                        },
598                    ));
599                }
600            }
601        }
602    }
603
604    fn visit_export_decl(&mut self, n: &ExportDecl) {
605        self.data.has_exports = true;
606
607        if self.comments.is_some() {
608            // only visit children if we potentially need to mark import / requires
609            n.visit_children_with(self);
610        }
611
612        match &n.decl {
613            Decl::Class(n) => {
614                self.data
615                    .exports
616                    .insert(n.ident.sym.as_str().into(), n.ident.to_id());
617            }
618            Decl::Fn(n) => {
619                self.data
620                    .exports
621                    .insert(n.ident.sym.as_str().into(), n.ident.to_id());
622            }
623            Decl::Var(..) | Decl::Using(..) => {
624                let ids: Vec<Id> = find_pat_ids(&n.decl);
625                for id in ids {
626                    self.data.exports.insert(id.0.as_str().into(), id);
627                }
628            }
629            _ => {}
630        }
631    }
632
633    fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) {
634        self.data.has_exports = true;
635
636        if self.comments.is_some() {
637            // only visit children if we potentially need to mark import / requires
638            n.visit_children_with(self);
639        }
640    }
641    fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) {
642        self.data.has_exports = true;
643
644        if self.comments.is_some() {
645            // only visit children if we potentially need to mark import / requires
646            n.visit_children_with(self);
647        }
648    }
649
650    fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) {
651        if let ModuleExportName::Ident(ident) = &n.exported.as_ref().unwrap_or(&n.orig) {
652            self.data
653                .exports
654                .insert(ident.sym.as_str().into(), ident.to_id());
655        }
656    }
657
658    fn visit_export_default_specifier(&mut self, n: &ExportDefaultSpecifier) {
659        self.data
660            .exports
661            .insert("default".into(), n.exported.to_id());
662    }
663
664    fn visit_export_namespace_specifier(&mut self, n: &ExportNamespaceSpecifier) {
665        if let ModuleExportName::Ident(ident) = &n.name {
666            self.data
667                .exports
668                .insert(ident.sym.as_str().into(), ident.to_id());
669        }
670    }
671
672    fn visit_program(&mut self, m: &Program) {
673        self.data.has_top_level_await = has_top_level_await(m).is_some();
674
675        m.visit_children_with(self);
676    }
677
678    fn visit_stmt(&mut self, n: &Stmt) {
679        if self.comments.is_some() {
680            // only visit children if we potentially need to mark import / requires
681            n.visit_children_with(self);
682        }
683    }
684
685    /// check if import or require contains an ignore comment
686    ///
687    /// We are checking for the following cases:
688    /// - import(/* webpackIgnore: true */ "a")
689    /// - require(/* webpackIgnore: true */ "a")
690    ///
691    /// We can do this by checking if any of the comment spans are between the
692    /// callee and the first argument.
693    //
694    // potentially support more webpack magic comments in the future:
695    // https://webpack.js.org/api/module-methods/#magic-comments
696    fn visit_call_expr(&mut self, n: &CallExpr) {
697        // we could actually unwrap thanks to the optimisation above but it can't hurt to be safe...
698        if let Some(comments) = self.comments {
699            let callee_span = match &n.callee {
700                Callee::Import(Import { span, .. }) => Some(*span),
701                Callee::Expr(e) => Some(e.span()),
702                _ => None,
703            };
704
705            let ignore_directive = parse_ignore_directive(comments, n.args.first());
706
707            if let Some((callee_span, ignore_directive)) = callee_span.zip(ignore_directive) {
708                self.data.attributes.insert(
709                    callee_span.lo,
710                    ImportAttributes {
711                        ignore: ignore_directive,
712                    },
713                );
714            };
715        }
716
717        n.visit_children_with(self);
718    }
719
720    fn visit_new_expr(&mut self, n: &NewExpr) {
721        // we could actually unwrap thanks to the optimisation above but it can't hurt to be safe...
722        if let Some(comments) = self.comments {
723            let callee_span = match &n.callee {
724                box Expr::Ident(Ident { sym, .. }) if sym == "Worker" => Some(n.span),
725                _ => None,
726            };
727
728            let ignore_directive = parse_ignore_directive(comments, n.args.iter().flatten().next());
729
730            if let Some((callee_span, ignore_directive)) = callee_span.zip(ignore_directive) {
731                self.data.attributes.insert(
732                    callee_span.lo,
733                    ImportAttributes {
734                        ignore: ignore_directive,
735                    },
736                );
737            };
738        }
739
740        n.visit_children_with(self);
741    }
742}
743
744fn parse_ignore_directive(comments: &dyn Comments, value: Option<&ExprOrSpread>) -> Option<bool> {
745    // we are interested here in the last comment with a valid directive
746    value
747        .map(|arg| arg.span_lo())
748        .and_then(|comment_pos| comments.get_leading(comment_pos))
749        .iter()
750        .flatten()
751        .rev()
752        .filter_map(|comment| {
753            let (directive, value) = comment.text.trim().split_once(':')?;
754            // support whitespace between the colon
755            match (directive.trim(), value.trim()) {
756                ("webpackIgnore" | "turbopackIgnore", "true") => Some(true),
757                ("webpackIgnore" | "turbopackIgnore", "false") => Some(false),
758                _ => None, // ignore anything else
759            }
760        })
761        .next()
762}
763
764pub(crate) fn orig_name(n: &ModuleExportName) -> Atom {
765    match n {
766        ModuleExportName::Ident(v) => v.sym.clone(),
767        ModuleExportName::Str(v) => v.value.clone(),
768    }
769}
770
771fn parse_with(with: Option<&ObjectLit>) -> Option<ImportedSymbol> {
772    find_turbopack_part_id_in_asserts(with?).map(|v| match v {
773        PartId::Internal(index, true) => ImportedSymbol::PartEvaluation(index),
774        PartId::Internal(index, false) => ImportedSymbol::Part(index),
775        PartId::ModuleEvaluation => ImportedSymbol::ModuleEvaluation,
776        PartId::Export(e) => ImportedSymbol::Symbol(e.as_str().into()),
777        PartId::Exports => ImportedSymbol::Exports,
778    })
779}
780
781fn get_import_symbol_from_import(specifier: &ImportSpecifier) -> ImportedSymbol {
782    match specifier {
783        ImportSpecifier::Named(ImportNamedSpecifier {
784            local, imported, ..
785        }) => ImportedSymbol::Symbol(match imported {
786            Some(imported) => orig_name(imported),
787            _ => local.sym.clone(),
788        }),
789        ImportSpecifier::Default(..) => ImportedSymbol::Symbol(atom!("default")),
790        ImportSpecifier::Namespace(..) => ImportedSymbol::Exports,
791    }
792}
793
794fn get_import_symbol_from_export(specifier: &ExportSpecifier) -> ImportedSymbol {
795    match specifier {
796        ExportSpecifier::Named(ExportNamedSpecifier { orig, .. }) => {
797            ImportedSymbol::Symbol(orig_name(orig))
798        }
799        ExportSpecifier::Default(..) => ImportedSymbol::Symbol(atom!("default")),
800        ExportSpecifier::Namespace(..) => ImportedSymbol::Exports,
801    }
802}