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}
337
338impl EsmAssetReference {
339 fn get_origin(&self) -> Vc<Box<dyn ResolveOrigin>> {
340 if let Some(transition) = self.annotations.as_ref().and_then(|a| a.transition()) {
341 self.origin.with_transition(transition.into())
342 } else {
343 *self.origin
344 }
345 }
346}
347
348impl EsmAssetReference {
349 pub fn new(
350 module: ResolvedVc<EcmascriptModuleAsset>,
351 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
352 request: RcStr,
353 issue_source: IssueSource,
354 annotations: Option<ImportAnnotations>,
355 export_name: Option<ModulePart>,
356 import_usage: ImportUsage,
357 import_externals: bool,
358 tree_shaking_mode: Option<TreeShakingMode>,
359 ) -> Self {
360 EsmAssetReference {
361 module,
362 origin,
363 request,
364 issue_source,
365 annotations,
366 export_name,
367 import_usage,
368 import_externals,
369 tree_shaking_mode,
370 is_pure_import: false,
371 }
372 }
373
374 pub fn new_pure(
375 module: ResolvedVc<EcmascriptModuleAsset>,
376 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
377 request: RcStr,
378 issue_source: IssueSource,
379 annotations: Option<ImportAnnotations>,
380 export_name: Option<ModulePart>,
381 import_usage: ImportUsage,
382 import_externals: bool,
383 tree_shaking_mode: Option<TreeShakingMode>,
384 ) -> Self {
385 EsmAssetReference {
386 module,
387 origin,
388 request,
389 issue_source,
390 annotations,
391 export_name,
392 import_usage,
393 import_externals,
394 tree_shaking_mode,
395 is_pure_import: true,
396 }
397 }
398 pub(crate) fn get_referenced_asset(self: Vc<Self>) -> Vc<ReferencedAsset> {
399 ReferencedAsset::from_resolve_result(self.resolve_reference())
400 }
401}
402
403#[turbo_tasks::value_impl]
404impl ModuleReference for EsmAssetReference {
405 #[turbo_tasks::function]
406 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
407 let ty = if let Some(loader) = self.annotations.as_ref().and_then(|a| a.turbopack_loader())
408 {
409 let origin = self.get_origin();
411 let origin_path = origin.origin_path().await?;
412 let loader_request = Request::parse(loader.loader.clone().into());
413 let resolved = resolve(
414 origin_path.parent(),
415 ReferenceType::Loader,
416 loader_request,
417 origin.resolve_options(),
418 );
419 let loader_fs_path = if let Some(source) = *resolved.first_source().await? {
420 (*source.ident().path().await?).clone()
421 } else {
422 bail!("Unable to resolve turbopackLoader '{}'", loader.loader);
423 };
424
425 EcmaScriptModulesReferenceSubType::ImportWithTurbopackUse {
426 loader: ResolvedWebpackLoaderItem {
427 loader: loader_fs_path,
428 options: loader.options.clone(),
429 },
430 rename_as: self
431 .annotations
432 .as_ref()
433 .and_then(|a| a.turbopack_rename_as())
434 .cloned(),
435 module_type: self
436 .annotations
437 .as_ref()
438 .and_then(|a| a.turbopack_module_type())
439 .cloned(),
440 }
441 } else if let Some(module_type) = self.annotations.as_ref().and_then(|a| a.module_type()) {
442 EcmaScriptModulesReferenceSubType::ImportWithType(RcStr::from(
443 &*module_type.to_string_lossy(),
444 ))
445 } else if let Some(part) = &self.export_name {
446 EcmaScriptModulesReferenceSubType::ImportPart(part.clone())
447 } else {
448 EcmaScriptModulesReferenceSubType::Import
449 };
450
451 let request = Request::parse(self.request.clone().into());
452
453 if let Some(TreeShakingMode::ModuleFragments) = self.tree_shaking_mode {
454 if let Some(ModulePart::Evaluation) = &self.export_name
455 && *self.module.side_effects().await? == ModuleSideEffects::SideEffectFree
456 {
457 return Ok(ModuleResolveResult {
458 primary: Box::new([(RequestKey::default(), ModuleResolveResultItem::Ignore)]),
459 affecting_sources: Default::default(),
460 }
461 .cell());
462 }
463
464 if let Request::Module { module, .. } = &*request.await?
465 && module.is_match(TURBOPACK_PART_IMPORT_SOURCE)
466 {
467 if let Some(part) = &self.export_name {
468 return Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
469 EcmascriptModulePartAsset::select_part(*self.module, part.clone())
470 .to_resolved()
471 .await?,
472 )));
473 }
474 bail!("export_name is required for part import")
475 }
476 }
477
478 let result = esm_resolve(
479 self.get_origin(),
480 request,
481 ty,
482 ResolveErrorMode::Error,
483 Some(self.issue_source),
484 )
485 .await?;
486
487 if let Some(ModulePart::Export(export_name)) = &self.export_name {
488 for &module in result.primary_modules().await? {
489 if let Some(module) = ResolvedVc::try_downcast(module)
490 && *is_export_missing(*module, export_name.clone()).await?
491 {
492 InvalidExport {
493 export: export_name.clone(),
494 module,
495 source: self.issue_source,
496 }
497 .resolved_cell()
498 .emit();
499 }
500 }
501 }
502
503 Ok(result)
504 }
505
506 fn chunking_type(&self) -> Option<ChunkingType> {
507 self.annotations
508 .as_ref()
509 .and_then(|a| a.chunking_type())
510 .map_or_else(
511 || {
512 Some(ChunkingType::Parallel {
513 inherit_async: true,
514 hoisted: true,
515 })
516 },
517 |c| c.as_chunking_type(true, true),
518 )
519 }
520
521 fn binding_usage(&self) -> BindingUsage {
522 BindingUsage {
523 import: self.import_usage.clone(),
524 export: match &self.export_name {
525 Some(ModulePart::Export(export_name)) => ExportUsage::Named(export_name.clone()),
526 Some(ModulePart::Evaluation) => ExportUsage::Evaluation,
527 _ => ExportUsage::All,
528 },
529 }
530 }
531}
532
533impl EsmAssetReference {
534 pub async fn code_generation(
535 self: ResolvedVc<Self>,
536 chunking_context: Vc<Box<dyn ChunkingContext>>,
537 scope_hoisting_context: ScopeHoistingContext<'_>,
538 ) -> Result<CodeGeneration> {
539 let this = &*self.await?;
540
541 if chunking_context
542 .unused_references()
543 .contains_key(&ResolvedVc::upcast(self))
544 .await?
545 {
546 return Ok(CodeGeneration::empty());
547 }
548
549 if this
551 .annotations
552 .as_ref()
553 .and_then(|a| a.chunking_type())
554 .is_none_or(|v| v != SpecifiedChunkingType::None)
555 {
556 let import_externals = this.import_externals;
557 let referenced_asset = self.get_referenced_asset().await?;
558
559 match &*referenced_asset {
560 ReferencedAsset::Unresolvable => {
561 let request = &this.request;
564 let stmt = Stmt::Expr(ExprStmt {
565 expr: Box::new(throw_module_not_found_expr(request)),
566 span: DUMMY_SP,
567 });
568 return Ok(CodeGeneration::hoisted_stmt(
569 format!("throw {request}").into(),
570 stmt,
571 ));
572 }
573 ReferencedAsset::None => {}
574 _ => {
575 let mut result = vec![];
576
577 let merged_index = if let ReferencedAsset::Some(asset) = &*referenced_asset {
578 scope_hoisting_context.get_module_index(*asset)
579 } else {
580 None
581 };
582
583 if let Some(merged_index) = merged_index {
584 result.push(CodeGenerationHoistedStmt::new(
587 format!("hoisted {merged_index}").into(),
588 quote!(
589 "__turbopack_merged_esm__($id);" as Stmt,
590 id: Expr = Lit::Num(merged_index.into()).into(),
591 ),
592 ));
593 }
594
595 if merged_index.is_some()
596 && matches!(this.export_name, Some(ModulePart::Evaluation))
597 {
598 } else {
602 let ident = referenced_asset
603 .get_ident(
604 chunking_context,
605 this.export_name.as_ref().and_then(|e| match e {
606 ModulePart::Export(export_name) => Some(export_name.clone()),
607 _ => None,
608 }),
609 scope_hoisting_context,
610 )
611 .await?;
612 match ident {
613 Some(ReferencedAssetIdent::LocalBinding { .. }) => {
614 }
616 Some(ident @ ReferencedAssetIdent::Module { .. }) => {
617 let span = this
618 .issue_source
619 .to_swc_offsets()
620 .await?
621 .map_or(DUMMY_SP, |(start, end)| {
622 Span::new(BytePos(start), BytePos(end))
623 });
624 match &*referenced_asset {
625 ReferencedAsset::Unresolvable => {
626 unreachable!();
627 }
628 ReferencedAsset::Some(asset) => {
629 let id = asset.chunk_item_id(chunking_context).await?;
630 let (sym, ctxt) =
631 ident.into_module_namespace_ident().unwrap();
632 let name = Ident::new(
633 sym.into(),
634 DUMMY_SP,
635 ctxt.unwrap_or_default(),
636 );
637 let mut call_expr = quote!(
638 "$turbopack_import($id)" as Expr,
639 turbopack_import: Expr = TURBOPACK_IMPORT.into(),
640 id: Expr = module_id_to_lit(&id),
641 );
642 if this.is_pure_import {
643 call_expr.set_span(PURE_SP);
644 }
645 result.push(CodeGenerationHoistedStmt::new(
646 id.to_string().into(),
647 var_decl_with_span(
648 quote!(
649 "var $name = $call;" as Stmt,
650 name = name,
651 call: Expr = call_expr
652 ),
653 span,
654 ),
655 ));
656 }
657 ReferencedAsset::External(
658 request,
659 ExternalType::EcmaScriptModule,
660 ) => {
661 if !*chunking_context
662 .environment()
663 .supports_esm_externals()
664 .await?
665 {
666 turbobail!(
667 "the chunking context ({}) does not support \
668 external modules (esm request: {request})",
669 chunking_context.name()
670 );
671 }
672 let (sym, ctxt) =
673 ident.into_module_namespace_ident().unwrap();
674 let name = Ident::new(
675 sym.into(),
676 DUMMY_SP,
677 ctxt.unwrap_or_default(),
678 );
679 let mut call_expr = if import_externals {
680 quote!(
681 "$turbopack_external_import($id)" as Expr,
682 turbopack_external_import: Expr = TURBOPACK_EXTERNAL_IMPORT.into(),
683 id: Expr = Expr::Lit(request.clone().to_string().into())
684 )
685 } else {
686 quote!(
687 "$turbopack_external_require($id, () => require($id), true)" as Expr,
688 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
689 id: Expr = Expr::Lit(request.clone().to_string().into())
690 )
691 };
692 if this.is_pure_import {
693 call_expr.set_span(PURE_SP);
694 }
695 result.push(CodeGenerationHoistedStmt::new(
696 name.sym.as_str().into(),
697 var_decl_with_span(
698 quote!(
699 "var $name = $call;" as Stmt,
700 name = name,
701 call: Expr = call_expr,
702 ),
703 span,
704 ),
705 ));
706 }
707 ReferencedAsset::External(
708 request,
709 ExternalType::CommonJs | ExternalType::Url,
710 ) => {
711 if !*chunking_context
712 .environment()
713 .supports_commonjs_externals()
714 .await?
715 {
716 turbobail!(
717 "the chunking context ({}) does not support \
718 external modules (request: {request})",
719 chunking_context.name()
720 );
721 }
722 let (sym, ctxt) =
723 ident.into_module_namespace_ident().unwrap();
724 let name = Ident::new(
725 sym.into(),
726 DUMMY_SP,
727 ctxt.unwrap_or_default(),
728 );
729 let mut call_expr = quote!(
730 "$turbopack_external_require($id, () => require($id), true)" as Expr,
731 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
732 id: Expr = Expr::Lit(request.clone().to_string().into())
733 );
734 if this.is_pure_import {
735 call_expr.set_span(PURE_SP);
736 }
737 result.push(CodeGenerationHoistedStmt::new(
738 name.sym.as_str().into(),
739 var_decl_with_span(
740 quote!(
741 "var $name = $call;" as Stmt,
742 name = name,
743 call: Expr = call_expr,
744 ),
745 span,
746 ),
747 ));
748 }
749 #[allow(unreachable_patterns)]
751 ReferencedAsset::External(request, ty) => {
752 bail!(
753 "Unsupported external type {:?} for ESM reference \
754 with request: {:?}",
755 ty,
756 request
757 )
758 }
759 ReferencedAsset::None => {}
760 };
761 }
762 None => {
763 }
765 }
766 }
767 return Ok(CodeGeneration::hoisted_stmts(result));
768 }
769 }
770 };
771
772 Ok(CodeGeneration::empty())
773 }
774}
775
776fn var_decl_with_span(mut decl: Stmt, span: Span) -> Stmt {
777 match &mut decl {
778 Stmt::Decl(Decl::Var(decl)) => decl.span = span,
779 _ => panic!("Expected Stmt::Decl::Var"),
780 };
781 decl
782}
783
784#[turbo_tasks::value(shared)]
785pub struct InvalidExport {
786 export: RcStr,
787 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
788 source: IssueSource,
789}
790
791#[async_trait]
792#[turbo_tasks::value_impl]
793impl Issue for InvalidExport {
794 fn severity(&self) -> IssueSeverity {
795 IssueSeverity::Error
796 }
797
798 async fn title(&self) -> Result<StyledString> {
799 Ok(StyledString::Line(vec![
800 StyledString::Text(rcstr!("Export ")),
801 StyledString::Code(self.export.clone()),
802 StyledString::Text(rcstr!(" doesn't exist in target module")),
803 ]))
804 }
805
806 fn stage(&self) -> IssueStage {
807 IssueStage::Bindings
808 }
809
810 async fn file_path(&self) -> Result<FileSystemPath> {
811 self.source.file_path().owned().await
812 }
813
814 async fn description(&self) -> Result<Option<StyledString>> {
815 let export_names = all_known_export_names(*self.module).await?;
816 let did_you_mean = export_names
817 .iter()
818 .map(|s| (s, jaro(self.export.as_str(), s.as_str())))
819 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
820 .map(|(s, _)| s);
821 Ok(Some(StyledString::Stack(vec![
822 StyledString::Line(vec![
823 StyledString::Text(rcstr!("The export ")),
824 StyledString::Code(self.export.clone()),
825 StyledString::Text(rcstr!(" was not found in module ")),
826 StyledString::Strong(self.module.ident().to_string().owned().await?),
827 StyledString::Text(rcstr!(".")),
828 ]),
829 if let Some(did_you_mean) = did_you_mean {
830 StyledString::Line(vec![
831 StyledString::Text(rcstr!("Did you mean to import ")),
832 StyledString::Code(did_you_mean.clone()),
833 StyledString::Text(rcstr!("?")),
834 ])
835 } else {
836 StyledString::Strong(rcstr!("The module has no exports at all."))
837 },
838 StyledString::Text(
839 "All exports of the module are statically known (It doesn't have dynamic \
840 exports). So it's known statically that the requested export doesn't exist."
841 .into(),
842 ),
843 ])))
844 }
845
846 async fn detail(&self) -> Result<Option<StyledString>> {
847 let export_names = all_known_export_names(*self.module).await?;
848 Ok(Some(StyledString::Line(vec![
849 StyledString::Text(rcstr!("These are the exports of the module:\n")),
850 StyledString::Code(
851 export_names
852 .iter()
853 .map(|s| s.as_str())
854 .intersperse(", ")
855 .collect::<String>()
856 .into(),
857 ),
858 ])))
859 }
860
861 fn source(&self) -> Option<IssueSource> {
862 Some(self.source)
863 }
864}
865
866#[turbo_tasks::value(shared)]
867pub struct CircularReExport {
868 export: RcStr,
869 import: Option<RcStr>,
870 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
871 module_cycle: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
872}
873
874#[async_trait]
875#[turbo_tasks::value_impl]
876impl Issue for CircularReExport {
877 fn severity(&self) -> IssueSeverity {
878 IssueSeverity::Error
879 }
880
881 async fn title(&self) -> Result<StyledString> {
882 Ok(StyledString::Line(vec![
883 StyledString::Text(rcstr!("Export ")),
884 StyledString::Code(self.export.clone()),
885 StyledString::Text(rcstr!(" is a circular re-export")),
886 ]))
887 }
888
889 fn stage(&self) -> IssueStage {
890 IssueStage::Bindings
891 }
892
893 async fn file_path(&self) -> Result<FileSystemPath> {
894 self.module.ident().path().owned().await
895 }
896
897 async fn description(&self) -> Result<Option<StyledString>> {
898 Ok(Some(StyledString::Stack(vec![
899 StyledString::Line(vec![StyledString::Text(rcstr!("The export"))]),
900 StyledString::Line(vec![
901 StyledString::Code(self.export.clone()),
902 StyledString::Text(rcstr!(" of module ")),
903 StyledString::Strong(self.module.ident().to_string().owned().await?),
904 ]),
905 StyledString::Line(vec![StyledString::Text(rcstr!(
906 "is a re-export of the export"
907 ))]),
908 StyledString::Line(vec![
909 StyledString::Code(self.import.clone().unwrap_or_else(|| rcstr!("*"))),
910 StyledString::Text(rcstr!(" of module ")),
911 StyledString::Strong(self.module_cycle.ident().to_string().owned().await?),
912 StyledString::Text(rcstr!(".")),
913 ]),
914 ])))
915 }
916
917 fn source(&self) -> Option<IssueSource> {
918 None
921 }
922}