intlayer_swc_plugin/
extra_caller.rs1use crate::{
5 ast::{
6 imported_specifier_name, make_ident, make_ident_arg, make_str, make_string_arg,
7 prop_name_matches, read_static_string, split_namespace,
8 },
9 config::ExtraCallerConfig,
10 dictionary_imports::{ImportKind, InjectedImports},
11 pre_pass::CallerMap,
12};
13use std::collections::BTreeMap;
14use swc_core::ecma::ast::*;
15
16#[derive(Debug)]
18pub enum ExtraNamespaceMatch {
19 Argument {
21 index: usize,
22 full_namespace: String,
23 },
24 Option {
26 argument_index: usize,
27 full_namespace: String,
28 },
29 Fixed { full_namespace: String },
31}
32
33impl ExtraNamespaceMatch {
34 pub fn full_namespace(&self) -> &str {
36 match self {
37 ExtraNamespaceMatch::Argument { full_namespace, .. }
38 | ExtraNamespaceMatch::Option { full_namespace, .. }
39 | ExtraNamespaceMatch::Fixed { full_namespace } => full_namespace,
40 }
41 }
42}
43
44pub fn resolve_extra_namespace(
49 extra_caller: &ExtraCallerConfig,
50 args: &[ExprOrSpread],
51) -> Option<ExtraNamespaceMatch> {
52 if let Some(index) = extra_caller.namespace_arg_index {
53 if let Some(arg) = args.get(index) {
54 if let Some(full_namespace) = read_static_string(&arg.expr) {
55 return Some(ExtraNamespaceMatch::Argument {
56 index,
57 full_namespace,
58 });
59 }
60 }
61 }
62
63 if let Some(fixed_namespace) = &extra_caller.fixed_namespace {
64 return Some(ExtraNamespaceMatch::Fixed {
65 full_namespace: fixed_namespace.clone(),
66 });
67 }
68
69 if let Some(option) = &extra_caller.namespace_option {
70 if let Some(arg) = args.get(option.argument_index) {
71 if let Expr::Object(object_lit) = &*arg.expr {
72 for object_prop in &object_lit.props {
73 if let PropOrSpread::Prop(prop) = object_prop {
74 if let Prop::KeyValue(KeyValueProp { key, value }) = &**prop {
75 if prop_name_matches(key, &option.property) {
76 if let Some(full_namespace) = read_static_string(value) {
77 return Some(ExtraNamespaceMatch::Option {
78 argument_index: option.argument_index,
79 full_namespace,
80 });
81 }
82 return None; }
84 }
85 }
86 }
87 }
88 }
89 }
90
91 None
92}
93
94pub fn rewrite_namespace_option(
99 args: &mut [ExprOrSpread],
100 argument_index: usize,
101 property: &str,
102 key_prefix: &str,
103) {
104 let Some(arg) = args.get_mut(argument_index) else {
105 return;
106 };
107 let Expr::Object(object_lit) = &mut *arg.expr else {
108 return;
109 };
110
111 if key_prefix.is_empty() {
112 object_lit.props.retain(|object_prop| {
113 if let PropOrSpread::Prop(prop) = object_prop {
114 if let Prop::KeyValue(KeyValueProp { key, .. }) = &**prop {
115 return !prop_name_matches(key, property);
116 }
117 }
118 true
119 });
120 return;
121 }
122
123 for object_prop in &mut object_lit.props {
124 if let PropOrSpread::Prop(prop) = object_prop {
125 if let Prop::KeyValue(KeyValueProp { key, value }) = &mut **prop {
126 if prop_name_matches(key, property) {
127 *value = Box::new(Expr::Lit(Lit::Str(make_str(key_prefix))));
128 }
129 }
130 }
131 }
132}
133
134pub struct ExtraCallerContext<'a> {
141 pub extra_callers: &'a [ExtraCallerConfig],
143 pub dictionary_mode_map: &'a BTreeMap<String, String>,
145 pub import_mode: ImportKind,
147 pub use_dynamic_helpers: bool,
151}
152
153impl<'a> ExtraCallerContext<'a> {
154 pub fn import_kind(&self, dictionary_key: &str) -> ImportKind {
156 if !self.use_dynamic_helpers {
157 return ImportKind::Static;
158 }
159
160 ImportKind::from_option(
161 self.dictionary_mode_map
162 .get(dictionary_key)
163 .map(String::as_str),
164 )
165 .filter(|kind| kind.is_dynamic_helper())
166 .unwrap_or(match self.import_mode {
167 ImportKind::Fetch => ImportKind::Fetch,
168 _ => ImportKind::Dynamic,
169 })
170 }
171
172 pub fn rewrite_call(
176 &self,
177 call: &mut CallExpr,
178 extra_index: usize,
179 imports: &mut InjectedImports,
180 ) {
181 let extra_caller = &self.extra_callers[extra_index];
182
183 let Some(namespace_match) = resolve_extra_namespace(extra_caller, &call.args) else {
184 return; };
186
187 let (dictionary_key, key_prefix) = {
188 let (dictionary_key, key_prefix) = split_namespace(namespace_match.full_namespace());
189 (dictionary_key.to_string(), key_prefix.to_string())
190 };
191
192 let namespace_option_property: Option<String> = extra_caller
193 .namespace_option
194 .as_ref()
195 .map(|option| option.property.clone());
196
197 let import_kind = self.import_kind(&dictionary_key);
198 let ident = imports.ident_for(&dictionary_key, import_kind);
199 let is_dynamic_helper = import_kind.is_dynamic_helper();
200
201 match &namespace_match {
202 ExtraNamespaceMatch::Argument { index, .. } => {
203 call.args[*index].expr = Box::new(Expr::Ident(ident));
206 let mut insert_at = index + 1;
207 if is_dynamic_helper {
208 call.args
209 .insert(insert_at, make_string_arg(&dictionary_key));
210 insert_at += 1;
211 }
212 if !key_prefix.is_empty() {
213 call.args.insert(insert_at, make_string_arg(&key_prefix));
214 }
215 }
216 ExtraNamespaceMatch::Fixed { .. } | ExtraNamespaceMatch::Option { .. } => {
217 if is_dynamic_helper {
220 call.args.insert(0, make_string_arg(&dictionary_key));
221 }
222 call.args.insert(0, make_ident_arg(ident));
223 }
224 }
225
226 if let ExtraNamespaceMatch::Option { argument_index, .. } = &namespace_match {
227 let shifted_index = argument_index + if is_dynamic_helper { 2 } else { 1 };
229 rewrite_namespace_option(
230 &mut call.args,
231 shifted_index,
232 namespace_option_property.as_deref().unwrap_or_default(),
233 &key_prefix,
234 );
235 }
236 }
237
238 pub fn owns_import_source(&self, package_specifier: &str) -> bool {
242 self.extra_callers.iter().any(|extra_caller| {
243 extra_caller
244 .import_sources
245 .iter()
246 .any(|source| source == package_specifier)
247 })
248 }
249
250 pub fn rewrite_import_specifier(
256 &self,
257 named: &mut ImportNamedSpecifier,
258 package_specifier: &str,
259 caller_map: &CallerMap,
260 ) {
261 let imported_name = imported_specifier_name(named);
262 let local_name = named.local.sym.to_string();
263
264 let is_registered_extra = caller_map
265 .get(&local_name)
266 .is_some_and(|meta| meta.extra_index.is_some());
267 if !is_registered_extra {
268 return;
269 }
270
271 let Some(extra_caller) = self.extra_callers.iter().find(|extra_caller| {
272 extra_caller
273 .import_sources
274 .iter()
275 .any(|source| source == package_specifier)
276 && extra_caller.caller_name == imported_name
277 }) else {
278 return;
279 };
280
281 let replacement_name = if self.use_dynamic_helpers {
282 &extra_caller.dynamic_replacement
283 } else {
284 &extra_caller.static_replacement
285 };
286
287 named.imported = Some(ModuleExportName::Ident(make_ident(replacement_name)));
288 }
289}