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 rustc_hash::{FxHashMap, FxHashSet};
10use smallvec::SmallVec;
11use swc_core::{
12 atoms::Wtf8Atom,
13 common::{BytePos, GLOBALS, Mark, Span, Spanned, SyntaxContext, comments::Comments},
14 ecma::{
15 ast::*,
16 atoms::{Atom, atom},
17 utils::{IsDirective, find_pat_ids},
18 visit::{Visit, VisitWith},
19 },
20};
21use turbo_frozenmap::FrozenMap;
22use turbo_rcstr::{RcStr, rcstr};
23use turbo_tasks::{FxIndexMap, FxIndexSet, ResolvedVc};
24use turbopack_core::{loader::WebpackLoaderItem, resolve::ImportUsage};
25
26use super::{JsValue, ModuleValue, top_level_await::has_top_level_await};
27use crate::{
28 SpecifiedModuleType,
29 analyzer::{
30 Bump, ConstantValue, ObjectPart,
31 graph::{AssignmentScope, AssignmentScopes, EvalContext},
32 is_unresolved, is_unresolved_id,
33 },
34 magic_identifier::{MAGIC_IDENTIFIER_DEFAULT_EXPORT, MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM},
35 references::{
36 esm::{EsmAssetReference, EsmExport, Liveness},
37 util::{SpecifiedChunkingType, parse_chunking_type_annotation},
38 },
39 tree_shake::{PartId, find_turbopack_part_id_in_asserts},
40};
41
42#[turbo_tasks::value]
43#[derive(Default, Debug, Clone, Hash)]
44pub struct ImportAnnotations {
45 #[turbo_tasks(trace_ignore)]
47 #[bincode(with_serde)]
48 map: BTreeMap<Wtf8Atom, Wtf8Atom>,
49 #[turbo_tasks(trace_ignore)]
52 #[bincode(with_serde)]
53 turbopack_loader: Option<WebpackLoaderItem>,
54 turbopack_rename_as: Option<RcStr>,
55 turbopack_module_type: Option<RcStr>,
56 chunking_type: Option<SpecifiedChunkingType>,
57}
58
59static ANNOTATION_TRANSITION: LazyLock<Wtf8Atom> =
61 LazyLock::new(|| crate::annotations::ANNOTATION_TRANSITION.into());
62
63static ATTRIBUTE_MODULE_TYPE: LazyLock<Wtf8Atom> = LazyLock::new(|| atom!("type").into());
65
66impl ImportAnnotations {
67 pub fn parse(with: Option<&ObjectLit>) -> Option<ImportAnnotations> {
68 let with = with?;
69
70 let mut map = BTreeMap::new();
71 let mut turbopack_loader_name: Option<RcStr> = None;
72 let mut turbopack_loader_options: serde_json::Map<String, serde_json::Value> =
73 serde_json::Map::new();
74 let mut turbopack_rename_as: Option<RcStr> = None;
75 let mut turbopack_module_type: Option<RcStr> = None;
76 let mut chunking_type: Option<SpecifiedChunkingType> = None;
77
78 for prop in &with.props {
79 let Some(kv) = prop.as_prop().and_then(|p| p.as_key_value()) else {
80 continue;
81 };
82
83 let key_str = match &kv.key {
84 PropName::Ident(ident) => Cow::Borrowed(ident.sym.as_str()),
85 PropName::Str(str) => str.value.to_string_lossy(),
86 _ => continue,
87 };
88
89 match &*key_str {
91 "turbopackLoader" => {
92 if let Some(Lit::Str(s)) = kv.value.as_lit() {
93 turbopack_loader_name =
94 Some(RcStr::from(s.value.to_string_lossy().into_owned()));
95 }
96 }
97 "turbopackLoaderOptions" => {
98 if let Some(Lit::Str(s)) = kv.value.as_lit() {
99 let json_str = s.value.to_string_lossy();
100 if let Ok(serde_json::Value::Object(map)) = serde_json::from_str(&json_str)
101 {
102 turbopack_loader_options = map;
103 }
104 }
105 }
106 "turbopackAs" => {
107 if let Some(Lit::Str(s)) = kv.value.as_lit() {
108 turbopack_rename_as =
109 Some(RcStr::from(s.value.to_string_lossy().into_owned()));
110 }
111 }
112 "turbopackModuleType" => {
113 if let Some(Lit::Str(s)) = kv.value.as_lit() {
114 turbopack_module_type =
115 Some(RcStr::from(s.value.to_string_lossy().into_owned()));
116 }
117 }
118 "turbopack-chunking-type" => {
119 if let Some(Lit::Str(s)) = kv.value.as_lit() {
120 chunking_type = parse_chunking_type_annotation(
121 kv.value.span(),
122 &s.value.to_string_lossy(),
123 );
124 }
125 }
126 _ => {
127 if let Some(Lit::Str(str)) = kv.value.as_lit() {
129 let key: Wtf8Atom = match &kv.key {
130 PropName::Ident(ident) => ident.sym.clone().into(),
131 PropName::Str(s) => s.value.clone(),
132 _ => continue,
133 };
134 map.insert(key, str.value.clone());
135 }
136 }
137 }
138 }
139
140 let turbopack_loader = turbopack_loader_name.map(|name| WebpackLoaderItem {
141 loader: name,
142 options: turbopack_loader_options,
143 });
144
145 if !map.is_empty()
146 || turbopack_loader.is_some()
147 || turbopack_rename_as.is_some()
148 || turbopack_module_type.is_some()
149 || chunking_type.is_some()
150 {
151 Some(ImportAnnotations {
152 map,
153 turbopack_loader,
154 turbopack_rename_as,
155 turbopack_module_type,
156 chunking_type,
157 })
158 } else {
159 None
160 }
161 }
162
163 pub fn parse_dynamic(with: &JsValue<'_>) -> Option<ImportAnnotations> {
164 let mut map = BTreeMap::new();
165
166 let JsValue::Object { parts, .. } = with else {
167 return None;
168 };
169
170 for part in parts.iter() {
171 let ObjectPart::KeyValue(key, value) = part else {
172 continue;
173 };
174 let (
175 JsValue::Constant(ConstantValue::Str(key)),
176 JsValue::Constant(ConstantValue::Str(value)),
177 ) = (key, value)
178 else {
179 continue;
180 };
181
182 map.insert(
183 key.as_atom().into_owned().into(),
184 value.as_atom().into_owned().into(),
185 );
186 }
187
188 if !map.is_empty() {
189 Some(ImportAnnotations {
190 map,
191 turbopack_loader: None,
192 turbopack_rename_as: None,
193 turbopack_module_type: None,
194 chunking_type: None,
195 })
196 } else {
197 None
198 }
199 }
200
201 pub fn transition(&self) -> Option<Cow<'_, str>> {
203 self.get(&ANNOTATION_TRANSITION)
204 .map(|v| v.to_string_lossy())
205 }
206
207 pub fn chunking_type(&self) -> Option<SpecifiedChunkingType> {
209 self.chunking_type
210 }
211
212 pub fn module_type(&self) -> Option<&Wtf8Atom> {
214 self.get(&ATTRIBUTE_MODULE_TYPE)
215 }
216
217 pub fn turbopack_loader(&self) -> Option<&WebpackLoaderItem> {
219 self.turbopack_loader.as_ref()
220 }
221
222 pub fn turbopack_rename_as(&self) -> Option<&RcStr> {
224 self.turbopack_rename_as.as_ref()
225 }
226
227 pub fn turbopack_module_type(&self) -> Option<&RcStr> {
229 self.turbopack_module_type.as_ref()
230 }
231
232 pub fn has_turbopack_loader(&self) -> bool {
234 self.turbopack_loader.is_some()
235 }
236
237 pub fn get(&self, key: &Wtf8Atom) -> Option<&Wtf8Atom> {
238 self.map.get(key)
239 }
240}
241
242impl Display for ImportAnnotations {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 let mut it = self.map.iter();
245 if let Some((k, v)) = it.next() {
246 write!(f, "{{ {}: {}", k.to_string_lossy(), v.to_string_lossy())?
247 } else {
248 return f.write_str("{}");
249 };
250 for (k, v) in it {
251 write!(f, ", {}: {}", k.to_string_lossy(), v.to_string_lossy())?
252 }
253 f.write_str(" }")
254 }
255}
256
257#[derive(Clone, Debug)]
258pub enum DeclUsage {
259 SideEffects,
260 Bindings(FxHashSet<Id>),
261}
262impl Default for DeclUsage {
263 fn default() -> Self {
264 DeclUsage::Bindings(Default::default())
265 }
266}
267impl DeclUsage {
268 fn add_usage(&mut self, user: &Id) {
269 match self {
270 Self::Bindings(set) => {
271 set.insert(user.clone());
272 }
273 Self::SideEffects => {}
274 }
275 }
276 fn make_side_effects(&mut self) {
277 *self = Self::SideEffects;
278 }
279}
280
281#[derive(Default, Debug)]
282pub(crate) struct ProgramDeclUsage {
283 pub(crate) decl_usages: FxHashMap<Id, DeclUsage>,
285 pub(crate) import_usages: FxHashMap<usize, DeclUsage>,
287 pub(crate) exports: FxHashMap<RcStr, Id>,
289}
290impl ProgramDeclUsage {
291 fn compute_import_usage(&self) -> FxHashMap<usize, ImportUsage> {
292 let mut import_usage =
293 FxHashMap::with_capacity_and_hasher(self.import_usages.len(), Default::default());
294 for (reference, usage) in &self.import_usages {
295 if let DeclUsage::Bindings(ids) = usage {
297 let mut visited = ids.clone();
299 let mut stack = ids.iter().collect::<Vec<_>>();
300 let mut has_global_usage = false;
301 while let Some(id) = stack.pop() {
302 match self.decl_usages.get(id) {
303 Some(DeclUsage::SideEffects) => {
304 has_global_usage = true;
305 break;
306 }
307 Some(DeclUsage::Bindings(callers)) => {
308 for caller in callers {
309 if visited.insert(caller.clone()) {
310 stack.push(caller);
311 }
312 }
313 }
314 _ => {}
315 }
316 }
317
318 import_usage.insert(
320 *reference,
321 if has_global_usage {
322 ImportUsage::TopLevel
323 } else {
324 ImportUsage::Exports(
325 self.exports
326 .iter()
327 .filter(|(_, id)| visited.contains(*id))
328 .map(|(exported, _)| exported.clone())
329 .collect(),
330 )
331 },
332 );
333 }
334 }
335 import_usage
336 }
337}
338
339#[derive(Debug)]
342pub enum Export {
343 LocalBinding(RcStr, bool),
347 ImportedBinding(usize, RcStr, bool),
351 ImportedNamespace(usize),
353 Error,
355}
356
357#[derive(Default, Debug)]
362pub(crate) struct ImportMap {
363 imports: FxIndexMap<Id, (usize, Atom)>,
365
366 namespace_imports: FxIndexMap<Id, usize>,
368
369 exports: BTreeMap<RcStr, Export>,
371
372 reexport_namespaces: Vec<usize>,
374
375 references: FxIndexSet<ImportMapReference>,
377
378 has_imports: bool,
381
382 has_exports: bool,
385
386 has_top_level_await: bool,
388
389 pub(crate) strict: bool,
391
392 attributes: FxHashMap<BytePos, ImportAttributes>,
399
400 full_star_imports: FxHashSet<Wtf8Atom>,
403
404 pub(super) assignment_scopes: FxHashMap<Id, AssignmentScopes>,
407
408 pub(crate) import_usage: FxHashMap<usize, ImportUsage>,
409
410 pub(crate) exports_ids: FxHashMap<RcStr, Id>,
412}
413
414#[derive(Debug)]
419pub struct ImportAttributes {
420 pub ignore: bool,
431 pub optional: bool,
441 pub export_names: Option<SmallVec<[RcStr; 1]>>,
455 pub chunking_type: Option<SpecifiedChunkingType>,
464}
465
466impl ImportAttributes {
467 pub const fn empty() -> Self {
468 ImportAttributes {
469 ignore: false,
470 optional: false,
471 export_names: None,
472 chunking_type: None,
473 }
474 }
475
476 pub fn empty_ref() -> &'static Self {
477 static DEFAULT_VALUE: ImportAttributes = ImportAttributes::empty();
479 &DEFAULT_VALUE
480 }
481}
482
483impl Default for ImportAttributes {
484 fn default() -> Self {
485 ImportAttributes::empty()
486 }
487}
488
489impl Default for &ImportAttributes {
490 fn default() -> Self {
491 ImportAttributes::empty_ref()
492 }
493}
494
495#[derive(Debug, Clone, PartialEq, Eq, Hash)]
496pub(crate) enum ImportedSymbol {
497 ModuleEvaluation,
498 Symbol(Atom),
499 Exports,
500 Part(u32),
501 PartEvaluation(u32),
502}
503
504#[derive(Debug, Clone, PartialEq, Eq, Hash)]
505pub(crate) struct ImportMapReference {
506 pub module_path: Wtf8Atom,
507 pub imported_symbol: ImportedSymbol,
508 pub annotations: Option<Arc<ImportAnnotations>>,
509 pub span: Span,
510}
511
512impl ImportMap {
513 pub fn is_esm(&self, specified_type: SpecifiedModuleType) -> bool {
514 if self.has_exports {
515 return true;
516 }
517
518 match specified_type {
519 SpecifiedModuleType::Automatic => {
520 self.has_exports || self.has_imports || self.has_top_level_await
521 }
522 SpecifiedModuleType::CommonJs => false,
523 SpecifiedModuleType::EcmaScript => true,
524 }
525 }
526
527 pub fn get_import<'a>(&self, arena: &'a Bump, id: &Id) -> Option<JsValue<'a>> {
528 if let Some((i, i_sym)) = self.imports.get(id) {
529 let r = &self.references[*i];
530 return Some(JsValue::member(
531 arena,
532 JsValue::Module(ModuleValue {
533 module: r.module_path.clone(),
534 annotations: r.annotations.clone(),
535 }),
536 i_sym.clone().into(),
537 ));
538 }
539 if let Some(i) = self.namespace_imports.get(id) {
540 let r = &self.references[*i];
541 return Some(JsValue::Module(ModuleValue {
542 module: r.module_path.clone(),
543 annotations: r.annotations.clone(),
544 }));
545 }
546 None
547 }
548
549 pub fn get_attributes(&self, span: Span) -> &ImportAttributes {
550 self.attributes.get(&span.lo).unwrap_or_default()
551 }
552
553 pub fn get_binding(&self, id: &Id) -> Option<(usize, Option<&Atom>)> {
554 if let Some((i, i_sym)) = self.imports.get(id) {
555 return Some((*i, Some(i_sym)));
556 }
557 if let Some(i) = self.namespace_imports.get(id) {
558 return Some((*i, None));
559 }
560 None
561 }
562
563 pub fn references(&self) -> impl ExactSizeIterator<Item = &ImportMapReference> {
564 self.references.iter()
565 }
566
567 pub fn reexports_reference_idxs(&self) -> impl Iterator<Item = usize> {
568 self.exports
569 .values()
570 .filter_map(|value| match value {
571 Export::ImportedBinding(i, ..) | Export::ImportedNamespace(i) => Some(*i),
572 Export::LocalBinding(..) | Export::Error => None,
573 })
574 .chain(self.reexport_namespaces.iter().copied())
575 }
576
577 pub fn as_esm_exports(
578 &self,
579 import_references: &[ResolvedVc<EsmAssetReference>],
580 eval_context: &EvalContext,
581 ) -> Result<FrozenMap<RcStr, EsmExport>> {
582 Ok(FrozenMap::from(
583 self.exports
584 .iter()
585 .map(|(name, value)| {
586 let value = match value {
587 Export::LocalBinding(local, is_fake_esm) => EsmExport::LocalBinding(
588 local.clone(),
589 if *is_fake_esm {
590 Liveness::Mutable
592 } else {
593 eval_context.imports.get_export_ident_liveness(
594 self.exports_ids.get(name).cloned().with_context(|| {
595 format!("Exported binding {name} not found in exports_ids")
596 })?,
597 eval_context.unresolved_mark,
598 )
599 },
600 ),
601 Export::ImportedBinding(i, name, is_fake_esm) => {
602 EsmExport::ImportedBinding(
603 ResolvedVc::upcast(import_references[*i]),
604 name.clone(),
605 *is_fake_esm,
606 )
607 }
608 Export::ImportedNamespace(i) => {
609 EsmExport::ImportedNamespace(ResolvedVc::upcast(import_references[*i]))
610 }
611 Export::Error => EsmExport::Error,
612 };
613 Ok((name.clone(), value))
614 })
615 .collect::<Result<Vec<_>>>()?,
616 ))
617 }
618
619 pub fn reexport_namespaces(&self) -> impl ExactSizeIterator<Item = usize> {
620 self.reexport_namespaces.iter().copied()
621 }
622
623 pub fn get_export_ident_liveness(&self, id: Id, unresolved_mark: Mark) -> Liveness {
626 if let Some(assignment_scopes) = self.assignment_scopes.get(&id) {
627 if *assignment_scopes != AssignmentScopes::AllInModuleEvalScope {
629 Liveness::Live
630 } else {
631 Liveness::Constant
632 }
633 } else {
634 debug_assert!(
639 self.imports.contains_key(&id)
640 || self.namespace_imports.contains_key(&id)
641 || !GLOBALS.is_set()
642 || is_unresolved_id(&id, unresolved_mark),
643 "export ident {id:?} without an assignment scope should be a free variable or an \
644 imported variable"
645 );
646
647 Liveness::Live
648 }
649 }
650
651 pub(super) fn analyze(
653 unresolved_mark: Mark,
654 m: &Program,
655 comments: Option<&dyn Comments>,
656 ) -> Self {
657 let mut data = ImportMap::default();
658 let mut analyzer = Analyzer {
659 unresolved_mark,
660 data: &mut data,
661 comments,
662 namespace_imports_to_specifier: FxIndexMap::default(),
663 state: Default::default(),
664 program_decl_usage: Default::default(),
665 };
666
667 if let Program::Module(m) = m {
669 for stmt in &m.body {
670 match stmt {
671 ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
672 if import.type_only {
673 continue;
674 }
675 analyzer.data.has_imports = true;
676 let annotations = ImportAnnotations::parse(import.with.as_deref());
677 let internal_symbol = parse_with(import.with.as_deref());
678 if internal_symbol.is_none() {
679 analyzer.ensure_reference(
680 import.span,
681 import.src.value.clone(),
682 ImportedSymbol::ModuleEvaluation,
683 annotations.clone(),
684 );
685 }
686
687 for s in &import.specifiers {
688 if s.is_type_only() {
689 continue;
690 }
691 let symbol = internal_symbol
692 .clone()
693 .unwrap_or_else(|| get_import_symbol_from_import(s));
694 let i = analyzer.ensure_reference(
695 import.span,
696 import.src.value.clone(),
697 symbol,
698 annotations.clone(),
699 );
700
701 let (local, orig_sym) = match s {
702 ImportSpecifier::Namespace(s) => {
703 analyzer
704 .namespace_imports_to_specifier
705 .insert(s.local.to_id(), import.src.value.clone());
706 analyzer.data.namespace_imports.insert(s.local.to_id(), i);
707 continue;
708 }
709 ImportSpecifier::Default(s) => (s.local.to_id(), atom!("default")),
710 ImportSpecifier::Named(s) => match &s.imported {
711 Some(imported) => {
712 (s.local.to_id(), imported.atom().into_owned())
713 }
714 _ => (s.local.to_id(), s.local.sym.clone()),
715 },
716 };
717 analyzer.data.imports.insert(local, (i, orig_sym));
718 }
719 if import.specifiers.is_empty()
720 && let Some(internal_symbol) = internal_symbol
721 {
722 analyzer.ensure_reference(
723 import.span,
724 import.src.value.clone(),
725 internal_symbol,
726 annotations,
727 );
728 }
729 }
730 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export)) => {
733 if export.type_only {
734 continue;
735 }
736 let annotations = ImportAnnotations::parse(export.with.as_deref());
737 analyzer.ensure_reference(
738 export.span,
739 export.src.value.clone(),
740 ImportedSymbol::ModuleEvaluation,
741 annotations.clone(),
742 );
743 }
744 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
745 if export.type_only {
746 continue;
747 }
748 if let Some(ref src) = export.src {
749 let annotations = ImportAnnotations::parse(export.with.as_deref());
750 let internal_symbol = parse_with(export.with.as_deref());
751 if internal_symbol.is_none() || export.specifiers.is_empty() {
752 analyzer.ensure_reference(
753 export.span,
754 src.value.clone(),
755 ImportedSymbol::ModuleEvaluation,
756 annotations.clone(),
757 );
758 }
759 }
760 }
761 _ => (),
762 }
763 }
764 }
765
766 m.visit_with(&mut analyzer);
767
768 data.import_usage = analyzer.program_decl_usage.compute_import_usage();
769
770 data
771 }
772
773 pub(crate) fn should_import_all(&self, esm_reference_index: usize) -> bool {
774 let r = &self.references[esm_reference_index];
775
776 self.full_star_imports.contains(&r.module_path)
777 }
778}
779
780mod analyzer_state {
781 use swc_core::ecma::ast::{Id, Ident};
782
783 use super::Analyzer;
784
785 #[derive(Default)]
786 pub(super) struct AnalyzerState {
787 is_in_fn: bool,
788 cur_top_level_decl_name: Option<Id>,
789 }
790
791 impl AnalyzerState {
792 pub(super) fn cur_top_level_decl_name(&self) -> &Option<Id> {
794 &self.cur_top_level_decl_name
795 }
796
797 pub(super) fn is_in_fn(&self) -> bool {
799 self.is_in_fn
800 }
801 }
802
803 impl Analyzer<'_> {
804 pub(super) fn enter_top_level_decl<T>(
806 &mut self,
807 name: &Ident,
808 visitor: impl FnOnce(&mut Self) -> T,
809 ) -> T {
810 let is_top_level_fn = self.state.cur_top_level_decl_name.is_none();
811 if is_top_level_fn {
812 self.state.cur_top_level_decl_name = Some(name.to_id());
813 }
814 let result = visitor(self);
815 if is_top_level_fn {
816 self.state.cur_top_level_decl_name = None;
817 }
818 result
819 }
820
821 pub(super) fn enter_fn<T>(&mut self, visitor: impl FnOnce(&mut Self) -> T) -> T {
823 let old_is_in_fn = self.state.is_in_fn;
824 self.state.is_in_fn = true;
825 let result = visitor(self);
826 self.state.is_in_fn = old_is_in_fn;
827 result
828 }
829 }
830}
831
832struct Analyzer<'a> {
833 unresolved_mark: Mark,
834 data: &'a mut ImportMap,
835 comments: Option<&'a dyn Comments>,
836 namespace_imports_to_specifier: FxIndexMap<Id, Wtf8Atom>,
839
840 program_decl_usage: ProgramDeclUsage,
841
842 state: analyzer_state::AnalyzerState,
843}
844
845impl Analyzer<'_> {
846 fn ensure_reference(
847 &mut self,
848 span: Span,
849 module_path: Wtf8Atom,
850 imported_symbol: ImportedSymbol,
851 annotations: Option<ImportAnnotations>,
852 ) -> usize {
853 let r = ImportMapReference {
854 module_path,
855 imported_symbol,
856 span,
857 annotations: annotations.map(Arc::new),
858 };
859 if let Some(i) = self.data.references.get_index_of(&r) {
860 i
861 } else {
862 let i = self.data.references.len();
863 self.data.references.insert(r);
864 i
865 }
866 }
867
868 fn register_assignment_scope(&mut self, id: Id) {
869 let scope = if self.state.is_in_fn() {
870 AssignmentScope::Function
871 } else {
872 AssignmentScope::ModuleEval
873 };
874
875 match self.data.assignment_scopes.entry(id) {
876 Entry::Occupied(mut e) => {
877 *e.get_mut() = e.get().merge(scope);
878 }
879 Entry::Vacant(e) => {
880 e.insert(AssignmentScopes::new(scope));
881 }
882 }
883 }
884}
885
886impl Visit for Analyzer<'_> {
887 fn visit_import_decl(&mut self, _: &ImportDecl) {
888 }
890
891 fn visit_export_all(&mut self, export: &ExportAll) {
892 if export.type_only {
893 return;
894 }
895
896 let annotations = ImportAnnotations::parse(export.with.as_deref());
897
898 let symbol = parse_with(export.with.as_deref());
899 let i = self.ensure_reference(
900 export.span,
901 export.src.value.clone(),
902 symbol.unwrap_or(ImportedSymbol::Exports),
903 annotations,
904 );
905 self.data.reexport_namespaces.push(i);
906 self.data.has_exports = true;
907 export.visit_children_with(self);
908 }
909
910 fn visit_named_export(&mut self, export: &NamedExport) {
911 if export.type_only {
912 return;
913 }
914
915 self.data.has_exports = true;
916
917 if let Some(ref src) = export.src {
918 let annotations = ImportAnnotations::parse(export.with.as_deref());
919 let internal_symbol = parse_with(export.with.as_deref());
920
921 for spec in export.specifiers.iter() {
922 let symbol = internal_symbol
923 .clone()
924 .unwrap_or_else(|| get_import_symbol_from_export(spec));
925
926 let i = self.ensure_reference(
927 export.span,
928 src.value.clone(),
929 symbol,
930 annotations.clone(),
931 );
932
933 match spec {
934 ExportSpecifier::Namespace(n) => {
935 self.data.exports.insert(
936 RcStr::from(n.name.atom().as_str()),
937 Export::ImportedNamespace(i),
938 );
939 }
940 ExportSpecifier::Default(d) => {
941 self.data.exports.insert(
942 RcStr::from(d.exported.sym.as_str()),
943 Export::ImportedBinding(i, rcstr!("default"), false),
944 );
945 }
946 ExportSpecifier::Named(n) => {
947 self.data.exports.insert(
948 RcStr::from(n.exported.as_ref().unwrap_or(&n.orig).atom().as_str()),
949 Export::ImportedBinding(i, RcStr::from(n.orig.atom().as_str()), false),
950 );
951 }
952 }
953 }
954 } else {
955 for spec in export.specifiers.iter() {
956 match spec {
957 ExportSpecifier::Namespace(_) => {
958 unreachable!(
959 "ExportNamespaceSpecifier will not happen in combination with src == \
960 None"
961 );
962 }
963 ExportSpecifier::Default(_) => {
964 unreachable!(
965 "ExportDefaultSpecifier will not happen in combination with src == \
966 None"
967 );
968 }
969 ExportSpecifier::Named(ExportNamedSpecifier {
970 orig,
971 exported,
972 is_type_only,
973 ..
974 }) => {
975 if *is_type_only {
976 continue;
977 }
978
979 let is_fake_esm = export
981 .with
982 .as_deref()
983 .map(find_turbopack_part_id_in_asserts)
984 .is_some();
985 let export = {
986 let imported_binding = if let ModuleExportName::Ident(ident) = orig {
987 self.data.get_binding(&ident.to_id())
988 } else {
989 None
990 };
991 if let Some((index, export)) = imported_binding {
992 if let Some(export) = export {
995 Export::ImportedBinding(
996 index,
997 RcStr::from(export.as_str()),
998 is_fake_esm,
999 )
1000 } else {
1001 Export::ImportedNamespace(index)
1002 }
1003 } else {
1004 Export::LocalBinding(RcStr::from(orig.atom().as_str()), is_fake_esm)
1005 }
1006 };
1007 self.data.exports.insert(
1008 RcStr::from(exported.as_ref().unwrap_or(orig).atom().as_str()),
1009 export,
1010 );
1011 }
1012 }
1013 }
1014 export.visit_children_with(self);
1015 }
1016 }
1017
1018 fn visit_export_decl(&mut self, n: &ExportDecl) {
1019 self.data.has_exports = true;
1020 match &n.decl {
1021 Decl::Class(n) => {
1022 let name = RcStr::from(n.ident.sym.as_str());
1023 self.data
1024 .exports
1025 .insert(name.clone(), Export::LocalBinding(name.clone(), false));
1026 self.data.exports_ids.insert(name.clone(), n.ident.to_id());
1027 self.program_decl_usage
1028 .exports
1029 .insert(name, n.ident.to_id());
1030 }
1031 Decl::Fn(n) => {
1032 let name = RcStr::from(n.ident.sym.as_str());
1033 self.data
1034 .exports
1035 .insert(name.clone(), Export::LocalBinding(name.clone(), false));
1036 self.data.exports_ids.insert(name.clone(), n.ident.to_id());
1037 self.program_decl_usage
1038 .exports
1039 .insert(name, n.ident.to_id());
1040 }
1041 Decl::Var(..) => {
1042 let ids: Vec<Id> = find_pat_ids(&n.decl);
1043 for id in ids {
1044 let name = RcStr::from(id.0.as_str());
1045 self.data
1046 .exports
1047 .insert(name.clone(), Export::LocalBinding(name.clone(), false));
1048 self.data.exports_ids.insert(name.clone(), id.clone());
1049 self.program_decl_usage.exports.insert(name, id);
1050 }
1051 }
1052 Decl::Using(_) => {
1053 unreachable!("using declarations can not be exported");
1055 }
1056 Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {
1057 }
1059 }
1060
1061 n.visit_children_with(self);
1062 }
1063
1064 fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) {
1065 self.data.has_exports = true;
1066
1067 let id = match &n.decl {
1068 DefaultDecl::Class(ClassExpr { ident, .. }) | DefaultDecl::Fn(FnExpr { ident, .. }) => {
1069 ident.as_ref().map_or_else(
1072 || {
1073 (
1074 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1075 SyntaxContext::empty(),
1076 )
1077 },
1078 |ident| ident.to_id(),
1079 )
1080 }
1081 DefaultDecl::TsInterfaceDecl(_) => {
1082 (
1084 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1085 SyntaxContext::empty(),
1086 )
1087 }
1088 };
1089
1090 self.register_assignment_scope(id.clone());
1091 self.data.exports.insert(
1092 rcstr!("default"),
1093 Export::LocalBinding(RcStr::from(id.0.as_str()), false),
1094 );
1095 self.data.exports_ids.insert(rcstr!("default"), id.clone());
1096 self.program_decl_usage
1097 .exports
1098 .insert(rcstr!("default"), id);
1099 n.visit_children_with(self);
1100 }
1101
1102 fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) {
1103 self.data.has_exports = true;
1104
1105 let default_id = (
1106 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1107 SyntaxContext::empty(),
1108 );
1109
1110 self.data.exports.insert(
1111 rcstr!("default"),
1112 Export::LocalBinding(MAGIC_IDENTIFIER_DEFAULT_EXPORT.clone(), false),
1113 );
1114 self.data
1115 .exports_ids
1116 .insert(rcstr!("default"), default_id.clone());
1117
1118 self.register_assignment_scope(default_id);
1119 n.visit_children_with(self);
1120 }
1121
1122 fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) {
1123 self.data.has_exports = true;
1124
1125 let ModuleExportName::Ident(local) = &n.orig else {
1126 unreachable!("exporting a string should be impossible")
1127 };
1128 let exported = RcStr::from(n.exported.as_ref().unwrap_or(&n.orig).atom().as_str());
1129 self.data
1130 .exports_ids
1131 .insert(exported.clone(), local.to_id());
1132 self.program_decl_usage
1133 .exports
1134 .insert(exported, local.to_id());
1135 n.visit_children_with(self);
1136 }
1137
1138 fn visit_export_default_specifier(&mut self, n: &ExportDefaultSpecifier) {
1139 self.data.has_exports = true;
1140
1141 self.data
1142 .exports_ids
1143 .insert(rcstr!("default"), n.exported.to_id());
1144 n.visit_children_with(self);
1145 }
1146
1147 fn visit_program(&mut self, m: &Program) {
1148 self.data.has_top_level_await = has_top_level_await(m).is_some();
1149 self.data.strict = match m {
1150 Program::Module(module) => module
1151 .body
1152 .iter()
1153 .take_while(|s| s.directive_continue())
1154 .any(IsDirective::is_use_strict),
1155 Program::Script(script) => script
1156 .body
1157 .iter()
1158 .take_while(|s| s.directive_continue())
1159 .any(IsDirective::is_use_strict),
1160 };
1161
1162 m.visit_children_with(self);
1163 }
1164
1165 fn visit_call_expr(&mut self, n: &CallExpr) {
1179 if let Some(comments) = self.comments {
1180 let callee_span = match &n.callee {
1181 Callee::Import(Import { span, .. }) => Some(*span),
1182 Callee::Expr(e) => Some(e.span()),
1183 _ => None,
1184 };
1185
1186 if let Some(callee_span) = callee_span
1187 && let Some(attributes) = parse_directives(comments, n.args.first())
1188 {
1189 self.data.attributes.insert(callee_span.lo, attributes);
1190 }
1191 }
1192
1193 n.visit_children_with(self);
1194 }
1195
1196 fn visit_new_expr(&mut self, n: &NewExpr) {
1197 if let Some(comments) = self.comments {
1198 let callee_span = match &*n.callee {
1199 Expr::Ident(Ident { sym, .. }) if sym == "Worker" => Some(n.span),
1200 _ => None,
1201 };
1202
1203 if let Some(callee_span) = callee_span
1204 && let Some(attributes) = parse_directives(comments, n.args.iter().flatten().next())
1205 {
1206 self.data.attributes.insert(callee_span.lo, attributes);
1207 }
1208 }
1209
1210 n.visit_children_with(self);
1211 }
1212
1213 fn visit_getter_prop(&mut self, node: &GetterProp) {
1214 self.enter_fn(|this| {
1215 node.visit_children_with(this);
1216 });
1217 }
1218 fn visit_setter_prop(&mut self, node: &SetterProp) {
1219 self.enter_fn(|this| {
1220 node.visit_children_with(this);
1221 });
1222 }
1223 fn visit_function(&mut self, node: &Function) {
1224 self.enter_fn(|this| {
1225 node.visit_children_with(this);
1226 });
1227 }
1228 fn visit_constructor(&mut self, node: &Constructor) {
1229 self.enter_fn(|this| {
1230 node.visit_children_with(this);
1231 });
1232 }
1233 fn visit_arrow_expr(&mut self, node: &ArrowExpr) {
1234 self.enter_fn(|this| {
1235 node.visit_children_with(this);
1236 });
1237 }
1238
1239 fn visit_member_expr(&mut self, node: &MemberExpr) {
1240 if matches!(
1241 &node.prop,
1242 MemberProp::Ident(..) | MemberProp::PrivateName(..)
1243 ) && let Expr::Ident(ident) = &*node.obj
1244 {
1245 ident.visit_with(self);
1248 } else {
1249 node.visit_children_with(self);
1250 }
1251 }
1252
1253 fn visit_expr(&mut self, node: &Expr) {
1254 if let Expr::Ident(i) = node
1257 && let Some(module_path) = self.namespace_imports_to_specifier.get(&i.to_id())
1258 {
1259 self.data.full_star_imports.insert(module_path.clone());
1260 }
1261 node.visit_children_with(self);
1262 }
1263
1264 fn visit_pat(&mut self, pat: &Pat) {
1265 if let Pat::Ident(i) = pat {
1266 self.register_assignment_scope(i.to_id());
1267 if let Some(module_path) = self.namespace_imports_to_specifier.get(&i.to_id()) {
1268 self.data.full_star_imports.insert(module_path.clone());
1269 }
1270 }
1271 pat.visit_children_with(self);
1272 }
1273
1274 fn visit_simple_assign_target(&mut self, node: &SimpleAssignTarget) {
1275 if let SimpleAssignTarget::Ident(i) = node {
1276 self.register_assignment_scope(i.to_id());
1277 if let Some(module_path) = self.namespace_imports_to_specifier.get(&i.to_id()) {
1278 self.data.full_star_imports.insert(module_path.clone());
1279 }
1280 }
1281 node.visit_children_with(self);
1282 }
1283
1284 fn visit_ident(&mut self, node: &Ident) {
1285 let id = node.to_id();
1286 if let Some((esm_reference_index, _)) = self.data.get_binding(&id) {
1287 let usage = self
1289 .program_decl_usage
1290 .import_usages
1291 .entry(esm_reference_index)
1292 .or_default();
1293 if let Some(top_level) = self.state.cur_top_level_decl_name() {
1294 usage.add_usage(top_level);
1295 } else {
1296 usage.make_side_effects();
1297 }
1298 } else {
1299 if !is_unresolved(node, self.unresolved_mark) {
1301 if let Some(top_level) = self.state.cur_top_level_decl_name() {
1302 if &id != top_level {
1303 self.program_decl_usage
1304 .decl_usages
1305 .entry(id)
1306 .or_default()
1307 .add_usage(top_level);
1308 }
1309 } else {
1310 self.program_decl_usage
1311 .decl_usages
1312 .entry(id)
1313 .or_default()
1314 .make_side_effects();
1315 }
1316 }
1317 }
1318 }
1319
1320 fn visit_fn_expr(&mut self, node: &FnExpr) {
1321 if let Some(ident) = &node.ident {
1322 self.register_assignment_scope(ident.to_id());
1323 }
1324 node.visit_children_with(self);
1325 }
1326
1327 fn visit_fn_decl(&mut self, node: &FnDecl) {
1328 self.enter_top_level_decl(&node.ident, |this| {
1329 node.visit_children_with(this);
1330 });
1331 }
1332
1333 fn visit_decl(&mut self, node: &Decl) {
1334 match node {
1335 Decl::Class(c) => {
1336 self.register_assignment_scope(c.ident.to_id());
1337 }
1338 Decl::Fn(f) => {
1339 self.register_assignment_scope(f.ident.to_id());
1340 }
1341 Decl::Using(v) => {
1342 let ids: Vec<Id> = find_pat_ids(&v.decls);
1343 for id in ids {
1344 self.register_assignment_scope(id);
1345 }
1346 }
1347 Decl::Var(v) => {
1348 let ids: Vec<Id> = find_pat_ids(&v.decls);
1349 for id in ids {
1350 self.register_assignment_scope(id);
1351 }
1352 }
1353 Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {}
1354 }
1355 node.visit_children_with(self);
1356 }
1357
1358 fn visit_update_expr(&mut self, node: &UpdateExpr) {
1359 if let Some(key) = node.arg.as_ident() {
1360 self.register_assignment_scope(key.to_id());
1362 }
1363 node.visit_children_with(self);
1364 }
1365}
1366
1367fn parse_directives(
1370 comments: &dyn Comments,
1371 value: Option<&ExprOrSpread>,
1372) -> Option<ImportAttributes> {
1373 let value = value?;
1374 let leading_comments = comments.get_leading(value.span_lo())?;
1375
1376 let mut ignore = None;
1377 let mut optional = None;
1378 let mut export_names = None;
1379 let mut chunking_type = None;
1380
1381 for comment in leading_comments.iter() {
1383 if let Some((directive, val)) = comment.text.trim().split_once(':') {
1384 let val = val.trim();
1385 match directive.trim() {
1386 "webpackIgnore" | "turbopackIgnore" => match val {
1387 "true" => ignore = Some(true),
1388 "false" => ignore = Some(false),
1389 _ => {}
1390 },
1391 "turbopackOptional" => match val {
1392 "true" => optional = Some(true),
1393 "false" => optional = Some(false),
1394 _ => {}
1395 },
1396 "webpackExports" | "turbopackExports" => {
1397 export_names = Some(parse_export_names(val));
1398 }
1399 "turbopackChunkingType" => {
1400 chunking_type = parse_chunking_type_annotation(value.span(), val);
1401 }
1402 _ => {} }
1404 }
1405 }
1406
1407 if ignore.is_some() || optional.is_some() || export_names.is_some() || chunking_type.is_some() {
1409 Some(ImportAttributes {
1410 ignore: ignore.unwrap_or(false),
1411 optional: optional.unwrap_or(false),
1412 export_names,
1413 chunking_type,
1414 })
1415 } else {
1416 None
1417 }
1418}
1419
1420fn parse_export_names(val: &str) -> SmallVec<[RcStr; 1]> {
1426 let val = val.trim();
1427
1428 if let Ok(names) = serde_json::from_str::<Vec<String>>(val) {
1430 return names.into_iter().map(|s| s.into()).collect();
1431 }
1432
1433 if let Ok(name) = serde_json::from_str::<String>(val) {
1435 return SmallVec::from_buf([name.into()]);
1436 }
1437
1438 if !val.is_empty() {
1440 return SmallVec::from_buf([val.into()]);
1441 }
1442
1443 SmallVec::new()
1444}
1445
1446fn parse_with(with: Option<&ObjectLit>) -> Option<ImportedSymbol> {
1447 find_turbopack_part_id_in_asserts(with?).map(|v| match v {
1448 PartId::Internal(index, true) => ImportedSymbol::PartEvaluation(index),
1449 PartId::Internal(index, false) => ImportedSymbol::Part(index),
1450 PartId::ModuleEvaluation => ImportedSymbol::ModuleEvaluation,
1451 PartId::Export(e) => ImportedSymbol::Symbol(e.as_str().into()),
1452 PartId::Exports => ImportedSymbol::Exports,
1453 })
1454}
1455
1456fn get_import_symbol_from_import(specifier: &ImportSpecifier) -> ImportedSymbol {
1457 match specifier {
1458 ImportSpecifier::Named(ImportNamedSpecifier {
1459 local, imported, ..
1460 }) => ImportedSymbol::Symbol(match imported {
1461 Some(imported) => imported.atom().into_owned(),
1462 _ => local.sym.clone(),
1463 }),
1464 ImportSpecifier::Default(..) => ImportedSymbol::Symbol(atom!("default")),
1465 ImportSpecifier::Namespace(..) => ImportedSymbol::Exports,
1466 }
1467}
1468
1469fn get_import_symbol_from_export(specifier: &ExportSpecifier) -> ImportedSymbol {
1470 match specifier {
1471 ExportSpecifier::Named(ExportNamedSpecifier { orig, .. }) => {
1472 ImportedSymbol::Symbol(orig.atom().into_owned())
1473 }
1474 ExportSpecifier::Default(..) => ImportedSymbol::Symbol(atom!("default")),
1475 ExportSpecifier::Namespace(..) => ImportedSymbol::Exports,
1476 }
1477}
1478
1479#[cfg(test)]
1480mod tests {
1481 use swc_core::{atoms::Atom, common::DUMMY_SP};
1482
1483 use super::*;
1484
1485 fn str_lit(s: &str) -> Box<Expr> {
1487 Box::new(Expr::Lit(Lit::Str(Str {
1488 span: DUMMY_SP,
1489 value: Atom::from(s).into(),
1490 raw: None,
1491 })))
1492 }
1493
1494 fn ident_key(s: &str) -> PropName {
1496 PropName::Ident(IdentName {
1497 span: DUMMY_SP,
1498 sym: Atom::from(s),
1499 })
1500 }
1501
1502 fn kv_prop(key: PropName, value: Box<Expr>) -> PropOrSpread {
1504 PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { key, value })))
1505 }
1506
1507 #[test]
1508 fn test_parse_turbopack_loader_annotation() {
1509 let with = ObjectLit {
1511 span: DUMMY_SP,
1512 props: vec![kv_prop(ident_key("turbopackLoader"), str_lit("raw-loader"))],
1513 };
1514
1515 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1516 assert!(annotations.has_turbopack_loader());
1517
1518 let loader = annotations.turbopack_loader().unwrap();
1519 assert_eq!(loader.loader.as_str(), "raw-loader");
1520 assert!(loader.options.is_empty());
1521 }
1522
1523 #[test]
1524 fn test_parse_turbopack_loader_with_options() {
1525 let with = ObjectLit {
1527 span: DUMMY_SP,
1528 props: vec![
1529 kv_prop(ident_key("turbopackLoader"), str_lit("my-loader")),
1530 kv_prop(
1531 ident_key("turbopackLoaderOptions"),
1532 str_lit(r#"{"flag":true}"#),
1533 ),
1534 ],
1535 };
1536
1537 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1538 assert!(annotations.has_turbopack_loader());
1539
1540 let loader = annotations.turbopack_loader().unwrap();
1541 assert_eq!(loader.loader.as_str(), "my-loader");
1542 assert_eq!(loader.options["flag"], serde_json::Value::Bool(true));
1543 }
1544
1545 #[test]
1546 fn test_parse_without_turbopack_loader() {
1547 let with = ObjectLit {
1549 span: DUMMY_SP,
1550 props: vec![kv_prop(ident_key("type"), str_lit("json"))],
1551 };
1552
1553 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1554 assert!(!annotations.has_turbopack_loader());
1555 assert!(annotations.module_type().is_some());
1556 }
1557
1558 #[test]
1559 fn test_parse_empty_with() {
1560 let annotations = ImportAnnotations::parse(None);
1561 assert!(annotations.is_none());
1562 }
1563}