next.js/turbopack/crates/turbopack-core/src/resolve/mod.rs
mod.rs3805 lines129.5 KB
use std::{
    borrow::Cow,
    collections::BTreeMap,
    fmt::{Display, Formatter, Write},
    future::Future,
    iter::{empty, once},
    sync::LazyLock,
};

use anyhow::{Result, bail};
use bincode::{Decode, Encode};
use either::Either;
use rustc_hash::{FxHashMap, FxHashSet};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use tracing::{Instrument, Level};
use turbo_frozenmap::{FrozenMap, FrozenSet};
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{
    FxIndexMap, FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, TaskInput, TryFlatJoinIterExt,
    TryJoinIterExt, ValueToString, ValueToStringRef, Vc, trace::TraceRawVcs,
};
use turbo_tasks_fs::{FileSystemEntryType, FileSystemPath};
use turbo_unix_path::normalize_request;

use crate::{
    context::AssetContext,
    data_uri_source::DataUriSource,
    file_source::FileSource,
    issue::{
        Issue, IssueExt, IssueSource, module::emit_unknown_module_type_error,
        resolve::ResolvingIssue,
    },
    module::{Module, Modules, OptionModule},
    package_json::{PackageJsonIssue, read_package_json},
    raw_module::RawModule,
    reference_type::ReferenceType,
    resolve::{
        alias_map::AliasKey,
        error::{handle_resolve_error, resolve_error_severity},
        node::{node_cjs_resolve_options, node_esm_resolve_options},
        options::{
            ConditionValue, ImportMapResult, ResolveInPackage, ResolveIntoPackage, ResolveModules,
            ResolveModulesOptions, ResolveOptions, resolve_modules_options,
        },
        origin::ResolveOrigin,
        parse::{Request, stringify_data_uri},
        pattern::{Pattern, PatternMatch, read_matches},
        plugin::{AfterResolvePlugin, AfterResolvePluginCondition, BeforeResolvePlugin},
        remap::{ExportsField, ImportsField, ReplacedSubpathValueResult},
    },
    source::{OptionSource, Source, Sources},
};

mod alias_map;
pub mod error;
pub mod node;
pub mod options;
pub mod origin;
pub mod parse;
pub mod pattern;
pub mod plugin;
pub(crate) mod remap;

pub use alias_map::{
    AliasMap, AliasMapIntoIter, AliasMapLookupIterator, AliasMatch, AliasPattern, AliasTemplate,
};
pub use remap::{ResolveAliasMap, SubpathValue};

/// Controls how resolve errors are handled.
#[turbo_tasks::value(shared)]
#[derive(Debug, Clone, Copy, Default, Hash, TaskInput)]
pub enum ResolveErrorMode {
    /// Emit an error issue (default behavior)
    #[default]
    Error,
    /// Emit a warning issue (e.g., when inside a try-catch block)
    Warn,
    /// Completely ignore the error (e.g., when marked with `turbopackOptional`)
    Ignore,
}

/// Type alias for a resolved after-resolve plugin paired with its condition.
type AfterResolvePluginWithCondition = (
    ResolvedVc<Box<dyn AfterResolvePlugin>>,
    ResolvedVc<AfterResolvePluginCondition>,
);

#[turbo_tasks::value(shared)]
#[derive(Clone, Debug)]
pub enum ModuleResolveResultItem {
    Module(ResolvedVc<Box<dyn Module>>),
    External {
        /// uri, path, reference, etc.
        name: RcStr,
        ty: ExternalType,
    },
    /// A module could not be created (according to the rules, e.g. no module type as assigned)
    Unknown(ResolvedVc<Box<dyn Source>>),
    /// Completely ignore this reference.
    Ignore,
    /// Emit the given issue, and generate a module which throws that issue's title at runtime.
    Error(ResolvedVc<Box<dyn Issue>>),
    /// Resolve the reference to an empty module.
    Empty,
    Custom(u8),
}

impl ModuleResolveResultItem {
    async fn as_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
        Ok(match *self {
            ModuleResolveResultItem::Module(module) => Some(module),
            ModuleResolveResultItem::Unknown(source) => {
                emit_unknown_module_type_error(*source).await?;
                None
            }
            ModuleResolveResultItem::Error(_err) => {
                // TODO emit error?
                None
            }
            _ => None,
        })
    }
}

#[turbo_tasks::value(shared)]
#[derive(Clone, Debug, Hash, Default, Serialize, Deserialize)]
pub struct BindingUsage {
    pub import: ImportUsage,
    pub export: ExportUsage,
}

#[turbo_tasks::value_impl]
impl BindingUsage {
    #[turbo_tasks::function]
    pub fn all() -> Vc<Self> {
        Self::default().cell()
    }
}

/// Defines where an import is used in a module
#[turbo_tasks::value(shared)]
#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
pub enum ImportUsage {
    /// This import is used at the top level of the module.  For example, for module level side
    /// effects
    #[default]
    TopLevel,
    /// This import is used only by these specific exports, if all exports are unused, the import
    /// can also be removed.
    ///
    /// (This is only ever set on `ModulePart::Export` references. Side effects are handled via
    /// `ModulePart::Evaluation` references, which always have `ImportUsage::TopLevel`.)
    Exports(FrozenSet<RcStr>),
}

/// Defines what parts of a module are used by another module
#[turbo_tasks::value]
#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
pub enum ExportUsage {
    Named(RcStr),
    /// Multiple named exports are used via a partial namespace object.
    PartialNamespaceObject(SmallVec<[RcStr; 1]>),
    /// This means the whole content of the module is used.
    #[default]
    All,
    /// Only side effects are used.
    Evaluation,
}

impl Display for ExportUsage {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ExportUsage::Named(name) => write!(f, "export {name}"),
            ExportUsage::PartialNamespaceObject(names) => {
                write!(f, "exports ")?;
                for (i, name) in names.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{name}")?;
                }
                Ok(())
            }
            ExportUsage::All => write!(f, "all"),
            ExportUsage::Evaluation => write!(f, "evaluation"),
        }
    }
}

#[turbo_tasks::value_impl]
impl ExportUsage {
    #[turbo_tasks::function]
    pub fn all() -> Vc<Self> {
        Self::All.cell()
    }

    #[turbo_tasks::function]
    pub fn evaluation() -> Vc<Self> {
        Self::Evaluation.cell()
    }

    #[turbo_tasks::function]
    pub fn named(name: RcStr) -> Vc<Self> {
        Self::Named(name).cell()
    }
}

#[turbo_tasks::value(shared)]
#[derive(Clone, Debug)]
pub struct ModuleResolveResult {
    pub primary: Box<[(RequestKey, ModuleResolveResultItem)]>,
    /// Affecting sources are other files that influence the resolve result.  For example,
    /// traversed symlinks
    pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
}

impl ModuleResolveResult {
    pub fn unresolvable() -> ResolvedVc<Self> {
        ModuleResolveResult {
            primary: Default::default(),
            affecting_sources: Default::default(),
        }
        .resolved_cell()
    }

    pub fn module(module: ResolvedVc<Box<dyn Module>>) -> ResolvedVc<Self> {
        Self::module_with_key(RequestKey::default(), module)
    }

    pub fn module_with_key(
        request_key: RequestKey,
        module: ResolvedVc<Box<dyn Module>>,
    ) -> ResolvedVc<Self> {
        ModuleResolveResult {
            primary: vec![(request_key, ModuleResolveResultItem::Module(module))]
                .into_boxed_slice(),
            affecting_sources: Default::default(),
        }
        .resolved_cell()
    }

    pub fn modules(
        modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
    ) -> ResolvedVc<Self> {
        ModuleResolveResult {
            primary: modules
                .into_iter()
                .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
                .collect(),
            affecting_sources: Default::default(),
        }
        .resolved_cell()
    }

    pub fn modules_with_affecting_sources(
        modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
    ) -> ResolvedVc<Self> {
        ModuleResolveResult {
            primary: modules
                .into_iter()
                .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
                .collect(),
            affecting_sources: affecting_sources.into_boxed_slice(),
        }
        .resolved_cell()
    }
}

impl ModuleResolveResult {
    /// Returns all module results (but ignoring any errors).
    pub fn primary_modules_raw_iter(
        &self,
    ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
        self.primary.iter().filter_map(|(_, item)| match *item {
            ModuleResolveResultItem::Module(a) => Some(a),
            _ => None,
        })
    }

    /// Returns a set (no duplicates) of primary modules in the result.
    pub async fn primary_modules_ref(&self) -> Result<Vec<ResolvedVc<Box<dyn Module>>>> {
        let mut set = FxIndexSet::default();
        for (_, item) in self.primary.iter() {
            if let Some(module) = item.as_module().await? {
                set.insert(module);
            }
        }
        Ok(set.into_iter().collect())
    }

    pub fn affecting_sources_iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
        self.affecting_sources.iter().copied()
    }

    pub fn is_unresolvable_ref(&self) -> bool {
        self.primary.is_empty()
    }

    pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
        self.primary.iter().filter_map(|i| match &i.1 {
            ModuleResolveResultItem::Error(e) => Some(*e),
            _ => None,
        })
    }
}

pub struct ModuleResolveResultBuilder {
    pub primary: FxIndexMap<RequestKey, ModuleResolveResultItem>,
    pub affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
}

impl From<ModuleResolveResultBuilder> for ModuleResolveResult {
    fn from(v: ModuleResolveResultBuilder) -> Self {
        ModuleResolveResult {
            primary: v.primary.into_iter().collect(),
            affecting_sources: v.affecting_sources.into_boxed_slice(),
        }
    }
}
impl From<ModuleResolveResult> for ModuleResolveResultBuilder {
    fn from(v: ModuleResolveResult) -> Self {
        ModuleResolveResultBuilder {
            primary: IntoIterator::into_iter(v.primary).collect(),
            affecting_sources: v.affecting_sources.into_vec(),
        }
    }
}
impl ModuleResolveResultBuilder {
    pub fn merge_alternatives(&mut self, other: &ModuleResolveResult) {
        for (k, v) in other.primary.iter() {
            if !self.primary.contains_key(k) {
                self.primary.insert(k.clone(), v.clone());
            }
        }
        let set = self
            .affecting_sources
            .iter()
            .copied()
            .collect::<FxHashSet<_>>();
        self.affecting_sources.extend(
            other
                .affecting_sources
                .iter()
                .filter(|source| !set.contains(source))
                .copied(),
        );
    }
}

#[turbo_tasks::value_impl]
impl ModuleResolveResult {
    #[turbo_tasks::function]
    pub async fn alternatives(results: Vec<Vc<ModuleResolveResult>>) -> Result<Vc<Self>> {
        if results.len() == 1 {
            return Ok(results.into_iter().next().unwrap());
        }
        let mut iter = results.into_iter().try_join().await?.into_iter();
        if let Some(current) = iter.next() {
            let mut current: ModuleResolveResultBuilder = ReadRef::into_owned(current).into();
            for result in iter {
                // For clippy -- This explicit deref is necessary
                let other = &*result;
                current.merge_alternatives(other);
            }
            Ok(Self::cell(current.into()))
        } else {
            Ok(*ModuleResolveResult::unresolvable())
        }
    }

    #[turbo_tasks::function]
    pub fn is_unresolvable(&self) -> Vc<bool> {
        Vc::cell(self.is_unresolvable_ref())
    }

    #[turbo_tasks::function]
    pub async fn first_module(&self) -> Result<Vc<OptionModule>> {
        for (_, item) in self.primary.iter() {
            if let Some(module) = item.as_module().await? {
                return Ok(Vc::cell(Some(module)));
            }
        }
        Ok(Vc::cell(None))
    }

    /// Returns a set (no duplicates) of primary modules in the result. All
    /// modules are already resolved Vc.
    #[turbo_tasks::function]
    pub async fn primary_modules(&self) -> Result<Vc<Modules>> {
        let mut set = FxIndexSet::default();
        for (_, item) in self.primary.iter() {
            if let Some(module) = item.as_module().await? {
                set.insert(module);
            }
        }
        Ok(Vc::cell(set.into_iter().collect()))
    }
}

#[derive(
    Copy,
    Clone,
    Debug,
    PartialEq,
    Eq,
    TaskInput,
    Hash,
    NonLocalValue,
    TraceRawVcs,
    Serialize,
    Deserialize,
    Encode,
    Decode,
)]
pub enum ExternalTraced {
    Untraced,
    Traced,
}

impl Display for ExternalTraced {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ExternalTraced::Untraced => write!(f, "untraced"),
            ExternalTraced::Traced => write!(f, "traced"),
        }
    }
}

#[derive(
    Copy,
    Clone,
    Debug,
    Eq,
    PartialEq,
    Hash,
    Serialize,
    Deserialize,
    TraceRawVcs,
    TaskInput,
    NonLocalValue,
    Encode,
    Decode,
)]
pub enum ExternalType {
    Url,
    CommonJs,
    EcmaScriptModule,
    Global,
    Script,
}

impl Display for ExternalType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ExternalType::CommonJs => write!(f, "commonjs"),
            ExternalType::EcmaScriptModule => write!(f, "esm"),
            ExternalType::Url => write!(f, "url"),
            ExternalType::Global => write!(f, "global"),
            ExternalType::Script => write!(f, "script"),
        }
    }
}

