1use std::{
2 borrow::Cow,
3 collections::{BTreeMap, hash_map::Entry},
4 fmt::Display,
5 sync::{Arc, LazyLock},
6};
7
8use anyhow::{Context, Result};
9use auto_hash_map::AutoSet;
10use bincode::{Decode, Encode};
11use rustc_hash::{FxHashMap, FxHashSet};
12use smallvec::SmallVec;
13use swc_core::{
14 atoms::Wtf8Atom,
15 common::{BytePos, GLOBALS, Mark, Span, Spanned, SyntaxContext, comments::Comments},
16 ecma::{
17 ast::*,
18 atoms::{Atom, atom},
19 utils::{IsDirective, find_pat_ids},
20 visit::{Visit, VisitWith},
21 },
22};
23use turbo_frozenmap::FrozenMap;
24use turbo_rcstr::{RcStr, rcstr};
25use turbo_tasks::{FxIndexMap, FxIndexSet, NonLocalValue, ResolvedVc};
26use turbopack_core::{
27 loader::WebpackLoaderItem,
28 resolve::{ExportUsage, ImportUsage},
29};
30
31use super::{JsValue, ModuleValue, top_level_await::has_top_level_await};
32use crate::{
33 SpecifiedModuleType,
34 analyzer::{
35 Bump, ConstantString, ConstantValue, ObjectPart,
36 cjs_ast::{is_global, is_module_dot_exports},
37 graph::{AssignmentScope, AssignmentScopes, EvalContext},
38 is_unresolved, is_unresolved_id,
39 },
40 magic_identifier::{MAGIC_IDENTIFIER_DEFAULT_EXPORT, MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM},
41 module_fragments::{PartId, find_turbopack_part_id_in_asserts},
42 references::{
43 cross_module_constants::is_import_name_eligible_for_exports,
44 esm::{EsmAssetReference, EsmExport, Liveness},
45 util::{SpecifiedChunkingType, parse_chunking_type_annotation},
46 },
47 utils::{extract_name_from_member_prop, extract_names_from_object_pat, unparen},
48};
49
50#[turbo_tasks::value]
51#[derive(Default, Debug, Clone, Hash)]
52pub struct ImportAnnotations {
53 #[turbo_tasks(unsafe_ignore)]
55 #[bincode(with_serde)]
56 map: BTreeMap<Wtf8Atom, Wtf8Atom>,
57
58 #[bincode(with_serde)]
61 turbopack_loader: Option<WebpackLoaderItem>,
62 turbopack_rename_as: Option<RcStr>,
63 turbopack_module_type: Option<RcStr>,
64 chunking_type: Option<SpecifiedChunkingType>,
65
66 turbopack_constants: Option<bool>,
67}
68
69static ANNOTATION_TRANSITION: LazyLock<Wtf8Atom> =
71 LazyLock::new(|| crate::annotations::ANNOTATION_TRANSITION.into());
72
73static ANNOTATION_EXPORT_USAGE: LazyLock<Wtf8Atom> =
75 LazyLock::new(|| crate::annotations::ANNOTATION_EXPORT_USAGE.into());
76
77static ATTRIBUTE_MODULE_TYPE: LazyLock<Wtf8Atom> = LazyLock::new(|| atom!("type").into());
79
80impl ImportAnnotations {
81 pub fn parse(with: Option<&ObjectLit>) -> Option<ImportAnnotations> {
82 let with = with?;
83
84 let mut map = BTreeMap::new();
85 let mut turbopack_loader_name: Option<RcStr> = None;
86 let mut turbopack_loader_options: serde_json::Map<String, serde_json::Value> =
87 serde_json::Map::new();
88 let mut turbopack_rename_as: Option<RcStr> = None;
89 let mut turbopack_module_type: Option<RcStr> = None;
90 let mut chunking_type: Option<SpecifiedChunkingType> = None;
91 let mut turbopack_constants: Option<bool> = None;
92 for prop in &with.props {
93 let Some(kv) = prop.as_prop().and_then(|p| p.as_key_value()) else {
94 continue;
95 };
96
97 let key_str = match &kv.key {
98 PropName::Ident(ident) => Cow::Borrowed(ident.sym.as_str()),
99 PropName::Str(str) => str.value.to_string_lossy(),
100 _ => continue,
101 };
102
103 match &*key_str {
105 "turbopackLoader" => {
106 if let Some(Lit::Str(s)) = kv.value.as_lit() {
107 turbopack_loader_name =
108 Some(RcStr::from(s.value.to_string_lossy().into_owned()));
109 }
110 }
111 "turbopackLoaderOptions" => {
112 if let Some(Lit::Str(s)) = kv.value.as_lit() {
113 let json_str = s.value.to_string_lossy();
114 if let Ok(serde_json::Value::Object(map)) = serde_json::from_str(&json_str)
115 {
116 turbopack_loader_options = map;
117 }
118 }
119 }
120 "turbopackAs" => {
121 if let Some(Lit::Str(s)) = kv.value.as_lit() {
122 turbopack_rename_as =
123 Some(RcStr::from(s.value.to_string_lossy().into_owned()));
124 }
125 }
126 "turbopackModuleType" => {
127 if let Some(Lit::Str(s)) = kv.value.as_lit() {
128 turbopack_module_type =
129 Some(RcStr::from(s.value.to_string_lossy().into_owned()));
130 }
131 }
132 "turbopack-chunking-type" => {
133 if let Some(Lit::Str(s)) = kv.value.as_lit() {
134 chunking_type = parse_chunking_type_annotation(
135 kv.value.span(),
136 &s.value.to_string_lossy(),
137 );
138 }
139 }
140 "turbopackConstants" => {
141 if let Some(Lit::Str(s)) = kv.value.as_lit() {
142 turbopack_constants = Some(s.value.to_string_lossy() == "true");
143 }
144 }
145 _ => {
146 if let Some(Lit::Str(str)) = kv.value.as_lit() {
148 let key: Wtf8Atom = match &kv.key {
149 PropName::Ident(ident) => ident.sym.clone().into(),
150 PropName::Str(s) => s.value.clone(),
151 _ => continue,
152 };
153 map.insert(key, str.value.clone());
154 }
155 }
156 }
157 }
158
159 let turbopack_loader = turbopack_loader_name.map(|name| WebpackLoaderItem {
160 loader: name,
161 options: turbopack_loader_options,
162 });
163
164 if !map.is_empty()
165 || turbopack_loader.is_some()
166 || turbopack_rename_as.is_some()
167 || turbopack_module_type.is_some()
168 || chunking_type.is_some()
169 || turbopack_constants.is_some()
170 {
171 Some(ImportAnnotations {
172 map,
173 turbopack_loader,
174 turbopack_rename_as,
175 turbopack_module_type,
176 chunking_type,
177 turbopack_constants,
178 })
179 } else {
180 None
181 }
182 }
183
184 pub fn parse_dynamic(with: &JsValue<'_>) -> Option<ImportAnnotations> {
185 let mut map = BTreeMap::new();
186
187 let JsValue::Object { parts, .. } = with else {
188 return None;
189 };
190
191 for part in parts.iter() {
192 let ObjectPart::KeyValue(key, value) = part else {
193 continue;
194 };
195 let (
196 JsValue::Constant(ConstantValue::Str(key)),
197 JsValue::Constant(ConstantValue::Str(value)),
198 ) = (key, value)
199 else {
200 continue;
201 };
202
203 map.insert(
204 key.as_atom().into_owned().into(),
205 value.as_atom().into_owned().into(),
206 );
207 }
208
209 if !map.is_empty() {
210 Some(ImportAnnotations {
211 map,
212 turbopack_loader: None,
213 turbopack_rename_as: None,
214 turbopack_module_type: None,
215 chunking_type: None,
216 turbopack_constants: None,
217 })
218 } else {
219 None
220 }
221 }
222
223 pub fn transition(&self) -> Option<Cow<'_, str>> {
225 self.get(&ANNOTATION_TRANSITION)
226 .map(|v| v.to_string_lossy())
227 }
228
229 pub fn export_usage_passthrough(&self) -> bool {
231 self.get(&ANNOTATION_EXPORT_USAGE)
232 .is_some_and(|value| value == "passthrough")
233 }
234
235 pub fn chunking_type(&self) -> Option<SpecifiedChunkingType> {
237 self.chunking_type
238 }
239
240 pub fn module_type(&self) -> Option<&Wtf8Atom> {
242 self.get(&ATTRIBUTE_MODULE_TYPE)
243 }
244
245 pub fn turbopack_loader(&self) -> Option<&WebpackLoaderItem> {
247 self.turbopack_loader.as_ref()
248 }
249
250 pub fn turbopack_rename_as(&self) -> Option<&RcStr> {
252 self.turbopack_rename_as.as_ref()
253 }
254
255 pub fn turbopack_module_type(&self) -> Option<&RcStr> {
257 self.turbopack_module_type.as_ref()
258 }
259
260 pub fn has_turbopack_loader(&self) -> bool {
262 self.turbopack_loader.is_some()
263 }
264
265 pub fn turbopack_constants(&self) -> Option<bool> {
267 self.turbopack_constants
268 }
269
270 pub fn get(&self, key: &Wtf8Atom) -> Option<&Wtf8Atom> {
271 self.map.get(key)
272 }
273}
274
275impl Display for ImportAnnotations {
276 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277 let mut it = self.map.iter();
278 if let Some((k, v)) = it.next() {
279 write!(f, "{{ {}: {}", k.to_string_lossy(), v.to_string_lossy())?
280 } else {
281 return f.write_str("{}");
282 };
283 for (k, v) in it {
284 write!(f, ", {}: {}", k.to_string_lossy(), v.to_string_lossy())?
285 }
286 f.write_str(" }")
287 }
288}
289
290#[derive(Clone, Debug)]
291pub enum DeclUsage {
292 SideEffects,
293 Bindings(FxHashSet<Id>),
294}
295impl Default for DeclUsage {
296 fn default() -> Self {
297 DeclUsage::Bindings(Default::default())
298 }
299}
300impl DeclUsage {
301 fn add_usage(&mut self, user: &Id) {
302 match self {
303 Self::Bindings(set) => {
304 set.insert(user.clone());
305 }
306 Self::SideEffects => {}
307 }
308 }
309 fn make_side_effects(&mut self) {
310 *self = Self::SideEffects;
311 }
312}
313
314#[derive(Default, Debug)]
315pub(crate) struct ProgramDeclUsage {
316 pub(crate) decl_usages: FxHashMap<Id, DeclUsage>,
318 pub(crate) import_usages: FxHashMap<usize, DeclUsage>,
320 pub(crate) named_reexports: FxHashMap<usize, AutoSet<RcStr>>,
322 pub(crate) exports: FxHashMap<RcStr, Id>,
324}
325impl ProgramDeclUsage {
326 fn compute_import_usage(&self) -> FxHashMap<usize, ImportUsage> {
327 let mut import_usage =
328 FxHashMap::with_capacity_and_hasher(self.import_usages.len(), Default::default());
329 for (reference, usage) in &self.import_usages {
330 if let DeclUsage::Bindings(ids) = usage {
332 let mut visited = ids.clone();
334 let mut stack = ids.iter().collect::<Vec<_>>();
335 let mut has_global_usage = false;
336 while let Some(id) = stack.pop() {
337 match self.decl_usages.get(id) {
338 Some(DeclUsage::SideEffects) => {
339 has_global_usage = true;
340 break;
341 }
342 Some(DeclUsage::Bindings(callers)) => {
343 for caller in callers {
344 if visited.insert(caller.clone()) {
345 stack.push(caller);
346 }
347 }
348 }
349 _ => {}
350 }
351 }
352
353 import_usage.insert(
355 *reference,
356 if has_global_usage {
357 ImportUsage::TopLevel
358 } else {
359 ImportUsage::Exports(
360 self.exports
361 .iter()
362 .filter(|(_, id)| visited.contains(*id))
363 .map(|(exported, _)| exported.clone())
364 .collect(),
365 )
366 },
367 );
368 }
369 }
370 for (reference, names) in &self.named_reexports {
372 let usage = match import_usage.get(reference) {
373 Some(ImportUsage::TopLevel) => continue,
374 Some(ImportUsage::Exports(existing)) => ImportUsage::Exports(
378 existing
379 .iter()
380 .cloned()
381 .chain(names.iter().cloned())
382 .collect(),
383 ),
384 None => ImportUsage::Exports(names.iter().cloned().collect()),
385 };
386 import_usage.insert(*reference, usage);
387 }
388 import_usage
389 }
390}
391
392#[derive(Debug)]
395pub enum Export {
396 LocalBinding(RcStr, bool),
400 ImportedBinding(usize, RcStr, bool),
404 ImportedNamespace(usize),
406 Error,
408}
409
410#[derive(Default, Debug)]
412pub(crate) struct ImportMap {
413 imports: FxIndexMap<Id, (usize, Atom)>,
415
416 namespace_imports: FxIndexMap<Id, usize>,
418
419 pub(crate) exports: BTreeMap<RcStr, Export>,
421
422 reexport_namespaces: Vec<usize>,
424
425 references: FxIndexSet<ImportMapReference>,
427
428 reference_declaration_orders: Vec<usize>,
431
432 has_imports: bool,
435
436 has_exports: bool,
439
440 has_top_level_await: bool,
442
443 pub(crate) strict: bool,
445
446 attributes: FxHashMap<BytePos, ImportAttributes>,
453
454 full_star_imports: FxHashSet<Wtf8Atom>,
457
458 pub(super) assignment_scopes: FxHashMap<Id, AssignmentScopes>,
461
462 pub(crate) import_usage: FxHashMap<usize, ImportUsage>,
463
464 pub(crate) exports_ids: FxHashMap<RcStr, (Id, Span)>,
466
467 cjs_imports: CjsImports,
470}
471
472#[derive(Default, Debug)]
473pub(crate) struct CjsImports {
474 pub(crate) resolved: FxHashMap<BytePos, ExportUsage>,
476
477 pub(crate) bindings: FxHashMap<Id, BytePos>,
479}
480
481#[derive(Debug)]
486pub struct ImportAttributes {
487 pub ignore: bool,
498 pub optional: bool,
508 pub export_names: Option<SmallVec<[RcStr; 1]>>,
522 pub chunking_type: Option<SpecifiedChunkingType>,
531}
532
533impl ImportAttributes {
534 pub const fn empty() -> Self {
535 ImportAttributes {
536 ignore: false,
537 optional: false,
538 export_names: None,
539 chunking_type: None,
540 }
541 }
542
543 pub fn empty_ref() -> &'static Self {
544 static DEFAULT_VALUE: ImportAttributes = ImportAttributes::empty();
546 &DEFAULT_VALUE
547 }
548}
549
550impl Default for ImportAttributes {
551 fn default() -> Self {
552 ImportAttributes::empty()
553 }
554}
555
556impl Default for &ImportAttributes {
557 fn default() -> Self {
558 ImportAttributes::empty_ref()
559 }
560}
561
562#[derive(Debug, Clone, PartialEq, Eq, Hash)]
563pub(crate) enum ImportedSymbol {
564 ModuleEvaluation,
565 Symbol(Atom),
566 Exports,
567 Part(u32),
568 PartEvaluation(u32),
569}
570
571#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, NonLocalValue, Encode, Decode, Default)]
576pub enum ExportRegistrationMode {
577 #[default]
579 Normal,
580 Mixed,
584 Reexport,
587}
588
589#[derive(Debug, Clone, PartialEq, Eq, Hash)]
590pub(crate) struct ImportMapReference {
591 pub module_path: Wtf8Atom,
592 pub imported_symbol: ImportedSymbol,
593 pub annotations: Option<Arc<ImportAnnotations>>,
594 pub span: Span,
595}
596
597impl ImportMap {
598 fn ensure_reference(
599 &mut self,
600 reference: ImportMapReference,
601 declaration_order: usize,
602 ) -> usize {
603 if let Some(i) = self.references.get_index_of(&reference) {
604 i
605 } else {
606 let i = self.references.len();
607 self.references.insert(reference);
608 self.reference_declaration_orders.push(declaration_order);
609 debug_assert_eq!(
610 self.references.len(),
611 self.reference_declaration_orders.len()
612 );
613 i
614 }
615 }
616
617 pub fn is_esm(&self, specified_type: SpecifiedModuleType) -> bool {
618 if self.has_exports {
619 return true;
620 }
621
622 match specified_type {
623 SpecifiedModuleType::Automatic => {
624 self.has_exports || self.has_imports || self.has_top_level_await
625 }
626 SpecifiedModuleType::CommonJs => false,
627 SpecifiedModuleType::EcmaScript => true,
628 }
629 }
630
631 pub fn is_cjs(&self, specified_type: SpecifiedModuleType) -> bool {
632 !self.is_esm(specified_type)
633 }
634
635 pub fn get_import_for_idx<'a>(
636 &self,
637 arena: &'a Bump,
638 esm_reference_idx: usize,
639 export: Option<ConstantString>,
640 ) -> JsValue<'a> {
641 let r = &self.references[esm_reference_idx];
642 if let Some(export) = export {
643 JsValue::member(
644 arena,
645 JsValue::Module(ModuleValue {
646 module: r.module_path.clone(),
647 annotations: r.annotations.clone(),
648 reference: Some((esm_reference_idx as u32).into()),
649 analyze_for_constants: is_import_name_eligible_for_exports(export.as_str()),
650 }),
651 JsValue::Constant(ConstantValue::Str(export)),
652 )
653 } else {
654 JsValue::Module(ModuleValue {
655 module: r.module_path.clone(),
656 annotations: r.annotations.clone(),
657 reference: Some((esm_reference_idx as u32).into()),
658 analyze_for_constants: false,
659 })
660 }
661 }
662
663 pub fn get_import<'a>(&self, arena: &'a Bump, id: &Id) -> Option<JsValue<'a>> {
664 if let Some((i, i_sym)) = self.imports.get(id) {
665 return Some(self.get_import_for_idx(arena, *i, Some(i_sym.clone().into())));
666 }
667 if let Some(i) = self.namespace_imports.get(id) {
668 return Some(self.get_import_for_idx(arena, *i, None));
669 }
670 None
671 }
672
673 pub fn get_attributes(&self, span: Span) -> &ImportAttributes {
674 self.attributes.get(&span.lo).unwrap_or_default()
675 }
676
677 pub fn get_annotations(&self, idx: usize) -> Option<&Arc<ImportAnnotations>> {
678 self.references
679 .get_index(idx)
680 .and_then(|r| r.annotations.as_ref())
681 }
682
683 pub fn get_binding(&self, id: &Id) -> Option<(usize, Option<&Atom>)> {
684 if let Some((i, i_sym)) = self.imports.get(id) {
685 return Some((*i, Some(i_sym)));
686 }
687 if let Some(i) = self.namespace_imports.get(id) {
688 return Some((*i, None));
689 }
690 None
691 }
692
693 pub fn references(&self) -> impl ExactSizeIterator<Item = &ImportMapReference> {
694 self.references.iter()
695 }
696
697 pub fn reference_span(&self, index: usize) -> Span {
698 self.references[index].span
699 }
700
701 pub fn reexports_reference_idxs(&self) -> impl Iterator<Item = usize> {
702 self.exports
703 .values()
704 .filter_map(|value| match value {
705 Export::ImportedBinding(i, ..) | Export::ImportedNamespace(i) => Some(*i),
706 Export::LocalBinding(..) | Export::Error => None,
707 })
708 .chain(self.reexport_namespaces.iter().copied())
709 }
710
711 pub fn locally_bound_reference_idxs(&self) -> impl Iterator<Item = usize> {
715 self.imports
716 .values()
717 .map(|(i, _)| *i)
718 .chain(self.namespace_imports.values().copied())
719 }
720
721 pub fn export_registration_mode(&self) -> ExportRegistrationMode {
728 let has_local_exports = self
729 .exports
730 .values()
731 .any(|export| matches!(export, Export::LocalBinding(..) | Export::Error));
732 if has_local_exports {
733 return ExportRegistrationMode::Normal;
734 }
735
736 let reexports: FxHashSet<usize> = self.reexports_reference_idxs().collect();
737 if reexports.is_empty() {
738 return ExportRegistrationMode::Normal;
739 }
740
741 let reexport_declarations: FxHashSet<_> = reexports
746 .iter()
747 .map(|i| self.reference_declaration_orders[*i])
748 .collect();
749 let first_reexport = *reexport_declarations.iter().min().unwrap();
750
751 let last_other = self
757 .reference_declaration_orders
758 .iter()
759 .filter(|order| !reexport_declarations.contains(order))
760 .copied()
761 .max();
762 let Some(last_other) = last_other else {
763 return ExportRegistrationMode::Reexport;
765 };
766
767 if last_other < first_reexport {
769 ExportRegistrationMode::Reexport
770 } else {
771 ExportRegistrationMode::Mixed
772 }
773 }
774
775 pub fn as_esm_exports(
776 &self,
777 import_references: &[ResolvedVc<EsmAssetReference>],
778 eval_context: &EvalContext,
779 ) -> Result<FrozenMap<RcStr, EsmExport>> {
780 Ok(FrozenMap::from(
781 self.exports
782 .iter()
783 .map(|(name, value)| {
784 let value = match value {
785 Export::LocalBinding(local, is_fake_esm) => EsmExport::LocalBinding(
786 local.clone(),
787 if *is_fake_esm {
788 Liveness::Mutable
790 } else {
791 eval_context.imports.get_export_ident_liveness(
792 self.exports_ids
793 .get(name)
794 .cloned()
795 .with_context(|| {
796 format!(
797 "Exported binding {name} not found in exports_ids"
798 )
799 })?
800 .0,
801 eval_context.unresolved_mark,
802 )
803 },
804 ),
805 Export::ImportedBinding(i, name, is_fake_esm) => {
806 EsmExport::ImportedBinding(
807 ResolvedVc::upcast(import_references[*i]),
808 name.clone(),
809 *is_fake_esm,
810 )
811 }
812 Export::ImportedNamespace(i) => {
813 EsmExport::ImportedNamespace(ResolvedVc::upcast(import_references[*i]))
814 }
815 Export::Error => EsmExport::Error,
816 };
817 Ok((name.clone(), value))
818 })
819 .collect::<Result<Vec<_>>>()?,
820 ))
821 }
822
823 pub fn reexport_namespaces(&self) -> impl ExactSizeIterator<Item = usize> {
824 self.reexport_namespaces.iter().copied()
825 }
826
827 pub fn get_export_ident_liveness(&self, id: Id, unresolved_mark: Mark) -> Liveness {
830 if let Some(assignment_scopes) = self.assignment_scopes.get(&id) {
831 if *assignment_scopes != AssignmentScopes::AllInModuleEvalScope {
833 Liveness::Live
834 } else {
835 Liveness::Constant
836 }
837 } else {
838 debug_assert!(
843 self.imports.contains_key(&id)
844 || self.namespace_imports.contains_key(&id)
845 || !GLOBALS.is_set()
846 || is_unresolved_id(&id, unresolved_mark),
847 "export ident {id:?} without an assignment scope should be a free variable or an \
848 imported variable"
849 );
850
851 Liveness::Live
852 }
853 }
854
855 pub(super) fn analyze(
857 unresolved_mark: Mark,
858 m: &Program,
859 comments: Option<&dyn Comments>,
860 ) -> Self {
861 let mut data = ImportMap::default();
862 let mut analyzer = Analyzer {
863 unresolved_mark,
864 data: &mut data,
865 comments,
866 namespace_imports_to_specifier: FxIndexMap::default(),
867 state: Default::default(),
868 program_decl_usage: Default::default(),
869 current_module_item_order: 0,
870 };
871
872 if let Program::Module(m) = m {
874 for (order, stmt) in m.body.iter().enumerate() {
875 analyzer.current_module_item_order = order;
876 match stmt {
877 ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
878 if import.type_only {
879 continue;
880 }
881 analyzer.data.has_imports = true;
882 let annotations = ImportAnnotations::parse(import.with.as_deref());
883 let internal_symbol = parse_with(import.with.as_deref());
884 if internal_symbol.is_none() {
885 analyzer.ensure_reference(
886 import.span,
887 import.src.value.clone(),
888 ImportedSymbol::ModuleEvaluation,
889 annotations.clone(),
890 );
891 }
892
893 for s in &import.specifiers {
894 if s.is_type_only() {
895 continue;
896 }
897 let symbol = internal_symbol
898 .clone()
899 .unwrap_or_else(|| get_import_symbol_from_import(s));
900 let i = analyzer.ensure_reference(
901 import.span,
902 import.src.value.clone(),
903 symbol,
904 annotations.clone(),
905 );
906
907 let (local, orig_sym) = match s {
908 ImportSpecifier::Namespace(s) => {
909 analyzer
910 .namespace_imports_to_specifier
911 .insert(s.local.to_id(), import.src.value.clone());
912 analyzer.data.namespace_imports.insert(s.local.to_id(), i);
913 continue;
914 }
915 ImportSpecifier::Default(s) => (s.local.to_id(), atom!("default")),
916 ImportSpecifier::Named(s) => match &s.imported {
917 Some(imported) => {
918 (s.local.to_id(), imported.atom().into_owned())
919 }
920 _ => (s.local.to_id(), s.local.sym.clone()),
921 },
922 };
923 analyzer.data.imports.insert(local, (i, orig_sym));
924 }
925 if import.specifiers.is_empty()
926 && let Some(internal_symbol) = internal_symbol
927 {
928 analyzer.ensure_reference(
929 import.span,
930 import.src.value.clone(),
931 internal_symbol,
932 annotations,
933 );
934 }
935 }
936 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export)) => {
939 if export.type_only {
940 continue;
941 }
942 let annotations = ImportAnnotations::parse(export.with.as_deref());
943 analyzer.ensure_reference(
944 export.span,
945 export.src.value.clone(),
946 ImportedSymbol::ModuleEvaluation,
947 annotations.clone(),
948 );
949 }
950 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
951 if export.type_only {
952 continue;
953 }
954 if let Some(ref src) = export.src {
955 let annotations = ImportAnnotations::parse(export.with.as_deref());
956 let internal_symbol = parse_with(export.with.as_deref());
957 if internal_symbol.is_none() || export.specifiers.is_empty() {
958 analyzer.ensure_reference(
959 export.span,
960 src.value.clone(),
961 ImportedSymbol::ModuleEvaluation,
962 annotations.clone(),
963 );
964 }
965 }
966 }
967 _ => (),
968 }
969 }
970 }
971
972 m.visit_with(&mut analyzer);
973
974 data.import_usage = analyzer.program_decl_usage.compute_import_usage();
975
976 data
977 }
978
979 pub(crate) fn should_import_all(&self, esm_reference_index: usize) -> bool {
980 let r = &self.references[esm_reference_index];
981
982 self.full_star_imports.contains(&r.module_path)
983 }
984
985 pub(crate) fn cjs_imports(&self) -> &CjsImports {
986 &self.cjs_imports
987 }
988}
989
990mod analyzer_state {
991 use swc_core::ecma::ast::{Id, Ident};
992
993 use super::Analyzer;
994
995 #[derive(Default)]
996 pub(super) struct AnalyzerState {
997 is_in_fn: bool,
998 cur_top_level_decl_name: Option<Id>,
999 }
1000
1001 impl AnalyzerState {
1002 pub(super) fn cur_top_level_decl_name(&self) -> &Option<Id> {
1004 &self.cur_top_level_decl_name
1005 }
1006
1007 pub(super) fn is_in_fn(&self) -> bool {
1009 self.is_in_fn
1010 }
1011 }
1012
1013 impl Analyzer<'_> {
1014 pub(super) fn enter_top_level_decl<T>(
1016 &mut self,
1017 name: &Ident,
1018 visitor: impl FnOnce(&mut Self) -> T,
1019 ) -> T {
1020 let is_top_level_fn = self.state.cur_top_level_decl_name.is_none();
1021 if is_top_level_fn {
1022 self.state.cur_top_level_decl_name = Some(name.to_id());
1023 }
1024 let result = visitor(self);
1025 if is_top_level_fn {
1026 self.state.cur_top_level_decl_name = None;
1027 }
1028 result
1029 }
1030
1031 pub(super) fn enter_fn<T>(&mut self, visitor: impl FnOnce(&mut Self) -> T) -> T {
1033 let old_is_in_fn = self.state.is_in_fn;
1034 self.state.is_in_fn = true;
1035 let result = visitor(self);
1036 self.state.is_in_fn = old_is_in_fn;
1037 result
1038 }
1039 }
1040}
1041
1042struct Analyzer<'a> {
1043 unresolved_mark: Mark,
1044 data: &'a mut ImportMap,
1045 comments: Option<&'a dyn Comments>,
1046 namespace_imports_to_specifier: FxIndexMap<Id, Wtf8Atom>,
1049
1050 program_decl_usage: ProgramDeclUsage,
1051
1052 current_module_item_order: usize,
1054
1055 state: analyzer_state::AnalyzerState,
1056}
1057
1058impl Analyzer<'_> {
1059 fn ensure_reference(
1060 &mut self,
1061 span: Span,
1062 module_path: Wtf8Atom,
1063 imported_symbol: ImportedSymbol,
1064 annotations: Option<ImportAnnotations>,
1065 ) -> usize {
1066 let reference = ImportMapReference {
1067 module_path,
1068 imported_symbol,
1069 span,
1070 annotations: annotations.map(Arc::new),
1071 };
1072 self.data
1073 .ensure_reference(reference, self.current_module_item_order)
1074 }
1075
1076 fn register_assignment_scope(&mut self, id: Id) {
1077 let scope = if self.state.is_in_fn() {
1078 AssignmentScope::Function
1079 } else {
1080 AssignmentScope::ModuleEval
1081 };
1082
1083 match self.data.assignment_scopes.entry(id) {
1084 Entry::Occupied(mut e) => {
1085 *e.get_mut() = e.get().merge(scope);
1086 }
1087 Entry::Vacant(e) => {
1088 e.insert(AssignmentScopes::new(scope));
1089 }
1090 }
1091 }
1092
1093 fn record_require_usage_var(&mut self, n: &VarDeclarator) {
1095 let Some(init) = &n.init else {
1096 return;
1097 };
1098 let Some(call) = as_require_call(init, self.unresolved_mark) else {
1099 return;
1100 };
1101 match &n.name {
1102 Pat::Ident(binding) => {
1103 self.data
1104 .cjs_imports
1105 .bindings
1106 .insert(binding.id.to_id(), call.span.lo);
1107 }
1108 Pat::Object(_) => {
1109 let usage = match extract_names_from_object_pat(&n.name) {
1110 Some(names) if names.is_empty() => ExportUsage::Evaluation,
1112 Some(names) => ExportUsage::PartialNamespaceObject(names),
1113 None => ExportUsage::All,
1114 };
1115 self.data.cjs_imports.resolved.insert(call.span.lo, usage);
1116 }
1117 _ => {
1118 self.data
1119 .cjs_imports
1120 .resolved
1121 .insert(call.span.lo, ExportUsage::All);
1122 }
1123 }
1124 }
1125}
1126
1127impl Visit for Analyzer<'_> {
1128 fn visit_module(&mut self, module: &Module) {
1129 for (order, item) in module.body.iter().enumerate() {
1130 self.current_module_item_order = order;
1131 item.visit_with(self);
1132 }
1133 }
1134
1135 fn visit_import_decl(&mut self, _: &ImportDecl) {
1136 }
1138
1139 fn visit_export_all(&mut self, export: &ExportAll) {
1140 if export.type_only {
1141 return;
1142 }
1143
1144 let annotations = ImportAnnotations::parse(export.with.as_deref());
1145
1146 let symbol = parse_with(export.with.as_deref());
1147 let i = self.ensure_reference(
1148 export.span,
1149 export.src.value.clone(),
1150 symbol.unwrap_or(ImportedSymbol::Exports),
1151 annotations,
1152 );
1153 self.data.reexport_namespaces.push(i);
1154 self.data.has_exports = true;
1155 export.visit_children_with(self);
1156 }
1157
1158 fn visit_named_export(&mut self, export: &NamedExport) {
1159 if export.type_only {
1160 return;
1161 }
1162
1163 self.data.has_exports = true;
1164
1165 if let Some(ref src) = export.src {
1166 let annotations = ImportAnnotations::parse(export.with.as_deref());
1167 let internal_symbol = parse_with(export.with.as_deref());
1168
1169 for spec in export.specifiers.iter() {
1170 let symbol = internal_symbol
1171 .clone()
1172 .unwrap_or_else(|| get_import_symbol_from_export(spec));
1173
1174 let i = self.ensure_reference(
1175 export.span,
1176 src.value.clone(),
1177 symbol,
1178 annotations.clone(),
1179 );
1180
1181 let name = match spec {
1182 ExportSpecifier::Namespace(n) => {
1183 let name = RcStr::from(n.name.atom().as_str());
1184 self.data
1185 .exports
1186 .insert(name.clone(), Export::ImportedNamespace(i));
1187 name
1188 }
1189 ExportSpecifier::Default(d) => {
1190 let name = RcStr::from(d.exported.sym.as_str());
1191 self.data.exports.insert(
1192 name.clone(),
1193 Export::ImportedBinding(i, rcstr!("default"), false),
1194 );
1195 name
1196 }
1197 ExportSpecifier::Named(n) => {
1198 let name =
1199 RcStr::from(n.exported.as_ref().unwrap_or(&n.orig).atom().as_str());
1200 self.data.exports.insert(
1201 name.clone(),
1202 Export::ImportedBinding(i, RcStr::from(n.orig.atom().as_str()), false),
1203 );
1204 name
1205 }
1206 };
1207 self.program_decl_usage
1208 .named_reexports
1209 .entry(i)
1210 .or_default()
1211 .insert(name);
1212 }
1213 } else {
1214 for spec in export.specifiers.iter() {
1215 match spec {
1216 ExportSpecifier::Namespace(_) => {
1217 unreachable!(
1218 "ExportNamespaceSpecifier will not happen in combination with src == \
1219 None"
1220 );
1221 }
1222 ExportSpecifier::Default(_) => {
1223 unreachable!(
1224 "ExportDefaultSpecifier will not happen in combination with src == \
1225 None"
1226 );
1227 }
1228 ExportSpecifier::Named(ExportNamedSpecifier {
1229 orig,
1230 exported,
1231 is_type_only,
1232 ..
1233 }) => {
1234 if *is_type_only {
1235 continue;
1236 }
1237
1238 let is_fake_esm = export
1240 .with
1241 .as_deref()
1242 .map(find_turbopack_part_id_in_asserts)
1243 .is_some();
1244 let export = {
1245 let imported_binding = if let ModuleExportName::Ident(ident) = orig {
1246 self.data.get_binding(&ident.to_id())
1247 } else {
1248 None
1249 };
1250 if let Some((index, export)) = imported_binding {
1251 if let Some(export) = export {
1254 Export::ImportedBinding(
1255 index,
1256 RcStr::from(export.as_str()),
1257 is_fake_esm,
1258 )
1259 } else {
1260 Export::ImportedNamespace(index)
1261 }
1262 } else {
1263 Export::LocalBinding(RcStr::from(orig.atom().as_str()), is_fake_esm)
1264 }
1265 };
1266 self.data.exports.insert(
1267 RcStr::from(exported.as_ref().unwrap_or(orig).atom().as_str()),
1268 export,
1269 );
1270 }
1271 }
1272 }
1273 export.visit_children_with(self);
1274 }
1275 }
1276
1277 fn visit_export_decl(&mut self, n: &ExportDecl) {
1278 self.data.has_exports = true;
1279 match &n.decl {
1280 Decl::Class(n) => {
1281 let name = RcStr::from(n.ident.sym.as_str());
1282 self.data
1283 .exports
1284 .insert(name.clone(), Export::LocalBinding(name.clone(), false));
1285 self.data
1286 .exports_ids
1287 .insert(name.clone(), (n.ident.to_id(), n.ident.span));
1288 self.program_decl_usage
1289 .exports
1290 .insert(name, n.ident.to_id());
1291 }
1292 Decl::Fn(n) => {
1293 let name = RcStr::from(n.ident.sym.as_str());
1294 self.data
1295 .exports
1296 .insert(name.clone(), Export::LocalBinding(name.clone(), false));
1297 self.data
1298 .exports_ids
1299 .insert(name.clone(), (n.ident.to_id(), n.ident.span));
1300 self.program_decl_usage
1301 .exports
1302 .insert(name, n.ident.to_id());
1303 }
1304 Decl::Var(..) => {
1305 let ids: Vec<Id> = find_pat_ids(&n.decl);
1306 for id in ids {
1307 let name = RcStr::from(id.0.as_str());
1308 self.data
1309 .exports
1310 .insert(name.clone(), Export::LocalBinding(name.clone(), false));
1311 self.data
1312 .exports_ids
1313 .insert(name.clone(), (id.clone(), n.span));
1314 self.program_decl_usage.exports.insert(name, id);
1315 }
1316 }
1317 Decl::Using(_) => {
1318 unreachable!("using declarations can not be exported");
1320 }
1321 Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {
1322 }
1324 }
1325
1326 n.visit_children_with(self);
1327 }
1328
1329 fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) {
1330 self.data.has_exports = true;
1331
1332 let id = match &n.decl {
1333 DefaultDecl::Class(ClassExpr { ident, .. }) | DefaultDecl::Fn(FnExpr { ident, .. }) => {
1334 ident.as_ref().map_or_else(
1337 || {
1338 (
1339 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1340 SyntaxContext::empty(),
1341 )
1342 },
1343 |ident| ident.to_id(),
1344 )
1345 }
1346 DefaultDecl::TsInterfaceDecl(_) => {
1347 (
1349 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1350 SyntaxContext::empty(),
1351 )
1352 }
1353 };
1354
1355 self.register_assignment_scope(id.clone());
1356 self.data.exports.insert(
1357 rcstr!("default"),
1358 Export::LocalBinding(RcStr::from(id.0.as_str()), false),
1359 );
1360 self.data
1361 .exports_ids
1362 .insert(rcstr!("default"), (id.clone(), n.span));
1363 self.program_decl_usage
1364 .exports
1365 .insert(rcstr!("default"), id);
1366 n.visit_children_with(self);
1367 }
1368
1369 fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) {
1370 self.data.has_exports = true;
1371
1372 let default_id = (
1373 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1374 SyntaxContext::empty(),
1375 );
1376
1377 self.data.exports.insert(
1378 rcstr!("default"),
1379 Export::LocalBinding(MAGIC_IDENTIFIER_DEFAULT_EXPORT.clone(), false),
1380 );
1381 self.data.exports_ids.insert(
1382 rcstr!("default"),
1383 (
1384 (
1385 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1387 SyntaxContext::empty(),
1388 ),
1389 n.span,
1390 ),
1391 );
1392
1393 self.register_assignment_scope(default_id);
1394 n.visit_children_with(self);
1395 }
1396
1397 fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) {
1398 self.data.has_exports = true;
1399
1400 let ModuleExportName::Ident(local) = &n.orig else {
1401 unreachable!("exporting a string should be impossible")
1402 };
1403 let exported = RcStr::from(n.exported.as_ref().unwrap_or(&n.orig).atom().as_str());
1404 self.data
1405 .exports_ids
1406 .insert(exported.clone(), (local.to_id(), n.span));
1407 self.program_decl_usage
1408 .exports
1409 .insert(exported, local.to_id());
1410 n.visit_children_with(self);
1411 }
1412
1413 fn visit_export_default_specifier(&mut self, n: &ExportDefaultSpecifier) {
1414 self.data.has_exports = true;
1415
1416 self.data
1417 .exports_ids
1418 .insert(rcstr!("default"), (n.exported.to_id(), n.exported.span));
1419 n.visit_children_with(self);
1420 }
1421
1422 fn visit_program(&mut self, m: &Program) {
1423 self.data.has_top_level_await = has_top_level_await(m).is_some();
1424 self.data.strict = match m {
1425 Program::Module(module) => module
1426 .body
1427 .iter()
1428 .take_while(|s| s.directive_continue())
1429 .any(IsDirective::is_use_strict),
1430 Program::Script(script) => script
1431 .body
1432 .iter()
1433 .take_while(|s| s.directive_continue())
1434 .any(IsDirective::is_use_strict),
1435 };
1436
1437 m.visit_children_with(self);
1438 }
1439
1440 fn visit_call_expr(&mut self, n: &CallExpr) {
1454 if let Some(comments) = self.comments {
1455 let callee_span = match &n.callee {
1456 Callee::Import(Import { span, .. }) => Some(*span),
1457 Callee::Expr(e) => Some(e.span()),
1458 _ => None,
1459 };
1460
1461 if let Some(callee_span) = callee_span
1462 && let Some(attributes) = parse_directives(comments, n.args.first())
1463 {
1464 self.data.attributes.insert(callee_span.lo, attributes);
1465 }
1466 }
1467
1468 n.visit_children_with(self);
1469 }
1470
1471 fn visit_new_expr(&mut self, n: &NewExpr) {
1472 if let Some(comments) = self.comments {
1473 let callee_span = match &*n.callee {
1474 Expr::Ident(Ident { sym, .. }) if sym == "Worker" => Some(n.span),
1475 _ => None,
1476 };
1477
1478 if let Some(callee_span) = callee_span
1479 && let Some(attributes) = parse_directives(comments, n.args.iter().flatten().next())
1480 {
1481 self.data.attributes.insert(callee_span.lo, attributes);
1482 }
1483 }
1484
1485 n.visit_children_with(self);
1486 }
1487
1488 fn visit_getter_prop(&mut self, node: &GetterProp) {
1489 self.enter_fn(|this| {
1490 node.visit_children_with(this);
1491 });
1492 }
1493 fn visit_setter_prop(&mut self, node: &SetterProp) {
1494 self.enter_fn(|this| {
1495 node.visit_children_with(this);
1496 });
1497 }
1498 fn visit_function(&mut self, node: &Function) {
1499 self.enter_fn(|this| {
1500 node.visit_children_with(this);
1501 });
1502 }
1503 fn visit_constructor(&mut self, node: &Constructor) {
1504 self.enter_fn(|this| {
1505 node.visit_children_with(this);
1506 });
1507 }
1508 fn visit_arrow_expr(&mut self, node: &ArrowExpr) {
1509 self.enter_fn(|this| {
1510 node.visit_children_with(this);
1511 });
1512 }
1513
1514 fn visit_member_expr(&mut self, node: &MemberExpr) {
1515 if let Some(call) = as_require_call(&node.obj, self.unresolved_mark) {
1517 let usage = match extract_name_from_member_prop(&node.prop) {
1518 Some(names) => ExportUsage::PartialNamespaceObject(names),
1519 None => ExportUsage::All,
1520 };
1521 self.data.cjs_imports.resolved.insert(call.span.lo, usage);
1522 }
1523
1524 if matches!(
1525 &node.prop,
1526 MemberProp::Ident(..)
1527 | MemberProp::PrivateName(..)
1528 | MemberProp::Computed(ComputedPropName {
1529 expr: Expr::Lit(Lit::Str(_)),
1530 ..
1531 })
1532 ) && let Expr::Ident(ident) = &*node.obj
1533 {
1534 ident.visit_with(self);
1537 } else {
1538 node.visit_children_with(self);
1539 }
1540 }
1541
1542 fn visit_expr(&mut self, node: &Expr) {
1543 if let Expr::Ident(i) = node
1546 && let Some(module_path) = self.namespace_imports_to_specifier.get(&i.to_id())
1547 {
1548 self.data.full_star_imports.insert(module_path.clone());
1549 }
1550 node.visit_children_with(self);
1551 }
1552
1553 fn visit_pat(&mut self, pat: &Pat) {
1554 if let Pat::Ident(i) = pat {
1555 self.register_assignment_scope(i.to_id());
1556 if let Some(module_path) = self.namespace_imports_to_specifier.get(&i.to_id()) {
1557 self.data.full_star_imports.insert(module_path.clone());
1558 }
1559 }
1560 pat.visit_children_with(self);
1561 }
1562
1563 fn visit_simple_assign_target(&mut self, node: &SimpleAssignTarget) {
1564 if let SimpleAssignTarget::Ident(i) = node {
1565 self.register_assignment_scope(i.to_id());
1566 if let Some(module_path) = self.namespace_imports_to_specifier.get(&i.to_id()) {
1567 self.data.full_star_imports.insert(module_path.clone());
1568 }
1569 }
1570 node.visit_children_with(self);
1571 }
1572
1573 fn visit_ident(&mut self, node: &Ident) {
1574 let id = node.to_id();
1575 if let Some((esm_reference_index, _)) = self.data.get_binding(&id) {
1576 let usage = self
1578 .program_decl_usage
1579 .import_usages
1580 .entry(esm_reference_index)
1581 .or_default();
1582 if let Some(top_level) = self.state.cur_top_level_decl_name() {
1583 usage.add_usage(top_level);
1584 } else {
1585 usage.make_side_effects();
1586 }
1587 } else {
1588 if !is_unresolved(node, self.unresolved_mark) {
1590 if let Some(top_level) = self.state.cur_top_level_decl_name() {
1591 if &id != top_level {
1592 self.program_decl_usage
1593 .decl_usages
1594 .entry(id)
1595 .or_default()
1596 .add_usage(top_level);
1597 }
1598 } else {
1599 self.program_decl_usage
1600 .decl_usages
1601 .entry(id)
1602 .or_default()
1603 .make_side_effects();
1604 }
1605 }
1606 }
1607 }
1608
1609 fn visit_fn_expr(&mut self, node: &FnExpr) {
1610 if let Some(ident) = &node.ident {
1611 self.register_assignment_scope(ident.to_id());
1612 }
1613 node.visit_children_with(self);
1614 }
1615
1616 fn visit_fn_decl(&mut self, node: &FnDecl) {
1617 self.enter_top_level_decl(&node.ident, |this| {
1618 node.visit_children_with(this);
1619 });
1620 }
1621
1622 fn visit_decl(&mut self, node: &Decl) {
1623 match node {
1624 Decl::Class(c) => {
1625 self.register_assignment_scope(c.ident.to_id());
1626 }
1627 Decl::Fn(f) => {
1628 self.register_assignment_scope(f.ident.to_id());
1629 }
1630 Decl::Using(v) => {
1631 let ids: Vec<Id> = find_pat_ids(&v.decls);
1632 for id in ids {
1633 self.register_assignment_scope(id);
1634 }
1635 }
1636 Decl::Var(v) => {
1637 let ids: Vec<Id> = find_pat_ids(&v.decls);
1638 for id in ids {
1639 self.register_assignment_scope(id);
1640 }
1641 }
1642 Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {}
1643 }
1644 node.visit_children_with(self);
1645 }
1646
1647 fn visit_var_declarator(&mut self, node: &VarDeclarator) {
1648 self.record_require_usage_var(node);
1649 node.visit_children_with(self);
1650 }
1651
1652 fn visit_assign_expr(&mut self, node: &AssignExpr) {
1653 if node.op == AssignOp::Assign
1654 && let AssignTarget::Simple(SimpleAssignTarget::Member(target)) = &node.left
1655 && is_module_dot_exports(target, self.unresolved_mark)
1656 && let Some(call) = as_require_call(&node.right, self.unresolved_mark)
1657 {
1658 self.data.cjs_imports.resolved.insert(
1659 call.span.lo,
1660 ExportUsage::Passthrough {
1661 namespace_object_may_escape: true,
1662 },
1663 );
1664 }
1665 node.visit_children_with(self);
1666 }
1667
1668 fn visit_expr_stmt(&mut self, node: &ExprStmt) {
1669 if let Some(call) = as_require_call(&node.expr, self.unresolved_mark) {
1671 self.data
1672 .cjs_imports
1673 .resolved
1674 .insert(call.span.lo, ExportUsage::Evaluation);
1675 }
1676 node.visit_children_with(self);
1677 }
1678
1679 fn visit_update_expr(&mut self, node: &UpdateExpr) {
1680 if let Some(key) = node.arg.as_ident() {
1681 self.register_assignment_scope(key.to_id());
1683 }
1684 node.visit_children_with(self);
1685 }
1686}
1687
1688fn parse_directives(
1691 comments: &dyn Comments,
1692 value: Option<&ExprOrSpread>,
1693) -> Option<ImportAttributes> {
1694 let value = value?;
1695 let leading_comments = comments.get_leading(value.span_lo())?;
1696
1697 let mut ignore = None;
1698 let mut optional = None;
1699 let mut export_names = None;
1700 let mut chunking_type = None;
1701
1702 for comment in leading_comments.iter() {
1704 if let Some((directive, val)) = comment.text.trim().split_once(':') {
1705 let val = val.trim();
1706 match directive.trim() {
1707 "webpackIgnore" | "turbopackIgnore" => match val {
1708 "true" => ignore = Some(true),
1709 "false" => ignore = Some(false),
1710 _ => {}
1711 },
1712 "turbopackOptional" => match val {
1713 "true" => optional = Some(true),
1714 "false" => optional = Some(false),
1715 _ => {}
1716 },
1717 "webpackExports" | "turbopackExports" => {
1718 export_names = Some(parse_export_names(val));
1719 }
1720 "turbopackChunkingType" => {
1721 chunking_type = parse_chunking_type_annotation(value.span(), val);
1722 }
1723 _ => {} }
1725 }
1726 }
1727
1728 if ignore.is_some() || optional.is_some() || export_names.is_some() || chunking_type.is_some() {
1730 Some(ImportAttributes {
1731 ignore: ignore.unwrap_or(false),
1732 optional: optional.unwrap_or(false),
1733 export_names,
1734 chunking_type,
1735 })
1736 } else {
1737 None
1738 }
1739}
1740
1741fn parse_export_names(val: &str) -> SmallVec<[RcStr; 1]> {
1747 let val = val.trim();
1748
1749 if let Ok(names) = serde_json::from_str::<Vec<String>>(val) {
1751 return names.into_iter().map(|s| s.into()).collect();
1752 }
1753
1754 if let Ok(name) = serde_json::from_str::<String>(val) {
1756 return SmallVec::from_buf([name.into()]);
1757 }
1758
1759 if !val.is_empty() {
1761 return SmallVec::from_buf([val.into()]);
1762 }
1763
1764 SmallVec::new()
1765}
1766
1767fn parse_with(with: Option<&ObjectLit>) -> Option<ImportedSymbol> {
1768 find_turbopack_part_id_in_asserts(with?).map(|v| match v {
1769 PartId::Internal(index, true) => ImportedSymbol::PartEvaluation(index),
1770 PartId::Internal(index, false) => ImportedSymbol::Part(index),
1771 PartId::ModuleEvaluation => ImportedSymbol::ModuleEvaluation,
1772 PartId::Export(e) => ImportedSymbol::Symbol(e.as_str().into()),
1773 PartId::Exports => ImportedSymbol::Exports,
1774 })
1775}
1776
1777fn get_import_symbol_from_import(specifier: &ImportSpecifier) -> ImportedSymbol {
1778 match specifier {
1779 ImportSpecifier::Named(ImportNamedSpecifier {
1780 local, imported, ..
1781 }) => ImportedSymbol::Symbol(match imported {
1782 Some(imported) => imported.atom().into_owned(),
1783 _ => local.sym.clone(),
1784 }),
1785 ImportSpecifier::Default(..) => ImportedSymbol::Symbol(atom!("default")),
1786 ImportSpecifier::Namespace(..) => ImportedSymbol::Exports,
1787 }
1788}
1789
1790fn get_import_symbol_from_export(specifier: &ExportSpecifier) -> ImportedSymbol {
1791 match specifier {
1792 ExportSpecifier::Named(ExportNamedSpecifier { orig, .. }) => {
1793 ImportedSymbol::Symbol(orig.atom().into_owned())
1794 }
1795 ExportSpecifier::Default(..) => ImportedSymbol::Symbol(atom!("default")),
1796 ExportSpecifier::Namespace(..) => ImportedSymbol::Exports,
1797 }
1798}
1799
1800fn as_require_call(expr: &Expr, unresolved_mark: Mark) -> Option<&CallExpr> {
1802 let Expr::Call(call) = unparen(expr) else {
1803 return None;
1804 };
1805 let Callee::Expr(callee) = &call.callee else {
1806 return None;
1807 };
1808 let Expr::Ident(f) = &**callee else {
1809 return None;
1810 };
1811 if !is_global(f, "require", unresolved_mark) {
1812 return None;
1813 }
1814 let [arg] = &call.args[..] else {
1815 return None;
1816 };
1817 if arg.spread.is_some() || !matches!(unparen(&arg.expr), Expr::Lit(Lit::Str(_))) {
1818 return None;
1819 }
1820 Some(call)
1821}
1822
1823#[cfg(test)]
1824mod tests {
1825 use swc_core::{atoms::Atom, common::DUMMY_SP};
1826
1827 use super::*;
1828
1829 fn str_lit(s: &str) -> Box<Expr> {
1831 Box::new(Expr::Lit(Lit::Str(Str {
1832 span: DUMMY_SP,
1833 value: Atom::from(s).into(),
1834 raw: None,
1835 })))
1836 }
1837
1838 fn ident_key(s: &str) -> PropName {
1840 PropName::Ident(IdentName {
1841 span: DUMMY_SP,
1842 sym: Atom::from(s),
1843 })
1844 }
1845
1846 fn str_key(s: &str) -> PropName {
1848 PropName::Str(Str {
1849 span: DUMMY_SP,
1850 value: Atom::from(s).into(),
1851 raw: None,
1852 })
1853 }
1854
1855 fn kv_prop(key: PropName, value: Box<Expr>) -> PropOrSpread {
1857 PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { key, value })))
1858 }
1859
1860 #[test]
1861 fn test_parse_turbopack_loader_annotation() {
1862 let with = ObjectLit {
1864 span: DUMMY_SP,
1865 props: vec![kv_prop(ident_key("turbopackLoader"), str_lit("raw-loader"))],
1866 };
1867
1868 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1869 assert!(annotations.has_turbopack_loader());
1870
1871 let loader = annotations.turbopack_loader().unwrap();
1872 assert_eq!(loader.loader.as_str(), "raw-loader");
1873 assert!(loader.options.is_empty());
1874 }
1875
1876 #[test]
1877 fn test_parse_turbopack_loader_with_options() {
1878 let with = ObjectLit {
1880 span: DUMMY_SP,
1881 props: vec![
1882 kv_prop(ident_key("turbopackLoader"), str_lit("my-loader")),
1883 kv_prop(
1884 ident_key("turbopackLoaderOptions"),
1885 str_lit(r#"{"flag":true}"#),
1886 ),
1887 ],
1888 };
1889
1890 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1891 assert!(annotations.has_turbopack_loader());
1892
1893 let loader = annotations.turbopack_loader().unwrap();
1894 assert_eq!(loader.loader.as_str(), "my-loader");
1895 assert_eq!(loader.options["flag"], serde_json::Value::Bool(true));
1896 }
1897
1898 #[test]
1899 fn test_parse_without_turbopack_loader() {
1900 let with = ObjectLit {
1902 span: DUMMY_SP,
1903 props: vec![kv_prop(ident_key("type"), str_lit("json"))],
1904 };
1905
1906 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1907 assert!(!annotations.has_turbopack_loader());
1908 assert!(annotations.module_type().is_some());
1909 }
1910
1911 #[test]
1912 fn test_parse_export_usage_passthrough() {
1913 let with = ObjectLit {
1914 span: DUMMY_SP,
1915 props: vec![kv_prop(
1916 str_key(crate::annotations::ANNOTATION_EXPORT_USAGE),
1917 str_lit("passthrough"),
1918 )],
1919 };
1920
1921 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1922 assert!(annotations.export_usage_passthrough());
1923 }
1924
1925 #[test]
1926 fn test_parse_empty_with() {
1927 let annotations = ImportAnnotations::parse(None);
1928 assert!(annotations.is_none());
1929 }
1930
1931 fn map_with(
1934 n_refs: usize,
1935 exports: Vec<(&str, Export)>,
1936 reexport_namespaces: &[usize],
1937 ) -> ImportMap {
1938 let mut map = ImportMap::default();
1939 for i in 0..n_refs {
1940 map.ensure_reference(
1941 ImportMapReference {
1942 module_path: format!("./m{i}").into(),
1943 imported_symbol: ImportedSymbol::Symbol(Atom::from("x")),
1944 annotations: None,
1945 span: Span::new(BytePos(i as u32 + 1), BytePos(i as u32 + 2)),
1946 },
1947 i,
1948 );
1949 }
1950 for (name, export) in exports {
1951 map.exports.insert(name.into(), export);
1952 }
1953 map.reexport_namespaces = reexport_namespaces.to_vec();
1954 map
1955 }
1956
1957 #[test]
1958 fn reference_declaration_order_does_not_affect_deduplication() {
1959 let mut map = ImportMap::default();
1960 let reference = ImportMapReference {
1961 module_path: "./transformed".into(),
1962 imported_symbol: ImportedSymbol::Symbol("value".into()),
1963 annotations: None,
1964 span: DUMMY_SP,
1965 };
1966
1967 let first = map.ensure_reference(reference.clone(), 2);
1968 let duplicate = map.ensure_reference(reference, 7);
1969
1970 assert_eq!(first, duplicate);
1971 assert_eq!(map.references.len(), 1);
1972 assert_eq!(map.reference_declaration_orders, vec![2]);
1973 }
1974
1975 #[test]
1976 fn export_registration_mode_normal_without_reexports() {
1977 let map = map_with(1, vec![], &[]);
1979 assert_eq!(
1980 map.export_registration_mode(),
1981 ExportRegistrationMode::Normal
1982 );
1983 }
1984
1985 #[test]
1986 fn export_registration_mode_normal_with_a_local_export() {
1987 let map = map_with(
1989 1,
1990 vec![
1991 ("local", Export::LocalBinding("local".into(), false)),
1992 ("a", Export::ImportedBinding(0, "a".into(), false)),
1993 ],
1994 &[],
1995 );
1996 assert_eq!(
1997 map.export_registration_mode(),
1998 ExportRegistrationMode::Normal
1999 );
2000 }
2001
2002 #[test]
2003 fn export_registration_mode_reexport_when_only_reexports() {
2004 let map = map_with(
2006 2,
2007 vec![
2008 ("a", Export::ImportedBinding(0, "a".into(), false)),
2009 ("b", Export::ImportedBinding(1, "b".into(), false)),
2010 ],
2011 &[],
2012 );
2013 assert_eq!(
2014 map.export_registration_mode(),
2015 ExportRegistrationMode::Reexport
2016 );
2017 }
2018
2019 #[test]
2020 fn export_registration_mode_reexport_when_imports_precede() {
2021 let map = map_with(
2024 3,
2025 vec![
2026 ("a", Export::ImportedBinding(1, "a".into(), false)),
2027 ("b", Export::ImportedBinding(2, "b".into(), false)),
2028 ],
2029 &[],
2030 );
2031 assert_eq!(
2032 map.export_registration_mode(),
2033 ExportRegistrationMode::Reexport
2034 );
2035 }
2036
2037 #[test]
2038 fn export_registration_mode_mixed_when_an_import_follows_a_reexport() {
2039 let map = map_with(
2043 3,
2044 vec![
2045 ("a", Export::ImportedBinding(0, "a".into(), false)),
2046 ("c", Export::ImportedBinding(2, "c".into(), false)),
2047 ],
2048 &[],
2049 );
2050 assert_eq!(
2051 map.export_registration_mode(),
2052 ExportRegistrationMode::Mixed
2053 );
2054 }
2055
2056 #[test]
2057 fn export_registration_mode_mixed_for_a_trailing_side_effect_import() {
2058 let mut map = map_with(
2062 1,
2063 vec![("a", Export::ImportedBinding(0, "a".into(), false))],
2064 &[],
2065 );
2066 map.ensure_reference(
2067 ImportMapReference {
2068 module_path: "./effect".into(),
2069 imported_symbol: ImportedSymbol::ModuleEvaluation,
2070 annotations: None,
2071 span: DUMMY_SP,
2072 },
2073 1,
2074 );
2075 assert_eq!(
2076 map.export_registration_mode(),
2077 ExportRegistrationMode::Mixed
2078 );
2079 }
2080
2081 #[test]
2082 fn analyzer_tracks_order_for_dummy_span_declarations() {
2083 fn reexport(path: &str, name: &str) -> ModuleItem {
2084 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
2085 span: DUMMY_SP,
2086 specifiers: vec![ExportSpecifier::Named(ExportNamedSpecifier {
2087 span: DUMMY_SP,
2088 orig: ModuleExportName::Ident(Ident::new(
2089 name.into(),
2090 DUMMY_SP,
2091 Default::default(),
2092 )),
2093 exported: None,
2094 is_type_only: false,
2095 })],
2096 src: Some(Box::new(Str {
2097 span: DUMMY_SP,
2098 value: path.into(),
2099 raw: None,
2100 })),
2101 type_only: false,
2102 with: None,
2103 }))
2104 }
2105
2106 let program = Program::Module(Module {
2107 span: DUMMY_SP,
2108 body: vec![
2109 reexport("./a", "a"),
2110 ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2111 span: DUMMY_SP,
2112 specifiers: vec![],
2113 src: Box::new(Str {
2114 span: DUMMY_SP,
2115 value: "./b".into(),
2116 raw: None,
2117 }),
2118 type_only: false,
2119 with: None,
2120 phase: Default::default(),
2121 })),
2122 reexport("./b", "b"),
2123 ],
2124 shebang: None,
2125 });
2126 let globals = Default::default();
2127 let map = GLOBALS.set(&globals, || ImportMap::analyze(Mark::new(), &program, None));
2128
2129 assert_eq!(map.reference_declaration_orders, vec![0, 1, 0, 2]);
2130 assert_eq!(
2131 map.export_registration_mode(),
2132 ExportRegistrationMode::Mixed
2133 );
2134 }
2135
2136 #[test]
2137 fn export_registration_mode_does_not_use_spans_or_reference_indices_for_order() {
2138 let mut map = ImportMap::default();
2144 for (path, symbol, declaration_order) in [
2145 ("./a", ImportedSymbol::ModuleEvaluation, 0),
2146 ("./b", ImportedSymbol::ModuleEvaluation, 1),
2147 ("./a", ImportedSymbol::Symbol("a".into()), 0),
2148 ("./b", ImportedSymbol::Symbol("b".into()), 2),
2149 ] {
2150 map.ensure_reference(
2151 ImportMapReference {
2152 module_path: path.into(),
2153 imported_symbol: symbol,
2154 annotations: None,
2155 span: DUMMY_SP,
2156 },
2157 declaration_order,
2158 );
2159 }
2160 map.exports
2161 .insert("a".into(), Export::ImportedBinding(2, "a".into(), false));
2162 map.exports
2163 .insert("b".into(), Export::ImportedBinding(3, "b".into(), false));
2164
2165 assert_eq!(
2166 map.export_registration_mode(),
2167 ExportRegistrationMode::Mixed
2168 );
2169 }
2170
2171 #[test]
2172 fn export_registration_mode_reexport_for_a_star_reexport() {
2173 let map = map_with(1, vec![], &[0]);
2175 assert_eq!(
2176 map.export_registration_mode(),
2177 ExportRegistrationMode::Reexport
2178 );
2179 }
2180}