Skip to content

Commit 2e29d78

Browse files
authored
fix: Decorator metadata (#1248)
swc_ecma_transforms: - Emit proper typename for `design:type` used with enum. (#1160)
1 parent 5478463 commit 2e29d78

File tree

20 files changed

+426
-25
lines changed

20 files changed

+426
-25
lines changed

benches/typescript.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,14 @@ fn bench_codegen(b: &mut Bencher, _target: JscTarget) {
116116

117117
b.iter(|| {
118118
black_box(
119-
c.print(&module, SourceMapsConfig::Bool(false), None, false)
120-
.unwrap(),
119+
c.print(
120+
&module,
121+
JscTarget::Es2020,
122+
SourceMapsConfig::Bool(false),
123+
None,
124+
false,
125+
)
126+
.unwrap(),
121127
);
122128
})
123129
}

bundler/tests/fixture/deno-8574/output/entry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ function onceStrict(fn) {
372372
f.called = false;
373373
return f;
374374
}
375-
const VERSION1 = "5.4.11";
375+
const VERSION1 = "5.4.12";
376376
function getBufferResponse(response) {
377377
return response.arrayBuffer();
378378
}

ecmascript/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ edition = "2018"
66
license = "Apache-2.0/MIT"
77
name = "swc_ecmascript"
88
repository = "https://github.com/swc-project/swc.git"
9-
version = "0.14.4"
9+
version = "0.14.5"
1010

1111
[features]
1212
codegen = ["swc_ecma_codegen"]

ecmascript/codegen/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs"]
77
license = "Apache-2.0/MIT"
88
name = "swc_ecma_codegen"
99
repository = "https://github.com/swc-project/swc.git"
10-
version = "0.41.3"
10+
version = "0.41.4"
1111

1212
[dependencies]
1313
bitflags = "1"
@@ -17,8 +17,8 @@ swc_atoms = {version = "0.2", path = "../../atoms"}
1717
swc_common = {version = "0.10.0", path = "../../common"}
1818
swc_ecma_ast = {version = "0.35.0", path = "../ast"}
1919
swc_ecma_codegen_macros = {version = "0.5", path = "./macros"}
20+
swc_ecma_parser = {version = "0.43.0", path = "../parser"}
2021

2122
[dev-dependencies]
2223
swc_common = {version = "0.10.0", path = "../../common", features = ["sourcemap"]}
23-
swc_ecma_parser = {version = "0.43.0", path = "../parser"}
2424
testing = {version = "0.10.0", path = "../../testing"}

ecmascript/codegen/src/lib.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use swc_common::{
1414
};
1515
use swc_ecma_ast::*;
1616
use swc_ecma_codegen_macros::emitter;
17+
use swc_ecma_parser::JscTarget;
1718

1819
#[macro_use]
1920
pub mod macros;
@@ -388,7 +389,13 @@ impl<'a> Emitter<'a> {
388389
// self.wr.write_str_lit(node.span, &s)?;
389390
// return Ok(());
390391
// }
391-
let value = escape(&self.cm, node.span, &node.value, single_quote);
392+
let value = escape(
393+
&self.cm,
394+
self.wr.target(),
395+
node.span,
396+
&node.value,
397+
single_quote,
398+
);
392399
// let value = node.value.replace("\n", "\\n");
393400

394401
let single_quote = single_quote.unwrap_or(false);
@@ -2412,7 +2419,13 @@ fn unescape(s: &str) -> String {
24122419
result
24132420
}
24142421

2415-
fn escape<'s>(cm: &SourceMap, span: Span, s: &'s str, single_quote: Option<bool>) -> Cow<'s, str> {
2422+
fn escape<'s>(
2423+
cm: &SourceMap,
2424+
target: JscTarget,
2425+
span: Span,
2426+
s: &'s str,
2427+
single_quote: Option<bool>,
2428+
) -> Cow<'s, str> {
24162429
if span.is_dummy() {
24172430
return Cow::Owned(s.escape_default().to_string());
24182431
}

ecmascript/codegen/src/text_writer.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub use self::{basic_impl::JsWriter, semicolon::omit_trailing_semi};
22
use super::*;
33
use swc_common::Span;
4+
use swc_ecma_parser::JscTarget;
45

56
mod basic_impl;
67
mod semicolon;
@@ -12,6 +13,17 @@ pub type Symbol = Str;
1213
///
1314
/// Ported from `EmitWriteJs`.
1415
pub trait WriteJs {
16+
/// Returns javascript target which should be used while generating code.
17+
///
18+
/// This defaults to [JscTarget::Es2020] because it preserves input as much
19+
/// as possible.
20+
///
21+
/// Implementor **should return same value** regardless how much time it is
22+
/// called.
23+
fn target(&self) -> JscTarget {
24+
JscTarget::Es2020
25+
}
26+
1527
fn increase_indent(&mut self) -> Result;
1628
fn decrease_indent(&mut self) -> Result;
1729

ecmascript/transforms/src/proposals/decorators/legacy.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,30 @@ use crate::util::{
44
alias_if_required, default_constructor, prepend, prop_name_to_expr_value, undefined,
55
ExprFactory, ModuleItemLike, StmtLike,
66
};
7+
use fxhash::FxHashMap;
78
use smallvec::SmallVec;
89
use std::mem::replace;
910
use swc_common::{util::move_map::MoveMap, DUMMY_SP};
1011
use swc_ecma_ast::*;
11-
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith, VisitWith};
12+
use swc_ecma_utils::{ident::IdentLike, Id};
13+
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith, Node, Visit, VisitWith};
1214

