Skip to content

Commit 1298e76

Browse files
authored
fix(ts/fast-dts): Correctly emit Symbol-keyed accessors in declarations (#10508)
**Related issue:** - Closes: #10507
1 parent 41075a1 commit 1298e76

File tree

6 files changed

+175
-84
lines changed

6 files changed

+175
-84
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
swc_core: patch
3+
swc_typescript: patch
4+
---
5+
6+
fix(ts/fast-dts): Correctly emit Symbol-keyed accessors in declarations

crates/swc_typescript/src/fast_dts/class.rs

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use swc_ecma_ast::{
88

99
use super::{
1010
type_ann,
11-
util::ast_ext::{ExprExit, PatExt, PropNameExit},
11+
util::ast_ext::{ExprExit, PatExt, PropNameExit, StaticProp},
1212
FastDts,
1313
};
1414

@@ -155,10 +155,10 @@ impl FastDts {
155155
} else {
156156
method.function.params.truncate(1);
157157
let param = method.function.params.first_mut().unwrap();
158-
let static_name = method.key.static_name();
158+
let static_prop = method.key.static_prop(self.unresolved_mark);
159159

160-
if let Some(type_ann) = static_name
161-
.and_then(|name| setter_getter_annotations.get(name.as_ref()))
160+
if let Some(type_ann) = static_prop
161+
.and_then(|prop| setter_getter_annotations.get(&prop))
162162
{
163163
param.pat.set_type_ann(Some(type_ann.clone()));
164164
}
@@ -179,8 +179,8 @@ impl FastDts {
179179
if method.function.return_type.is_none() {
180180
method.function.return_type = method
181181
.key
182-
.static_name()
183-
.and_then(|name| setter_getter_annotations.get(name.as_ref()))
182+
.static_prop(self.unresolved_mark)
183+
.and_then(|prop| setter_getter_annotations.get(&prop))
184184
.cloned();
185185
}
186186
if method.function.return_type.is_none() {
@@ -460,7 +460,7 @@ impl FastDts {
460460
pub(crate) fn collect_getter_or_setter_annotations(
461461
&mut self,
462462
class: &Class,
463-
) -> FxHashMap<String, Box<TsTypeAnn>> {
463+
) -> FxHashMap<StaticProp, Box<TsTypeAnn>> {
464464
let mut annotations = FxHashMap::default();
465465
for member in &class.body {
466466
let ClassMember::Method(method) = member else {
@@ -478,7 +478,7 @@ impl FastDts {
478478
continue;
479479
}
480480

481-
let Some(static_name) = method.key.static_name().map(|name| name.to_string()) else {
481+
let Some(static_prop) = method.key.static_prop(self.unresolved_mark) else {
482482
continue;
483483
};
484484

@@ -490,7 +490,7 @@ impl FastDts {
490490
.clone()
491491
.or_else(|| self.infer_function_return_type(&method.function))
492492
{
493-
annotations.insert(static_name, type_ann);
493+
annotations.insert(static_prop, type_ann);
494494
}
495495
}
496496
MethodKind::Setter => {
@@ -499,7 +499,7 @@ impl FastDts {
499499
};
500500

501501
if let Some(type_ann) = first_param.pat.get_type_ann() {
502-
annotations.insert(static_name, type_ann.clone());
502+
annotations.insert(static_prop, type_ann.clone());
503503
}
504504
}
505505
_ => continue,
@@ -522,31 +522,6 @@ impl FastDts {
522522
}
523523

524524
pub(crate) fn is_global_symbol_object(&self, expr: &Expr) -> bool {
525-
let Some(obj) = (match expr {
526-
Expr::Member(member) => Some(&member.obj),
527-
Expr::OptChain(opt_chain) => opt_chain.base.as_member().map(|member| &member.obj),
528-
_ => None,
529-
}) else {
530-
return false;
531-
};
532-
533-
// https://github.com/microsoft/TypeScript/blob/cbac1ddfc73ca3b9d8741c1b51b74663a0f24695/src/compiler/transformers/declarations.ts#L1011
534-
if let Some(ident) = obj.as_ident() {
535-
// Exactly `Symbol.something` and `Symbol` either does not resolve
536-
// or definitely resolves to the global Symbol
537-
return ident.sym.as_str() == "Symbol" && ident.ctxt.has_mark(self.unresolved_mark);
538-
}
539-
540-
if let Some(member_expr) = obj.as_member() {
541-
// Exactly `globalThis.Symbol.something` and `globalThis` resolves
542-
// to the global `globalThis`
543-
if let Some(ident) = member_expr.obj.as_ident() {
544-
return ident.sym.as_str() == "globalThis"
545-
&& ident.ctxt.has_mark(self.unresolved_mark)
546-
&& member_expr.prop.is_ident_with("Symbol");
547-
}
548-
}
549-
550-
false
525+
expr.get_global_symbol_prop(self.unresolved_mark).is_some()
551526
}
552527
}

crates/swc_typescript/src/fast_dts/types.rs

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use super::{
1111
inferrer::ReturnTypeInferrer,
1212
type_ann,
1313
util::{
14-
ast_ext::{ExprExit, PatExt},
14+
ast_ext::{ExprExit, PatExt, StaticProp},
1515
types::{ts_keyword_type, ts_lit_type},
1616
},
1717
FastDts,
@@ -191,30 +191,16 @@ impl FastDts {
191191

192192
let mut has_setter = false;
193193

194-
if let Some(static_name) = getter.key.static_name() {
195-
if has_seen.contains(&static_name) {
194+
if let Some(static_prop) = getter.key.static_prop(self.unresolved_mark) {
195+
if has_seen.contains(&static_prop) {
196196
continue;
197197
}
198-
has_setter = seen_setter.contains(static_name.as_ref());
199-
if let Some(type_ann) =
200-
setter_getter_annotations.get(static_name.as_ref())
201-
{
198+
has_setter = seen_setter.contains(&static_prop);
199+
if let Some(type_ann) = setter_getter_annotations.get(&static_prop) {
202200
getter_type_ann = Some(type_ann.clone());
203201
}
204202

205-
has_seen.insert(static_name);
206-
}
207-
208-
// [TODO]: check cases not handled by
209-
// collect_object_getter_or_setter_annotations
210-
if getter_type_ann.is_none() {
211-
getter_type_ann = getter.type_ann.clone().or_else(|| {
212-
getter
213-
.body
214-
.as_ref()
215-
.and_then(|body| ReturnTypeInferrer::infer(self, &body.stmts))
216-
.map(type_ann)
217-
});
203+
has_seen.insert(static_prop);
218204
}
219205

220206
if getter_type_ann.is_none() {
@@ -238,17 +224,15 @@ impl FastDts {
238224

239225
let mut setter_type_ann = None;
240226

241-
if let Some(static_name) = setter.key.static_name() {
242-
if has_seen.contains(&static_name) {
227+
if let Some(static_prop) = setter.key.static_prop(self.unresolved_mark) {
228+
if has_seen.contains(&static_prop) {
243229
continue;
244230
}
245-
if let Some(type_ann) =
246-
setter_getter_annotations.get(static_name.as_ref())
247-
{
231+
if let Some(type_ann) = setter_getter_annotations.get(&static_prop) {
248232
setter_type_ann = Some(type_ann.clone());
249233
}
250234

251-
has_seen.insert(static_name);
235+
has_seen.insert(static_prop);
252236
}
253237

254238
if setter_type_ann.is_none() {
@@ -434,7 +418,7 @@ impl FastDts {
434418
pub(crate) fn collect_object_getter_or_setter_annotations(
435419
&mut self,
436420
object: &ObjectLit,
437-
) -> (FxHashMap<String, Box<TsTypeAnn>>, FxHashSet<String>) {
421+
) -> (FxHashMap<StaticProp, Box<TsTypeAnn>>, FxHashSet<StaticProp>) {
438422
let mut annotations = FxHashMap::default();
439423
let mut seen_setter = FxHashSet::default();
440424

@@ -443,7 +427,7 @@ impl FastDts {
443427
continue;
444428
};
445429

446-
let Some(static_name) = prop.static_name().map(|name| name.to_string()) else {
430+
let Some(static_prop) = prop.static_prop(self.unresolved_mark) else {
447431
continue;
448432
};
449433

@@ -457,14 +441,14 @@ impl FastDts {
457441
.clone()
458442
.or_else(|| ReturnTypeInferrer::infer(self, &body.stmts).map(type_ann))
459443
{
460-
annotations.insert(static_name, type_ann);
444+
annotations.insert(static_prop, type_ann);
461445
}
462446
}
463447
Prop::Setter(setter) => {
464448
if let Some(type_ann) = setter.param.get_type_ann().clone() {
465-
annotations.insert(static_name.clone(), type_ann);
449+
annotations.insert(static_prop.clone(), type_ann);
466450
}
467-
seen_setter.insert(static_name);
451+
seen_setter.insert(static_prop);
468452
}
469453
_ => {}
470454
}

crates/swc_typescript/src/fast_dts/util/ast_ext.rs

Lines changed: 106 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use std::borrow::Cow;
22

33
use swc_atoms::Atom;
4+
use swc_common::Mark;
45
use swc_ecma_ast::{
5-
BindingIdent, Expr, Ident, Lit, MemberProp, ObjectPatProp, Pat, Prop, PropName, TsTypeAnn,
6+
BindingIdent, ComputedPropName, Expr, Ident, Lit, MemberProp, ObjectPatProp, Pat, Prop,
7+
PropName, TsTypeAnn,
68
};
79

810
pub trait ExprExit {
911
fn get_root_ident(&self) -> Option<&Ident>;
12+
fn get_global_symbol_prop(&self, unresolved_mark: Mark) -> Option<&MemberProp>;
1013
}
1114

1215
impl ExprExit for Expr {
@@ -21,6 +24,43 @@ impl ExprExit for Expr {
2124
_ => None,
2225
}
2326
}
27+
28+
fn get_global_symbol_prop(&self, unresolved_mark: Mark) -> Option<&MemberProp> {
29+
let (obj, prop) = (match self {
30+
Expr::Member(member) => Some((&member.obj, &member.prop)),
31+
Expr::OptChain(opt_chain) => opt_chain
32+
.base
33+
.as_member()
34+
.map(|member| (&member.obj, &member.prop)),
35+
_ => None,
36+
})?;
37+
38+
// https://github.com/microsoft/TypeScript/blob/cbac1ddfc73ca3b9d8741c1b51b74663a0f24695/src/compiler/transformers/declarations.ts#L1011
39+
if let Some(ident) = obj.as_ident() {
40+
// Exactly `Symbol.something` and `Symbol` either does not resolve
41+
// or definitely resolves to the global Symbol
42+
return if ident.sym.as_str() == "Symbol" && ident.ctxt.has_mark(unresolved_mark) {
43+
Some(prop)
44+
} else {
45+
None
46+
};
47+
}
48+
49+
if let Some(member_expr) = obj.as_member() {
50+
// Exactly `globalThis.Symbol.something` and `globalThis` resolves
51+
// to the global `globalThis`
52+
if let Some(ident) = member_expr.obj.as_ident() {
53+
if ident.sym.as_str() == "globalThis"
54+
&& ident.ctxt.has_mark(unresolved_mark)
55+
&& member_expr.prop.is_ident_with("Symbol")
56+
{
57+
return Some(prop);
58+
}
59+
}
60+
}
61+
62+
None
63+
}
2464
}
2565

2666
pub trait PatExt {
@@ -88,6 +128,7 @@ impl PatExt for Pat {
88128

89129
pub trait PropNameExit {
90130
fn static_name(&self) -> Option<Cow<str>>;
131+
fn static_prop(&self, unresolved_mark: Mark) -> Option<StaticProp>;
91132
}
92133

93134
impl PropNameExit for PropName {
@@ -97,23 +138,54 @@ impl PropNameExit for PropName {
97138
PropName::Str(string) => Some(Cow::Borrowed(string.value.as_str())),
98139
PropName::Num(number) => Some(Cow::Owned(number.value.to_string())),
99140
PropName::BigInt(big_int) => Some(Cow::Owned(big_int.value.to_string())),
100-
PropName::Computed(computed_prop_name) => match computed_prop_name.expr.as_ref() {
101-
Expr::Lit(lit) => match lit {
102-
Lit::Str(string) => Some(Cow::Borrowed(string.value.as_str())),
103-
Lit::Bool(b) => Some(Cow::Owned(b.value.to_string())),
104-
Lit::Null(_) => Some(Cow::Borrowed("null")),
105-
Lit::Num(number) => Some(Cow::Owned(number.value.to_string())),
106-
Lit::BigInt(big_int) => Some(Cow::Owned(big_int.value.to_string())),
107-
Lit::Regex(regex) => Some(Cow::Owned(regex.exp.to_string())),
108-
Lit::JSXText(_) => None,
109-
},
110-
Expr::Tpl(tpl) if tpl.exprs.is_empty() => tpl
111-
.quasis
112-
.first()
113-
.and_then(|e| e.cooked.as_ref())
114-
.map(|atom| Cow::Borrowed(atom.as_str())),
115-
_ => None,
141+
PropName::Computed(computed_prop_name) => computed_prop_name.static_name(),
142+
}
143+
}
144+
145+
fn static_prop(&self, unresolved_mark: Mark) -> Option<StaticProp> {
146+
match self {
147+
PropName::Computed(c) => c.static_prop(unresolved_mark),
148+
prop => prop.static_name().map(Into::into).map(StaticProp::Name),
149+
}
150+
}
151+
}
152+
153+
impl PropNameExit for ComputedPropName {
154+
fn static_name(&self) -> Option<Cow<str>> {
155+
match self.expr.as_ref() {
156+
Expr::Lit(lit) => match lit {
157+
Lit::Str(string) => Some(Cow::Borrowed(string.value.as_str())),
158+
Lit::Bool(b) => Some(Cow::Owned(b.value.to_string())),
159+
Lit::Null(_) => Some(Cow::Borrowed("null")),
160+
Lit::Num(number) => Some(Cow::Owned(number.value.to_string())),
161+
Lit::BigInt(big_int) => Some(Cow::Owned(big_int.value.to_string())),
162+
Lit::Regex(regex) => Some(Cow::Owned(regex.exp.to_string())),
163+
Lit::JSXText(_) => None,
116164
},
165+
Expr::Tpl(tpl) if tpl.exprs.is_empty() => tpl
166+
.quasis
167+
.first()
168+
.and_then(|e| e.cooked.as_ref())
169+
.map(|atom| Cow::Borrowed(atom.as_str())),
170+
_ => None,
171+
}
172+
}
173+
174+
fn static_prop(&self, unresolved_mark: Mark) -> Option<StaticProp> {
175+
match self.expr.as_ref() {
176+
Expr::Member(..) | Expr::OptChain(..) => self
177+
.expr
178+
.get_global_symbol_prop(unresolved_mark)
179+
.and_then(|prop| match prop {
180+
MemberProp::Ident(ident_name) => {
181+
Some(StaticProp::Symbol(ident_name.sym.clone()))
182+
}
183+
MemberProp::Computed(c) => {
184+
c.static_name().map(Into::into).map(StaticProp::Symbol)
185+
}
186+
MemberProp::PrivateName(..) => None,
187+
}),
188+
_ => self.static_name().map(Into::into).map(StaticProp::Name),
117189
}
118190
}
119191
}
@@ -129,6 +201,17 @@ impl PropNameExit for Prop {
129201
Self::Method(method_prop) => method_prop.key.static_name(),
130202
}
131203
}
204+
205+
fn static_prop(&self, unresolved_mark: Mark) -> Option<StaticProp> {
206+
match self {
207+
Self::Shorthand(ident_name) => Some(StaticProp::Name(ident_name.sym.clone())),
208+
Self::KeyValue(key_value_prop) => key_value_prop.key.static_prop(unresolved_mark),
209+
Self::Assign(..) => None,
210+
Self::Getter(getter_prop) => getter_prop.key.static_prop(unresolved_mark),
211+
Self::Setter(setter_prop) => setter_prop.key.static_prop(unresolved_mark),
212+
Self::Method(method_prop) => method_prop.key.static_prop(unresolved_mark),
213+
}
214+
}
132215
}
133216

134217
pub trait MemberPropExt {
@@ -150,3 +233,9 @@ impl MemberPropExt for MemberProp {
150233
}
151234
}
152235
}
236+
237+
#[derive(Eq, Hash, PartialEq, Clone)]
238+
pub(crate) enum StaticProp {
239+
Name(Atom),
240+
Symbol(Atom),
241+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
```==================== .D.TS ====================
2+
3+
// @isolatedDeclarations: true
4+
// @emitDeclarationOnly: true
5+
export declare const foo: {
6+
[Symbol.toStringTag]: string;
7+
};
8+
export declare class Foo {
9+
#private;
10+
get [Symbol.toStringTag](): string;
11+
set [Symbol.toStringTag](value: string);
12+
}
13+
14+

0 commit comments

Comments
 (0)