#[turbo_tasks::value(shared)]
#[derive(Debug, Clone)]
pub enum ResolveResultItem {
    Source(ResolvedVc<Box<dyn Source>>),
    External {
        /// uri, path, reference, etc.
        name: RcStr,
        ty: ExternalType,
        traced: ExternalTraced,
        /// The file path to the resolved file. Passing a value will create a symlink in the output
        /// root to be able to access potentially transitive dependencies.
        target: Option<FileSystemPath>,
    },
    /// Completely ignore this reference.
    Ignore,
    /// Emit the given issue, and generate a module which throws that issue's title at runtime.
    Error(ResolvedVc<Box<dyn Issue>>),
    /// Resolve the reference to an empty module.
    Empty,
    Custom(u8),
}

/// Represents the key for a request that leads to a certain results during
/// resolving.
///
/// A primary factor is the actual request string, but there are
/// other factors like exports conditions that can affect resolving and become
/// part of the key (assuming the condition is unknown at compile time)
#[derive(Clone, Debug, Default, Hash, TaskInput)]
#[turbo_tasks::value]
pub struct RequestKey {
    pub request: Option<RcStr>,
    pub conditions: FrozenMap<RcStr, bool>,
}

impl Display for RequestKey {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        if let Some(request) = &self.request {
            write!(f, "{request}")?;
        } else {
            write!(f, "<default>")?;
        }
        if !self.conditions.is_empty() {
            write!(f, " (")?;
            for (i, (k, v)) in self.conditions.iter().enumerate() {
                if i > 0 {
                    write!(f, ", ")?;
                }
                write!(f, "{k}={v}")?;
            }
            write!(f, ")")?;
        }
        Ok(())
    }
}

impl RequestKey {
    pub fn new(request: RcStr) -> Self {
        RequestKey {
            request: Some(request),
            ..Default::default()
        }
    }
}

#[turbo_tasks::value(shared)]
#[derive(Clone)]
pub struct ResolveResult {
    pub primary: Box<[(RequestKey, ResolveResultItem)]>,
    /// Affecting sources are other files that influence the resolve result.  For example,
    /// traversed symlinks
    pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
}

#[turbo_tasks::value_impl]
impl ValueToString for ResolveResult {
    #[turbo_tasks::function]
    async fn to_string(&self) -> Result<Vc<RcStr>> {
        let mut result = String::new();
        if self.is_unresolvable_ref() {
            result.push_str("unresolvable");
        }
        for (i, (request, item)) in self.primary.iter().enumerate() {
            if i > 0 {
                result.push_str(", ");
            }
            write!(result, "{request} -> ").unwrap();
            match item {
                ResolveResultItem::Source(a) => {
                    result.push_str(&a.ident().to_string().await?);
                }
                ResolveResultItem::External {
                    name: s,
                    ty,
                    traced,
                    target,
                } => {
                    result.push_str("external ");
                    result.push_str(s);
                    write!(
                        result,
                        " ({ty}, {traced}, {:?})",
                        if let Some(target) = target {
                            Some(target.to_string_ref().await?)
                        } else {
                            None
                        }
                    )?;
                }
                ResolveResultItem::Ignore => {
                    result.push_str("ignore");
                }
                ResolveResultItem::Empty => {
                    result.push_str("empty");
                }
                ResolveResultItem::Error(_) => {
                    result.push_str("error");
                }
                ResolveResultItem::Custom(_) => {
                    result.push_str("custom");
                }
            }
            result.push('\n');
        }
        if !self.affecting_sources.is_empty() {
            result.push_str(" (affecting sources: ");
            for (i, source) in self.affecting_sources.iter().enumerate() {
                if i > 0 {
                    result.push_str(", ");
                }
                result.push_str(&source.ident().to_string().await?);
            }
            result.push(')');
        }
        Ok(Vc::cell(result.into()))
    }
}

impl ResolveResult {
    pub fn unresolvable() -> Self {
        ResolveResult {
            primary: Default::default(),
            affecting_sources: Default::default(),
        }
    }

    pub fn unresolvable_with_affecting_sources(
        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
    ) -> Self {
        ResolveResult {
            primary: Default::default(),
            affecting_sources: affecting_sources.into_boxed_slice(),
        }
    }

    pub fn primary(result: ResolveResultItem) -> Self {
        Self::primary_with_key(RequestKey::default(), result)
    }

    pub fn primary_with_key(request_key: RequestKey, result: ResolveResultItem) -> Self {
        ResolveResult {
            primary: vec![(request_key, result)].into_boxed_slice(),
            affecting_sources: Default::default(),
        }
    }

    pub fn primary_with_affecting_sources(
        request_key: RequestKey,
        result: ResolveResultItem,
        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
    ) -> Self {
        ResolveResult {
            primary: vec![(request_key, result)].into_boxed_slice(),
            affecting_sources: affecting_sources.into_boxed_slice(),
        }
    }

    pub fn source(source: ResolvedVc<Box<dyn Source>>) -> Self {
        Self::source_with_key(RequestKey::default(), source)
    }

    fn source_with_key(request_key: RequestKey, source: ResolvedVc<Box<dyn Source>>) -> Self {
        ResolveResult {
            primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
            affecting_sources: Default::default(),
        }
    }

    fn source_with_affecting_sources(
        request_key: RequestKey,
        source: ResolvedVc<Box<dyn Source>>,
        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
    ) -> Self {
        ResolveResult {
            primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
            affecting_sources: affecting_sources.into_boxed_slice(),
        }
    }

    pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
        self.primary.iter().filter_map(|i| match &i.1 {
            ResolveResultItem::Error(e) => Some(*e),
            _ => None,
        })
    }
}

impl ResolveResult {
    /// Returns the affecting sources for this result. Will be empty if affecting sources are
    /// disabled for this result.
    pub fn get_affecting_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
        self.affecting_sources.iter().copied()
    }

    pub fn is_unresolvable_ref(&self) -> bool {
        self.primary.is_empty()
    }

    pub async fn map_module<A, AF>(&self, source_fn: A) -> Result<ModuleResolveResult>
    where
        A: Fn(ResolvedVc<Box<dyn Source>>) -> AF,
        AF: Future<Output = Result<ModuleResolveResultItem>>,
    {
        Ok(ModuleResolveResult {
            primary: self
                .primary
                .iter()
                .map(|(request, item)| {
                    let asset_fn = &source_fn;
                    let request = request.clone();
                    let item = item.clone();
                    async move {
                        Ok((
                            request,
                            match item {
                                ResolveResultItem::Source(source) => asset_fn(source).await?,
                                ResolveResultItem::External {
                                    name,
                                    ty,
                                    traced,
                                    target,
                                } => {
                                    if traced == ExternalTraced::Traced || target.is_some() {
                                        // Should use map_primary_items instead
                                        bail!("map_module doesn't handle traced externals");
                                    }
                                    ModuleResolveResultItem::External { name, ty }
                                }
                                ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore,
                                ResolveResultItem::Empty => ModuleResolveResultItem::Empty,
                                ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e),
                                ResolveResultItem::Custom(u8) => {
                                    ModuleResolveResultItem::Custom(u8)
                                }
                            },
                        ))
                    }
                })
                .try_join()
                .await?
                .into_iter()
                .collect(),
            affecting_sources: self.affecting_sources.clone(),
        })
    }

    pub async fn map_primary_items<A, AF>(&self, item_fn: A) -> Result<ModuleResolveResult>
    where
        A: Fn(ResolveResultItem) -> AF,
        AF: Future<Output = Result<ModuleResolveResultItem>>,
    {
        Ok(ModuleResolveResult {
            primary: self
                .primary
                .iter()
                .map(|(request, item)| {
                    let asset_fn = &item_fn;
                    let request = request.clone();
                    let item = item.clone();
                    async move { Ok((request, asset_fn(item).await?)) }
                })
                .try_join()
                .await?
                .into_iter()
                .collect(),
            affecting_sources: self.affecting_sources.clone(),
        })
    }

    /// Returns a new [ResolveResult] where all [RequestKey]s are set to the
    /// passed `request`.
    fn with_request_ref(&self, request: RcStr) -> Self {
        let new_primary = self
            .primary
            .iter()
            .map(|(k, v)| {
                (
                    RequestKey {
                        request: Some(request.clone()),
                        conditions: k.conditions.clone(),
                    },
                    v.clone(),
                )
            })
            .collect();
        ResolveResult {
            primary: new_primary,
            affecting_sources: self.affecting_sources.clone(),
        }
    }

    pub fn with_conditions(&self, new_conditions: &[(RcStr, bool)]) -> Self {
        let primary = self
            .primary
            .iter()
            .map(|(k, v)| {
                (
                    RequestKey {
                        request: k.request.clone(),
                        conditions: k.conditions.extend(new_conditions.iter().cloned()),
                    },
                    v.clone(),
                )
            })
            .collect::<FxIndexMap<_, _>>() // Deduplicate
            .into_iter()
            .collect();
        ResolveResult {
            primary,
            affecting_sources: self.affecting_sources.clone(),
        }
    }
}

struct ResolveResultBuilder {
    primary: FxIndexMap<RequestKey, ResolveResultItem>,
    affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
}

impl From<ResolveResultBuilder> for ResolveResult {
    fn from(v: ResolveResultBuilder) -> Self {
        ResolveResult {
            primary: v.primary.into_iter().collect(),
            affecting_sources: v.affecting_sources.into_boxed_slice(),
        }
    }
}
impl From<ResolveResult> for ResolveResultBuilder {
    fn from(v: ResolveResult) -> Self {
        ResolveResultBuilder {
            primary: IntoIterator::into_iter(v.primary).collect(),
            affecting_sources: v.affecting_sources.into_vec(),
        }
    }
}
impl ResolveResultBuilder {
    pub fn merge_alternatives(&mut self, other: &ResolveResult) {
        for (k, v) in other.primary.iter() {
            if !self.primary.contains_key(k) {
                self.primary.insert(k.clone(), v.clone());
            }
        }
        let set = self
            .affecting_sources
            .iter()
            .copied()
            .collect::<FxHashSet<_>>();
        self.affecting_sources.extend(
            other
                .affecting_sources
                .iter()
                .filter(|source| !set.contains(source))
                .copied(),
        );
    }
}

#[turbo_tasks::value_impl]
impl ResolveResult {
    #[turbo_tasks::function]
    pub async fn as_raw_module_result(&self) -> Result<Vc<ModuleResolveResult>> {
        Ok(self
            .map_module(|asset| async move {
                Ok(ModuleResolveResultItem::Module(ResolvedVc::upcast(
                    RawModule::new(*asset).to_resolved().await?,
                )))
            })
            .await?
            .cell())
    }

    #[turbo_tasks::function]
    fn with_affecting_sources(
        &self,
        sources: Vec<ResolvedVc<Box<dyn Source>>>,
    ) -> Result<Vc<Self>> {
        Ok(Self {
            primary: self.primary.clone(),
            affecting_sources: self
                .affecting_sources
                .iter()
                .copied()
                .chain(sources)
                .collect(),
        }
        .cell())
    }

    #[turbo_tasks::function]
    async fn alternatives(results: Vec<Vc<ResolveResult>>) -> Result<Vc<Self>> {
        if results.len() == 1 {
            return Ok(results.into_iter().next().unwrap());
        }
        let mut iter = results.into_iter().try_join().await?.into_iter();
        if let Some(current) = iter.next() {
            let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
            for result in iter {
                // For clippy -- This explicit deref is necessary
                let other = &*result;
                current.merge_alternatives(other);
            }
            Ok(Self::cell(current.into()))
        } else {
            Ok(ResolveResult::unresolvable().cell())
        }
    }

