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