Fix decorators (#899)

swc_ecma_transforms:
 - typescript::strip: Preserve a class property if it has decorators
 - decorators::legacy: Implement parameter decorator.
This commit is contained in:
강동윤 2020-07-26 17:07:24 +09:00 committed by GitHub
parent b72901b5e0
commit 53b09aa356
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 289 additions and 8 deletions

View File

@ -37,11 +37,9 @@ install:
- npm install
- npm install browserslist regenerator
- npm install -g jest
- travis_wait 50 RUST_BACKTRACE=0 cargo test --no-run --color always --all --all-features
script:
# - RUST_BACKTRACE=0 cargo check --color always --all --all-targets
# - RUST_BACKTRACE=full cargo test --color always --all --all-features
- true
before_deploy:

View File

@ -207,7 +207,9 @@ impl Legacy {
definite: false,
});
// Injected to sequence expression which is wrapped with parenthesis.
let mut extra_exprs = vec![];
// Injected to constructor
let mut constructor_stmts = SmallVec::<[_; 8]>::new();
let prototype = MemberExpr {
@ -218,7 +220,10 @@ impl Legacy {
};
c.class.body = c.class.body.move_flat_map(|m| match m {
ClassMember::Method(m) if !m.function.decorators.is_empty() => {
ClassMember::Method(mut m)
if !m.function.decorators.is_empty()
|| m.function.params.iter().any(|p| !p.decorators.is_empty()) =>
{
let prototype = if m.is_static {
cls_ident.clone().as_arg()
} else {
@ -274,8 +279,6 @@ impl Legacy {
dec_exprs.push(Some(i.as_arg()))
}
let callee = helper!(apply_decorated_descriptor, "applyDecoratedDescriptor");
let name = match m.key {
PropName::Computed(..) => {
unimplemented!("decorators on methods with computed key")
@ -283,6 +286,38 @@ impl Legacy {
_ => prop_name_to_expr_value(m.key.clone()),
};
{
// https://github.com/swc-project/swc/issues/863
let mut new_params = Vec::with_capacity(m.function.params.len());
for (index, param) in m.function.params.into_iter().enumerate() {
for dec in param.decorators {
//
extra_exprs.push(Box::new(Expr::Call(CallExpr {
span: dec.span,
callee: dec.expr.as_callee(),
args: vec![
prototype.clone(),
name.clone().as_arg(),
Lit::Num(Number {
span: param.span,
value: index as _,
})
.as_arg(),
],
type_args: None,
})))
}
new_params.push(Param {
decorators: Default::default(),
..param
});
}
m.function.params = new_params;
}
let callee = helper!(apply_decorated_descriptor, "applyDecoratedDescriptor");
extra_exprs.extend(dec_inits);
extra_exprs.push(Box::new(Expr::Call(CallExpr {
@ -646,6 +681,7 @@ impl Legacy {
expr
}
/// Apply class decorators.
fn apply(&mut self, mut expr: Box<Expr>, decorators: Vec<Decorator>) -> Box<Expr> {
for dec in decorators.into_iter().rev() {
let (i, aliased) = alias_if_required(&dec.expr, "_dec");

View File

@ -722,8 +722,12 @@ impl Fold for Strip {
| ClassMember::Method(ClassMethod {
function: Function { body: None, .. },
..
})
| ClassMember::ClassProp(ClassProp { value: None, .. }) => None,
}) => None,
ClassMember::ClassProp(ClassProp {
value: None,
ref decorators,
..
}) if decorators.is_empty() => None,
_ => Some(member),
})

View File

@ -5,12 +5,20 @@ use swc_ecma_transforms::{
compat::{es2015::classes::Classes, es2020::class_properties},
proposals::{decorators, decorators::Config},
resolver, typescript,
typescript::strip,
};
use swc_ecma_visit::Fold;
#[macro_use]
mod common;
fn ts() -> Syntax {
Syntax::Typescript(TsConfig {
decorators: true,
..Default::default()
})
}
fn syntax(decorators_before_export: bool) -> Syntax {
Syntax::Es(EsConfig {
decorators_before_export,
@ -24,9 +32,20 @@ fn tr() -> impl Fold {
chain!(decorators(Default::default()), class_properties(),)
}
fn ts_transform() -> impl Fold {
chain!(
strip(),
decorators(Config {
legacy: true,
..Default::default()
}),
class_properties(),
)
}
/// Folder for `transformation_*` tests
fn transformation() -> impl Fold {
chain!(decorators(Default::default()), class_properties(),)
chain!(strip(), decorators(Default::default()), class_properties(),)
}
// transformation_declaration
@ -4329,3 +4348,227 @@ let Person = ((_class = function() {
const p = new Person();
p.save();"
);
test!(
ts(),
|_| ts_transform(),
issue_862_1,
"
@Entity()
export class Product extends TimestampedEntity {
@PrimaryGeneratedColumn('uuid')
public id!: string;
@Column()
public price!: number;
@Column({ enum: ProductType })
public type!: ProductType;
@Column()
public productEntityId!: string;
/* ANCHOR: Relations ------------------------------------------------------ */
@OneToMany(() => Order, (order) => order.product)
public orders!: Order[];
@OneToMany(() => Discount, (discount) => discount.product)
public discounts!: Discount[];
}
",
"var _class, _descriptor, _descriptor1, _descriptor2, _descriptor3, _descriptor4, \
_descriptor5;
var _dec = PrimaryGeneratedColumn('uuid'), _dec1 = Column(), _dec2 = Column({
enum: ProductType
}), _dec3 = Column(), _dec4 = OneToMany(()=>Order
, (order)=>order.product
), _dec5 = OneToMany(()=>Discount
, (discount)=>discount.product
), _dec6 = Entity();
export let Product = _dec6(((_class = function() {
class Product extends TimestampedEntity {
constructor(...args){
super(...args);
_initializerDefineProperty(this, 'id', _descriptor, this);
_initializerDefineProperty(this, 'price', _descriptor1, this);
_initializerDefineProperty(this, 'type', _descriptor2, this);
_initializerDefineProperty(this, 'productEntityId', _descriptor3, this);
_initializerDefineProperty(this, 'orders', _descriptor4, this);
_initializerDefineProperty(this, 'discounts', _descriptor5, this);
}
}
return Product;
}()) || _class, _descriptor = _applyDecoratedDescriptor(_class.prototype, 'id', [
_dec
], {
configurable: true,
enumerable: true,
writable: true,
initializer: function() {
return;
}
}), _descriptor1 = _applyDecoratedDescriptor(_class.prototype, 'price', [
_dec1
], {
configurable: true,
enumerable: true,
writable: true,
initializer: function() {
return;
}
}), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, 'type', [
_dec2
], {
configurable: true,
enumerable: true,
writable: true,
initializer: function() {
return;
}
}), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, 'productEntityId', [
_dec3
], {
configurable: true,
enumerable: true,
writable: true,
initializer: function() {
return;
}
}), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, 'orders', [
_dec4
], {
configurable: true,
enumerable: true,
writable: true,
initializer: function() {
return;
}
}), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, 'discounts', [
_dec5
], {
configurable: true,
enumerable: true,
writable: true,
initializer: function() {
return;
}
}), _class));
"
);
test!(
ts(),
|_| ts_transform(),
issue_862_2,
"@Entity()
export class Product extends TimestampedEntity {
@PrimaryGeneratedColumn('uuid')
public id!: string;
}
",
"var _class, _descriptor;
var _dec = PrimaryGeneratedColumn('uuid'), _dec1 = Entity();
export let Product = _dec1(((_class = function() {
class Product extends TimestampedEntity {
constructor(...args){
super(...args);
_initializerDefineProperty(this, 'id', _descriptor, this);
}
}
return Product;
}()) || _class, _descriptor = _applyDecoratedDescriptor(_class.prototype, 'id', [
_dec
], {
configurable: true,
enumerable: true,
writable: true,
initializer: function() {
return;
}
}), _class));
"
);
test_exec!(
ts(),
|_| ts_transform(),
issue_862_3,
"var log: number[] = [];
function push(x: number) { log.push(x); return x; }
function saveOrder(x: number) {
return function (el: any) {
log.push(x);
return el;
};
}
@saveOrder(1)
class Product {
@saveOrder(0)
public id!: string;
}
var nums = Array.from({ length: 2 }, (_, i) => i);
expect(log).toEqual(nums)"
);
test!(
ts(),
|_| ts_transform(),
issue_863_1,
"class ProductController {
@bar()
findById(
@foo()
id: number
) {
// ...
}
}",
" var _class, _dec;
let ProductController = ((_class = function() {
class ProductController {
findById(id) {
}
}
return ProductController;
}()) || _class, foo()(_class.prototype, 'findById', 0), _dec = bar(), \
_applyDecoratedDescriptor(_class.prototype, 'findById', [
_dec
], Object.getOwnPropertyDescriptor(_class.prototype, 'findById'), _class.prototype), _class);"
);
test_exec!(
ts(),
|_| ts_transform(),
issue_863_2,
"const logs: number[] = [];
function foo() {
return function (target: any, member: any, ix: any) {
logs.push(0);
};
}
function bar() {
return function (target: any, member: any, ix: any) {
logs.push(1);
};
}
class ProductController {
findById(
@foo()
@bar()
id: number
) {
// ...
}
}
expect(logs).toEqual([0, 1])
const c = new ProductController();
c.findById(100);
"
);