    #[turbo_tasks::function]
    async fn alternatives_with_affecting_sources(
        results: Vec<Vc<ResolveResult>>,
        affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
    ) -> Result<Vc<Self>> {
        debug_assert!(
            !affecting_sources.is_empty(),
            "Caller should not call this function if there are no affecting sources"
        );
        if results.len() == 1 {
            return Ok(results
                .into_iter()
                .next()
                .unwrap()
                .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()));
        }
        let mut iter = results.into_iter().try_join().await?.into_iter();
        if let Some(current) = iter.next() {
            let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
            for result in iter {
                // For clippy -- This explicit deref is necessary
                let other = &*result;
                current.merge_alternatives(other);
            }
            current.affecting_sources.extend(affecting_sources);
            Ok(Self::cell(current.into()))
        } else {
            Ok(ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell())
        }
    }

    #[turbo_tasks::function]
    pub fn is_unresolvable(&self) -> Vc<bool> {
        Vc::cell(self.is_unresolvable_ref())
    }

    #[turbo_tasks::function]
    pub fn first_source(&self) -> Vc<OptionSource> {
        Vc::cell(self.primary.iter().find_map(|(_, item)| {
            if let &ResolveResultItem::Source(a) = item {
                Some(a)
            } else {
                None
            }
        }))
    }

    #[turbo_tasks::function]
    pub fn primary_sources(&self) -> Vc<Sources> {
        Vc::cell(
            self.primary
                .iter()
                .filter_map(|(_, item)| {
                    if let &ResolveResultItem::Source(a) = item {
                        Some(a)
                    } else {
                        None
                    }
                })
                .collect(),
        )
    }

    /// Returns a new [ResolveResult] where all [RequestKey]s are updated. The `old_request_key`
    /// (prefix) is replaced with the `request_key`. It's not expected that the [ResolveResult]
    /// contains [RequestKey]s that don't have the `old_request_key` prefix, but if there are still
    /// some, they are discarded.
    #[turbo_tasks::function]
    fn with_replaced_request_key(
        &self,
        old_request_key: RcStr,
        request_key: RequestKey,
    ) -> Result<Vc<Self>> {
        let new_primary = self
            .primary
            .iter()
            .filter_map(|(k, v)| {
                let remaining = k.request.as_ref()?.strip_prefix(&*old_request_key)?;
                Some((
                    RequestKey {
                        request: request_key
                            .request
                            .as_ref()
                            .map(|r| format!("{r}{remaining}").into()),
                        conditions: request_key.conditions.clone(),
                    },
                    v.clone(),
                ))
            })
            .collect();
        Ok(ResolveResult {
            primary: new_primary,
            affecting_sources: self.affecting_sources.clone(),
        }
        .cell())
    }

    /// Returns a new [ResolveResult] where all [RequestKey]s are updated. The prefix is removed
    /// from all [RequestKey]s. It's not expected that the [ResolveResult] contains [RequestKey]s
    /// without the prefix, but if there are still some, they are discarded.
    #[turbo_tasks::function]
    fn with_stripped_request_key_prefix(&self, prefix: RcStr) -> Result<Vc<Self>> {
        let new_primary = self
            .primary
            .iter()
            .filter_map(|(k, v)| {
                let remaining = k.request.as_ref()?.strip_prefix(&*prefix)?;
                Some((
                    RequestKey {
                        request: Some(remaining.into()),
                        conditions: k.conditions.clone(),
                    },
                    v.clone(),
                ))
            })
            .collect();
        Ok(ResolveResult {
            primary: new_primary,
            affecting_sources: self.affecting_sources.clone(),
        }
        .cell())
    }

    /// Returns a new [ResolveResult] where all [RequestKey]s are updated. All keys matching
    /// `old_request_key` are rewritten according to `request_key`. It's not expected that the
    /// [ResolveResult] contains [RequestKey]s that do not match the `old_request_key` prefix, but
    /// if there are still some, they are discarded.
    #[turbo_tasks::function]
    async fn with_replaced_request_key_pattern(
        &self,
        old_request_key: Vc<Pattern>,
        request_key: Vc<Pattern>,
    ) -> Result<Vc<Self>> {
        let old_request_key = &*old_request_key.await?;
        let request_key = &*request_key.await?;

        let new_primary = self
            .primary
            .iter()
            .map(|(k, v)| {
                (
                    RequestKey {
                        request: k
                            .request
                            .as_ref()
                            .and_then(|r| old_request_key.match_apply_template(r, request_key))
                            .map(Into::into),
                        conditions: k.conditions.clone(),
                    },
                    v.clone(),
                )
            })
            .collect();
        Ok(ResolveResult {
            primary: new_primary,
            affecting_sources: self.affecting_sources.clone(),
        }
        .cell())
    }

    /// Returns a new [ResolveResult] where all [RequestKey]s are set to the
    /// passed `request`.
    #[turbo_tasks::function]
    fn with_request(&self, request: RcStr) -> Vc<Self> {
        let new_primary = self
            .primary
            .iter()
            .map(|(k, v)| {
                (
                    RequestKey {
                        request: Some(request.clone()),
                        conditions: k.conditions.clone(),
                    },
                    v.clone(),
                )
            })
            .collect();
        ResolveResult {
            primary: new_primary,
            affecting_sources: self.affecting_sources.clone(),
        }
        .cell()
    }
}

#[turbo_tasks::value(transparent)]
pub struct ResolveResultOption(Option<ResolvedVc<ResolveResult>>);

#[turbo_tasks::value_impl]
impl ResolveResultOption {
    #[turbo_tasks::function]
    pub fn some(result: ResolvedVc<ResolveResult>) -> Vc<Self> {
        ResolveResultOption(Some(result)).cell()
    }

    #[turbo_tasks::function]
    pub fn none() -> Vc<Self> {
        ResolveResultOption(None).cell()
    }
}

async fn exists(
    fs_path: &FileSystemPath,
    refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
) -> Result<Option<FileSystemPath>> {
    type_exists(fs_path, FileSystemEntryType::File, refs).await
}

async fn dir_exists(
    fs_path: &FileSystemPath,
    refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
) -> Result<Option<FileSystemPath>> {
    type_exists(fs_path, FileSystemEntryType::Directory, refs).await
}

async fn type_exists(
    fs_path: &FileSystemPath,
    ty: FileSystemEntryType,
    refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
) -> Result<Option<FileSystemPath>> {
    let path = realpath(fs_path, refs).await?;
    Ok(if *path.get_type().await? == ty {
        Some(path)
    } else {
        None
    })
}

async fn realpath(
    fs_path: &FileSystemPath,
    refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
) -> Result<FileSystemPath> {
    let result = fs_path.realpath_with_links().await?;
    if let Some(refs) = refs {
        refs.extend(
            result
                .symlinks
                .iter()
                .map(|path| async move {
                    Ok(ResolvedVc::upcast(
                        FileSource::new(path.clone()).to_resolved().await?,
                    ))
                })
                .try_join()
                .await?,
        );
    }
    match &result.path_result {
        Ok(path) => Ok(path.clone()),
        Err(e) => bail!(e.as_error_message(fs_path, &result).await?),
    }
}

#[turbo_tasks::value(shared)]
enum ExportsFieldResult {
    Some(#[turbo_tasks(debug_ignore, trace_ignore)] ExportsField),
    None,
}

/// Extracts the "exports" field out of the package.json, parsing it into an
/// appropriate [AliasMap] for lookups.
#[turbo_tasks::function]
async fn exports_field(
    package_json_path: ResolvedVc<Box<dyn Source>>,
) -> Result<Vc<ExportsFieldResult>> {
    let read = read_package_json(*package_json_path).await?;
    let package_json = match &*read {
        Some(json) => json,
        None => return Ok(ExportsFieldResult::None.cell()),
    };

    let Some(exports) = package_json.get("exports") else {
        return Ok(ExportsFieldResult::None.cell());
    };
    match exports.try_into() {
        Ok(exports) => Ok(ExportsFieldResult::Some(exports).cell()),
        Err(err) => {
            PackageJsonIssue {
                error_message: err.to_string().into(),
                // TODO(PACK-4879): add line column information
                source: IssueSource::from_source_only(package_json_path),
            }
            .resolved_cell()
            .emit();
            Ok(ExportsFieldResult::None.cell())
        }
    }
}

#[turbo_tasks::value(shared)]
enum ImportsFieldResult {
    Some(
        #[turbo_tasks(debug_ignore, trace_ignore)] ImportsField,
        FileSystemPath,
    ),
    None,
}

/// Extracts the "imports" field out of the nearest package.json, parsing it
/// into an appropriate [AliasMap] for lookups.
#[turbo_tasks::function]
async fn imports_field(lookup_path: FileSystemPath) -> Result<Vc<ImportsFieldResult>> {
    // We don't need to collect affecting sources here because we don't use them
    let package_json_context =
        find_context_file(lookup_path, *package_json().to_resolved().await?, false).await?;
    let FindContextFileResult::Found(package_json_path, _refs) = &*package_json_context else {
        return Ok(ImportsFieldResult::None.cell());
    };
    let source = Vc::upcast::<Box<dyn Source>>(FileSource::new(package_json_path.clone()))
        .to_resolved()
        .await?;

    let read = read_package_json(*source).await?;
    let package_json = match &*read {
        Some(json) => json,
        None => return Ok(ImportsFieldResult::None.cell()),
    };

    let Some(imports) = package_json.get("imports") else {
        return Ok(ImportsFieldResult::None.cell());
    };
    match imports.try_into() {
        Ok(imports) => Ok(ImportsFieldResult::Some(imports, package_json_path.clone()).cell()),
        Err(err) => {
            PackageJsonIssue {
                error_message: err.to_string().into(),
                // TODO(PACK-4879): Add line-column information
                source: IssueSource::from_source_only(source),
            }
            .resolved_cell()
            .emit();
            Ok(ImportsFieldResult::None.cell())
        }
    }
}

#[turbo_tasks::function]
pub fn package_json() -> Vc<Vec<RcStr>> {
    Vc::cell(vec![rcstr!("package.json")])
}

#[turbo_tasks::value(shared)]
pub enum FindContextFileResult {
    Found(FileSystemPath, Vec<ResolvedVc<Box<dyn Source>>>),
    NotFound(Vec<ResolvedVc<Box<dyn Source>>>),
}

#[turbo_tasks::function]
pub async fn find_context_file(
    lookup_path: FileSystemPath,
    names: Vc<Vec<RcStr>>,
    collect_affecting_sources: bool,
) -> Result<Vc<FindContextFileResult>> {
    let mut refs = Vec::new();
    for name in &*names.await? {
        let fs_path = lookup_path.join(name)?;
        if let Some(fs_path) = exists(
            &fs_path,
            if collect_affecting_sources {
                Some(&mut refs)
            } else {
                None
            },
        )
        .await?
        {
            return Ok(FindContextFileResult::Found(fs_path, refs).cell());
        }
    }
    if lookup_path.is_root() {
        return Ok(FindContextFileResult::NotFound(refs).cell());
    }
    if refs.is_empty() {
        // Tailcall
        Ok(find_context_file(
            lookup_path.parent(),
            names,
            collect_affecting_sources,
        ))
    } else {
        let parent_result =
            find_context_file(lookup_path.parent(), names, collect_affecting_sources).await?;
        Ok(match &*parent_result {
            FindContextFileResult::Found(p, r) => {
                refs.extend(r.iter().copied());
                FindContextFileResult::Found(p.clone(), refs)
            }
            FindContextFileResult::NotFound(r) => {
                refs.extend(r.iter().copied());
                FindContextFileResult::NotFound(refs)
            }
        }
        .cell())
    }
}

// Same as find_context_file, but also stop for package.json with the specified key
// This function never collects affecting sources
#[turbo_tasks::function]
pub async fn find_context_file_or_package_key(
    lookup_path: FileSystemPath,
    names: Vc<Vec<RcStr>>,
    package_key: RcStr,
) -> Result<Vc<FindContextFileResult>> {
    let package_json_path = lookup_path.join("package.json")?;
    if let Some(package_json_path) = exists(&package_json_path, None).await?
        && let Some(json) =
            &*read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?
        && json.get(&*package_key).is_some()
    {
        return Ok(FindContextFileResult::Found(package_json_path, Vec::new()).cell());
    }
    for name in &*names.await? {
        let fs_path = lookup_path.join(name)?;
        if let Some(fs_path) = exists(&fs_path, None).await? {
            return Ok(FindContextFileResult::Found(fs_path, Vec::new()).cell());
        }
    }
    if lookup_path.is_root() {
        return Ok(FindContextFileResult::NotFound(Vec::new()).cell());
    }

    Ok(find_context_file(lookup_path.parent(), names, false))
}

#[derive(Clone, PartialEq, Eq, TraceRawVcs, Debug, NonLocalValue, Encode, Decode)]
enum FindPackageItem {
    PackageDirectory { name: RcStr, dir: FileSystemPath },
    PackageFile { name: RcStr, file: FileSystemPath },
}

#[turbo_tasks::value]
#[derive(Debug)]
struct FindPackageResult {
    packages: Vec<FindPackageItem>,
    // Only populated if collect_affecting_sources is true
    affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
}

#[turbo_tasks::function]
async fn find_package(
    lookup_path: FileSystemPath,
    package_name: Pattern,
    options: Vc<ResolveModulesOptions>,
    collect_affecting_sources: bool,
) -> Result<Vc<FindPackageResult>> {
    let mut packages = vec![];
    let mut affecting_sources = vec![];
    let options = options.await?;
    let package_name_cell = Pattern::new(package_name.clone());

    fn get_package_name(basepath: &FileSystemPath, package_dir: &FileSystemPath) -> Result<RcStr> {
        if let Some(name) = basepath.get_path_to(package_dir) {
            Ok(name.into())
        } else {
            bail!("Package directory {package_dir} is not inside the lookup path {basepath}",);
        }
    }

    for resolve_modules in &options.modules {
        match resolve_modules {
            ResolveModules::Nested(root, names) => {
                let mut lookup_path = lookup_path.clone();
                let mut lookup_path_value = lookup_path.clone();
                while lookup_path_value.is_inside_ref(root) {
                    for name in names.iter() {
                        let fs_path = lookup_path.join(name)?;
                        if let Some(fs_path) = dir_exists(
                            &fs_path,
                            collect_affecting_sources.then_some(&mut affecting_sources),
                        )
                        .await?
                        {
                            let matches =
                                read_matches(fs_path.clone(), rcstr!(""), true, package_name_cell)
                                    .await?;
                            for m in &*matches {
                                if let PatternMatch::Directory(_, package_dir) = m {
                                    packages.push(FindPackageItem::PackageDirectory {
                                        name: get_package_name(&fs_path, package_dir)?,
                                        dir: realpath(
                                            package_dir,
                                            collect_affecting_sources
                                                .then_some(&mut affecting_sources),
                                        )
                                        .await?,
                                    });
                                }
                            }
                        }
                    }
                    lookup_path = lookup_path.parent();
                    let new_context_value = lookup_path.clone();
                    if new_context_value == lookup_path_value {
                        break;
                    }
                    lookup_path_value = new_context_value;
                }
            }
            ResolveModules::Path {
                dir,
                excluded_extensions,
            } => {
                let matches =
                    read_matches(dir.clone(), rcstr!(""), true, package_name_cell).await?;
                for m in &*matches {
                    match m {
                        PatternMatch::Directory(_, package_dir) => {
                            packages.push(FindPackageItem::PackageDirectory {
                                name: get_package_name(dir, package_dir)?,
                                dir: realpath(
                                    package_dir,
                                    collect_affecting_sources.then_some(&mut affecting_sources),
                                )
                                .await?,
                            });
                        }
                        PatternMatch::File(_, package_file) => {
                            packages.push(FindPackageItem::PackageFile {
                                name: get_package_name(dir, package_file)?,
                                file: realpath(
                                    package_file,
                                    collect_affecting_sources.then_some(&mut affecting_sources),
                                )
                                .await?,
                            });
                        }
                    }
                }

                let excluded_extensions = excluded_extensions.await?;
                let mut package_name_with_extensions = package_name.clone();
                package_name_with_extensions.push(Pattern::alternatives(
                    options
                        .extensions
                        .iter()
                        .filter(|ext| !excluded_extensions.contains(*ext))
                        .cloned()
                        .map(Pattern::from),
                ));
                let package_name_with_extensions = Pattern::new(package_name_with_extensions);

                let matches =
                    read_matches(dir.clone(), rcstr!(""), true, package_name_with_extensions)
                        .await?;
                for m in matches {
                    if let PatternMatch::File(_, package_file) = m {
                        packages.push(FindPackageItem::PackageFile {
                            name: get_package_name(dir, package_file)?,
                            file: realpath(
                                package_file,
                                collect_affecting_sources.then_some(&mut affecting_sources),
                            )
                            .await?,
                        });
                    }
                }
            }
        }
    }
    Ok(FindPackageResult::cell(FindPackageResult {
        packages,
        affecting_sources,
    }))
}

fn merge_results(results: Vec<Vc<ResolveResult>>) -> Vc<ResolveResult> {
    match results.len() {
        0 => ResolveResult::unresolvable().cell(),
        1 => results.into_iter().next().unwrap(),
        _ => ResolveResult::alternatives(results),
    }
}

fn merge_results_with_affecting_sources(
    results: Vec<Vc<ResolveResult>>,
    affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
) -> Vc<ResolveResult> {
    if affecting_sources.is_empty() {
        return merge_results(results);
    }
    match results.len() {
        0 => ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell(),
        1 => results
            .into_iter()
            .next()
            .unwrap()
            .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()),
        _ => ResolveResult::alternatives_with_affecting_sources(
            results,
            affecting_sources.into_iter().map(|src| *src).collect(),
        ),
    }
}

