Skip to main content

turbopack_core/resolve/
mod.rs

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