1315
mod metadata;
1416

17+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18+
enum EnumKind {
19+
Mixed,
20+
Str,
21+
Num,
22+
}
23+
1524
#[derive(Debug)]
1625
pub(super) struct Legacy {
1726
metadata: bool,
1827
uninitialized_vars: Vec<VarDeclarator>,
1928
initialized_vars: Vec<VarDeclarator>,
2029
exports: Vec<ExportSpecifier>,
30+
enums: FxHashMap<Id, EnumKind>,
2131
}
2232

2333
pub(super) fn new(metadata: bool) -> Legacy {
@@ -26,6 +36,48 @@ pub(super) fn new(metadata: bool) -> Legacy {
2636
uninitialized_vars: Default::default(),
2737
initialized_vars: Default::default(),
2838
exports: Default::default(),
39+
enums: Default::default(),
40+
}
41+
}
42+
43+
impl Visit for Legacy {
44+
fn visit_ts_enum_decl(&mut self, e: &TsEnumDecl, _: &dyn Node) {
45+
let enum_kind = e
46+
.members
47+
.iter()
48+
.map(|member| member.init.as_ref())
49+
.map(|init| match init {
50+
Some(e) => match &**e {
51+
Expr::Lit(lit) => match lit {
52+
Lit::Str(_) => EnumKind::Str,
53+
Lit::Num(_) => EnumKind::Num,
54+
_ => EnumKind::Mixed,
55+
},
56+
_ => EnumKind::Mixed,
57+
},
58+
None => EnumKind::Num,
59+
})
60+
.fold(None, |opt: Option<EnumKind>, item| {
61+
//
62+
let a = match item {
63+
EnumKind::Mixed => return Some(EnumKind::Mixed),
64+
_ => item,
65+
};
66+
67+
let b = match opt {
68+
Some(EnumKind::Mixed) => return Some(EnumKind::Mixed),
69+
Some(v) => v,
70+
None => return Some(item),
71+
};
72+
if a == b {
73+
return Some(a);
74+
} else {
75+
return Some(EnumKind::Mixed);
76+
}
77+
});
78+
if let Some(kind) = enum_kind {
79+
self.enums.insert(e.id.to_id(), kind);
80+
}
2981
}
3082
}
3183

@@ -78,6 +130,10 @@ impl Fold for Legacy {
78130
}
79131

