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