use swc_atoms::{js_word, JsWord};
use swc_common::{
collections::{AHashMap, AHashSet},
sync::Lrc,
EqIgnoreSpan,
};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Parallel;
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::collect_decls;
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
pub type GlobalExprMap = Lrc<Vec<(Expr, Expr)>>;
pub fn inline_globals(
envs: Lrc<AHashMap<JsWord, Expr>>,
globals: Lrc<AHashMap<JsWord, Expr>>,
typeofs: Lrc<AHashMap<JsWord, JsWord>>,
) -> impl Fold + VisitMut {
inline_globals2(envs, globals, Default::default(), typeofs)
}
pub fn inline_globals2(
envs: Lrc<AHashMap<JsWord, Expr>>,
globals: Lrc<AHashMap<JsWord, Expr>>,
global_exprs: GlobalExprMap,
typeofs: Lrc<AHashMap<JsWord, JsWord>>,
) -> impl Fold + VisitMut {
as_folder(InlineGlobals {
envs,
globals,
global_exprs,
typeofs,
bindings: Default::default(),
})
}
#[derive(Clone)]
struct InlineGlobals {
envs: Lrc<AHashMap<JsWord, Expr>>,
globals: Lrc<AHashMap<JsWord, Expr>>,
global_exprs: GlobalExprMap,
typeofs: Lrc<AHashMap<JsWord, JsWord>>,
bindings: Lrc<AHashSet<Id>>,
}
impl Parallel for InlineGlobals {
fn create(&self) -> Self {
self.clone()
}
fn merge(&mut self, _: Self) {}
}
#[parallel]
impl VisitMut for InlineGlobals {
noop_visit_mut_type!();
fn visit_mut_assign_expr(&mut self, n: &mut AssignExpr) {
n.right.visit_mut_with(self);
match &mut n.left {
PatOrExpr::Expr(l) => {
(**l).visit_mut_children_with(self);
}
PatOrExpr::Pat(l) => match &mut **l {
Pat::Expr(l) => {
(**l).visit_mut_children_with(self);
}
_ => {
l.visit_mut_with(self);
}
},
}
}
fn visit_mut_expr(&mut self, expr: &mut Expr) {
if let Expr::Ident(Ident { ref sym, span, .. }) = expr {
if self.bindings.contains(&(sym.clone(), span.ctxt)) {
return;
}
}
for (key, value) in self.global_exprs.iter() {
if Ident::within_ignored_ctxt(|| key.eq_ignore_span(&*expr)) {
*expr = value.clone();
expr.visit_mut_with(self);
return;
}
}
expr.visit_mut_children_with(self);
match expr {
Expr::Ident(Ident { ref sym, .. }) => {
if let Some(value) = self.globals.get(sym) {
let mut value = value.clone();
value.visit_mut_with(self);
*expr = value;
}
}
Expr::Unary(UnaryExpr {
span,
op: op!("typeof"),
arg,
..
}) => {
if let Expr::Ident(Ident {
ref sym,
span: arg_span,
..
}) = &**arg
{
if self.bindings.contains(&(sym.clone(), arg_span.ctxt)) {
return;
}
if let Some(value) = self.typeofs.get(sym).cloned() {
*expr = Expr::Lit(Lit::Str(Str {
span: *span,
raw: None,
value,
}));
}
}
}
Expr::Member(MemberExpr { obj, prop, .. }) => {
if let Expr::Member(MemberExpr {
obj: first_obj,
prop:
MemberProp::Ident(Ident {
sym: js_word!("env"),
..
}),
..
}) = &**obj
{
if let Expr::Ident(Ident {
sym: js_word!("process"),
..
}) = &**first_obj
{
match prop {
MemberProp::Computed(ComputedPropName { expr: c, .. }) => {
if let Expr::Lit(Lit::Str(Str { value: sym, .. })) = &**c {
if let Some(env) = self.envs.get(sym) {
*expr = env.clone();
}
}
}
MemberProp::Ident(Ident { sym, .. }) => {
if let Some(env) = self.envs.get(sym) {
*expr = env.clone();
}
}
_ => {}
}
}
}
}
_ => {}
}
}
fn visit_mut_module(&mut self, module: &mut Module) {
self.bindings = Lrc::new(collect_decls(&*module));
module.visit_mut_children_with(self);
}
fn visit_mut_prop(&mut self, p: &mut Prop) {
p.visit_mut_children_with(self);
if let Prop::Shorthand(i) = p {
if self.bindings.contains(&i.to_id()) {
return;
}
if let Some(mut value) = self.globals.get(&i.sym).cloned().map(Box::new) {
value.visit_mut_with(self);
*p = Prop::KeyValue(KeyValueProp {
key: PropName::Ident(i.clone()),
value,
});
}
}
}
fn visit_mut_script(&mut self, script: &mut Script) {
self.bindings = Lrc::new(collect_decls(&*script));
script.visit_mut_children_with(self);
}
}
#[cfg(test)]
mod tests {
use swc_ecma_transforms_testing::{test, Tester};
use swc_ecma_utils::DropSpan;
use swc_ecma_visit::as_folder;
use super::*;
fn mk_map(
tester: &mut Tester<'_>,
values: &[(&str, &str)],
is_env: bool,
) -> AHashMap<JsWord, Expr> {
let mut m = AHashMap::default();
for (k, v) in values {
let v = if is_env {
format!("'{}'", v)
} else {
(*v).into()
};
let mut v = tester
.apply_transform(
as_folder(DropSpan {
preserve_ctxt: false,
}),
"global.js",
::swc_ecma_parser::Syntax::default(),
&v,
)
.unwrap();
assert_eq!(v.body.len(), 1);
let v = match v.body.pop().unwrap() {
ModuleItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) => *expr,
_ => unreachable!(),
};
m.insert((*k).into(), v);
}
m
}
fn envs(tester: &mut Tester<'_>, values: &[(&str, &str)]) -> Lrc<AHashMap<JsWord, Expr>> {
Lrc::new(mk_map(tester, values, true))
}
fn globals(tester: &mut Tester<'_>, values: &[(&str, &str)]) -> Lrc<AHashMap<JsWord, Expr>> {
Lrc::new(mk_map(tester, values, false))
}
test!(
::swc_ecma_parser::Syntax::default(),
|tester| inline_globals(envs(tester, &[]), globals(tester, &[]), Default::default(),),
issue_215,
r#"if (process.env.x === 'development') {}"#,
r#"if (process.env.x === 'development') {}"#
);
test!(
::swc_ecma_parser::Syntax::default(),
|tester| inline_globals(
envs(tester, &[("NODE_ENV", "development")]),
globals(tester, &[]),
Default::default(),
),
node_env,
r#"if (process.env.NODE_ENV === 'development') {}"#,
r#"if ('development' === 'development') {}"#
);
test!(
::swc_ecma_parser::Syntax::default(),
|tester| inline_globals(
envs(tester, &[]),
globals(tester, &[("__DEBUG__", "true")]),
Default::default(),
),
globals_simple,
r#"if (__DEBUG__) {}"#,
r#"if (true) {}"#
);
test!(
::swc_ecma_parser::Syntax::default(),
|tester| inline_globals(
envs(tester, &[]),
globals(tester, &[("debug", "true")]),
Default::default(),
),
non_global,
r#"if (foo.debug) {}"#,
r#"if (foo.debug) {}"#
);
test!(
Default::default(),
|tester| inline_globals(envs(tester, &[]), globals(tester, &[]), Default::default(),),
issue_417_1,
"const test = process.env['x']",
"const test = process.env['x']"
);
test!(
Default::default(),
|tester| inline_globals(
envs(tester, &[("x", "FOO")]),
globals(tester, &[]),
Default::default(),
),
issue_417_2,
"const test = process.env['x']",
"const test = 'FOO'"
);
test!(
Default::default(),
|tester| inline_globals(
envs(tester, &[("x", "BAR")]),
globals(tester, &[]),
Default::default(),
),
issue_2499_1,
"process.env.x = 'foo'",
"process.env.x = 'foo'"
);
}