diff --git a/src/main.rs b/src/main.rs index 533745b..59260eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,7 +98,7 @@ fn main() -> Result<(), NiceError> { log::info!("Done ir2ast @ {}", Time(start.elapsed())); let ast = if opt_ast { - let ast = opt_ast::run(g, ast); + let ast = opt_ast::run(g, ast, opt_ast::Opt { minify }); log::info!("Done ast optimization @ {}", Time(start.elapsed())); ast } else { diff --git a/src/opt_ast.rs b/src/opt_ast.rs index d0c586d..7e7bb49 100644 --- a/src/opt_ast.rs +++ b/src/opt_ast.rs @@ -4,16 +4,27 @@ use swc_ecma_visit::FoldWith; use crate::swc_globals; mod if2cond; +mod merge_vars; mod swc; #[cfg(test)] mod tests; +pub struct Opt { + pub minify: bool, +} + #[inline(never)] // for better profiling -pub fn run(g: &swc_globals::Initialized, ast: ast::Program) -> ast::Program { +pub fn run(g: &swc_globals::Initialized, ast: ast::Program, options: Opt) -> ast::Program { let ast = swc::run_passes(g, ast); let ast = ast.fold_with(&mut if2cond::If2Cond); + let ast = if options.minify { + ast.fold_with(&mut merge_vars::MergeVars) + } else { + ast + }; + swc::run_passes(g, ast) } diff --git a/src/opt_ast/merge_vars.rs b/src/opt_ast/merge_vars.rs new file mode 100644 index 0000000..31c563a --- /dev/null +++ b/src/opt_ast/merge_vars.rs @@ -0,0 +1,48 @@ +use std::mem; + +use swc_ecma_ast as ast; +use swc_ecma_visit::{Fold, FoldWith}; + +/// Merges adjacent variable declarations. +pub struct MergeVars; + +impl Fold for MergeVars { + fn fold_stmts(&mut self, stmts: Vec) -> Vec { + let stmts = stmts.fold_children_with(self); + + let var_to_stmt = |var| ast::Stmt::Decl(ast::Decl::Var(var)); + + let mut out = Vec::with_capacity(stmts.len()); + let mut buffered_var = None; + for stmt in stmts { + match (stmt, &mut buffered_var) { + (ast::Stmt::Decl(ast::Decl::Var(cur)), buf @ None) => { + // no buffer yet, buffer this decl + *buf = Some(cur); + } + (ast::Stmt::Decl(ast::Decl::Var(cur)), Some(buf)) if cur.kind == buf.kind => { + // same kind, add to buffer + buf.decls.extend(cur.decls); + } + (ast::Stmt::Decl(ast::Decl::Var(cur)), Some(_)) => { + // different kinds, swap into buffer + let buffered_var = mem::replace(&mut buffered_var, Some(cur)); + if let Some(buf) = buffered_var { + out.push(var_to_stmt(buf)); + } + } + (stmt, _) => { + // not a var decl, flush buffer + if let Some(buf) = buffered_var.take() { + out.push(var_to_stmt(buf)); + } + out.push(stmt); + } + } + } + if let Some(buf) = buffered_var { + out.push(var_to_stmt(buf)); + } + out + } +} diff --git a/src/opt_ast/tests/merge_vars.rs b/src/opt_ast/tests/merge_vars.rs new file mode 100644 index 0000000..76f575d --- /dev/null +++ b/src/opt_ast/tests/merge_vars.rs @@ -0,0 +1,77 @@ +use crate::opt_ast::merge_vars; + +case!(basic, || merge_vars::MergeVars, r#" + var x; + var y; + var z; + let a; + let b; + let c; + const d; + const e; + const f; +"#, @r###" +var x, y, z; +let a, b, c; +const d, e, f; +"###); + +case!(basic_values, || merge_vars::MergeVars, r#" + var x; + var y = 1; + var z; + let a; + let b = 2; + let c; + const d; + const e = 3; + const f; +"#, @r###" +var x, y = 1, z; +let a, b = 2, c; +const d, e = 3, f; +"###); + +case!(inner_scopes, || merge_vars::MergeVars, r#" + if (foo) { + var a; + var b = 1; + } +"#, @r###" +if (foo) { + var a, b = 1; +} +"###); + +case!(inner_fn_scopes, || merge_vars::MergeVars, r#" + function foo() { + var a; + var b = 1; + } +"#, @r###" +function foo() { + var a, b = 1; +} +"###); + +case!(bail_nondecl, || merge_vars::MergeVars, r#" + var a; + foo(); + var b = 1; +"#, @r###" +var a; +foo(); +var b = 1; +"###); + +case!(bail_different_types, || merge_vars::MergeVars, r#" + var x; + let y; + const z; + var a; +"#, @r###" +var x; +let y; +const z; +var a; +"###); diff --git a/src/opt_ast/tests/mod.rs b/src/opt_ast/tests/mod.rs index 17b64db..7f06c85 100644 --- a/src/opt_ast/tests/mod.rs +++ b/src/opt_ast/tests/mod.rs @@ -19,7 +19,7 @@ macro_rules! case { use crate::{emit, opt_ast, parse, swc_globals}; swc_globals::with(|g| { let (ast, files) = parse::parse(g, $string)?; - let ast = opt_ast::run(g, ast); + let ast = opt_ast::run(g, ast, opt_ast::Opt { minify: false }); let js = emit::emit(g, ast, files, emit::Opt { minify: false })?; insta::assert_snapshot!(js, @ $expected); Ok(()) @@ -29,4 +29,5 @@ macro_rules! case { } mod if2cond; +mod merge_vars; mod swc; diff --git a/src/tests/all_phases.rs b/src/tests/all_phases.rs index f098bd7..a459984 100644 --- a/src/tests/all_phases.rs +++ b/src/tests/all_phases.rs @@ -23,7 +23,7 @@ macro_rules! case { minify: false, }, ); - let ast = opt_ast::run(g, ast); + let ast = opt_ast::run(g, ast, opt_ast::Opt { minify: false }); let js = emit::emit(g, ast, files, emit::Opt { minify: false })?; insta::assert_snapshot!(js, @ $expected); Ok(()) @@ -48,7 +48,7 @@ macro_rules! extern_case { minify: false, }, ); - let ast = opt_ast::run(g, ast); + let ast = opt_ast::run(g, ast, opt_ast::Opt { minify: false }); let js = emit::emit(g, ast, files, emit::Opt { minify: false })?; insta::assert_snapshot!(stringify!($name), js); Ok(())