intlayer_swc_plugin/
pre_pass.rs1use crate::{
14 ast::{callee_ident_name, imported_specifier_name, read_static_string, split_namespace},
15 config::ExtraCallerConfig,
16 dictionary_imports::ImportKind,
17 extra_caller::resolve_extra_namespace,
18 packages::{NATIVE_CALLER_NAMES, PACKAGE_LIST},
19};
20use std::collections::{BTreeMap, HashSet};
21use swc_core::ecma::{
22 ast::*,
23 visit::{Visit, VisitWith},
24};
25
26#[derive(Clone, Debug)]
30pub struct CallerMeta {
31 pub original_name: String,
33 pub extra_index: Option<usize>,
35 pub package: Option<String>,
40}
41
42pub type CallerMap = BTreeMap<String, CallerMeta>;
44
45pub struct PrePassResult {
47 pub caller_map: CallerMap,
50 pub packages_with_dynamic_call: HashSet<String>,
53 pub packages_with_fetch_call: HashSet<String>,
56 pub extra_has_dynamic_call: bool,
58}
59
60struct PrePassVisitor<'a> {
61 dictionary_mode_map: &'a BTreeMap<String, String>,
62 extra_callers: &'a [ExtraCallerConfig],
63 packages_with_dynamic_call: HashSet<String>,
64 packages_with_fetch_call: HashSet<String>,
65 extra_has_dynamic_call: bool,
66 unresolvable_extra_locals: HashSet<String>,
70 caller_map: CallerMap,
71}
72
73impl PrePassVisitor<'_> {
74 fn dictionary_override(&self, dictionary_key: &str) -> Option<ImportKind> {
76 ImportKind::from_option(
77 self.dictionary_mode_map
78 .get(dictionary_key)
79 .map(String::as_str),
80 )
81 }
82
83 fn is_dynamic_dictionary(&self, dictionary_key: &str) -> bool {
85 self.dictionary_override(dictionary_key)
86 .is_some_and(|kind| kind.is_dynamic_helper())
87 }
88
89 fn visit_extra_caller_call(&mut self, callee_name: &str, call: &CallExpr, extra_index: usize) {
92 let extra_caller = &self.extra_callers[extra_index];
93
94 match resolve_extra_namespace(extra_caller, &call.args) {
95 Some(namespace_match) => {
96 let (dictionary_key, _prefix) = split_namespace(namespace_match.full_namespace());
97 if self.is_dynamic_dictionary(dictionary_key) {
98 self.extra_has_dynamic_call = true;
99 }
100 }
101 None => {
102 self.unresolvable_extra_locals
103 .insert(callee_name.to_string());
104 }
105 }
106 }
107
108 fn visit_native_call(&mut self, call: &CallExpr, package: &str) {
115 let Some(dictionary_key) = call
116 .args
117 .first()
118 .and_then(|arg| read_static_string(&arg.expr))
119 else {
120 return;
121 };
122
123 match self.dictionary_override(&dictionary_key) {
124 Some(ImportKind::Dynamic) => {
125 self.packages_with_dynamic_call.insert(package.to_string());
126 }
127 Some(ImportKind::Fetch) => {
128 self.packages_with_fetch_call.insert(package.to_string());
129 }
130 _ => {}
131 }
132 }
133}
134
135fn collect_caller_map(program: &Program, extra_callers: &[ExtraCallerConfig]) -> CallerMap {
142 let mut caller_map = CallerMap::new();
143
144 let Program::Module(module) = program else {
145 return caller_map;
146 };
147
148 for item in &module.body {
149 let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = item else {
150 continue;
151 };
152
153 let package_specifier = import.src.value.as_str().unwrap_or_default();
154
155 let is_native_package = PACKAGE_LIST.contains(&package_specifier);
156
157 let extra_callers_for_package: Vec<(usize, &str)> = extra_callers
161 .iter()
162 .enumerate()
163 .filter(|(_, extra_caller)| {
164 extra_caller
165 .import_sources
166 .iter()
167 .any(|source| source == package_specifier)
168 })
169 .map(|(extra_index, extra_caller)| (extra_index, extra_caller.caller_name.as_str()))
170 .collect();
171
172 if !is_native_package && extra_callers_for_package.is_empty() {
173 continue;
174 }
175
176 for specifier in &import.specifiers {
177 let ImportSpecifier::Named(named) = specifier else {
178 continue;
179 };
180 let imported_name = imported_specifier_name(named);
181
182 let meta = extra_callers_for_package
185 .iter()
186 .find(|(_, caller_name)| *caller_name == imported_name)
187 .map(|(extra_index, _)| CallerMeta {
188 original_name: imported_name.clone(),
189 extra_index: Some(*extra_index),
190 package: None,
191 })
192 .or_else(|| {
193 let is_native_caller =
194 is_native_package && NATIVE_CALLER_NAMES.contains(&imported_name.as_str());
195
196 is_native_caller.then(|| CallerMeta {
197 original_name: imported_name.clone(),
198 extra_index: None,
199 package: Some(package_specifier.to_string()),
200 })
201 });
202
203 if let Some(meta) = meta {
204 caller_map.insert(named.local.sym.to_string(), meta);
205 }
206 }
207 }
208
209 caller_map
210}
211
212impl Visit for PrePassVisitor<'_> {
213 fn visit_call_expr(&mut self, call: &CallExpr) {
214 call.visit_children_with(self);
215
216 let Some(callee_name) = callee_ident_name(&call.callee) else {
217 return;
218 };
219
220 let Some(meta) = self.caller_map.get(callee_name).cloned() else {
221 return;
222 };
223
224 match meta.extra_index {
225 Some(extra_index) => self.visit_extra_caller_call(callee_name, call, extra_index),
226 None if meta.original_name == "useIntlayer" => {
227 if let Some(package) = meta.package.as_deref() {
228 self.visit_native_call(call, package);
229 }
230 }
231 None => {}
232 }
233 }
234}
235
236pub fn run_pre_pass(
238 program: &Program,
239 dictionary_mode_map: &BTreeMap<String, String>,
240 extra_callers: &[ExtraCallerConfig],
241) -> PrePassResult {
242 let mut visitor = PrePassVisitor {
243 dictionary_mode_map,
244 extra_callers,
245 packages_with_dynamic_call: HashSet::new(),
246 packages_with_fetch_call: HashSet::new(),
247 extra_has_dynamic_call: false,
248 unresolvable_extra_locals: HashSet::new(),
249 caller_map: collect_caller_map(program, extra_callers),
250 };
251 program.visit_with(&mut visitor);
252
253 let unresolvable_extra_locals = visitor.unresolvable_extra_locals;
254
255 let mut caller_map = visitor.caller_map;
259 caller_map.retain(|local_name, meta| {
260 meta.extra_index.is_none() || !unresolvable_extra_locals.contains(local_name)
261 });
262
263 PrePassResult {
264 caller_map,
265 packages_with_dynamic_call: visitor.packages_with_dynamic_call,
266 packages_with_fetch_call: visitor.packages_with_fetch_call,
267 extra_has_dynamic_call: visitor.extra_has_dynamic_call,
268 }
269}