// Resolves the pattern
#[turbo_tasks::function]
pub async fn resolve_raw(
    lookup_dir: FileSystemPath,
    path: Vc<Pattern>,
    collect_affecting_sources: bool,
    force_in_lookup_dir: bool,
) -> Result<Vc<ResolveResult>> {
    async fn to_result(
        request: RcStr,
        path: &FileSystemPath,
        collect_affecting_sources: bool,
    ) -> Result<ResolveResult> {
        let result = &*path.realpath_with_links().await?;
        let path = match &result.path_result {
            Ok(path) => path,
            Err(e) => bail!(e.as_error_message(path, result).await?),
        };
        let request_key = RequestKey::new(request);
        let source = ResolvedVc::upcast(FileSource::new(path.clone()).to_resolved().await?);
        Ok(if collect_affecting_sources {
            ResolveResult::source_with_affecting_sources(
                request_key,
                source,
                result
                    .symlinks
                    .iter()
                    .map(|symlink| {
                        Vc::upcast::<Box<dyn Source>>(FileSource::new(symlink.clone()))
                            .to_resolved()
                    })
                    .try_join()
                    .await?,
            )
        } else {
            ResolveResult::source_with_key(request_key, source)
        })
    }

    async fn collect_matches(
        matches: &[PatternMatch],
        collect_affecting_sources: bool,
    ) -> Result<Vec<Vc<ResolveResult>>> {
        Ok(matches
            .iter()
            .map(|m| async move {
                Ok(if let PatternMatch::File(request, path) = m {
                    Some(to_result(request.clone(), path, collect_affecting_sources).await?)
                } else {
                    None
                })
            })
            .try_flat_join()
            .await?
            // Construct all the cells after resolving the results to ensure they are constructed in
            // a deterministic order.
            .into_iter()
            .map(|res| res.cell())
            .collect())
    }

    let mut results = Vec::new();

    let pat = path.await?;
    if let Some(pat) = pat
        .filter_could_match("/ROOT/")
        // Checks if this pattern is more specific than everything, so we test using a random path
        // that is unlikely to actually exist
        .and_then(|pat| pat.filter_could_not_match("/ROOT/fsd8nz8og54z"))
    {
        let path = Pattern::new(pat);
        let matches = read_matches(
            lookup_dir.root().owned().await?,
            rcstr!("/ROOT/"),
            true,
            path,
        )
        .await?;
        results.extend(collect_matches(&matches, collect_affecting_sources).await?);
    }

    {
        let matches =
            read_matches(lookup_dir.clone(), rcstr!(""), force_in_lookup_dir, path).await?;

        results.extend(collect_matches(&matches, collect_affecting_sources).await?);
    }

    Ok(merge_results(results))
}

#[turbo_tasks::function]
pub async fn resolve(
    lookup_path: FileSystemPath,
    reference_type: ReferenceType,
    request: Vc<Request>,
    options: Vc<ResolveOptions>,
) -> Result<Vc<ResolveResult>> {
    resolve_inline(lookup_path, reference_type, request, options).await
}

pub async fn resolve_inline(
    lookup_path: FileSystemPath,
    reference_type: ReferenceType,
    request: Vc<Request>,
    options: Vc<ResolveOptions>,
) -> Result<Vc<ResolveResult>> {
    let span = tracing::info_span!(
        "resolving",
        lookup_path = display(lookup_path.to_string_ref().await?),
        name = tracing::field::Empty,
        reference_type = display(&reference_type),
    );
    if !span.is_disabled() {
        // You can't await multiple times in the span macro call parameters.
        span.record("name", request.to_string().await?.as_str());
    }

    async {
        // Pre-fetch options once to avoid repeated await calls
        let options_value = options.await?;

        // Fast path: skip plugin handling if no plugins are configured
        let has_before_plugins = !options_value.before_resolve_plugins.is_empty();
        let has_after_plugins = !options_value.after_resolve_plugins.is_empty();

        let before_plugins_result = if has_before_plugins {
            handle_before_resolve_plugins(
                lookup_path.clone(),
                reference_type.clone(),
                request,
                options,
            )
            .await?
        } else {
            None
        };

        let raw_result = match before_plugins_result {
            Some(result) => result,
            None => {
                *resolve_internal(lookup_path.clone(), request, options)
                    .to_resolved()
                    .await?
            }
        };

        let result = if has_after_plugins {
            handle_after_resolve_plugins(lookup_path, reference_type, request, options, raw_result)
                .await?
        } else {
            raw_result
        };

        Ok(result)
    }
    .instrument(span)
    .await
}

#[turbo_tasks::function]
pub async fn url_resolve(
    origin: Vc<Box<dyn ResolveOrigin>>,
    request: Vc<Request>,
    reference_type: ReferenceType,
    issue_source: Option<IssueSource>,
    error_mode: ResolveErrorMode,
) -> Result<Vc<ModuleResolveResult>> {
    let resolve_options = origin.resolve_options();
    let rel_request = request.as_relative();
    let origin_path_parent = origin.origin_path().await?.parent();
    let rel_result = resolve(
        origin_path_parent.clone(),
        reference_type.clone(),
        rel_request,
        resolve_options,
    );
    let result =
        if *rel_result.is_unresolvable().await? && *rel_request.to_resolved().await? != request {
            let result = resolve(
                origin_path_parent,
                reference_type.clone(),
                request,
                resolve_options,
            );
            if resolve_options.await?.collect_affecting_sources {
                result.with_affecting_sources(
                    rel_result
                        .await?
                        .get_affecting_sources()
                        .map(|src| *src)
                        .collect(),
                )
            } else {
                result
            }
        } else {
            rel_result
        };
    let result = origin
        .asset_context()
        .process_resolve_result(result, reference_type.clone());
    handle_resolve_error(
        result,
        reference_type,
        origin,
        request,
        resolve_options,
        error_mode,
        issue_source,
    )
    .await
}

#[turbo_tasks::value(transparent)]
struct MatchingBeforeResolvePlugins(Vec<ResolvedVc<Box<dyn BeforeResolvePlugin>>>);

#[turbo_tasks::function]
async fn get_matching_before_resolve_plugins(
    options: Vc<ResolveOptions>,
    request: Vc<Request>,
) -> Result<Vc<MatchingBeforeResolvePlugins>> {
    let mut matching_plugins = Vec::new();
    for &plugin in &options.await?.before_resolve_plugins {
        let condition = plugin.before_resolve_condition().to_resolved().await?;
        if *condition.matches(request).await? {
            matching_plugins.push(plugin);
        }
    }
    Ok(Vc::cell(matching_plugins))
}

#[tracing::instrument(level = "trace", skip_all)]
async fn handle_before_resolve_plugins(
    lookup_path: FileSystemPath,
    reference_type: ReferenceType,
    request: Vc<Request>,
    options: Vc<ResolveOptions>,
) -> Result<Option<Vc<ResolveResult>>> {
    for plugin in get_matching_before_resolve_plugins(options, request).await? {
        if let Some(result) = *plugin
            .before_resolve(lookup_path.clone(), reference_type.clone(), request)
            .await?
        {
            return Ok(Some(*result));
        }
    }
    Ok(None)
}

#[tracing::instrument(level = "trace", skip_all)]
async fn handle_after_resolve_plugins(
    lookup_path: FileSystemPath,
    reference_type: ReferenceType,
    request: Vc<Request>,
    options: Vc<ResolveOptions>,
    result: Vc<ResolveResult>,
) -> Result<Vc<ResolveResult>> {
    // Pre-fetch options to avoid repeated await calls in the inner loop
    let options_value = options.await?;

    // Pre-resolve all plugin conditions once to avoid repeated resolve calls in the loop
    let resolved_conditions = options_value
        .after_resolve_plugins
        .iter()
        .map(async |p| Ok((*p, p.after_resolve_condition().to_resolved().await?)))
        .try_join()
        .await?;

    async fn apply_plugins_to_path(
        path: FileSystemPath,
        lookup_path: FileSystemPath,
        reference_type: ReferenceType,
        request: Vc<Request>,
        plugins_with_conditions: &[AfterResolvePluginWithCondition],
    ) -> Result<Option<Vc<ResolveResult>>> {
        for (plugin, after_resolve_condition) in plugins_with_conditions {
            if *after_resolve_condition.matches(path.clone()).await?
                && let Some(result) = *plugin
                    .after_resolve(
                        path.clone(),
                        lookup_path.clone(),
                        reference_type.clone(),
                        request,
                    )
                    .await?
            {
                return Ok(Some(*result));
            }
        }
        Ok(None)
    }

    let mut changed = false;
    let result_value = result.await?;

    let mut new_primary = FxIndexMap::default();
    let mut new_affecting_sources = Vec::new();

    for (key, primary) in result_value.primary.iter() {
        if let &ResolveResultItem::Source(source) = primary {
            let path = source.ident().path().owned().await?;
            if let Some(new_result) = apply_plugins_to_path(
                path.clone(),
                lookup_path.clone(),
                reference_type.clone(),
                request,
                &resolved_conditions,
            )
            .await?
            {
                let new_result = new_result.await?;
                changed = true;
                new_primary.extend(
                    new_result
                        .primary
                        .iter()
                        .map(|(_, item)| (key.clone(), item.clone())),
                );
                new_affecting_sources.extend(new_result.affecting_sources.iter().copied());
            } else {
                new_primary.insert(key.clone(), primary.clone());
            }
        } else {
            new_primary.insert(key.clone(), primary.clone());
        }
    }

    if !changed {
        return Ok(result);
    }

    let mut affecting_sources = result_value.affecting_sources.to_vec();
    affecting_sources.append(&mut new_affecting_sources);

    Ok(ResolveResult {
        primary: new_primary.into_iter().collect(),
        affecting_sources: affecting_sources.into_boxed_slice(),
    }
    .cell())
}

#[turbo_tasks::function]
async fn resolve_internal(
    lookup_path: FileSystemPath,
    request: ResolvedVc<Request>,
    options: ResolvedVc<ResolveOptions>,
) -> Result<Vc<ResolveResult>> {
    resolve_internal_inline(lookup_path.clone(), *request, *options).await
}

