// Copyright 2018-2026 the Deno authors. MIT license.

#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]

use std::borrow::Cow;
use std::path::PathBuf;

use boxed_error::Boxed;
use deno_cache_dir::npm::NpmCacheDir;
use deno_error::JsError;
use deno_package_json::PackageJsonDepValue;
use deno_package_json::PackageJsonDepValueParseError;
use deno_semver::npm::NpmPackageReqReference;
pub use node_resolver::DenoIsBuiltInNodeModuleChecker;
use node_resolver::InNpmPackageChecker;
use node_resolver::IsBuiltInNodeModuleChecker;
use node_resolver::NodeResolutionKind;
pub use node_resolver::NodeResolverOptions;
use node_resolver::NodeResolverRc;
use node_resolver::NpmPackageFolderResolver;
use node_resolver::ResolutionMode;
use node_resolver::UrlOrPath;
use node_resolver::UrlOrPathRef;
use node_resolver::errors::NodeJsErrorCode;
use node_resolver::errors::NodeResolveError;
use node_resolver::errors::NodeResolveErrorKind;
use node_resolver::errors::UnknownBuiltInNodeModuleError;
use npm::NodeModulesOutOfDateError;
use npm::NpmReqResolverRc;
use npm::ResolveIfForNpmPackageErrorKind;
use npm::ResolvePkgFolderFromDenoReqError;
use thiserror::Error;
use url::Url;

use self::npm::NpmResolver;
use self::npm::NpmResolverSys;
use self::npm::ResolveNpmReqRefError;
use crate::workspace::MappedResolution;
use crate::workspace::MappedResolutionDiagnostic;
use crate::workspace::MappedResolutionError;
use crate::workspace::WorkspaceResolvePkgJsonFolderError;
use crate::workspace::WorkspaceResolver;

pub mod bun_lockfile_import;
pub mod cache;
pub mod cjs;
pub mod collections;
pub mod deno_json;
pub mod display;
#[cfg(feature = "deno_ast")]
pub mod emit;
pub mod factory;
#[cfg(feature = "graph")]
pub mod file_fetcher;
#[cfg(feature = "graph")]
pub mod graph;
pub mod import_map;
pub mod loader;
pub mod lockfile;
pub mod npm;
pub mod npm_lockfile_import;
pub mod npmrc;
pub mod pnpm_lockfile_import;
#[cfg(feature = "sync")]
mod rt;
pub mod workspace;
pub mod yarn_lockfile_import;

#[allow(clippy::disallowed_types, reason = "definition")]
pub type WorkspaceResolverRc<TSys> =
  deno_maybe_sync::MaybeArc<WorkspaceResolver<TSys>>;

#[allow(clippy::disallowed_types, reason = "definition")]
pub(crate) type NpmCacheDirRc = deno_maybe_sync::MaybeArc<NpmCacheDir>;

#[derive(Debug, Clone)]
pub struct DenoResolution {
  pub url: Url,
  pub maybe_diagnostic: Option<Box<MappedResolutionDiagnostic>>,
  pub found_package_json_dep: bool,
}

#[derive(Debug, Boxed, JsError)]
pub struct DenoResolveError(pub Box<DenoResolveErrorKind>);

impl DenoResolveError {
  #[cfg(feature = "graph")]
  pub fn into_deno_graph_error(self) -> deno_graph::source::ResolveError {
    use deno_graph::source::ResolveError;

    match self.into_kind() {
      DenoResolveErrorKind::MappedResolution(mapped_resolution_error) => {
        match mapped_resolution_error {
          MappedResolutionError::Specifier(e) => ResolveError::Specifier(e),
          // deno_graph checks specifically for an ImportMapError
          MappedResolutionError::ImportMap(e) => ResolveError::from_err(e),
          MappedResolutionError::Workspace(e) => ResolveError::from_err(e),
          MappedResolutionError::NotFoundInCompilerOptionsPaths(e) => {
            ResolveError::from_err(e)
          }
        }
      }
      err => ResolveError::from_err(err),
    }
  }

