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 LocalBinding {
64 ident: RcStr,
65 ctxt: SyntaxContext,
66 liveness: Liveness,
67 },
68 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 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 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 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 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 }
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 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 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 _ => {}
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 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 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 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 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 } 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 }
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 #[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 }
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 Vc::cell(None)
916 }
917}