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