swc_ecma_parser:
 - Allow `in` in class properties (#944)
 - Make `delete` with optional chaining valid (#947)

swc_ecma_transforms:
 - Add a `typescript_class_properties` pass (#930)
This commit is contained in:
강동윤 2020-08-09 16:45:00 +09:00 committed by GitHub
parent 26f49099aa
commit 1315d58059
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 516 additions and 88 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "swc_ecma_parser"
version = "0.33.1"
version = "0.33.2"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"

View File

@ -364,6 +364,7 @@ impl Error {
Eof => "Unexpected eof".into(),
TS2703 => "The operand of a delete operator must be a property reference.".into(),
// TODO:
_ => format!("{:?}", kind).into(),
};

View File

@ -750,6 +750,7 @@ impl<'a, I: Tokens> Parser<I> {
let ctx = Context {
in_class_prop: true,
in_method: false,
include_in_expr: true,
..self.ctx()
};
self.with_ctx(ctx).parse_with(|p| {

View File

@ -284,8 +284,13 @@ impl<'a, I: Tokens> Parser<I> {
_ => e,
}
}
match *arg {
match &*arg {
Expr::Member(..) => {}
Expr::OptChain(e)
if match &*e.expr {
Expr::Member(..) => true,
_ => false,
} => {}
_ => self.emit_err(unwrap_paren(&arg).span(), SyntaxError::TS2703),
}
}

View File

@ -0,0 +1,3 @@
class CTest {
myFunc = () => "key" in {};
}

View File

@ -0,0 +1,109 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 47,
"ctxt": 0
},
"body": [
{
"type": "ClassDeclaration",
"identifier": {
"type": "Identifier",
"span": {
"start": 6,
"end": 11,
"ctxt": 0
},
"value": "CTest",
"typeAnnotation": null,
"optional": false
},
"declare": false,
"span": {
"start": 0,
"end": 47,
"ctxt": 0
},
"decorators": [],
"body": [
{
"type": "ClassProperty",
"span": {
"start": 18,
"end": 45,
"ctxt": 0
},
"key": {
"type": "Identifier",
"span": {
"start": 18,
"end": 24,
"ctxt": 0
},
"value": "myFunc",
"typeAnnotation": null,
"optional": false
},
"value": {
"type": "ArrowFunctionExpression",
"span": {
"start": 27,
"end": 44,
"ctxt": 0
},
"params": [],
"body": {
"type": "BinaryExpression",
"span": {
"start": 33,
"end": 44,
"ctxt": 0
},
"operator": "in",
"left": {
"type": "StringLiteral",
"span": {
"start": 33,
"end": 38,
"ctxt": 0
},
"value": "key",
"hasEscape": false
},
"right": {
"type": "ObjectExpression",
"span": {
"start": 42,
"end": 44,
"ctxt": 0
},
"properties": []
}
},
"async": false,
"generator": false,
"typeParameters": null,
"returnType": null
},
"typeAnnotation": null,
"isStatic": false,
"decorators": [],
"computed": false,
"accessibility": null,
"isAbstract": false,
"isOptional": false,
"readonly": false,
"declare": false,
"definite": false
}
],
"superClass": null,
"isAbstract": false,
"typeParams": null,
"superTypeParams": null,
"implements": []
}
],
"interpreter": null
}

View File

@ -0,0 +1 @@
delete obj?.myProp;

View File

@ -0,0 +1,72 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 19,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 19,
"ctxt": 0
},
"expression": {
"type": "UnaryExpression",
"span": {
"start": 0,
"end": 18,
"ctxt": 0
},
"operator": "delete",
"argument": {
"type": "OptionalChainingExpression",
"span": {
"start": 7,
"end": 18,
"ctxt": 0
},
"questionDotToken": {
"start": 10,
"end": 11,
"ctxt": 0
},
"expr": {
"type": "MemberExpression",
"span": {
"start": 7,
"end": 18,
"ctxt": 0
},
"object": {
"type": "Identifier",
"span": {
"start": 7,
"end": 10,
"ctxt": 0
},
"value": "obj",
"typeAnnotation": null,
"optional": false
},
"property": {
"type": "Identifier",
"span": {
"start": 12,
"end": 18,
"ctxt": 0
},
"value": "myProp",
"typeAnnotation": null,
"optional": false
},
"computed": false
}
}
}
}
],
"interpreter": null
}

View File

@ -0,0 +1,73 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 30,
"ctxt": 0
},
"body": [
{
"type": "TryStatement",
"span": {
"start": 0,
"end": 30,
"ctxt": 0
},
"block": {
"type": "BlockStatement",
"span": {
"start": 4,
"end": 7,
"ctxt": 0
},
"stmts": []
},
"handler": {
"type": "CatchClause",
"span": {
"start": 8,
"end": 30,
"ctxt": 0
},
"param": {
"type": "Identifier",
"span": {
"start": 15,
"end": 16,
"ctxt": 0
},
"value": "e",
"typeAnnotation": {
"type": "TsTypeAnnotation",
"span": {
"start": 16,
"end": 25,
"ctxt": 0
},
"typeAnnotation": {
"type": "TsKeywordType",
"span": {
"start": 18,
"end": 25,
"ctxt": 0
},
"kind": "unknown"
}
},
"optional": false
},
"body": {
"type": "BlockStatement",
"span": {
"start": 27,
"end": 30,
"ctxt": 0
},
"stmts": []
}
},
"finalizer": null
}
],
"interpreter": null
}

