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] [package]
name = "swc_ecma_parser" name = "swc_ecma_parser"
version = "0.33.1" version = "0.33.2"
authors = ["강동윤 <kdy1997.dev@gmail.com>"] authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT" license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git" repository = "https://github.com/swc-project/swc.git"

View File

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

View File

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

View File

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

View File

@ -30,11 +30,23 @@ mod used_name;
/// ///
/// We use custom helper to handle export defaul class /// We use custom helper to handle export defaul class
pub fn class_properties() -> impl Fold { 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)] #[derive(Clone)]
struct ClassProperties { struct ClassProperties {
typescript: bool,
mark: Mark, mark: Mark,
} }
@ -329,77 +341,135 @@ impl ClassProperties {
); );
} }
let key = match *prop.key { let key = if self.typescript {
Expr::Ident(ref i) if !prop.computed => Lit::Str(Str { // `b` in
span: i.span, //
value: i.sym.clone(), // class A {
has_escape: false, // b = 'foo';
}) // }
.as_arg(), prop.key
Expr::Lit(ref lit) if !prop.computed => lit.clone().as_arg(), } 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 { let (ident, aliased) = if let Expr::Ident(ref i) = *prop.key {
if used_key_names.contains(&i.sym) { if used_key_names.contains(&i.sym) {
(alias_ident_for(&prop.key, "_ref"), true) (alias_ident_for(&prop.key, "_ref"), true)
} else {
alias_if_required(&prop.key, "_ref")
}
} else { } else {
alias_if_required(&prop.key, "_ref") 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 { Box::new(Expr::from(ident))
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,
});
} }
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 { if prop.is_static {
extra_stmts.push( extra_stmts.push(
CallExpr { 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, span: DUMMY_SP,
callee, callee,
args: vec![ args: vec![
ident.clone().as_arg(), ThisExpr { span: DUMMY_SP }.as_arg(),
key, key.as_arg(),
value value.as_arg(),
.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(),
}),
], ],
type_args: Default::default(), 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) => { 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)] #[allow(clippy::vec_box)]
fn process_constructor( fn process_constructor(
&mut self, &mut self,
@ -534,28 +633,32 @@ impl ClassProperties {
) -> Option<Constructor> { ) -> Option<Constructor> {
let constructor = constructor let constructor = constructor
.map(|c| { .map(|c| {
let mut folder = UsedNameRenamer { if self.typescript {
mark: Mark::fresh(Mark::root()), c
used_names, } else {
}; let mut folder = UsedNameRenamer {
mark: Mark::fresh(Mark::root()),
used_names,
};
// Handle collisions like // Handle collisions like
// //
// var foo = "bar"; // var foo = "bar";
// //
// class Foo { // class Foo {
// bar = foo; // bar = foo;
// static bar = baz; // static bar = baz;
// //
// constructor() { // constructor() {
// var foo = "foo"; // var foo = "foo";
// var baz = "baz"; // var baz = "baz";
// } // }
// } // }
let body = c.body.fold_with(&mut folder); let body = c.body.fold_with(&mut folder);
let params = c.params.fold_with(&mut folder); let params = c.params.fold_with(&mut folder);
Constructor { body, params, ..c } Constructor { body, params, ..c }
}
}) })
.or_else(|| { .or_else(|| {
if constructor_exprs.is_empty() { if constructor_exprs.is_empty() {
@ -565,8 +668,19 @@ impl ClassProperties {
} }
}); });
if let Some(c) = constructor { if let Some(mut c) = constructor {
Some(inject_after_super(c, constructor_exprs)) 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 { } else {
None None
} }

View File

@ -1,15 +1,22 @@
#![feature(test)] #![feature(test)]
use swc_common::chain; 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] #[macro_use]
mod common; mod common;
fn tr() -> impl Fold {
strip()
}
macro_rules! to { macro_rules! to {
($name:ident, $from:expr, $to:expr) => { ($name:ident, $from:expr, $to:expr) => {
test!( test!(
::swc_ecma_parser::Syntax::Typescript(Default::default()), ::swc_ecma_parser::Syntax::Typescript(Default::default()),
|_| strip(), |_| tr(),
$name, $name,
$from, $from,
$to, $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(), compat::es2020::optional_chaining(),
self.target < JscTarget::Es2020 self.target < JscTarget::Es2020
), ),
Optional::new( if syntax.typescript() {
compat::es2020::class_properties(), Either::Left(Optional::new(
self.target < JscTarget::Es2020 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::es2018(), self.target <= JscTarget::Es2018),
Optional::new(compat::es2017(), self.target <= JscTarget::Es2017), Optional::new(compat::es2017(), self.target <= JscTarget::Es2017),
Optional::new(compat::es2016(), self.target <= JscTarget::Es2016), Optional::new(compat::es2016(), self.target <= JscTarget::Es2016),