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