Add const_modules pass (#268)

swc_ecma_transforms:
 - use fxhash for inline_globals pass
 - handle member expression in inlne_globals pass
 - add const_modules pass
This commit is contained in:
강동윤 2019-02-22 15:42:27 +09:00 committed by GitHub
parent c1de0a5c86
commit a785ecc960
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 211 additions and 11 deletions

View File

@ -0,0 +1,186 @@
use crate::{pass::Pass, util::State};
use ast::*;
use fxhash::FxHashMap;
use std::sync::Arc;
use swc_atoms::JsWord;
use swc_common::{util::move_map::MoveMap, Fold, FoldWith};
pub fn const_modules(
globals: FxHashMap<JsWord, FxHashMap<JsWord, Arc<Expr>>>,
) -> impl Pass + Clone {
ConstModules {
globals,
scope: Default::default(),
}
}
#[derive(Clone)]
struct ConstModules {
globals: FxHashMap<JsWord, FxHashMap<JsWord, Arc<Expr>>>,
scope: State<Scope>,
}
#[derive(Default)]
struct Scope {
imported: FxHashMap<JsWord, Arc<Expr>>,
}
impl Fold<Vec<ModuleItem>> for ConstModules {
fn fold(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
items.move_flat_map(|item| match item {
ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
let entry = self.globals.get(&import.src.value);
if let Some(entry) = entry {
for s in &import.specifiers {
let i = match *s {
ImportSpecifier::Specific(ref s) => &s.local,
ImportSpecifier::Namespace(..) => unimplemented!(
"const modules does not support namespace import yet"
),
ImportSpecifier::Default(..) => {
panic!("const_modules does not support default import")
}
};
let value = entry.get(&i.sym).cloned().unwrap_or_else(|| {
panic!(
"const_modules: {} does not contain flags named {}",
import.src.value, i.sym
)
});
self.scope.value.imported.insert(i.sym.clone(), value);
}
None
} else {
Some(ModuleItem::ModuleDecl(ModuleDecl::Import(import)))
}
}
_ => Some(item.fold_with(self)),
})
}
}
impl Fold<Expr> for ConstModules {
fn fold(&mut self, expr: Expr) -> Expr {
let expr = match expr {
Expr::Member(expr) => {
if expr.computed {
Expr::Member(MemberExpr {
obj: expr.obj.fold_with(self),
prop: expr.prop.fold_with(self),
..expr
})
} else {
Expr::Member(MemberExpr {
obj: expr.obj.fold_with(self),
..expr
})
}
}
_ => expr.fold_children(self),
};
match expr {
Expr::Ident(Ident { ref sym, .. }) => {
// It's ok because we don't recurse into member expressions.
if let Some(value) = self.scope.imported.get(sym) {
return (**value).clone();
} else {
expr
}
}
_ => expr,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::Tester;
fn tr(tester: &mut Tester, sources: &[(&str, &[(&str, &str)])]) -> impl Fold<Module> {
let mut m = FxHashMap::default();
for (src, values) in sources {
let values = values
.iter()
.map(|(k, v)| {
let mut v = tester
.apply_transform(
::testing::DropSpan,
"global.js",
::swc_ecma_parser::Syntax::default(),
v,
)
.unwrap();
let v = match v.body.pop().unwrap() {
ModuleItem::Stmt(Stmt::Expr(box expr)) => expr,
_ => unreachable!(),
};
((*k).into(), Arc::new(v))
})
.collect();
m.insert((*src).into(), values);
}
const_modules(m)
}
test!(
::swc_ecma_parser::Syntax::default(),
|tester| tr(tester, &[("@ember/env-flags", &[("DEBUG", "true")])]),
simple_flags,
r#"import { DEBUG } from '@ember/env-flags';
if (DEBUG) {
console.log('Foo!');
}"#,
r#"
if (true) {
console.log('Foo!');
}"#
);
test!(
::swc_ecma_parser::Syntax::default(),
|tester| tr(
tester,
&[
("@ember/env-flags", &[("DEBUG", "true")]),
(
"@ember/features",
&[("FEATURE_A", "false"), ("FEATURE_B", "true")]
)
]
),
complex_multiple,
"
import { DEBUG } from '@ember/env-flags';
import { FEATURE_A, FEATURE_B } from '@ember/features';
if (DEBUG) {
console.log('Foo!');
}
let woot;
if (FEATURE_A) {
woot = () => 'woot';
} else if (FEATURE_B) {
woot = () => 'toow';
}
",
"
if (true) {
console.log('Foo!');
}
let woot;
if (false) {
woot = () => 'woot';
} else if (true) {
woot = () => 'toow';
}
"
);
}

View File

@ -1,19 +1,31 @@
use ast::*;
use std::collections::HashMap;
use fxhash::FxHashMap;
use swc_atoms::JsWord;
use swc_common::{Fold, FoldWith};
#[derive(Clone)]
pub struct InlineGlobals {
pub envs: HashMap<JsWord, Expr>,
pub globals: HashMap<JsWord, Expr>,
pub envs: FxHashMap<JsWord, Expr>,
pub globals: FxHashMap<JsWord, Expr>,
}
impl Fold<Expr> for InlineGlobals {
fn fold(&mut self, expr: Expr) -> Expr {
let expr = match expr {
// Don't recurese into member expression.
Expr::Member(..) => expr,
Expr::Member(expr) => {
if expr.computed {
Expr::Member(MemberExpr {
obj: expr.obj.fold_with(self),
prop: expr.prop.fold_with(self),
..expr
})
} else {
Expr::Member(MemberExpr {
obj: expr.obj.fold_with(self),
..expr
})
}
}
_ => expr.fold_children(self),
};
@ -21,7 +33,7 @@ impl Fold<Expr> for InlineGlobals {
Expr::Ident(Ident { ref sym, .. }) => {
// It's ok because we don't recurse into member expressions.
if let Some(value) = self.globals.get(sym) {
return value.clone();
return value.clone().fold_with(self);
} else {
expr
}
@ -82,8 +94,8 @@ mod tests {
tester: &mut crate::tests::Tester,
values: &[(&str, &str)],
is_env: bool,
) -> HashMap<JsWord, Expr> {
let mut m = HashMap::new();
) -> FxHashMap<JsWord, Expr> {
let mut m = FxHashMap::default();
for (k, v) in values {
let v = if is_env {
@ -112,14 +124,14 @@ mod tests {
m
}
fn envs(tester: &mut crate::tests::Tester, values: &[(&str, &str)]) -> HashMap<JsWord, Expr> {
fn envs(tester: &mut crate::tests::Tester, values: &[(&str, &str)]) -> FxHashMap<JsWord, Expr> {
mk_map(tester, values, true)
}
fn globals(
tester: &mut crate::tests::Tester,
values: &[(&str, &str)],
) -> HashMap<JsWord, Expr> {
) -> FxHashMap<JsWord, Expr> {
mk_map(tester, values, false)
}

View File

@ -42,7 +42,8 @@ extern crate serde;
extern crate unicode_xid;
pub use self::{
fixer::fixer, hygiene::hygiene, inline_globals::InlineGlobals, simplify::simplifier,
const_modules::const_modules, fixer::fixer, hygiene::hygiene, inline_globals::InlineGlobals,
simplify::simplifier,
};
#[cfg(test)]
@ -57,6 +58,7 @@ mod macros;
#[macro_use]
mod hygiene;
pub mod compat;
mod const_modules;
mod fixer;
mod inline_globals;
pub mod modules;