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};
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};
68pub use remap::{
69    ReplacedSubpathValue, ReplacedSubpathValueResult, ReplacedSubpathValueResultType,
70    ResolveAliasMap, SubpathValue, TerminalState,
71};
72
73/// Controls how resolve errors are handled.
74#[turbo_tasks::value(shared, task_input)]
75#[derive(Debug, Clone, Copy, Default, Hash)]
76pub enum ResolveErrorMode {
77    /// Emit an error issue (default behavior)
78    #[default]
79    Error,
80    /// Emit a warning issue (e.g., when inside a try-catch block)
81    Warn,
82    /// Completely ignore the error (e.g., when marked with `turbopackOptional`)
83    Ignore,
84}
85
86/// Type alias for a resolved after-resolve plugin paired with its condition.
87type AfterResolvePluginWithCondition = (
88    ResolvedVc<Box<dyn AfterResolvePlugin>>,
89    ReadRef<AfterResolvePluginCondition>,
90);
91
92#[turbo_tasks::value(shared)]
93#[derive(Clone, Debug)]
94pub enum ModuleResolveResultItem {
95    Module(ResolvedVc<Box<dyn Module>>),
96    External {
97        /// uri, path, reference, etc.
98        name: RcStr,
99        ty: ExternalType,
100    },
101    /// A module could not be created (according to the rules, e.g. no module type as assigned)
102    Unknown(ResolvedVc<Box<dyn Source>>),
103    /// Completely ignore this reference.
104    Ignore,
105    /// Emit the given issue, and generate a module which throws that issue's title at runtime.
106    Error(ResolvedVc<Box<dyn Issue>>),
107    /// Resolve the reference to an empty module.
108    Empty,
109    Custom(u8),
110    /// A duplicate of an item that appeared earlier in the primary array.
111    /// The usize is the index of the first occurrence. Most callers should skip
112    /// this variant.
113    ///
114    /// Bakes duplicate detection into the datastructure to make filtering for uniques trivial which
115    /// is required by primary_modules.
116    Duplicate(usize),
117}
118
119impl ModuleResolveResultItem {
120    // Returns the module for this item if it is one
121    // NOTE: if this is a `ModuleResolveResultItem::Duplicate` we return `None`, it is expected that
122    // callers will have already found the module earlier.
123    async fn as_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
124        Ok(match *self {
125            ModuleResolveResultItem::Module(module) => Some(module),
126            ModuleResolveResultItem::Unknown(source) => {
127                emit_unknown_module_type_error(*source).await?;
128                None
129            }
130            ModuleResolveResultItem::Error(_err) => {
131                // TODO emit error?
132                None
133            }
134            _ => None,
135        })
136    }
137}
138
139#[turbo_tasks::value(shared)]
140#[derive(Clone, Debug, Hash, Default, Serialize, Deserialize)]
141pub struct BindingUsage {
142    pub import: ImportUsage,
143    pub export: ExportUsage,
144}
145
146#[turbo_tasks::value_impl]
147impl BindingUsage {
148    #[turbo_tasks::function]
149    pub fn all() -> Vc<Self> {
150        Self::default().cell()
151    }
152}
153
154/// Defines where an import is used in a module
155#[turbo_tasks::value(shared)]
156#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
157pub enum ImportUsage {
158    /// This import is used at the top level of the module.  For example, for module level side
159    /// effects
160    #[default]
161    TopLevel,
162    /// This import is used only by these specific exports, if all exports are unused, the import
163    /// can also be removed.
164    ///
165    /// (This is only ever set on named export module-part references. Side effects are handled via
166    /// `ModulePart::Evaluation` references, which always have `ImportUsage::TopLevel`.)
167    Exports(FrozenSet<RcStr>),
168}
169
170/// Defines what parts of a module are used by another module
171#[turbo_tasks::value]
172#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
173pub enum ExportUsage {
174    Named(RcStr),
175    /// Multiple named exports are used via a partial namespace object.
176    PartialNamespaceObject(SmallVec<[RcStr; 1]>),
177    /// This means the whole content of the module is used.
178    #[default]
179    All,
180    /// Only side effects are used.
181    Evaluation,
182    /// Use the same exports that are used from the referencing module. This is used by transparent
183    /// module proxies and re-exports that forward their export surface to another module.
184    ///
185    /// Namespace provenance that reached the referencing module is forwarded independently of the
186    /// used names. This keeps multi-hop namespace reads safe for export-name mangling.
187    Passthrough {
188        /// Whether this edge itself exposes a namespace object's original property names, even if
189        /// the referencing module was only consumed through statically known named exports.
190        namespace_object_may_escape: bool,
191    },
192}
193
194impl Display for ExportUsage {
195    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
196        match self {
197            ExportUsage::Named(name) => write!(f, "export {name}"),
198            ExportUsage::PartialNamespaceObject(names) => {
199                write!(f, "exports ")?;
200                for (i, name) in names.iter().enumerate() {
201                    if i > 0 {
202                        write!(f, ", ")?;
203                    }
204                    write!(f, "{name}")?;
205                }
206                Ok(())
207            }
208            ExportUsage::All => write!(f, "all"),
209            ExportUsage::Evaluation => write!(f, "evaluation"),
210            ExportUsage::Passthrough {
211                namespace_object_may_escape,
212                ..
213            } => write!(
214                f,
215                "passthrough{}",
216                if *namespace_object_may_escape {
217                    " namespace"
218                } else {
219                    ""
220                }
221            ),
222        }
223    }
224}
225
226#[turbo_tasks::value_impl]
227impl ExportUsage {
228    #[turbo_tasks::function]
229    pub fn all() -> Vc<Self> {
230        Self::All.cell()
231    }
232
233    #[turbo_tasks::function]
234    pub fn evaluation() -> Vc<Self> {
235        Self::Evaluation.cell()
236    }
237
238    #[turbo_tasks::function]
239    pub fn named(name: RcStr) -> Vc<Self> {
240        Self::Named(name).cell()
241    }
242
243    #[turbo_tasks::function]
244    pub fn partial_namespace_object(names: Vec<RcStr>) -> Vc<Self> {
245        Self::PartialNamespaceObject(names.into_iter().collect()).cell()
246    }
247
248    #[turbo_tasks::function]
249    pub fn passthrough(namespace_object_may_escape: bool) -> Vc<Self> {
250        Self::Passthrough {
251            namespace_object_may_escape,
252        }
253        .cell()
254    }
255}
256
257#[turbo_tasks::value(shared)]
258#[derive(Clone, Debug)]
259pub struct ModuleResolveResult {
260    pub primary: Box<[(RequestKey, ModuleResolveResultItem)]>,
261    /// Affecting sources are other files that influence the resolve result.  For example,
262    /// traversed symlinks
263    pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
264}
265
266impl ModuleResolveResult {
267    pub fn unresolvable() -> ResolvedVc<Self> {
268        ModuleResolveResult {
269            primary: Default::default(),
270            affecting_sources: Default::default(),
271        }
272        .resolved_cell()
273    }
274
275    pub fn module(module: ResolvedVc<Box<dyn Module>>) -> ResolvedVc<Self> {
276        Self::module_with_key(RequestKey::default(), module)
277    }
278
279    pub fn module_with_key(
280        request_key: RequestKey,
281        module: ResolvedVc<Box<dyn Module>>,
282    ) -> ResolvedVc<Self> {
283        ModuleResolveResult {
284            primary: vec![(request_key, ModuleResolveResultItem::Module(module))]
285                .into_boxed_slice(),
286            affecting_sources: Default::default(),
287        }
288        .resolved_cell()
289    }
290
291    pub fn modules(
292        modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
293    ) -> ResolvedVc<Self> {
294        let mut primary: Vec<_> = modules
295            .into_iter()
296            .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
297            .collect();
298        Self::mark_duplicates(&mut primary);
299        ModuleResolveResult {
300            primary: primary.into_boxed_slice(),
301            affecting_sources: Default::default(),
302        }
303        .resolved_cell()
304    }
305
306    pub fn modules_with_affecting_sources(
307        modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
308        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
309    ) -> ResolvedVc<Self> {
310        let mut primary: Vec<_> = modules
311            .into_iter()
312            .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
313            .collect();
314        Self::mark_duplicates(&mut primary);
315        ModuleResolveResult {
316            primary: primary.into_boxed_slice(),
317            affecting_sources: affecting_sources.into_boxed_slice(),
318        }
319        .resolved_cell()
320    }
321}
322
323impl ModuleResolveResult {
324    /// Marks duplicate items as `Duplicate(first_index)` in place.
325    /// Preserves ordering; the first occurrence stays, subsequent occurrences
326    /// of the same module/output asset become `Duplicate`.
327    fn mark_duplicates(primary: &mut [(RequestKey, ModuleResolveResultItem)]) {
328        if primary.len() <= 1 {
329            return;
330        }
331        // Map from module identity to the index of first occurrence
332        let mut seen_modules = FxHashMap::default();
333        for (i, (_, item)) in primary.iter_mut().enumerate() {
334            if let ModuleResolveResultItem::Module(m) = *item {
335                if let Some(&first) = seen_modules.get(&m) {
336                    *item = ModuleResolveResultItem::Duplicate(first);
337                } else {
338                    seen_modules.insert(m, i);
339                }
340            }
341        }
342    }
343
344    /// Returns all module results (but ignoring any errors).
345    pub fn primary_modules_raw_iter(
346        &self,
347    ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
348        self.primary.iter().filter_map(|(_, item)| match *item {
349            ModuleResolveResultItem::Module(a) => Some(a),
350            _ => None,
351        })
352    }
353
354    /// Returns primary modules (no duplicates). Emits errors for Unknown items.
355    /// Duplicates are already marked at construction time so no extra dedup is
356    /// needed here.
357    pub async fn primary_modules(&self) -> Result<SmallVec<[ResolvedVc<Box<dyn Module>>; 2]>> {
358        self.primary
359            .iter()
360            .map(async |(_, item)| item.as_module().await)
361            .join()
362            .await
363            .into_iter()
364            .filter_map(Result::transpose)
365            .collect()
366    }
367
368    /// Returns the first module in the result, or None.
369    pub async fn first_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
370        for (_, item) in self.primary.iter() {
371            if let Some(module) = item.as_module().await? {
372                return Ok(Some(module));
373            }
374        }
375        Ok(None)
376    }
377
378    pub fn affecting_sources_iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
379        self.affecting_sources.iter().copied()
380    }
381
382    pub fn is_unresolvable(&self) -> bool {
383        self.primary.is_empty()
384    }
385
386    pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
387        self.primary.iter().filter_map(|i| match &i.1 {
388            ModuleResolveResultItem::Error(e) => Some(*e),
389            _ => None,
390        })
391    }
392}
393
394pub struct ModuleResolveResultBuilder {
395    pub primary: FxIndexMap<RequestKey, ModuleResolveResultItem>,
396    pub affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
397}
398
399impl From<ModuleResolveResultBuilder> for ModuleResolveResult {
400    fn from(v: ModuleResolveResultBuilder) -> Self {
401        let mut primary: Vec<_> = v.primary.into_iter().collect();
402        Self::mark_duplicates(&mut primary);
403        ModuleResolveResult {
404            primary: primary.into_boxed_slice(),
405            affecting_sources: v.affecting_sources.into_boxed_slice(),
406        }
407    }
408}
409
410/// Resolves a `Duplicate(i)` marker by looking up the underlying item in `source`.
411/// `mark_duplicates` only ever produces backwards-pointing `Duplicate` indices into
412/// `Module(_)` entries, so a single lookup is enough.
413fn expand_duplicate<'a>(
414    source: &'a [(RequestKey, ModuleResolveResultItem)],
415    item: &'a ModuleResolveResultItem,
416) -> &'a ModuleResolveResultItem {
417    if let ModuleResolveResultItem::Duplicate(i) = *item {
418        &source[i].1
419    } else {
420        item
421    }
422}
423
424impl From<ModuleResolveResult> for ModuleResolveResultBuilder {
425    fn from(v: ModuleResolveResult) -> Self {
426        // Expand `Duplicate(i)` markers as we copy into the builder. The indices are valid
427        // for `v.primary`, but the builder's `FxIndexMap` may be re-keyed and merged with
428        // other results, so the indices wouldn't survive. The final
429        // `From<Builder> for ModuleResolveResult` re-runs `mark_duplicates` on the merged
430        // primary array.
431        let primary = v
432            .primary
433            .iter()
434            .map(|(k, item)| (k.clone(), expand_duplicate(&v.primary, item).clone()))
435            .collect();
436        ModuleResolveResultBuilder {
437            primary,
438            affecting_sources: v.affecting_sources.into_vec(),
439        }
440    }
441}
442impl ModuleResolveResultBuilder {
443    pub fn merge_alternatives(&mut self, other: &ModuleResolveResult) {
444        // Expand `Duplicate(i)` markers from `other` against `other.primary` before
445        // inserting — the indices only make sense within `other`, not within the merged
446        // result. The final `mark_duplicates` pass on conversion will re-derive markers.
447        for (k, v) in other.primary.iter() {
448            if !self.primary.contains_key(k) {
449                self.primary
450                    .insert(k.clone(), expand_duplicate(&other.primary, v).clone());
451            }
452        }
453        let set = self
454            .affecting_sources
455            .iter()
456            .copied()
457            .collect::<FxHashSet<_>>();
458        self.affecting_sources.extend(
459            other
460                .affecting_sources
461                .iter()
462                .filter(|source| !set.contains(source))
463                .copied(),
464        );
465    }
466}
467
468#[turbo_tasks::value_impl]
469impl ModuleResolveResult {
470    #[turbo_tasks::function]
471    pub async fn alternatives(results: Vec<Vc<ModuleResolveResult>>) -> Result<Vc<Self>> {
472        if results.len() == 1 {
473            return Ok(results.into_iter().next().unwrap());
474        }
475        let mut iter = results.into_iter().try_join().await?.into_iter();
476        if let Some(current) = iter.next() {
477            let mut current: ModuleResolveResultBuilder = ReadRef::into_owned(current).into();
478            for result in iter {
479                // For clippy -- This explicit deref is necessary
480                let other = &*result;
481                current.merge_alternatives(other);
482            }
483            Ok(Self::cell(current.into()))
484        } else {
485            Ok(*ModuleResolveResult::unresolvable())
486        }
487    }
488
489    /// Combines results from distinct lookup directories. Unlike `alternatives`, entries with
490    /// identical request keys can point at different modules and must both be kept.
491    #[turbo_tasks::function]
492    pub async fn concat(results: Vec<Vc<ModuleResolveResult>>) -> Result<Vc<Self>> {
493        if results.len() == 1 {
494            return Ok(results.into_iter().next().unwrap());
495        }
496        let mut primary = Vec::new();
497        let mut affecting_sources = Vec::new();
498        let mut seen_sources = FxHashSet::default();
499        for result in results.into_iter().try_join().await? {
500            primary.extend(
501                result.primary.iter().map(|(key, item)| {
502                    (key.clone(), expand_duplicate(&result.primary, item).clone())
503                }),
504            );
505            for source in result.affecting_sources.iter().copied() {
506                if seen_sources.insert(source) {
507                    affecting_sources.push(source);
508                }
509            }
510        }
511        Self::mark_duplicates(&mut primary);
512        Ok(Self::cell(Self {
513            primary: primary.into_boxed_slice(),
514            affecting_sources: affecting_sources.into_boxed_slice(),
515        }))
516    }
517}
518
519#[turbo_tasks::task_input]
520#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
521pub enum ExternalTraced {
522    Untraced,
523    Traced,
524}
525
526impl Display for ExternalTraced {
527    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
528        match self {
529            ExternalTraced::Untraced => write!(f, "untraced"),
530            ExternalTraced::Traced => write!(f, "traced"),
531        }
532    }
533}
534
535#[turbo_tasks::task_input]
536#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encode, Decode)]
537pub enum ExternalType {
538    Url,
539    CommonJs,
540    EcmaScriptModule,
541    Global,
542    Script,
543}
544
545impl Display for ExternalType {
546    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
547        match self {
548            ExternalType::CommonJs => write!(f, "commonjs"),
549            ExternalType::EcmaScriptModule => write!(f, "esm"),
550            ExternalType::Url => write!(f, "url"),
551            ExternalType::Global => write!(f, "global"),
552            ExternalType::Script => write!(f, "script"),
553        }
554    }
555}
556
557#[turbo_tasks::value(shared)]
558#[derive(Debug, Clone)]
559pub enum ResolveResultItem {
560    Source(ResolvedVc<Box<dyn Source>>),
561    External {
562        /// uri, path, reference, etc.
563        name: RcStr,
564        ty: ExternalType,
565        traced: ExternalTraced,
566        /// The file path to the resolved file. Passing a value will create a symlink in the output
567        /// root to be able to access potentially transitive dependencies.
568        target: Option<FileSystemPath>,
569    },
570    /// Completely ignore this reference.
571    Ignore,
572    /// Emit the given issue, and generate a module which throws that issue's title at runtime.
573    Error(ResolvedVc<Box<dyn Issue>>),
574    /// Resolve the reference to an empty module.
575    Empty,
576    Custom(u8),
577}
578
579/// Represents the key for a request that leads to a certain results during
580/// resolving.
581///
582/// A primary factor is the actual request string, but there are
583/// other factors like exports conditions that can affect resolving and become
584/// part of the key (assuming the condition is unknown at compile time)
585#[derive(Clone, Debug, Default, Hash)]
586#[turbo_tasks::value(task_input)]
587pub struct RequestKey {
588    pub request: Option<RcStr>,
589    pub conditions: FrozenMap<RcStr, bool>,
590}
591
592impl Display for RequestKey {
593    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
594        if let Some(request) = &self.request {
595            write!(f, "{request}")?;
596        } else {
597            write!(f, "<default>")?;
598        }
599        if !self.conditions.is_empty() {
600            write!(f, " (")?;
601            for (i, (k, v)) in self.conditions.iter().enumerate() {
602                if i > 0 {
603                    write!(f, ", ")?;
604                }
605                write!(f, "{k}={v}")?;
606            }
607            write!(f, ")")?;
608        }
609        Ok(())
610    }
611}
612
613impl RequestKey {
614    pub fn new(request: RcStr) -> Self {
615        RequestKey {
616            request: Some(request),
617            ..Default::default()
618        }
619    }
620}
621
622#[turbo_tasks::value(shared)]
623#[derive(Clone)]
624pub struct ResolveResult {
625    pub primary: Box<[(RequestKey, ResolveResultItem)]>,
626    /// Affecting sources are other files that influence the resolve result.  For example,
627    /// traversed symlinks
628    pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
629}
630
631#[turbo_tasks::value_impl]
632impl ValueToString for ResolveResult {
633    #[turbo_tasks::function]
634    async fn to_string(&self) -> Result<Vc<RcStr>> {
635        let mut result = String::new();
636        if self.is_unresolvable() {
637            result.push_str("unresolvable");
638        }
639        for (i, (request, item)) in self.primary.iter().enumerate() {
640            if i > 0 {
641                result.push_str(", ");
642            }
643            write!(result, "{request} -> ").unwrap();
644            match item {
645                ResolveResultItem::Source(a) => {
646                    result.push_str(&a.ident().to_string().await?);
647                }
648                ResolveResultItem::External {
649                    name: s,
650                    ty,
651                    traced,
652                    target,
653                } => {
654                    result.push_str("external ");
655                    result.push_str(s);
656                    write!(
657                        result,
658                        " ({ty}, {traced}, {:?})",
659                        if let Some(target) = target {
660                            Some(target.to_string_ref().await?)
661                        } else {
662                            None
663                        }
664                    )?;
665                }
666                ResolveResultItem::Ignore => {
667                    result.push_str("ignore");
668                }
669                ResolveResultItem::Empty => {
670                    result.push_str("empty");
671                }
672                ResolveResultItem::Error(_) => {
673                    result.push_str("error");
674                }
675                ResolveResultItem::Custom(_) => {
676                    result.push_str("custom");
677                }
678            }
679            result.push('\n');
680        }
681        if !self.affecting_sources.is_empty() {
682            result.push_str(" (affecting sources: ");
683            for (i, source) in self.affecting_sources.iter().enumerate() {
684                if i > 0 {
685                    result.push_str(", ");
686                }
687                result.push_str(&source.ident().to_string().await?);
688            }
689            result.push(')');
690        }
691        Ok(Vc::cell(result.into()))
692    }
693}
694
695impl ResolveResult {
696    pub fn unresolvable() -> Self {
697        ResolveResult {
698            primary: Default::default(),
699            affecting_sources: Default::default(),
700        }
701    }
702
703    pub fn unresolvable_with_affecting_sources(
704        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
705    ) -> Self {
706        ResolveResult {
707            primary: Default::default(),
708            affecting_sources: affecting_sources.into_boxed_slice(),
709        }
710    }
711
712    pub fn primary(result: ResolveResultItem) -> Self {
713        Self::primary_with_key(RequestKey::default(), result)
714    }
715
716    pub fn primary_with_key(request_key: RequestKey, result: ResolveResultItem) -> Self {
717        ResolveResult {
718            primary: vec![(request_key, result)].into_boxed_slice(),
719            affecting_sources: Default::default(),
720        }
721    }
722
723    pub fn primary_with_affecting_sources(
724        request_key: RequestKey,
725        result: ResolveResultItem,
726        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
727    ) -> Self {
728        ResolveResult {
729            primary: vec![(request_key, result)].into_boxed_slice(),
730            affecting_sources: affecting_sources.into_boxed_slice(),
731        }
732    }
733
734    pub fn source(source: ResolvedVc<Box<dyn Source>>) -> Self {
735        Self::source_with_key(RequestKey::default(), source)
736    }
737
738    fn source_with_key(request_key: RequestKey, source: ResolvedVc<Box<dyn Source>>) -> Self {
739        ResolveResult {
740            primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
741            affecting_sources: Default::default(),
742        }
743    }
744
745    fn source_with_affecting_sources(
746        request_key: RequestKey,
747        source: ResolvedVc<Box<dyn Source>>,
748        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
749    ) -> Self {
750        ResolveResult {
751            primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
752            affecting_sources: affecting_sources.into_boxed_slice(),
753        }
754    }
755
756    pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
757        self.primary.iter().filter_map(|i| match &i.1 {
758            ResolveResultItem::Error(e) => Some(*e),
759            _ => None,
760        })
761    }
762}
763
764impl ResolveResult {
765    /// Returns the affecting sources for this result. Will be empty if affecting sources are
766    /// disabled for this result.
767    pub fn get_affecting_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
768        self.affecting_sources.iter().copied()
769    }
770
771    pub fn is_unresolvable(&self) -> bool {
772        self.primary.is_empty()
773    }
774
775    pub fn first_source(&self) -> Option<ResolvedVc<Box<dyn Source>>> {
776        self.primary.iter().find_map(|(_, item)| {
777            if let &ResolveResultItem::Source(a) = item {
778                Some(a)
779            } else {
780                None
781            }
782        })
783    }
784
785    pub fn primary_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> {
786        self.primary.iter().filter_map(|(_, item)| {
787            if let &ResolveResultItem::Source(a) = item {
788                Some(a)
789            } else {
790                None
791            }
792        })
793    }
794
795    pub async fn map_module<A, AF>(&self, source_fn: A) -> Result<ModuleResolveResult>
796    where
797        A: Fn(ResolvedVc<Box<dyn Source>>) -> AF,
798        AF: Future<Output = Result<ModuleResolveResultItem>>,
799    {
800        Ok(ModuleResolveResult {
801            primary: self
802                .primary
803                .iter()
804                .map(|(request, item)| {
805                    let asset_fn = &source_fn;
806                    let request = request.clone();
807                    let item = item.clone();
808                    async move {
809                        Ok((
810                            request,
811                            match item {
812                                ResolveResultItem::Source(source) => asset_fn(source).await?,
813                                ResolveResultItem::External {
814                                    name,
815                                    ty,
816                                    traced,
817                                    target,
818                                } => {
819                                    if traced == ExternalTraced::Traced || target.is_some() {
820                                        // Should use map_primary_items instead
821                                        bail!("map_module doesn't handle traced externals");
822                                    }
823                                    ModuleResolveResultItem::External { name, ty }
824                                }
825                                ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore,
826                                ResolveResultItem::Empty => ModuleResolveResultItem::Empty,
827                                ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e),
828                                ResolveResultItem::Custom(u8) => {
829                                    ModuleResolveResultItem::Custom(u8)
830                                }
831                            },
832                        ))
833                    }
834                })
835                .try_join()
836                .await?
837                .into_iter()
838                .collect(),
839            affecting_sources: self.affecting_sources.clone(),
840        })
841    }
842
843    pub async fn map_primary_items<A, AF>(&self, item_fn: A) -> Result<ModuleResolveResult>
844    where
845        A: Fn(ResolveResultItem) -> AF,
846        AF: Future<Output = Result<ModuleResolveResultItem>>,
847    {
848        Ok(ModuleResolveResult {
849            primary: self
850                .primary
851                .iter()
852                .map(|(request, item)| {
853                    let asset_fn = &item_fn;
854                    let request = request.clone();
855                    let item = item.clone();
856                    async move { Ok((request, asset_fn(item).await?)) }
857                })
858                .try_join()
859                .await?
860                .into_iter()
861                .collect(),
862            affecting_sources: self.affecting_sources.clone(),
863        })
864    }
865
866    /// Returns a new [ResolveResult] where all [RequestKey]s are set to the
867    /// passed `request`.
868    fn with_request_ref(&self, request: RcStr) -> Self {
869        let new_primary = self
870            .primary
871            .iter()
872            .map(|(k, v)| {
873                (
874                    RequestKey {
875                        request: Some(request.clone()),
876                        conditions: k.conditions.clone(),
877                    },
878                    v.clone(),
879                )
880            })
881            .collect();
882        ResolveResult {
883            primary: new_primary,
884            affecting_sources: self.affecting_sources.clone(),
885        }
886    }
887
888    pub fn with_conditions(&self, new_conditions: &[(RcStr, bool)]) -> Self {
889        let primary = self
890            .primary
891            .iter()
892            .map(|(k, v)| {
893                (
894                    RequestKey {
895                        request: k.request.clone(),
896                        conditions: k.conditions.extend(new_conditions.iter().cloned()),
897                    },
898                    v.clone(),
899                )
900            })
901            .collect::<FxIndexMap<_, _>>() // Deduplicate
902            .into_iter()
903            .collect();
904        ResolveResult {
905            primary,
906            affecting_sources: self.affecting_sources.clone(),
907        }
908    }
909}
910
911struct ResolveResultBuilder {
912    primary: FxIndexMap<RequestKey, ResolveResultItem>,
913    affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
914}
915
916impl From<ResolveResultBuilder> for ResolveResult {
917    fn from(v: ResolveResultBuilder) -> Self {
918        ResolveResult {
919            primary: v.primary.into_iter().collect(),
920            affecting_sources: v.affecting_sources.into_boxed_slice(),
921        }
922    }
923}
924impl From<ResolveResult> for ResolveResultBuilder {
925    fn from(v: ResolveResult) -> Self {
926        ResolveResultBuilder {
927            primary: IntoIterator::into_iter(v.primary).collect(),
928            affecting_sources: v.affecting_sources.into_vec(),
929        }
930    }
931}
932impl ResolveResultBuilder {
933    pub fn merge_alternatives(&mut self, other: &ResolveResult) {
934        for (k, v) in other.primary.iter() {
935            if !self.primary.contains_key(k) {
936                self.primary.insert(k.clone(), v.clone());
937            }
938        }
939        let set = self
940            .affecting_sources
941            .iter()
942            .copied()
943            .collect::<FxHashSet<_>>();
944        self.affecting_sources.extend(
945            other
946                .affecting_sources
947                .iter()
948                .filter(|source| !set.contains(source))
949                .copied(),
950        );
951    }
952}
953
954#[turbo_tasks::value_impl]
955impl ResolveResult {
956    #[turbo_tasks::function]
957    pub async fn as_raw_module_result(&self) -> Result<Vc<ModuleResolveResult>> {
958        Ok(self
959            .map_module(async |asset| {
960                Ok(ModuleResolveResultItem::Module(ResolvedVc::upcast(
961                    RawModule::new(*asset).to_resolved().await?,
962                )))
963            })
964            .await?
965            .cell())
966    }
967
968    #[turbo_tasks::function]
969    fn with_affecting_sources(
970        &self,
971        sources: Vec<ResolvedVc<Box<dyn Source>>>,
972    ) -> Result<Vc<Self>> {
973        Ok(Self {
974            primary: self.primary.clone(),
975            affecting_sources: self
976                .affecting_sources
977                .iter()
978                .copied()
979                .chain(sources)
980                .collect(),
981        }
982        .cell())
983    }
984
985    #[turbo_tasks::function]
986    async fn alternatives(results: Vec<Vc<ResolveResult>>) -> Result<Vc<Self>> {
987        if results.len() == 1 {
988            return Ok(results.into_iter().next().unwrap());
989        }
990        let mut iter = results.into_iter().try_join().await?.into_iter();
991        if let Some(current) = iter.next() {
992            let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
993            for result in iter {
994                // For clippy -- This explicit deref is necessary
995                let other = &*result;
996                current.merge_alternatives(other);
997            }
998            Ok(Self::cell(current.into()))
999        } else {
1000            Ok(ResolveResult::unresolvable().cell())
1001        }
1002    }
1003
1004    #[turbo_tasks::function]
1005    async fn alternatives_with_affecting_sources(
1006        results: Vec<Vc<ResolveResult>>,
1007        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
1008    ) -> Result<Vc<Self>> {
1009        debug_assert!(
1010            !affecting_sources.is_empty(),
1011            "Caller should not call this function if there are no affecting sources"
1012        );
1013        if results.len() == 1 {
1014            return Ok(results
1015                .into_iter()
1016                .next()
1017                .unwrap()
1018                .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()));
1019        }
1020        let mut iter = results.into_iter().try_join().await?.into_iter();
1021        if let Some(current) = iter.next() {
1022            let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
1023            for result in iter {
1024                // For clippy -- This explicit deref is necessary
1025                let other = &*result;
1026                current.merge_alternatives(other);
1027            }
1028            current.affecting_sources.extend(affecting_sources);
1029            Ok(Self::cell(current.into()))
1030        } else {
1031            Ok(ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell())
1032        }
1033    }
1034
1035    /// Returns a new [ResolveResult] where all [RequestKey]s are updated. The `old_request_key`
1036    /// (prefix) is replaced with the `request_key`. It's not expected that the [ResolveResult]
1037    /// contains [RequestKey]s that don't have the `old_request_key` prefix, but if there are still
1038    /// some, they are discarded.
1039    #[turbo_tasks::function]
1040    fn with_replaced_request_key(
1041        &self,
1042        old_request_key: RcStr,
1043        new_request_key: RcStr,
1044    ) -> Result<Vc<Self>> {
1045        let new_primary = self
1046            .primary
1047            .iter()
1048            .filter_map(|(k, v)| {
1049                let remaining = k.request.as_ref()?.strip_prefix(&*old_request_key)?;
1050                Some((
1051                    RequestKey {
1052                        request: Some(format!("{new_request_key}{remaining}").into()),
1053                        conditions: k.conditions.clone(),
1054                    },
1055                    v.clone(),
1056                ))
1057            })
1058            .collect();
1059        Ok(ResolveResult {
1060            primary: new_primary,
1061            affecting_sources: self.affecting_sources.clone(),
1062        }
1063        .cell())
1064    }
1065
1066    /// Returns a new [ResolveResult] where all [RequestKey]s are updated. The prefix is removed
1067    /// from all [RequestKey]s. It's not expected that the [ResolveResult] contains [RequestKey]s
1068    /// without the prefix, but if there are still some, they are discarded.
1069    #[turbo_tasks::function]
1070    fn with_stripped_request_key_prefix(&self, prefix: RcStr) -> Result<Vc<Self>> {
1071        let new_primary = self
1072            .primary
1073            .iter()
1074            .filter_map(|(k, v)| {
1075                let remaining = k.request.as_ref()?.strip_prefix(&*prefix)?;
1076                Some((
1077                    RequestKey {
1078                        request: Some(remaining.into()),
1079                        conditions: k.conditions.clone(),
1080                    },
1081                    v.clone(),
1082                ))
1083            })
1084            .collect();
1085        Ok(ResolveResult {
1086            primary: new_primary,
1087            affecting_sources: self.affecting_sources.clone(),
1088        }
1089        .cell())
1090    }
1091
1092    /// Returns a new [ResolveResult] where all [RequestKey]s are updated. All keys matching
1093    /// `old_request_key` are rewritten according to `request_key`. It's not expected that the
1094    /// [ResolveResult] contains [RequestKey]s that do not match the `old_request_key` prefix, but
1095    /// if there are still some, they are discarded.
1096    #[turbo_tasks::function]
1097    async fn with_replaced_request_key_pattern(
1098        &self,
1099        old_request_key: Vc<Pattern>,
1100        request_key: Vc<Pattern>,
1101    ) -> Result<Vc<Self>> {
1102        let old_request_key = &*old_request_key.await?;
1103        let request_key = &*request_key.await?;
1104
1105        let new_primary = self
1106            .primary
1107            .iter()
1108            .map(|(k, v)| {
1109                (
1110                    RequestKey {
1111                        request: k
1112                            .request
1113                            .as_ref()
1114                            .and_then(|r| old_request_key.match_apply_template(r, request_key))
1115                            .map(Into::into),
1116                        conditions: k.conditions.clone(),
1117                    },
1118                    v.clone(),
1119                )
1120            })
1121            .collect();
1122        Ok(ResolveResult {
1123            primary: new_primary,
1124            affecting_sources: self.affecting_sources.clone(),
1125        }
1126        .cell())
1127    }
1128
1129    /// Returns a new [ResolveResult] where all [RequestKey]s are set to the
1130    /// passed `request`.
1131    #[turbo_tasks::function]
1132    fn with_request(&self, request: RcStr) -> Vc<Self> {
1133        let new_primary = self
1134            .primary
1135            .iter()
1136            .map(|(k, v)| {
1137                (
1138                    RequestKey {
1139                        request: Some(request.clone()),
1140                        conditions: k.conditions.clone(),
1141                    },
1142                    v.clone(),
1143                )
1144            })
1145            .collect();
1146        ResolveResult {
1147            primary: new_primary,
1148            affecting_sources: self.affecting_sources.clone(),
1149        }
1150        .cell()
1151    }
1152}
1153
1154#[turbo_tasks::value(transparent)]
1155pub struct ResolveResultOption(Option<ResolvedVc<ResolveResult>>);
1156
1157#[turbo_tasks::value_impl]
1158impl ResolveResultOption {
1159    #[turbo_tasks::function]
1160    pub fn some(result: ResolvedVc<ResolveResult>) -> Vc<Self> {
1161        ResolveResultOption(Some(result)).cell()
1162    }
1163
1164    #[turbo_tasks::function]
1165    pub fn none() -> Vc<Self> {
1166        ResolveResultOption(None).cell()
1167    }
1168}
1169
1170async fn exists(
1171    fs_path: &FileSystemPath,
1172    refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1173) -> Result<Option<FileSystemPath>> {
1174    type_exists(fs_path, FileSystemEntryType::File, refs).await
1175}
1176
1177async fn dir_exists(
1178    fs_path: &FileSystemPath,
1179    refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1180) -> Result<Option<FileSystemPath>> {
1181    type_exists(fs_path, FileSystemEntryType::Directory, refs).await
1182}
1183
1184async fn type_exists(
1185    fs_path: &FileSystemPath,
1186    ty: FileSystemEntryType,
1187    refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1188) -> Result<Option<FileSystemPath>> {
1189    let Some(path) = realpath_if_exists(fs_path, refs).await? else {
1190        return Ok(None);
1191    };
1192    Ok(if *path.get_type().await? == ty {
1193        Some(path)
1194    } else {
1195        None
1196    })
1197}
1198
1199async fn realpath_if_exists(
1200    fs_path: &FileSystemPath,
1201    refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1202) -> Result<Option<FileSystemPath>> {
1203    let result = fs_path.realpath_with_links().await?;
1204    if let Some(refs) = refs {
1205        refs.extend(
1206            result
1207                .symlinks
1208                .iter()
1209                .map(async |path| {
1210                    Ok(ResolvedVc::upcast(
1211                        FileSource::new(path.clone()).to_resolved().await?,
1212                    ))
1213                })
1214                .try_join()
1215                .await?,
1216        );
1217    }
1218    match &result.path_result {
1219        Ok(path) => Ok(Some(path.clone())),
1220        // Treat paths whose symlinks cannot be resolved as missing candidates, matching Node.js
1221        // module resolution so lookup can continue in ancestor directories.
1222        Err(_) => Ok(None),
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_symlink_resolution_failures_are_missing_candidates() {
3633        use std::os::unix::fs::symlink;
3634
3635        #[turbo_tasks::value]
3636        struct MissingPathsResult {
3637            missing_file: bool,
3638            dangling: bool,
3639            self_cycle: bool,
3640        }
3641
3642        let scratch = tempfile::tempdir().unwrap();
3643        create_dir_all(scratch.path().join("package")).unwrap();
3644        symlink("package", scratch.path().join("linked-package")).unwrap();
3645        symlink("missing", scratch.path().join("dangling")).unwrap();
3646        symlink("self-cycle", scratch.path().join("self-cycle")).unwrap();
3647
3648        let path = RcStr::from(scratch.path().to_str().unwrap());
3649        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3650            BackendOptions::default(),
3651            noop_backing_storage(),
3652        ));
3653
3654        #[turbo_tasks::function(operation, root)]
3655        async fn missing_paths_through_symlinks_operation(
3656            path: RcStr,
3657        ) -> Result<Vc<MissingPathsResult>> {
3658            let fs = DiskFileSystem::new(rcstr!("temp"), Vc::cell(path));
3659            let root = fs.root().owned().await?;
3660            Ok(MissingPathsResult {
3661                missing_file: realpath_if_exists(&root.join("linked-package/package.json")?, None)
3662                    .await?
3663                    .is_none(),
3664                dangling: realpath_if_exists(&root.join("dangling")?, None)
3665                    .await?
3666                    .is_none(),
3667                self_cycle: realpath_if_exists(&root.join("self-cycle")?, None)
3668                    .await?
3669                    .is_none(),
3670            }
3671            .cell())
3672        }
3673
3674        tt.run_once(async move {
3675            let missing = missing_paths_through_symlinks_operation(path)
3676                .read_strongly_consistent()
3677                .await?;
3678            assert!(missing.missing_file);
3679            assert!(missing.dangling);
3680            assert!(missing.self_cycle);
3681
3682            anyhow::Ok(())
3683        })
3684        .await
3685        .unwrap();
3686    }
3687
3688    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3689    async fn test_explicit_js_resolves_to_ts() {
3690        resolve_relative_request_test(TestParams {
3691            files: vec!["foo.js", "foo.ts"],
3692            pattern: rcstr!("./foo.js").into(),
3693            enable_typescript_with_output_extension: true,
3694            fully_specified: false,
3695            custom_extensions: None,
3696            expected: vec![("./foo.js", "foo.ts")],
3697        })
3698        .await;
3699    }
3700
3701    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3702    async fn test_implicit_request_ts_priority() {
3703        resolve_relative_request_test(TestParams {
3704            files: vec!["foo.js", "foo.ts"],
3705            pattern: rcstr!("./foo").into(),
3706            enable_typescript_with_output_extension: true,
3707            fully_specified: false,
3708            custom_extensions: None,
3709            expected: vec![("./foo", "foo.ts")],
3710        })
3711        .await;
3712    }
3713
3714    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3715    async fn test_ts_priority_over_json() {
3716        resolve_relative_request_test(TestParams {
3717            files: vec!["posts.json", "posts.ts"],
3718            pattern: rcstr!("./posts").into(),
3719            enable_typescript_with_output_extension: true,
3720            fully_specified: false,
3721            custom_extensions: None,
3722            expected: vec![("./posts", "posts.ts")],
3723        })
3724        .await;
3725    }
3726
3727    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3728    async fn test_only_js_file_no_ts() {
3729        resolve_relative_request_test(TestParams {
3730            files: vec!["bar.js"],
3731            pattern: rcstr!("./bar.js").into(),
3732            enable_typescript_with_output_extension: true,
3733            fully_specified: false,
3734            custom_extensions: None,
3735            expected: vec![("./bar.js", "bar.js")],
3736        })
3737        .await;
3738    }
3739
3740    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3741    async fn test_explicit_ts_request() {
3742        resolve_relative_request_test(TestParams {
3743            files: vec!["foo.js", "foo.ts"],
3744            pattern: rcstr!("./foo.ts").into(),
3745            enable_typescript_with_output_extension: true,
3746            fully_specified: false,
3747            custom_extensions: None,
3748            expected: vec![("./foo.ts", "foo.ts")],
3749        })
3750        .await;
3751    }
3752
3753    // Fragment handling tests
3754    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3755    async fn test_fragment() {
3756        resolve_relative_request_test(TestParams {
3757            files: vec!["client.ts"],
3758            pattern: rcstr!("./client#frag").into(),
3759            enable_typescript_with_output_extension: true,
3760            fully_specified: false,
3761            custom_extensions: None,
3762            expected: vec![("./client", "client.ts")],
3763        })
3764        .await;
3765    }
3766
3767    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3768    async fn test_fragment_as_part_of_filename() {
3769        // When a file literally contains '#' in its name, it should be preserved
3770        resolve_relative_request_test(TestParams {
3771            files: vec!["client#component.js", "client#component.ts"],
3772            pattern: rcstr!("./client#component.js").into(),
3773            enable_typescript_with_output_extension: true,
3774            fully_specified: false,
3775            custom_extensions: None,
3776            // Whether or not this request key is correct somewhat ambiguous.  It depends on whether
3777            // or not we consider this fragment to be part of the request pattern
3778            expected: vec![("./client", "client#component.ts")],
3779        })
3780        .await;
3781    }
3782
3783    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3784    async fn test_fragment_with_ts_priority() {
3785        // Fragment handling with extension priority
3786        resolve_relative_request_test(TestParams {
3787            files: vec!["page#section.js", "page#section.ts"],
3788            pattern: rcstr!("./page#section").into(),
3789            enable_typescript_with_output_extension: true,
3790            fully_specified: false,
3791            custom_extensions: None,
3792            expected: vec![("./page", "page#section.ts")],
3793        })
3794        .await;
3795    }
3796
3797    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3798    async fn test_query() {
3799        resolve_relative_request_test(TestParams {
3800            files: vec!["client.ts", "client.js"],
3801            pattern: rcstr!("./client?q=s").into(),
3802            enable_typescript_with_output_extension: true,
3803            fully_specified: false,
3804            custom_extensions: None,
3805            expected: vec![("./client", "client.ts")],
3806        })
3807        .await;
3808    }
3809
3810    // Dynamic pattern tests
3811    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3812    async fn test_dynamic_pattern_with_js_extension() {
3813        // Pattern: ./src/*.js should generate multiple keys with .ts priority
3814        // When both foo.js and foo.ts exist, dynamic patterns need both keys for runtime resolution
3815        // Results are sorted alphabetically by key
3816        resolve_relative_request_test(TestParams {
3817            files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3818            pattern: Pattern::Concatenation(vec![
3819                Pattern::Constant(rcstr!("./src/")),
3820                Pattern::Dynamic,
3821                Pattern::Constant(rcstr!(".js")),
3822            ]),
3823            enable_typescript_with_output_extension: true,
3824            fully_specified: false,
3825            custom_extensions: None,
3826            expected: vec![
3827                ("./src/foo.js", "src/foo.ts"),
3828                ("./src/bar.js", "src/bar.js"),
3829            ],
3830        })
3831        .await;
3832    }
3833
3834    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3835    async fn test_dynamic_pattern_without_extension() {
3836        // Pattern: ./src/* (no extension) with TypeScript priority
3837        // Dynamic patterns generate keys for all matched files, including extension alternatives
3838        // Results are sorted deterministically by matched file name
3839        resolve_relative_request_test(TestParams {
3840            files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3841            pattern: Pattern::Concatenation(vec![
3842                Pattern::Constant(rcstr!("./src/")),
3843                Pattern::Dynamic,
3844            ]),
3845            enable_typescript_with_output_extension: true,
3846            fully_specified: false,
3847            custom_extensions: None,
3848            expected: vec![
3849                ("./src/bar.js", "src/bar.js"),
3850                ("./src/bar", "src/bar.js"),
3851                // TODO: all three should point at the .ts file
3852                // This happens because read_matches returns the `.js` file first (alphabetically
3853                // foo.js < foo.ts) and foo (extensionless) is deduped to point at foo.js since it
3854                // was the first file with that base name encountered. To fix this we would need to
3855                // change how we handle extension priority for dynamic patterns.
3856                ("./src/foo.js", "src/foo.js"),
3857                ("./src/foo", "src/foo.js"),
3858                ("./src/foo.ts", "src/foo.ts"),
3859            ],
3860        })
3861        .await;
3862    }
3863
3864    /// Test that custom `resolveExtensions` ordering is respected:
3865    /// `.web.tsx` appears before `.tsx` in the list, so it must win when both exist.
3866    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3867    async fn test_custom_extensions_web_before_default() {
3868        resolve_relative_request_test(TestParams {
3869            files: vec!["Component.web.tsx", "Component.tsx"],
3870            pattern: rcstr!("./Component").into(),
3871            enable_typescript_with_output_extension: false,
3872            fully_specified: false,
3873            custom_extensions: Some(vec![
3874                rcstr!(".web.tsx"),
3875                rcstr!(".web.ts"),
3876                rcstr!(".web.jsx"),
3877                rcstr!(".web.js"),
3878                rcstr!(".tsx"),
3879                rcstr!(".ts"),
3880                rcstr!(".jsx"),
3881                rcstr!(".js"),
3882            ]),
3883            expected: vec![("./Component", "Component.web.tsx")],
3884        })
3885        .await;
3886    }
3887
3888    /// Test that when `.web.tsx` doesn't exist, resolution falls back to `.tsx`.
3889    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3890    async fn test_custom_extensions_fallback_when_web_missing() {
3891        resolve_relative_request_test(TestParams {
3892            files: vec!["Component.tsx"],
3893            pattern: rcstr!("./Component").into(),
3894            enable_typescript_with_output_extension: false,
3895            fully_specified: false,
3896            custom_extensions: Some(vec![
3897                rcstr!(".web.tsx"),
3898                rcstr!(".web.ts"),
3899                rcstr!(".web.jsx"),
3900                rcstr!(".web.js"),
3901                rcstr!(".tsx"),
3902                rcstr!(".ts"),
3903                rcstr!(".jsx"),
3904                rcstr!(".js"),
3905            ]),
3906            expected: vec![("./Component", "Component.tsx")],
3907        })
3908        .await;
3909    }
3910
3911    /// Parameters for resolve_relative_request_test
3912    struct TestParams<'a> {
3913        files: Vec<&'a str>,
3914        pattern: Pattern,
3915        enable_typescript_with_output_extension: bool,
3916        fully_specified: bool,
3917        /// Custom extensions list; when `None`, uses the default `[".ts", ".js", ".json"]`
3918        custom_extensions: Option<Vec<RcStr>>,
3919        expected: Vec<(&'a str, &'a str)>,
3920    }
3921
3922    /// Helper function to run a single extension priority test case
3923    async fn resolve_relative_request_test(
3924        TestParams {
3925            files,
3926            pattern,
3927            enable_typescript_with_output_extension,
3928            fully_specified,
3929            custom_extensions,
3930            expected,
3931        }: TestParams<'_>,
3932    ) {
3933        let scratch = tempfile::tempdir().unwrap();
3934        {
3935            let path = scratch.path();
3936
3937            for file_name in &files {
3938                let file_path = path.join(file_name);
3939                if let Some(parent) = file_path.parent() {
3940                    create_dir_all(parent).unwrap();
3941                }
3942                File::create_new(&file_path)
3943                    .unwrap()
3944                    .write_all(format!("export default '{file_name}'").as_bytes())
3945                    .unwrap();
3946            }
3947        }
3948
3949        let path: RcStr = scratch.path().to_str().unwrap().into();
3950        let expected_owned: Vec<(String, String)> = expected
3951            .iter()
3952            .map(|(k, v)| (k.to_string(), v.to_string()))
3953            .collect();
3954
3955        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3956            BackendOptions::default(),
3957            noop_backing_storage(),
3958        ));
3959
3960        let custom_extensions_owned = custom_extensions;
3961
3962        tt.run_once(async move {
3963            #[turbo_tasks::value(transparent)]
3964            struct ResolveRelativeRequestOutput(Vec<(String, String)>);
3965
3966            #[turbo_tasks::function(operation, root)]
3967            async fn resolve_relative_request_operation(
3968                path: RcStr,
3969                pattern: Pattern,
3970                enable_typescript_with_output_extension: bool,
3971                fully_specified: bool,
3972                custom_extensions: Option<Vec<RcStr>>,
3973            ) -> Result<Vc<ResolveRelativeRequestOutput>> {
3974                let fs = DiskFileSystem::new(rcstr!("temp"), Vc::cell(path));
3975                let lookup_path = fs.root().owned().await?;
3976
3977                let result = resolve_relative_helper(
3978                    lookup_path,
3979                    pattern,
3980                    enable_typescript_with_output_extension,
3981                    fully_specified,
3982                    custom_extensions,
3983                )
3984                .await?;
3985
3986                let results: Vec<(String, String)> = result
3987                    .primary
3988                    .iter()
3989                    .map(async |(k, v)| {
3990                        Ok((
3991                            k.to_string(),
3992                            if let ResolveResultItem::Source(source) = v {
3993                                source.ident().await?.path.path.to_string()
3994                            } else {
3995                                unreachable!()
3996                            },
3997                        ))
3998                    })
3999                    .try_join()
4000                    .await?;
4001
4002                Ok(Vc::cell(results))
4003            }
4004
4005            let results = resolve_relative_request_operation(
4006                path,
4007                pattern,
4008                enable_typescript_with_output_extension,
4009                fully_specified,
4010                custom_extensions_owned,
4011            )
4012            .read_strongly_consistent()
4013            .await?;
4014
4015            assert_eq!(&*results, &expected_owned);
4016
4017            Ok(())
4018        })
4019        .await
4020        .unwrap();
4021    }
4022
4023    #[turbo_tasks::function]
4024    async fn resolve_relative_helper(
4025        lookup_path: FileSystemPath,
4026        pattern: Pattern,
4027        enable_typescript_with_output_extension: bool,
4028        fully_specified: bool,
4029        custom_extensions: Option<Vec<RcStr>>,
4030    ) -> Result<Vc<ResolveResult>> {
4031        let request = Request::parse(pattern.clone());
4032
4033        let extensions = custom_extensions
4034            .unwrap_or_else(|| vec![rcstr!(".ts"), rcstr!(".js"), rcstr!(".json")]);
4035        let mut options_value = node_esm_resolve_options()
4036            .with_fully_specified(fully_specified)
4037            .with_extensions(extensions)
4038            .owned()
4039            .await?;
4040        options_value.enable_typescript_with_output_extension =
4041            enable_typescript_with_output_extension;
4042        let options = options_value.clone().cell();
4043        match &*request.await? {
4044            Request::Relative {
4045                path,
4046                query,
4047                force_in_lookup_dir,
4048                fragment,
4049            } => {
4050                resolve_relative_request(
4051                    lookup_path,
4052                    request,
4053                    options,
4054                    &options_value,
4055                    path,
4056                    query.clone(),
4057                    *force_in_lookup_dir,
4058                    fragment.clone(),
4059                )
4060                .await
4061            }
4062            r => panic!("request should be relative, got {r:?}"),
4063        }
4064    }
4065
4066    /// Snapshot of a `ModuleResolveResult::primary` array, encoded as `Vec<String>` so it
4067    /// can cross the strongly-consistent read boundary (operation outputs need to be
4068    /// `Encode`/`Decode`). One string per entry:
4069    ///   - `module:<path>`  for `Module(_)`
4070    ///   - `dup:<i>`        for `Duplicate(i)`
4071    ///   - `other`          for everything else
4072    #[turbo_tasks::value(transparent)]
4073    pub struct DupCheckResult(Vec<String>);
4074
4075    async fn snapshot_primary(result: &ModuleResolveResult) -> Result<Vec<String>> {
4076        let mut out = Vec::with_capacity(result.primary.len());
4077        for (_, item) in result.primary.iter() {
4078            out.push(match *item {
4079                ModuleResolveResultItem::Module(m) => {
4080                    let ident = m.ident().await?;
4081                    format!("module:{}", ident.path.path)
4082                }
4083                ModuleResolveResultItem::Duplicate(i) => format!("dup:{i}"),
4084                _ => "other".to_string(),
4085            });
4086        }
4087        Ok(out)
4088    }
4089
4090    #[turbo_tasks::function]
4091    fn fs() -> Vc<Box<dyn FileSystem>> {
4092        Vc::upcast(DiskFileSystem::new(rcstr!("temp"), Vc::cell(fs_path())))
4093    }
4094
4095    #[turbo_tasks::function]
4096    async fn make_module(name: RcStr) -> Result<Vc<Box<dyn Module>>> {
4097        let path = fs().root().await?.join(&name)?;
4098        let file_content =
4099            FileContent::Content(turbo_tasks_fs::File::from(format!("// {name}"))).resolved_cell();
4100        let content = AssetContent::file(*file_content).to_resolved().await?;
4101        let source = VirtualSource::new(path, *content);
4102        let module = RawModule::new(Vc::upcast(source)).to_resolved().await?;
4103        Ok(Vc::upcast(*module))
4104    }
4105
4106    fn fs_path() -> RcStr {
4107        rcstr!("/tmp/_mdt")
4108    }
4109
4110    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4111    async fn modules_constructor_marks_module_duplicates() {
4112        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4113            BackendOptions::default(),
4114            noop_backing_storage(),
4115        ));
4116        #[turbo_tasks::function(operation, root)]
4117        async fn run_test() -> Result<Vc<DupCheckResult>> {
4118            let m_a = make_module(rcstr!("a.js")).to_resolved().await?;
4119            let m_b = make_module(rcstr!("b.js")).to_resolved().await?;
4120
4121            let result = ModuleResolveResult::modules([
4122                (RequestKey::new(rcstr!("a")), m_a),
4123                (RequestKey::new(rcstr!("b")), m_b),
4124                (RequestKey::new(rcstr!("a-again")), m_a),
4125                (RequestKey::new(rcstr!("b-again")), m_b),
4126            ])
4127            .await?;
4128
4129            // primary_modules() yields each module exactly once, in first-seen order.
4130            let modules = result.primary_modules().await?;
4131            assert_eq!(modules.as_slice(), [m_a, m_b]);
4132
4133            Ok(Vc::cell(snapshot_primary(&result).await?))
4134        }
4135        tt.run_once(async move {
4136            let snap = run_test().read_strongly_consistent().await?;
4137            assert_eq!(
4138                snap.iter().map(String::as_str).collect::<Vec<_>>(),
4139                vec!["module:a.js", "module:b.js", "dup:0", "dup:1"]
4140            );
4141            Ok(())
4142        })
4143        .await
4144        .unwrap();
4145    }
4146
4147    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4148    async fn first_module_returns_first_when_duplicates_follow() {
4149        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4150            BackendOptions::default(),
4151            noop_backing_storage(),
4152        ));
4153        #[turbo_tasks::function(operation, root)]
4154        async fn run_test() -> Result<Vc<DupCheckResult>> {
4155            let m = make_module(rcstr!("a.js")).to_resolved().await?;
4156
4157            let result = ModuleResolveResult::modules([
4158                (RequestKey::default(), m),
4159                (RequestKey::new(rcstr!("again")), m),
4160                (RequestKey::new(rcstr!("once-more")), m),
4161            ])
4162            .await?;
4163
4164            assert_eq!(result.first_module().await?, Some(m));
4165            assert_eq!(result.primary_modules().await?.as_slice(), [m]);
4166            Ok(Vc::cell(snapshot_primary(&result).await?))
4167        }
4168        tt.run_once(async move {
4169            let snap = run_test().read_strongly_consistent().await?;
4170            assert_eq!(
4171                snap.iter().map(String::as_str).collect::<Vec<_>>(),
4172                vec!["module:a.js", "dup:0", "dup:0"]
4173            );
4174            Ok(())
4175        })
4176        .await
4177        .unwrap();
4178    }
4179
4180    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4181    async fn builder_marks_module_duplicates_skipping_non_dedup_items() {
4182        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4183            BackendOptions::default(),
4184            noop_backing_storage(),
4185        ));
4186        #[turbo_tasks::function(operation, root)]
4187        async fn run_test() -> Result<Vc<DupCheckResult>> {
4188            let m = make_module(rcstr!("a.js")).to_resolved().await?;
4189
4190            let mut builder = ModuleResolveResultBuilder {
4191                primary: Default::default(),
4192                affecting_sources: Vec::new(),
4193            };
4194            builder.primary.insert(
4195                RequestKey::new(rcstr!("k0")),
4196                ModuleResolveResultItem::Module(m),
4197            );
4198            builder.primary.insert(
4199                RequestKey::new(rcstr!("k1")),
4200                ModuleResolveResultItem::Empty,
4201            );
4202            builder.primary.insert(
4203                RequestKey::new(rcstr!("k2")),
4204                ModuleResolveResultItem::Module(m),
4205            );
4206            let result: ModuleResolveResult = builder.into();
4207            assert_eq!(result.primary_modules().await?.as_slice(), [m]);
4208            Ok(Vc::cell(snapshot_primary(&result).await?))
4209        }
4210        tt.run_once(async move {
4211            let snap = run_test().read_strongly_consistent().await?;
4212
4213            assert_eq!(
4214                snap.iter().map(String::as_str).collect::<Vec<_>>(),
4215                vec!["module:a.js", "other", "dup:0"]
4216            );
4217            Ok(())
4218        })
4219        .await
4220        .unwrap();
4221    }
4222
4223    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4224    async fn alternatives_preserves_unique_module_set() {
4225        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4226            BackendOptions::default(),
4227            noop_backing_storage(),
4228        ));
4229        #[turbo_tasks::function(operation, root)]
4230        async fn run_test() -> Result<Vc<DupCheckResult>> {
4231            let m_a = make_module(rcstr!("a.js")).to_resolved().await?;
4232            let m_b = make_module(rcstr!("b.js")).to_resolved().await?;
4233
4234            // r1 has m_a twice → Module(m_a), Duplicate(0).
4235            let r1 = *ModuleResolveResult::modules([
4236                (RequestKey::new(rcstr!("k1")), m_a),
4237                (RequestKey::new(rcstr!("k2")), m_a),
4238            ]);
4239            // r2 prepended with m_b so the ordering inside r2 puts m_b at index 0 — a "stale"
4240            // 0 from r1 would now incorrectly point at m_b after a naive concatenation.
4241            let r2 = *ModuleResolveResult::module(m_b);
4242
4243            let merged = ModuleResolveResult::alternatives(vec![r1, r2]).await?;
4244            assert_eq!(merged.primary_modules().await?.as_slice(), [m_a, m_b]);
4245
4246            // Verify every Duplicate(i) is well-formed
4247            for (i, (_, item)) in merged.primary.iter().enumerate() {
4248                if let ModuleResolveResultItem::Duplicate(first) = *item {
4249                    assert!(
4250                        first < i,
4251                        "Duplicate index {first} at position {i} must point backwards"
4252                    );
4253                    let pointed = &merged.primary[first].1;
4254                    let ModuleResolveResultItem::Module(pointed_module) = *pointed else {
4255                        panic!(
4256                            "Duplicate({first}) at {i} points at {pointed:?}, expected a concrete \
4257                             Module"
4258                        );
4259                    };
4260                    // The pointed-at module must be m_a — proves the index was re-derived
4261                    // against the merged array, not carried stale from r1.
4262                    assert_eq!(
4263                        pointed_module, m_a,
4264                        "Duplicate({first}) at position {i} points at the wrong module"
4265                    );
4266                }
4267            }
4268            Ok(Vc::cell(snapshot_primary(&merged).await?))
4269        }
4270        tt.run_once(async move {
4271            let snap = run_test().read_strongly_consistent().await?;
4272
4273            assert_eq!(
4274                snap.iter().map(String::as_str).collect::<Vec<_>>(),
4275                vec!["module:a.js", "dup:0", "module:b.js"]
4276            );
4277            Ok(())
4278        })
4279        .await
4280        .unwrap();
4281    }
4282
4283    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4284    async fn concat_preserves_distinct_modules_with_same_request_key() {
4285        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4286            BackendOptions::default(),
4287            noop_backing_storage(),
4288        ));
4289        #[turbo_tasks::function(operation, root)]
4290        async fn run_test() -> Result<Vc<DupCheckResult>> {
4291            let app = make_module(rcstr!("app.js")).to_resolved().await?;
4292            let package = make_module(rcstr!("package.js")).to_resolved().await?;
4293            let key = RequestKey::new(rcstr!("./config.js"));
4294            let app_result = *ModuleResolveResult::module_with_key(key.clone(), app);
4295            let package_result = *ModuleResolveResult::modules([
4296                (key, package),
4297                (RequestKey::new(rcstr!("also-app")), app),
4298            ]);
4299            let combined = ModuleResolveResult::concat(vec![app_result, package_result]).await?;
4300            assert_eq!(combined.primary_modules().await?.as_slice(), [app, package]);
4301            Ok(Vc::cell(snapshot_primary(&combined).await?))
4302        }
4303        tt.run_once(async move {
4304            let snap = run_test().read_strongly_consistent().await?;
4305            assert_eq!(
4306                snap.iter().map(String::as_str).collect::<Vec<_>>(),
4307                vec!["module:app.js", "module:package.js", "dup:0"]
4308            );
4309            Ok(())
4310        })
4311        .await
4312        .unwrap();
4313    }
4314}