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