use crate::{
ast::{callee_ident_name, imported_specifier_name, read_static_string, split_namespace},
config::ExtraCallerConfig,
dictionary_imports::ImportKind,
extra_caller::resolve_extra_namespace,
packages::{NATIVE_CALLER_NAMES, PACKAGE_LIST},
};
use std::collections::{BTreeMap, HashSet};
use swc_core::ecma::{
ast::*,
visit::{Visit, VisitWith},
};
#[derive(Clone, Debug)]
pub struct CallerMeta {
pub original_name: String,
pub extra_index: Option<usize>,
pub package: Option<String>,
}
pub type CallerMap = BTreeMap<String, CallerMeta>;
pub struct PrePassResult {
pub caller_map: CallerMap,
pub packages_with_dynamic_call: HashSet<String>,
pub packages_with_fetch_call: HashSet<String>,
pub extra_has_dynamic_call: bool,
}
struct PrePassVisitor<'a> {
dictionary_mode_map: &'a BTreeMap<String, String>,
extra_callers: &'a [ExtraCallerConfig],
packages_with_dynamic_call: HashSet<String>,
packages_with_fetch_call: HashSet<String>,
extra_has_dynamic_call: bool,
unresolvable_extra_locals: HashSet<String>,
caller_map: CallerMap,
}
impl PrePassVisitor<'_> {
fn dictionary_override(&self, dictionary_key: &str) -> Option<ImportKind> {
ImportKind::from_option(
self.dictionary_mode_map
.get(dictionary_key)
.map(String::as_str),
)
}
fn is_dynamic_dictionary(&self, dictionary_key: &str) -> bool {
self.dictionary_override(dictionary_key)
.is_some_and(|kind| kind.is_dynamic_helper())
}
fn visit_extra_caller_call(&mut self, callee_name: &str, call: &CallExpr, extra_index: usize) {
let extra_caller = &self.extra_callers[extra_index];
match resolve_extra_namespace(extra_caller, &call.args) {
Some(namespace_match) => {
let (dictionary_key, _prefix) = split_namespace(namespace_match.full_namespace());
if self.is_dynamic_dictionary(dictionary_key) {
self.extra_has_dynamic_call = true;
}
}
None => {
self.unresolvable_extra_locals
.insert(callee_name.to_string());
}
}
}
fn visit_native_call(&mut self, call: &CallExpr, package: &str) {
let Some(dictionary_key) = call
.args
.first()
.and_then(|arg| read_static_string(&arg.expr))
else {
return;
};
match self.dictionary_override(&dictionary_key) {
Some(ImportKind::Dynamic) => {
self.packages_with_dynamic_call.insert(package.to_string());
}
Some(ImportKind::Fetch) => {
self.packages_with_fetch_call.insert(package.to_string());
}
_ => {}
}
}
}
fn collect_caller_map(program: &Program, extra_callers: &[ExtraCallerConfig]) -> CallerMap {
let mut caller_map = CallerMap::new();
let Program::Module(module) = program else {
return caller_map;
};
for item in &module.body {
let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = item else {
continue;
};
let package_specifier = import.src.value.as_str().unwrap_or_default();
let is_native_package = PACKAGE_LIST.contains(&package_specifier);
let extra_callers_for_package: Vec<(usize, &str)> = extra_callers
.iter()
.enumerate()
.filter(|(_, extra_caller)| {
extra_caller
.import_sources
.iter()
.any(|source| source == package_specifier)
})
.map(|(extra_index, extra_caller)| {
(extra_index, extra_caller.caller_name.as_str())
})
.collect();
if !is_native_package && extra_callers_for_package.is_empty() {
continue;
}
for specifier in &import.specifiers {
let ImportSpecifier::Named(named) = specifier else {
continue;
};
let imported_name = imported_specifier_name(named);
let meta = extra_callers_for_package
.iter()
.find(|(_, caller_name)| *caller_name == imported_name)
.map(|(extra_index, _)| CallerMeta {
original_name: imported_name.clone(),
extra_index: Some(*extra_index),
package: None,
})
.or_else(|| {
let is_native_caller = is_native_package
&& NATIVE_CALLER_NAMES.contains(&imported_name.as_str());
is_native_caller.then(|| CallerMeta {
original_name: imported_name.clone(),
extra_index: None,
package: Some(package_specifier.to_string()),
})
});
if let Some(meta) = meta {
caller_map.insert(named.local.sym.to_string(), meta);
}
}
}
caller_map
}
impl Visit for PrePassVisitor<'_> {
fn visit_call_expr(&mut self, call: &CallExpr) {
call.visit_children_with(self);
let Some(callee_name) = callee_ident_name(&call.callee) else {
return;
};
let Some(meta) = self.caller_map.get(callee_name).cloned() else {
return;
};
match meta.extra_index {
Some(extra_index) => self.visit_extra_caller_call(callee_name, call, extra_index),
None if meta.original_name == "useIntlayer" => {
if let Some(package) = meta.package.as_deref() {
self.visit_native_call(call, package);
}
}
None => {}
}
}
}
pub fn run_pre_pass(
program: &Program,
dictionary_mode_map: &BTreeMap<String, String>,
extra_callers: &[ExtraCallerConfig],
) -> PrePassResult {
let mut visitor = PrePassVisitor {
dictionary_mode_map,
extra_callers,
packages_with_dynamic_call: HashSet::new(),
packages_with_fetch_call: HashSet::new(),
extra_has_dynamic_call: false,
unresolvable_extra_locals: HashSet::new(),
caller_map: collect_caller_map(program, extra_callers),
};
program.visit_with(&mut visitor);
let unresolvable_extra_locals = visitor.unresolvable_extra_locals;
let mut caller_map = visitor.caller_map;
caller_map.retain(|local_name, meta| {
meta.extra_index.is_none() || !unresolvable_extra_locals.contains(local_name)
});
PrePassResult {
caller_map,
packages_with_dynamic_call: visitor.packages_with_dynamic_call,
packages_with_fetch_call: visitor.packages_with_fetch_call,
extra_has_dynamic_call: visitor.extra_has_dynamic_call,
}
}