Perfect fixer (#212)

swc_ecma_transforms:
 - test fixer using test262
 - make fixer perfect
This commit is contained in:
강동윤 2019-02-14 21:12:05 +09:00 committed by GitHub
parent b76c4f26e6
commit 9bd7a9c484
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 526 additions and 56 deletions

View File

@ -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<Stmt> 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<Expr> 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<Expr> 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::<Vec<_>>();
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<Expr> 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<Expr> for Fixer {
}
}
fn ignore_return_value(expr: Box<Expr>) -> Option<Box<Expr>> {
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");
}

View File

@ -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<F: FnOnce() + Send + 'static>(
tests: &mut Vec<TestDescAndFn>,
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<TestDescAndFn>) -> 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<String> = 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<SourceFileInput> = 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<SourceFileInput> = 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<RwLock<Vec<u8>>>);
impl Write for Buf {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
self.0.write().unwrap().write(data)
}
fn flush(&mut self) -> io::Result<()> {
self.0.write().unwrap().flush()
}
}
struct Normalizer;
impl Fold<Stmt> 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<PropName> 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<NewExpr> 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<T>(node: T) -> T
where
T: FoldWith<Normalizer> + FoldWith<::testing::DropSpan>,
{
node.fold_with(&mut Normalizer)
.fold_with(&mut ::testing::DropSpan)
}