use crate::{
ast::{
imported_specifier_name, make_ident, make_ident_arg, make_str, make_string_arg,
prop_name_matches, read_static_string, split_namespace,
},
config::ExtraCallerConfig,
dictionary_imports::{ImportKind, InjectedImports},
pre_pass::CallerMap,
};
use std::collections::BTreeMap;
use swc_core::ecma::ast::*;
#[derive(Debug)]
pub enum ExtraNamespaceMatch {
Argument {
index: usize,
full_namespace: String,
},
Option {
argument_index: usize,
full_namespace: String,
},
Fixed { full_namespace: String },
}
impl ExtraNamespaceMatch {
pub fn full_namespace(&self) -> &str {
match self {
ExtraNamespaceMatch::Argument { full_namespace, .. }
| ExtraNamespaceMatch::Option { full_namespace, .. }
| ExtraNamespaceMatch::Fixed { full_namespace } => full_namespace,
}
}
}
pub fn resolve_extra_namespace(
extra_caller: &ExtraCallerConfig,
args: &[ExprOrSpread],
) -> Option<ExtraNamespaceMatch> {
if let Some(index) = extra_caller.namespace_arg_index {
if let Some(arg) = args.get(index) {
if let Some(full_namespace) = read_static_string(&arg.expr) {
return Some(ExtraNamespaceMatch::Argument {
index,
full_namespace,
});
}
}
}
if let Some(fixed_namespace) = &extra_caller.fixed_namespace {
return Some(ExtraNamespaceMatch::Fixed {
full_namespace: fixed_namespace.clone(),
});
}
if let Some(option) = &extra_caller.namespace_option {
if let Some(arg) = args.get(option.argument_index) {
if let Expr::Object(object_lit) = &*arg.expr {
for object_prop in &object_lit.props {
if let PropOrSpread::Prop(prop) = object_prop {
if let Prop::KeyValue(KeyValueProp { key, value }) = &**prop {
if prop_name_matches(key, &option.property) {
if let Some(full_namespace) = read_static_string(value) {
return Some(ExtraNamespaceMatch::Option {
argument_index: option.argument_index,
full_namespace,
});
}
return None; }
}
}
}
}
}
}
None
}
pub fn rewrite_namespace_option(
args: &mut [ExprOrSpread],
argument_index: usize,
property: &str,
key_prefix: &str,
) {
let Some(arg) = args.get_mut(argument_index) else {
return;
};
let Expr::Object(object_lit) = &mut *arg.expr else {
return;
};
if key_prefix.is_empty() {
object_lit.props.retain(|object_prop| {
if let PropOrSpread::Prop(prop) = object_prop {
if let Prop::KeyValue(KeyValueProp { key, .. }) = &**prop {
return !prop_name_matches(key, property);
}
}
true
});
return;
}
for object_prop in &mut object_lit.props {
if let PropOrSpread::Prop(prop) = object_prop {
if let Prop::KeyValue(KeyValueProp { key, value }) = &mut **prop {
if prop_name_matches(key, property) {
*value = Box::new(Expr::Lit(Lit::Str(make_str(key_prefix))));
}
}
}
}
}
pub struct ExtraCallerContext<'a> {
pub extra_callers: &'a [ExtraCallerConfig],
pub dictionary_mode_map: &'a BTreeMap<String, String>,
pub import_mode: ImportKind,
pub use_dynamic_helpers: bool,
}
impl<'a> ExtraCallerContext<'a> {
pub fn import_kind(&self, dictionary_key: &str) -> ImportKind {
if !self.use_dynamic_helpers {
return ImportKind::Static;
}
ImportKind::from_option(
self.dictionary_mode_map
.get(dictionary_key)
.map(String::as_str),
)
.filter(|kind| kind.is_dynamic_helper())
.unwrap_or(match self.import_mode {
ImportKind::Fetch => ImportKind::Fetch,
_ => ImportKind::Dynamic,
})
}
pub fn rewrite_call(
&self,
call: &mut CallExpr,
extra_index: usize,
imports: &mut InjectedImports,
) {
let extra_caller = &self.extra_callers[extra_index];
let Some(namespace_match) = resolve_extra_namespace(extra_caller, &call.args) else {
return; };
let (dictionary_key, key_prefix) = {
let (dictionary_key, key_prefix) = split_namespace(namespace_match.full_namespace());
(dictionary_key.to_string(), key_prefix.to_string())
};
let namespace_option_property: Option<String> = extra_caller
.namespace_option
.as_ref()
.map(|option| option.property.clone());
let import_kind = self.import_kind(&dictionary_key);
let ident = imports.ident_for(&dictionary_key, import_kind);
let is_dynamic_helper = import_kind.is_dynamic_helper();
match &namespace_match {
ExtraNamespaceMatch::Argument { index, .. } => {
call.args[*index].expr = Box::new(Expr::Ident(ident));
let mut insert_at = index + 1;
if is_dynamic_helper {
call.args
.insert(insert_at, make_string_arg(&dictionary_key));
insert_at += 1;
}
if !key_prefix.is_empty() {
call.args.insert(insert_at, make_string_arg(&key_prefix));
}
}
ExtraNamespaceMatch::Fixed { .. } | ExtraNamespaceMatch::Option { .. } => {
if is_dynamic_helper {
call.args.insert(0, make_string_arg(&dictionary_key));
}
call.args.insert(0, make_ident_arg(ident));
}
}
if let ExtraNamespaceMatch::Option { argument_index, .. } = &namespace_match {
let shifted_index = argument_index + if is_dynamic_helper { 2 } else { 1 };
rewrite_namespace_option(
&mut call.args,
shifted_index,
namespace_option_property.as_deref().unwrap_or_default(),
&key_prefix,
);
}
}
pub fn owns_import_source(&self, package_specifier: &str) -> bool {
self.extra_callers.iter().any(|extra_caller| {
extra_caller
.import_sources
.iter()
.any(|source| source == package_specifier)
})
}
pub fn rewrite_import_specifier(
&self,
named: &mut ImportNamedSpecifier,
package_specifier: &str,
caller_map: &CallerMap,
) {
let imported_name = imported_specifier_name(named);
let local_name = named.local.sym.to_string();
let is_registered_extra = caller_map
.get(&local_name)
.is_some_and(|meta| meta.extra_index.is_some());
if !is_registered_extra {
return;
}
let Some(extra_caller) = self.extra_callers.iter().find(|extra_caller| {
extra_caller
.import_sources
.iter()
.any(|source| source == package_specifier)
&& extra_caller.caller_name == imported_name
}) else {
return;
};
let replacement_name = if self.use_dynamic_helpers {
&extra_caller.dynamic_replacement
} else {
&extra_caller.static_replacement
};
named.imported = Some(ModuleExportName::Ident(make_ident(replacement_name)));
}
}