use std::{path::PathBuf, sync::Arc};
use napi::bindgen_prelude::{Either, Either3};
use napi_derive::napi;
use regex::Regex;
use rspack_resolver::AliasValue;
use rustc_hash::FxHashMap;
#[derive(Debug, Clone)]
#[napi(object)]
pub struct NapiResolveOptions {
pub tsconfig: Option<TsconfigOptions>,
#[napi(ts_type = "Record<string, string | false | string[]>")]
pub alias: Option<FxHashMap<String, AliasRawValueType>>,
#[napi(ts_type = "(string | string[])[]")]
pub alias_fields: Option<Vec<StrOrStrListType>>,
pub condition_names: Option<Vec<String>>,
pub description_files: Option<Vec<String>>,
pub enforce_extension: Option<EnforceExtension>,
#[napi(ts_type = "(string | string[])[]")]
pub exports_fields: Option<Vec<StrOrStrListType>>,
#[napi(ts_type = "(string | string[])[]")]
pub imports_fields: Option<Vec<StrOrStrListType>>,
#[napi(ts_type = "Record<string, Array<string>>")]
pub extension_alias: Option<FxHashMap<String, Vec<String>>>,
pub extensions: Option<Vec<String>>,
#[napi(ts_type = "Record<string, Array<string | undefined | null>>")]
pub fallback: Option<FxHashMap<String, Vec<Option<String>>>>,
pub fully_specified: Option<bool>,
#[napi(ts_type = "string | string[]")]
pub main_fields: Option<StrOrStrListType>,
pub main_files: Option<Vec<String>>,
#[napi(ts_type = "string | string[]")]
pub modules: Option<StrOrStrListType>,
pub resolve_to_context: Option<bool>,
pub prefer_relative: Option<bool>,
pub prefer_absolute: Option<bool>,
pub restrictions: Option<Vec<Restriction>>,
pub roots: Option<Vec<String>>,
pub symlinks: Option<bool>,
pub builtin_modules: Option<bool>,
pub enable_pnp: Option<bool>,
}
#[napi]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum EnforceExtension {
Auto,
Enabled,
Disabled,
}
impl EnforceExtension {
pub fn is_auto(&self) -> bool {
*self == Self::Auto
}
pub fn is_enabled(&self) -> bool {
*self == Self::Enabled
}
pub fn is_disabled(&self) -> bool {
*self == Self::Disabled
}
}
#[napi(object)]
#[derive(Debug, Clone)]
pub struct Restriction {
pub path: Option<String>,
pub regex: Option<String>,
}
#[napi(object)]
#[derive(Debug, Clone)]
pub struct TsconfigOptions {
pub config_file: String,
#[napi(ts_type = "'auto' | string[]")]
pub references: Option<Either<String, Vec<String>>>,
}
impl From<Restriction> for rspack_resolver::Restriction {
fn from(r: Restriction) -> Self {
match (r.path, r.regex) {
(None, None) => {
panic!("Should specify path or regex")
}
(None, Some(regex)) => {
let re = Regex::new(®ex).unwrap_or_else(|_| panic!("Invalid regex pattern: {regex}"));
Self::Fn(Arc::new(move |path| {
re.is_match(path.to_str().unwrap_or_default())
}))
}
(Some(path), None) => Self::Path(PathBuf::from(path)),
(Some(_), Some(_)) => {
panic!("Restriction can't be path and regex at the same time")
}
}
}
}
impl From<EnforceExtension> for rspack_resolver::EnforceExtension {
fn from(extension: EnforceExtension) -> Self {
match extension {
EnforceExtension::Auto => Self::Auto,
EnforceExtension::Enabled => Self::Enabled,
EnforceExtension::Disabled => Self::Disabled,
}
}
}
impl From<TsconfigOptions> for rspack_resolver::TsconfigOptions {
fn from(options: TsconfigOptions) -> Self {
Self {
config_file: PathBuf::from(options.config_file),
references: match options.references {
Some(Either::A(string)) if string.as_str() == "auto" => {
rspack_resolver::TsconfigReferences::Auto
}
Some(Either::A(opt)) => {
panic!("`{opt}` is not a valid option for tsconfig references")
}
Some(Either::B(paths)) => rspack_resolver::TsconfigReferences::Paths(
paths.into_iter().map(PathBuf::from).collect::<Vec<_>>(),
),
None => rspack_resolver::TsconfigReferences::Disabled,
},
}
}
}
type StrOrStrListType = Either<String, Vec<String>>;
pub struct StrOrStrList(pub StrOrStrListType);
impl From<StrOrStrList> for Vec<String> {
fn from(value: StrOrStrList) -> Self {
match value {
StrOrStrList(Either::A(s)) => Vec::from([s]),
StrOrStrList(Either::B(a)) => a,
}
}
}
pub type AliasRawValueType = Either3<Option<String>, bool, Vec<Option<String>>>;
pub(crate) struct AliasRawValue(pub AliasRawValueType);
impl From<AliasRawValue> for Vec<AliasValue> {
fn from(value: AliasRawValue) -> Self {
match value.0 {
Either3::A(opt) => match opt {
Some(s) => vec![AliasValue::Path(s)],
None => vec![AliasValue::Ignore],
},
Either3::B(_) => {
vec![AliasValue::Ignore]
}
Either3::C(vec) => vec
.into_iter()
.map(|opt| match opt {
Some(s) => AliasValue::Path(s),
None => AliasValue::Ignore,
})
.collect(),
}
}
}