use crate::fast_string::FastString;
use crate::module_specifier::ModuleSpecifier;
use anyhow::bail;
use anyhow::Error;
use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use std::future::Future;
mod loaders;
mod map;
mod module_map_data;
mod recursive_load;
#[cfg(all(test, not(miri)))]
mod tests;
#[cfg(test)]
pub use loaders::ModuleLoadEventCounts;
#[cfg(test)]
pub use loaders::TestingModuleLoader;
pub(crate) use loaders::ExtModuleLoader;
pub use loaders::ExtModuleLoaderCb;
pub use loaders::FsModuleLoader;
pub use loaders::ModuleLoader;
pub use loaders::NoopModuleLoader;
pub use loaders::StaticModuleLoader;
pub(crate) use map::ModuleMap;
pub type ModuleId = usize;
pub(crate) type ModuleLoadId = i32;
pub type ModuleCode = FastString;
pub type ModuleName = FastString;
pub type ImportMetaResolveCallback = Box<
dyn Fn(&dyn ModuleLoader, String, String) -> Result<ModuleSpecifier, Error>,
>;
pub(crate) fn default_import_meta_resolve_cb(
loader: &dyn ModuleLoader,
specifier: String,
referrer: String,
) -> Result<ModuleSpecifier, Error> {
if specifier.starts_with("npm:") {
bail!("\"npm:\" specifiers are currently not supported in import.meta.resolve()");
}
loader.resolve(&specifier, &referrer, ResolutionKind::DynamicImport)
}
pub type ValidateImportAttributesCb =
Box<dyn Fn(&mut v8::HandleScope, &HashMap<String, String>)>;
const SUPPORTED_TYPE_ASSERTIONS: &[&str] = &["json"];
pub(crate) fn validate_import_attributes(
scope: &mut v8::HandleScope,
assertions: &HashMap<String, String>,
) {
for (key, value) in assertions {
let msg = if key != "type" {
Some(format!("\"{key}\" attribute is not supported."))
} else if !SUPPORTED_TYPE_ASSERTIONS.contains(&value.as_str()) {
Some(format!("\"{value}\" is not a valid module type."))
} else {
None
};
let Some(msg) = msg else {
continue;
};
let message = v8::String::new(scope, &msg).unwrap();
let exception = v8::Exception::type_error(scope, message);
scope.throw_exception(exception);
return;
}
}
#[derive(Debug)]
pub(crate) enum ImportAssertionsKind {
StaticImport,
DynamicImport,
}
pub(crate) fn parse_import_assertions(
scope: &mut v8::HandleScope,
import_assertions: v8::Local<v8::FixedArray>,
kind: ImportAssertionsKind,
) -> HashMap<String, String> {
let mut assertions: HashMap<String, String> = HashMap::default();
let assertions_per_line = match kind {
ImportAssertionsKind::StaticImport => 3,
ImportAssertionsKind::DynamicImport => 2,
};
assert_eq!(import_assertions.length() % assertions_per_line, 0);
let no_of_assertions = import_assertions.length() / assertions_per_line;
for i in 0..no_of_assertions {
let assert_key = import_assertions
.get(scope, assertions_per_line * i)
.unwrap();
let assert_key_val = v8::Local::<v8::Value>::try_from(assert_key).unwrap();
let assert_value = import_assertions
.get(scope, (assertions_per_line * i) + 1)
.unwrap();
let assert_value_val =
v8::Local::<v8::Value>::try_from(assert_value).unwrap();
assertions.insert(
assert_key_val.to_rust_string_lossy(scope),
assert_value_val.to_rust_string_lossy(scope),
);
}
assertions
}
pub(crate) fn get_asserted_module_type_from_assertions(
assertions: &HashMap<String, String>,
) -> AssertedModuleType {
assertions
.get("type")
.map(|ty| {
if ty == "json" {
AssertedModuleType::Json
} else {
AssertedModuleType::JavaScriptOrWasm
}
})
.unwrap_or(AssertedModuleType::JavaScriptOrWasm)
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[repr(u32)]
pub enum ModuleType {
JavaScript,
Json,
}
impl std::fmt::Display for ModuleType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::JavaScript => write!(f, "JavaScript"),
Self::Json => write!(f, "JSON"),
}
}
}
#[derive(Debug)]
pub struct ModuleSource {
pub code: ModuleCode,
pub module_type: ModuleType,
module_url_specified: ModuleName,
module_url_found: Option<ModuleName>,
}
impl ModuleSource {
pub fn new(
module_type: impl Into<ModuleType>,
code: ModuleCode,
specifier: &ModuleSpecifier,
) -> Self {
let module_url_specified = specifier.as_ref().to_owned().into();
Self {
code,
module_type: module_type.into(),
module_url_specified,
module_url_found: None,
}
}
pub fn new_with_redirect(
module_type: impl Into<ModuleType>,
code: ModuleCode,
specifier: &ModuleSpecifier,
specifier_found: &ModuleSpecifier,
) -> Self {
let module_url_found = if specifier == specifier_found {
None
} else {
Some(specifier_found.as_ref().to_owned().into())
};
let module_url_specified = specifier.as_ref().to_owned().into();
Self {
code,
module_type: module_type.into(),
module_url_specified,
module_url_found,
}
}
#[cfg(test)]
pub fn for_test(code: &'static str, file: impl AsRef<str>) -> Self {
Self {
code: ModuleCode::from_static(code),
module_type: ModuleType::JavaScript,
module_url_specified: file.as_ref().to_owned().into(),
module_url_found: None,
}
}
#[cfg(test)]
pub fn for_test_with_redirect(
code: &'static str,
specified: impl AsRef<str>,
found: impl AsRef<str>,
) -> Self {
let specified = specified.as_ref().to_string();
let found = found.as_ref().to_string();
let found = if found == specified {
None
} else {
Some(found.into())
};
Self {
code: ModuleCode::from_static(code),
module_type: ModuleType::JavaScript,
module_url_specified: specified.into(),
module_url_found: found,
}
}
}
pub type ModuleSourceFuture = dyn Future<Output = Result<ModuleSource, Error>>;
#[derive(Debug, PartialEq, Eq)]
pub enum ResolutionKind {
MainModule,
Import,
DynamicImport,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[repr(u8)]
pub(crate) enum AssertedModuleType {
JavaScriptOrWasm,
Json,
Other(Cow<'static, str>),
}
impl AssertedModuleType {
pub fn to_v8<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
) -> v8::Local<'s, v8::Value> {
match self {
AssertedModuleType::JavaScriptOrWasm => v8::Integer::new(scope, 0).into(),
AssertedModuleType::Json => v8::Integer::new(scope, 1).into(),
AssertedModuleType::Other(ty) => {
v8::String::new(scope, ty).unwrap().into()
}
}
}
pub fn try_from_v8(
scope: &mut v8::HandleScope,
value: v8::Local<v8::Value>,
) -> Option<Self> {
Some(if let Some(int) = value.to_integer(scope) {
match int.int32_value(scope).unwrap_or_default() {
0 => AssertedModuleType::JavaScriptOrWasm,
1 => AssertedModuleType::Json,
_ => return None,
}
} else if let Ok(str) = v8::Local::<v8::String>::try_from(value) {
AssertedModuleType::Other(Cow::Owned(str.to_rust_string_lossy(scope)))
} else {
return None;
})
}
}
impl AsRef<AssertedModuleType> for AssertedModuleType {
fn as_ref(&self) -> &AssertedModuleType {
self
}
}
impl PartialEq<ModuleType> for AssertedModuleType {
fn eq(&self, other: &ModuleType) -> bool {
match other {
ModuleType::JavaScript => self == &AssertedModuleType::JavaScriptOrWasm,
ModuleType::Json => self == &AssertedModuleType::Json,
}
}
}
impl From<ModuleType> for AssertedModuleType {
fn from(module_type: ModuleType) -> AssertedModuleType {
match module_type {
ModuleType::JavaScript => AssertedModuleType::JavaScriptOrWasm,
ModuleType::Json => AssertedModuleType::Json,
}
}
}
impl std::fmt::Display for AssertedModuleType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::JavaScriptOrWasm => write!(f, "JavaScriptOrWasm"),
Self::Json => write!(f, "JSON"),
Self::Other(ty) => write!(f, "Other({ty})"),
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub(crate) struct ModuleRequest {
pub specifier: String,
pub asserted_module_type: AssertedModuleType,
}
#[derive(Debug, PartialEq)]
pub(crate) struct ModuleInfo {
#[allow(unused)]
pub id: ModuleId,
pub main: bool,
pub name: ModuleName,
pub requests: Vec<ModuleRequest>,
pub module_type: AssertedModuleType,
}
#[derive(Debug)]
pub(crate) enum ModuleError {
Exception(v8::Global<v8::Value>),
Other(Error),
}