  pub fn maybe_specifier(&self) -> Option<Cow<'_, UrlOrPath>> {
    match self.as_kind() {
      DenoResolveErrorKind::Node(err) => err.maybe_specifier(),
      DenoResolveErrorKind::PathToUrl(err) => {
        Some(Cow::Owned(UrlOrPath::Path(err.0.clone())))
      }
      DenoResolveErrorKind::ResolveNpmReqRef(err) => err.err.maybe_specifier(),
      DenoResolveErrorKind::MappedResolution(_)
      | DenoResolveErrorKind::WorkspaceResolvePkgJsonFolder(_)
      | DenoResolveErrorKind::ResolvePkgFolderFromDenoReq(_)
      | DenoResolveErrorKind::InvalidVendorFolderImport
      | DenoResolveErrorKind::UnsupportedPackageJsonFileSpecifier
      | DenoResolveErrorKind::NodeModulesOutOfDate(_)
      | DenoResolveErrorKind::CatalogPackageNotFound(_)
      | DenoResolveErrorKind::PackageJsonDepValueParse(_)
      | DenoResolveErrorKind::PackageJsonDepValueUrlParse(_) => None,
    }
  }
}

#[derive(Debug, Error, JsError)]
pub enum DenoResolveErrorKind {
  #[class(type)]
  #[error(
    "Importing from the vendor directory is not permitted. Use a remote specifier instead or disable vendoring."
  )]
  InvalidVendorFolderImport,
  #[class(type)]
  #[error(
    "Importing npm packages via a file: specifier is only supported with --node-modules-dir=manual"
  )]
  UnsupportedPackageJsonFileSpecifier,
  #[class(type)]
  #[error("Package '{0}' not found in catalog")]
  CatalogPackageNotFound(String),
  #[class(inherit)]
  #[error(transparent)]
  MappedResolution(#[from] MappedResolutionError),
  #[class(inherit)]
  #[error(transparent)]
  Node(#[from] NodeResolveError),
  #[class(inherit)]
  #[error(transparent)]
  NodeModulesOutOfDate(#[from] NodeModulesOutOfDateError),
  #[class(inherit)]
  #[error(transparent)]
  PackageJsonDepValueParse(#[from] PackageJsonDepValueParseError),
  #[class(inherit)]
  #[error(transparent)]
  PackageJsonDepValueUrlParse(url::ParseError),
  #[class(inherit)]
  #[error(transparent)]
  PathToUrl(#[from] deno_path_util::PathToUrlError),
  #[class(inherit)]
  #[error(transparent)]
  ResolveNpmReqRef(#[from] ResolveNpmReqRefError),
  #[class(inherit)]
  #[error(transparent)]
  ResolvePkgFolderFromDenoReq(#[from] ResolvePkgFolderFromDenoReqError),
  #[class(inherit)]
  #[error(transparent)]
  WorkspaceResolvePkgJsonFolder(#[from] WorkspaceResolvePkgJsonFolderError),
}

impl DenoResolveErrorKind {
  pub fn maybe_node_code(&self) -> Option<NodeJsErrorCode> {
    match self {
      DenoResolveErrorKind::InvalidVendorFolderImport
      | DenoResolveErrorKind::UnsupportedPackageJsonFileSpecifier
      | DenoResolveErrorKind::CatalogPackageNotFound { .. }
      | DenoResolveErrorKind::MappedResolution { .. }
      | DenoResolveErrorKind::NodeModulesOutOfDate { .. }
      | DenoResolveErrorKind::PackageJsonDepValueParse { .. }
      | DenoResolveErrorKind::PackageJsonDepValueUrlParse { .. }
      | DenoResolveErrorKind::PathToUrl { .. }
      | DenoResolveErrorKind::ResolvePkgFolderFromDenoReq { .. }
      | DenoResolveErrorKind::WorkspaceResolvePkgJsonFolder { .. } => None,
      DenoResolveErrorKind::ResolveNpmReqRef(err) => {
        err.err.as_kind().maybe_code()
      }
      DenoResolveErrorKind::Node(err) => err.as_kind().maybe_code(),
    }
  }
}

#[derive(Debug)]
pub struct NodeAndNpmResolvers<
  TInNpmPackageChecker: InNpmPackageChecker,
  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
  TNpmPackageFolderResolver: NpmPackageFolderResolver,
  TSys: NpmResolverSys,
> {
  pub node_resolver: NodeResolverRc<
    TInNpmPackageChecker,
    TIsBuiltInNodeModuleChecker,
    TNpmPackageFolderResolver,
    TSys,
  >,
  pub npm_resolver: NpmResolver<TSys>,
  pub npm_req_resolver: NpmReqResolverRc<
    TInNpmPackageChecker,
    TIsBuiltInNodeModuleChecker,
    TNpmPackageFolderResolver,
    TSys,
  >,
}

#[sys_traits::auto_impl]
pub trait DenoResolverSys: NpmResolverSys {}

pub struct DenoResolverOptions<
  'a,
  TInNpmPackageChecker: InNpmPackageChecker,
  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
  TNpmPackageFolderResolver: NpmPackageFolderResolver,
  TSys: DenoResolverSys,
> {
  pub in_npm_pkg_checker: TInNpmPackageChecker,
  pub node_and_req_resolver: Option<
    NodeAndNpmResolvers<
      TInNpmPackageChecker,
      TIsBuiltInNodeModuleChecker,
      TNpmPackageFolderResolver,
      TSys,
    >,
  >,
  pub workspace_resolver: WorkspaceResolverRc<TSys>,
  /// Whether "bring your own node_modules" is enabled where Deno does not
  /// setup the node_modules directories automatically, but instead uses
  /// what already exists on the file system.
  pub is_byonm: bool,
  pub maybe_vendor_dir: Option<&'a PathBuf>,
}

#[allow(clippy::disallowed_types, reason = "definition")]
pub type RawDenoResolverRc<
  TInNpmPackageChecker,
  TIsBuiltInNodeModuleChecker,
  TNpmPackageFolderResolver,
  TSys,
> = deno_maybe_sync::MaybeArc<
  RawDenoResolver<
    TInNpmPackageChecker,
    TIsBuiltInNodeModuleChecker,
    TNpmPackageFolderResolver,
    TSys,
  >,
>;

/// Helper type for a RawDenoResolverRc that has the implementations
/// used by the Deno CLI.
pub type DefaultRawDenoResolverRc<TSys> = RawDenoResolverRc<
  npm::DenoInNpmPackageChecker,
  DenoIsBuiltInNodeModuleChecker,
  npm::NpmResolver<TSys>,
  TSys,
>;

/// A resolver that takes care of resolution, taking into account loaded
/// import map, JSX settings.
#[derive(Debug)]
pub struct RawDenoResolver<
  TInNpmPackageChecker: InNpmPackageChecker,
  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
  TNpmPackageFolderResolver: NpmPackageFolderResolver,
  TSys: DenoResolverSys,
> {
  in_npm_pkg_checker: TInNpmPackageChecker,
  node_and_npm_resolver: Option<
    NodeAndNpmResolvers<
      TInNpmPackageChecker,
      TIsBuiltInNodeModuleChecker,
      TNpmPackageFolderResolver,
      TSys,
    >,
  >,
  workspace_resolver: WorkspaceResolverRc<TSys>,
  is_byonm: bool,
  maybe_vendor_specifier: Option<Url>,
}

impl<
  TInNpmPackageChecker: InNpmPackageChecker,
  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
  TNpmPackageFolderResolver: NpmPackageFolderResolver,
  TSys: DenoResolverSys,
>
  RawDenoResolver<
    TInNpmPackageChecker,
    TIsBuiltInNodeModuleChecker,
    TNpmPackageFolderResolver,
    TSys,
  >
{
  pub fn new(
    options: DenoResolverOptions<
      TInNpmPackageChecker,
      TIsBuiltInNodeModuleChecker,
      TNpmPackageFolderResolver,
      TSys,
    >,
  ) -> Self {
    Self {
      in_npm_pkg_checker: options.in_npm_pkg_checker,
      node_and_npm_resolver: options.node_and_req_resolver,
      workspace_resolver: options.workspace_resolver,
      is_byonm: options.is_byonm,
      maybe_vendor_specifier: options
        .maybe_vendor_dir
        .and_then(|v| deno_path_util::url_from_directory_path(v).ok()),
    }
  }

  pub fn resolve(
    &self,
    raw_specifier: &str,
    referrer: &Url,
    resolution_mode: ResolutionMode,
    resolution_kind: NodeResolutionKind,
  ) -> Result<DenoResolution, DenoResolveError> {
    let mut found_package_json_dep = false;
    let mut maybe_diagnostic = None;
    // Use node resolution if we're in an npm package
    if let Some(node_and_npm_resolver) = self.node_and_npm_resolver.as_ref() {
      let node_resolver = &node_and_npm_resolver.node_resolver;
      if referrer.scheme() == "file"
        && self.in_npm_pkg_checker.in_npm_package(referrer)
      {
        log::debug!(
          "{}: specifier={} referrer={} mode={:?} kind={:?}",
          deno_terminal::colors::magenta("resolving in npm package"),
          raw_specifier,
          referrer,
          resolution_mode,
          resolution_kind
        );
        return node_resolver
          .resolve(raw_specifier, referrer, resolution_mode, resolution_kind)
          .and_then(|res| {
            Ok(DenoResolution {
              url: res.into_url()?,
              found_package_json_dep,
              maybe_diagnostic,
            })
          })
          .map_err(|e| e.into());
      }
    }

    // Attempt to resolve with the workspace resolver
    let result = self.workspace_resolver.resolve(
      raw_specifier,
      referrer,
      resolution_kind.into(),
    );
    let result = match result {
      Ok(resolution) => match resolution {
        MappedResolution::Normal {
          specifier,
          maybe_diagnostic: current_diagnostic,
          ..
        } => {
          maybe_diagnostic = current_diagnostic;
          Ok(specifier)
        }
        MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
          Ok(specifier)
        }
        MappedResolution::WorkspaceNpmPackage {
          target_pkg_json: pkg_json,
          sub_path,
          ..
        } => self
          .node_and_npm_resolver
          .as_ref()
          .unwrap()
          .node_resolver
          .resolve_package_subpath_from_deno_module(
            pkg_json.dir_path(),
            sub_path.as_deref(),
            Some(referrer),
            resolution_mode,
            resolution_kind,
          )
          .map_err(|e| {
            DenoResolveErrorKind::Node(e.into_node_resolve_error()).into_box()
          })
          .and_then(|r| Ok(r.into_url()?)),
        MappedResolution::PackageJson {
          dep_result,
          alias,
          sub_path,
          ..
        } => {
          // found a specifier in the package.json, so mark that
          // we need to do an "npm install" later
          found_package_json_dep = true;

          dep_result
            .as_ref()
            .map_err(|e| {
              DenoResolveErrorKind::PackageJsonDepValueParse(e.clone())
                .into_box()
            })
            .and_then(|dep| match dep {
              PackageJsonDepValue::File(_) => {
                // We don't support --node-modules-dir=auto/none because it's too
                // much work to get this to work with a lockfile properly and for
                // multiple managed node_modules directories to work. If someone wants
                // to do this, then they need to use the default (manual)
                Err(
                  DenoResolveErrorKind::UnsupportedPackageJsonFileSpecifier
                    .into_box(),
                )
              }
              // todo(dsherret): it seems bad that we're converting this
              // to a url because the req might not be a valid url.
              PackageJsonDepValue::Req(req) => Url::parse(&format!(
                "npm:{}{}",
                req,
                sub_path.map(|s| format!("/{}", s)).unwrap_or_default()
              ))
              .map_err(|e| {
                DenoResolveErrorKind::PackageJsonDepValueUrlParse(e).into_box()
              }),
              PackageJsonDepValue::Workspace { name, version_req } => self
                .workspace_resolver
                .resolve_workspace_pkg_json_folder_for_pkg_json_dep(
                  name.as_deref().unwrap_or(alias),
                  version_req,
                )
                .map_err(|e| {
                  DenoResolveErrorKind::WorkspaceResolvePkgJsonFolder(e)
                    .into_box()
                })
                .and_then(|pkg_folder| {
                  self
                    .node_and_npm_resolver
                    .as_ref()
                    .unwrap()
                    .node_resolver
                    .resolve_package_subpath_from_deno_module(
                      pkg_folder,
                      sub_path.as_deref(),
                      Some(referrer),
                      resolution_mode,
                      resolution_kind,
                    )
                    .map_err(|e| {
                      DenoResolveErrorKind::Node(e.into_node_resolve_error())
                        .into_box()
                    })
                })
                .and_then(|r| Ok(r.into_url()?)),
              PackageJsonDepValue::Catalog(catalog_name) => self
                .workspace_resolver
                .resolve_catalog_dep(alias, catalog_name)
                .ok_or_else(|| {
                  DenoResolveErrorKind::CatalogPackageNotFound(
                    alias.to_string(),
                  )
                  .into_box()
                })
                .and_then(|req| {
                  Url::parse(&format!(
                    "npm:{}{}",
                    req,
                    sub_path.map(|s| format!("/{}", s)).unwrap_or_default()
                  ))
                  .map_err(|e| {
                    DenoResolveErrorKind::PackageJsonDepValueUrlParse(e)
                      .into_box()
                  })
                }),
            })
        }
        MappedResolution::PackageJsonImport { pkg_json } => self
          .node_and_npm_resolver
          .as_ref()
          .unwrap()
          .node_resolver
          .resolve_package_import(
            raw_specifier,
            Some(&UrlOrPathRef::from_url(referrer)),
            Some(pkg_json),
            resolution_mode,
            resolution_kind,
          )
          .map_err(|e| {
            DenoResolveErrorKind::Node(
              NodeResolveErrorKind::PackageImportsResolve(e).into_box(),
            )
            .into_box()
          })
          .and_then(|r| Ok(r.into_url()?)),
      },
      Err(err) => Err(err.into()),
    };

    // When the user is vendoring, don't allow them to import directly from the vendor/ directory
    // as it might cause them confusion or duplicate dependencies. Additionally, this folder has
    // special treatment in the language server so it will definitely cause issues/confusion there
    // if they do this.
    if let Some(vendor_specifier) = &self.maybe_vendor_specifier
      && let Ok(specifier) = &result
      && specifier.as_str().starts_with(vendor_specifier.as_str())
    {
      return Err(DenoResolveErrorKind::InvalidVendorFolderImport.into_box());
    }

    let Some(NodeAndNpmResolvers {
      node_resolver,
      npm_req_resolver,
      ..
    }) = &self.node_and_npm_resolver
    else {
      return Ok(DenoResolution {
        url: result?,
        maybe_diagnostic,
        found_package_json_dep,
      });
    };

    match result {
      Ok(specifier) => {
        if specifier.scheme() == "node" {
          let module_name = specifier.path();
          return if node_resolver.is_builtin_node_module(module_name) {
            Ok(DenoResolution {
              url: specifier,
              maybe_diagnostic,
              found_package_json_dep,
            })
          } else {
            Err(
              NodeResolveErrorKind::UnknownBuiltInNodeModule(
                UnknownBuiltInNodeModuleError {
                  module_name: module_name.to_string(),
                },
              )
              .into_box()
              .into(),
            )
          };
        }

        if let Ok(npm_req_ref) =
          NpmPackageReqReference::from_specifier(&specifier)
        {
          // check if the npm specifier resolves to a workspace member
          if let Some(pkg_folder) = self
            .workspace_resolver
            .resolve_workspace_pkg_json_folder_for_npm_specifier(
              npm_req_ref.req(),
            )
          {
            return node_resolver
              .resolve_package_subpath_from_deno_module(
                pkg_folder,
                npm_req_ref.sub_path(),
                Some(referrer),
                resolution_mode,
                resolution_kind,
              )
              .map_err(|err| {
                DenoResolveErrorKind::ResolveNpmReqRef(ResolveNpmReqRefError {
                  npm_req_ref: npm_req_ref.clone(),
                  err: err.into(),
                })
                .into_box()
              })
              .and_then(|url_or_path| {
                Ok(DenoResolution {
                  url: url_or_path.into_url()?,
                  maybe_diagnostic,
                  found_package_json_dep,
                })
              });
          }

          if self.is_byonm {
            return npm_req_resolver
              .resolve_req_reference(
                &npm_req_ref,
                referrer,
                resolution_mode,
                resolution_kind,
              )
              .map_err(|err| {
                DenoResolveErrorKind::ResolveNpmReqRef(err).into_box()
              })
              .and_then(|url_or_path| {
                Ok(DenoResolution {
                  url: url_or_path.into_url()?,
                  maybe_diagnostic,
                  found_package_json_dep,
                })
              });
          }
        }

        Ok(DenoResolution {
          url: node_resolver
            .handle_if_in_node_modules(&specifier)
            .unwrap_or(specifier),
          maybe_diagnostic,
          found_package_json_dep,
        })
      }
      Err(err) => {
        // Check if the bare specifier is a known Node built-in module.
        // Built-ins always take priority, matching Node.js behavior where
        // built-in modules cannot be shadowed by node_modules packages.
        if node_resolver.is_builtin_node_module(raw_specifier) {
          return Ok(DenoResolution {
            url: Url::parse(&format!("node:{}", raw_specifier)).unwrap(),
            maybe_diagnostic,
            found_package_json_dep,
          });
        }

        // If byonm, check if the bare specifier resolves to an npm package
        if self.is_byonm && referrer.scheme() == "file" {
          let maybe_resolution = npm_req_resolver
            .resolve_if_for_npm_pkg(
              raw_specifier,
              referrer,
              resolution_mode,
              resolution_kind,
            )
            .map_err(|e| match e.into_kind() {
              ResolveIfForNpmPackageErrorKind::NodeResolve(e) => {
                DenoResolveErrorKind::Node(e).into_box()
              }
              ResolveIfForNpmPackageErrorKind::NodeModulesOutOfDate(e) => {
                e.into()
              }
            })?;
          if let Some(res) = maybe_resolution {
            return Ok(DenoResolution {
              url: res.into_url()?,
              maybe_diagnostic,
              found_package_json_dep,
            });
          }
        }

        Err(err)
      }
    }
  }

  #[cfg(feature = "graph")]
  pub(crate) fn resolve_non_workspace_npm_req_ref_to_file(
    &self,
    npm_req_ref: &NpmPackageReqReference,
    referrer: &Url,
    resolution_mode: ResolutionMode,
    resolution_kind: NodeResolutionKind,
  ) -> Result<node_resolver::UrlOrPath, npm::ResolveNpmReqRefError> {
    let Some(NodeAndNpmResolvers {
      npm_req_resolver, ..
    }) = &self.node_and_npm_resolver
    else {
      return Err(npm::ResolveNpmReqRefError {
        npm_req_ref: npm_req_ref.clone(),
        err: npm::ResolveReqWithSubPathErrorKind::NoNpm(npm::NoNpmError)
          .into_box(),
      });
    };
    npm_req_resolver.resolve_req_reference(
      npm_req_ref,
      referrer,
      resolution_mode,
      resolution_kind,
    )
  }
}

/// Whether the React CVE source patches are enabled. Opt in by setting the
/// `DENO_PATCH_REACT_CVE` environment variable to a non-empty value other than
/// `0`.
///
/// The variable is read exactly once (via `sys`) and cached. Module loading
/// happens before any user code runs, so the value is effectively snapshotted
/// at startup and cannot be toggled later via `Deno.env.set`.
pub fn is_react_cve_patch_enabled(sys: &impl sys_traits::EnvVar) -> bool {
  static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
  *ENABLED.get_or_init(|| {
    sys
      .env_var("DENO_PATCH_REACT_CVE")
      .map(|v| !v.is_empty() && v != "0")
      .unwrap_or(false)
  })
}

/// Load time source patches that neutralize two known React Server Components
/// vulnerabilities shipped in affected `react-server-dom-*` builds:
///
/// * CVE-2025-55182 (RCE): deserialized model keys are not filtered, letting a
///   crafted payload reach `constructor` / `prototype` / `_response`.
/// * CVE-2025-55184 (DoS): a cyclic thenable makes chunk fulfillment loop
///   forever.
///
/// Callers must gate this behind [`is_react_cve_patch_enabled`] (the patches
/// are opt-in via the `DENO_PATCH_REACT_CVE` environment variable) and only
/// apply it to JavaScript source.
pub fn patch_react_cves<'a>(
  filename: &str,
  code: Cow<'a, str>,
) -> Cow<'a, str> {
  let mut modified = Cow::Borrowed(&*code);
  let mut offset = 0;

  for (index, _) in code.match_indices("split(") {
    let index = index + offset;

    let len = "split(".len();
    if !modified[index + len..].starts_with("':')")
      && !modified[index + len..].starts_with("\":\")")
    {
      continue;
    }

    let Some((end, _)) = modified[index..].char_indices().nth(100) else {
      break;
    };

    if !modified[index..index + end].contains("resolved_model") {
      continue;
    }

    let insert =
      ".filter(x => !(['constructor', 'prototype', '_response'].includes(x)))";
    let mut s = String::with_capacity(modified.len() + insert.len());
    s.push_str(&modified[0..index + len + 4]);
    s.push_str(insert);
    offset += insert.len();
    s.push_str(&modified[(index + len + 4)..]);
    modified = Cow::Owned(s);
  }

  if offset > 0 {
    log::debug!("Patched React RCE (CVE-2025-55182) in {filename}");
  }

  // Collapse the stage 1 (RCE) result back into a `Cow<'a>` before stage 2
  // borrows it: the original input when nothing was patched, otherwise the
  // owned patched string. Stage 2 must fall back to this (not `code`) so a
  // stage 1 patch is preserved even when stage 2 makes no change.
  let stage1: Cow<'a, str> = match modified {
    Cow::Borrowed(_) => code,
    Cow::Owned(s) => Cow::Owned(s),
  };

  let code2 = &*stage1;
  let mut modified = Cow::Borrowed(&*stage1);
  let mut offset = 0;

  const PROTOTYPE_THEN: &str = ".prototype.then";
  'outer: for (index, _) in code2.match_indices(PROTOTYPE_THEN) {
    let index = index + offset;

    let after_equals;
    if modified[index + PROTOTYPE_THEN.len()..].starts_with(" =") {
      after_equals = index + PROTOTYPE_THEN.len() + 2;
    } else if modified[index + PROTOTYPE_THEN.len()..].starts_with("=") {
      after_equals = index + PROTOTYPE_THEN.len() + 1;
    } else {
      continue;
    };

    let Some((end, _)) = modified[index..].char_indices().nth(300) else {
      break;
    };

    if !modified[index..index + end].contains("resolved_model") {
      continue;
    }

    if !modified[index..index + end].contains("fulfilled") {
      continue;
    }

    // now match { until we end up at the next }
    let mut brace_count = 0;
    let mut insert_index = None;
    for (i, c) in modified[index..].char_indices() {
      if c == '{' {
        brace_count += 1;
      } else if c == '}' {
        if brace_count == 0 {
          continue 'outer;
        }
        brace_count -= 1;
        if brace_count == 0 {
          insert_index = Some(index + i + 1);
          break;
        }
      }
    }
    let Some(insert_index) = insert_index else {
      continue;
    };

    const INSERT_BEFORE: &str = "(()=>{const __old=";
    const INSERT_AFTER: &str = ";const __new=function(res, rej){return __old.call(this,(v)=>{let w=v;let l=0;while(w&&typeof w==='object'&&w.then===__new){l++;if(w===this||l>1000){if(typeof rej==='function')rej(new Error('Cannot have cyclic thenables.'));return}if(w.status==='fulfilled')w=w.value;else break;}res(v)},rej)};return __new})()";

    // after the = insert the before, and after the } insert the after
    let mut s = String::with_capacity(
      modified.len() + INSERT_BEFORE.len() + INSERT_AFTER.len(),
    );
    s.push_str(&modified[0..after_equals]);
    s.push_str(INSERT_BEFORE);
    s.push_str(&modified[after_equals..insert_index]);
    s.push_str(INSERT_AFTER);
    s.push_str(&modified[insert_index..]);
    offset += INSERT_BEFORE.len() + INSERT_AFTER.len();
    modified = Cow::Owned(s);
  }

  if offset > 0 {
    log::debug!("Patched React DoS (CVE-2025-55184) in {filename}");
  }

  match modified {
    Cow::Borrowed(_) => stage1,
    Cow::Owned(s) => Cow::Owned(s),
  }
}