async fn resolve_internal_inline(
    lookup_path: FileSystemPath,
    request: Vc<Request>,
    options: Vc<ResolveOptions>,
) -> Result<Vc<ResolveResult>> {
    let span = tracing::info_span!(
        "internal resolving",
        lookup_path = display(lookup_path.to_string_ref().await?),
        name = tracing::field::Empty
    );
    if !span.is_disabled() {
        // You can't await multiple times in the span macro call parameters.
        span.record("name", request.to_string().await?.as_str());
    }

    async move {
        let options_value: &ResolveOptions = &*options.await?;

        let request_value = request.await?;

        // Apply import mappings if provided
        let mut has_alias = false;
        if let Some(import_map) = &options_value.import_map {
            let request_parts = match &*request_value {
                Request::Alternatives { requests } => requests.as_slice(),
                _ => &[request.to_resolved().await?],
            };
            for &request in request_parts {
                let result = import_map
                    .await?
                    .lookup(lookup_path.clone(), *request)
                    .await?;
                if !matches!(result, ImportMapResult::NoEntry) {
                    has_alias = true;
                    let resolved_result = resolve_import_map_result(
                        &result,
                        lookup_path.clone(),
                        lookup_path.clone(),
                        *request,
                        options,
                        request.query().owned().await?,
                    )
                    .await?;
                    // We might have matched an alias in the import map, but there is no guarantee
                    // the alias actually resolves to something. For instance, a tsconfig.json
                    // `compilerOptions.paths` option might alias "@*" to "./*", which
                    // would also match a request to "@emotion/core". Here, we follow what the
                    // Typescript resolution algorithm does in case an alias match
                    // doesn't resolve to anything: fall back to resolving the request normally.
                    if let Some(resolved_result) = resolved_result {
                        let resolved_result = resolved_result.into_cell_if_resolvable().await?;
                        if let Some(result) = resolved_result {
                            return Ok(result);
                        }
                    }
                }
            }
        }

        let result = match &*request_value {
            Request::Dynamic => ResolveResult::unresolvable().cell(),
            Request::Alternatives { requests } => {
                let results = requests
                    .iter()
                    .map(|req| async {
                        resolve_internal_inline(lookup_path.clone(), **req, options).await
                    })
                    .try_join()
                    .await?;

                merge_results(results)
            }
            Request::Raw {
                path,
                query,
                force_in_lookup_dir,
                fragment,
            } => {
                let mut results = Vec::new();
                let matches = read_matches(
                    lookup_path.clone(),
                    rcstr!(""),
                    *force_in_lookup_dir,
                    *Pattern::new(path.clone()).to_resolved().await?,
                )
                .await?;

                for m in matches.iter() {
                    match m {
                        PatternMatch::File(matched_pattern, path) => {
                            results.push(
                                resolved(
                                    RequestKey::new(matched_pattern.clone()),
                                    path.clone(),
                                    lookup_path.clone(),
                                    request,
                                    options_value,
                                    options,
                                    query.clone(),
                                    fragment.clone(),
                                )
                                .await?
                                .into_cell(),
                            );
                        }
                        PatternMatch::Directory(matched_pattern, path) => {
                            results.push(
                                resolve_into_folder(path.clone(), options)
                                    .with_request(matched_pattern.clone()),
                            );
                        }
                    }
                }

                merge_results(results)
            }
            Request::Relative {
                path,
                query,
                force_in_lookup_dir,
                fragment,
            } => {
                resolve_relative_request(
                    lookup_path.clone(),
                    request,
                    options,
                    options_value,
                    path,
                    query.clone(),
                    *force_in_lookup_dir,
                    fragment.clone(),
                )
                .await?
            }
            Request::Module {
                module,
                path,
                query,
                fragment,
            } => {
                resolve_module_request(
                    lookup_path.clone(),
                    request,
                    options,
                    options_value,
                    module,
                    path,
                    query.clone(),
                    fragment.clone(),
                )
                .await?
            }
            Request::ServerRelative {
                path,
                query,
                fragment,
            } => {
                let mut new_pat = path.clone();
                new_pat.push_front(rcstr!(".").into());
                let relative = Request::relative(new_pat, query.clone(), fragment.clone(), true);

                if !has_alias {
                    ResolvingIssue {
                        severity: resolve_error_severity(options).await?,
                        request_type: "server relative import: not implemented yet".to_string(),
                        request: relative.to_resolved().await?,
                        file_path: lookup_path.clone(),
                        resolve_options: options.to_resolved().await?,
                        error_message: Some(
                            "server relative imports are not implemented yet. Please try an \
                             import relative to the file you are importing from."
                                .to_string(),
                        ),
                        source: None,
                    }
                    .resolved_cell()
                    .emit();
                }

                Box::pin(resolve_internal_inline(
                    lookup_path.root().owned().await?,
                    relative,
                    options,
                ))
                .await?
            }
            Request::Windows {
                path: _,
                query: _,
                fragment: _,
            } => {
                if !has_alias {
                    ResolvingIssue {
                        severity: resolve_error_severity(options).await?,
                        request_type: "windows import: not implemented yet".to_string(),
                        request: request.to_resolved().await?,
                        file_path: lookup_path.clone(),
                        resolve_options: options.to_resolved().await?,
                        error_message: Some("windows imports are not implemented yet".to_string()),
                        source: None,
                    }
                    .resolved_cell()
                    .emit();
                }

                ResolveResult::unresolvable().cell()
            }
            Request::Empty => ResolveResult::unresolvable().cell(),
            Request::PackageInternal { path } => {
                let (conditions, unspecified_conditions) = options_value
                    .in_package
                    .iter()
                    .find_map(|item| match item {
                        ResolveInPackage::ImportsField {
                            conditions,
                            unspecified_conditions,
                        } => Some((Cow::Borrowed(conditions), *unspecified_conditions)),
                        _ => None,
                    })
                    .unwrap_or_else(|| (Default::default(), ConditionValue::Unset));
                resolve_package_internal_with_imports_field(
                    lookup_path.clone(),
                    request,
                    options,
                    path,
                    &conditions,
                    &unspecified_conditions,
                )
                .await?
            }
            Request::DataUri {
                media_type,
                encoding,
                data,
            } => {
                // Behave like Request::Uri
                let uri: RcStr = stringify_data_uri(media_type, encoding, *data)
                    .await?
                    .into();
                if options_value.parse_data_uris {
                    ResolveResult::primary_with_key(
                        RequestKey::new(uri.clone()),
                        ResolveResultItem::Source(ResolvedVc::upcast(
                            DataUriSource::new(
                                media_type.clone(),
                                encoding.clone(),
                                **data,
                                lookup_path.clone(),
                            )
                            .to_resolved()
                            .await?,
                        )),
                    )
                    .cell()
                } else {
                    ResolveResult::primary_with_key(
                        RequestKey::new(uri.clone()),
                        ResolveResultItem::External {
                            name: uri,
                            ty: ExternalType::Url,
                            traced: ExternalTraced::Untraced,
                            target: None,
                        },
                    )
                    .cell()
                }
            }
            Request::Uri {
                protocol,
                remainder,
                query: _,
                fragment: _,
            } => {
                let uri: RcStr = format!("{protocol}{remainder}").into();
                ResolveResult::primary_with_key(
                    RequestKey::new(uri.clone()),
                    ResolveResultItem::External {
                        name: uri,
                        ty: ExternalType::Url,
                        traced: ExternalTraced::Untraced,
                        target: None,
                    },
                )
                .cell()
            }
            Request::Unknown { path } => {
                if !has_alias {
                    ResolvingIssue {
                        severity: resolve_error_severity(options).await?,
                        request_type: format!("unknown import: `{}`", path.describe_as_string()),
                        request: request.to_resolved().await?,
                        file_path: lookup_path.clone(),
                        resolve_options: options.to_resolved().await?,
                        error_message: None,
                        source: None,
                    }
                    .resolved_cell()
                    .emit();
                }
                ResolveResult::unresolvable().cell()
            }
        };

        // The individual variants inside the alternative already looked at the fallback import
        // map in the recursive `resolve_internal_inline` calls
        if !matches!(*request_value, Request::Alternatives { .. }) {
            // Apply fallback import mappings if provided
            if let Some(import_map) = &options_value.fallback_import_map
                && *result.is_unresolvable().await?
            {
                let result = import_map
                    .await?
                    .lookup(lookup_path.clone(), request)
                    .await?;
                let resolved_result = resolve_import_map_result(
                    &result,
                    lookup_path.clone(),
                    lookup_path.clone(),
                    request,
                    options,
                    request.query().owned().await?,
                )
                .await?;
                if let Some(resolved_result) = resolved_result {
                    let resolved_result = resolved_result.into_cell_if_resolvable().await?;
                    if let Some(result) = resolved_result {
                        return Ok(result);
                    }
                }
            }
        }

        Ok(result)
    }
    .instrument(span)
    .await
}

#[turbo_tasks::function]
async fn resolve_into_folder(
    package_path: FileSystemPath,
    options: Vc<ResolveOptions>,
) -> Result<Vc<ResolveResult>> {
    let options_value = options.await?;

    let mut affecting_sources = vec![];
    if let Some(package_json_path) = exists(
        &package_path.join("package.json")?,
        if options_value.collect_affecting_sources {
            Some(&mut affecting_sources)
        } else {
            None
        },
    )
    .await?
    {
        for resolve_into_package in options_value.into_package.iter() {
            match resolve_into_package {
                ResolveIntoPackage::MainField { field: name } => {
                    if let Some(package_json) =
                        &*read_package_json(Vc::upcast(FileSource::new(package_json_path.clone())))
                            .await?
                        && let Some(field_value) = package_json[name.as_str()].as_str()
                    {
                        let normalized_request = RcStr::from(normalize_request(field_value));
                        if normalized_request.is_empty()
                            || &*normalized_request == "."
                            || &*normalized_request == "./"
                        {
                            continue;
                        }
                        let request = Request::parse_string(normalized_request);

                        // main field will always resolve not fully specified
                        let options = if options_value.fully_specified {
                            *options.with_fully_specified(false).to_resolved().await?
                        } else {
                            options
                        };
                        let result =
                            &*resolve_internal_inline(package_path.clone(), request, options)
                                .await?
                                .await?;
                        // we are not that strict when a main field fails to resolve
                        // we continue to try other alternatives
                        if !result.is_unresolvable_ref() {
                            let mut result: ResolveResultBuilder =
                                result.with_request_ref(rcstr!(".")).into();
                            if options_value.collect_affecting_sources {
                                result.affecting_sources.push(ResolvedVc::upcast(
                                    FileSource::new(package_json_path).to_resolved().await?,
                                ));
                                result.affecting_sources.extend(affecting_sources);
                            }
                            return Ok(ResolveResult::from(result).cell());
                        }
                    };
                }
                ResolveIntoPackage::ExportsField { .. } => {}
            }
        }
    }

    if options_value.fully_specified {
        return Ok(ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell());
    }

    // fall back to dir/index.[js,ts,...]
    let pattern = match &options_value.default_files[..] {
        [] => {
            return Ok(
                ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell(),
            );
        }
        [file] => Pattern::Constant(format!("./{file}").into()),
        files => Pattern::Alternatives(
            files
                .iter()
                .map(|file| Pattern::Constant(format!("./{file}").into()))
                .collect(),
        ),
    };

    let request = Request::parse(pattern);
    let result = resolve_internal_inline(package_path.clone(), request, options)
        .await?
        .with_request(rcstr!("."));

    Ok(if !affecting_sources.is_empty() {
        result.with_affecting_sources(ResolvedVc::deref_vec(affecting_sources))
    } else {
        result
    })
}

