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