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#[derive(PartialEq, Eq)]
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
330impl ReferencedAsset {
331 pub async fn from_resolve_result(resolve_result: Vc<ModuleResolveResult>) -> Result<Self> {
332 let result = resolve_result.await?;
334 if result.is_unresolvable() {
335 return Ok(ReferencedAsset::Unresolvable);
336 }
337 for (_, result) in result.primary.iter() {
338 match result {
339 ModuleResolveResultItem::External {
340 name: request, ty, ..
341 } => {
342 return Ok(ReferencedAsset::External(request.clone(), *ty));
343 }
344 ModuleResolveResultItem::Module(module) => {
345 if let Some(placeable) =
346 ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(*module)
347 {
348 return Ok(ReferencedAsset::Some(placeable));
349 }
350 }
351 _ => {}
353 }
354 }
355 Ok(ReferencedAsset::None)
356 }
357}
358
359#[turbo_tasks::value(transparent)]
360pub struct EsmAssetReferences(Vec<ResolvedVc<EsmAssetReference>>);
361
362#[turbo_tasks::value_impl]
363impl EsmAssetReferences {
364 #[turbo_tasks::function]
365 pub fn empty() -> Vc<Self> {
366 Vc::cell(Vec::new())
367 }
368}
369
370#[turbo_tasks::value(shared)]
371#[derive(Hash, Debug, ValueToString)]
372#[value_to_string("import {request}")]
373pub struct EsmAssetReference {
374 pub module: ResolvedVc<EcmascriptModuleAsset>,
375 pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
376 pub request: RcStr,
378 pub annotations: Option<ImportAnnotations>,
379 pub issue_source: IssueSource,
380 pub export_name: Option<ModulePart>,
381 pub import_usage: ImportUsage,
382 pub import_externals: bool,
383 pub tree_shaking_mode: Option<TreeShakingMode>,
384 pub is_pure_import: bool,
385 pub resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
386}
387
388impl EsmAssetReference {
389 fn get_origin(&self) -> Vc<Box<dyn ResolveOrigin>> {
390 if let Some(transition) = self.annotations.as_ref().and_then(|a| a.transition()) {
391 self.origin.with_transition(transition.into())
392 } else {
393 *self.origin
394 }
395 }
396}
397
398impl EsmAssetReference {
399 pub fn new(
400 module: ResolvedVc<EcmascriptModuleAsset>,
401 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
402 request: RcStr,
403 issue_source: IssueSource,
404 annotations: Option<ImportAnnotations>,
405 export_name: Option<ModulePart>,
406 import_usage: ImportUsage,
407 import_externals: bool,
408 tree_shaking_mode: Option<TreeShakingMode>,
409 resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
410 ) -> Self {
411 EsmAssetReference {
412 module,
413 origin,
414 request,
415 issue_source,
416 annotations,
417 export_name,
418 import_usage,
419 import_externals,
420 tree_shaking_mode,
421 is_pure_import: false,
422 resolve_override,
423 }
424 }
425
426 pub fn new_pure(
427 module: ResolvedVc<EcmascriptModuleAsset>,
428 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
429 request: RcStr,
430 issue_source: IssueSource,
431 annotations: Option<ImportAnnotations>,
432 export_name: Option<ModulePart>,
433 import_usage: ImportUsage,
434 import_externals: bool,
435 tree_shaking_mode: Option<TreeShakingMode>,
436 resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
437 ) -> Self {
438 EsmAssetReference {
439 module,
440 origin,
441 request,
442 issue_source,
443 annotations,
444 export_name,
445 import_usage,
446 import_externals,
447 tree_shaking_mode,
448 is_pure_import: true,
449 resolve_override,
450 }
451 }
452 pub(crate) fn get_referenced_asset(
453 self: Vc<Self>,
454 ) -> impl Future<Output = Result<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.await?.first_source() {
479 source.ident().await?.path.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.await?.primary_modules().await?.iter() {
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 fn source(&self) -> Option<IssueSource> {
592 Some(self.issue_source)
593 }
594}
595
596impl EsmAssetReference {
597 pub async fn code_generation(
598 self: ResolvedVc<Self>,
599 chunking_context: Vc<Box<dyn ChunkingContext>>,
600 scope_hoisting_context: ScopeHoistingContext<'_>,
601 ) -> Result<CodeGeneration> {
602 let this = &*self.await?;
603
604 if chunking_context
605 .unused_references()
606 .contains_key(&ResolvedVc::upcast(self))
607 .await?
608 {
609 return Ok(CodeGeneration::empty());
610 }
611
612 if this
614 .annotations
615 .as_ref()
616 .and_then(|a| a.chunking_type())
617 .is_none_or(|v| v != SpecifiedChunkingType::None)
618 {
619 let import_externals = this.import_externals;
620 let referenced_asset = self.get_referenced_asset().await?;
621
622 match &referenced_asset {
623 ReferencedAsset::Unresolvable => {
624 let request = &this.request;
627 let stmt = Stmt::Expr(ExprStmt {
628 expr: Box::new(throw_module_not_found_expr(request)),
629 span: DUMMY_SP,
630 });
631 return Ok(CodeGeneration::hoisted_stmt(
632 format!("throw {request}").into(),
633 stmt,
634 ));
635 }
636 ReferencedAsset::None => {}
637 _ => {
638 let mut result = vec![];
639
640 let merged_index = if let ReferencedAsset::Some(asset) = &referenced_asset {
641 scope_hoisting_context.get_module_index(*asset)
642 } else {
643 None
644 };
645
646 if let Some(merged_index) = merged_index {
647 result.push(CodeGenerationHoistedStmt::new(
650 format!("hoisted {merged_index}").into(),
651 quote!(
652 "__turbopack_merged_esm__($id);" as Stmt,
653 id: Expr = Lit::Num(merged_index.into()).into(),
654 ),
655 ));
656 }
657
658 if merged_index.is_some()
659 && matches!(this.export_name, Some(ModulePart::Evaluation))
660 {
661 } else {
665 let ident = referenced_asset
666 .get_ident(
667 chunking_context,
668 this.export_name.as_ref().and_then(|e| match e {
669 ModulePart::Export(export_name) => Some(export_name.clone()),
670 _ => None,
671 }),
672 scope_hoisting_context,
673 )
674 .await?;
675 drop(referenced_asset);
681 match ident {
682 Some(ReferencedAssetIdent::LocalBinding { .. }) => {
683 }
685 Some(ReferencedAssetIdent::Module {
686 namespace_ident,
687 ctxt,
688 export: _,
689 import_source,
690 }) => {
691 let span = this
692 .issue_source
693 .to_swc_offsets()
694 .await?
695 .map_or(DUMMY_SP, |(start, end)| {
696 Span::new(BytePos(start), BytePos(end))
697 });
698 let name = Ident::new(
699 namespace_ident.into(),
700 DUMMY_SP,
701 ctxt.unwrap_or_default(),
702 );
703 let (key, mut call_expr) = match import_source {
704 ImportSource::Module { asset } => {
705 let id = asset.chunk_item_id(chunking_context).await?;
706 (
711 format!("{} {:?}", id, ctxt).into(),
712 quote!(
713 "$turbopack_import($id)" as Expr,
714 turbopack_import: Expr = TURBOPACK_IMPORT.into(),
715 id: Expr = module_id_to_lit(&id),
716 ),
717 )
718 }
719 ImportSource::External {
720 request,
721 ty: ExternalType::EcmaScriptModule,
722 } => {
723 if !*chunking_context
724 .environment()
725 .supports_esm_externals()
726 .await?
727 {
728 turbobail!(
729 "the chunking context ({}) does not support \
730 external modules (esm request: {request})",
731 chunking_context.name()
732 );
733 }
734 let call = if import_externals {
735 quote!(
736 "$turbopack_external_import($id)" as Expr,
737 turbopack_external_import: Expr = TURBOPACK_EXTERNAL_IMPORT.into(),
738 id: Expr = Expr::Lit(request.to_string().into())
739 )
740 } else {
741 quote!(
742 "$turbopack_external_require($id, () => require($id), true)" as Expr,
743 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
744 id: Expr = Expr::Lit(request.to_string().into())
745 )
746 };
747 (name.sym.as_str().into(), call)
748 }
749 ImportSource::External {
750 request,
751 ty: ExternalType::CommonJs | ExternalType::Url,
752 } => {
753 if !*chunking_context
754 .environment()
755 .supports_commonjs_externals()
756 .await?
757 {
758 turbobail!(
759 "the chunking context ({}) does not support \
760 external modules (request: {request})",
761 chunking_context.name()
762 );
763 }
764 let call = quote!(
765 "$turbopack_external_require($id, () => require($id), true)" as Expr,
766 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
767 id: Expr = Expr::Lit(request.to_string().into())
768 );
769 (name.sym.as_str().into(), call)
770 }
771 #[allow(unreachable_patterns)]
773 ImportSource::External { request, ty, .. } => {
774 bail!(
775 "Unsupported external type {:?} for ESM reference \
776 with request: {:?}",
777 ty,
778 request
779 )
780 }
781 };
782 if this.is_pure_import {
783 call_expr.set_span(PURE_SP);
784 }
785 result.push(CodeGenerationHoistedStmt::new(
786 key,
787 var_decl_with_span(
788 quote!(
789 "var $name = $call;" as Stmt,
790 name = name,
791 call: Expr = call_expr
792 ),
793 span,
794 ),
795 ));
796 }
797 None => {
798 }
800 }
801 }
802 return Ok(CodeGeneration::hoisted_stmts(result));
803 }
804 }
805 };
806
807 Ok(CodeGeneration::empty())
808 }
809}
810
811fn var_decl_with_span(mut decl: Stmt, span: Span) -> Stmt {
812 match &mut decl {
813 Stmt::Decl(Decl::Var(decl)) => decl.span = span,
814 _ => panic!("Expected Stmt::Decl::Var"),
815 };
816 decl
817}
818
819#[turbo_tasks::value(shared)]
820pub struct InvalidExport {
821 export: RcStr,
822 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
823 source: IssueSource,
824}
825
826#[async_trait]
827#[turbo_tasks::value_impl]
828impl Issue for InvalidExport {
829 fn severity(&self) -> IssueSeverity {
830 IssueSeverity::Error
831 }
832
833 async fn title(&self) -> Result<StyledString> {
834 Ok(StyledString::Line(vec![
835 StyledString::Text(rcstr!("Export ")),
836 StyledString::Code(self.export.clone()),
837 StyledString::Text(rcstr!(" doesn't exist in target module")),
838 ]))
839 }
840
841 fn stage(&self) -> IssueStage {
842 IssueStage::Bindings
843 }
844
845 async fn file_path(&self) -> Result<FileSystemPath> {
846 self.source.file_path().await
847 }
848
849 async fn description(&self) -> Result<Option<StyledString>> {
850 let export_names = all_known_export_names(*self.module).await?;
851 let did_you_mean = export_names
852 .iter()
853 .map(|s| (s, jaro(self.export.as_str(), s.as_str())))
854 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
855 .map(|(s, _)| s);
856 Ok(Some(StyledString::Stack(vec![
857 StyledString::Line(vec![
858 StyledString::Text(rcstr!("The export ")),
859 StyledString::Code(self.export.clone()),
860 StyledString::Text(rcstr!(" was not found in module ")),
861 StyledString::Strong(self.module.ident().to_string().owned().await?),
862 StyledString::Text(rcstr!(".")),
863 ]),
864 if let Some(did_you_mean) = did_you_mean {
865 StyledString::Line(vec![
866 StyledString::Text(rcstr!("Did you mean to import ")),
867 StyledString::Code(did_you_mean.clone()),
868 StyledString::Text(rcstr!("?")),
869 ])
870 } else {
871 StyledString::Strong(rcstr!("The module has no exports at all."))
872 },
873 StyledString::Text(
874 "All exports of the module are statically known (It doesn't have dynamic \
875 exports). So it's known statically that the requested export doesn't exist."
876 .into(),
877 ),
878 ])))
879 }
880
881 async fn detail(&self) -> Result<Option<StyledString>> {
882 let export_names = all_known_export_names(*self.module).await?;
883 Ok(Some(StyledString::Line(vec![
884 StyledString::Text(rcstr!("These are the exports of the module:\n")),
885 StyledString::Code(
886 export_names
887 .iter()
888 .map(|s| s.as_str())
889 .intersperse(", ")
890 .collect::<String>()
891 .into(),
892 ),
893 ])))
894 }
895
896 fn source(&self) -> Option<IssueSource> {
897 Some(self.source)
898 }
899}
900
901#[turbo_tasks::value(shared)]
902pub struct CircularReExport {
903 export: RcStr,
904 import: Option<RcStr>,
905 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
906 module_cycle: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
907}
908
909#[async_trait]
910#[turbo_tasks::value_impl]
911impl Issue for CircularReExport {
912 fn severity(&self) -> IssueSeverity {
913 IssueSeverity::Error
914 }
915
916 async fn title(&self) -> Result<StyledString> {
917 Ok(StyledString::Line(vec![
918 StyledString::Text(rcstr!("Export ")),
919 StyledString::Code(self.export.clone()),
920 StyledString::Text(rcstr!(" is a circular re-export")),
921 ]))
922 }
923
924 fn stage(&self) -> IssueStage {
925 IssueStage::Bindings
926 }
927
928 async fn file_path(&self) -> Result<FileSystemPath> {
929 Ok(self.module.ident().await?.path.clone())
930 }
931
932 async fn description(&self) -> Result<Option<StyledString>> {
933 Ok(Some(StyledString::Stack(vec![
934 StyledString::Line(vec![StyledString::Text(rcstr!("The export"))]),
935 StyledString::Line(vec![
936 StyledString::Code(self.export.clone()),
937 StyledString::Text(rcstr!(" of module ")),
938 StyledString::Strong(self.module.ident().to_string().owned().await?),
939 ]),
940 StyledString::Line(vec![StyledString::Text(rcstr!(
941 "is a re-export of the export"
942 ))]),
943 StyledString::Line(vec![
944 StyledString::Code(self.import.clone().unwrap_or_else(|| rcstr!("*"))),
945 StyledString::Text(rcstr!(" of module ")),
946 StyledString::Strong(self.module_cycle.ident().to_string().owned().await?),
947 StyledString::Text(rcstr!(".")),
948 ]),
949 ])))
950 }
951
952 fn source(&self) -> Option<IssueSource> {
953 None
956 }
957}