From 9bd7a9c484dfedeb2fa90a7bc108515c7b425b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Thu, 14 Feb 2019 21:12:05 +0900 Subject: [PATCH] Perfect fixer (#212) swc_ecma_transforms: - test fixer using test262 - make fixer perfect --- ecmascript/transforms/src/fixer.rs | 217 ++++++++++++---- ecmascript/transforms/tests/fixer.rs | 365 +++++++++++++++++++++++++++ 2 files changed, 526 insertions(+), 56 deletions(-) create mode 100644 ecmascript/transforms/tests/fixer.rs diff --git a/ecmascript/transforms/src/fixer.rs b/ecmascript/transforms/src/fixer.rs index 12e6b465392..41cf9ab64c5 100644 --- a/ecmascript/transforms/src/fixer.rs +++ b/ecmascript/transforms/src/fixer.rs @@ -1,6 +1,6 @@ use crate::{ pass::Pass, - util::{ExprFactory, State}, + util::{ExprExt, ExprFactory, State}, }; use ast::*; use swc_common::{ @@ -104,49 +104,6 @@ impl Fold for Fixer { _ => stmt.fold_children(self), }; - fn handle_expr_stmt(expr: Expr) -> Expr { - match expr { - // It's important for arrow pass to work properly. - Expr::Object(..) | Expr::Fn(..) => expr.wrap_with_paren(), - - // ({ a } = foo) - Expr::Assign(AssignExpr { - span, - left: PatOrExpr::Pat(left @ box Pat::Object(..)), - op, - right, - }) => AssignExpr { - span, - left: PatOrExpr::Pat(left), - op, - right, - } - .wrap_with_paren(), - - Expr::Seq(SeqExpr { span, exprs }) => { - debug_assert!( - exprs.len() != 1, - "SeqExpr should be unwrapped if exprs.len() == 1, but length is 1" - ); - - let mut first = true; - Expr::Seq(SeqExpr { - span, - exprs: exprs.move_map(|expr| { - if first { - first = false; - expr.map(handle_expr_stmt) - } else { - expr - } - }), - }) - } - - _ => expr, - } - } - let stmt = match stmt { Stmt::Expr(expr) => Stmt::Expr(expr.map(handle_expr_stmt)), @@ -222,7 +179,7 @@ impl Fold for Fixer { fn unwrap_expr(mut e: Expr) -> Expr { match e { Expr::Seq(SeqExpr { ref mut exprs, .. }) if exprs.len() == 1 => { - *exprs.pop().unwrap() + unwrap_expr(*exprs.pop().unwrap()) } Expr::Paren(ParenExpr { expr, .. }) => unwrap_expr(*expr), _ => e, @@ -285,26 +242,59 @@ impl Fold for Fixer { }) .sum(); - let expr = if len == exprs.len() { + let exprs_len = exprs.len(); + let expr = if len == exprs_len { + let mut exprs = exprs + .into_iter() + .enumerate() + .filter_map(|(i, e)| { + let is_last = i + 1 == exprs_len; + if is_last { + Some(e) + } else { + ignore_return_value(e) + } + }) + .collect::>(); + if exprs.len() == 1 { + return *exprs.pop().unwrap(); + } Expr::Seq(SeqExpr { span, exprs }) } else { let mut buf = Vec::with_capacity(len); - for expr in exprs { - match *expr { - Expr::Seq(SeqExpr { mut exprs, .. }) => { - // Remove useless items - while let Some(box Expr::Ident(..)) = exprs.last() { - let _ = exprs.pop(); - } + for (i, expr) in exprs.into_iter().enumerate() { + let is_last = i + 1 == exprs_len; - buf.append(&mut exprs) + match *expr { + Expr::Seq(SeqExpr { exprs, .. }) => { + if !is_last { + buf.extend(exprs.into_iter().filter_map(ignore_return_value)); + } else { + let exprs_len = exprs.len(); + for (i, expr) in exprs.into_iter().enumerate() { + let is_last = i + 1 == exprs_len; + if is_last { + buf.push(expr); + } else { + buf.extend(ignore_return_value(expr)); + } + } + } } _ => buf.push(expr), } + + if is_last { + + } else { + + } } + if buf.len() == 1 { + return *buf.pop().unwrap(); + } buf.shrink_to_fit(); - Expr::Seq(SeqExpr { span, exprs: buf }) }; @@ -319,7 +309,7 @@ impl Fold for Fixer { Expr::Bin(mut expr) => { expr.right = match *expr.right { - e @ Expr::Assign(..) => box e.wrap_with_paren(), + e @ Expr::Assign(..) | e @ Expr::Seq(..) => box e.wrap_with_paren(), _ => expr.right, }; @@ -403,6 +393,64 @@ impl Fold for Fixer { } } +fn ignore_return_value(expr: Box) -> Option> { + match *expr { + Expr::Ident(..) | Expr::Fn(..) | Expr::Lit(..) => None, + Expr::Unary(UnaryExpr { + op: op!("void"), + arg, + .. + }) => ignore_return_value(arg), + _ => Some(expr), + } +} + +fn handle_expr_stmt(expr: Expr) -> Expr { + match expr { + // It's important for arrow pass to work properly. + Expr::Object(..) | Expr::Class(..) | Expr::Fn(..) => expr.wrap_with_paren(), + + // ({ a } = foo) + Expr::Assign(AssignExpr { + span, + left: PatOrExpr::Pat(left @ box Pat::Object(..)), + op, + right, + }) => AssignExpr { + span, + left: PatOrExpr::Pat(left), + op, + right, + } + .wrap_with_paren(), + + Expr::Seq(SeqExpr { span, exprs }) => { + debug_assert!( + exprs.len() != 1, + "SeqExpr should be unwrapped if exprs.len() == 1, but length is 1" + ); + + let mut i = 0; + let len = exprs.len(); + Expr::Seq(SeqExpr { + span, + exprs: exprs.move_map(|expr| { + i += 1; + let is_last = len == i; + + if !is_last { + expr.map(handle_expr_stmt) + } else { + expr + } + }), + }) + } + + _ => expr, + } +} + #[cfg(test)] mod tests { struct Noop; @@ -497,4 +545,61 @@ const _ref = {}, { c =( _tmp = {}, d = _extends({}, _tmp), _tmp) } = _ref;" ); identical!(issue_207, "a => ({x: 'xxx', y: {a}});"); + + test_fixer!( + fixer_01, + "var a, b, c, d, e, f; +((a, b), (c())) + ((d, e), (f())); +", + "var a, b, c, d, e, f; +c() + f()" + ); + + test_fixer!(fixer_02, "(b, c), d;", "d;"); + + test_fixer!(fixer_03, "((a, b), (c && d)) && e;", "c && d && e;"); + + test_fixer!(fixer_04, "for ((a, b), c;;) ;", "for(c;;);"); + + test_fixer!( + fixer_05, + "var a, b, c = (1), d, e, f = (2); +((a, b), c) + ((d, e), f);", + "var a, b, c = 1, d, e, f = 2; +c + f;" + ); + + test_fixer!( + fixer_06, + "var a, b, c, d; +a = ((b, c), d);", + "var a, b, c, d; +a = d;" + ); + + test_fixer!(fixer_07, "a => ((b, c) => ((a, b), c));", "(a)=>(b, c)=>c;"); + + test_fixer!(fixer_08, "typeof (((1), a), (2));", "typeof 2"); + + test_fixer!(fixer_09, "(((a, b), c), d) ? e : f;", "d ? e : f;"); + + test_fixer!( + fixer_10, + " +function a() { + return (((void (1)), (void (2))), a), (void (3)); +} +", + " +function a() { + return void 3; +} +" + ); + + test_fixer!(fixer_11, "c && ((((2), (3)), d), b);", "c && b"); + + test_fixer!(fixer_12, "(((a, b), c), d) + e;", "d + e;"); + + test_fixer!(fixer_13, "delete (((1), a), (2));", "delete 2"); } diff --git a/ecmascript/transforms/tests/fixer.rs b/ecmascript/transforms/tests/fixer.rs new file mode 100644 index 00000000000..e89ea3aa0f0 --- /dev/null +++ b/ecmascript/transforms/tests/fixer.rs @@ -0,0 +1,365 @@ +#![feature(box_syntax)] +#![feature(box_patterns)] +#![feature(specialization)] +#![feature(test)] +extern crate sourcemap; +extern crate swc_common; +extern crate swc_ecma_ast; +extern crate swc_ecma_codegen; +extern crate swc_ecma_parser; +extern crate swc_ecma_transforms; +extern crate test; +extern crate testing; +use sourcemap::SourceMapBuilder; +use std::{ + env, + fs::{read_dir, File}, + io::{self, Read, Write}, + path::Path, + sync::{Arc, RwLock}, +}; +use swc_common::{Fold, FoldWith}; +use swc_ecma_ast::*; +use swc_ecma_codegen::Emitter; +use swc_ecma_parser::{Parser, Session, SourceFileInput, Syntax}; +use swc_ecma_transforms::fixer; +use test::{test_main, Options, ShouldPanic::No, TestDesc, TestDescAndFn, TestFn, TestName}; + +const IGNORED_PASS_TESTS: &[&str] = &[ + // TODO: uningnore + "5654d4106d7025c2.js", + "431ecef8c85d4d24.js", + // Generated code is better than it from `pass` + "0da4b57d03d33129.js", + "aec65a9745669870.js", + "1c055d256ec34f17.js", + "d57a361bc638f38c.js", + "95520bedf0fdd4c9.js", + "5f1e0eff7ac775ee.js", + "90ad0135b905a622.js", + "7da12349ac9f51f2.js", + "46173461e93df4c2.js", + "446ffc8afda7e47f.js", + "3b5d1fb0e093dab8.js", + "0140c25a4177e5f7.module.js", + "e877f5e6753dc7e4.js", + "aac70baa56299267.js", + // Wrong tests (normalized expected.js is wrong) + "50c6ab935ccb020a.module.js", + "9949a2e1a6844836.module.js", + "1efde9ddd9d6e6ce.module.js", + // Wrong tests (variable name or value is different) + "8386fbff927a9e0e.js", + "0339fa95c78c11bd.js", + "0426f15dac46e92d.js", + "0b4d61559ccce0f9.js", + "0f88c334715d2489.js", + "1093d98f5fc0758d.js", + "15d9592709b947a0.js", + "2179895ec5cc6276.js", + "247a3a57e8176ebd.js", + "441a92357939904a.js", + "47f974d6fc52e3e4.js", + "4e1a0da46ca45afe.js", + "5829d742ab805866.js", + "589dc8ad3b9aa28f.js", + "598a5cedba92154d.js", + "72d79750e81ef03d.js", + "7788d3c1e1247da9.js", + "7b72d7b43bedc895.js", + "7dab6e55461806c9.js", + "82c827ccaecbe22b.js", + "87a9b0d1d80812cc.js", + "8c80f7ee04352eba.js", + "96f5d93be9a54573.js", + "988e362ed9ddcac5.js", + "9bcae7c7f00b4e3c.js", + "a8a03a88237c4e8f.js", + "ad06370e34811a6a.js", + "b0fdc038ee292aba.js", + "b62c6dd890bef675.js", + "cb211fadccb029c7.js", + "ce968fcdf3a1987c.js", + "db3c01738aaf0b92.js", + "e1387fe892984e2b.js", + "e71c1d5f0b6b833c.js", + "e8ea384458526db0.js", + // We don't implement Annex B fully. + "1c1e2a43fe5515b6.js", + "3dabeca76119d501.js", + "52aeec7b8da212a2.js", + "59ae0289778b80cd.js", + "a4d62a651f69d815.js", + "c06df922631aeabc.js", +]; + +fn add_test( + tests: &mut Vec, + name: String, + ignore: bool, + f: F, +) { + tests.push(TestDescAndFn { + desc: TestDesc { + name: TestName::DynTestName(name), + ignore, + should_panic: No, + allow_fail: false, + }, + testfn: TestFn::DynTestFn(box f), + }); +} + +struct MyHandlers; + +impl swc_ecma_codegen::Handlers for MyHandlers {} + +fn error_tests(tests: &mut Vec) -> Result<(), io::Error> { + let dir = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("parser") + .join("tests") + .join("test262-parser"); + + eprintln!("Loading tests from {}", dir.display()); + + let normal = dir.join("pass"); + let explicit = dir.join("pass-explicit"); + + for entry in read_dir(&explicit).expect("failed to read directory") { + let entry = entry?; + + let file_name = entry + .path() + .strip_prefix(&explicit) + .expect("failed to strip prefix") + .to_str() + .expect("to_str() failed") + .to_string(); + + let input = { + let mut buf = String::new(); + File::open(entry.path())?.read_to_string(&mut buf)?; + buf + }; + + let ignore = IGNORED_PASS_TESTS.contains(&&*file_name); + + let module = file_name.contains("module"); + + let name = format!("fixer::{}", file_name); + + add_test(tests, name, ignore, { + let normal = normal.clone(); + move || { + eprintln!( + "\n\n========== Running fixer test {}\nSource:\n{}\n", + file_name, input + ); + let mut wr = Buf(Arc::new(RwLock::new(vec![]))); + let mut wr2 = Buf(Arc::new(RwLock::new(vec![]))); + + ::testing::run_test(false, |cm, handler| { + let src = cm.load_file(&entry.path()).expect("failed to load file"); + let expected = cm + .load_file(&normal.join(file_name)) + .expect("failed to load reference file"); + + let s: Arc = src.src.clone(); + let mut src_map_builder = SourceMapBuilder::new(Some(&s)); + let mut src_map_builder2 = SourceMapBuilder::new(Some(&s)); + { + let handlers = box MyHandlers; + let handlers2 = box MyHandlers; + let mut parser: Parser = Parser::new( + Session { handler: &handler }, + Syntax::default(), + (&*src).into(), + None, + ); + + let mut emitter = Emitter { + cfg: swc_ecma_codegen::Config { minify: false }, + cm: cm.clone(), + wr: box swc_ecma_codegen::text_writer::JsWriter::new( + cm.clone(), + "\n", + &mut wr, + &mut src_map_builder, + ), + comments: None, + handlers, + pos_of_leading_comments: Default::default(), + }; + let mut expected_emitter = Emitter { + cfg: swc_ecma_codegen::Config { minify: false }, + cm: cm.clone(), + wr: box swc_ecma_codegen::text_writer::JsWriter::new( + cm.clone(), + "\n", + &mut wr2, + &mut src_map_builder2, + ), + comments: None, + handlers: handlers2, + pos_of_leading_comments: Default::default(), + }; + + // Parse source + + let mut e_parser: Parser = Parser::new( + Session { handler: &handler }, + Syntax::default(), + (&*expected).into(), + None, + ); + + if module { + let module = parser + .parse_module() + .map(normalize) + .map(|p| p.fold_with(&mut fixer())) + .map_err(|mut e| { + e.emit(); + () + })?; + let module2 = e_parser + .parse_module() + .map(normalize) + .map_err(|mut e| { + e.emit(); + () + }) + .expect("failed to parse reference file"); + if module == module2 { + return Ok(()); + } + emitter.emit_module(&module).unwrap(); + expected_emitter.emit_module(&module2).unwrap(); + } else { + let script = parser + .parse_script() + .map(normalize) + .map(|p| p.fold_with(&mut fixer())) + .map_err(|mut e| { + e.emit(); + () + })?; + let script2 = e_parser + .parse_script() + .map(normalize) + .map(|p| p.fold_with(&mut fixer())) + .map_err(|mut e| { + e.emit(); + () + })?; + + if script == script2 { + return Ok(()); + } + emitter.emit_script(&script).unwrap(); + expected_emitter.emit_script(&script2).unwrap(); + } + } + + let output = String::from_utf8_lossy(&*wr.0.read().unwrap()).to_string(); + let expected = String::from_utf8_lossy(&*wr2.0.read().unwrap()).to_string(); + if output == expected { + return Ok(()); + } + eprintln!("Wrong output:\n{}\n-----\n{}", output, expected); + + Err(()) + }) + .expect("failed to run test"); + } + }); + } + + Ok(()) +} + +#[test] +fn identity() { + let args: Vec<_> = env::args().collect(); + let mut tests = Vec::new(); + error_tests(&mut tests).expect("failed to load testss"); + test_main(&args, tests, Options::new()); +} + +#[derive(Debug, Clone)] +struct Buf(Arc>>); +impl Write for Buf { + fn write(&mut self, data: &[u8]) -> io::Result { + self.0.write().unwrap().write(data) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.write().unwrap().flush() + } +} + +struct Normalizer; +impl Fold for Normalizer { + fn fold(&mut self, stmt: Stmt) -> Stmt { + let stmt = stmt.fold_children(self); + + match stmt { + Stmt::Expr(box Expr::Paren(ParenExpr { expr, .. })) => Stmt::Expr(expr), + _ => stmt, + } + } +} + +impl Fold for Normalizer { + fn fold(&mut self, name: PropName) -> PropName { + let name = name.fold_children(self); + + match name { + PropName::Ident(i) => PropName::Str(Str { + value: i.sym, + span: i.span, + has_escape: false, + }), + PropName::Num(n) => { + let s = if n.value.is_infinite() { + if n.value.is_sign_positive() { + "Infinity".into() + } else { + "-Infinity".into() + } + } else { + format!("{}", n.value) + }; + PropName::Str(Str { + value: s.into(), + span: n.span, + has_escape: false, + }) + } + _ => name, + } + } +} + +impl Fold for Normalizer { + fn fold(&mut self, expr: NewExpr) -> NewExpr { + let mut expr = expr.fold_children(self); + + expr.args = match expr.args { + Some(..) => expr.args, + None => Some(vec![]), + }; + + expr + } +} + +fn normalize(node: T) -> T +where + T: FoldWith + FoldWith<::testing::DropSpan>, +{ + node.fold_with(&mut Normalizer) + .fold_with(&mut ::testing::DropSpan) +}