Skip to content

File tree

7 files changed

+154
-24
lines changed

7 files changed

+154
-24
lines changed

.changeset/twelve-colts-call.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
swc_core: minor
3+
swc_typescript: minor
4+
---
5+
6+
feat(typescript): Check computed property names of ts signatures

crates/swc_typescript/src/fast_dts/class.rs

Lines changed: 2 additions & 20 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::{PatExt, PropNameExit},
11+
util::ast_ext::{MemberExprExt, PatExt, PropNameExit},
1212
FastDts,
1313
};
1414

@@ -17,25 +17,7 @@ impl FastDts {
1717
if let Some(super_class) = &class.super_class {
1818
let is_not_allowed = match super_class.as_ref() {
1919
Expr::Ident(_) => false,
20-
Expr::Member(member_expr) => {
21-
let mut object = &member_expr.obj;
22-
loop {
23-
match object.as_ref() {
24-
Expr::Member(member_expr) => {
25-
object = &member_expr.obj;
26-
continue;
27-
}
28-
Expr::OptChain(opt_chain) => {
29-
if let Some(member_expr) = opt_chain.base.as_member() {
30-
object = &member_expr.obj;
31-
continue;
32-
}
33-
}
34-
_ => {}
35-
}
36-
break !object.is_ident();
37-
}
38-
}
20+
Expr::Member(member_expr) => !member_expr.get_first_object().is_ident(),
3921
_ => true,
4022
};
4123

crates/swc_typescript/src/fast_dts/decl.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,27 @@ impl FastDts {
101101
);
102102
}
103103
}
104-
Decl::TsInterface(_) | Decl::TsTypeAlias(_) => {
104+
Decl::TsInterface(ts_interface) => {
105105
if let Some(internal_annotations) = self.internal_annotations.as_ref() {
106-
decl.visit_mut_children_with(&mut InternalAnnotationTransformer::new(
106+
ts_interface.visit_mut_children_with(&mut InternalAnnotationTransformer::new(
107107
internal_annotations,
108108
))
109109
}
110+
for type_element in ts_interface.body.body.iter() {
111+
self.check_ts_signature(type_element);
112+
}
113+
}
114+
Decl::TsTypeAlias(ts_type_alias) => {
115+
if let Some(internal_annotations) = self.internal_annotations.as_ref() {
116+
ts_type_alias.visit_mut_children_with(&mut InternalAnnotationTransformer::new(
117+
internal_annotations,
118+
))
119+
}
120+
if let Some(ts_lit) = ts_type_alias.type_ann.as_ts_type_lit() {
121+
for type_element in ts_lit.members.iter() {
122+
self.check_ts_signature(type_element);
123+
}
124+
}
110125
}
111126
}
112127
}

crates/swc_typescript/src/fast_dts/types.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use super::{
1010
inferrer::ReturnTypeInferrer,
1111
type_ann,
1212
util::{
13-
ast_ext::PatExt,
13+
ast_ext::{MemberExprExt, PatExt},
1414
types::{ts_keyword_type, ts_lit_type},
1515
},
1616
FastDts,
@@ -315,6 +315,52 @@ impl FastDts {
315315
}
316316
}
317317

318+
pub(crate) fn check_ts_signature(&mut self, signature: &TsTypeElement) {
319+
match signature {
320+
TsTypeElement::TsPropertySignature(ts_property_signature) => {
321+
self.report_signature_property_key(
322+
&ts_property_signature.key,
323+
ts_property_signature.computed,
324+
);
325+
}
326+
TsTypeElement::TsGetterSignature(ts_getter_signature) => {
327+
self.report_signature_property_key(
328+
&ts_getter_signature.key,
329+
ts_getter_signature.computed,
330+
);
331+
}
332+
TsTypeElement::TsSetterSignature(ts_setter_signature) => {
333+
self.report_signature_property_key(
334+
&ts_setter_signature.key,
335+
ts_setter_signature.computed,
336+
);
337+
}
338+
TsTypeElement::TsMethodSignature(ts_method_signature) => {
339+
self.report_signature_property_key(
340+
&ts_method_signature.key,
341+
ts_method_signature.computed,
342+
);
343+
}
344+
_ => {}
345+
}
346+
}
347+
348+
pub(crate) fn report_signature_property_key(&mut self, key: &Expr, computed: bool) {
349+
if !computed {
350+
return;
351+
}
352+
353+
let is_not_allowed = match key {
354+
Expr::Ident(_) => false,
355+
Expr::Member(member) => !member.get_first_object().is_ident(),
356+
_ => !Self::is_literal(key),
357+
};
358+
359+
if is_not_allowed {
360+
self.signature_computed_property_name(key.span());
361+
}
362+
}
363+
318364
pub(crate) fn tpl_to_string(&mut self, tpl: &Tpl) -> Option<Str> {
319365
if !tpl.exprs.is_empty() {
320366
return None;

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::borrow::Cow;
22

33
use swc_atoms::Atom;
4-
use swc_ecma_ast::{BindingIdent, Expr, Lit, MemberProp, ObjectPatProp, Pat, PropName, TsTypeAnn};
4+
use swc_ecma_ast::{
5+
BindingIdent, Expr, Lit, MemberExpr, MemberProp, ObjectPatProp, Pat, PropName, TsTypeAnn,
6+
};
57

68
pub trait PatExt {
79
fn get_type_ann(&self) -> &Option<Box<TsTypeAnn>>;
@@ -117,3 +119,29 @@ impl MemberPropExt for MemberProp {
117119
}
118120
}
119121
}
122+
123+
pub trait MemberExprExt {
124+
fn get_first_object(&self) -> &Expr;
125+
}
126+
127+
impl MemberExprExt for MemberExpr {
128+
fn get_first_object(&self) -> &Expr {
129+
let mut object = &self.obj;
130+
loop {
131+
match object.as_ref() {
132+
Expr::Member(member_expr) => {
133+
object = &member_expr.obj;
134+
continue;
135+
}
136+
Expr::OptChain(opt_chain) => {
137+
if let Some(member_expr) = opt_chain.base.as_member() {
138+
object = &member_expr.obj;
139+
continue;
140+
}
141+
}
142+
_ => {}
143+
}
144+
break object;
145+
}
146+
}
147+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
```==================== .D.TS ====================
2+
3+
export interface A {
4+
["foo" as string]: number;
5+
["bar" as string](a: number): string;
6+
}
7+
export type B = {
8+
["foo" as string]: number;
9+
["bar" as string](a: number): string;
10+
};
11+
12+
13+
==================== Errors ====================
14+
x TS9014: Computed properties must be number or string literals, variables or dotted expressions with --isolatedDeclarations.
15+
,-[$DIR/tests/fixture/signature-computed-property-name.ts:2:1]
16+
1 | export interface A {
17+
2 | ["foo" as string]: number;
18+
: ^^^^^^^^^^^^^^^
19+
3 | ["bar" as string](a: number): string;
20+
`----
21+
x TS9014: Computed properties must be number or string literals, variables or dotted expressions with --isolatedDeclarations.
22+
,-[$DIR/tests/fixture/signature-computed-property-name.ts:3:1]
23+
2 | ["foo" as string]: number;
24+
3 | ["bar" as string](a: number): string;
25+
: ^^^^^^^^^^^^^^^
26+
4 | }
27+
`----
28+
x TS9014: Computed properties must be number or string literals, variables or dotted expressions with --isolatedDeclarations.
29+
,-[$DIR/tests/fixture/signature-computed-property-name.ts:7:1]
30+
6 | export type B = {
31+
7 | ["foo" as string]: number;
32+
: ^^^^^^^^^^^^^^^
33+
8 | ["bar" as string](a: number): string;
34+
`----
35+
x TS9014: Computed properties must be number or string literals, variables or dotted expressions with --isolatedDeclarations.
36+
,-[$DIR/tests/fixture/signature-computed-property-name.ts:8:1]
37+
7 | ["foo" as string]: number;
38+
8 | ["bar" as string](a: number): string;
39+
: ^^^^^^^^^^^^^^^
40+
9 | };
41+
`----
42+
43+
44+
```
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface A {
2+
["foo" as string]: number;
3+
["bar" as string](a: number): string;
4+
}
5+
6+
export type B = {
7+
["foo" as string]: number;
8+
["bar" as string](a: number): string;
9+
};

0 commit comments

Comments
 (0)