turbopack_ecmascript/references/esm/
base.rs

1use anyhow::{Result, anyhow, bail};
2use either::Either;
3use strsim::jaro;
4use swc_core::{
5    common::{BytePos, DUMMY_SP, Span, SyntaxContext, source_map::PURE_SP},
6    ecma::ast::{
7        ComputedPropName, Decl, Expr, ExprStmt, Ident, Lit, MemberExpr, MemberProp, Number,
8        SeqExpr, Stmt, Str,
9    },
10    quote,
11};
12use turbo_rcstr::{RcStr, rcstr};
13use turbo_tasks::{ResolvedVc, ValueToString, Vc};
14use turbo_tasks_fs::FileSystemPath;
15use turbopack_core::{
16    chunk::{
17        ChunkableModuleReference, ChunkingContext, ChunkingType, ChunkingTypeOption,
18        ModuleChunkItemIdExt,
19    },
20    issue::{
21        Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, OptionIssueSource,
22        OptionStyledString, StyledString,
23    },
24    module::{Module, ModuleSideEffects},
25    module_graph::binding_usage_info::ModuleExportUsageInfo,
26    reference::ModuleReference,
27    reference_type::{EcmaScriptModulesReferenceSubType, ImportWithType},
28    resolve::{
29        BindingUsage, ExportUsage, ExternalType, ImportUsage, ModulePart, ModuleResolveResult,
30        ModuleResolveResultItem, RequestKey,
31        origin::{ResolveOrigin, ResolveOriginExt},
32        parse::Request,
33    },
34};
35use turbopack_resolve::ecmascript::esm_resolve;
36
37use crate::{
38    EcmascriptModuleAsset, ScopeHoistingContext, TreeShakingMode,
39    analyzer::imports::ImportAnnotations,
40    chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
41    code_gen::{CodeGeneration, CodeGenerationHoistedStmt},
42    export::Liveness,
43    magic_identifier,
44    references::{
45        esm::{
46            EsmExport,
47            export::{all_known_export_names, is_export_missing},
48        },
49        util::throw_module_not_found_expr,
50    },
51    runtime_functions::{TURBOPACK_EXTERNAL_IMPORT, TURBOPACK_EXTERNAL_REQUIRE, TURBOPACK_IMPORT},
52    tree_shake::{TURBOPACK_PART_IMPORT_SOURCE, asset::EcmascriptModulePartAsset},
53    utils::module_id_to_lit,
54};
55
56#[turbo_tasks::value]
57pub enum ReferencedAsset {
58    Some(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>),
59    External(RcStr, ExternalType),
60    None,
61    Unresolvable,
62}
63
64#[derive(Debug)]
65pub enum ReferencedAssetIdent {
66    /// The given export (or namespace) is a local binding in the current scope hoisting group.
67    LocalBinding {
68        ident: RcStr,
69        ctxt: SyntaxContext,
70        liveness: Liveness,
71    },
72    /// The given export (or namespace) should be imported and will be assigned to a new variable.
73    Module {
74        namespace_ident: String,
75        ctxt: Option<SyntaxContext>,
76        export: Option<RcStr>,
77    },
78}
79
80impl ReferencedAssetIdent {
81    pub fn into_module_namespace_ident(self) -> Option<(String, Option<SyntaxContext>)> {
82        match self {
83            ReferencedAssetIdent::Module {
84                namespace_ident,
85                ctxt,
86                ..
87            } => Some((namespace_ident, ctxt)),
88            ReferencedAssetIdent::LocalBinding { .. } => None,
89        }
90    }
91
92    pub fn as_expr_individual(&self, span: Span) -> Either<Ident, MemberExpr> {
93        match self {
94            ReferencedAssetIdent::LocalBinding {
95                ident,
96                ctxt,
97                liveness: _,
98            } => Either::Left(Ident::new(ident.as_str().into(), span, *ctxt)),
99            ReferencedAssetIdent::Module {
100                namespace_ident,
101                ctxt,
102                export,
103            } => {
104                if let Some(export) = export {
105                    Either::Right(MemberExpr {
106                        span,
107                        obj: Box::new(Expr::Ident(Ident::new(
108                            namespace_ident.as_str().into(),
109                            DUMMY_SP,
110                            ctxt.unwrap_or_default(),
111                        ))),
112                        prop: MemberProp::Computed(ComputedPropName {
113                            span: DUMMY_SP,
114                            expr: Box::new(Expr::Lit(Lit::Str(Str {
115                                span: DUMMY_SP,
116                                value: export.as_str().into(),
117                                raw: None,
118                            }))),
119                        }),
120                    })
121                } else {
122                    Either::Left(Ident::new(
123                        namespace_ident.as_str().into(),
124                        span,
125                        ctxt.unwrap_or_default(),
126                    ))
127                }
128            }
129        }
130    }
131    pub fn as_expr(&self, span: Span, is_callee: bool) -> Expr {
132        match self.as_expr_individual(span) {
133            Either::Left(ident) => ident.into(),
134            Either::Right(member) => {
135                if is_callee {
136                    Expr::Seq(SeqExpr {
137                        exprs: vec![
138                            Box::new(Expr::Lit(Lit::Num(Number {
139                                span: DUMMY_SP,
140                                value: 0.0,
141                                raw: None,
142                            }))),
143                            Box::new(member.into()),
144                        ],
145                        span: DUMMY_SP,
146                    })
147                } else {
148                    member.into()
149                }
150            }
151        }
152    }
153}
154
155impl ReferencedAsset {
156    pub async fn get_ident(
157        &self,
158        chunking_context: Vc<Box<dyn ChunkingContext>>,
159        export: Option<RcStr>,
160        scope_hoisting_context: ScopeHoistingContext<'_>,
161    ) -> Result<Option<ReferencedAssetIdent>> {
162        self.get_ident_inner(chunking_context, export, scope_hoisting_context, None)
163            .await
164    }
165
166    async fn get_ident_inner(
167        &self,
168        chunking_context: Vc<Box<dyn ChunkingContext>>,
169        export: Option<RcStr>,
170        scope_hoisting_context: ScopeHoistingContext<'_>,
171        initial: Option<&ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
172    ) -> Result<Option<ReferencedAssetIdent>> {
173        Ok(match self {
174            ReferencedAsset::Some(asset) => {
175                if let Some(ctxt) = scope_hoisting_context.get_module_syntax_context(*asset)
176                    && let Some(export) = &export
177                    && let EcmascriptExports::EsmExports(exports) = *asset.get_exports().await?
178                {
179                    let exports = exports.expand_exports(ModuleExportUsageInfo::all()).await?;
180                    let esm_export = exports.exports.get(export);
181                    match esm_export {
182                        Some(EsmExport::LocalBinding(_name, liveness)) => {
183                            // A local binding in a module that is merged in the same group. Use the
184                            // export name as identifier, it will be replaced with the actual
185                            // variable name during AST merging.
186                            return Ok(Some(ReferencedAssetIdent::LocalBinding {
187                                ident: export.clone(),
188                                ctxt,
189                                liveness: *liveness,
190                            }));
191                        }
192                        Some(b @ EsmExport::ImportedBinding(esm_ref, _, _))
193                        | Some(b @ EsmExport::ImportedNamespace(esm_ref)) => {
194                            let imported = if let EsmExport::ImportedBinding(_, export, _) = b {
195                                Some(export.clone())
196                            } else {
197                                None
198                            };
199
200                            let referenced_asset =
201                                ReferencedAsset::from_resolve_result(esm_ref.resolve_reference())
202                                    .await?;
203
204                            if let Some(&initial) = initial
205                                && *referenced_asset == ReferencedAsset::Some(initial)
206                            {
207                                // `initial` reexports from `asset` reexports from
208                                // `referenced_asset` (which is `initial`)
209                                CircularReExport {
210                                    export: export.clone(),
211                                    import: imported.clone(),
212                                    module: *asset,
213                                    module_cycle: initial,
214                                }
215                                .resolved_cell()
216                                .emit();
217                                return Ok(None);
218                            }
219
220                            // If the target module is still in the same group, we can
221                            // refer it locally, otherwise it will be imported
222                            return Ok(
223                                match Box::pin(referenced_asset.get_ident_inner(
224                                    chunking_context,
225                                    imported,
226                                    scope_hoisting_context,
227                                    Some(asset),
228                                ))
229                                .await?
230                                {
231                                    Some(ReferencedAssetIdent::Module {
232                                        namespace_ident,
233                                        // Overwrite the context. This import isn't
234                                        // inserted in the module that uses the import,
235                                        // but in the module containing the reexport
236                                        ctxt: None,
237                                        export,
238                                    }) => Some(ReferencedAssetIdent::Module {
239                                        namespace_ident,
240                                        ctxt: Some(ctxt),
241                                        export,
242                                    }),
243                                    ident => ident,
244                                },
245                            );
246                        }
247                        Some(EsmExport::Error) | None => {
248                            // Export not found, either there was already an error, or
249                            // this is some dynamic (CJS) (re)export situation.
250                        }
251                    }
252                }
253
254                Some(ReferencedAssetIdent::Module {
255                    namespace_ident: Self::get_ident_from_placeable(asset, chunking_context)
256                        .await?,
257                    ctxt: None,
258                    export,
259                })
260            }
261            ReferencedAsset::External(request, ty) => Some(ReferencedAssetIdent::Module {
262                namespace_ident: magic_identifier::mangle(&format!("{ty} external {request}")),
263                ctxt: None,
264                export,
265            }),
266            ReferencedAsset::None | ReferencedAsset::Unresolvable => None,
267        })
268    }
269
270    pub(crate) async fn get_ident_from_placeable(
271        asset: &Vc<Box<dyn EcmascriptChunkPlaceable>>,
272        chunking_context: Vc<Box<dyn ChunkingContext>>,
273    ) -> Result<String> {
274        let id = asset.chunk_item_id(chunking_context).await?;
275        // There are a number of places in `next` that match on this prefix.
276        // See `packages/next/src/shared/lib/magic-identifier.ts`
277        Ok(magic_identifier::mangle(&format!("imported module {id}")))
278    }
279}
280
281#[turbo_tasks::value_impl]
282impl ReferencedAsset {
283    #[turbo_tasks::function]
284    pub async fn from_resolve_result(resolve_result: Vc<ModuleResolveResult>) -> Result<Vc<Self>> {
285        // TODO handle multiple keyed results
286        let result = resolve_result.await?;
287        if result.is_unresolvable_ref() {
288            return Ok(ReferencedAsset::Unresolvable.cell());
289        }
290        for (_, result) in result.primary.iter() {
291            match result {
292                ModuleResolveResultItem::External {
293                    name: request, ty, ..
294                } => {
295                    return Ok(ReferencedAsset::External(request.clone(), *ty).cell());
296                }
297                &ModuleResolveResultItem::Module(module) => {
298                    if let Some(placeable) =
299                        ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(module)
300                    {
301                        return Ok(ReferencedAsset::Some(placeable).cell());
302                    }
303                }
304                // TODO ignore should probably be handled differently
305                _ => {}
306            }
307        }
308        Ok(ReferencedAsset::None.cell())
309    }
310}
311
312#[turbo_tasks::value(transparent)]
313pub struct EsmAssetReferences(Vec<ResolvedVc<EsmAssetReference>>);
314
315#[turbo_tasks::value_impl]
316impl EsmAssetReferences {
317    #[turbo_tasks::function]
318    pub fn empty() -> Vc<Self> {
319        Vc::cell(Vec::new())
320    }
321}
322
323#[turbo_tasks::value(shared)]
324#[derive(Hash, Debug)]
325pub struct EsmAssetReference {
326    pub module: ResolvedVc<EcmascriptModuleAsset>,
327    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
328    // Request is a string to avoid eagerly parsing into a `Request` VC
329    pub request: RcStr,
330    pub annotations: ImportAnnotations,
331    pub issue_source: IssueSource,
332    pub export_name: Option<ModulePart>,
333    pub import_usage: ImportUsage,
334    pub import_externals: bool,
335    pub tree_shaking_mode: Option<TreeShakingMode>,
336    pub is_pure_import: bool,
337}
338
339impl EsmAssetReference {
340    fn get_origin(&self) -> Vc<Box<dyn ResolveOrigin>> {
341        if let Some(transition) = self.annotations.transition() {
342            self.origin.with_transition(transition.into())
343        } else {
344            *self.origin
345        }
346    }
347}
348
349impl EsmAssetReference {
350    pub fn new(
351        module: ResolvedVc<EcmascriptModuleAsset>,
352        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
353        request: RcStr,
354        issue_source: IssueSource,
355        annotations: ImportAnnotations,
356        export_name: Option<ModulePart>,
357        import_usage: ImportUsage,
358        import_externals: bool,
359        tree_shaking_mode: Option<TreeShakingMode>,
360    ) -> Self {
361        EsmAssetReference {
362            module,
363            origin,
364            request,
365            issue_source,
366            annotations,
367            export_name,
368            import_usage,
369            import_externals,
370            tree_shaking_mode,
371            is_pure_import: false,
372        }
373    }
374
375    pub fn new_pure(
376        module: ResolvedVc<EcmascriptModuleAsset>,
377        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
378        request: RcStr,
379        issue_source: IssueSource,
380        annotations: ImportAnnotations,
381        export_name: Option<ModulePart>,
382        import_usage: ImportUsage,
383        import_externals: bool,
384        tree_shaking_mode: Option<TreeShakingMode>,
385    ) -> Self {
386        EsmAssetReference {
387            module,
388            origin,
389            request,
390            issue_source,
391            annotations,
392            export_name,
393            import_usage,
394            import_externals,
395            tree_shaking_mode,
396            is_pure_import: true,
397        }
398    }
399}
400
401#[turbo_tasks::value_impl]
402impl EsmAssetReference {
403    #[turbo_tasks::function]
404    pub(crate) fn get_referenced_asset(self: Vc<Self>) -> Vc<ReferencedAsset> {
405        ReferencedAsset::from_resolve_result(self.resolve_reference())
406    }
407}
408
409#[turbo_tasks::value_impl]
410impl ModuleReference for EsmAssetReference {
411    #[turbo_tasks::function]
412    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
413        let ty = if self.annotations.module_type().is_some_and(|v| v == "json") {
414            EcmaScriptModulesReferenceSubType::ImportWithType(ImportWithType::Json)
415        } else if self.annotations.module_type().is_some_and(|v| v == "bytes") {
416            EcmaScriptModulesReferenceSubType::ImportWithType(ImportWithType::Bytes)
417        } else if let Some(part) = &self.export_name {
418            EcmaScriptModulesReferenceSubType::ImportPart(part.clone())
419        } else {
420            EcmaScriptModulesReferenceSubType::Import
421        };
422
423        let request = Request::parse(self.request.clone().into());
424
425        if let Some(TreeShakingMode::ModuleFragments) = self.tree_shaking_mode {
426            if let Some(ModulePart::Evaluation) = &self.export_name
427                && *self.module.side_effects().await? == ModuleSideEffects::SideEffectFree
428            {
429                return Ok(ModuleResolveResult {
430                    primary: Box::new([(RequestKey::default(), ModuleResolveResultItem::Ignore)]),
431                    affecting_sources: Default::default(),
432                }
433                .cell());
434            }
435
436            if let Request::Module { module, .. } = &*request.await?
437                && module.is_match(TURBOPACK_PART_IMPORT_SOURCE)
438            {
439                if let Some(part) = &self.export_name {
440                    return Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
441                        EcmascriptModulePartAsset::select_part(*self.module, part.clone())
442                            .to_resolved()
443                            .await?,
444                    )));
445                }
446                bail!("export_name is required for part import")
447            }
448        }
449
450        let result = esm_resolve(
451            self.get_origin(),
452            request,
453            ty,
454            false,
455            Some(self.issue_source),
456        )
457        .await?;
458
459        if let Some(ModulePart::Export(export_name)) = &self.export_name {
460            for &module in result.primary_modules().await? {
461                if let Some(module) = ResolvedVc::try_downcast(module)
462                    && *is_export_missing(*module, export_name.clone()).await?
463                {
464                    InvalidExport {
465                        export: export_name.clone(),
466                        module,
467                        source: self.issue_source,
468                    }
469                    .resolved_cell()
470                    .emit();
471                }
472            }
473        }
474
475        Ok(result)
476    }
477}
478
479#[turbo_tasks::value_impl]
480impl ValueToString for EsmAssetReference {
481    #[turbo_tasks::function]
482    fn to_string(&self) -> Vc<RcStr> {
483        Vc::cell(format!("import {} with {}", self.request, self.annotations).into())
484    }
485}
486
487#[turbo_tasks::value_impl]
488impl ChunkableModuleReference for EsmAssetReference {
489    #[turbo_tasks::function]
490    fn chunking_type(&self) -> Result<Vc<ChunkingTypeOption>> {
491        Ok(Vc::cell(
492            if let Some(chunking_type) = self.annotations.chunking_type() {
493                if chunking_type == "parallel" {
494                    Some(ChunkingType::Parallel {
495                        inherit_async: true,
496                        hoisted: true,
497                    })
498                } else if chunking_type == "none" {
499                    None
500                } else {
501                    return Err(anyhow!(
502                        "unknown chunking_type: {}",
503                        chunking_type.to_string_lossy()
504                    ));
505                }
506            } else {
507                Some(ChunkingType::Parallel {
508                    inherit_async: true,
509                    hoisted: true,
510                })
511            },
512        ))
513    }
514
515    #[turbo_tasks::function]
516    fn binding_usage(&self) -> Vc<BindingUsage> {
517        BindingUsage {
518            import: self.import_usage.clone(),
519            export: match &self.export_name {
520                Some(ModulePart::Export(export_name)) => ExportUsage::Named(export_name.clone()),
521                Some(ModulePart::Evaluation) => ExportUsage::Evaluation,
522                _ => ExportUsage::All,
523            },
524        }
525        .cell()
526    }
527}
528
529impl EsmAssetReference {
530    pub async fn code_generation(
531        self: Vc<Self>,
532        chunking_context: Vc<Box<dyn ChunkingContext>>,
533        scope_hoisting_context: ScopeHoistingContext<'_>,
534    ) -> Result<CodeGeneration> {
535        let this = &*self.await?;
536
537        if *chunking_context
538            .is_reference_unused(Vc::upcast(self))
539            .await?
540        {
541            return Ok(CodeGeneration::empty());
542        }
543
544        // only chunked references can be imported
545        if this.annotations.chunking_type().is_none_or(|v| v != "none") {
546            let import_externals = this.import_externals;
547            let referenced_asset = self.get_referenced_asset().await?;
548
549            match &*referenced_asset {
550                ReferencedAsset::Unresolvable => {
551                    // Insert code that throws immediately at time of import if a request is
552                    // unresolvable
553                    let request = &this.request;
554                    let stmt = Stmt::Expr(ExprStmt {
555                        expr: Box::new(throw_module_not_found_expr(request)),
556                        span: DUMMY_SP,
557                    });
558                    return Ok(CodeGeneration::hoisted_stmt(
559                        format!("throw {request}").into(),
560                        stmt,
561                    ));
562                }
563                ReferencedAsset::None => {}
564                _ => {
565                    let mut result = vec![];
566
567                    let merged_index = if let ReferencedAsset::Some(asset) = &*referenced_asset {
568                        scope_hoisting_context.get_module_index(*asset)
569                    } else {
570                        None
571                    };
572
573                    if let Some(merged_index) = merged_index {
574                        // Insert a placeholder to inline the merged module at the right place
575                        // relative to the other references (so to keep reference order).
576                        result.push(CodeGenerationHoistedStmt::new(
577                            format!("hoisted {merged_index}").into(),
578                            quote!(
579                                "__turbopack_merged_esm__($id);" as Stmt,
580                                id: Expr = Lit::Num(merged_index.into()).into(),
581                            ),
582                        ));
583                    }
584
585                    if merged_index.is_some()
586                        && matches!(this.export_name, Some(ModulePart::Evaluation))
587                    {
588                        // No need to import, the module was already executed and is available in
589                        // the same scope hoisting group (unless it's a
590                        // namespace import)
591                    } else {
592                        let ident = referenced_asset
593                            .get_ident(
594                                chunking_context,
595                                this.export_name.as_ref().and_then(|e| match e {
596                                    ModulePart::Export(export_name) => Some(export_name.clone()),
597                                    _ => None,
598                                }),
599                                scope_hoisting_context,
600                            )
601                            .await?;
602                        match ident {
603                            Some(ReferencedAssetIdent::LocalBinding { .. }) => {
604                                // no need to import
605                            }
606                            Some(ident @ ReferencedAssetIdent::Module { .. }) => {
607                                let span = this
608                                    .issue_source
609                                    .to_swc_offsets()
610                                    .await?
611                                    .map_or(DUMMY_SP, |(start, end)| {
612                                        Span::new(BytePos(start), BytePos(end))
613                                    });
614                                match &*referenced_asset {
615                                    ReferencedAsset::Unresolvable => {
616                                        unreachable!();
617                                    }
618                                    ReferencedAsset::Some(asset) => {
619                                        let id = asset.chunk_item_id(chunking_context).await?;
620                                        let (sym, ctxt) =
621                                            ident.into_module_namespace_ident().unwrap();
622                                        let name = Ident::new(
623                                            sym.into(),
624                                            DUMMY_SP,
625                                            ctxt.unwrap_or_default(),
626                                        );
627                                        let mut call_expr = quote!(
628                                            "$turbopack_import($id)" as Expr,
629                                            turbopack_import: Expr = TURBOPACK_IMPORT.into(),
630                                            id: Expr = module_id_to_lit(&id),
631                                        );
632                                        if this.is_pure_import {
633                                            call_expr.set_span(PURE_SP);
634                                        }
635                                        result.push(CodeGenerationHoistedStmt::new(
636                                            id.to_string().into(),
637                                            var_decl_with_span(
638                                                quote!(
639                                                    "var $name = $call;" as Stmt,
640                                                    name = name,
641                                                    call: Expr = call_expr
642                                                ),
643                                                span,
644                                            ),
645                                        ));
646                                    }
647                                    ReferencedAsset::External(
648                                        request,
649                                        ExternalType::EcmaScriptModule,
650                                    ) => {
651                                        if !*chunking_context
652                                            .environment()
653                                            .supports_esm_externals()
654                                            .await?
655                                        {
656                                            bail!(
657                                                "the chunking context ({}) does not support \
658                                                 external modules (esm request: {})",
659                                                chunking_context.name().await?,
660                                                request
661                                            );
662                                        }
663                                        let (sym, ctxt) =
664                                            ident.into_module_namespace_ident().unwrap();
665                                        let name = Ident::new(
666                                            sym.into(),
667                                            DUMMY_SP,
668                                            ctxt.unwrap_or_default(),
669                                        );
670                                        let mut call_expr = if import_externals {
671                                            quote!(
672                                                "$turbopack_external_import($id)" as Expr,
673                                                turbopack_external_import: Expr = TURBOPACK_EXTERNAL_IMPORT.into(),
674                                                id: Expr = Expr::Lit(request.clone().to_string().into())
675                                            )
676                                        } else {
677                                            quote!(
678                                                "$turbopack_external_require($id, () => require($id), true)" as Expr,
679                                                turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
680                                                id: Expr = Expr::Lit(request.clone().to_string().into())
681                                            )
682                                        };
683                                        if this.is_pure_import {
684                                            call_expr.set_span(PURE_SP);
685                                        }
686                                        result.push(CodeGenerationHoistedStmt::new(
687                                            name.sym.as_str().into(),
688                                            var_decl_with_span(
689                                                quote!(
690                                                    "var $name = $call;" as Stmt,
691                                                    name = name,
692                                                    call: Expr = call_expr,
693                                                ),
694                                                span,
695                                            ),
696                                        ));
697                                    }
698                                    ReferencedAsset::External(
699                                        request,
700                                        ExternalType::CommonJs | ExternalType::Url,
701                                    ) => {
702                                        if !*chunking_context
703                                            .environment()
704                                            .supports_commonjs_externals()
705                                            .await?
706                                        {
707                                            bail!(
708                                                "the chunking context ({}) does not support \
709                                                 external modules (request: {})",
710                                                chunking_context.name().await?,
711                                                request
712                                            );
713                                        }
714                                        let (sym, ctxt) =
715                                            ident.into_module_namespace_ident().unwrap();
716                                        let name = Ident::new(
717                                            sym.into(),
718                                            DUMMY_SP,
719                                            ctxt.unwrap_or_default(),
720                                        );
721                                        let mut call_expr = quote!(
722                                            "$turbopack_external_require($id, () => require($id), true)" as Expr,
723                                            turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
724                                            id: Expr = Expr::Lit(request.clone().to_string().into())
725                                        );
726                                        if this.is_pure_import {
727                                            call_expr.set_span(PURE_SP);
728                                        }
729                                        result.push(CodeGenerationHoistedStmt::new(
730                                            name.sym.as_str().into(),
731                                            var_decl_with_span(
732                                                quote!(
733                                                    "var $name = $call;" as Stmt,
734                                                    name = name,
735                                                    call: Expr = call_expr,
736                                                ),
737                                                span,
738                                            ),
739                                        ));
740                                    }
741                                    // fallback in case we introduce a new `ExternalType`
742                                    #[allow(unreachable_patterns)]
743                                    ReferencedAsset::External(request, ty) => {
744                                        bail!(
745                                            "Unsupported external type {:?} for ESM reference \
746                                             with request: {:?}",
747                                            ty,
748                                            request
749                                        )
750                                    }
751                                    ReferencedAsset::None => {}
752                                };
753                            }
754                            None => {
755                                // Nothing to import.
756                            }
757                        }
758                    }
759                    return Ok(CodeGeneration::hoisted_stmts(result));
760                }
761            }
762        };
763
764        Ok(CodeGeneration::empty())
765    }
766}
767
768fn var_decl_with_span(mut decl: Stmt, span: Span) -> Stmt {
769    match &mut decl {
770        Stmt::Decl(Decl::Var(decl)) => decl.span = span,
771        _ => panic!("Expected Stmt::Decl::Var"),
772    };
773    decl
774}
775
776#[turbo_tasks::value(shared)]
777pub struct InvalidExport {
778    export: RcStr,
779    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
780    source: IssueSource,
781}
782
783#[turbo_tasks::value_impl]
784impl Issue for InvalidExport {
785    fn severity(&self) -> IssueSeverity {
786        IssueSeverity::Error
787    }
788
789    #[turbo_tasks::function]
790    fn title(&self) -> Result<Vc<StyledString>> {
791        Ok(StyledString::Line(vec![
792            StyledString::Text(rcstr!("Export ")),
793            StyledString::Code(self.export.clone()),
794            StyledString::Text(rcstr!(" doesn't exist in target module")),
795        ])
796        .cell())
797    }
798
799    #[turbo_tasks::function]
800    fn stage(&self) -> Vc<IssueStage> {
801        IssueStage::Bindings.cell()
802    }
803
804    #[turbo_tasks::function]
805    fn file_path(&self) -> Vc<FileSystemPath> {
806        self.source.file_path()
807    }
808
809    #[turbo_tasks::function]
810    async fn description(&self) -> Result<Vc<OptionStyledString>> {
811        let export_names = all_known_export_names(*self.module).await?;
812        let did_you_mean = export_names
813            .iter()
814            .map(|s| (s, jaro(self.export.as_str(), s.as_str())))
815            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
816            .map(|(s, _)| s);
817        Ok(Vc::cell(Some(
818            StyledString::Stack(vec![
819                StyledString::Line(vec![
820                    StyledString::Text(rcstr!("The export ")),
821                    StyledString::Code(self.export.clone()),
822                    StyledString::Text(rcstr!(" was not found in module ")),
823                    StyledString::Strong(self.module.ident().to_string().owned().await?),
824                    StyledString::Text(rcstr!(".")),
825                ]),
826                if let Some(did_you_mean) = did_you_mean {
827                    StyledString::Line(vec![
828                        StyledString::Text(rcstr!("Did you mean to import ")),
829                        StyledString::Code(did_you_mean.clone()),
830                        StyledString::Text(rcstr!("?")),
831                    ])
832                } else {
833                    StyledString::Strong(rcstr!("The module has no exports at all."))
834                },
835                StyledString::Text(
836                    "All exports of the module are statically known (It doesn't have dynamic \
837                     exports). So it's known statically that the requested export doesn't exist."
838                        .into(),
839                ),
840            ])
841            .resolved_cell(),
842        )))
843    }
844
845    #[turbo_tasks::function]
846    async fn detail(&self) -> Result<Vc<OptionStyledString>> {
847        let export_names = all_known_export_names(*self.module).await?;
848        Ok(Vc::cell(Some(
849            StyledString::Line(vec![
850                StyledString::Text(rcstr!("These are the exports of the module:\n")),
851                StyledString::Code(
852                    export_names
853                        .iter()
854                        .map(|s| s.as_str())
855                        .intersperse(", ")
856                        .collect::<String>()
857                        .into(),
858                ),
859            ])
860            .resolved_cell(),
861        )))
862    }
863
864    #[turbo_tasks::function]
865    fn source(&self) -> Vc<OptionIssueSource> {
866        Vc::cell(Some(self.source))
867    }
868}
869
870#[turbo_tasks::value(shared)]
871pub struct CircularReExport {
872    export: RcStr,
873    import: Option<RcStr>,
874    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
875    module_cycle: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
876}
877
878#[turbo_tasks::value_impl]
879impl Issue for CircularReExport {
880    fn severity(&self) -> IssueSeverity {
881        IssueSeverity::Error
882    }
883
884    #[turbo_tasks::function]
885    async fn title(&self) -> Result<Vc<StyledString>> {
886        Ok(StyledString::Line(vec![
887            StyledString::Text(rcstr!("Export ")),
888            StyledString::Code(self.export.clone()),
889            StyledString::Text(rcstr!(" is a circular re-export")),
890        ])
891        .cell())
892    }
893
894    #[turbo_tasks::function]
895    fn stage(&self) -> Vc<IssueStage> {
896        IssueStage::Bindings.cell()
897    }
898
899    #[turbo_tasks::function]
900    fn file_path(&self) -> Vc<FileSystemPath> {
901        self.module.ident().path()
902    }
903
904    #[turbo_tasks::function]
905    async fn description(&self) -> Result<Vc<OptionStyledString>> {
906        Ok(Vc::cell(Some(
907            StyledString::Stack(vec![
908                StyledString::Line(vec![StyledString::Text(rcstr!("The export"))]),
909                StyledString::Line(vec![
910                    StyledString::Code(self.export.clone()),
911                    StyledString::Text(rcstr!(" of module ")),
912                    StyledString::Strong(self.module.ident().to_string().owned().await?),
913                ]),
914                StyledString::Line(vec![StyledString::Text(rcstr!(
915                    "is a re-export of the export"
916                ))]),
917                StyledString::Line(vec![
918                    StyledString::Code(self.import.clone().unwrap_or_else(|| rcstr!("*"))),
919                    StyledString::Text(rcstr!(" of module ")),
920                    StyledString::Strong(self.module_cycle.ident().to_string().owned().await?),
921                    StyledString::Text(rcstr!(".")),
922                ]),
923            ])
924            .resolved_cell(),
925        )))
926    }
927
928    #[turbo_tasks::function]
929    fn source(&self) -> Vc<OptionIssueSource> {
930        // TODO(PACK-4879): This should point at the buggy export by querying for the source
931        // location
932        Vc::cell(None)
933    }
934}