fix(es/transforms): Fix bugs (#2181)

swc_ecma_transforms_compat:
 - `regenerator`: Fix variable hoisting for for-in/of loops. (#2164)
 - Ensure #2071 is fixed. (#2071)
This commit is contained in:
강동윤 2021-08-30 13:08:24 +09:00 committed by GitHub
parent b2c99719fd
commit ee16139a19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 261 additions and 28 deletions

View File

@ -5,7 +5,7 @@ use swc_ecma_ast::*;
use swc_ecma_utils::{find_ids, private_ident};
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
pub(super) type Vars = SmallVec<[Ident; 32]>;
pub(super) type Vars = SmallVec<[Ident; 12]>;
pub(super) fn hoist<T>(node: T) -> (T, Hoister)
where
@ -138,7 +138,10 @@ impl Fold for Hoister {
VarDeclOrPat::VarDecl(mut var) => {
if var.decls.len() == 1 && var.decls[0].init.is_none() {
return var.decls.remove(0).name.into();
let pat = var.decls.remove(0).name;
self.vars.extend(find_ids(&pat));
return pat.into();
}
var.into()
@ -154,4 +157,20 @@ impl Fold for Hoister {
_ => var.fold_children_with(self),
}
}
fn fold_opt_var_decl_or_expr(&mut self, v: Option<VarDeclOrExpr>) -> Option<VarDeclOrExpr> {
let v = v.fold_children_with(self);
match &v {
Some(VarDeclOrExpr::Expr(e)) => {
if e.is_invalid() {
return None;
}
}
_ => {}
}
v
}
}

View File

@ -16,7 +16,6 @@ pub fn regenerator(global_mark: Mark) -> impl Fold {
Regenerator {
global_mark,
regenerator_runtime: Default::default(),
outer_fn_vars: Default::default(),
top_level_vars: Default::default(),
}
}
@ -26,8 +25,6 @@ struct Regenerator {
global_mark: Mark,
/// [Some] if used.
regenerator_runtime: Option<Ident>,
/// Variables declared in outer function.
outer_fn_vars: Vec<VarDeclarator>,
/// mark
top_level_vars: Vec<VarDeclarator>,
}
@ -322,28 +319,27 @@ impl Regenerator {
f.body = f.body.fold_with(&mut FnSentVisitor { ctx: ctx.clone() });
let uses_this = contains_this_expr(&f.body);
let (body, hoister) = hoist(f.body.unwrap());
self.outer_fn_vars
.extend(hoister.vars.into_iter().map(|id| VarDeclarator {
let mut outer_fn_vars = vec![];
outer_fn_vars.extend(hoister.vars.into_iter().map(|id| VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(id.into()),
init: None,
definite: false,
}));
outer_fn_vars.extend(hoister.arguments.into_iter().map(|id| {
VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(id.into()),
init: None,
name: Pat::Ident(id.clone().into()),
init: Some(Box::new(
Ident {
sym: js_word!("arguments"),
..id
}
.into(),
)),
definite: false,
}));
self.outer_fn_vars
.extend(hoister.arguments.into_iter().map(|id| {
VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(id.clone().into()),
init: Some(Box::new(
Ident {
sym: js_word!("arguments"),
..id
}
.into(),
)),
definite: false,
}
}));
}
}));
handler.explode_stmts(body.stmts);
@ -419,11 +415,11 @@ impl Regenerator {
span: body_span,
stmts: {
let mut buf = vec![];
if !self.outer_fn_vars.is_empty() {
if !outer_fn_vars.is_empty() {
buf.push(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
decls: take(&mut self.outer_fn_vars),
decls: outer_fn_vars,
declare: false,
})));
}

View File

@ -1,3 +1,5 @@
use std::{fs::read_to_string, path::PathBuf};
use swc_common::{chain, Mark};
use swc_ecma_parser::Syntax;
use swc_ecma_transforms_base::resolver::resolver;
@ -7,7 +9,7 @@ use swc_ecma_transforms_compat::{
es2016, es2017, es2018, es2020,
es2020::class_properties,
};
use swc_ecma_transforms_testing::{test, test_exec, Tester};
use swc_ecma_transforms_testing::{compare_stdout, test, test_exec, Tester};
use swc_ecma_visit::Fold;
fn syntax() -> Syntax {
@ -6531,3 +6533,13 @@ var Extended = function(Base) {
}(Base);
"
);
#[testing::fixture("tests/fixture/classes/**/exec.js")]
fn exec(input: PathBuf) {
let src = read_to_string(&input).unwrap();
compare_stdout(
Default::default(),
|t| classes(Some(t.comments.clone())),
&src,
);
}

View File

@ -0,0 +1,17 @@
const sleep = (ms) => new Promise((resolve, reject) => { setTimeout(() => resolve(), ms) })
class Project {
constructor(name) {
this.name = name
}
};
Project.f = async function () {
await sleep(100)
console.log(this['a'])
return new this('a')
}
Project.f().then((project) => {
console.log(project.name)
})

View File

@ -0,0 +1,16 @@
const sleep = (ms) => new Promise((resolve, reject) => { setTimeout(() => resolve(), ms) })
class Project {
constructor(name) {
this.name = name
}
static async g() {
await sleep(100)
console.log(this['a'])
return new this('a')
}
};
Project.g().then((project) => {
console.log(project.name)
})

View File

@ -0,0 +1,5 @@
{
"jsc": {
"target": "es2015"
}
}

View File

@ -0,0 +1,3 @@
async function fn() {
for (const key in {});
}

View File

@ -0,0 +1,49 @@
import regeneratorRuntime from "regenerator-runtime";
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _fn() {
_fn = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
var key;
return regeneratorRuntime.wrap(function _callee$(_ctx) {
while(1)switch(_ctx.prev = _ctx.next){
case 0:
for(key in {
});
case 1:
case "end":
return _ctx.stop();
}
}, _callee);
}));
return _fn.apply(this, arguments);
}
function fn() {
return _fn.apply(this, arguments);
}

View File

@ -0,0 +1,5 @@
{
"jsc": {
"target": "es2016"
}
}

View File

@ -0,0 +1,3 @@
async function fn() {
for (const key in {});
}

View File

@ -0,0 +1,39 @@
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _fn() {
_fn = _asyncToGenerator(function*() {
for(const key in {
});
});
return _fn.apply(this, arguments);
}
function fn() {
return _fn.apply(this, arguments);
}

View File

@ -0,0 +1,5 @@
{
"jsc": {
"target": "es2018"
}
}

View File

@ -0,0 +1,3 @@
async function fn() {
for (const key in {});
}

View File

@ -0,0 +1,4 @@
async function fn() {
for(const key in {
});
}

View File

@ -0,0 +1,5 @@
{
"jsc": {
"target": "es5"
}
}

View File

@ -0,0 +1,3 @@
async function fn() {
for (const key in {});
}

View File

@ -0,0 +1,49 @@
import regeneratorRuntime from "regenerator-runtime";
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _fn() {
_fn = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
var key;
return regeneratorRuntime.wrap(function _callee$(_ctx) {
while(1)switch(_ctx.prev = _ctx.next){
case 0:
for(key in {
});
case 1:
case "end":
return _ctx.stop();
}
}, _callee);
}));
return _fn.apply(this, arguments);
}
function fn() {
return _fn.apply(this, arguments);
}