Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(es/decorators): Fix bugs of 2022-03 implementation #9145

Merged
merged 4 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 14 additions & 28 deletions crates/swc/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use swc_ecma_minifier::option::terser::TerserTopLevelOptions;
#[allow(deprecated)]
pub use swc_ecma_parser::JscTarget;
use swc_ecma_parser::{parse_file_as_expr, Syntax, TsSyntax};
pub use swc_ecma_transforms::proposals::DecoratorVersion;
use swc_ecma_transforms::{
feature::FeatureFlag,
hygiene, modules,
Expand Down Expand Up @@ -676,6 +677,18 @@ impl Options {
{
Box::new(plugin_transforms)
} else {
let decorator_pass: Box<dyn Fold> =
match transform.decorator_version.unwrap_or_default() {
DecoratorVersion::V202112 => Box::new(decorators(decorators::Config {
legacy: transform.legacy_decorator.into_bool(),
emit_metadata: transform.decorator_metadata.into_bool(),
use_define_for_class_fields: !assumptions.set_public_class_fields,
})),
DecoratorVersion::V202203 => Box::new(
swc_ecma_transforms::proposals::decorator_2022_03::decorator_2022_03(),
),
};

Box::new(chain!(
lint_to_fold(swc_ecma_lints::rules::all(LintParams {
program: &program,
Expand All @@ -686,23 +699,7 @@ impl Options {
source_map: cm.clone(),
})),
// Decorators may use type information
Optional::new(
match transform.decorator_version.unwrap_or_default() {
DecoratorVersion::V202112 => {
Either::Left(decorators(decorators::Config {
legacy: transform.legacy_decorator.into_bool(),
emit_metadata: transform.decorator_metadata.into_bool(),
use_define_for_class_fields: !assumptions.set_public_class_fields,
}))
}
DecoratorVersion::V202203 => {
Either::Right(
swc_ecma_transforms::proposals::decorator_2022_03::decorator_2022_03(),
)
}
},
syntax.decorators()
),
Optional::new(decorator_pass, syntax.decorators()),
Optional::new(
explicit_resource_management(),
syntax.explicit_resource_management()
Expand Down Expand Up @@ -1446,17 +1443,6 @@ pub struct TransformConfig {
pub decorator_version: Option<DecoratorVersion>,
}

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub enum DecoratorVersion {
#[default]
#[serde(rename = "2021-12")]
V202112,

#[serde(rename = "2022-03")]
V202203,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct HiddenTransformConfig {
Expand Down
43 changes: 35 additions & 8 deletions crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ use swc_ecma_utils::{
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};

pub fn decorator_2022_03() -> impl VisitMut + Fold {
as_folder(Decorator202203::default())
as_folder(Decorator2022_03::default())
}

#[derive(Default)]
struct Decorator202203 {
struct Decorator2022_03 {
/// Variables without initializer.
extra_vars: Vec<VarDeclarator>,

Expand All @@ -35,6 +35,8 @@ struct Decorator202203 {

#[derive(Default)]
struct ClassState {
private_id_index: u32,

static_lhs: Vec<Ident>,
proto_lhs: Vec<Ident>,

Expand All @@ -55,7 +57,7 @@ struct ClassState {
super_class: Option<Ident>,
}

impl Decorator202203 {
impl Decorator2022_03 {
fn preserve_side_effect_of_decorators(
&mut self,
decorators: Vec<Decorator>,
Expand Down Expand Up @@ -463,6 +465,7 @@ impl Decorator202203 {
let has_static_member = body.iter().any(|m| match m {
ClassMember::Method(m) => m.is_static,
ClassMember::PrivateMethod(m) => m.is_static,
ClassMember::AutoAccessor(m) => m.is_static,
ClassMember::ClassProp(ClassProp { is_static, .. })
| ClassMember::PrivateProp(PrivateProp { is_static, .. }) => *is_static,
ClassMember::StaticBlock(_) => true,
Expand Down Expand Up @@ -527,7 +530,9 @@ impl Decorator202203 {

for m in body.iter_mut() {
match m {
ClassMember::ClassProp(..) | ClassMember::PrivateProp(..) => {
ClassMember::ClassProp(..)
| ClassMember::PrivateProp(..)
| ClassMember::AutoAccessor(..) => {
replace_ident(m, c.ident.to_id(), &new_class_name);
}

Expand Down Expand Up @@ -568,6 +573,13 @@ impl Decorator202203 {
p.is_static = false;
}
}

ClassMember::AutoAccessor(p) => {
if p.is_static {
should_move = true;
p.is_static = false;
}
}
_ => (),
}

Expand Down Expand Up @@ -753,7 +765,7 @@ impl Decorator202203 {
}
}

impl VisitMut for Decorator202203 {
impl VisitMut for Decorator2022_03 {
noop_visit_mut_type!();

fn visit_mut_class(&mut self, n: &mut Class) {
Expand Down Expand Up @@ -932,6 +944,8 @@ impl VisitMut for Decorator202203 {
for mut m in members.take() {
match m {
ClassMember::AutoAccessor(mut accessor) => {
accessor.value.visit_mut_with(self);

let name;
let init;
let field_name_like: JsWord;
Expand All @@ -947,9 +961,14 @@ impl VisitMut for Decorator202203 {
init = private_ident!(format!("_init_{}", k.id.sym));
field_name_like = format!("__{}", k.id.sym).into();

self.state.private_id_index += 1;
PrivateName {
span: k.span,
id: Ident::new(format!("__{}", k.id.sym).into(), k.id.span),
id: Ident::new(
format!("__{}_{}", k.id.sym, self.state.private_id_index)
.into(),
k.id.span,
),
}
}
Key::Public(k) => {
Expand All @@ -958,10 +977,16 @@ impl VisitMut for Decorator202203 {
.replacen("init", "private", 1)
.into();

self.state.private_id_index += 1;

PrivateName {
span: init.span.with_ctxt(SyntaxContext::empty()),
id: Ident::new(
field_name_like.clone(),
format!(
"{field_name_like}_{}",
self.state.private_id_index
)
.into(),
init.span.with_ctxt(SyntaxContext::empty()),
),
}
Expand Down Expand Up @@ -1310,7 +1335,9 @@ impl VisitMut for Decorator202203 {

for mut m in new.take() {
match m {
ClassMember::Method(..) | ClassMember::PrivateMethod(..) => {}
ClassMember::Method(..)
| ClassMember::PrivateMethod(..)
| ClassMember::AutoAccessor(..) => {}

_ => {
if !m.span().is_dummy() {
Expand Down
13 changes: 13 additions & 0 deletions crates/swc_ecma_transforms_proposal/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
#![deny(clippy::all)]
#![allow(clippy::vec_box)]

use serde::{Deserialize, Serialize};

pub use self::{
decorators::decorators, export_default_from::export_default_from,
import_assertions::import_assertions,
};

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub enum DecoratorVersion {
#[default]
#[serde(rename = "2021-12")]
V202112,

#[serde(rename = "2022-03")]
V202203,
}

pub mod decorator_2022_03;
pub mod decorators;
pub mod explicit_resource_management;
Expand Down
1 change: 0 additions & 1 deletion crates/swc_ecma_transforms_proposal/tests/.gitignore

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
(() => {
let old;
let block;
class Bar {
}
const dec = (cls, ctx) => {
old = cls;
return Bar;
};
const Foo =
@dec
class Foo {
static { block = Foo; }
method() { return Foo; }
static method() { return Foo; }
field = Foo;
static field = Foo;
get getter() { return Foo; }
static get getter() { return Foo; }
set setter(x) { x.foo = Foo; }
static set setter(x) { x.foo = Foo; }
accessor accessor = Foo;
static accessor accessor = Foo;
};
const foo = new old;
let obj;
assertEq(() => Foo !== old, true);
assertEq(() => Foo, Bar);
assertEq(() => block, Bar);
assertEq(() => Foo.field, Bar);
assertEq(() => foo.field, Bar);
assertEq(() => old.getter, Bar);
assertEq(() => foo.getter, Bar);
assertEq(() => (obj = { foo: null }, old.setter = obj, obj.foo), Bar);
assertEq(() => (obj = { foo: null }, foo.setter = obj, obj.foo), Bar);
// The specification for accessors is potentially wrong at the moment: https://github.com/tc39/proposal-decorators/issues/529
// assertEq(() => old.accessor, Bar)
// assertEq(() => foo.accessor, Bar)
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
(() => {
let old;
let block;
class Bar {
}
const dec = (cls, ctx) => {
old = cls;
return Bar;
};
@dec
class Foo {
static { block = Foo; }
method() { return Foo; }
static method() { return Foo; }
field = Foo;
static field = Foo;
get getter() { return Foo; }
static get getter() { return Foo; }
set setter(x) { x.foo = Foo; }
static set setter(x) { x.foo = Foo; }
accessor accessor = Foo;
static accessor accessor = Foo;
}
const foo = new old;
let obj;
assertEq(() => Foo !== old, true);
assertEq(() => Foo, Bar);
assertEq(() => block, Bar);
assertEq(() => Foo.field, Bar);
assertEq(() => foo.field, Bar);
assertEq(() => old.getter, Bar);
assertEq(() => foo.getter, Bar);
assertEq(() => (obj = { foo: null }, old.setter = obj, obj.foo), Bar);
assertEq(() => (obj = { foo: null }, foo.setter = obj, obj.foo), Bar);
// The specification for accessors is potentially wrong at the moment: https://github.com/tc39/proposal-decorators/issues/529
// assertEq(() => old.accessor, Bar)
// assertEq(() => foo.accessor, Bar)
})();
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,23 @@
assertEq(() => Object.getPrototypeOf(foo), null);
assertEq(() => order(bar), '0,1,2,3,11,12,13,14,4,5,6,7,15,16,17,18,8,19,9,20,10,21');
assertEq(() => Object.getPrototypeOf(bar), foo);
// Test an undecorated class
const FooNoDec = class {
};
const BarNoDec = class extends FooNoDec {
};
assertEq(() => FooNoDec[Symbol.metadata], null);
assertEq(() => BarNoDec[Symbol.metadata], null);
// Test a class with no class decorator
const FooOneDec = class {
@dec
x;
};
const BarOneDec = class extends FooOneDec {
@dec
y;
};
assertEq(() => JSON.stringify(FooOneDec[Symbol.metadata]), JSON.stringify({ x: 22 }));
assertEq(() => JSON.stringify(BarOneDec[Symbol.metadata]), JSON.stringify({ y: 23 }));
assertEq(() => Object.getPrototypeOf(BarOneDec[Symbol.metadata]), FooOneDec[Symbol.metadata]);
})();
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,23 @@
assertEq(() => Object.getPrototypeOf(foo), null);
assertEq(() => order(bar), '0,1,2,3,11,12,13,14,4,5,6,7,15,16,17,18,8,19,9,20,10,21');
assertEq(() => Object.getPrototypeOf(bar), foo);
// Test an undecorated class
class FooNoDec {
}
class BarNoDec extends FooNoDec {
}
assertEq(() => FooNoDec[Symbol.metadata], null);
assertEq(() => BarNoDec[Symbol.metadata], null);
// Test a class with no class decorator
class FooOneDec {
@dec
x;
}
class BarOneDec extends FooOneDec {
@dec
y;
}
assertEq(() => JSON.stringify(FooOneDec[Symbol.metadata]), JSON.stringify({ x: 22 }));
assertEq(() => JSON.stringify(BarOneDec[Symbol.metadata]), JSON.stringify({ y: 23 }));
assertEq(() => Object.getPrototypeOf(BarOneDec[Symbol.metadata]), FooOneDec[Symbol.metadata]);
})();
Loading
Loading