#[tracing::instrument(level = Level::TRACE, skip_all)]
async fn resolve_relative_request(
    lookup_path: FileSystemPath,
    request: Vc<Request>,
    options: Vc<ResolveOptions>,
    options_value: &ResolveOptions,
    path_pattern: &Pattern,
    query: RcStr,
    force_in_lookup_dir: bool,
    fragment: RcStr,
) -> Result<Vc<ResolveResult>> {
    debug_assert!(query.is_empty() || query.starts_with("?"));
    debug_assert!(fragment.is_empty() || fragment.starts_with("#"));
    // Check alias field for aliases first
    let lookup_path_ref = lookup_path.clone();
    if let Some(result) = apply_in_package(
        lookup_path.clone(),
        options,
        options_value,
        |package_path| {
            let request = path_pattern.as_constant_string()?;
            let prefix_path = package_path.get_path_to(&lookup_path_ref)?;
            let request = normalize_request(&format!("./{prefix_path}/{request}"));
            Some(request.into())
        },
        query.clone(),
        fragment.clone(),
    )
    .await?
    {
        return Ok(result.into_cell());
    }

    let mut new_path = path_pattern.clone();

    // A small tree to 'undo' the set of modifications we make to patterns, ensuring that we produce
    // correct request keys
    #[derive(Eq, PartialEq, Clone, Hash, Debug)]
    enum RequestKeyTransform {
        /// A leaf node for 'no change'
        None,
        /// We added a fragment to the request and thus need to potentially remove it when matching
        AddedFragment,
        // We added an extension to the request and thus need to potentially remove it when
        // matching
        AddedExtension {
            /// The extension that was added
            ext: RcStr,
            /// This modification can be composed with others
            /// In reality just `None' or `AddedFragment``
            next: Vec<RequestKeyTransform>,
        },
        ReplacedExtension {
            /// The extension that was replaced, to figure out the original you need to query
            /// [TS_EXTENSION_REPLACEMENTS]
            ext: RcStr,
            /// This modification can be composed with others
            /// In just [AddedExtension], [None] or [AddedFragment]
            next: Vec<RequestKeyTransform>,
        },
    }

    impl RequestKeyTransform {
        /// Modifies the matched pattern using the modification rules and produces results if they
        /// match the supplied [pattern]
        fn undo(
            &self,
            matched_pattern: &RcStr,
            fragment: &RcStr,
            pattern: &Pattern,
        ) -> impl Iterator<Item = (RcStr, RcStr)> {
            let mut result = SmallVec::new();
            self.apply_internal(matched_pattern, fragment, pattern, &mut result);
            result.into_iter()
        }

        fn apply_internal(
            &self,
            matched_pattern: &RcStr,
            fragment: &RcStr,
            pattern: &Pattern,
            result: &mut SmallVec<[(RcStr, RcStr); 2]>,
        ) {
            match self {
                RequestKeyTransform::None => {
                    if pattern.is_match(matched_pattern.as_str()) {
                        result.push((matched_pattern.clone(), fragment.clone()));
                    }
                }
                RequestKeyTransform::AddedFragment => {
                    debug_assert!(
                        !fragment.is_empty(),
                        "can only have an AddedFragment modification if there was a fragment"
                    );
                    if let Some(stripped_pattern) = matched_pattern.strip_suffix(fragment.as_str())
                        && pattern.is_match(stripped_pattern)
                    {
                        result.push((stripped_pattern.into(), RcStr::default()));
                    }
                }
                RequestKeyTransform::AddedExtension { ext, next } => {
                    if let Some(stripped_pattern) = matched_pattern.strip_suffix(ext.as_str()) {
                        let stripped_pattern: RcStr = stripped_pattern.into();
                        Self::apply_all(next, &stripped_pattern, fragment, pattern, result);
                    }
                }
                RequestKeyTransform::ReplacedExtension { ext, next } => {
                    if let Some(stripped_pattern) = matched_pattern.strip_suffix(ext.as_str()) {
                        let replaced_pattern: RcStr = format!(
                            "{stripped_pattern}{old_ext}",
                            old_ext = TS_EXTENSION_REPLACEMENTS.reverse.get(ext).unwrap()
                        )
                        .into();
                        Self::apply_all(next, &replaced_pattern, fragment, pattern, result);
                    }
                }
            }
        }

        fn apply_all(
            list: &[RequestKeyTransform],
            matched_pattern: &RcStr,
            fragment: &RcStr,
            pattern: &Pattern,
            result: &mut SmallVec<[(RcStr, RcStr); 2]>,
        ) {
            list.iter()
                .for_each(|pm| pm.apply_internal(matched_pattern, fragment, pattern, result));
        }
    }

    let mut modifications = Vec::new();
    modifications.push(RequestKeyTransform::None);

    // Fragments are a bit odd. `require()` allows importing files with literal `#` characters in
    // them, but `import` treats it like a url and drops it from resolution. So we need to consider
    // both cases here.
    if !fragment.is_empty() {
        modifications.push(RequestKeyTransform::AddedFragment);
        new_path.push(Pattern::Alternatives(vec![
            Pattern::Constant(RcStr::default()),
            Pattern::Constant(fragment.clone()),
        ]));
    }

    if !options_value.fully_specified {
        // For each current set of modifications append an extension modification
        modifications =
            modifications
                .iter()
                .cloned()
                .chain(options_value.extensions.iter().map(|ext| {
                    RequestKeyTransform::AddedExtension {
                        ext: ext.clone(),
                        next: modifications.clone(),
                    }
                }))
                .collect();
        // Add the extensions as alternatives to the path
        // read_matches keeps the order of alternatives intact
        // TODO: if the pattern has a dynamic suffix then this 'ordering' doesn't work since we just
        // take the slowpath and return everything from the directory in `read_matches`
        new_path.push(Pattern::Alternatives(
            once(Pattern::Constant(RcStr::default()))
                .chain(
                    options_value
                        .extensions
                        .iter()
                        .map(|ext| Pattern::Constant(ext.clone())),
                )
                .collect(),
        ));
        new_path.normalize();
    };

    struct ExtensionReplacements {
        forward: FxHashMap<RcStr, SmallVec<[RcStr; 3]>>,
        reverse: FxHashMap<RcStr, RcStr>,
    }
    static TS_EXTENSION_REPLACEMENTS: LazyLock<ExtensionReplacements> = LazyLock::new(|| {
        let mut forward = FxHashMap::default();
        forward.insert(
            rcstr!(".js"),
            SmallVec::from_vec(vec![rcstr!(".ts"), rcstr!(".tsx"), rcstr!(".js")]),
        );

        forward.insert(
            rcstr!(".mjs"),
            SmallVec::from_vec(vec![rcstr!(".mts"), rcstr!(".mjs")]),
        );

        forward.insert(
            rcstr!(".cjs"),
            SmallVec::from_vec(vec![rcstr!(".cts"), rcstr!(".cjs")]),
        );
        let reverse = forward
            .iter()
            .flat_map(|(k, v)| v.iter().map(|v: &RcStr| (v.clone(), k.clone())))
            .collect::<FxHashMap<_, _>>();
        ExtensionReplacements { forward, reverse }
    });

    if options_value.enable_typescript_with_output_extension {
        // there are at most 4 possible replacements (the size of the reverse map)
        let mut replaced_extensions = SmallVec::<[RcStr; 4]>::new();
        let replaced = new_path.replace_final_constants(&mut |c: &RcStr| -> Option<Pattern> {
            let (base, ext) = c.split_at(c.rfind('.')?);

            let (ext, replacements) = TS_EXTENSION_REPLACEMENTS.forward.get_key_value(ext)?;
            for replacement in replacements {
                if replacement != ext && !replaced_extensions.contains(replacement) {
                    replaced_extensions.push(replacement.clone());
                    debug_assert!(replaced_extensions.len() <= replaced_extensions.inline_size());
                }
            }

            let replacements = replacements
                .iter()
                .cloned()
                .map(Pattern::Constant)
                .collect();

            if base.is_empty() {
                Some(Pattern::Alternatives(replacements))
            } else {
                Some(Pattern::Concatenation(vec![
                    Pattern::Constant(base.into()),
                    Pattern::Alternatives(replacements),
                ]))
            }
        });
        if replaced {
            // For each current set of modifications append an extension replacement modification
            modifications = modifications
                .iter()
                .cloned()
                .chain(replaced_extensions.iter().map(|ext| {
                    RequestKeyTransform::ReplacedExtension {
                        ext: ext.clone(),
                        next: modifications.clone(),
                    }
                }))
                .collect();
            new_path.normalize();
        }
    }

    let matches = read_matches(
        lookup_path.clone(),
        rcstr!(""),
        force_in_lookup_dir,
        *Pattern::new(new_path.clone()).to_resolved().await?,
    )
    .await?;

    // This loop is necessary to 'undo' the modifications to 'new_path' that were performed above.
    // e.g. we added extensions but these shouldn't be part of the request key so remove them.

    let mut keys = FxHashSet::default();
    let results = matches
        .iter()
        .flat_map(|m| {
            if let PatternMatch::File(matched_pattern, path) = m {
                Either::Left(
                    modifications
                        .iter()
                        .flat_map(|m| m.undo(matched_pattern, &fragment, path_pattern))
                        .map(move |result| (result, path)),
                )
            } else {
                Either::Right(empty())
            }
        })
        // Dedupe here before calling `resolved`
        .filter(move |((matched_pattern, _), _)| keys.insert(matched_pattern.clone()))
        .map(|((matched_pattern, fragment), path)| {
            resolved(
                RequestKey::new(matched_pattern),
                path.clone(),
                lookup_path.clone(),
                request,
                options_value,
                options,
                query.clone(),
                fragment,
            )
        })
        .try_join()
        .await?;

    // Convert ResolveResultOrCells to cells in deterministic order (after concurrent resolution)
    let mut results: Vec<Vc<ResolveResult>> = results.into_iter().map(|r| r.into_cell()).collect();

    // Directory matches must be resolved AFTER file matches
    for m in matches.iter() {
        if let PatternMatch::Directory(matched_pattern, path) = m {
            results.push(
                resolve_into_folder(path.clone(), options).with_request(matched_pattern.clone()),
            );
        }
    }

    Ok(merge_results(results))
}

#[tracing::instrument(level = Level::TRACE, skip_all)]
async fn apply_in_package(
    lookup_path: FileSystemPath,
    options: Vc<ResolveOptions>,
    options_value: &ResolveOptions,
    get_request: impl Fn(&FileSystemPath) -> Option<RcStr>,
    query: RcStr,
    fragment: RcStr,
) -> Result<Option<ResolveResultOrCell>> {
    // Check alias field for module aliases first
    for in_package in options_value.in_package.iter() {
        // resolve_module_request is called when importing a node
        // module, not a PackageInternal one, so the imports field
        // doesn't apply.
        let ResolveInPackage::AliasField(field) = in_package else {
            continue;
        };

        let FindContextFileResult::Found(package_json_path, refs) = &*find_context_file(
            lookup_path.clone(),
            *package_json().to_resolved().await?,
            options_value.collect_affecting_sources,
        )
        .await?
        else {
            continue;
        };

        let read =
            read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?;
        let Some(package_json) = &*read else {
            continue;
        };

        let Some(field_value) = package_json[field.as_str()].as_object() else {
            continue;
        };

        let package_path = package_json_path.parent();

        let Some(request) = get_request(&package_path) else {
            continue;
        };

        let value = if let Some(value) = field_value.get(&*request) {
            value
        } else if let Some(request) = request.strip_prefix("./") {
            let Some(value) = field_value.get(request) else {
                continue;
            };
            value
        } else {
            continue;
        };

        let refs = refs.clone();
        let request_key = RequestKey::new(request.clone());

        if value.as_bool() == Some(false) {
            return Ok(Some(ResolveResultOrCell::Value(
                ResolveResult::primary_with_affecting_sources(
                    request_key,
                    ResolveResultItem::Ignore,
                    refs,
                ),
            )));
        }

        if let Some(value) = value.as_str() {
            if value == &*request {
                // This would be a cycle, so we ignore it
                return Ok(None);
            }
            let mut result = resolve_internal(
                package_path,
                Request::parse(Pattern::Constant(value.into()))
                    .with_query(query.clone())
                    .with_fragment(fragment.clone()),
                options,
            )
            .with_replaced_request_key(value.into(), request_key);
            if options_value.collect_affecting_sources && !refs.is_empty() {
                result = result.with_affecting_sources(refs.into_iter().map(|src| *src).collect());
            }
            return Ok(Some(ResolveResultOrCell::Cell(result)));
        }

        ResolvingIssue {
            severity: resolve_error_severity(options).await?,
            file_path: package_json_path.clone(),
            request_type: format!("alias field ({field})"),
            request: Request::parse(Pattern::Constant(request))
                .to_resolved()
                .await?,
            resolve_options: options.to_resolved().await?,
            error_message: Some(format!("invalid alias field value: {value}")),
            source: None,
        }
        .resolved_cell()
        .emit();

        return Ok(Some(ResolveResultOrCell::Value(
            ResolveResult::unresolvable_with_affecting_sources(refs),
        )));
    }
    Ok(None)
}

#[turbo_tasks::value]
enum FindSelfReferencePackageResult {
    Found {
        name: String,
        package_path: FileSystemPath,
    },
    NotFound,
}

#[turbo_tasks::function]
/// Finds the nearest folder containing package.json that could be used for a
/// self-reference (i.e. has an exports fields).
async fn find_self_reference(
    lookup_path: FileSystemPath,
) -> Result<Vc<FindSelfReferencePackageResult>> {
    let package_json_context =
        find_context_file(lookup_path, *package_json().to_resolved().await?, false).await?;
    if let FindContextFileResult::Found(package_json_path, _refs) = &*package_json_context {
        let read =
            read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?;
        if let Some(json) = &*read
            && json.get("exports").is_some()
            && let Some(name) = json["name"].as_str()
        {
            return Ok(FindSelfReferencePackageResult::Found {
                name: name.to_string(),
                package_path: package_json_path.parent(),
            }
            .cell());
        }
    }
    Ok(FindSelfReferencePackageResult::NotFound.cell())
}

#[tracing::instrument(level = Level::TRACE, skip_all)]
async fn resolve_module_request(
    lookup_path: FileSystemPath,
    request: Vc<Request>,
    options: Vc<ResolveOptions>,
    options_value: &ResolveOptions,
    module: &Pattern,
    path: &Pattern,
    query: RcStr,
    fragment: RcStr,
) -> Result<Vc<ResolveResult>> {
    // Check alias field for module aliases first
    if let Some(result) = apply_in_package(
        lookup_path.clone(),
        options,
        options_value,
        |_| {
            let full_pattern = Pattern::concat([module.clone(), path.clone()]);
            full_pattern.as_constant_string().cloned()
        },
        query.clone(),
        fragment.clone(),
    )
    .await?
    {
        return Ok(result.into_cell());
    }

    let mut results = vec![];

    // Self references, if the nearest package.json has the name of the requested
    // module. This should match only using the exports field and no other
    // fields/fallbacks.
    if let FindSelfReferencePackageResult::Found { name, package_path } =
        &*find_self_reference(lookup_path.clone()).await?
        && module.is_match(name)
    {
        let result = resolve_into_package(
            path.clone(),
            package_path.clone(),
            query.clone(),
            fragment.clone(),
            options,
        );
        if !(*result.is_unresolvable().await?) {
            return Ok(result);
        }
    }

    let result = find_package(
        lookup_path.clone(),
        module.clone(),
        *resolve_modules_options(options).to_resolved().await?,
        options_value.collect_affecting_sources,
    )
    .await?;

    if result.packages.is_empty() {
        return Ok(ResolveResult::unresolvable_with_affecting_sources(
            result.affecting_sources.clone(),
        )
        .cell());
    }

    // There may be more than one package with the same name. For instance, in a
    // TypeScript project, `compilerOptions.baseUrl` can declare a path where to
    // resolve packages. A request to "foo/bar" might resolve to either
    // "[baseUrl]/foo/bar" or "[baseUrl]/node_modules/foo/bar", and we'll need to
    // try both.
    for item in &result.packages {
        match item {
            FindPackageItem::PackageDirectory { name, dir } => {
                results.push(
                    resolve_into_package(
                        path.clone(),
                        dir.clone(),
                        query.clone(),
                        fragment.clone(),
                        options,
                    )
                    .with_replaced_request_key(rcstr!("."), RequestKey::new(name.clone())),
                );
            }
            FindPackageItem::PackageFile { name, file } => {
                if path.is_match("") {
                    let resolved_result = resolved(
                        RequestKey::new(rcstr!(".")),
                        file.clone(),
                        lookup_path.clone(),
                        request,
                        options_value,
                        options,
                        query.clone(),
                        fragment.clone(),
                    )
                    .await?
                    .into_cell()
                    .with_replaced_request_key(rcstr!("."), RequestKey::new(name.clone()));
                    results.push(resolved_result)
                }
            }
        }
    }

    let module_result =
        merge_results_with_affecting_sources(results, result.affecting_sources.clone());

    if options_value.prefer_relative {
        let mut module_prefixed = module.clone();
        module_prefixed.push_front(rcstr!("./").into());
        let pattern = Pattern::concat([module_prefixed.clone(), rcstr!("/").into(), path.clone()]);
        let relative = Request::relative(pattern, query, fragment, true)
            .to_resolved()
            .await?;
        let relative_result = Box::pin(resolve_internal_inline(
            lookup_path.clone(),
            *relative,
            options,
        ))
        .await?;
        let relative_result = relative_result.with_stripped_request_key_prefix(rcstr!("./"));

        Ok(merge_results(vec![relative_result, module_result]))
    } else {
        Ok(module_result)
    }
}

