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