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