#[turbo_tasks::function]
async fn resolve_into_package(
    path: Pattern,
    package_path: FileSystemPath,
    query: RcStr,
    fragment: RcStr,
    options: ResolvedVc<ResolveOptions>,
) -> Result<Vc<ResolveResult>> {
    let options_value = options.await?;
    let mut results = Vec::new();

    let is_root_match = path.is_match("") || path.is_match("/");
    let could_match_others = path.could_match_others("");

    let mut export_path_request = path.clone();
    export_path_request.push_front(rcstr!(".").into());
    for resolve_into_package in options_value.into_package.iter() {
        match resolve_into_package {
            // handled by the `resolve_into_folder` call below
            ResolveIntoPackage::MainField { .. } => {}
            ResolveIntoPackage::ExportsField {
                conditions,
                unspecified_conditions,
            } => {
                let package_json_path = package_path.join("package.json")?;
                let ExportsFieldResult::Some(exports_field) =
                    &*exports_field(Vc::upcast(FileSource::new(package_json_path.clone()))).await?
                else {
                    continue;
                };

                results.push(
                    handle_exports_imports_field(
                        package_path.clone(),
                        package_json_path,
                        *options,
                        exports_field,
                        export_path_request.clone(),
                        conditions,
                        unspecified_conditions,
                        query,
                    )
                    .await?,
                );

                // other options do not apply anymore when an exports
                // field exist
                return Ok(merge_results(results));
            }
        }
    }

    // apply main field(s) or fallback to index.js if there's no subpath
    if is_root_match {
        results.push(resolve_into_folder(
            package_path.clone(),
            options.with_fully_specified(false),
        ));
    }

    if could_match_others {
        let mut new_pat = path.clone();
        new_pat.push_front(rcstr!(".").into());

        let relative = Request::relative(new_pat, query, fragment, true)
            .to_resolved()
            .await?;
        results.push(resolve_internal_inline(package_path.clone(), *relative, *options).await?);
    }

    Ok(merge_results(results))
}

#[tracing::instrument(level = Level::TRACE, skip_all)]
async fn resolve_import_map_result(
    result: &ImportMapResult,
    lookup_path: FileSystemPath,
    original_lookup_path: FileSystemPath,
    original_request: Vc<Request>,
    options: Vc<ResolveOptions>,
    query: RcStr,
) -> Result<Option<ResolveResultOrCell>> {
    Ok(match result {
        ImportMapResult::Result(result) => Some(ResolveResultOrCell::Cell(**result)),
        ImportMapResult::Alias(request, alias_lookup_path) => {
            let request_vc: Vc<Request> = **request;
            // Only add query if the aliased request doesn't already have one
            let request = if request_vc.query().await?.is_empty() && !query.is_empty() {
                request_vc.with_query(query.clone())
            } else {
                request_vc
            };
            let lookup_path = alias_lookup_path.clone().unwrap_or(lookup_path);

            // Compare request patterns to avoid cycles (ignoring query differences)
            let request_pattern = request.request_pattern();
            let original_pattern = original_request.request_pattern();

            if *request_pattern.await? == *original_pattern.await?
                && lookup_path == original_lookup_path
            {
                None
            } else {
                Some(ResolveResultOrCell::Cell(
                    resolve_internal(lookup_path, request, options)
                        .with_replaced_request_key_pattern(request_pattern, original_pattern),
                ))
            }
        }
        ImportMapResult::External {
            name,
            ty,
            traced,
            target,
        } => Some(ResolveResultOrCell::Value(ResolveResult::primary(
            ResolveResultItem::External {
                name: name.clone(),
                ty: *ty,
                traced: *traced,
                target: target.clone(),
            },
        ))),
        ImportMapResult::AliasExternal {
            name,
            ty,
            traced,
            lookup_dir: alias_lookup_path,
        } => {
            let request = Request::parse_string(name.clone());

            // We must avoid cycles during resolving
            if *request.to_resolved().await? == original_request
                && *alias_lookup_path == original_lookup_path
            {
                None
            } else {
                let is_external_resolvable = !resolve_internal(
                    alias_lookup_path.clone(),
                    request,
                    match ty {
                        // TODO is that root correct?
                        ExternalType::CommonJs => {
                            node_cjs_resolve_options(alias_lookup_path.root().owned().await?)
                        }
                        ExternalType::EcmaScriptModule => {
                            node_esm_resolve_options(alias_lookup_path.root().owned().await?)
                        }
                        ExternalType::Script | ExternalType::Url | ExternalType::Global => options,
                    },
                )
                .await?
                .is_unresolvable_ref();
                if is_external_resolvable {
                    Some(ResolveResultOrCell::Value(ResolveResult::primary(
                        ResolveResultItem::External {
                            name: name.clone(),
                            ty: *ty,
                            traced: *traced,
                            target: None,
                        },
                    )))
                } else {
                    None
                }
            }
        }
        ImportMapResult::Alternatives(list) => {
            let results = list
                .iter()
                .map(|result| {
                    resolve_import_map_result(
                        result,
                        lookup_path.clone(),
                        original_lookup_path.clone(),
                        original_request,
                        options,
                        query.clone(),
                    )
                })
                .try_join()
                .await?;

            // Convert ResolveResultOrCells to cells in deterministic order after try_join completes
            let cells: Vec<Vc<ResolveResult>> = results
                .into_iter()
                .flatten()
                .map(|r| r.into_cell())
                .collect();
            Some(ResolveResultOrCell::Cell(merge_results(cells)))
        }
        ImportMapResult::NoEntry => None,
        ImportMapResult::Error(issue) => Some(ResolveResultOrCell::Value(ResolveResult::primary(
            ResolveResultItem::Error(*issue),
        ))),
    })
}

/// Result of resolving a file path. Either a cell (from early return paths like alias resolution)
/// or a value that needs to be converted to a cell later.
enum ResolveResultOrCell {
    Cell(Vc<ResolveResult>),
    Value(ResolveResult),
}

impl ResolveResultOrCell {
    fn into_cell(self) -> Vc<ResolveResult> {
        match self {
            ResolveResultOrCell::Cell(vc) => vc,
            ResolveResultOrCell::Value(value) => value.cell(),
        }
    }

    async fn into_cell_if_resolvable(self) -> Result<Option<Vc<ResolveResult>>> {
        match self {
            ResolveResultOrCell::Cell(resolved_result) => {
                if !*resolved_result.is_unresolvable().await? {
                    return Ok(Some(resolved_result));
                }
            }
            ResolveResultOrCell::Value(resolve_result) => {
                if !resolve_result.is_unresolvable_ref() {
                    return Ok(Some(resolve_result.cell()));
                }
            }
        }
        Ok(None)
    }
}

#[tracing::instrument(level = Level::TRACE, skip_all)]
async fn resolved(
    request_key: RequestKey,
    fs_path: FileSystemPath,
    original_context: FileSystemPath,
    original_request: Vc<Request>,
    options_value: &ResolveOptions,
    options: Vc<ResolveOptions>,
    query: RcStr,
    fragment: RcStr,
) -> Result<ResolveResultOrCell> {
    let result = &*fs_path.realpath_with_links().await?;
    let path = match &result.path_result {
        Ok(path) => path,
        Err(e) => bail!(e.as_error_message(&fs_path, result).await?),
    };

    let path_ref = path.clone();
    // Check alias field for path aliases first
    if let Some(result) = apply_in_package(
        path.parent(),
        options,
        options_value,
        |package_path| package_path.get_relative_path_to(&path_ref),
        query.clone(),
        fragment.clone(),
    )
    .await?
    {
        return Ok(result);
    }

    if let Some(resolved_map) = options_value.resolved_map {
        let result = resolved_map
            .lookup(path.clone(), original_context.clone(), original_request)
            .await?;

        let resolved_result = resolve_import_map_result(
            &result,
            path.parent(),
            original_context.clone(),
            original_request,
            options,
            query.clone(),
        )
        .await?;

        if let Some(result) = resolved_result {
            return Ok(result);
        }
    }
    let source = ResolvedVc::upcast(
        FileSource::new_with_query_and_fragment(path.clone(), query, fragment)
            .to_resolved()
            .await?,
    );
    Ok(ResolveResultOrCell::Value(
        if options_value.collect_affecting_sources {
            ResolveResult::source_with_affecting_sources(
                request_key,
                source,
                result
                    .symlinks
                    .iter()
                    .map(|symlink| async move {
                        anyhow::Ok(ResolvedVc::upcast(
                            FileSource::new(symlink.clone()).to_resolved().await?,
                        ))
                    })
                    .try_join()
                    .await?,
            )
        } else {
            ResolveResult::source_with_key(request_key, source)
        },
    ))
}

async fn handle_exports_imports_field(
    package_path: FileSystemPath,
    package_json_path: FileSystemPath,
    options: Vc<ResolveOptions>,
    exports_imports_field: &AliasMap<SubpathValue>,
    mut path: Pattern,
    conditions: &BTreeMap<RcStr, ConditionValue>,
    unspecified_conditions: &ConditionValue,
    query: RcStr,
) -> Result<Vc<ResolveResult>> {
    let mut results = Vec::new();
    let mut conditions_state = FxHashMap::default();

    if !query.is_empty() {
        path.push(query.into());
    }
    let req = path;

    let values = exports_imports_field.lookup(&req);
    for value in values {
        let value = value?;
        if value.output.add_results(
            value.prefix,
            value.key,
            conditions,
            unspecified_conditions,
            &mut conditions_state,
            &mut results,
        ) {
            // Match found, stop (leveraging the lazy `lookup` iterator).
            break;
        }
    }

    let mut resolved_results = Vec::new();
    for ReplacedSubpathValueResult {
        result_path,
        conditions,
        map_prefix,
        map_key,
    } in results
    {
        if let Some(result_path) = result_path.with_normalized_path() {
            let request = *Request::parse(Pattern::Concatenation(vec![
                Pattern::Constant(rcstr!("./")),
                result_path.clone(),
            ]))
            .to_resolved()
            .await?;

            let resolve_result = Box::pin(resolve_internal_inline(
                package_path.clone(),
                request,
                options,
            ))
            .await?;

            let resolve_result = if let Some(req) = req.as_constant_string() {
                resolve_result.with_request(req.clone())
            } else {
                match map_key {
                    AliasKey::Exact => resolve_result.with_request(map_prefix.clone().into()),
                    AliasKey::Wildcard { .. } => {
                        // - `req` is the user's request (key of the export map)
                        // - `result_path` is the final request (value of the export map), so
                        //   effectively `'{foo}*{bar}'`

                        // Because of the assertion in AliasMapLookupIterator, `req` is of the
                        // form:
                        // - "prefix...<dynamic>" or
                        // - "prefix...<dynamic>...suffix"

                        let mut old_request_key = result_path;
                        // Remove the Pattern::Constant(rcstr!("./")), from above again
                        old_request_key.push_front(rcstr!("./").into());
                        let new_request_key = req.clone();

                        resolve_result.with_replaced_request_key_pattern(
                            Pattern::new(old_request_key),
                            Pattern::new(new_request_key),
                        )
                    }
                }
            };

            let resolve_result = if !conditions.is_empty() {
                let resolve_result = resolve_result.await?.with_conditions(&conditions);
                resolve_result.cell()
            } else {
                resolve_result
            };
            resolved_results.push(resolve_result);
        }
    }

    // other options do not apply anymore when an exports field exist
    Ok(merge_results_with_affecting_sources(
        resolved_results,
        vec![ResolvedVc::upcast(
            FileSource::new(package_json_path).to_resolved().await?,
        )],
    ))
}

/// Resolves a `#dep` import using the containing package.json's `imports`
/// field. The dep may be a constant string or a pattern, and the values can be
/// static strings or conditions like `import` or `require` to handle ESM/CJS
/// with differently compiled files.
async fn resolve_package_internal_with_imports_field(
    file_path: FileSystemPath,
    request: Vc<Request>,
    resolve_options: Vc<ResolveOptions>,
    pattern: &Pattern,
    conditions: &BTreeMap<RcStr, ConditionValue>,
    unspecified_conditions: &ConditionValue,
) -> Result<Vc<ResolveResult>> {
    let Pattern::Constant(specifier) = pattern else {
        bail!("PackageInternal requests can only be Constant strings");
    };
    // https://github.com/nodejs/node/blob/1b177932/lib/internal/modules/esm/resolve.js#L615-L619
    if specifier == "#" || specifier.starts_with("#/") || specifier.ends_with('/') {
        ResolvingIssue {
            severity: resolve_error_severity(resolve_options).await?,
            file_path: file_path.clone(),
            request_type: format!("package imports request: `{specifier}`"),
            request: request.to_resolved().await?,
            resolve_options: resolve_options.to_resolved().await?,
            error_message: None,
            source: None,
        }
        .resolved_cell()
        .emit();
        return Ok(ResolveResult::unresolvable().cell());
    }

    let imports_result = imports_field(file_path).await?;
    let (imports, package_json_path) = match &*imports_result {
        ImportsFieldResult::Some(i, p) => (i, p.clone()),
        ImportsFieldResult::None => return Ok(ResolveResult::unresolvable().cell()),
    };

    handle_exports_imports_field(
        package_json_path.parent(),
        package_json_path.clone(),
        resolve_options,
        imports,
        Pattern::Constant(specifier.clone()),
        conditions,
        unspecified_conditions,
        RcStr::default(),
    )
    .await
}

