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)| {
170 (extra_index, extra_caller.caller_name.as_str())
171 })
172 .collect();
173
174 if !is_native_package && extra_callers_for_package.is_empty() {
175 continue;
176 }
177
178 for specifier in &import.specifiers {
179 let ImportSpecifier::Named(named) = specifier else {
180 continue;
181 };
182 let imported_name = imported_specifier_name(named);
183
184 let meta = extra_callers_for_package
187 .iter()
188 .find(|(_, caller_name)| *caller_name == imported_name)
189 .map(|(extra_index, _)| CallerMeta {
190 original_name: imported_name.clone(),
191 extra_index: Some(*extra_index),
192 package: None,
193 })
194 .or_else(|| {
195 let is_native_caller = is_native_package
196 && NATIVE_CALLER_NAMES.contains(&imported_name.as_str());
197
198 is_native_caller.then(|| CallerMeta {
199 original_name: imported_name.clone(),
200 extra_index: None,
201 package: Some(package_specifier.to_string()),
202 })
203 });
204
205 if let Some(meta) = meta {
206 caller_map.insert(named.local.sym.to_string(), meta);
207 }
208 }
209 }
210
211 caller_map
212}
213
214impl Visit for PrePassVisitor<'_> {
215 fn visit_call_expr(&mut self, call: &CallExpr) {
216 call.visit_children_with(self);
217
218 let Some(callee_name) = callee_ident_name(&call.callee) else {
219 return;
220 };
221
222 let Some(meta) = self.caller_map.get(callee_name).cloned() else {
223 return;
224 };
225
226 match meta.extra_index {
227 Some(extra_index) => self.visit_extra_caller_call(callee_name, call, extra_index),
228 None if meta.original_name == "useIntlayer" => {
229 if let Some(package) = meta.package.as_deref() {
230 self.visit_native_call(call, package);
231 }
232 }
233 None => {}
234 }
235 }
236}
237
238pub fn run_pre_pass(
240 program: &Program,
241 dictionary_mode_map: &BTreeMap<String, String>,
242 extra_callers: &[ExtraCallerConfig],
243) -> PrePassResult {
244 let mut visitor = PrePassVisitor {
245 dictionary_mode_map,
246 extra_callers,
247 packages_with_dynamic_call: HashSet::new(),
248 packages_with_fetch_call: HashSet::new(),
249 extra_has_dynamic_call: false,
250 unresolvable_extra_locals: HashSet::new(),
251 caller_map: collect_caller_map(program, extra_callers),
252 };
253 program.visit_with(&mut visitor);
254
255 let unresolvable_extra_locals = visitor.unresolvable_extra_locals;
256
257 let mut caller_map = visitor.caller_map;
261 caller_map.retain(|local_name, meta| {
262 meta.extra_index.is_none() || !unresolvable_extra_locals.contains(local_name)
263 });
264
265 PrePassResult {
266 caller_map,
267 packages_with_dynamic_call: visitor.packages_with_dynamic_call,
268 packages_with_fetch_call: visitor.packages_with_fetch_call,
269 extra_has_dynamic_call: visitor.extra_has_dynamic_call,
270 }
271}