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