/// ModulePart represents a part of a module.
///
/// Currently this is used only for ESMs.
#[derive(
    Serialize,
    Deserialize,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Hash,
    TraceRawVcs,
    TaskInput,
    NonLocalValue,
    Encode,
    Decode,
)]
pub enum ModulePart {
    /// Represents the side effects of a module. This part is evaluated even if
    /// all exports are unused.
    Evaluation,
    /// Represents an export of a module.
    Export(RcStr),
    /// Represents a renamed export of a module.
    RenamedExport {
        original_export: RcStr,
        export: RcStr,
    },
    /// Represents a namespace object of a module exported as named export.
    RenamedNamespace { export: RcStr },
    /// A pointer to a specific part.
    Internal(u32),
    /// The local declarations of a module.
    Locals,
    /// The whole exports of a module.
    Exports,
    /// A facade of the module behaving like the original, but referencing
    /// internal parts.
    Facade,
}

impl ModulePart {
    pub fn evaluation() -> Self {
        ModulePart::Evaluation
    }

    pub fn export(export: RcStr) -> Self {
        ModulePart::Export(export)
    }

    pub fn renamed_export(original_export: RcStr, export: RcStr) -> Self {
        ModulePart::RenamedExport {
            original_export,
            export,
        }
    }

    pub fn renamed_namespace(export: RcStr) -> Self {
        ModulePart::RenamedNamespace { export }
    }

    pub fn internal(id: u32) -> Self {
        ModulePart::Internal(id)
    }

    pub fn locals() -> Self {
        ModulePart::Locals
    }

    pub fn exports() -> Self {
        ModulePart::Exports
    }

    pub fn facade() -> Self {
        ModulePart::Facade
    }
}

impl Display for ModulePart {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ModulePart::Evaluation => f.write_str("module evaluation"),
            ModulePart::Export(export) => write!(f, "export {export}"),
            ModulePart::RenamedExport {
                original_export,
                export,
            } => write!(f, "export {original_export} as {export}"),
            ModulePart::RenamedNamespace { export } => {
                write!(f, "export * as {export}")
            }
            ModulePart::Internal(id) => write!(f, "internal part {id}"),
            ModulePart::Locals => f.write_str("locals"),
            ModulePart::Exports => f.write_str("exports"),
            ModulePart::Facade => f.write_str("facade"),
        }
    }
}
#[cfg(test)]
mod tests {
    use std::{
        fs::{File, create_dir_all},
        io::Write,
    };

    use turbo_rcstr::{RcStr, rcstr};
    use turbo_tasks::{TryJoinIterExt, Vc};
    use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
    use turbo_tasks_fs::{DiskFileSystem, FileSystem, FileSystemPath};

    use crate::{
        resolve::{
            ResolveResult, ResolveResultItem, node::node_esm_resolve_options, parse::Request,
            pattern::Pattern,
        },
        source::Source,
    };

    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_explicit_js_resolves_to_ts() {
        resolve_relative_request_test(TestParams {
            files: vec!["foo.js", "foo.ts"],
            pattern: rcstr!("./foo.js").into(),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![("./foo.js", "foo.ts")],
        })
        .await;
    }

    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_implicit_request_ts_priority() {
        resolve_relative_request_test(TestParams {
            files: vec!["foo.js", "foo.ts"],
            pattern: rcstr!("./foo").into(),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![("./foo", "foo.ts")],
        })
        .await;
    }

    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_ts_priority_over_json() {
        resolve_relative_request_test(TestParams {
            files: vec!["posts.json", "posts.ts"],
            pattern: rcstr!("./posts").into(),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![("./posts", "posts.ts")],
        })
        .await;
    }

    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_only_js_file_no_ts() {
        resolve_relative_request_test(TestParams {
            files: vec!["bar.js"],
            pattern: rcstr!("./bar.js").into(),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![("./bar.js", "bar.js")],
        })
        .await;
    }

    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_explicit_ts_request() {
        resolve_relative_request_test(TestParams {
            files: vec!["foo.js", "foo.ts"],
            pattern: rcstr!("./foo.ts").into(),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![("./foo.ts", "foo.ts")],
        })
        .await;
    }

    // Fragment handling tests
    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_fragment() {
        resolve_relative_request_test(TestParams {
            files: vec!["client.ts"],
            pattern: rcstr!("./client#frag").into(),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![("./client", "client.ts")],
        })
        .await;
    }

    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_fragment_as_part_of_filename() {
        // When a file literally contains '#' in its name, it should be preserved
        resolve_relative_request_test(TestParams {
            files: vec!["client#component.js", "client#component.ts"],
            pattern: rcstr!("./client#component.js").into(),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            // Whether or not this request key is correct somewhat ambiguous.  It depends on whether
            // or not we consider this fragment to be part of the request pattern
            expected: vec![("./client", "client#component.ts")],
        })
        .await;
    }

    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_fragment_with_ts_priority() {
        // Fragment handling with extension priority
        resolve_relative_request_test(TestParams {
            files: vec!["page#section.js", "page#section.ts"],
            pattern: rcstr!("./page#section").into(),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![("./page", "page#section.ts")],
        })
        .await;
    }

    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_query() {
        resolve_relative_request_test(TestParams {
            files: vec!["client.ts", "client.js"],
            pattern: rcstr!("./client?q=s").into(),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![("./client", "client.ts")],
        })
        .await;
    }

    // Dynamic pattern tests
    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_dynamic_pattern_with_js_extension() {
        // Pattern: ./src/*.js should generate multiple keys with .ts priority
        // When both foo.js and foo.ts exist, dynamic patterns need both keys for runtime resolution
        // Results are sorted alphabetically by key
        resolve_relative_request_test(TestParams {
            files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
            pattern: Pattern::Concatenation(vec![
                Pattern::Constant(rcstr!("./src/")),
                Pattern::Dynamic,
                Pattern::Constant(rcstr!(".js")),
            ]),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![
                ("./src/foo.js", "src/foo.ts"),
                ("./src/bar.js", "src/bar.js"),
            ],
        })
        .await;
    }

    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_dynamic_pattern_without_extension() {
        // Pattern: ./src/* (no extension) with TypeScript priority
        // Dynamic patterns generate keys for all matched files, including extension alternatives
        // Results are sorted deterministically by matched file name
        resolve_relative_request_test(TestParams {
            files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
            pattern: Pattern::Concatenation(vec![
                Pattern::Constant(rcstr!("./src/")),
                Pattern::Dynamic,
            ]),
            enable_typescript_with_output_extension: true,
            fully_specified: false,
            custom_extensions: None,
            expected: vec![
                ("./src/bar.js", "src/bar.js"),
                ("./src/bar", "src/bar.js"),
                // TODO: all three should point at the .ts file
                // This happens because read_matches returns the `.js` file first (alphabetically
                // foo.js < foo.ts) and foo (extensionless) is deduped to point at foo.js since it
                // was the first file with that base name encountered. To fix this we would need to
                // change how we handle extension priority for dynamic patterns.
                ("./src/foo.js", "src/foo.js"),
                ("./src/foo", "src/foo.js"),
                ("./src/foo.ts", "src/foo.ts"),
            ],
        })
        .await;
    }

    /// Test that custom `resolveExtensions` ordering is respected:
    /// `.web.tsx` appears before `.tsx` in the list, so it must win when both exist.
    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_custom_extensions_web_before_default() {
        resolve_relative_request_test(TestParams {
            files: vec!["Component.web.tsx", "Component.tsx"],
            pattern: rcstr!("./Component").into(),
            enable_typescript_with_output_extension: false,
            fully_specified: false,
            custom_extensions: Some(vec![
                rcstr!(".web.tsx"),
                rcstr!(".web.ts"),
                rcstr!(".web.jsx"),
                rcstr!(".web.js"),
                rcstr!(".tsx"),
                rcstr!(".ts"),
                rcstr!(".jsx"),
                rcstr!(".js"),
            ]),
            expected: vec![("./Component", "Component.web.tsx")],
        })
        .await;
    }

    /// Test that when `.web.tsx` doesn't exist, resolution falls back to `.tsx`.
    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
    async fn test_custom_extensions_fallback_when_web_missing() {
        resolve_relative_request_test(TestParams {
            files: vec!["Component.tsx"],
            pattern: rcstr!("./Component").into(),
            enable_typescript_with_output_extension: false,
            fully_specified: false,
            custom_extensions: Some(vec![
                rcstr!(".web.tsx"),
                rcstr!(".web.ts"),
                rcstr!(".web.jsx"),
                rcstr!(".web.js"),
                rcstr!(".tsx"),
                rcstr!(".ts"),
                rcstr!(".jsx"),
                rcstr!(".js"),
            ]),
            expected: vec![("./Component", "Component.tsx")],
        })
        .await;
    }

    /// Parameters for resolve_relative_request_test
    struct TestParams<'a> {
        files: Vec<&'a str>,
        pattern: Pattern,
        enable_typescript_with_output_extension: bool,
        fully_specified: bool,
        /// Custom extensions list; when `None`, uses the default `[".ts", ".js", ".json"]`
        custom_extensions: Option<Vec<RcStr>>,
        expected: Vec<(&'a str, &'a str)>,
    }

    /// Helper function to run a single extension priority test case
    async fn resolve_relative_request_test(
        TestParams {
            files,
            pattern,
            enable_typescript_with_output_extension,
            fully_specified,
            custom_extensions,
            expected,
        }: TestParams<'_>,
    ) {
        let scratch = tempfile::tempdir().unwrap();
        {
            let path = scratch.path();

            for file_name in &files {
                let file_path = path.join(file_name);
                if let Some(parent) = file_path.parent() {
                    create_dir_all(parent).unwrap();
                }
                File::create_new(&file_path)
                    .unwrap()
                    .write_all(format!("export default '{file_name}'").as_bytes())
                    .unwrap();
            }
        }

        let path: RcStr = scratch.path().to_str().unwrap().into();
        let expected_owned: Vec<(String, String)> = expected
            .iter()
            .map(|(k, v)| (k.to_string(), v.to_string()))
            .collect();

        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
            BackendOptions::default(),
            noop_backing_storage(),
        ));

        let custom_extensions_owned = custom_extensions;

        tt.run_once(async move {
            #[turbo_tasks::value(transparent)]
            struct ResolveRelativeRequestOutput(Vec<(String, String)>);

            #[turbo_tasks::function(operation)]
            async fn resolve_relative_request_operation(
                path: RcStr,
                pattern: Pattern,
                enable_typescript_with_output_extension: bool,
                fully_specified: bool,
                custom_extensions: Option<Vec<RcStr>>,
            ) -> anyhow::Result<Vc<ResolveRelativeRequestOutput>> {
                let fs = DiskFileSystem::new(rcstr!("temp"), Vc::cell(path));
                let lookup_path = fs.root().owned().await?;

                let result = resolve_relative_helper(
                    lookup_path,
                    pattern,
                    enable_typescript_with_output_extension,
                    fully_specified,
                    custom_extensions,
                )
                .await?;

                let results: Vec<(String, String)> = result
                    .primary
                    .iter()
                    .map(async |(k, v)| {
                        Ok((
                            k.to_string(),
                            if let ResolveResultItem::Source(source) = v {
                                source.ident().await?.path.path.to_string()
                            } else {
                                unreachable!()
                            },
                        ))
                    })
                    .try_join()
                    .await?;

                Ok(Vc::cell(results))
            }

            let results = resolve_relative_request_operation(
                path,
                pattern,
                enable_typescript_with_output_extension,
                fully_specified,
                custom_extensions_owned,
            )
            .read_strongly_consistent()
            .await?;

            assert_eq!(&*results, &expected_owned);

            Ok(())
        })
        .await
        .unwrap();
    }

    #[turbo_tasks::function]
    async fn resolve_relative_helper(
        lookup_path: FileSystemPath,
        pattern: Pattern,
        enable_typescript_with_output_extension: bool,
        fully_specified: bool,
        custom_extensions: Option<Vec<RcStr>>,
    ) -> anyhow::Result<Vc<ResolveResult>> {
        let request = Request::parse(pattern.clone());

        let extensions = custom_extensions
            .unwrap_or_else(|| vec![rcstr!(".ts"), rcstr!(".js"), rcstr!(".json")]);
        let mut options_value = node_esm_resolve_options(lookup_path.clone())
            .with_fully_specified(fully_specified)
            .with_extensions(extensions)
            .owned()
            .await?;
        options_value.enable_typescript_with_output_extension =
            enable_typescript_with_output_extension;
        let options = options_value.clone().cell();
        match &*request.await? {
            Request::Relative {
                path,
                query,
                force_in_lookup_dir,
                fragment,
            } => {
                super::resolve_relative_request(
                    lookup_path,
                    request,
                    options,
                    &options_value,
                    path,
                    query.clone(),
                    *force_in_lookup_dir,
                    fragment.clone(),
                )
                .await
            }
            r => panic!("request should be relative, got {r:?}"),
        }
    }
}
Quest for Codev2.0.0
/
SIGN IN