80132
fn fold_module(&mut self, m: Module) -> Module {
133+
// Collect required information.
134+
// For example, value type of enum affects codegen
135+
m.visit_with(&Invalid { span: DUMMY_SP }, self);
136+
81137
let mut m = m.fold_children_with(self);
82138

83139
if !self.uninitialized_vars.is_empty() {
@@ -214,6 +270,7 @@ impl Legacy {
214270
let i = c.ident.clone();
215271

216272
c = c.fold_with(&mut ParamMetadata).fold_with(&mut Metadata {
273+
enums: &self.enums,
217274
class_name: i.as_ref(),
218275
});
219276
}

ecmascript/transforms/src/proposals/decorators/legacy/metadata.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
use fxhash::FxHashMap;
12
use swc_common::{util::move_map::MoveMap, Spanned, DUMMY_SP};
23
use swc_ecma_ast::*;
3-
use swc_ecma_utils::{undefined, ExprFactory};
4+
use swc_ecma_utils::{ident::IdentLike, undefined, ExprFactory, Id};
45
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
56

7+
use super::EnumKind;
8+
69
/// https://github.com/leonardfactory/babel-plugin-transform-typescript-metadata/blob/master/src/parameter/parameterVisitor.ts
710
pub(super) struct ParamMetadata;
811

@@ -118,6 +121,8 @@ impl ParamMetadata {
118121

119122
/// https://github.com/leonardfactory/babel-plugin-transform-typescript-metadata/blob/master/src/metadata/metadataVisitor.ts
120123
pub(super) struct Metadata<'a> {
124+
pub(super) enums: &'a FxHashMap<Id, EnumKind>,
125+
121126
pub(super) class_name: Option<&'a Ident>,
122127
}
123128

@@ -219,6 +224,35 @@ impl Fold for Metadata<'_> {
219224
return p;
220225
}
221226

227+
if let Some(name) = p
228+
.type_ann
229+
.as_ref()
230+
.map(|ty| &ty.type_ann)
231+
.map(|type_ann| match &**type_ann {
232+
TsType::TsTypeRef(r) => Some(r),
233+
_ => None,
234+
})
235+
.flatten()
236+
.map(|r| match &r.type_name {
237+
TsEntityName::TsQualifiedName(_) => None,
238+
TsEntityName::Ident(i) => Some(i),
239+
})
240+
.flatten()
241+
{
242+
if let Some(kind) = self.enums.get(&name.to_id()) {
243+
let dec = self.create_metadata_design_decorator(
244+
"design:type",
245+
match kind {
246+
EnumKind::Mixed => quote_ident!("Object").as_arg(),
247+
EnumKind::Str => quote_ident!("String").as_arg(),
248+
EnumKind::Num => quote_ident!("Number").as_arg(),
249+
},
250+
);
251+
p.decorators.push(dec);
252+
return p;
253+
}
254+
}
255+
222256
let dec = self.create_metadata_design_decorator(
223257
"design:type",
224258
serialize_type(self.class_name, p.type_ann.as_ref()).as_arg(),

ecmascript/transforms/tests/proposal_decorators.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5129,3 +5129,54 @@ let Sample = _class = _dec11(_class = _dec10(_class = _dec9(((_class = class Sam
51295129
], Object.getOwnPropertyDescriptor(_class.prototype, "assignments"), _class.prototype), _class)) || _class) || _class) || _class;"##,
51305130
ok_if_code_eq
51315131
);
5132+
5133+
test!(
5134+
ts(),
5135+
|_| decorators(Config {
5136+
legacy: true,
5137+
emit_metadata: true,
5138+
}),
5139+
issue_1160_1,
5140+
"
5141+
enum MyEnum {
5142+
x = \"xxx\",
5143+
y = \"yyy\"
5144+
}
5145+
5146+
class Xpto {
5147+
@Decorator()
5148+
value!: MyEnum;
5149+
}
5150+
5151+
function Decorator() {
5152+
return function (...args) {};
5153+
}
5154+
",
5155+
"
5156+
var _class, _descriptor;
5157+
enum MyEnum {
5158+
x = \"xxx\",
5159+
y = \"yyy\"
5160+
}
5161+
var _dec = Decorator(), _dec1 = typeof Reflect !== \"undefined\" && typeof Reflect.metadata === \
5162+
\"function\" && Reflect.metadata(\"design:type\", String);
5163+
let Xpto = ((_class = class Xpto {
5164+
constructor(){
5165+
_initializerDefineProperty(this, \"value\", _descriptor, this);
5166+
}
5167+
}) || _class, _descriptor = _applyDecoratedDescriptor(_class.prototype, \"value\", [
5168+
_dec,
5169+
_dec1
5170+
], {
5171+
configurable: true,
5172+
enumerable: true,
5173+
writable: true,
5174+
initializer: void 0
5175+
}), _class);
5176+
function Decorator() {
5177+
return function(...args) {
5178+
};
5179+
}
5180+
",
5181+
ok_if_code_eq
5182+
);

native/src/bundle.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ impl Task for BundleTask {
4444
type JsValue = JsObject;
4545

4646
fn compute(&mut self) -> napi::Result<Self::Output> {
47+
// Defaults to es3
48+
let codegen_target = self
49+
.config
50+
.static_items
51+
.config
52+
.codegen_target()
53+
.unwrap_or_default();
54+
4755
let res = catch_unwind(AssertUnwindSafe(|| {
4856
let bundler = Bundler::new(
4957
self.swc.globals(),
@@ -125,9 +133,13 @@ impl Task for BundleTask {
125133
})
126134
.unwrap_or(false);
127135

128-
let output =
129-
self.swc
130-
.print(&m, SourceMapsConfig::Bool(true), None, minify)?;
136+
let output = self.swc.print(
137+
&m,
138+
codegen_target,
139+
SourceMapsConfig::Bool(true),
140+
None,
141+
minify,
142+
)?;
131143

132144
Ok((k, output))
133145
})

native/src/print.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use swc::{
99
Compiler, TransformOutput,
1010
};
1111
use swc_ecma_ast::Program;
12+
use swc_ecma_parser::JscTarget;
1213

1314
// ----- Printing -----
1415

@@ -26,6 +27,12 @@ impl Task for PrintTask {
2627
self.c
2728
.print(
2829
&self.program,
30+
self.options
31+
.config
32+
.as_ref()
33+
.map(|config| &config.jsc)
34+
.map(|jsc| jsc.target)
35+
.unwrap_or(JscTarget::Es2020),
2936
self.options
3037
.source_maps
3138
.clone()
@@ -72,9 +79,13 @@ pub fn print_sync(cx: CallContext) -> napi::Result<JsObject> {
7279

7380
let options: Options = cx.get_deserialized(1)?;
7481

82+
// Defaults to es3
83+
let codegen_target = options.codegen_target().unwrap_or_default();
84+
7585
let result = {
7686
c.print(
7787
&program,
88+
codegen_target,
7889
options
7990
.source_maps
8091
.clone()

0 commit comments

Comments
 (0)