1use std::{
2 borrow::Cow,
3 collections::BTreeMap,
4 fmt::{Display, Formatter, Write},
5 future::Future,
6 iter::{empty, once},
7 sync::LazyLock,
8};
9
10use anyhow::{Result, bail};
11use bincode::{Decode, Encode};
12use either::Either;
13use rustc_hash::{FxHashMap, FxHashSet};
14use serde::{Deserialize, Serialize};
15use smallvec::SmallVec;
16use tracing::{Instrument, Level};
17use turbo_frozenmap::{FrozenMap, FrozenSet};
18use turbo_rcstr::{RcStr, rcstr};
19use turbo_tasks::{
20 FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt,
21 ValueToString, ValueToStringRef, Vc, trace::TraceRawVcs,
22};
23use turbo_tasks_fs::{FileSystemEntryType, FileSystemPath};
24use turbo_unix_path::normalize_request;
25
26use crate::{
27 context::AssetContext,
28 data_uri_source::DataUriSource,
29 file_source::FileSource,
30 issue::{
31 Issue, IssueExt, IssueSource, module::emit_unknown_module_type_error,
32 resolve::ResolvingIssue,
33 },
34 module::Module,
35 package_json::{PackageJsonIssue, read_package_json},
36 raw_module::RawModule,
37 reference_type::ReferenceType,
38 resolve::{
39 alias_map::AliasKey,
40 error::{handle_resolve_error, resolve_error_severity},
41 node::{node_cjs_resolve_options, node_esm_resolve_options},
42 options::{
43 ConditionValue, ImportMapResult, ResolveInPackage, ResolveIntoPackage, ResolveModules,
44 ResolveModulesOptions, ResolveOptions, resolve_modules_options,
45 },
46 origin::ResolveOrigin,
47 parse::{Request, stringify_data_uri},
48 pattern::{Pattern, PatternMatch, read_matches},
49 plugin::{AfterResolvePlugin, AfterResolvePluginCondition, BeforeResolvePlugin},
50 remap::{ExportImport, ExportsField, ImportsField, ReplacedSubpathValueResult},
51 },
52 source::Source,
53};
54
55mod alias_map;
56pub mod error;
57pub mod node;
58pub mod options;
59pub mod origin;
60pub mod parse;
61pub mod pattern;
62pub mod plugin;
63pub(crate) mod remap;
64
65pub use alias_map::{
66 AliasMap, AliasMapIntoIter, AliasMapLookupIterator, AliasMatch, AliasPattern, AliasTemplate,
67};
68pub use remap::{ResolveAliasMap, SubpathValue};
69
70#[turbo_tasks::value(shared, task_input)]
72#[derive(Debug, Clone, Copy, Default, Hash)]
73pub enum ResolveErrorMode {
74 #[default]
76 Error,
77 Warn,
79 Ignore,
81}
82
83type AfterResolvePluginWithCondition = (
85 ResolvedVc<Box<dyn AfterResolvePlugin>>,
86 ReadRef<AfterResolvePluginCondition>,
87);
88
89#[turbo_tasks::value(shared)]
90#[derive(Clone, Debug)]
91pub enum ModuleResolveResultItem {
92 Module(ResolvedVc<Box<dyn Module>>),
93 External {
94 name: RcStr,
96 ty: ExternalType,
97 },
98 Unknown(ResolvedVc<Box<dyn Source>>),
100 Ignore,
102 Error(ResolvedVc<Box<dyn Issue>>),
104 Empty,
106 Custom(u8),
107 Duplicate(usize),
114}
115
116impl ModuleResolveResultItem {
117 async fn as_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
121 Ok(match *self {
122 ModuleResolveResultItem::Module(module) => Some(module),
123 ModuleResolveResultItem::Unknown(source) => {
124 emit_unknown_module_type_error(*source).await?;
125 None
126 }
127 ModuleResolveResultItem::Error(_err) => {
128 None
130 }
131 _ => None,
132 })
133 }
134}
135
136#[turbo_tasks::value(shared)]
137#[derive(Clone, Debug, Hash, Default, Serialize, Deserialize)]
138pub struct BindingUsage {
139 pub import: ImportUsage,
140 pub export: ExportUsage,
141}
142
143#[turbo_tasks::value_impl]
144impl BindingUsage {
145 #[turbo_tasks::function]
146 pub fn all() -> Vc<Self> {
147 Self::default().cell()
148 }
149}
150
151#[turbo_tasks::value(shared)]
153#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
154pub enum ImportUsage {
155 #[default]
158 TopLevel,
159 Exports(FrozenSet<RcStr>),
165}
166
167#[turbo_tasks::value]
169#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
170pub enum ExportUsage {
171 Named(RcStr),
172 PartialNamespaceObject(SmallVec<[RcStr; 1]>),
174 #[default]
176 All,
177 Evaluation,
179}
180
181impl Display for ExportUsage {
182 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
183 match self {
184 ExportUsage::Named(name) => write!(f, "export {name}"),
185 ExportUsage::PartialNamespaceObject(names) => {
186 write!(f, "exports ")?;
187 for (i, name) in names.iter().enumerate() {
188 if i > 0 {
189 write!(f, ", ")?;
190 }
191 write!(f, "{name}")?;
192 }
193 Ok(())
194 }
195 ExportUsage::All => write!(f, "all"),
196 ExportUsage::Evaluation => write!(f, "evaluation"),
197 }
198 }
199}
200
201#[turbo_tasks::value_impl]
202impl ExportUsage {
203 #[turbo_tasks::function]
204 pub fn all() -> Vc<Self> {
205 Self::All.cell()
206 }
207
208 #[turbo_tasks::function]
209 pub fn evaluation() -> Vc<Self> {
210 Self::Evaluation.cell()
211 }
212
213 #[turbo_tasks::function]
214 pub fn named(name: RcStr) -> Vc<Self> {
215 Self::Named(name).cell()
216 }
217}
218
219#[turbo_tasks::value(shared)]
220#[derive(Clone, Debug)]
221pub struct ModuleResolveResult {
222 pub primary: Box<[(RequestKey, ModuleResolveResultItem)]>,
223 pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
226}
227
228impl ModuleResolveResult {
229 pub fn unresolvable() -> ResolvedVc<Self> {
230 ModuleResolveResult {
231 primary: Default::default(),
232 affecting_sources: Default::default(),
233 }
234 .resolved_cell()
235 }
236
237 pub fn module(module: ResolvedVc<Box<dyn Module>>) -> ResolvedVc<Self> {
238 Self::module_with_key(RequestKey::default(), module)
239 }
240
241 pub fn module_with_key(
242 request_key: RequestKey,
243 module: ResolvedVc<Box<dyn Module>>,
244 ) -> ResolvedVc<Self> {
245 ModuleResolveResult {
246 primary: vec![(request_key, ModuleResolveResultItem::Module(module))]
247 .into_boxed_slice(),
248 affecting_sources: Default::default(),
249 }
250 .resolved_cell()
251 }
252
253 pub fn modules(
254 modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
255 ) -> ResolvedVc<Self> {
256 let mut primary: Vec<_> = modules
257 .into_iter()
258 .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
259 .collect();
260 Self::mark_duplicates(&mut primary);
261 ModuleResolveResult {
262 primary: primary.into_boxed_slice(),
263 affecting_sources: Default::default(),
264 }
265 .resolved_cell()
266 }
267
268 pub fn modules_with_affecting_sources(
269 modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
270 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
271 ) -> ResolvedVc<Self> {
272 let mut primary: Vec<_> = modules
273 .into_iter()
274 .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
275 .collect();
276 Self::mark_duplicates(&mut primary);
277 ModuleResolveResult {
278 primary: primary.into_boxed_slice(),
279 affecting_sources: affecting_sources.into_boxed_slice(),
280 }
281 .resolved_cell()
282 }
283}
284
285impl ModuleResolveResult {
286 fn mark_duplicates(primary: &mut [(RequestKey, ModuleResolveResultItem)]) {
290 if primary.len() <= 1 {
291 return;
292 }
293 let mut seen_modules = FxHashMap::default();
295 for (i, (_, item)) in primary.iter_mut().enumerate() {
296 if let ModuleResolveResultItem::Module(m) = *item {
297 if let Some(&first) = seen_modules.get(&m) {
298 *item = ModuleResolveResultItem::Duplicate(first);
299 } else {
300 seen_modules.insert(m, i);
301 }
302 }
303 }
304 }
305
306 pub fn primary_modules_raw_iter(
308 &self,
309 ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
310 self.primary.iter().filter_map(|(_, item)| match *item {
311 ModuleResolveResultItem::Module(a) => Some(a),
312 _ => None,
313 })
314 }
315
316 pub async fn primary_modules(&self) -> Result<Vec<ResolvedVc<Box<dyn Module>>>> {
320 self.primary
321 .iter()
322 .map(async |(_, item)| item.as_module().await)
323 .try_flat_join()
324 .await
325 }
326
327 pub async fn first_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
329 for (_, item) in self.primary.iter() {
330 if let Some(module) = item.as_module().await? {
331 return Ok(Some(module));
332 }
333 }
334 Ok(None)
335 }
336
337 pub fn affecting_sources_iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
338 self.affecting_sources.iter().copied()
339 }
340
341 pub fn is_unresolvable(&self) -> bool {
342 self.primary.is_empty()
343 }
344
345 pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
346 self.primary.iter().filter_map(|i| match &i.1 {
347 ModuleResolveResultItem::Error(e) => Some(*e),
348 _ => None,
349 })
350 }
351}
352
353pub struct ModuleResolveResultBuilder {
354 pub primary: FxIndexMap<RequestKey, ModuleResolveResultItem>,
355 pub affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
356}
357
358impl From<ModuleResolveResultBuilder> for ModuleResolveResult {
359 fn from(v: ModuleResolveResultBuilder) -> Self {
360 let mut primary: Vec<_> = v.primary.into_iter().collect();
361 Self::mark_duplicates(&mut primary);
362 ModuleResolveResult {
363 primary: primary.into_boxed_slice(),
364 affecting_sources: v.affecting_sources.into_boxed_slice(),
365 }
366 }
367}
368
369fn expand_duplicate<'a>(
373 source: &'a [(RequestKey, ModuleResolveResultItem)],
374 item: &'a ModuleResolveResultItem,
375) -> &'a ModuleResolveResultItem {
376 if let ModuleResolveResultItem::Duplicate(i) = *item {
377 &source[i].1
378 } else {
379 item
380 }
381}
382
383impl From<ModuleResolveResult> for ModuleResolveResultBuilder {
384 fn from(v: ModuleResolveResult) -> Self {
385 let primary = v
391 .primary
392 .iter()
393 .map(|(k, item)| (k.clone(), expand_duplicate(&v.primary, item).clone()))
394 .collect();
395 ModuleResolveResultBuilder {
396 primary,
397 affecting_sources: v.affecting_sources.into_vec(),
398 }
399 }
400}
401impl ModuleResolveResultBuilder {
402 pub fn merge_alternatives(&mut self, other: &ModuleResolveResult) {
403 for (k, v) in other.primary.iter() {
407 if !self.primary.contains_key(k) {
408 self.primary
409 .insert(k.clone(), expand_duplicate(&other.primary, v).clone());
410 }
411 }
412 let set = self
413 .affecting_sources
414 .iter()
415 .copied()
416 .collect::<FxHashSet<_>>();
417 self.affecting_sources.extend(
418 other
419 .affecting_sources
420 .iter()
421 .filter(|source| !set.contains(source))
422 .copied(),
423 );
424 }
425}
426
427#[turbo_tasks::value_impl]
428impl ModuleResolveResult {
429 #[turbo_tasks::function]
430 pub async fn alternatives(results: Vec<Vc<ModuleResolveResult>>) -> Result<Vc<Self>> {
431 if results.len() == 1 {
432 return Ok(results.into_iter().next().unwrap());
433 }
434 let mut iter = results.into_iter().try_join().await?.into_iter();
435 if let Some(current) = iter.next() {
436 let mut current: ModuleResolveResultBuilder = ReadRef::into_owned(current).into();
437 for result in iter {
438 let other = &*result;
440 current.merge_alternatives(other);
441 }
442 Ok(Self::cell(current.into()))
443 } else {
444 Ok(*ModuleResolveResult::unresolvable())
445 }
446 }
447}
448
449#[turbo_tasks::task_input]
450#[derive(
451 Copy, Clone, Debug, PartialEq, Eq, Hash, TraceRawVcs, Serialize, Deserialize, Encode, Decode,
452)]
453pub enum ExternalTraced {
454 Untraced,
455 Traced,
456}
457
458impl Display for ExternalTraced {
459 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
460 match self {
461 ExternalTraced::Untraced => write!(f, "untraced"),
462 ExternalTraced::Traced => write!(f, "traced"),
463 }
464 }
465}
466
467#[turbo_tasks::task_input]
468#[derive(
469 Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, TraceRawVcs, Encode, Decode,
470)]
471pub enum ExternalType {
472 Url,
473 CommonJs,
474 EcmaScriptModule,
475 Global,
476 Script,
477}
478
479impl Display for ExternalType {
480 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
481 match self {
482 ExternalType::CommonJs => write!(f, "commonjs"),
483 ExternalType::EcmaScriptModule => write!(f, "esm"),
484 ExternalType::Url => write!(f, "url"),
485 ExternalType::Global => write!(f, "global"),
486 ExternalType::Script => write!(f, "script"),
487 }
488 }
489}
490
491#[turbo_tasks::value(shared)]
492#[derive(Debug, Clone)]
493pub enum ResolveResultItem {
494 Source(ResolvedVc<Box<dyn Source>>),
495 External {
496 name: RcStr,
498 ty: ExternalType,
499 traced: ExternalTraced,
500 target: Option<FileSystemPath>,
503 },
504 Ignore,
506 Error(ResolvedVc<Box<dyn Issue>>),
508 Empty,
510 Custom(u8),
511}
512
513#[derive(Clone, Debug, Default, Hash)]
520#[turbo_tasks::value(task_input)]
521pub struct RequestKey {
522 pub request: Option<RcStr>,
523 pub conditions: FrozenMap<RcStr, bool>,
524}
525
526impl Display for RequestKey {
527 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
528 if let Some(request) = &self.request {
529 write!(f, "{request}")?;
530 } else {
531 write!(f, "<default>")?;
532 }
533 if !self.conditions.is_empty() {
534 write!(f, " (")?;
535 for (i, (k, v)) in self.conditions.iter().enumerate() {
536 if i > 0 {
537 write!(f, ", ")?;
538 }
539 write!(f, "{k}={v}")?;
540 }
541 write!(f, ")")?;
542 }
543 Ok(())
544 }
545}
546
547impl RequestKey {
548 pub fn new(request: RcStr) -> Self {
549 RequestKey {
550 request: Some(request),
551 ..Default::default()
552 }
553 }
554}
555
556#[turbo_tasks::value(shared)]
557#[derive(Clone)]
558pub struct ResolveResult {
559 pub primary: Box<[(RequestKey, ResolveResultItem)]>,
560 pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
563}
564
565#[turbo_tasks::value_impl]
566impl ValueToString for ResolveResult {
567 #[turbo_tasks::function]
568 async fn to_string(&self) -> Result<Vc<RcStr>> {
569 let mut result = String::new();
570 if self.is_unresolvable() {
571 result.push_str("unresolvable");
572 }
573 for (i, (request, item)) in self.primary.iter().enumerate() {
574 if i > 0 {
575 result.push_str(", ");
576 }
577 write!(result, "{request} -> ").unwrap();
578 match item {
579 ResolveResultItem::Source(a) => {
580 result.push_str(&a.ident().to_string().await?);
581 }
582 ResolveResultItem::External {
583 name: s,
584 ty,
585 traced,
586 target,
587 } => {
588 result.push_str("external ");
589 result.push_str(s);
590 write!(
591 result,
592 " ({ty}, {traced}, {:?})",
593 if let Some(target) = target {
594 Some(target.to_string_ref().await?)
595 } else {
596 None
597 }
598 )?;
599 }
600 ResolveResultItem::Ignore => {
601 result.push_str("ignore");
602 }
603 ResolveResultItem::Empty => {
604 result.push_str("empty");
605 }
606 ResolveResultItem::Error(_) => {
607 result.push_str("error");
608 }
609 ResolveResultItem::Custom(_) => {
610 result.push_str("custom");
611 }
612 }
613 result.push('\n');
614 }
615 if !self.affecting_sources.is_empty() {
616 result.push_str(" (affecting sources: ");
617 for (i, source) in self.affecting_sources.iter().enumerate() {
618 if i > 0 {
619 result.push_str(", ");
620 }
621 result.push_str(&source.ident().to_string().await?);
622 }
623 result.push(')');
624 }
625 Ok(Vc::cell(result.into()))
626 }
627}
628
629impl ResolveResult {
630 pub fn unresolvable() -> Self {
631 ResolveResult {
632 primary: Default::default(),
633 affecting_sources: Default::default(),
634 }
635 }
636
637 pub fn unresolvable_with_affecting_sources(
638 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
639 ) -> Self {
640 ResolveResult {
641 primary: Default::default(),
642 affecting_sources: affecting_sources.into_boxed_slice(),
643 }
644 }
645
646 pub fn primary(result: ResolveResultItem) -> Self {
647 Self::primary_with_key(RequestKey::default(), result)
648 }
649
650 pub fn primary_with_key(request_key: RequestKey, result: ResolveResultItem) -> Self {
651 ResolveResult {
652 primary: vec![(request_key, result)].into_boxed_slice(),
653 affecting_sources: Default::default(),
654 }
655 }
656
657 pub fn primary_with_affecting_sources(
658 request_key: RequestKey,
659 result: ResolveResultItem,
660 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
661 ) -> Self {
662 ResolveResult {
663 primary: vec![(request_key, result)].into_boxed_slice(),
664 affecting_sources: affecting_sources.into_boxed_slice(),
665 }
666 }
667
668 pub fn source(source: ResolvedVc<Box<dyn Source>>) -> Self {
669 Self::source_with_key(RequestKey::default(), source)
670 }
671
672 fn source_with_key(request_key: RequestKey, source: ResolvedVc<Box<dyn Source>>) -> Self {
673 ResolveResult {
674 primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
675 affecting_sources: Default::default(),
676 }
677 }
678
679 fn source_with_affecting_sources(
680 request_key: RequestKey,
681 source: ResolvedVc<Box<dyn Source>>,
682 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
683 ) -> Self {
684 ResolveResult {
685 primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
686 affecting_sources: affecting_sources.into_boxed_slice(),
687 }
688 }
689
690 pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
691 self.primary.iter().filter_map(|i| match &i.1 {
692 ResolveResultItem::Error(e) => Some(*e),
693 _ => None,
694 })
695 }
696}
697
698impl ResolveResult {
699 pub fn get_affecting_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
702 self.affecting_sources.iter().copied()
703 }
704
705 pub fn is_unresolvable(&self) -> bool {
706 self.primary.is_empty()
707 }
708
709 pub fn first_source(&self) -> Option<ResolvedVc<Box<dyn Source>>> {
710 self.primary.iter().find_map(|(_, item)| {
711 if let &ResolveResultItem::Source(a) = item {
712 Some(a)
713 } else {
714 None
715 }
716 })
717 }
718
719 pub fn primary_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> {
720 self.primary.iter().filter_map(|(_, item)| {
721 if let &ResolveResultItem::Source(a) = item {
722 Some(a)
723 } else {
724 None
725 }
726 })
727 }
728
729 pub async fn map_module<A, AF>(&self, source_fn: A) -> Result<ModuleResolveResult>
730 where
731 A: Fn(ResolvedVc<Box<dyn Source>>) -> AF,
732 AF: Future<Output = Result<ModuleResolveResultItem>>,
733 {
734 Ok(ModuleResolveResult {
735 primary: self
736 .primary
737 .iter()
738 .map(|(request, item)| {
739 let asset_fn = &source_fn;
740 let request = request.clone();
741 let item = item.clone();
742 async move {
743 Ok((
744 request,
745 match item {
746 ResolveResultItem::Source(source) => asset_fn(source).await?,
747 ResolveResultItem::External {
748 name,
749 ty,
750 traced,
751 target,
752 } => {
753 if traced == ExternalTraced::Traced || target.is_some() {
754 bail!("map_module doesn't handle traced externals");
756 }
757 ModuleResolveResultItem::External { name, ty }
758 }
759 ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore,
760 ResolveResultItem::Empty => ModuleResolveResultItem::Empty,
761 ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e),
762 ResolveResultItem::Custom(u8) => {
763 ModuleResolveResultItem::Custom(u8)
764 }
765 },
766 ))
767 }
768 })
769 .try_join()
770 .await?
771 .into_iter()
772 .collect(),
773 affecting_sources: self.affecting_sources.clone(),
774 })
775 }
776
777 pub async fn map_primary_items<A, AF>(&self, item_fn: A) -> Result<ModuleResolveResult>
778 where
779 A: Fn(ResolveResultItem) -> AF,
780 AF: Future<Output = Result<ModuleResolveResultItem>>,
781 {
782 Ok(ModuleResolveResult {
783 primary: self
784 .primary
785 .iter()
786 .map(|(request, item)| {
787 let asset_fn = &item_fn;
788 let request = request.clone();
789 let item = item.clone();
790 async move { Ok((request, asset_fn(item).await?)) }
791 })
792 .try_join()
793 .await?
794 .into_iter()
795 .collect(),
796 affecting_sources: self.affecting_sources.clone(),
797 })
798 }
799
800 fn with_request_ref(&self, request: RcStr) -> Self {
803 let new_primary = self
804 .primary
805 .iter()
806 .map(|(k, v)| {
807 (
808 RequestKey {
809 request: Some(request.clone()),
810 conditions: k.conditions.clone(),
811 },
812 v.clone(),
813 )
814 })
815 .collect();
816 ResolveResult {
817 primary: new_primary,
818 affecting_sources: self.affecting_sources.clone(),
819 }
820 }
821
822 pub fn with_conditions(&self, new_conditions: &[(RcStr, bool)]) -> Self {
823 let primary = self
824 .primary
825 .iter()
826 .map(|(k, v)| {
827 (
828 RequestKey {
829 request: k.request.clone(),
830 conditions: k.conditions.extend(new_conditions.iter().cloned()),
831 },
832 v.clone(),
833 )
834 })
835 .collect::<FxIndexMap<_, _>>() .into_iter()
837 .collect();
838 ResolveResult {
839 primary,
840 affecting_sources: self.affecting_sources.clone(),
841 }
842 }
843}
844
845struct ResolveResultBuilder {
846 primary: FxIndexMap<RequestKey, ResolveResultItem>,
847 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
848}
849
850impl From<ResolveResultBuilder> for ResolveResult {
851 fn from(v: ResolveResultBuilder) -> Self {
852 ResolveResult {
853 primary: v.primary.into_iter().collect(),
854 affecting_sources: v.affecting_sources.into_boxed_slice(),
855 }
856 }
857}
858impl From<ResolveResult> for ResolveResultBuilder {
859 fn from(v: ResolveResult) -> Self {
860 ResolveResultBuilder {
861 primary: IntoIterator::into_iter(v.primary).collect(),
862 affecting_sources: v.affecting_sources.into_vec(),
863 }
864 }
865}
866impl ResolveResultBuilder {
867 pub fn merge_alternatives(&mut self, other: &ResolveResult) {
868 for (k, v) in other.primary.iter() {
869 if !self.primary.contains_key(k) {
870 self.primary.insert(k.clone(), v.clone());
871 }
872 }
873 let set = self
874 .affecting_sources
875 .iter()
876 .copied()
877 .collect::<FxHashSet<_>>();
878 self.affecting_sources.extend(
879 other
880 .affecting_sources
881 .iter()
882 .filter(|source| !set.contains(source))
883 .copied(),
884 );
885 }
886}
887
888#[turbo_tasks::value_impl]
889impl ResolveResult {
890 #[turbo_tasks::function]
891 pub async fn as_raw_module_result(&self) -> Result<Vc<ModuleResolveResult>> {
892 Ok(self
893 .map_module(|asset| async move {
894 Ok(ModuleResolveResultItem::Module(ResolvedVc::upcast(
895 RawModule::new(*asset).to_resolved().await?,
896 )))
897 })
898 .await?
899 .cell())
900 }
901
902 #[turbo_tasks::function]
903 fn with_affecting_sources(
904 &self,
905 sources: Vec<ResolvedVc<Box<dyn Source>>>,
906 ) -> Result<Vc<Self>> {
907 Ok(Self {
908 primary: self.primary.clone(),
909 affecting_sources: self
910 .affecting_sources
911 .iter()
912 .copied()
913 .chain(sources)
914 .collect(),
915 }
916 .cell())
917 }
918
919 #[turbo_tasks::function]
920 async fn alternatives(results: Vec<Vc<ResolveResult>>) -> Result<Vc<Self>> {
921 if results.len() == 1 {
922 return Ok(results.into_iter().next().unwrap());
923 }
924 let mut iter = results.into_iter().try_join().await?.into_iter();
925 if let Some(current) = iter.next() {
926 let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
927 for result in iter {
928 let other = &*result;
930 current.merge_alternatives(other);
931 }
932 Ok(Self::cell(current.into()))
933 } else {
934 Ok(ResolveResult::unresolvable().cell())
935 }
936 }
937
938 #[turbo_tasks::function]
939 async fn alternatives_with_affecting_sources(
940 results: Vec<Vc<ResolveResult>>,
941 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
942 ) -> Result<Vc<Self>> {
943 debug_assert!(
944 !affecting_sources.is_empty(),
945 "Caller should not call this function if there are no affecting sources"
946 );
947 if results.len() == 1 {
948 return Ok(results
949 .into_iter()
950 .next()
951 .unwrap()
952 .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()));
953 }
954 let mut iter = results.into_iter().try_join().await?.into_iter();
955 if let Some(current) = iter.next() {
956 let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
957 for result in iter {
958 let other = &*result;
960 current.merge_alternatives(other);
961 }
962 current.affecting_sources.extend(affecting_sources);
963 Ok(Self::cell(current.into()))
964 } else {
965 Ok(ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell())
966 }
967 }
968
969 #[turbo_tasks::function]
974 fn with_replaced_request_key(
975 &self,
976 old_request_key: RcStr,
977 request_key: RequestKey,
978 ) -> Result<Vc<Self>> {
979 let new_primary = self
980 .primary
981 .iter()
982 .filter_map(|(k, v)| {
983 let remaining = k.request.as_ref()?.strip_prefix(&*old_request_key)?;
984 Some((
985 RequestKey {
986 request: request_key
987 .request
988 .as_ref()
989 .map(|r| format!("{r}{remaining}").into()),
990 conditions: request_key.conditions.clone(),
991 },
992 v.clone(),
993 ))
994 })
995 .collect();
996 Ok(ResolveResult {
997 primary: new_primary,
998 affecting_sources: self.affecting_sources.clone(),
999 }
1000 .cell())
1001 }
1002
1003 #[turbo_tasks::function]
1007 fn with_stripped_request_key_prefix(&self, prefix: RcStr) -> Result<Vc<Self>> {
1008 let new_primary = self
1009 .primary
1010 .iter()
1011 .filter_map(|(k, v)| {
1012 let remaining = k.request.as_ref()?.strip_prefix(&*prefix)?;
1013 Some((
1014 RequestKey {
1015 request: Some(remaining.into()),
1016 conditions: k.conditions.clone(),
1017 },
1018 v.clone(),
1019 ))
1020 })
1021 .collect();
1022 Ok(ResolveResult {
1023 primary: new_primary,
1024 affecting_sources: self.affecting_sources.clone(),
1025 }
1026 .cell())
1027 }
1028
1029 #[turbo_tasks::function]
1034 async fn with_replaced_request_key_pattern(
1035 &self,
1036 old_request_key: Vc<Pattern>,
1037 request_key: Vc<Pattern>,
1038 ) -> Result<Vc<Self>> {
1039 let old_request_key = &*old_request_key.await?;
1040 let request_key = &*request_key.await?;
1041
1042 let new_primary = self
1043 .primary
1044 .iter()
1045 .map(|(k, v)| {
1046 (
1047 RequestKey {
1048 request: k
1049 .request
1050 .as_ref()
1051 .and_then(|r| old_request_key.match_apply_template(r, request_key))
1052 .map(Into::into),
1053 conditions: k.conditions.clone(),
1054 },
1055 v.clone(),
1056 )
1057 })
1058 .collect();
1059 Ok(ResolveResult {
1060 primary: new_primary,
1061 affecting_sources: self.affecting_sources.clone(),
1062 }
1063 .cell())
1064 }
1065
1066 #[turbo_tasks::function]
1069 fn with_request(&self, request: RcStr) -> Vc<Self> {
1070 let new_primary = self
1071 .primary
1072 .iter()
1073 .map(|(k, v)| {
1074 (
1075 RequestKey {
1076 request: Some(request.clone()),
1077 conditions: k.conditions.clone(),
1078 },
1079 v.clone(),
1080 )
1081 })
1082 .collect();
1083 ResolveResult {
1084 primary: new_primary,
1085 affecting_sources: self.affecting_sources.clone(),
1086 }
1087 .cell()
1088 }
1089}
1090
1091#[turbo_tasks::value(transparent)]
1092pub struct ResolveResultOption(Option<ResolvedVc<ResolveResult>>);
1093
1094#[turbo_tasks::value_impl]
1095impl ResolveResultOption {
1096 #[turbo_tasks::function]
1097 pub fn some(result: ResolvedVc<ResolveResult>) -> Vc<Self> {
1098 ResolveResultOption(Some(result)).cell()
1099 }
1100
1101 #[turbo_tasks::function]
1102 pub fn none() -> Vc<Self> {
1103 ResolveResultOption(None).cell()
1104 }
1105}
1106
1107async fn exists(
1108 fs_path: &FileSystemPath,
1109 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1110) -> Result<Option<FileSystemPath>> {
1111 type_exists(fs_path, FileSystemEntryType::File, refs).await
1112}
1113
1114async fn dir_exists(
1115 fs_path: &FileSystemPath,
1116 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1117) -> Result<Option<FileSystemPath>> {
1118 type_exists(fs_path, FileSystemEntryType::Directory, refs).await
1119}
1120
1121async fn type_exists(
1122 fs_path: &FileSystemPath,
1123 ty: FileSystemEntryType,
1124 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1125) -> Result<Option<FileSystemPath>> {
1126 let path = realpath(fs_path, refs).await?;
1127 Ok(if *path.get_type().await? == ty {
1128 Some(path)
1129 } else {
1130 None
1131 })
1132}
1133
1134async fn realpath(
1135 fs_path: &FileSystemPath,
1136 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1137) -> Result<FileSystemPath> {
1138 let result = fs_path.realpath_with_links().await?;
1139 if let Some(refs) = refs {
1140 refs.extend(
1141 result
1142 .symlinks
1143 .iter()
1144 .map(|path| async move {
1145 Ok(ResolvedVc::upcast(
1146 FileSource::new(path.clone()).to_resolved().await?,
1147 ))
1148 })
1149 .try_join()
1150 .await?,
1151 );
1152 }
1153 match &result.path_result {
1154 Ok(path) => Ok(path.clone()),
1155 Err(e) => bail!(e.as_error_message(fs_path, &result).await?),
1156 }
1157}
1158
1159#[turbo_tasks::value(shared)]
1160enum ExportsFieldResult {
1161 Some(#[turbo_tasks(debug_ignore, trace_ignore)] ExportsField),
1162 None,
1163}
1164
1165#[turbo_tasks::function]
1168async fn exports_field(
1169 package_json_path: ResolvedVc<Box<dyn Source>>,
1170) -> Result<Vc<ExportsFieldResult>> {
1171 let read = read_package_json(*package_json_path).await?;
1172 let package_json = match &*read {
1173 Some(json) => json,
1174 None => return Ok(ExportsFieldResult::None.cell()),
1175 };
1176
1177 let Some(exports) = package_json.get("exports") else {
1178 return Ok(ExportsFieldResult::None.cell());
1179 };
1180 match exports.try_into() {
1181 Ok(exports) => Ok(ExportsFieldResult::Some(exports).cell()),
1182 Err(err) => {
1183 PackageJsonIssue {
1184 error_message: err.to_string().into(),
1185 source: IssueSource::from_source_only(package_json_path),
1187 }
1188 .resolved_cell()
1189 .emit();
1190 Ok(ExportsFieldResult::None.cell())
1191 }
1192 }
1193}
1194
1195#[turbo_tasks::value(shared)]
1196enum ImportsFieldResult {
1197 Some(
1198 #[turbo_tasks(debug_ignore, trace_ignore)] ImportsField,
1199 FileSystemPath,
1200 ),
1201 None,
1202}
1203
1204#[turbo_tasks::function]
1207async fn imports_field(lookup_path: FileSystemPath) -> Result<Vc<ImportsFieldResult>> {
1208 let package_json_context =
1210 find_context_file(lookup_path, *package_json().to_resolved().await?, false).await?;
1211 let FindContextFileResult::Found(package_json_path, _refs) = &*package_json_context else {
1212 return Ok(ImportsFieldResult::None.cell());
1213 };
1214 let source = Vc::upcast::<Box<dyn Source>>(FileSource::new(package_json_path.clone()))
1215 .to_resolved()
1216 .await?;
1217
1218 let read = read_package_json(*source).await?;
1219 let package_json = match &*read {
1220 Some(json) => json,
1221 None => return Ok(ImportsFieldResult::None.cell()),
1222 };
1223
1224 let Some(imports) = package_json.get("imports") else {
1225 return Ok(ImportsFieldResult::None.cell());
1226 };
1227 match imports.try_into() {
1228 Ok(imports) => Ok(ImportsFieldResult::Some(imports, package_json_path.clone()).cell()),
1229 Err(err) => {
1230 PackageJsonIssue {
1231 error_message: err.to_string().into(),
1232 source: IssueSource::from_source_only(source),
1234 }
1235 .resolved_cell()
1236 .emit();
1237 Ok(ImportsFieldResult::None.cell())
1238 }
1239 }
1240}
1241
1242#[turbo_tasks::function]
1243pub fn package_json() -> Vc<Vec<RcStr>> {
1244 Vc::cell(vec![rcstr!("package.json")])
1245}
1246
1247#[turbo_tasks::value(shared)]
1248pub enum FindContextFileResult {
1249 Found(FileSystemPath, Vec<ResolvedVc<Box<dyn Source>>>),
1250 NotFound(Vec<ResolvedVc<Box<dyn Source>>>),
1251}
1252
1253#[turbo_tasks::function]
1254pub async fn find_context_file(
1255 lookup_path: FileSystemPath,
1256 names: Vc<Vec<RcStr>>,
1257 collect_affecting_sources: bool,
1258) -> Result<Vc<FindContextFileResult>> {
1259 let mut refs = Vec::new();
1260 for name in &*names.await? {
1261 let fs_path = lookup_path.join(name)?;
1262 if let Some(fs_path) = exists(
1263 &fs_path,
1264 if collect_affecting_sources {
1265 Some(&mut refs)
1266 } else {
1267 None
1268 },
1269 )
1270 .await?
1271 {
1272 return Ok(FindContextFileResult::Found(fs_path, refs).cell());
1273 }
1274 }
1275 if lookup_path.is_root() {
1276 return Ok(FindContextFileResult::NotFound(refs).cell());
1277 }
1278 if refs.is_empty() {
1279 Ok(find_context_file(
1281 lookup_path.parent(),
1282 names,
1283 collect_affecting_sources,
1284 ))
1285 } else {
1286 let parent_result =
1287 find_context_file(lookup_path.parent(), names, collect_affecting_sources).await?;
1288 Ok(match &*parent_result {
1289 FindContextFileResult::Found(p, r) => {
1290 refs.extend(r.iter().copied());
1291 FindContextFileResult::Found(p.clone(), refs)
1292 }
1293 FindContextFileResult::NotFound(r) => {
1294 refs.extend(r.iter().copied());
1295 FindContextFileResult::NotFound(refs)
1296 }
1297 }
1298 .cell())
1299 }
1300}
1301
1302#[turbo_tasks::function]
1305pub async fn find_context_file_or_package_key(
1306 lookup_path: FileSystemPath,
1307 names: Vc<Vec<RcStr>>,
1308 package_key: RcStr,
1309) -> Result<Vc<FindContextFileResult>> {
1310 let package_json_path = lookup_path.join("package.json")?;
1311 if let Some(package_json_path) = exists(&package_json_path, None).await?
1312 && let Some(json) =
1313 &*read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?
1314 && json.get(&*package_key).is_some()
1315 {
1316 return Ok(FindContextFileResult::Found(package_json_path, Vec::new()).cell());
1317 }
1318 for name in &*names.await? {
1319 let fs_path = lookup_path.join(name)?;
1320 if let Some(fs_path) = exists(&fs_path, None).await? {
1321 return Ok(FindContextFileResult::Found(fs_path, Vec::new()).cell());
1322 }
1323 }
1324 if lookup_path.is_root() {
1325 return Ok(FindContextFileResult::NotFound(Vec::new()).cell());
1326 }
1327
1328 Ok(find_context_file(lookup_path.parent(), names, false))
1329}
1330
1331#[derive(Clone, PartialEq, Eq, TraceRawVcs, Debug, NonLocalValue, Encode, Decode)]
1332enum FindPackageItem {
1333 PackageDirectory { name: RcStr, dir: FileSystemPath },
1334 PackageFile { name: RcStr, file: FileSystemPath },
1335}
1336
1337#[turbo_tasks::value]
1338#[derive(Debug)]
1339struct FindPackageResult {
1340 packages: Vec<FindPackageItem>,
1341 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
1343}
1344
1345#[turbo_tasks::function]
1346async fn find_package(
1347 lookup_path: FileSystemPath,
1348 package_name: Pattern,
1349 options: Vc<ResolveModulesOptions>,
1350 collect_affecting_sources: bool,
1351) -> Result<Vc<FindPackageResult>> {
1352 let mut packages = vec![];
1353 let mut affecting_sources = vec![];
1354 let options = options.await?;
1355 let package_name_cell = Pattern::new(package_name.clone());
1356
1357 fn get_package_name(basepath: &FileSystemPath, package_dir: &FileSystemPath) -> Result<RcStr> {
1358 if let Some(name) = basepath.get_path_to(package_dir) {
1359 Ok(name.into())
1360 } else {
1361 bail!("Package directory {package_dir} is not inside the lookup path {basepath}",);
1362 }
1363 }
1364
1365 for resolve_modules in &options.modules {
1366 match resolve_modules {
1367 ResolveModules::Nested(root, names) => {
1368 let mut lookup_path = lookup_path.clone();
1369 let mut lookup_path_value = lookup_path.clone();
1370 while lookup_path_value.is_inside_ref(root) {
1371 for name in names.iter() {
1372 let fs_path = lookup_path.join(name)?;
1373 if let Some(fs_path) = dir_exists(
1374 &fs_path,
1375 collect_affecting_sources.then_some(&mut affecting_sources),
1376 )
1377 .await?
1378 {
1379 let matches =
1380 read_matches(fs_path.clone(), rcstr!(""), true, package_name_cell)
1381 .await?;
1382 for m in &*matches {
1383 if let PatternMatch::Directory(_, package_dir) = m {
1384 packages.push(FindPackageItem::PackageDirectory {
1385 name: get_package_name(&fs_path, package_dir)?,
1386 dir: realpath(
1387 package_dir,
1388 collect_affecting_sources
1389 .then_some(&mut affecting_sources),
1390 )
1391 .await?,
1392 });
1393 }
1394 }
1395 }
1396 }
1397 lookup_path = lookup_path.parent();
1398 let new_context_value = lookup_path.clone();
1399 if new_context_value == lookup_path_value {
1400 break;
1401 }
1402 lookup_path_value = new_context_value;
1403 }
1404 }
1405 ResolveModules::Path {
1406 dir,
1407 excluded_extensions,
1408 } => {
1409 let matches =
1410 read_matches(dir.clone(), rcstr!(""), true, package_name_cell).await?;
1411 for m in &*matches {
1412 match m {
1413 PatternMatch::Directory(_, package_dir) => {
1414 packages.push(FindPackageItem::PackageDirectory {
1415 name: get_package_name(dir, package_dir)?,
1416 dir: realpath(
1417 package_dir,
1418 collect_affecting_sources.then_some(&mut affecting_sources),
1419 )
1420 .await?,
1421 });
1422 }
1423 PatternMatch::File(_, package_file) => {
1424 packages.push(FindPackageItem::PackageFile {
1425 name: get_package_name(dir, package_file)?,
1426 file: realpath(
1427 package_file,
1428 collect_affecting_sources.then_some(&mut affecting_sources),
1429 )
1430 .await?,
1431 });
1432 }
1433 }
1434 }
1435
1436 let excluded_extensions = excluded_extensions.await?;
1437 let mut package_name_with_extensions = package_name.clone();
1438 package_name_with_extensions.push(Pattern::alternatives(
1439 options
1440 .extensions
1441 .iter()
1442 .filter(|ext| !excluded_extensions.contains(*ext))
1443 .cloned()
1444 .map(Pattern::from),
1445 ));
1446 let package_name_with_extensions = Pattern::new(package_name_with_extensions);
1447
1448 let matches =
1449 read_matches(dir.clone(), rcstr!(""), true, package_name_with_extensions)
1450 .await?;
1451 for m in &matches {
1452 if let PatternMatch::File(_, package_file) = m {
1453 packages.push(FindPackageItem::PackageFile {
1454 name: get_package_name(dir, package_file)?,
1455 file: realpath(
1456 package_file,
1457 collect_affecting_sources.then_some(&mut affecting_sources),
1458 )
1459 .await?,
1460 });
1461 }
1462 }
1463 }
1464 }
1465 }
1466 Ok(FindPackageResult::cell(FindPackageResult {
1467 packages,
1468 affecting_sources,
1469 }))
1470}
1471
1472fn merge_results(results: Vec<Vc<ResolveResult>>) -> Vc<ResolveResult> {
1473 match results.len() {
1474 0 => ResolveResult::unresolvable().cell(),
1475 1 => results.into_iter().next().unwrap(),
1476 _ => ResolveResult::alternatives(results),
1477 }
1478}
1479
1480fn merge_results_with_affecting_sources(
1481 results: Vec<Vc<ResolveResult>>,
1482 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
1483) -> Vc<ResolveResult> {
1484 if affecting_sources.is_empty() {
1485 return merge_results(results);
1486 }
1487 match results.len() {
1488 0 => ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell(),
1489 1 => results
1490 .into_iter()
1491 .next()
1492 .unwrap()
1493 .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()),
1494 _ => ResolveResult::alternatives_with_affecting_sources(
1495 results,
1496 affecting_sources.into_iter().map(|src| *src).collect(),
1497 ),
1498 }
1499}
1500
1501#[turbo_tasks::function]
1503pub async fn resolve_raw(
1504 lookup_dir: FileSystemPath,
1505 path: Vc<Pattern>,
1506 collect_affecting_sources: bool,
1507 force_in_lookup_dir: bool,
1508) -> Result<Vc<ResolveResult>> {
1509 async fn to_result(
1510 request: RcStr,
1511 path: &FileSystemPath,
1512 collect_affecting_sources: bool,
1513 ) -> Result<ResolveResult> {
1514 let result = &*path.realpath_with_links().await?;
1515 let path = match &result.path_result {
1516 Ok(path) => path,
1517 Err(e) => bail!(e.as_error_message(path, result).await?),
1518 };
1519 let request_key = RequestKey::new(request);
1520 let source = ResolvedVc::upcast(FileSource::new(path.clone()).to_resolved().await?);
1521 Ok(if collect_affecting_sources {
1522 ResolveResult::source_with_affecting_sources(
1523 request_key,
1524 source,
1525 result
1526 .symlinks
1527 .iter()
1528 .map(|symlink| {
1529 Vc::upcast::<Box<dyn Source>>(FileSource::new(symlink.clone()))
1530 .to_resolved()
1531 })
1532 .try_join()
1533 .await?,
1534 )
1535 } else {
1536 ResolveResult::source_with_key(request_key, source)
1537 })
1538 }
1539
1540 async fn collect_matches(
1541 matches: &[PatternMatch],
1542 collect_affecting_sources: bool,
1543 ) -> Result<Vec<Vc<ResolveResult>>> {
1544 Ok(matches
1545 .iter()
1546 .map(|m| async move {
1547 Ok(if let PatternMatch::File(request, path) = m {
1548 Some(to_result(request.clone(), path, collect_affecting_sources).await?)
1549 } else {
1550 None
1551 })
1552 })
1553 .try_flat_join()
1554 .await?
1555 .into_iter()
1558 .map(|res| res.cell())
1559 .collect())
1560 }
1561
1562 let mut results = Vec::new();
1563
1564 let pat = path.await?;
1565 if let Some(pat) = pat
1566 .filter_could_match("/ROOT/")
1567 .and_then(|pat| pat.filter_could_not_match("/ROOT/fsd8nz8og54z"))
1570 {
1571 let path = Pattern::new(pat);
1572 let matches = read_matches(
1573 lookup_dir.root().owned().await?,
1574 rcstr!("/ROOT/"),
1575 true,
1576 path,
1577 )
1578 .await?;
1579 results.extend(collect_matches(&matches, collect_affecting_sources).await?);
1580 }
1581
1582 {
1583 let matches =
1584 read_matches(lookup_dir.clone(), rcstr!(""), force_in_lookup_dir, path).await?;
1585
1586 results.extend(collect_matches(&matches, collect_affecting_sources).await?);
1587 }
1588
1589 Ok(merge_results(results))
1590}
1591
1592#[turbo_tasks::function]
1593pub async fn resolve(
1594 lookup_path: FileSystemPath,
1595 reference_type: ReferenceType,
1596 request: Vc<Request>,
1597 options: Vc<ResolveOptions>,
1598) -> Result<Vc<ResolveResult>> {
1599 resolve_inline(lookup_path, reference_type, request, options).await
1600}
1601
1602pub async fn resolve_inline(
1603 lookup_path: FileSystemPath,
1604 reference_type: ReferenceType,
1605 request: Vc<Request>,
1606 options: Vc<ResolveOptions>,
1607) -> Result<Vc<ResolveResult>> {
1608 let span = tracing::info_span!(
1609 "resolving",
1610 lookup_path = display(lookup_path.to_string_ref().await?),
1611 name = tracing::field::Empty,
1612 reference_type = display(&reference_type),
1613 );
1614 if !span.is_disabled() {
1615 span.record("name", request.to_string().await?.as_str());
1617 }
1618
1619 async {
1620 let options_value = options.await?;
1622
1623 let has_before_plugins = !options_value.before_resolve_plugins.is_empty();
1625 let has_after_plugins = !options_value.after_resolve_plugins.is_empty();
1626
1627 let before_plugins_result = if has_before_plugins {
1628 handle_before_resolve_plugins(
1629 lookup_path.clone(),
1630 reference_type.clone(),
1631 request,
1632 options,
1633 )
1634 .await?
1635 } else {
1636 None
1637 };
1638
1639 let raw_result = match before_plugins_result {
1640 Some(result) => result,
1641 None => {
1642 *resolve_internal(lookup_path.clone(), request, options)
1643 .to_resolved()
1644 .await?
1645 }
1646 };
1647
1648 let result = if has_after_plugins {
1649 handle_after_resolve_plugins(lookup_path, reference_type, request, options, raw_result)
1650 .await?
1651 } else {
1652 raw_result
1653 };
1654
1655 Ok(result)
1656 }
1657 .instrument(span)
1658 .await
1659}
1660
1661#[turbo_tasks::function]
1662pub async fn url_resolve(
1663 origin: Vc<Box<dyn ResolveOrigin>>,
1664 request: ResolvedVc<Request>,
1665 reference_type: ReferenceType,
1666 issue_source: Option<IssueSource>,
1667 error_mode: ResolveErrorMode,
1668) -> Result<Vc<ModuleResolveResult>> {
1669 let origin_ref = origin.into_trait_ref().await?;
1670 let resolve_options = origin_ref.resolve_options();
1671 let rel_request = request.as_relative();
1672 let origin_path = origin_ref.origin_path();
1673 let origin_path_parent = origin_path.parent();
1674 let rel_result = resolve(
1675 origin_path_parent.clone(),
1676 reference_type.clone(),
1677 rel_request,
1678 resolve_options,
1679 );
1680 let result =
1681 if rel_result.await?.is_unresolvable() && rel_request.to_resolved().await? != request {
1682 let result = resolve(
1683 origin_path_parent,
1684 reference_type.clone(),
1685 *request,
1686 resolve_options,
1687 );
1688 if resolve_options.await?.collect_affecting_sources {
1689 result.with_affecting_sources(
1690 rel_result
1691 .await?
1692 .get_affecting_sources()
1693 .map(|src| *src)
1694 .collect(),
1695 )
1696 } else {
1697 result
1698 }
1699 } else {
1700 rel_result
1701 };
1702 let result = origin_ref
1703 .asset_context()
1704 .process_resolve_result(result, reference_type.clone());
1705 handle_resolve_error(
1706 result,
1707 reference_type,
1708 origin_path,
1709 *request,
1710 resolve_options,
1711 error_mode,
1712 issue_source,
1713 )
1714 .await
1715}
1716
1717#[turbo_tasks::value(transparent)]
1718struct MatchingBeforeResolvePlugins(Vec<ResolvedVc<Box<dyn BeforeResolvePlugin>>>);
1719
1720#[turbo_tasks::function]
1721async fn get_matching_before_resolve_plugins(
1722 options: Vc<ResolveOptions>,
1723 request: Vc<Request>,
1724) -> Result<Vc<MatchingBeforeResolvePlugins>> {
1725 let request_ref = request.await?;
1726 let matching_plugins = options
1727 .await?
1728 .before_resolve_plugins
1729 .iter()
1730 .map(async |plugin| {
1731 Ok(
1732 if plugin
1733 .into_trait_ref()
1734 .await?
1735 .before_resolve_condition()
1736 .await?
1737 .matches(&request_ref)
1738 {
1739 Some(*plugin)
1740 } else {
1741 None
1742 },
1743 )
1744 })
1745 .try_flat_join()
1746 .await?;
1747 Ok(Vc::cell(matching_plugins))
1748}
1749
1750#[tracing::instrument(level = "trace", skip_all)]
1751async fn handle_before_resolve_plugins(
1752 lookup_path: FileSystemPath,
1753 reference_type: ReferenceType,
1754 request: Vc<Request>,
1755 options: Vc<ResolveOptions>,
1756) -> Result<Option<Vc<ResolveResult>>> {
1757 for plugin in get_matching_before_resolve_plugins(options, request).await? {
1758 if let Some(result) = *plugin
1759 .before_resolve(lookup_path.clone(), reference_type.clone(), request)
1760 .await?
1761 {
1762 return Ok(Some(*result));
1763 }
1764 }
1765 Ok(None)
1766}
1767
1768#[tracing::instrument(level = "trace", skip_all)]
1769async fn handle_after_resolve_plugins(
1770 lookup_path: FileSystemPath,
1771 reference_type: ReferenceType,
1772 request: Vc<Request>,
1773 options: Vc<ResolveOptions>,
1774 result: Vc<ResolveResult>,
1775) -> Result<Vc<ResolveResult>> {
1776 let options_value = options.await?;
1778
1779 let resolved_conditions = options_value
1781 .after_resolve_plugins
1782 .iter()
1783 .map(async |p| {
1784 let condition = p.into_trait_ref().await?.after_resolve_condition().await?;
1785 Ok((*p, condition))
1786 })
1787 .try_join()
1788 .await?;
1789
1790 async fn apply_plugins_to_path(
1791 path: FileSystemPath,
1792 lookup_path: FileSystemPath,
1793 reference_type: ReferenceType,
1794 request: Vc<Request>,
1795 plugins_with_conditions: &[AfterResolvePluginWithCondition],
1796 ) -> Result<Option<Vc<ResolveResult>>> {
1797 for (plugin, after_resolve_condition) in plugins_with_conditions {
1798 if after_resolve_condition.matches(&path)
1799 && let Some(result) = *plugin
1800 .after_resolve(
1801 path.clone(),
1802 lookup_path.clone(),
1803 reference_type.clone(),
1804 request,
1805 )
1806 .await?
1807 {
1808 return Ok(Some(*result));
1809 }
1810 }
1811 Ok(None)
1812 }
1813
1814 let mut changed = false;
1815 let result_value = result.await?;
1816
1817 let mut new_primary = FxIndexMap::default();
1818 let mut new_affecting_sources = Vec::new();
1819
1820 for (key, primary) in result_value.primary.iter() {
1821 if let &ResolveResultItem::Source(source) = primary {
1822 let path = source.ident().await?.path.clone();
1823 if let Some(new_result) = apply_plugins_to_path(
1824 path,
1825 lookup_path.clone(),
1826 reference_type.clone(),
1827 request,
1828 &resolved_conditions,
1829 )
1830 .await?
1831 {
1832 let new_result = new_result.await?;
1833 changed = true;
1834 new_primary.extend(
1835 new_result
1836 .primary
1837 .iter()
1838 .map(|(_, item)| (key.clone(), item.clone())),
1839 );
1840 new_affecting_sources.extend(new_result.affecting_sources.iter().copied());
1841 } else {
1842 new_primary.insert(key.clone(), primary.clone());
1843 }
1844 } else {
1845 new_primary.insert(key.clone(), primary.clone());
1846 }
1847 }
1848
1849 if !changed {
1850 return Ok(result);
1851 }
1852
1853 let mut affecting_sources = result_value.affecting_sources.to_vec();
1854 affecting_sources.append(&mut new_affecting_sources);
1855
1856 Ok(ResolveResult {
1857 primary: new_primary.into_iter().collect(),
1858 affecting_sources: affecting_sources.into_boxed_slice(),
1859 }
1860 .cell())
1861}
1862
1863#[turbo_tasks::function]
1864async fn resolve_internal(
1865 lookup_path: FileSystemPath,
1866 request: ResolvedVc<Request>,
1867 options: ResolvedVc<ResolveOptions>,
1868) -> Result<Vc<ResolveResult>> {
1869 resolve_internal_inline(lookup_path.clone(), *request, *options).await
1870}
1871
1872async fn resolve_internal_inline(
1873 lookup_path: FileSystemPath,
1874 request: Vc<Request>,
1875 options: Vc<ResolveOptions>,
1876) -> Result<Vc<ResolveResult>> {
1877 let span = tracing::info_span!(
1878 "internal resolving",
1879 lookup_path = display(lookup_path.to_string_ref().await?),
1880 name = tracing::field::Empty
1881 );
1882 if !span.is_disabled() {
1883 span.record("name", request.to_string().await?.as_str());
1885 }
1886
1887 async move {
1888 let options_value: &ResolveOptions = &*options.await?;
1889
1890 let request_value = request.await?;
1891
1892 let mut has_alias = false;
1894 if let Some(import_map) = &options_value.import_map {
1895 let request_parts = match &*request_value {
1896 Request::Alternatives { requests } => requests.as_slice(),
1897 _ => &[request.to_resolved().await?],
1898 };
1899 for &request in request_parts {
1900 let result = import_map
1901 .await?
1902 .lookup(lookup_path.clone(), *request)
1903 .await?;
1904 if !matches!(result, ImportMapResult::NoEntry) {
1905 has_alias = true;
1906 let resolved_result = resolve_import_map_result(
1907 &result,
1908 lookup_path.clone(),
1909 lookup_path.clone(),
1910 *request,
1911 options,
1912 request.query().owned().await?,
1913 )
1914 .await?;
1915 if let Some(resolved_result) = resolved_result {
1922 let resolved_result = resolved_result.into_cell_if_resolvable().await?;
1923 if let Some(result) = resolved_result {
1924 return Ok(result);
1925 }
1926 }
1927 }
1928 }
1929 }
1930
1931 let result = match &*request_value {
1932 Request::Dynamic => ResolveResult::unresolvable().cell(),
1933 Request::Alternatives { requests } => {
1934 let results = requests
1935 .iter()
1936 .map(|req| async {
1937 resolve_internal_inline(lookup_path.clone(), **req, options).await
1938 })
1939 .try_join()
1940 .await?;
1941
1942 merge_results(results)
1943 }
1944 Request::Raw {
1945 path,
1946 query,
1947 force_in_lookup_dir,
1948 fragment,
1949 } => {
1950 let mut results = Vec::new();
1951 let matches = read_matches(
1952 lookup_path.clone(),
1953 rcstr!(""),
1954 *force_in_lookup_dir,
1955 *Pattern::new(path.clone()).to_resolved().await?,
1956 )
1957 .await?;
1958
1959 for m in matches.iter() {
1960 match m {
1961 PatternMatch::File(matched_pattern, path) => {
1962 results.push(
1963 resolved(
1964 RequestKey::new(matched_pattern.clone()),
1965 path.clone(),
1966 lookup_path.clone(),
1967 request,
1968 options_value,
1969 options,
1970 query.clone(),
1971 fragment.clone(),
1972 )
1973 .await?
1974 .into_cell(),
1975 );
1976 }
1977 PatternMatch::Directory(matched_pattern, path) => {
1978 results.push(
1979 resolve_into_folder(path.clone(), options)
1980 .with_request(matched_pattern.clone()),
1981 );
1982 }
1983 }
1984 }
1985
1986 merge_results(results)
1987 }
1988 Request::Relative {
1989 path,
1990 query,
1991 force_in_lookup_dir,
1992 fragment,
1993 } => {
1994 resolve_relative_request(
1995 lookup_path.clone(),
1996 request,
1997 options,
1998 options_value,
1999 path,
2000 query.clone(),
2001 *force_in_lookup_dir,
2002 fragment.clone(),
2003 )
2004 .await?
2005 }
2006 Request::Module {
2007 module,
2008 path,
2009 query,
2010 fragment,
2011 } => {
2012 resolve_module_request(
2013 lookup_path.clone(),
2014 request,
2015 options,
2016 options_value,
2017 module,
2018 path,
2019 query.clone(),
2020 fragment.clone(),
2021 )
2022 .await?
2023 }
2024 Request::ServerRelative {
2025 path,
2026 query,
2027 fragment,
2028 } => {
2029 let mut new_pat = path.clone();
2030 new_pat.push_front(rcstr!(".").into());
2031 let relative = Request::relative(new_pat, query.clone(), fragment.clone(), true);
2032
2033 if !has_alias {
2034 ResolvingIssue {
2035 severity: resolve_error_severity(options).await?,
2036 request_type: "server relative import: not implemented yet".to_string(),
2037 request: relative.to_resolved().await?,
2038 file_path: lookup_path.clone(),
2039 resolve_options: options.to_resolved().await?,
2040 error_message: Some(
2041 "server relative imports are not implemented yet. Please try an \
2042 import relative to the file you are importing from."
2043 .to_string(),
2044 ),
2045 source: None,
2046 }
2047 .resolved_cell()
2048 .emit();
2049 }
2050
2051 Box::pin(resolve_internal_inline(
2052 lookup_path.root().owned().await?,
2053 relative,
2054 options,
2055 ))
2056 .await?
2057 }
2058 Request::Windows {
2059 path: _,
2060 query: _,
2061 fragment: _,
2062 } => {
2063 if !has_alias {
2064 ResolvingIssue {
2065 severity: resolve_error_severity(options).await?,
2066 request_type: "windows import: not implemented yet".to_string(),
2067 request: request.to_resolved().await?,
2068 file_path: lookup_path.clone(),
2069 resolve_options: options.to_resolved().await?,
2070 error_message: Some("windows imports are not implemented yet".to_string()),
2071 source: None,
2072 }
2073 .resolved_cell()
2074 .emit();
2075 }
2076
2077 ResolveResult::unresolvable().cell()
2078 }
2079 Request::Empty => ResolveResult::unresolvable().cell(),
2080 Request::PackageInternal { path } => {
2081 let (conditions, unspecified_conditions) = options_value
2082 .in_package
2083 .iter()
2084 .find_map(|item| match item {
2085 ResolveInPackage::ImportsField {
2086 conditions,
2087 unspecified_conditions,
2088 } => Some((Cow::Borrowed(conditions), *unspecified_conditions)),
2089 _ => None,
2090 })
2091 .unwrap_or_else(|| (Default::default(), ConditionValue::Unset));
2092 resolve_package_internal_with_imports_field(
2093 lookup_path.clone(),
2094 request,
2095 options,
2096 path,
2097 &conditions,
2098 &unspecified_conditions,
2099 )
2100 .await?
2101 }
2102 Request::DataUri {
2103 media_type,
2104 encoding,
2105 data,
2106 } => {
2107 let uri: RcStr = stringify_data_uri(media_type, encoding, *data)
2109 .await?
2110 .into();
2111 if options_value.parse_data_uris {
2112 ResolveResult::primary_with_key(
2113 RequestKey::new(uri.clone()),
2114 ResolveResultItem::Source(ResolvedVc::upcast(
2115 DataUriSource::new(
2116 media_type.clone(),
2117 encoding.clone(),
2118 **data,
2119 lookup_path.clone(),
2120 )
2121 .to_resolved()
2122 .await?,
2123 )),
2124 )
2125 .cell()
2126 } else {
2127 ResolveResult::primary_with_key(
2128 RequestKey::new(uri.clone()),
2129 ResolveResultItem::External {
2130 name: uri,
2131 ty: ExternalType::Url,
2132 traced: ExternalTraced::Untraced,
2133 target: None,
2134 },
2135 )
2136 .cell()
2137 }
2138 }
2139 Request::Uri {
2140 protocol,
2141 remainder,
2142 query: _,
2143 fragment: _,
2144 } => {
2145 let uri: RcStr = format!("{protocol}{remainder}").into();
2146 ResolveResult::primary_with_key(
2147 RequestKey::new(uri.clone()),
2148 ResolveResultItem::External {
2149 name: uri,
2150 ty: ExternalType::Url,
2151 traced: ExternalTraced::Untraced,
2152 target: None,
2153 },
2154 )
2155 .cell()
2156 }
2157 Request::Unknown { path } => {
2158 if !has_alias {
2159 ResolvingIssue {
2160 severity: resolve_error_severity(options).await?,
2161 request_type: format!("unknown import: `{}`", path.describe_as_string()),
2162 request: request.to_resolved().await?,
2163 file_path: lookup_path.clone(),
2164 resolve_options: options.to_resolved().await?,
2165 error_message: None,
2166 source: None,
2167 }
2168 .resolved_cell()
2169 .emit();
2170 }
2171 ResolveResult::unresolvable().cell()
2172 }
2173 };
2174
2175 if !matches!(*request_value, Request::Alternatives { .. }) {
2178 if let Some(import_map) = &options_value.fallback_import_map
2180 && result.await?.is_unresolvable()
2181 {
2182 let result = import_map
2183 .await?
2184 .lookup(lookup_path.clone(), request)
2185 .await?;
2186 let resolved_result = resolve_import_map_result(
2187 &result,
2188 lookup_path.clone(),
2189 lookup_path.clone(),
2190 request,
2191 options,
2192 request.query().owned().await?,
2193 )
2194 .await?;
2195 if let Some(resolved_result) = resolved_result {
2196 let resolved_result = resolved_result.into_cell_if_resolvable().await?;
2197 if let Some(result) = resolved_result {
2198 return Ok(result);
2199 }
2200 }
2201 }
2202 }
2203
2204 Ok(result)
2205 }
2206 .instrument(span)
2207 .await
2208}
2209
2210#[turbo_tasks::function]
2211async fn resolve_into_folder(
2212 package_path: FileSystemPath,
2213 options: Vc<ResolveOptions>,
2214) -> Result<Vc<ResolveResult>> {
2215 let options_value = options.await?;
2216
2217 let mut affecting_sources = vec![];
2218 if let Some(package_json_path) = exists(
2219 &package_path.join("package.json")?,
2220 if options_value.collect_affecting_sources {
2221 Some(&mut affecting_sources)
2222 } else {
2223 None
2224 },
2225 )
2226 .await?
2227 {
2228 for resolve_into_package in options_value.into_package.iter() {
2229 match resolve_into_package {
2230 ResolveIntoPackage::MainField { field: name } => {
2231 if let Some(package_json) =
2232 &*read_package_json(Vc::upcast(FileSource::new(package_json_path.clone())))
2233 .await?
2234 && let Some(field_value) = package_json[name.as_str()].as_str()
2235 {
2236 let normalized_request = RcStr::from(normalize_request(field_value));
2237 if normalized_request.is_empty()
2238 || &*normalized_request == "."
2239 || &*normalized_request == "./"
2240 {
2241 continue;
2242 }
2243 let request = Request::parse_string(normalized_request);
2244
2245 let options = if options_value.fully_specified {
2247 *options.with_fully_specified(false).to_resolved().await?
2248 } else {
2249 options
2250 };
2251 let result =
2252 &*resolve_internal_inline(package_path.clone(), request, options)
2253 .await?
2254 .await?;
2255 if !result.is_unresolvable() {
2258 let mut result: ResolveResultBuilder =
2259 result.with_request_ref(rcstr!(".")).into();
2260 if options_value.collect_affecting_sources {
2261 result.affecting_sources.push(ResolvedVc::upcast(
2262 FileSource::new(package_json_path).to_resolved().await?,
2263 ));
2264 result.affecting_sources.extend(affecting_sources);
2265 }
2266 return Ok(ResolveResult::from(result).cell());
2267 }
2268 };
2269 }
2270 ResolveIntoPackage::ExportsField { .. } => {}
2271 }
2272 }
2273 }
2274
2275 if options_value.fully_specified {
2276 return Ok(ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell());
2277 }
2278
2279 let pattern = match &options_value.default_files[..] {
2281 [] => {
2282 return Ok(
2283 ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell(),
2284 );
2285 }
2286 [file] => Pattern::Constant(format!("./{file}").into()),
2287 files => Pattern::Alternatives(
2288 files
2289 .iter()
2290 .map(|file| Pattern::Constant(format!("./{file}").into()))
2291 .collect(),
2292 ),
2293 };
2294
2295 let request = Request::parse(pattern);
2296 let result = resolve_internal_inline(package_path.clone(), request, options)
2297 .await?
2298 .with_request(rcstr!("."));
2299
2300 Ok(if !affecting_sources.is_empty() {
2301 result.with_affecting_sources(ResolvedVc::deref_vec(affecting_sources))
2302 } else {
2303 result
2304 })
2305}
2306
2307#[tracing::instrument(level = Level::TRACE, skip_all)]
2308async fn resolve_relative_request(
2309 lookup_path: FileSystemPath,
2310 request: Vc<Request>,
2311 options: Vc<ResolveOptions>,
2312 options_value: &ResolveOptions,
2313 path_pattern: &Pattern,
2314 query: RcStr,
2315 force_in_lookup_dir: bool,
2316 fragment: RcStr,
2317) -> Result<Vc<ResolveResult>> {
2318 debug_assert!(query.is_empty() || query.starts_with("?"));
2319 debug_assert!(fragment.is_empty() || fragment.starts_with("#"));
2320 let lookup_path_ref = lookup_path.clone();
2322 if let Some(result) = apply_in_package(
2323 lookup_path.clone(),
2324 options,
2325 options_value,
2326 |package_path| {
2327 let request = path_pattern.as_constant_string()?;
2328 let prefix_path = package_path.get_path_to(&lookup_path_ref)?;
2329 let request = normalize_request(&format!("./{prefix_path}/{request}"));
2330 Some(request.into())
2331 },
2332 query.clone(),
2333 fragment.clone(),
2334 )
2335 .await?
2336 {
2337 return Ok(result.into_cell());
2338 }
2339
2340 let mut new_path = path_pattern.clone();
2341
2342 #[derive(Eq, PartialEq, Clone, Hash, Debug)]
2345 enum RequestKeyTransform {
2346 None,
2348 AddedFragment,
2350 AddedExtension {
2353 ext: RcStr,
2355 next: Vec<RequestKeyTransform>,
2358 },
2359 ReplacedExtension {
2360 ext: RcStr,
2363 next: Vec<RequestKeyTransform>,
2366 },
2367 }
2368
2369 impl RequestKeyTransform {
2370 fn undo(
2373 &self,
2374 matched_pattern: &RcStr,
2375 fragment: &RcStr,
2376 pattern: &Pattern,
2377 ) -> impl Iterator<Item = (RcStr, RcStr)> {
2378 let mut result = SmallVec::new();
2379 self.apply_internal(matched_pattern, fragment, pattern, &mut result);
2380 result.into_iter()
2381 }
2382
2383 fn apply_internal(
2384 &self,
2385 matched_pattern: &RcStr,
2386 fragment: &RcStr,
2387 pattern: &Pattern,
2388 result: &mut SmallVec<[(RcStr, RcStr); 2]>,
2389 ) {
2390 match self {
2391 RequestKeyTransform::None => {
2392 if pattern.is_match(matched_pattern.as_str()) {
2393 result.push((matched_pattern.clone(), fragment.clone()));
2394 }
2395 }
2396 RequestKeyTransform::AddedFragment => {
2397 debug_assert!(
2398 !fragment.is_empty(),
2399 "can only have an AddedFragment modification if there was a fragment"
2400 );
2401 if let Some(stripped_pattern) = matched_pattern.strip_suffix(fragment.as_str())
2402 && pattern.is_match(stripped_pattern)
2403 {
2404 result.push((stripped_pattern.into(), RcStr::default()));
2405 }
2406 }
2407 RequestKeyTransform::AddedExtension { ext, next } => {
2408 if let Some(stripped_pattern) = matched_pattern.strip_suffix(ext.as_str()) {
2409 let stripped_pattern: RcStr = stripped_pattern.into();
2410 Self::apply_all(next, &stripped_pattern, fragment, pattern, result);
2411 }
2412 }
2413 RequestKeyTransform::ReplacedExtension { ext, next } => {
2414 if let Some(stripped_pattern) = matched_pattern.strip_suffix(ext.as_str()) {
2415 let replaced_pattern: RcStr = format!(
2416 "{stripped_pattern}{old_ext}",
2417 old_ext = TS_EXTENSION_REPLACEMENTS.reverse.get(ext).unwrap()
2418 )
2419 .into();
2420 Self::apply_all(next, &replaced_pattern, fragment, pattern, result);
2421 }
2422 }
2423 }
2424 }
2425
2426 fn apply_all(
2427 list: &[RequestKeyTransform],
2428 matched_pattern: &RcStr,
2429 fragment: &RcStr,
2430 pattern: &Pattern,
2431 result: &mut SmallVec<[(RcStr, RcStr); 2]>,
2432 ) {
2433 list.iter()
2434 .for_each(|pm| pm.apply_internal(matched_pattern, fragment, pattern, result));
2435 }
2436 }
2437
2438 let mut modifications = Vec::new();
2439 modifications.push(RequestKeyTransform::None);
2440
2441 if !fragment.is_empty() {
2445 modifications.push(RequestKeyTransform::AddedFragment);
2446 new_path.push(Pattern::Alternatives(vec![
2447 Pattern::Constant(RcStr::default()),
2448 Pattern::Constant(fragment.clone()),
2449 ]));
2450 }
2451
2452 if !options_value.fully_specified {
2453 modifications =
2455 modifications
2456 .iter()
2457 .cloned()
2458 .chain(options_value.extensions.iter().map(|ext| {
2459 RequestKeyTransform::AddedExtension {
2460 ext: ext.clone(),
2461 next: modifications.clone(),
2462 }
2463 }))
2464 .collect();
2465 new_path.push(Pattern::Alternatives(
2470 once(Pattern::Constant(RcStr::default()))
2471 .chain(
2472 options_value
2473 .extensions
2474 .iter()
2475 .map(|ext| Pattern::Constant(ext.clone())),
2476 )
2477 .collect(),
2478 ));
2479 new_path.normalize();
2480 };
2481
2482 struct ExtensionReplacements {
2483 forward: FxHashMap<RcStr, SmallVec<[RcStr; 3]>>,
2484 reverse: FxHashMap<RcStr, RcStr>,
2485 }
2486 static TS_EXTENSION_REPLACEMENTS: LazyLock<ExtensionReplacements> = LazyLock::new(|| {
2487 let mut forward = FxHashMap::default();
2488 forward.insert(
2489 rcstr!(".js"),
2490 SmallVec::from_vec(vec![rcstr!(".ts"), rcstr!(".tsx"), rcstr!(".js")]),
2491 );
2492
2493 forward.insert(
2494 rcstr!(".mjs"),
2495 SmallVec::from_vec(vec![rcstr!(".mts"), rcstr!(".mjs")]),
2496 );
2497
2498 forward.insert(
2499 rcstr!(".cjs"),
2500 SmallVec::from_vec(vec![rcstr!(".cts"), rcstr!(".cjs")]),
2501 );
2502 let reverse = forward
2503 .iter()
2504 .flat_map(|(k, v)| v.iter().map(|v: &RcStr| (v.clone(), k.clone())))
2505 .collect::<FxHashMap<_, _>>();
2506 ExtensionReplacements { forward, reverse }
2507 });
2508
2509 if options_value.enable_typescript_with_output_extension {
2510 let mut replaced_extensions = SmallVec::<[RcStr; 4]>::new();
2512 let replaced = new_path.replace_final_constants(&mut |c: &RcStr| -> Option<Pattern> {
2513 let (base, ext) = c.split_at(c.rfind('.')?);
2514
2515 let (ext, replacements) = TS_EXTENSION_REPLACEMENTS.forward.get_key_value(ext)?;
2516 for replacement in replacements {
2517 if replacement != ext && !replaced_extensions.contains(replacement) {
2518 replaced_extensions.push(replacement.clone());
2519 debug_assert!(replaced_extensions.len() <= replaced_extensions.inline_size());
2520 }
2521 }
2522
2523 let replacements = replacements
2524 .iter()
2525 .cloned()
2526 .map(Pattern::Constant)
2527 .collect();
2528
2529 if base.is_empty() {
2530 Some(Pattern::Alternatives(replacements))
2531 } else {
2532 Some(Pattern::Concatenation(vec![
2533 Pattern::Constant(base.into()),
2534 Pattern::Alternatives(replacements),
2535 ]))
2536 }
2537 });
2538 if replaced {
2539 modifications = modifications
2541 .iter()
2542 .cloned()
2543 .chain(replaced_extensions.iter().map(|ext| {
2544 RequestKeyTransform::ReplacedExtension {
2545 ext: ext.clone(),
2546 next: modifications.clone(),
2547 }
2548 }))
2549 .collect();
2550 new_path.normalize();
2551 }
2552 }
2553
2554 let matches = read_matches(
2555 lookup_path.clone(),
2556 rcstr!(""),
2557 force_in_lookup_dir,
2558 *Pattern::new(new_path.clone()).to_resolved().await?,
2559 )
2560 .await?;
2561
2562 let mut keys = FxHashSet::default();
2566 let results = matches
2567 .iter()
2568 .flat_map(|m| {
2569 if let PatternMatch::File(matched_pattern, path) = m {
2570 Either::Left(
2571 modifications
2572 .iter()
2573 .flat_map(|m| m.undo(matched_pattern, &fragment, path_pattern))
2574 .map(move |result| (result, path)),
2575 )
2576 } else {
2577 Either::Right(empty())
2578 }
2579 })
2580 .filter(move |((matched_pattern, _), _)| keys.insert(matched_pattern.clone()))
2582 .map(|((matched_pattern, fragment), path)| {
2583 resolved(
2584 RequestKey::new(matched_pattern),
2585 path.clone(),
2586 lookup_path.clone(),
2587 request,
2588 options_value,
2589 options,
2590 query.clone(),
2591 fragment,
2592 )
2593 })
2594 .try_join()
2595 .await?;
2596
2597 let mut results: Vec<Vc<ResolveResult>> = results.into_iter().map(|r| r.into_cell()).collect();
2599
2600 for m in matches.iter() {
2602 if let PatternMatch::Directory(matched_pattern, path) = m {
2603 results.push(
2604 resolve_into_folder(path.clone(), options).with_request(matched_pattern.clone()),
2605 );
2606 }
2607 }
2608
2609 Ok(merge_results(results))
2610}
2611
2612#[tracing::instrument(level = Level::TRACE, skip_all)]
2613async fn apply_in_package(
2614 lookup_path: FileSystemPath,
2615 options: Vc<ResolveOptions>,
2616 options_value: &ResolveOptions,
2617 get_request: impl Fn(&FileSystemPath) -> Option<RcStr>,
2618 query: RcStr,
2619 fragment: RcStr,
2620) -> Result<Option<ResolveResultOrCell>> {
2621 for in_package in options_value.in_package.iter() {
2623 let ResolveInPackage::AliasField(field) = in_package else {
2627 continue;
2628 };
2629
2630 let FindContextFileResult::Found(package_json_path, refs) = &*find_context_file(
2631 lookup_path.clone(),
2632 *package_json().to_resolved().await?,
2633 options_value.collect_affecting_sources,
2634 )
2635 .await?
2636 else {
2637 continue;
2638 };
2639
2640 let read =
2641 read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?;
2642 let Some(package_json) = &*read else {
2643 continue;
2644 };
2645
2646 let Some(field_value) = package_json[field.as_str()].as_object() else {
2647 continue;
2648 };
2649
2650 let package_path = package_json_path.parent();
2651
2652 let Some(request) = get_request(&package_path) else {
2653 continue;
2654 };
2655
2656 let value = if let Some(value) = field_value.get(&*request) {
2657 value
2658 } else if let Some(request) = request.strip_prefix("./") {
2659 let Some(value) = field_value.get(request) else {
2660 continue;
2661 };
2662 value
2663 } else {
2664 continue;
2665 };
2666
2667 let refs = refs.clone();
2668 let request_key = RequestKey::new(request.clone());
2669
2670 if value.as_bool() == Some(false) {
2671 return Ok(Some(ResolveResultOrCell::Value(
2672 ResolveResult::primary_with_affecting_sources(
2673 request_key,
2674 ResolveResultItem::Ignore,
2675 refs,
2676 ),
2677 )));
2678 }
2679
2680 if let Some(value) = value.as_str() {
2681 if value == &*request {
2682 return Ok(None);
2684 }
2685 let mut result = resolve_internal(
2686 package_path,
2687 Request::parse(Pattern::Constant(value.into()))
2688 .with_query(query.clone())
2689 .with_fragment(fragment.clone()),
2690 options,
2691 )
2692 .with_replaced_request_key(value.into(), request_key);
2693 if options_value.collect_affecting_sources && !refs.is_empty() {
2694 result = result.with_affecting_sources(refs.into_iter().map(|src| *src).collect());
2695 }
2696 return Ok(Some(ResolveResultOrCell::Cell(result)));
2697 }
2698
2699 ResolvingIssue {
2700 severity: resolve_error_severity(options).await?,
2701 file_path: package_json_path.clone(),
2702 request_type: format!("alias field ({field})"),
2703 request: Request::parse(Pattern::Constant(request))
2704 .to_resolved()
2705 .await?,
2706 resolve_options: options.to_resolved().await?,
2707 error_message: Some(format!("invalid alias field value: {value}")),
2708 source: None,
2709 }
2710 .resolved_cell()
2711 .emit();
2712
2713 return Ok(Some(ResolveResultOrCell::Value(
2714 ResolveResult::unresolvable_with_affecting_sources(refs),
2715 )));
2716 }
2717 Ok(None)
2718}
2719
2720#[turbo_tasks::value]
2721enum FindSelfReferencePackageResult {
2722 Found {
2723 name: String,
2724 package_path: FileSystemPath,
2725 },
2726 NotFound,
2727}
2728
2729#[turbo_tasks::function]
2730async fn find_self_reference(
2733 lookup_path: FileSystemPath,
2734) -> Result<Vc<FindSelfReferencePackageResult>> {
2735 let package_json_context =
2736 find_context_file(lookup_path, *package_json().to_resolved().await?, false).await?;
2737 if let FindContextFileResult::Found(package_json_path, _refs) = &*package_json_context {
2738 let read =
2739 read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?;
2740 if let Some(json) = &*read
2741 && json.get("exports").is_some()
2742 && let Some(name) = json["name"].as_str()
2743 {
2744 return Ok(FindSelfReferencePackageResult::Found {
2745 name: name.to_string(),
2746 package_path: package_json_path.parent(),
2747 }
2748 .cell());
2749 }
2750 }
2751 Ok(FindSelfReferencePackageResult::NotFound.cell())
2752}
2753
2754#[tracing::instrument(level = Level::TRACE, skip_all)]
2755async fn resolve_module_request(
2756 lookup_path: FileSystemPath,
2757 request: Vc<Request>,
2758 options: Vc<ResolveOptions>,
2759 options_value: &ResolveOptions,
2760 module: &Pattern,
2761 path: &Pattern,
2762 query: RcStr,
2763 fragment: RcStr,
2764) -> Result<Vc<ResolveResult>> {
2765 if let Some(result) = apply_in_package(
2767 lookup_path.clone(),
2768 options,
2769 options_value,
2770 |_| {
2771 let full_pattern = Pattern::concat([module.clone(), path.clone()]);
2772 full_pattern.as_constant_string().cloned()
2773 },
2774 query.clone(),
2775 fragment.clone(),
2776 )
2777 .await?
2778 {
2779 return Ok(result.into_cell());
2780 }
2781
2782 let mut results = vec![];
2783
2784 if let FindSelfReferencePackageResult::Found { name, package_path } =
2788 &*find_self_reference(lookup_path.clone()).await?
2789 && module.is_match(name)
2790 {
2791 let result = resolve_into_package(
2792 path.clone(),
2793 package_path.clone(),
2794 query.clone(),
2795 fragment.clone(),
2796 options,
2797 );
2798 if !result.await?.is_unresolvable() {
2799 return Ok(result);
2800 }
2801 }
2802
2803 let result = find_package(
2804 lookup_path.clone(),
2805 module.clone(),
2806 *resolve_modules_options(options).to_resolved().await?,
2807 options_value.collect_affecting_sources,
2808 )
2809 .await?;
2810
2811 if result.packages.is_empty() {
2812 return Ok(ResolveResult::unresolvable_with_affecting_sources(
2813 result.affecting_sources.clone(),
2814 )
2815 .cell());
2816 }
2817
2818 for item in &result.packages {
2824 match item {
2825 FindPackageItem::PackageDirectory { name, dir } => {
2826 results.push(
2827 resolve_into_package(
2828 path.clone(),
2829 dir.clone(),
2830 query.clone(),
2831 fragment.clone(),
2832 options,
2833 )
2834 .with_replaced_request_key(rcstr!("."), RequestKey::new(name.clone())),
2835 );
2836 }
2837 FindPackageItem::PackageFile { name, file } => {
2838 if path.is_match("") {
2839 let resolved_result = resolved(
2840 RequestKey::new(rcstr!(".")),
2841 file.clone(),
2842 lookup_path.clone(),
2843 request,
2844 options_value,
2845 options,
2846 query.clone(),
2847 fragment.clone(),
2848 )
2849 .await?
2850 .into_cell()
2851 .with_replaced_request_key(rcstr!("."), RequestKey::new(name.clone()));
2852 results.push(resolved_result)
2853 }
2854 }
2855 }
2856 }
2857
2858 let module_result =
2859 merge_results_with_affecting_sources(results, result.affecting_sources.clone());
2860
2861 if options_value.prefer_relative {
2862 let mut module_prefixed = module.clone();
2863 module_prefixed.push_front(rcstr!("./").into());
2864 let pattern = Pattern::concat([module_prefixed.clone(), rcstr!("/").into(), path.clone()]);
2865 let relative = Request::relative(pattern, query, fragment, true)
2866 .to_resolved()
2867 .await?;
2868 let relative_result = Box::pin(resolve_internal_inline(
2869 lookup_path.clone(),
2870 *relative,
2871 options,
2872 ))
2873 .await?;
2874 let relative_result = relative_result.with_stripped_request_key_prefix(rcstr!("./"));
2875
2876 Ok(merge_results(vec![relative_result, module_result]))
2877 } else {
2878 Ok(module_result)
2879 }
2880}
2881
2882#[turbo_tasks::function]
2883async fn resolve_into_package(
2884 path: Pattern,
2885 package_path: FileSystemPath,
2886 query: RcStr,
2887 fragment: RcStr,
2888 options: ResolvedVc<ResolveOptions>,
2889) -> Result<Vc<ResolveResult>> {
2890 let options_value = options.await?;
2891 let mut results = Vec::new();
2892
2893 let is_root_match = path.is_match("") || path.is_match("/");
2894 let could_match_others = path.could_match_others("");
2895
2896 let mut export_path_request = path.clone();
2897 export_path_request.push_front(rcstr!(".").into());
2898 for resolve_into_package in options_value.into_package.iter() {
2899 match resolve_into_package {
2900 ResolveIntoPackage::MainField { .. } => {}
2902 ResolveIntoPackage::ExportsField {
2903 conditions,
2904 unspecified_conditions,
2905 } => {
2906 let package_json_path = package_path.join("package.json")?;
2907 let ExportsFieldResult::Some(exports_field) =
2908 &*exports_field(Vc::upcast(FileSource::new(package_json_path.clone()))).await?
2909 else {
2910 continue;
2911 };
2912
2913 results.push(
2914 handle_exports_imports_field(
2915 package_path.clone(),
2916 package_json_path,
2917 *options,
2918 exports_field,
2919 export_path_request.clone(),
2920 conditions,
2921 unspecified_conditions,
2922 query,
2923 ExportImport::Export,
2924 )
2925 .await?,
2926 );
2927
2928 return Ok(merge_results(results));
2931 }
2932 }
2933 }
2934
2935 if is_root_match {
2937 results.push(resolve_into_folder(
2938 package_path.clone(),
2939 options.with_fully_specified(false),
2940 ));
2941 }
2942
2943 if could_match_others {
2944 let mut new_pat = path.clone();
2945 new_pat.push_front(rcstr!(".").into());
2946
2947 let relative = Request::relative(new_pat, query, fragment, true)
2948 .to_resolved()
2949 .await?;
2950 results.push(resolve_internal_inline(package_path.clone(), *relative, *options).await?);
2951 }
2952
2953 Ok(merge_results(results))
2954}
2955
2956#[tracing::instrument(level = Level::TRACE, skip_all)]
2957async fn resolve_import_map_result(
2958 result: &ImportMapResult,
2959 lookup_path: FileSystemPath,
2960 original_lookup_path: FileSystemPath,
2961 original_request: Vc<Request>,
2962 options: Vc<ResolveOptions>,
2963 query: RcStr,
2964) -> Result<Option<ResolveResultOrCell>> {
2965 Ok(match result {
2966 ImportMapResult::Result(result) => Some(ResolveResultOrCell::Cell(**result)),
2967 ImportMapResult::Alias(request, alias_lookup_path) => {
2968 let request_vc: Vc<Request> = **request;
2969 let request = if request_vc.query().await?.is_empty() && !query.is_empty() {
2971 request_vc.with_query(query.clone())
2972 } else {
2973 request_vc
2974 };
2975 let lookup_path = alias_lookup_path.clone().unwrap_or(lookup_path);
2976
2977 let request_pattern = request.request_pattern();
2979 let original_pattern = original_request.request_pattern();
2980
2981 if *request_pattern.await? == *original_pattern.await?
2982 && lookup_path == original_lookup_path
2983 {
2984 None
2985 } else {
2986 Some(ResolveResultOrCell::Cell(
2987 resolve_internal(lookup_path, request, options)
2988 .with_replaced_request_key_pattern(request_pattern, original_pattern),
2989 ))
2990 }
2991 }
2992 ImportMapResult::External {
2993 name,
2994 ty,
2995 traced,
2996 target,
2997 } => Some(ResolveResultOrCell::Value(ResolveResult::primary(
2998 ResolveResultItem::External {
2999 name: name.clone(),
3000 ty: *ty,
3001 traced: *traced,
3002 target: target.clone(),
3003 },
3004 ))),
3005 ImportMapResult::AliasExternal {
3006 name,
3007 ty,
3008 traced,
3009 lookup_dir: alias_lookup_path,
3010 } => {
3011 let request = Request::parse_string(name.clone());
3012
3013 if *request.to_resolved().await? == original_request
3015 && *alias_lookup_path == original_lookup_path
3016 {
3017 None
3018 } else {
3019 let is_external_resolvable = !resolve_internal(
3020 alias_lookup_path.clone(),
3021 request,
3022 match ty {
3023 ExternalType::CommonJs => {
3025 node_cjs_resolve_options(alias_lookup_path.root().owned().await?)
3026 }
3027 ExternalType::EcmaScriptModule => {
3028 node_esm_resolve_options(alias_lookup_path.root().owned().await?)
3029 }
3030 ExternalType::Script | ExternalType::Url | ExternalType::Global => options,
3031 },
3032 )
3033 .await?
3034 .is_unresolvable();
3035 if is_external_resolvable {
3036 Some(ResolveResultOrCell::Value(ResolveResult::primary(
3037 ResolveResultItem::External {
3038 name: name.clone(),
3039 ty: *ty,
3040 traced: *traced,
3041 target: None,
3042 },
3043 )))
3044 } else {
3045 None
3046 }
3047 }
3048 }
3049 ImportMapResult::Alternatives(list) => {
3050 let results = list
3051 .iter()
3052 .map(|result| {
3053 resolve_import_map_result(
3054 result,
3055 lookup_path.clone(),
3056 original_lookup_path.clone(),
3057 original_request,
3058 options,
3059 query.clone(),
3060 )
3061 })
3062 .try_join()
3063 .await?;
3064
3065 let cells: Vec<Vc<ResolveResult>> = results
3067 .into_iter()
3068 .flatten()
3069 .map(|r| r.into_cell())
3070 .collect();
3071 Some(ResolveResultOrCell::Cell(merge_results(cells)))
3072 }
3073 ImportMapResult::NoEntry => None,
3074 ImportMapResult::Error(issue) => Some(ResolveResultOrCell::Value(ResolveResult::primary(
3075 ResolveResultItem::Error(*issue),
3076 ))),
3077 })
3078}
3079
3080enum ResolveResultOrCell {
3083 Cell(Vc<ResolveResult>),
3084 Value(ResolveResult),
3085}
3086
3087impl ResolveResultOrCell {
3088 fn into_cell(self) -> Vc<ResolveResult> {
3089 match self {
3090 ResolveResultOrCell::Cell(vc) => vc,
3091 ResolveResultOrCell::Value(value) => value.cell(),
3092 }
3093 }
3094
3095 async fn into_cell_if_resolvable(self) -> Result<Option<Vc<ResolveResult>>> {
3096 match self {
3097 ResolveResultOrCell::Cell(resolved_result) => {
3098 if !resolved_result.await?.is_unresolvable() {
3099 return Ok(Some(resolved_result));
3100 }
3101 }
3102 ResolveResultOrCell::Value(resolve_result) => {
3103 if !resolve_result.is_unresolvable() {
3104 return Ok(Some(resolve_result.cell()));
3105 }
3106 }
3107 }
3108 Ok(None)
3109 }
3110}
3111
3112#[tracing::instrument(level = Level::TRACE, skip_all)]
3113async fn resolved(
3114 request_key: RequestKey,
3115 fs_path: FileSystemPath,
3116 original_context: FileSystemPath,
3117 original_request: Vc<Request>,
3118 options_value: &ResolveOptions,
3119 options: Vc<ResolveOptions>,
3120 query: RcStr,
3121 fragment: RcStr,
3122) -> Result<ResolveResultOrCell> {
3123 let result = &*fs_path.realpath_with_links().await?;
3124 let path = match &result.path_result {
3125 Ok(path) => path,
3126 Err(e) => bail!(e.as_error_message(&fs_path, result).await?),
3127 };
3128
3129 let path_ref = path.clone();
3130 if let Some(result) = apply_in_package(
3132 path.parent(),
3133 options,
3134 options_value,
3135 |package_path| package_path.get_relative_path_to(&path_ref),
3136 query.clone(),
3137 fragment.clone(),
3138 )
3139 .await?
3140 {
3141 return Ok(result);
3142 }
3143
3144 if let Some(resolved_map) = options_value.resolved_map {
3145 let result = resolved_map
3146 .lookup(path.clone(), original_context.clone(), original_request)
3147 .await?;
3148
3149 let resolved_result = resolve_import_map_result(
3150 &result,
3151 path.parent(),
3152 original_context.clone(),
3153 original_request,
3154 options,
3155 query.clone(),
3156 )
3157 .await?;
3158
3159 if let Some(result) = resolved_result {
3160 return Ok(result);
3161 }
3162 }
3163 let source = ResolvedVc::upcast(
3164 FileSource::new_with_query_and_fragment(path.clone(), query, fragment)
3165 .to_resolved()
3166 .await?,
3167 );
3168 Ok(ResolveResultOrCell::Value(
3169 if options_value.collect_affecting_sources {
3170 ResolveResult::source_with_affecting_sources(
3171 request_key,
3172 source,
3173 result
3174 .symlinks
3175 .iter()
3176 .map(|symlink| async move {
3177 anyhow::Ok(ResolvedVc::upcast(
3178 FileSource::new(symlink.clone()).to_resolved().await?,
3179 ))
3180 })
3181 .try_join()
3182 .await?,
3183 )
3184 } else {
3185 ResolveResult::source_with_key(request_key, source)
3186 },
3187 ))
3188}
3189
3190async fn handle_exports_imports_field(
3191 package_path: FileSystemPath,
3192 package_json_path: FileSystemPath,
3193 options: Vc<ResolveOptions>,
3194 exports_imports_field: &AliasMap<SubpathValue>,
3195 mut path: Pattern,
3196 conditions: &BTreeMap<RcStr, ConditionValue>,
3197 unspecified_conditions: &ConditionValue,
3198 query: RcStr,
3199 ty: ExportImport,
3200) -> Result<Vc<ResolveResult>> {
3201 let mut results = Vec::new();
3202 let mut conditions_state = FxHashMap::default();
3203
3204 if !query.is_empty() {
3205 path.push(query.into());
3206 }
3207 let req = path;
3208
3209 let values = exports_imports_field.lookup(&req);
3210 for value in values {
3211 let value = value?;
3212 if value.output.add_results(
3213 value.prefix,
3214 value.key,
3215 conditions,
3216 unspecified_conditions,
3217 &mut conditions_state,
3218 &mut results,
3219 ) {
3220 break;
3222 }
3223 }
3224
3225 let mut resolved_results = Vec::new();
3226 for ReplacedSubpathValueResult {
3227 result_path,
3228 conditions,
3229 map_prefix,
3230 map_key,
3231 } in results
3232 {
3233 let request = match ty {
3234 ExportImport::Export => {
3235 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("./")), result_path.clone()])
3237 }
3238 ExportImport::Import => result_path.clone(),
3239 };
3240 let request = *Request::parse(request).to_resolved().await?;
3241
3242 let resolve_result = Box::pin(resolve_internal_inline(
3243 package_path.clone(),
3244 request,
3245 options,
3246 ))
3247 .await?;
3248
3249 let resolve_result = if let Some(req) = req.as_constant_string() {
3250 resolve_result.with_request(req.clone())
3251 } else {
3252 match map_key {
3253 AliasKey::Exact => resolve_result.with_request(map_prefix.clone().into()),
3254 AliasKey::Wildcard { .. } => {
3255 let mut old_request_key = result_path;
3265 if matches!(ty, ExportImport::Export) {
3266 old_request_key.push_front(rcstr!("./").into());
3268 }
3269 let new_request_key = req.clone();
3270
3271 resolve_result.with_replaced_request_key_pattern(
3272 Pattern::new(old_request_key),
3273 Pattern::new(new_request_key),
3274 )
3275 }
3276 }
3277 };
3278
3279 let resolve_result = if !conditions.is_empty() {
3280 let resolve_result = resolve_result.await?.with_conditions(&conditions);
3281 resolve_result.cell()
3282 } else {
3283 resolve_result
3284 };
3285 resolved_results.push(resolve_result);
3286 }
3287
3288 Ok(merge_results_with_affecting_sources(
3290 resolved_results,
3291 vec![ResolvedVc::upcast(
3292 FileSource::new(package_json_path).to_resolved().await?,
3293 )],
3294 ))
3295}
3296
3297async fn resolve_package_internal_with_imports_field(
3302 file_path: FileSystemPath,
3303 request: Vc<Request>,
3304 resolve_options: Vc<ResolveOptions>,
3305 pattern: &Pattern,
3306 conditions: &BTreeMap<RcStr, ConditionValue>,
3307 unspecified_conditions: &ConditionValue,
3308) -> Result<Vc<ResolveResult>> {
3309 let Pattern::Constant(specifier) = pattern else {
3310 bail!("PackageInternal requests can only be Constant strings");
3311 };
3312 if specifier == "#" || specifier.starts_with("#/") || specifier.ends_with('/') {
3314 ResolvingIssue {
3315 severity: resolve_error_severity(resolve_options).await?,
3316 file_path: file_path.clone(),
3317 request_type: format!("package imports request: `{specifier}`"),
3318 request: request.to_resolved().await?,
3319 resolve_options: resolve_options.to_resolved().await?,
3320 error_message: None,
3321 source: None,
3322 }
3323 .resolved_cell()
3324 .emit();
3325 return Ok(ResolveResult::unresolvable().cell());
3326 }
3327
3328 let imports_result = imports_field(file_path).await?;
3329 let (imports, package_json_path) = match &*imports_result {
3330 ImportsFieldResult::Some(i, p) => (i, p.clone()),
3331 ImportsFieldResult::None => return Ok(ResolveResult::unresolvable().cell()),
3332 };
3333
3334 handle_exports_imports_field(
3335 package_json_path.parent(),
3336 package_json_path.clone(),
3337 resolve_options,
3338 imports,
3339 Pattern::Constant(specifier.clone()),
3340 conditions,
3341 unspecified_conditions,
3342 RcStr::default(),
3343 ExportImport::Import,
3344 )
3345 .await
3346}
3347
3348#[turbo_tasks::task_input]
3352#[derive(
3353 Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, Encode, Decode,
3354)]
3355pub enum ModulePart {
3356 Evaluation,
3359 Export(RcStr),
3361 RenamedExport {
3363 original_export: RcStr,
3364 export: RcStr,
3365 },
3366 RenamedNamespace { export: RcStr },
3368 Internal(u32),
3370 Locals,
3372 Exports,
3374 Facade,
3377}
3378
3379impl ModulePart {
3380 pub fn evaluation() -> Self {
3381 ModulePart::Evaluation
3382 }
3383
3384 pub fn export(export: RcStr) -> Self {
3385 ModulePart::Export(export)
3386 }
3387
3388 pub fn renamed_export(original_export: RcStr, export: RcStr) -> Self {
3389 ModulePart::RenamedExport {
3390 original_export,
3391 export,
3392 }
3393 }
3394
3395 pub fn renamed_namespace(export: RcStr) -> Self {
3396 ModulePart::RenamedNamespace { export }
3397 }
3398
3399 pub fn internal(id: u32) -> Self {
3400 ModulePart::Internal(id)
3401 }
3402
3403 pub fn locals() -> Self {
3404 ModulePart::Locals
3405 }
3406
3407 pub fn exports() -> Self {
3408 ModulePart::Exports
3409 }
3410
3411 pub fn facade() -> Self {
3412 ModulePart::Facade
3413 }
3414}
3415
3416impl Display for ModulePart {
3417 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3418 match self {
3419 ModulePart::Evaluation => f.write_str("module evaluation"),
3420 ModulePart::Export(export) => write!(f, "export {export}"),
3421 ModulePart::RenamedExport {
3422 original_export,
3423 export,
3424 } => write!(f, "export {original_export} as {export}"),
3425 ModulePart::RenamedNamespace { export } => {
3426 write!(f, "export * as {export}")
3427 }
3428 ModulePart::Internal(id) => write!(f, "internal part {id}"),
3429 ModulePart::Locals => f.write_str("locals"),
3430 ModulePart::Exports => f.write_str("exports"),
3431 ModulePart::Facade => f.write_str("facade"),
3432 }
3433 }
3434}
3435#[cfg(test)]
3436mod tests {
3437 use std::{
3438 fs::{File, create_dir_all},
3439 io::Write,
3440 };
3441
3442 use anyhow::Result;
3443 use turbo_rcstr::{RcStr, rcstr};
3444 use turbo_tasks::{TryJoinIterExt, Vc};
3445 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
3446 use turbo_tasks_fs::{DiskFileSystem, FileContent, FileSystem, FileSystemPath};
3447
3448 use crate::{
3449 asset::AssetContent,
3450 module::Module,
3451 raw_module::RawModule,
3452 resolve::{
3453 ModuleResolveResult, ModuleResolveResultBuilder, ModuleResolveResultItem, RequestKey,
3454 ResolveResult, ResolveResultItem, node::node_esm_resolve_options, parse::Request,
3455 pattern::Pattern,
3456 },
3457 source::Source,
3458 virtual_source::VirtualSource,
3459 };
3460
3461 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3462 async fn test_explicit_js_resolves_to_ts() {
3463 resolve_relative_request_test(TestParams {
3464 files: vec!["foo.js", "foo.ts"],
3465 pattern: rcstr!("./foo.js").into(),
3466 enable_typescript_with_output_extension: true,
3467 fully_specified: false,
3468 custom_extensions: None,
3469 expected: vec![("./foo.js", "foo.ts")],
3470 })
3471 .await;
3472 }
3473
3474 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3475 async fn test_implicit_request_ts_priority() {
3476 resolve_relative_request_test(TestParams {
3477 files: vec!["foo.js", "foo.ts"],
3478 pattern: rcstr!("./foo").into(),
3479 enable_typescript_with_output_extension: true,
3480 fully_specified: false,
3481 custom_extensions: None,
3482 expected: vec![("./foo", "foo.ts")],
3483 })
3484 .await;
3485 }
3486
3487 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3488 async fn test_ts_priority_over_json() {
3489 resolve_relative_request_test(TestParams {
3490 files: vec!["posts.json", "posts.ts"],
3491 pattern: rcstr!("./posts").into(),
3492 enable_typescript_with_output_extension: true,
3493 fully_specified: false,
3494 custom_extensions: None,
3495 expected: vec![("./posts", "posts.ts")],
3496 })
3497 .await;
3498 }
3499
3500 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3501 async fn test_only_js_file_no_ts() {
3502 resolve_relative_request_test(TestParams {
3503 files: vec!["bar.js"],
3504 pattern: rcstr!("./bar.js").into(),
3505 enable_typescript_with_output_extension: true,
3506 fully_specified: false,
3507 custom_extensions: None,
3508 expected: vec![("./bar.js", "bar.js")],
3509 })
3510 .await;
3511 }
3512
3513 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3514 async fn test_explicit_ts_request() {
3515 resolve_relative_request_test(TestParams {
3516 files: vec!["foo.js", "foo.ts"],
3517 pattern: rcstr!("./foo.ts").into(),
3518 enable_typescript_with_output_extension: true,
3519 fully_specified: false,
3520 custom_extensions: None,
3521 expected: vec![("./foo.ts", "foo.ts")],
3522 })
3523 .await;
3524 }
3525
3526 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3528 async fn test_fragment() {
3529 resolve_relative_request_test(TestParams {
3530 files: vec!["client.ts"],
3531 pattern: rcstr!("./client#frag").into(),
3532 enable_typescript_with_output_extension: true,
3533 fully_specified: false,
3534 custom_extensions: None,
3535 expected: vec![("./client", "client.ts")],
3536 })
3537 .await;
3538 }
3539
3540 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3541 async fn test_fragment_as_part_of_filename() {
3542 resolve_relative_request_test(TestParams {
3544 files: vec!["client#component.js", "client#component.ts"],
3545 pattern: rcstr!("./client#component.js").into(),
3546 enable_typescript_with_output_extension: true,
3547 fully_specified: false,
3548 custom_extensions: None,
3549 expected: vec![("./client", "client#component.ts")],
3552 })
3553 .await;
3554 }
3555
3556 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3557 async fn test_fragment_with_ts_priority() {
3558 resolve_relative_request_test(TestParams {
3560 files: vec!["page#section.js", "page#section.ts"],
3561 pattern: rcstr!("./page#section").into(),
3562 enable_typescript_with_output_extension: true,
3563 fully_specified: false,
3564 custom_extensions: None,
3565 expected: vec![("./page", "page#section.ts")],
3566 })
3567 .await;
3568 }
3569
3570 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3571 async fn test_query() {
3572 resolve_relative_request_test(TestParams {
3573 files: vec!["client.ts", "client.js"],
3574 pattern: rcstr!("./client?q=s").into(),
3575 enable_typescript_with_output_extension: true,
3576 fully_specified: false,
3577 custom_extensions: None,
3578 expected: vec![("./client", "client.ts")],
3579 })
3580 .await;
3581 }
3582
3583 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3585 async fn test_dynamic_pattern_with_js_extension() {
3586 resolve_relative_request_test(TestParams {
3590 files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3591 pattern: Pattern::Concatenation(vec![
3592 Pattern::Constant(rcstr!("./src/")),
3593 Pattern::Dynamic,
3594 Pattern::Constant(rcstr!(".js")),
3595 ]),
3596 enable_typescript_with_output_extension: true,
3597 fully_specified: false,
3598 custom_extensions: None,
3599 expected: vec![
3600 ("./src/foo.js", "src/foo.ts"),
3601 ("./src/bar.js", "src/bar.js"),
3602 ],
3603 })
3604 .await;
3605 }
3606
3607 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3608 async fn test_dynamic_pattern_without_extension() {
3609 resolve_relative_request_test(TestParams {
3613 files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3614 pattern: Pattern::Concatenation(vec![
3615 Pattern::Constant(rcstr!("./src/")),
3616 Pattern::Dynamic,
3617 ]),
3618 enable_typescript_with_output_extension: true,
3619 fully_specified: false,
3620 custom_extensions: None,
3621 expected: vec![
3622 ("./src/bar.js", "src/bar.js"),
3623 ("./src/bar", "src/bar.js"),
3624 ("./src/foo.js", "src/foo.js"),
3630 ("./src/foo", "src/foo.js"),
3631 ("./src/foo.ts", "src/foo.ts"),
3632 ],
3633 })
3634 .await;
3635 }
3636
3637 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3640 async fn test_custom_extensions_web_before_default() {
3641 resolve_relative_request_test(TestParams {
3642 files: vec!["Component.web.tsx", "Component.tsx"],
3643 pattern: rcstr!("./Component").into(),
3644 enable_typescript_with_output_extension: false,
3645 fully_specified: false,
3646 custom_extensions: Some(vec![
3647 rcstr!(".web.tsx"),
3648 rcstr!(".web.ts"),
3649 rcstr!(".web.jsx"),
3650 rcstr!(".web.js"),
3651 rcstr!(".tsx"),
3652 rcstr!(".ts"),
3653 rcstr!(".jsx"),
3654 rcstr!(".js"),
3655 ]),
3656 expected: vec![("./Component", "Component.web.tsx")],
3657 })
3658 .await;
3659 }
3660
3661 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3663 async fn test_custom_extensions_fallback_when_web_missing() {
3664 resolve_relative_request_test(TestParams {
3665 files: vec!["Component.tsx"],
3666 pattern: rcstr!("./Component").into(),
3667 enable_typescript_with_output_extension: false,
3668 fully_specified: false,
3669 custom_extensions: Some(vec![
3670 rcstr!(".web.tsx"),
3671 rcstr!(".web.ts"),
3672 rcstr!(".web.jsx"),
3673 rcstr!(".web.js"),
3674 rcstr!(".tsx"),
3675 rcstr!(".ts"),
3676 rcstr!(".jsx"),
3677 rcstr!(".js"),
3678 ]),
3679 expected: vec![("./Component", "Component.tsx")],
3680 })
3681 .await;
3682 }
3683
3684 struct TestParams<'a> {
3686 files: Vec<&'a str>,
3687 pattern: Pattern,
3688 enable_typescript_with_output_extension: bool,
3689 fully_specified: bool,
3690 custom_extensions: Option<Vec<RcStr>>,
3692 expected: Vec<(&'a str, &'a str)>,
3693 }
3694
3695 async fn resolve_relative_request_test(
3697 TestParams {
3698 files,
3699 pattern,
3700 enable_typescript_with_output_extension,
3701 fully_specified,
3702 custom_extensions,
3703 expected,
3704 }: TestParams<'_>,
3705 ) {
3706 let scratch = tempfile::tempdir().unwrap();
3707 {
3708 let path = scratch.path();
3709
3710 for file_name in &files {
3711 let file_path = path.join(file_name);
3712 if let Some(parent) = file_path.parent() {
3713 create_dir_all(parent).unwrap();
3714 }
3715 File::create_new(&file_path)
3716 .unwrap()
3717 .write_all(format!("export default '{file_name}'").as_bytes())
3718 .unwrap();
3719 }
3720 }
3721
3722 let path: RcStr = scratch.path().to_str().unwrap().into();
3723 let expected_owned: Vec<(String, String)> = expected
3724 .iter()
3725 .map(|(k, v)| (k.to_string(), v.to_string()))
3726 .collect();
3727
3728 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3729 BackendOptions::default(),
3730 noop_backing_storage(),
3731 ));
3732
3733 let custom_extensions_owned = custom_extensions;
3734
3735 tt.run_once(async move {
3736 #[turbo_tasks::value(transparent)]
3737 struct ResolveRelativeRequestOutput(Vec<(String, String)>);
3738
3739 #[turbo_tasks::function(operation, root)]
3740 async fn resolve_relative_request_operation(
3741 path: RcStr,
3742 pattern: Pattern,
3743 enable_typescript_with_output_extension: bool,
3744 fully_specified: bool,
3745 custom_extensions: Option<Vec<RcStr>>,
3746 ) -> Result<Vc<ResolveRelativeRequestOutput>> {
3747 let fs = DiskFileSystem::new(rcstr!("temp"), Vc::cell(path));
3748 let lookup_path = fs.root().owned().await?;
3749
3750 let result = resolve_relative_helper(
3751 lookup_path,
3752 pattern,
3753 enable_typescript_with_output_extension,
3754 fully_specified,
3755 custom_extensions,
3756 )
3757 .await?;
3758
3759 let results: Vec<(String, String)> = result
3760 .primary
3761 .iter()
3762 .map(async |(k, v)| {
3763 Ok((
3764 k.to_string(),
3765 if let ResolveResultItem::Source(source) = v {
3766 source.ident().await?.path.path.to_string()
3767 } else {
3768 unreachable!()
3769 },
3770 ))
3771 })
3772 .try_join()
3773 .await?;
3774
3775 Ok(Vc::cell(results))
3776 }
3777
3778 let results = resolve_relative_request_operation(
3779 path,
3780 pattern,
3781 enable_typescript_with_output_extension,
3782 fully_specified,
3783 custom_extensions_owned,
3784 )
3785 .read_strongly_consistent()
3786 .await?;
3787
3788 assert_eq!(&*results, &expected_owned);
3789
3790 Ok(())
3791 })
3792 .await
3793 .unwrap();
3794 }
3795
3796 #[turbo_tasks::function]
3797 async fn resolve_relative_helper(
3798 lookup_path: FileSystemPath,
3799 pattern: Pattern,
3800 enable_typescript_with_output_extension: bool,
3801 fully_specified: bool,
3802 custom_extensions: Option<Vec<RcStr>>,
3803 ) -> Result<Vc<ResolveResult>> {
3804 let request = Request::parse(pattern.clone());
3805
3806 let extensions = custom_extensions
3807 .unwrap_or_else(|| vec![rcstr!(".ts"), rcstr!(".js"), rcstr!(".json")]);
3808 let mut options_value = node_esm_resolve_options(lookup_path.clone())
3809 .with_fully_specified(fully_specified)
3810 .with_extensions(extensions)
3811 .owned()
3812 .await?;
3813 options_value.enable_typescript_with_output_extension =
3814 enable_typescript_with_output_extension;
3815 let options = options_value.clone().cell();
3816 match &*request.await? {
3817 Request::Relative {
3818 path,
3819 query,
3820 force_in_lookup_dir,
3821 fragment,
3822 } => {
3823 super::resolve_relative_request(
3824 lookup_path,
3825 request,
3826 options,
3827 &options_value,
3828 path,
3829 query.clone(),
3830 *force_in_lookup_dir,
3831 fragment.clone(),
3832 )
3833 .await
3834 }
3835 r => panic!("request should be relative, got {r:?}"),
3836 }
3837 }
3838
3839 #[turbo_tasks::value(transparent)]
3846 pub struct DupCheckResult(Vec<String>);
3847
3848 async fn snapshot_primary(result: &ModuleResolveResult) -> Result<Vec<String>> {
3849 let mut out = Vec::with_capacity(result.primary.len());
3850 for (_, item) in result.primary.iter() {
3851 out.push(match *item {
3852 ModuleResolveResultItem::Module(m) => {
3853 let ident = m.ident().await?;
3854 format!("module:{}", ident.path.path)
3855 }
3856 ModuleResolveResultItem::Duplicate(i) => format!("dup:{i}"),
3857 _ => "other".to_string(),
3858 });
3859 }
3860 Ok(out)
3861 }
3862
3863 #[turbo_tasks::function]
3864 fn fs() -> Vc<Box<dyn FileSystem>> {
3865 Vc::upcast(DiskFileSystem::new(rcstr!("temp"), Vc::cell(fs_path())))
3866 }
3867
3868 #[turbo_tasks::function]
3869 async fn make_module(name: RcStr) -> Result<Vc<Box<dyn Module>>> {
3870 let path = fs().root().await?.join(&name)?;
3871 let file_content =
3872 FileContent::Content(turbo_tasks_fs::File::from(format!("// {name}"))).resolved_cell();
3873 let content = AssetContent::file(*file_content).to_resolved().await?;
3874 let source = VirtualSource::new(path, *content);
3875 let module = RawModule::new(Vc::upcast(source)).to_resolved().await?;
3876 Ok(Vc::upcast(*module))
3877 }
3878
3879 fn fs_path() -> RcStr {
3880 rcstr!("/tmp/_mdt")
3881 }
3882
3883 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3884 async fn modules_constructor_marks_module_duplicates() {
3885 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3886 BackendOptions::default(),
3887 noop_backing_storage(),
3888 ));
3889 #[turbo_tasks::function(operation, root)]
3890 async fn run_test() -> Result<Vc<DupCheckResult>> {
3891 let m_a = make_module(rcstr!("a.js")).to_resolved().await?;
3892 let m_b = make_module(rcstr!("b.js")).to_resolved().await?;
3893
3894 let result = ModuleResolveResult::modules([
3895 (RequestKey::new(rcstr!("a")), m_a),
3896 (RequestKey::new(rcstr!("b")), m_b),
3897 (RequestKey::new(rcstr!("a-again")), m_a),
3898 (RequestKey::new(rcstr!("b-again")), m_b),
3899 ])
3900 .await?;
3901
3902 let modules = result.primary_modules().await?;
3904 assert_eq!(modules, vec![m_a, m_b]);
3905
3906 Ok(Vc::cell(snapshot_primary(&result).await?))
3907 }
3908 tt.run_once(async move {
3909 let snap = run_test().read_strongly_consistent().await?;
3910 assert_eq!(
3911 snap.iter().map(String::as_str).collect::<Vec<_>>(),
3912 vec!["module:a.js", "module:b.js", "dup:0", "dup:1"]
3913 );
3914 Ok(())
3915 })
3916 .await
3917 .unwrap();
3918 }
3919
3920 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3921 async fn first_module_returns_first_when_duplicates_follow() {
3922 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3923 BackendOptions::default(),
3924 noop_backing_storage(),
3925 ));
3926 #[turbo_tasks::function(operation, root)]
3927 async fn run_test() -> Result<Vc<DupCheckResult>> {
3928 let m = make_module(rcstr!("a.js")).to_resolved().await?;
3929
3930 let result = ModuleResolveResult::modules([
3931 (RequestKey::default(), m),
3932 (RequestKey::new(rcstr!("again")), m),
3933 (RequestKey::new(rcstr!("once-more")), m),
3934 ])
3935 .await?;
3936
3937 assert_eq!(result.first_module().await?, Some(m));
3938 assert_eq!(result.primary_modules().await?, vec![m]);
3939 Ok(Vc::cell(snapshot_primary(&result).await?))
3940 }
3941 tt.run_once(async move {
3942 let snap = run_test().read_strongly_consistent().await?;
3943 assert_eq!(
3944 snap.iter().map(String::as_str).collect::<Vec<_>>(),
3945 vec!["module:a.js", "dup:0", "dup:0"]
3946 );
3947 Ok(())
3948 })
3949 .await
3950 .unwrap();
3951 }
3952
3953 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3954 async fn builder_marks_module_duplicates_skipping_non_dedup_items() {
3955 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3956 BackendOptions::default(),
3957 noop_backing_storage(),
3958 ));
3959 #[turbo_tasks::function(operation, root)]
3960 async fn run_test() -> Result<Vc<DupCheckResult>> {
3961 let m = make_module(rcstr!("a.js")).to_resolved().await?;
3962
3963 let mut builder = ModuleResolveResultBuilder {
3964 primary: Default::default(),
3965 affecting_sources: Vec::new(),
3966 };
3967 builder.primary.insert(
3968 RequestKey::new(rcstr!("k0")),
3969 ModuleResolveResultItem::Module(m),
3970 );
3971 builder.primary.insert(
3972 RequestKey::new(rcstr!("k1")),
3973 ModuleResolveResultItem::Empty,
3974 );
3975 builder.primary.insert(
3976 RequestKey::new(rcstr!("k2")),
3977 ModuleResolveResultItem::Module(m),
3978 );
3979 let result: ModuleResolveResult = builder.into();
3980 assert_eq!(result.primary_modules().await?, vec![m]);
3981 Ok(Vc::cell(snapshot_primary(&result).await?))
3982 }
3983 tt.run_once(async move {
3984 let snap = run_test().read_strongly_consistent().await?;
3985
3986 assert_eq!(
3987 snap.iter().map(String::as_str).collect::<Vec<_>>(),
3988 vec!["module:a.js", "other", "dup:0"]
3989 );
3990 Ok(())
3991 })
3992 .await
3993 .unwrap();
3994 }
3995
3996 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3997 async fn alternatives_preserves_unique_module_set() {
3998 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3999 BackendOptions::default(),
4000 noop_backing_storage(),
4001 ));
4002 #[turbo_tasks::function(operation, root)]
4003 async fn run_test() -> Result<Vc<DupCheckResult>> {
4004 let m_a = make_module(rcstr!("a.js")).to_resolved().await?;
4005 let m_b = make_module(rcstr!("b.js")).to_resolved().await?;
4006
4007 let r1 = *ModuleResolveResult::modules([
4009 (RequestKey::new(rcstr!("k1")), m_a),
4010 (RequestKey::new(rcstr!("k2")), m_a),
4011 ]);
4012 let r2 = *ModuleResolveResult::module(m_b);
4015
4016 let merged = ModuleResolveResult::alternatives(vec![r1, r2]).await?;
4017 assert_eq!(merged.primary_modules().await?, vec![m_a, m_b]);
4018
4019 for (i, (_, item)) in merged.primary.iter().enumerate() {
4021 if let ModuleResolveResultItem::Duplicate(first) = *item {
4022 assert!(
4023 first < i,
4024 "Duplicate index {first} at position {i} must point backwards"
4025 );
4026 let pointed = &merged.primary[first].1;
4027 let ModuleResolveResultItem::Module(pointed_module) = *pointed else {
4028 panic!(
4029 "Duplicate({first}) at {i} points at {pointed:?}, expected a concrete \
4030 Module"
4031 );
4032 };
4033 assert_eq!(
4036 pointed_module, m_a,
4037 "Duplicate({first}) at position {i} points at the wrong module"
4038 );
4039 }
4040 }
4041 Ok(Vc::cell(snapshot_primary(&merged).await?))
4042 }
4043 tt.run_once(async move {
4044 let snap = run_test().read_strongly_consistent().await?;
4045
4046 assert_eq!(
4047 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4048 vec!["module:a.js", "dup:0", "module:b.js"]
4049 );
4050 Ok(())
4051 })
4052 .await
4053 .unwrap();
4054 }
4055}