diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index 25d4bf62734..14fecc2866a 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -167,6 +167,7 @@ jobs: - swc_timer - swc_visit - swc_visit_macros + - swc_webpack_ast - testing - testing_macros - wasm diff --git a/Cargo.lock b/Cargo.lock index 2611fc537d1..18ba77bce78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3154,7 +3154,7 @@ dependencies = [ [[package]] name = "swc_estree_compat" -version = "0.3.0" +version = "0.3.1" dependencies = [ "ahash", "anyhow", @@ -3359,6 +3359,29 @@ dependencies = [ "syn", ] +[[package]] +name = "swc_webpack_ast" +version = "0.1.0" +dependencies = [ + "anyhow", + "rayon", + "serde_json", + "swc_atoms 0.2.9", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_testing", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_estree_ast", + "swc_estree_compat", + "swc_node_base", + "swc_timer", + "testing", + "tracing", +] + [[package]] name = "syn" version = "1.0.65" diff --git a/Cargo.toml b/Cargo.toml index 312b906a617..fb7e349784f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/swc_plugin_testing", "crates/swc_stylis", "crates/swc_timer", + "crates/swc_webpack_ast", "crates/wasm", ] diff --git a/crates/swc_ecma_transforms_testing/src/lib.rs b/crates/swc_ecma_transforms_testing/src/lib.rs index 15561e999a4..3a7965c29e8 100644 --- a/crates/swc_ecma_transforms_testing/src/lib.rs +++ b/crates/swc_ecma_transforms_testing/src/lib.rs @@ -685,7 +685,7 @@ fn test_fixture_inner

( as_folder(::swc_ecma_utils::DropSpan { preserve_ctxt: true, }), - "output.js", + "expected.js", syntax, &expected, )?; diff --git a/crates/swc_estree_compat/Cargo.toml b/crates/swc_estree_compat/Cargo.toml index 51aff9639c5..1943373e015 100644 --- a/crates/swc_estree_compat/Cargo.toml +++ b/crates/swc_estree_compat/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "Apache-2.0/MIT" name = "swc_estree_compat" repository = "https://github.com/swc-project/swc.git" -version = "0.3.0" +version = "0.3.1" [package.metadata.docs.rs] all-features = true diff --git a/crates/swc_estree_compat/src/babelify/module.rs b/crates/swc_estree_compat/src/babelify/module.rs index 9148e38a72b..637fbf2d5d1 100644 --- a/crates/swc_estree_compat/src/babelify/module.rs +++ b/crates/swc_estree_compat/src/babelify/module.rs @@ -141,7 +141,7 @@ impl Babelify for ModuleItem { type Output = ModuleItemOutput; fn parallel(cnt: usize) -> bool { - cnt >= 4 + cnt >= 32 } fn babelify(self, ctx: &Context) -> Self::Output { diff --git a/crates/swc_estree_compat/src/babelify/pat.rs b/crates/swc_estree_compat/src/babelify/pat.rs index 43408713f8c..929d4d0f127 100644 --- a/crates/swc_estree_compat/src/babelify/pat.rs +++ b/crates/swc_estree_compat/src/babelify/pat.rs @@ -55,7 +55,11 @@ impl From for ObjectPropVal { fn from(pat: PatOutput) -> Self { match pat { PatOutput::Expr(e) => ObjectPropVal::Expr(e), - other => ObjectPropVal::Pattern(other.into()), + PatOutput::Id(p) => ObjectPropVal::Pattern(PatternLike::Id(p)), + PatOutput::Array(p) => ObjectPropVal::Pattern(PatternLike::ArrayPat(p)), + PatOutput::Rest(p) => ObjectPropVal::Pattern(PatternLike::RestEl(p)), + PatOutput::Object(p) => ObjectPropVal::Pattern(PatternLike::ObjectPat(p)), + PatOutput::Assign(p) => ObjectPropVal::Pattern(PatternLike::AssignmentPat(p)), } } } @@ -119,7 +123,10 @@ impl From for Param { match pat { PatOutput::Id(i) => Param::Id(i), PatOutput::Rest(r) => Param::Rest(r), - other => other.into(), + PatOutput::Array(p) => Param::Pat(Pattern::Array(p)), + PatOutput::Object(p) => Param::Pat(Pattern::Object(p)), + PatOutput::Assign(p) => Param::Pat(Pattern::Assignment(p)), + PatOutput::Expr(p) => panic!("Cannot convert {:?} to Param", p), } } } diff --git a/crates/swc_webpack_ast/.gitignore b/crates/swc_webpack_ast/.gitignore new file mode 100644 index 00000000000..4d6d587d619 --- /dev/null +++ b/crates/swc_webpack_ast/.gitignore @@ -0,0 +1 @@ +tests/**/output.json \ No newline at end of file diff --git a/crates/swc_webpack_ast/Cargo.toml b/crates/swc_webpack_ast/Cargo.toml new file mode 100644 index 00000000000..780fc14acda --- /dev/null +++ b/crates/swc_webpack_ast/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = ["강동윤 "] +description = "Webpack AST optimizer" +edition = "2018" +license = "Apache-2.0" +name = "swc_webpack_ast" +repository = "https://github.com/swc-project/swc.git" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.48" +rayon = "1.5.1" +serde_json = "1.0.72" +swc_atoms = {version = "0.2.9", path = "../swc_atoms"} +swc_common = {version = "0.14.6", path = "../swc_common"} +swc_ecma_ast = {version = "0.58.0", path = "../swc_ecma_ast"} +swc_ecma_transforms_base = {version = "0.44.3", path = "../swc_ecma_transforms_base"} +swc_ecma_utils = {version = "0.52.3", path = "../swc_ecma_utils"} +swc_ecma_visit = {version = "0.44.0", path = "../swc_ecma_visit"} +swc_estree_ast = {version = "0.2.0", path = "../swc_estree_ast"} +swc_estree_compat = {version = "0.3.0", path = "../swc_estree_compat"} +swc_timer = {version = "0.1.0", path = "../swc_timer"} +tracing = "0.1.29" + +[dev-dependencies] +swc_ecma_parser = {version = "0.78.9", path = "../swc_ecma_parser"} +swc_ecma_transforms_testing = {version = "0.45.1", path = "../swc_ecma_transforms_testing"} +swc_node_base = {version = "0.5.1", path = "../swc_node_base"} +testing = {version = "0.15.2", path = "../testing"} diff --git a/crates/swc_webpack_ast/benches/webpack_ast.rs b/crates/swc_webpack_ast/benches/webpack_ast.rs new file mode 100644 index 00000000000..144d8f7d1aa --- /dev/null +++ b/crates/swc_webpack_ast/benches/webpack_ast.rs @@ -0,0 +1,48 @@ +#![feature(test)] + +use std::path::Path; +use swc_ecma_parser::{EsConfig, Parser, StringInput, Syntax}; +use test::Bencher; + +extern crate swc_node_base; +extern crate test; + +/// this benchmark requires real input, which cannot be committed into git +/// repository +#[bench] +#[cfg(not(all))] +fn total(b: &mut Bencher) { + let input = Path::new("tests/fixture/real/input.js"); + + b.iter(|| { + testing::run_test(false, |cm, handler| { + let fm = cm.load_file(&input).unwrap(); + + let module = { + let mut p = Parser::new( + Syntax::Es(EsConfig { + jsx: true, + ..Default::default() + }), + StringInput::from(&*fm), + None, + ); + let res = p + .parse_module() + .map_err(|e| e.into_diagnostic(&handler).emit()); + + for e in p.take_errors() { + e.into_diagnostic(&handler).emit() + } + + res? + }; + + let s = swc_webpack_ast::webpack_ast(cm.clone(), fm.clone(), module).unwrap(); + println!("{} bytes", s.len()); + + Ok(()) + }) + .unwrap(); + }); +} diff --git a/crates/swc_webpack_ast/scripts/bench.js b/crates/swc_webpack_ast/scripts/bench.js new file mode 100644 index 00000000000..5a30d0900ab --- /dev/null +++ b/crates/swc_webpack_ast/scripts/bench.js @@ -0,0 +1,66 @@ + + + +const Benchmark = require('benchmark'); +const acorn = require("acorn"); +const jsx = require("acorn-jsx"); +const parser = acorn.Parser.extend(jsx()); + + +const fs = require('fs'); +const path = require('path'); + + +const src = fs.readFileSync(path.join(process.argv[2], "input.js"), 'utf8'); +const jsonStr = fs.readFileSync(path.join(process.argv[2], "output.json"), 'utf8'); + + +{ + parser.parse(src, { + ecmaVersion: 2020, + ranges: true, + allowHashBang: true, + sourceType: 'module' + }) +} +const suite = new Benchmark.Suite; + +suite + .add('acorn', () => { + parser.parse(src, { + ecmaVersion: 2020, + ranges: true, + allowHashBang: true, + sourceType: 'module' + }) + }) + .add({ + name: 'acorn-real', + fn: (deferred) => { + fs.promises.readFile(path.join(process.argv[2], "input.js"), 'utf8') + .then((src) => { + parser.parse(src, { + ecmaVersion: 2020, + ranges: true, + allowHashBang: true, + sourceType: 'module' + }); + deferred.resolve() + }) + }, + defer: true, + async: true, + queued: true + }) + .add('json', () => { + JSON.parse(jsonStr) + }) + .on('cycle', function (event) { + console.log(String(event.target)); + }) + .on('complete', function () { + console.log('Fastest is ' + this.filter('fastest').map('name')); + }) + .run({ + async: true + }) diff --git a/crates/swc_webpack_ast/src/lib.rs b/crates/swc_webpack_ast/src/lib.rs new file mode 100644 index 00000000000..3ac9643c338 --- /dev/null +++ b/crates/swc_webpack_ast/src/lib.rs @@ -0,0 +1,45 @@ +use crate::reducer::ast_reducer; +use anyhow::{Context, Error}; +use swc_common::{sync::Lrc, Mark, SourceFile, SourceMap}; +use swc_ecma_ast::*; +use swc_ecma_transforms_base::resolver::resolver_with_mark; +use swc_ecma_visit::VisitMutWith; +use swc_estree_ast::flavor::Flavor; +use swc_estree_compat::babelify::Babelify; +use swc_timer::timer; + +pub mod reducer; + +/// `n` is expected to be pure (`resolver` is not applied) +pub fn webpack_ast( + cm: Lrc, + fm: Lrc, + mut n: Module, +) -> Result { + let _timer = timer!("webpack_ast"); + let top_level_mark = Mark::fresh(Mark::root()); + + Flavor::Acorn.with(|| { + { + let _timer = timer!("resolver"); + n.visit_mut_with(&mut resolver_with_mark(top_level_mark)); + } + { + n.visit_mut_with(&mut ast_reducer(top_level_mark)); + } + + let ctx = swc_estree_compat::babelify::Context { + fm, + cm: cm.clone(), + comments: Default::default(), + }; + + let babel_ast = { + let _timer = timer!("babelify"); + n.babelify(&ctx) + }; + + let _timer = timer!("acorn ast to json"); + serde_json::to_string(&babel_ast).context("failed to serialize babel ast") + }) +} diff --git a/crates/swc_webpack_ast/src/reducer.rs b/crates/swc_webpack_ast/src/reducer.rs new file mode 100644 index 00000000000..adb022c8df4 --- /dev/null +++ b/crates/swc_webpack_ast/src/reducer.rs @@ -0,0 +1,1247 @@ +use std::sync::Arc; +use swc_atoms::js_word; +use swc_common::{collections::AHashSet, util::take::Take, Mark, SyntaxContext, DUMMY_SP}; +use swc_ecma_ast::*; +use swc_ecma_utils::{ident::IdentLike, Id, StmtLike, StmtOrModuleItem}; +use swc_ecma_visit::{VisitMut, VisitMutWith}; +use swc_timer::timer; + +/// # Usage +/// +/// This transform should be applied after applying resolver. +/// +/// +/// # Preserved nodes +/// +/// - import +/// - export +/// - all imported identifiers +/// - `process.env.NODE_ENV` +/// - `require` +/// - `module` +/// - `__webpack_*` +/// - `import.meta` +/// - `import()` +/// - `define()` +/// - `require.ensure` +/// +/// +/// +/// # Example +/// +/// ## Input +/// +///```js +/// import { a } from "x"; +/// import b from "y"; +/// +/// function d() { +/// a.x.y(), console.log(b); +/// require("z"); +/// module.hot.accept("x", x => { ... }) +/// } +/// ``` +/// +/// ## Output +/// +/// ```js +/// import { a } from "x"; +/// import b from "y"; +/// +/// +/// a.x.y(); b; +/// require("z") +/// module.hot.accept("x", () => { }) +/// ``` +pub fn ast_reducer(top_level_mark: Mark) -> impl VisitMut { + Minimalizer { + top_level_ctxt: SyntaxContext::empty().apply_mark(top_level_mark), + ..Default::default() + } +} + +#[derive(Default)] +struct ScopeData { + imported_ids: AHashSet, +} + +impl ScopeData { + fn analyze(items: &[ModuleItem]) -> Self { + let mut imported_ids = AHashSet::default(); + + for item in items { + match item { + ModuleItem::ModuleDecl(ModuleDecl::Import(i)) => { + for s in &i.specifiers { + match s { + ImportSpecifier::Named(s) => { + imported_ids.insert(s.local.to_id()); + } + ImportSpecifier::Default(s) => { + imported_ids.insert(s.local.to_id()); + } + ImportSpecifier::Namespace(s) => { + imported_ids.insert(s.local.to_id()); + } + } + } + } + + _ => {} + } + } + + ScopeData { imported_ids } + } + + fn should_preserve(&self, i: &Ident) -> bool { + if self.imported_ids.contains(&i.to_id()) { + return true; + } + + match &*i.sym { + "process" | "module" | "import" | "define" | "require" => { + return true; + } + + _ => { + if i.sym.starts_with("__webpack_") { + return true; + } + } + } + + false + } +} + +#[derive(Clone, Default)] +struct Minimalizer { + data: Arc, + top_level_ctxt: SyntaxContext, + + var_decl_kind: Option, + + can_remove_pat: bool, +} + +impl Minimalizer { + fn flatten_stmt(&mut self, to: &mut Vec, item: &mut T) + where + T: StmtOrModuleItem + StmtLike + Take, + { + let item = item.take(); + + match item.try_into_stmt() { + Ok(stmt) => match stmt { + Stmt::Block(b) => { + to.extend(b.stmts.into_iter().map(T::from_stmt)); + } + + // Flatten a function declaration. + Stmt::Decl(Decl::Fn(fn_decl)) => { + let Function { + params, + decorators, + body, + .. + } = fn_decl.function; + + if !decorators.is_empty() { + let mut s = Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs: decorators.into_iter().map(|d| d.expr).collect(), + })), + }); + s.visit_mut_with(self); + to.push(T::from_stmt(s)); + } + + if !params.is_empty() { + let mut exprs = Vec::with_capacity(params.len()); + + for p in params { + exprs.extend(p.decorators.into_iter().map(|d| d.expr)); + + preserve_pat(&mut exprs, p.pat); + } + + let mut s = Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + })), + }); + s.visit_mut_with(self); + to.push(T::from_stmt(s)); + } + + if let Some(body) = body { + to.extend(body.stmts.into_iter().map(T::from_stmt)); + } + } + Stmt::Try(ts) => { + to.extend(ts.block.stmts.into_iter().map(T::from_stmt)); + if let Some(h) = ts.handler { + if let Some(p) = h.param { + let mut exprs = vec![]; + preserve_pat(&mut exprs, p); + + if !exprs.is_empty() { + to.push(T::from_stmt(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + })), + }))); + } + } + to.extend(h.body.stmts.into_iter().map(T::from_stmt)); + } + + if let Some(f) = ts.finalizer { + to.extend(f.stmts.into_iter().map(T::from_stmt)); + } + } + Stmt::Decl(Decl::Var(d)) => { + let mut exprs = vec![]; + + for decl in d.decls { + preserve_pat(&mut exprs, decl.name); + exprs.extend(decl.init); + } + + if !exprs.is_empty() { + let mut s = Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + })), + }); + s.visit_mut_with(self); + to.push(T::from_stmt(s)); + } + } + _ => { + to.push(T::from_stmt(stmt)); + } + }, + Err(item) => { + to.push(item); + } + } + } + + fn visit_mut_stmt_likes(&mut self, stmts: &mut Vec) + where + T: StmtOrModuleItem + StmtLike + VisitMutWith + Take, + Vec: VisitMutWith, + { + stmts.visit_mut_children_with(self); + + let mut new = Vec::with_capacity(stmts.len()); + for stmt in stmts.iter_mut() { + self.flatten_stmt(&mut new, stmt); + } + + // Remove empty statements + new.retain(|stmt| match StmtOrModuleItem::as_stmt(stmt) { + Ok(Stmt::Empty(..)) => return false, + Ok(Stmt::Expr(es)) => return !can_remove(&es.expr), + _ => true, + }); + + *stmts = new; + } + + fn ignore_expr(&mut self, e: &mut Expr) { + match e { + Expr::Lit(..) + | Expr::This(..) + | Expr::Member(MemberExpr { + obj: ExprOrSuper::Super(..), + computed: false, + .. + }) + | Expr::Yield(YieldExpr { arg: None, .. }) => { + e.take(); + return; + } + + Expr::Ident(i) => { + if !self.data.should_preserve(&*i) { + e.take(); + } + return; + } + + Expr::Member(MemberExpr { + obj: ExprOrSuper::Expr(obj), + prop, + computed, + .. + }) => { + self.ignore_expr(obj); + if *computed { + self.ignore_expr(prop); + } + + match (obj.is_invalid(), prop.is_invalid()) { + (true, true) => { + e.take(); + return; + } + (true, false) => { + if *computed { + *e = *prop.take(); + } else { + e.take(); + return; + } + } + (false, true) => { + *e = *obj.take(); + } + (false, false) => {} + } + } + + Expr::Array(a) => { + if a.elems.is_empty() { + e.take(); + return; + } + } + + Expr::Object(obj) => { + if obj.props.is_empty() { + e.take(); + return; + } + } + + Expr::Seq(seq) => { + // visit_mut_seq_expr handles the elements other than last one. + if let Some(e) = seq.exprs.last_mut() { + self.ignore_expr(&mut **e); + } + seq.exprs.retain(|e| !e.is_invalid()); + if seq.exprs.is_empty() { + e.take(); + return; + } + if seq.exprs.len() == 1 { + *e = *seq.exprs.pop().unwrap(); + return; + } + } + + _ => {} + } + } +} + +impl VisitMut for Minimalizer { + fn visit_mut_arrow_expr(&mut self, e: &mut ArrowExpr) { + let old_can_remove_pat = self.can_remove_pat; + self.can_remove_pat = true; + e.params.visit_mut_with(self); + self.can_remove_pat = old_can_remove_pat; + + e.body.visit_mut_with(self); + + e.type_params.visit_mut_with(self); + + e.return_type.visit_mut_with(self); + } + + fn visit_mut_assign_pat_prop(&mut self, p: &mut AssignPatProp) { + p.visit_mut_children_with(self); + + if let Some(v) = &mut p.value { + self.ignore_expr(&mut **v); + if v.is_invalid() { + p.value = None; + } + } + } + + fn visit_mut_class_members(&mut self, v: &mut Vec) { + v.visit_mut_children_with(self); + + v.retain(|m| { + match m { + ClassMember::ClassProp(p) => { + if !p.computed + && p.decorators.is_empty() + && can_remove(&p.key) + && p.value.as_deref().map(can_remove).unwrap_or(true) + { + return false; + } + } + ClassMember::Method(m) => { + if !m.key.is_computed() + && m.function.decorators.is_empty() + && m.function.params.is_empty() + && m.function + .body + .as_ref() + .map(|v| v.stmts.is_empty()) + .unwrap_or(true) + { + return false; + } + } + _ => {} + } + + true + }); + } + + /// Normalize expressions. + /// + /// - empty [Expr::Seq] => [Expr::Invalid] + fn visit_mut_expr(&mut self, e: &mut Expr) { + e.visit_mut_children_with(self); + + match e { + Expr::Seq(seq) => { + if seq.exprs.is_empty() { + *e = null_expr(); + return; + } + + if seq.exprs.len() == 1 { + *e = *seq.exprs.pop().unwrap(); + } + } + _ => {} + } + + match e { + Expr::Paren(p) => { + *e = *p.expr.take(); + } + + Expr::Ident(i) => { + if self.data.should_preserve(&i) { + return; + } + + *e = null_expr(); + } + + Expr::Member(MemberExpr { + obj: ExprOrSuper::Expr(obj), + computed: false, + .. + }) => { + if let Some(left) = left_most(&obj) { + if self.data.should_preserve(&left) { + return; + } + } + *e = *obj.take(); + return; + } + + Expr::OptChain(opt) => { + *e = *opt.expr.take(); + } + + Expr::Await(expr) => { + *e = *expr.arg.take(); + } + + Expr::Yield(expr) => { + if let Some(arg) = expr.arg.take() { + *e = *arg; + } + } + + Expr::Unary(expr) => { + *e = *expr.arg.take(); + } + + Expr::Update(expr) => { + *e = *expr.arg.take(); + } + + Expr::TsAs(expr) => { + *e = *expr.expr.take(); + } + + Expr::TsConstAssertion(expr) => { + *e = *expr.expr.take(); + } + + Expr::TsTypeAssertion(expr) => { + *e = *expr.expr.take(); + } + + Expr::TsNonNull(expr) => { + *e = *expr.expr.take(); + } + + Expr::TaggedTpl(expr) => { + let mut exprs = Vec::with_capacity(expr.tpl.exprs.len() + 1); + exprs.push(expr.tag.take()); + exprs.extend(expr.tpl.exprs.take()); + + let mut seq = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + }); + + seq.visit_mut_with(self); + + *e = seq; + } + + Expr::Tpl(expr) => { + let mut seq = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs: expr.exprs.take(), + }); + + seq.visit_mut_with(self); + + *e = seq; + } + + Expr::Assign(expr) => { + let mut exprs = Vec::with_capacity(2); + preserve_pat_or_expr(&mut exprs, expr.left.take()); + exprs.push(expr.right.take()); + let mut seq = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + }); + + seq.visit_mut_with(self); + + *e = seq; + } + + Expr::Seq(seq) => { + if seq.exprs.is_empty() { + *e = Expr::Invalid(Invalid { span: DUMMY_SP }); + return; + } + } + + Expr::Bin(expr) => { + let mut exprs = Vec::with_capacity(2); + + exprs.push(expr.left.take()); + exprs.push(expr.right.take()); + + let mut seq = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + }); + + seq.visit_mut_with(self); + + *e = seq; + } + + Expr::Cond(expr) => { + let mut exprs = Vec::with_capacity(3); + + exprs.push(expr.test.take()); + exprs.push(expr.cons.take()); + exprs.push(expr.alt.take()); + + let mut seq = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + }); + + seq.visit_mut_with(self); + + *e = seq; + } + + Expr::Array(arr) => { + let mut seq = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs: arr + .elems + .take() + .into_iter() + .flatten() + .map(|elem| elem.expr) + .collect(), + }); + + seq.visit_mut_with(self); + + *e = seq; + } + + Expr::Object(obj) => { + if obj.props.iter().all(|prop| match prop { + PropOrSpread::Spread(..) => true, + PropOrSpread::Prop(prop) => match &**prop { + Prop::Shorthand(..) | Prop::KeyValue(..) | Prop::Assign(..) => true, + _ => false, + }, + }) { + let mut exprs = Vec::with_capacity(obj.props.len()); + + for prop in obj.props.take() { + match prop { + PropOrSpread::Spread(prop) => { + exprs.push(prop.expr); + } + PropOrSpread::Prop(prop) => match *prop { + Prop::Shorthand(i) => { + exprs.push(Box::new(Expr::Ident(i))); + } + Prop::KeyValue(p) => { + preserve_prop_name(&mut exprs, p.key); + exprs.push(p.value); + } + Prop::Assign(p) => { + exprs.push(Box::new(Expr::Ident(p.key))); + exprs.push(p.value); + } + _ => { + unreachable!() + } + }, + } + } + + let mut seq = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + }); + + seq.visit_mut_with(self); + + *e = seq; + } + } + + Expr::Call(CallExpr { + callee: ExprOrSuper::Expr(callee), + args, + .. + }) + | Expr::New(NewExpr { + callee, + args: Some(args), + .. + }) => { + self.ignore_expr(callee); + + if callee.is_invalid() { + let mut seq = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs: args.take().into_iter().map(|arg| arg.expr).collect(), + }); + + seq.visit_mut_with(self); + + *e = seq; + } + } + + Expr::JSXElement(el) => { + // Remove empty, non-component elements. + match &el.opening.name { + JSXElementName::Ident(name) => { + if name.sym.chars().next().unwrap().is_uppercase() { + if el.opening.attrs.is_empty() && el.children.is_empty() { + *e = Expr::Ident(name.clone()); + return; + } + + return; + } + } + _ => return, + } + + if el.opening.attrs.is_empty() && el.children.is_empty() { + *e = null_expr(); + return; + } + } + + Expr::Arrow(ArrowExpr { + params, + body: BlockStmtOrExpr::Expr(body), + .. + }) => { + let mut exprs = vec![]; + for p in params.take() { + preserve_pat(&mut exprs, p); + } + + exprs.push(body.take()); + + let mut seq = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + }); + + seq.visit_mut_with(self); + + *e = seq; + } + + // TODO: + // Expr::Class(_) => todo!(), + // Expr::MetaProp(_) => todo!(), + // Expr::JSXMember(_) => todo!(), + // Expr::JSXNamespacedName(_) => todo!(), + // Expr::JSXEmpty(_) => todo!(), + // Expr::JSXFragment(_) => todo!(), + // Expr::OptChain(_) => todo!(), + _ => {} + } + } + + fn visit_mut_expr_or_spread(&mut self, expr: &mut ExprOrSpread) { + expr.spread = None; + expr.expr.visit_mut_with(self); + } + + fn visit_mut_expr_or_spreads(&mut self, elems: &mut Vec) { + elems.visit_mut_children_with(self); + + elems.retain(|e| !e.expr.is_invalid()); + } + + fn visit_mut_expr_stmt(&mut self, s: &mut ExprStmt) { + s.visit_mut_children_with(self); + + self.ignore_expr(&mut s.expr); + } + + fn visit_mut_exprs(&mut self, exprs: &mut Vec>) { + exprs.visit_mut_children_with(self); + + exprs.retain(|e| !e.is_invalid()); + } + + fn visit_mut_function(&mut self, f: &mut Function) { + f.decorators.visit_mut_with(self); + + let old_can_remove_pat = self.can_remove_pat; + self.can_remove_pat = true; + f.params.visit_mut_with(self); + self.can_remove_pat = old_can_remove_pat; + + f.body.visit_mut_with(self); + + f.type_params.visit_mut_with(self); + + f.return_type.visit_mut_with(self); + } + + fn visit_mut_jsx_attr_or_spreads(&mut self, attrs: &mut Vec) { + attrs.visit_mut_children_with(self); + + attrs.retain(|attr| match attr { + JSXAttrOrSpread::JSXAttr(attr) => { + match &attr.name { + JSXAttrName::Ident(..) => {} + JSXAttrName::JSXNamespacedName(_) => { + // We don't handle this because no one uses it + return true; + } + } + + // Remove jsx attributes + match &attr.value { + Some(v) => match v { + JSXAttrValue::Lit(_) => return false, + JSXAttrValue::JSXExprContainer(e) => match &e.expr { + JSXExpr::JSXEmptyExpr(_) => { + return true; + } + JSXExpr::Expr(e) => { + if can_remove(&e) { + return false; + } + } + }, + JSXAttrValue::JSXElement(_) => {} + JSXAttrValue::JSXFragment(_) => {} + }, + None => return false, + } + + true + } + JSXAttrOrSpread::SpreadElement(s) => { + if can_remove(&s.expr) { + return false; + } + + true + } + }); + } + + fn visit_mut_jsx_element(&mut self, el: &mut JSXElement) { + el.visit_mut_children_with(self); + + // Remove empty, non-component elements. + match &el.opening.name { + JSXElementName::Ident(name) => { + if name.sym.chars().next().unwrap().is_uppercase() { + return; + } + } + _ => return, + } + + if !el.opening.attrs.is_empty() { + return; + } + + if el.children.len() == 1 { + match &mut el.children[0] { + JSXElementChild::JSXElement(c) => { + *el = *c.take(); + } + _ => {} + } + } + } + + fn visit_mut_jsx_element_children(&mut self, v: &mut Vec) { + v.visit_mut_children_with(self); + + v.retain(|c| match c { + JSXElementChild::JSXText(_) + | JSXElementChild::JSXExprContainer(JSXExprContainer { + expr: JSXExpr::JSXEmptyExpr(..), + .. + }) => return false, + JSXElementChild::JSXExprContainer(JSXExprContainer { + expr: JSXExpr::Expr(expr), + .. + }) => return !can_remove(&expr), + + JSXElementChild::JSXElement(el) => { + // Remove empty, non-component elements. + match &el.opening.name { + JSXElementName::Ident(name) => { + if name.sym.chars().next().unwrap().is_uppercase() { + return true; + } + } + _ => return true, + } + + if el.opening.attrs.is_empty() && el.children.is_empty() { + return false; + } + + true + } + + _ => true, + }) + } + + fn visit_mut_jsx_expr(&mut self, e: &mut JSXExpr) { + e.visit_mut_children_with(self); + + match e { + JSXExpr::JSXEmptyExpr(_) => {} + JSXExpr::Expr(expr) => { + if expr.is_invalid() { + *e = JSXExpr::JSXEmptyExpr(JSXEmptyExpr { span: DUMMY_SP }); + } + } + } + } + + fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) { + e.obj.visit_mut_with(self); + + if e.computed { + e.prop.visit_mut_with(self); + } + } + + fn visit_mut_module_items(&mut self, stmts: &mut Vec) { + let _timer = timer!("reduce ast"); + + self.data = Arc::new(ScopeData::analyze(&stmts)); + + self.visit_mut_stmt_likes(stmts); + } + + fn visit_mut_object_pat_props(&mut self, props: &mut Vec) { + props.visit_mut_children_with(self); + + props.retain(|prop| match prop { + ObjectPatProp::Rest(p) => { + if p.arg.is_invalid() { + return false; + } + + true + } + ObjectPatProp::Assign(p) => { + if self.can_remove_pat { + if p.value.is_none() { + return false; + } + } + + true + } + + ObjectPatProp::KeyValue(p) => { + if p.value.is_invalid() { + return false; + } + + true + } + }); + } + + fn visit_mut_opt_expr(&mut self, e: &mut Option>) { + e.visit_mut_children_with(self); + + if let Some(Expr::Invalid(..)) = e.as_deref() { + e.take(); + } + } + + fn visit_mut_opt_expr_or_spread(&mut self, e: &mut Option) { + e.visit_mut_children_with(self); + + if let Some(elem) = e { + if elem.expr.is_invalid() { + *e = None; + } + } + } + + fn visit_mut_params(&mut self, ps: &mut Vec) { + ps.visit_mut_children_with(self); + + ps.retain(|param| !param.pat.is_invalid()); + } + + fn visit_mut_pat(&mut self, pat: &mut Pat) { + // We don't need rest pattern. + match pat { + Pat::Rest(rest) => { + *pat = *rest.arg.take(); + } + _ => {} + } + + pat.visit_mut_children_with(self); + + if !self.can_remove_pat { + return; + } + + match pat { + Pat::Ident(p) => { + if p.id.span.ctxt != self.top_level_ctxt { + pat.take(); + return; + } + } + + Pat::Array(arr) => { + if arr.elems.is_empty() { + pat.take(); + return; + } + } + + Pat::Object(obj) => { + if obj.props.is_empty() { + pat.take(); + return; + } + } + + _ => {} + } + } + + fn visit_mut_pats(&mut self, pats: &mut Vec) { + pats.visit_mut_children_with(self); + + pats.retain(|pat| !pat.is_invalid()); + } + + fn visit_mut_prop(&mut self, p: &mut Prop) { + p.visit_mut_children_with(self); + + match p { + Prop::Shorthand(i) => { + if !self.data.should_preserve(&*i) { + i.take(); + return; + } + } + _ => {} + } + } + + fn visit_mut_prop_or_spreads(&mut self, props: &mut Vec) { + props.visit_mut_children_with(self); + + props.retain(|p| match p { + PropOrSpread::Spread(..) => true, + PropOrSpread::Prop(p) => match &**p { + Prop::Shorthand(p) => { + if p.sym == js_word!("") { + return false; + } + + true + } + _ => true, + }, + }) + } + + fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) { + e.visit_mut_children_with(self); + + let cnt = e.exprs.len(); + + for (idx, elem) in e.exprs.iter_mut().enumerate() { + if idx == cnt - 1 { + continue; + } + + self.ignore_expr(&mut **elem); + } + + e.exprs.retain(|e| !can_remove(&e)); + } + + /// Normalize statements. + /// + /// - Invalid [Stmt::Expr] => [Stmt::Empty] + /// - Empty [Stmt::Block] => [Stmt::Empty] + /// - Single-item [Stmt::Block] => the item + /// - Invalid [Stmt::Decl] => [Stmt::Empty] + /// - Useless stmt => [Stmt::Empty] + fn visit_mut_stmt(&mut self, stmt: &mut Stmt) { + match stmt { + Stmt::Debugger(_) | Stmt::Break(_) | Stmt::Continue(_) => { + *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); + return; + } + + Stmt::Return(s) => { + if let Some(arg) = s.arg.take() { + *stmt = Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: arg, + }); + } else { + *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); + return; + } + } + Stmt::Throw(s) => { + *stmt = Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: s.arg.take(), + }); + } + + _ => {} + } + + stmt.visit_mut_children_with(self); + + match stmt { + Stmt::Labeled(l) => { + *stmt = *l.body.take(); + } + + _ => {} + } + + match stmt { + Stmt::Expr(e) => { + if e.expr.is_invalid() { + *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); + return; + } + } + + Stmt::Block(block) => { + if block.stmts.is_empty() { + *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); + return; + } + if block.stmts.len() == 1 { + *stmt = block.stmts.take().into_iter().next().unwrap(); + return; + } + } + + Stmt::Decl(Decl::Var(var)) => { + if var.decls.is_empty() { + *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); + return; + } + } + + Stmt::If(is) => { + if let Some(alt) = &mut is.alt { + if alt.is_empty() { + is.alt = None; + } + } + + // + if can_remove(&is.test) { + if is.cons.is_empty() && is.alt.is_none() { + *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); + return; + } + + if is.alt.is_none() { + *stmt = *is.cons.take(); + return; + } + } + } + + // TODO: Flatten loops + // TODO: Flatten try catch + _ => {} + } + } + + fn visit_mut_stmts(&mut self, stmts: &mut Vec) { + self.visit_mut_stmt_likes(stmts); + } + + fn visit_mut_var_decl(&mut self, var: &mut VarDecl) { + let old_var_decl_kind = self.var_decl_kind; + self.var_decl_kind = Some(var.kind); + + var.visit_mut_children_with(self); + + self.var_decl_kind = old_var_decl_kind; + } + + fn visit_mut_var_declarator(&mut self, v: &mut VarDeclarator) { + v.visit_mut_children_with(self); + + if let Some(e) = &mut v.init { + self.ignore_expr(&mut **e); + + if e.is_invalid() { + v.init = None; + } + } + + if v.init.is_none() && matches!(self.var_decl_kind, Some(VarDeclKind::Const)) { + v.init = Some(Box::new(null_expr())); + } + } +} + +fn preserve_pat_or_expr(exprs: &mut Vec>, p: PatOrExpr) { + match p { + PatOrExpr::Expr(e) => { + exprs.push(e); + } + PatOrExpr::Pat(p) => preserve_pat(exprs, *p), + } +} + +fn preserve_pat(exprs: &mut Vec>, p: Pat) { + match p { + Pat::Ident(..) => {} + Pat::Array(p) => { + p.elems + .into_iter() + .flatten() + .for_each(|elem| preserve_pat(exprs, elem)); + } + Pat::Rest(p) => preserve_pat(exprs, *p.arg), + Pat::Object(p) => p.props.into_iter().for_each(|p| { + preserve_obj_pat(exprs, p); + }), + Pat::Assign(p) => { + preserve_pat(exprs, *p.left); + exprs.push(p.right) + } + Pat::Invalid(_) => {} + Pat::Expr(e) => { + exprs.push(e); + } + } +} + +fn preserve_obj_pat(exprs: &mut Vec>, p: ObjectPatProp) { + match p { + ObjectPatProp::KeyValue(p) => { + preserve_prop_name(exprs, p.key); + preserve_pat(exprs, *p.value); + } + ObjectPatProp::Assign(p) => { + exprs.extend(p.value); + } + ObjectPatProp::Rest(p) => preserve_pat(exprs, *p.arg), + } +} + +fn preserve_prop_name(exprs: &mut Vec>, p: PropName) { + match p { + PropName::Computed(e) => { + exprs.push(e.expr); + } + _ => {} + } +} + +fn left_most(e: &Expr) -> Option { + match e { + Expr::Ident(i) => Some(i.clone()), + Expr::Member(MemberExpr { + obj: ExprOrSuper::Expr(obj), + computed: false, + .. + }) => left_most(obj), + + _ => None, + } +} + +fn null_expr() -> Expr { + Expr::Lit(Lit::Null(Null { span: DUMMY_SP })) +} + +fn can_remove(e: &Expr) -> bool { + match e { + Expr::Invalid(..) => true, + Expr::Lit(..) => true, + Expr::Seq(seq) => seq.exprs.iter().all(|e| can_remove(e)), + _ => false, + } +} diff --git a/crates/swc_webpack_ast/tests/fixture.rs b/crates/swc_webpack_ast/tests/fixture.rs new file mode 100644 index 00000000000..e4afa8326cb --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture.rs @@ -0,0 +1,66 @@ +use std::{fs, path::PathBuf}; +use swc_common::{chain, Mark}; +use swc_ecma_parser::{EsConfig, Parser, StringInput, Syntax}; +use swc_ecma_transforms_base::resolver::resolver_with_mark; +use swc_ecma_transforms_testing::test_fixture; +use swc_ecma_visit::as_folder; +use swc_webpack_ast::reducer::ast_reducer; + +#[testing::fixture("tests/fixture/**/input.js")] +fn fixture(input: PathBuf) { + let output = input.parent().unwrap().join("output.js"); + + test_fixture( + Syntax::Es(EsConfig { + jsx: true, + ..Default::default() + }), + &|_| { + let top_level_mark = Mark::fresh(Mark::root()); + + chain!( + resolver_with_mark(top_level_mark), + as_folder(ast_reducer(top_level_mark)) + ) + }, + &input, + &output, + ); +} + +#[testing::fixture("tests/fixture/**/input.js")] +fn test_babelify(input: PathBuf) { + let output_path = input.parent().unwrap().join("output.json"); + + testing::run_test(false, |cm, handler| { + let fm = cm.load_file(&input).unwrap(); + + let module = { + let mut p = Parser::new( + Syntax::Es(EsConfig { + jsx: true, + ..Default::default() + }), + StringInput::from(&*fm), + None, + ); + let res = p + .parse_module() + .map_err(|e| e.into_diagnostic(&handler).emit()); + + for e in p.take_errors() { + e.into_diagnostic(&handler).emit() + } + + res? + }; + + let s = swc_webpack_ast::webpack_ast(cm.clone(), fm.clone(), module).unwrap(); + println!("{} bytes", s.len()); + + fs::write(&output_path, s.as_bytes()).unwrap(); + + Ok(()) + }) + .unwrap(); +} diff --git a/crates/swc_webpack_ast/tests/fixture/array-literal/input.js b/crates/swc_webpack_ast/tests/fixture/array-literal/input.js new file mode 100644 index 00000000000..2de3bf8dc63 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/array-literal/input.js @@ -0,0 +1,5 @@ +import { a } from "x"; +import b from "y"; + +const arr = [a, 'foo', b(), 'bar']; +console.log(arr); \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/array-literal/output.js b/crates/swc_webpack_ast/tests/fixture/array-literal/output.js new file mode 100644 index 00000000000..8e28a5c9eac --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/array-literal/output.js @@ -0,0 +1,3 @@ +import { a } from "x"; +import b from "y"; +a, b(); diff --git a/crates/swc_webpack_ast/tests/fixture/base/input.js b/crates/swc_webpack_ast/tests/fixture/base/input.js new file mode 100644 index 00000000000..2c2297a099e --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/base/input.js @@ -0,0 +1,10 @@ +import { a } from "x"; +import b from "y"; + +function d() { + a.x.y(), console.log(b); + require("z"); + module.hot.accept("x", x => { + + }) +} \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/base/output.js b/crates/swc_webpack_ast/tests/fixture/base/output.js new file mode 100644 index 00000000000..e5e7794c4ee --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/base/output.js @@ -0,0 +1,6 @@ +import { a } from "x"; +import b from "y"; +a.x.y(), b; +require("z"); +module.hot.accept("x", ()=>{ +}); diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/1/input.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/1/input.js new file mode 100644 index 00000000000..1bc030298ef --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/1/input.js @@ -0,0 +1,6 @@ +import y from 'y;' + +const obj = { + [1 + 1]: 2, + y, +} diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/1/output.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/1/output.js new file mode 100644 index 00000000000..35f334603b8 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/1/output.js @@ -0,0 +1,2 @@ +import y from "y;"; +y; diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/2/input.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/2/input.js new file mode 100644 index 00000000000..0767535e5fe --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/2/input.js @@ -0,0 +1,9 @@ +import x from 'x;/' +import y from 'y;' + +const obj = { + [1 + y]: x, + y, +} + +console.log(obj) \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/2/output.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/2/output.js new file mode 100644 index 00000000000..9f4d3ef1964 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/2/output.js @@ -0,0 +1,3 @@ +import x from "x;/"; +import y from "y;"; +y, x, y; diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/3/input.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/3/input.js new file mode 100644 index 00000000000..1bc6f2ff2c7 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/3/input.js @@ -0,0 +1,9 @@ +import x from 'x;/' +import y from 'y;' + +const obj = { + [1 + y]: 1, + x, +} + +console.log(obj) \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/3/output.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/3/output.js new file mode 100644 index 00000000000..1b62ff0a003 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/3/output.js @@ -0,0 +1,3 @@ +import x from "x;/"; +import y from "y;"; +y, x; diff --git a/crates/swc_webpack_ast/tests/fixture/class-decl/1/input.js b/crates/swc_webpack_ast/tests/fixture/class-decl/1/input.js new file mode 100644 index 00000000000..6e67216044f --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/class-decl/1/input.js @@ -0,0 +1,8 @@ +import bar from 'bar'; + +class Foo { + [bar]() { } +} + + +console.log(new Foo()) \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/class-decl/1/output.js b/crates/swc_webpack_ast/tests/fixture/class-decl/1/output.js new file mode 100644 index 00000000000..8f7fe4b2912 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/class-decl/1/output.js @@ -0,0 +1,5 @@ +import bar from "bar"; +class Foo { + [bar]() { + } +} diff --git a/crates/swc_webpack_ast/tests/fixture/new-expr/1/input.js b/crates/swc_webpack_ast/tests/fixture/new-expr/1/input.js new file mode 100644 index 00000000000..9ddd6ff68b5 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/new-expr/1/input.js @@ -0,0 +1,6 @@ +import Bar from 'bar'; + +class Foo extends Bar { +} + +console.log(new Foo()) \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/new-expr/1/output.js b/crates/swc_webpack_ast/tests/fixture/new-expr/1/output.js new file mode 100644 index 00000000000..423cf3f8366 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/new-expr/1/output.js @@ -0,0 +1,3 @@ +import Bar from "bar"; +class Foo extends Bar { +} diff --git a/crates/swc_webpack_ast/tests/fixture/object-literal/input.js b/crates/swc_webpack_ast/tests/fixture/object-literal/input.js new file mode 100644 index 00000000000..5bb9a1fd868 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/object-literal/input.js @@ -0,0 +1,12 @@ +import { a } from "x"; +import b from "y"; + +const d = {}; + +const obj = { + a, + [b]: 'foo', + c: 'bar', + d +}; +console.log(obj); \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/object-literal/output.js b/crates/swc_webpack_ast/tests/fixture/object-literal/output.js new file mode 100644 index 00000000000..aad75448c2c --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/object-literal/output.js @@ -0,0 +1,3 @@ +import { a } from "x"; +import b from "y"; +a, b; diff --git a/crates/swc_webpack_ast/tests/fixture/object-patterns/input.js b/crates/swc_webpack_ast/tests/fixture/object-patterns/input.js new file mode 100644 index 00000000000..b195a9a22c4 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/object-patterns/input.js @@ -0,0 +1,10 @@ +import { a } from "x"; +import b from "y"; + +function d() { + a.x.y(), console.log(b); + require("z"); + module.hot.accept("x", ({ a = 1, b: { foo }, c: { ...rest } }) => { + + }) +} \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/object-patterns/output.js b/crates/swc_webpack_ast/tests/fixture/object-patterns/output.js new file mode 100644 index 00000000000..e5e7794c4ee --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/object-patterns/output.js @@ -0,0 +1,6 @@ +import { a } from "x"; +import b from "y"; +a.x.y(), b; +require("z"); +module.hot.accept("x", ()=>{ +}); diff --git a/crates/swc_webpack_ast/tests/fixture/opt-chain/1/input.js b/crates/swc_webpack_ast/tests/fixture/opt-chain/1/input.js new file mode 100644 index 00000000000..91e0027e661 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/opt-chain/1/input.js @@ -0,0 +1 @@ +const a = obj?.foo; \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/opt-chain/1/output.js b/crates/swc_webpack_ast/tests/fixture/opt-chain/1/output.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/input.js b/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/input.js new file mode 100644 index 00000000000..ed33c2fe5dd --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/input.js @@ -0,0 +1,5 @@ + + +export const foo = \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/output.js b/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/output.js new file mode 100644 index 00000000000..f3194e8e688 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/output.js @@ -0,0 +1 @@ +export const foo = null; diff --git a/crates/swc_webpack_ast/tests/fixture/try-catch/1/input.js b/crates/swc_webpack_ast/tests/fixture/try-catch/1/input.js new file mode 100644 index 00000000000..bf9774c8926 --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/try-catch/1/input.js @@ -0,0 +1,19 @@ +export function foo (){ + try { + const meta = this.meta(); + // const { event, team, user } = this.props; + const b = team ? event.user : user; + + if (meta && meta.p1) { + return meta.p1; + } else if (meta && meta.p2) { + return meta.p2; + } else if (meta && meta.p3) { + return meta.p3; + } + + return b.p4 === u.u ? 'You' : b.u; + } catch (e) { + return 'You'; + } +} \ No newline at end of file diff --git a/crates/swc_webpack_ast/tests/fixture/try-catch/1/output.js b/crates/swc_webpack_ast/tests/fixture/try-catch/1/output.js new file mode 100644 index 00000000000..0da5001a08e --- /dev/null +++ b/crates/swc_webpack_ast/tests/fixture/try-catch/1/output.js @@ -0,0 +1,2 @@ +export function foo() { +} diff --git a/package.json b/package.json index c86cdb0e57e..72279f986db 100644 --- a/package.json +++ b/package.json @@ -75,8 +75,10 @@ "@types/jest": "^26.0.23", "@types/node": "^14.14.41", "acorn": "^8.6.0", + "acorn-jsx": "^5.3.2", "axios": "^0.21.1", "babel-plugin-transform-node-env-inline": "^0.4.3", + "benchmark": "^2.1.4", "class-validator": "^0.13.1", "core-js": "^2.6.11", "cross-env": "^7.0.3", diff --git a/yarn.lock b/yarn.lock index f6d8721e061..27557a24049 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1730,6 +1730,11 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" @@ -1942,6 +1947,14 @@ before-after-hook@^2.1.0, before-after-hook@^2.2.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== +benchmark@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629" + integrity sha1-CfPeMckWQl1JjMLuVloOvzwqVik= + dependencies: + lodash "^4.17.4" + platform "^1.3.3" + bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -3625,7 +3638,7 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" -lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3972,6 +3985,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +platform@^1.3.3: + version "1.3.6" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" + integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"