View File

@ -1,5 +1,6 @@
pub use self::{
class_properties::class_properties, nullish_coalescing::nullish_coalescing,
class_properties::{class_properties, typescript_class_properties},
nullish_coalescing::nullish_coalescing,
opt_chaining::optional_chaining,
};

View File

@ -30,11 +30,23 @@ mod used_name;
///
/// We use custom helper to handle export defaul class
pub fn class_properties() -> impl Fold {
ClassProperties { mark: Mark::root() }
ClassProperties {
typescript: false,
mark: Mark::root(),
}
}
/// Class properties pass for the typescript.
pub fn typescript_class_properties() -> impl Fold {
ClassProperties {
typescript: true,
mark: Mark::root(),
}
}
#[derive(Clone)]
struct ClassProperties {
typescript: bool,
mark: Mark,
}
@ -329,77 +341,135 @@ impl ClassProperties {
);
}
let key = match *prop.key {
Expr::Ident(ref i) if !prop.computed => Lit::Str(Str {
span: i.span,
value: i.sym.clone(),
has_escape: false,
})
.as_arg(),
Expr::Lit(ref lit) if !prop.computed => lit.clone().as_arg(),
let key = if self.typescript {
// `b` in
//
// class A {
// b = 'foo';
// }
prop.key
} else {
match *prop.key {
Expr::Ident(ref i) if !prop.computed => {
Box::new(Expr::from(Lit::Str(Str {
span: i.span,
value: i.sym.clone(),
has_escape: false,
})))
}
Expr::Lit(ref lit) if !prop.computed => {
Box::new(Expr::from(lit.clone()))
}
_ => {
let (ident, aliased) = if let Expr::Ident(ref i) = *prop.key {
if used_key_names.contains(&i.sym) {
(alias_ident_for(&prop.key, "_ref"), true)
_ => {
let (ident, aliased) = if let Expr::Ident(ref i) = *prop.key {
if used_key_names.contains(&i.sym) {
(alias_ident_for(&prop.key, "_ref"), true)
} else {
alias_if_required(&prop.key, "_ref")
}
} else {
alias_if_required(&prop.key, "_ref")
};
// ident.span = ident.span.apply_mark(Mark::fresh(Mark::root()));
if aliased {
// Handle computed property
vars.push(VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(ident.clone()),
init: Some(prop.key),
definite: false,
});
}
} else {
alias_if_required(&prop.key, "_ref")
};
// ident.span = ident.span.apply_mark(Mark::fresh(Mark::root()));
if aliased {
// Handle computed property
vars.push(VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(ident.clone()),
init: Some(prop.key),
definite: false,
});
Box::new(Expr::from(ident))
}
ident.as_arg()
}
};
let value = prop.value.unwrap_or_else(|| undefined(prop_span)).as_arg();
let value = prop.value.unwrap_or_else(|| undefined(prop_span));
let value = if prop.is_static {
value
.fold_with(&mut SuperFieldAccessFolder {
class_name: &ident,
vars: &mut vars,
constructor_this_mark: None,
is_static: true,
folding_constructor: false,
in_injected_define_property_call: false,
in_nested_scope: false,
this_alias_mark: None,
})
.fold_with(&mut ThisInStaticFolder {
ident: ident.clone(),
})
} else {
value
};
let callee = helper!(define_property, "defineProperty");
if self.typescript {
if prop.is_static {
extra_stmts.push(
AssignExpr {
span: DUMMY_SP,
left: PatOrExpr::Expr(Box::new(
MemberExpr {
span: DUMMY_SP,
obj: ident.clone().as_obj(),
computed: false,
prop: key,
}
.into(),
)),
op: op!("="),
right: value,
}
.into_stmt(),
);
} else {
constructor_exprs.push(Box::new(Expr::Assign(AssignExpr {
span: DUMMY_SP,
left: (PatOrExpr::Expr(Box::new(
MemberExpr {
span: DUMMY_SP,
obj: ThisExpr { span: DUMMY_SP }.as_obj(),
computed: false,
prop: key,
}
.into(),
))),
op: op!("="),
right: value,
})));
}
} else {
let callee = helper!(define_property, "defineProperty");
if prop.is_static {
extra_stmts.push(
CallExpr {
if prop.is_static {
extra_stmts.push(
CallExpr {
span: DUMMY_SP,
callee,
args: vec![
ident.clone().as_arg(),
key.as_arg(),
value.as_arg(),
],
type_args: Default::default(),
}
.into_stmt(),
)
} else {
constructor_exprs.push(Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee,
args: vec![
ident.clone().as_arg(),
key,
value
.fold_with(&mut SuperFieldAccessFolder {
class_name: &ident,
vars: &mut vars,
constructor_this_mark: None,
is_static: true,
folding_constructor: false,
in_injected_define_property_call: false,
in_nested_scope: false,
this_alias_mark: None,
})
.fold_with(&mut ThisInStaticFolder {
ident: ident.clone(),
}),
ThisExpr { span: DUMMY_SP }.as_arg(),
key.as_arg(),
value.as_arg(),
],
type_args: Default::default(),
}
.into_stmt(),
)
} else {
constructor_exprs.push(Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee,
args: vec![ThisExpr { span: DUMMY_SP }.as_arg(), key, value],
type_args: Default::default(),
})));
})));
}
}
}
ClassMember::PrivateProp(prop) => {
@ -524,6 +594,35 @@ impl ClassProperties {
)
}
/// # Legacy support.
///
/// ## Why is this required?
///
/// Hygiene data of
///
///```ts
/// class A {
/// b = this.a;
/// constructor(a){
/// this.a = a;
/// }
/// }
/// ```
///
/// is
///
///```ts
/// class A0 {
/// constructor(a1){
/// this.a0 = a0;
/// this.b0 = this.a0;
/// }
/// }
/// ```
///
/// which is valid only for es2020 properties.
///
/// Legacy proposal which is used by typescript requires different hygiene.
#[allow(clippy::vec_box)]
fn process_constructor(
&mut self,
@ -534,28 +633,32 @@ impl ClassProperties {
) -> Option<Constructor> {
let constructor = constructor
.map(|c| {
let mut folder = UsedNameRenamer {
mark: Mark::fresh(Mark::root()),
used_names,
};
if self.typescript {
c
} else {
let mut folder = UsedNameRenamer {
mark: Mark::fresh(Mark::root()),
used_names,
};
// Handle collisions like
//
// var foo = "bar";
//
// class Foo {
// bar = foo;
// static bar = baz;
//
// constructor() {
// var foo = "foo";
// var baz = "baz";
// }
// }
let body = c.body.fold_with(&mut folder);
// Handle collisions like
//
// var foo = "bar";
//
// class Foo {
// bar = foo;
// static bar = baz;
//
// constructor() {
// var foo = "foo";
// var baz = "baz";
// }
// }
let body = c.body.fold_with(&mut folder);
let params = c.params.fold_with(&mut folder);
Constructor { body, params, ..c }
let params = c.params.fold_with(&mut folder);
Constructor { body, params, ..c }
}
})
.or_else(|| {
if constructor_exprs.is_empty() {
@ -565,8 +668,19 @@ impl ClassProperties {
}
});
if let Some(c) = constructor {
Some(inject_after_super(c, constructor_exprs))
if let Some(mut c) = constructor {
if self.typescript {
// Append properties
c.body
.as_mut()
.unwrap()
.stmts
.extend(constructor_exprs.into_iter().map(|v| v.into_stmt()));
Some(c)
} else {
// Prepend properties
Some(inject_after_super(c, constructor_exprs))
}
} else {
None
}

View File

@ -1,15 +1,22 @@
#![feature(test)]
use swc_common::chain;
use swc_ecma_transforms::{resolver, typescript::strip};
use swc_ecma_transforms::{
compat::es2020::typescript_class_properties, resolver, typescript::strip,
};
use swc_ecma_visit::Fold;
#[macro_use]
mod common;
fn tr() -> impl Fold {
strip()
}
macro_rules! to {
($name:ident, $from:expr, $to:expr) => {
test!(
::swc_ecma_parser::Syntax::Typescript(Default::default()),
|_| strip(),
|_| tr(),
$name,
$from,
$to,
@ -695,3 +702,37 @@ to!(
}
}"
);
test!(
::swc_ecma_parser::Syntax::Typescript(Default::default()),
|_| chain!(tr(), typescript_class_properties()),
issue_930_instance,
"class A {
b = this.a;
constructor(a){
this.a = a;
}
}",
"class A {
constructor(a) {
this.a = a;
this.b = this.a;
}
}"
);
test!(
::swc_ecma_parser::Syntax::Typescript(Default::default()),
|_| chain!(tr(), typescript_class_properties()),
issue_930_static,
"class A {
static b = 'foo';
constructor(a){
}
}",
"class A {
constructor(a) {
}
}
A.b = 'foo';"
);

View File

@ -144,10 +144,17 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
compat::es2020::optional_chaining(),
self.target < JscTarget::Es2020
),
Optional::new(
compat::es2020::class_properties(),
self.target < JscTarget::Es2020
),
if syntax.typescript() {
Either::Left(Optional::new(
compat::es2020::typescript_class_properties(),
self.target < JscTarget::Es2020,
))
} else {
Either::Right(Optional::new(
compat::es2020::class_properties(),
self.target < JscTarget::Es2020,
))
},
Optional::new(compat::es2018(), self.target <= JscTarget::Es2018),
Optional::new(compat::es2017(), self.target <= JscTarget::Es2017),
Optional::new(compat::es2016(), self.target <= JscTarget::Es2016),