From caed78a7105969ac15347e99bc2a1c998fa5f7f7 Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Sun, 12 Jan 2025 10:48:38 +0800 Subject: [PATCH] feat(typescript): Check computed property names of ts signatures (#9867) I find this missing part when I check https://github.com/swc-project/swc/issues/9859 swc: https://play.swc.rs/?version=1.10.6&code=H4sIAAAAAAAAA0utKMgvKlHIzCtJLUpLTE5VcFSo5lIAgmiltPx8JYXEYoXikqLMvPRYK4W80tyk1CJrqHRSYhGytEYiTIGmFVTMmquWiysVYkFJZUGqgpOCLVVNt%2BYCAA%2Bn8R2%2FAAAA&config=H4sIAAAAAAAAA1WMOQ6AMAwEe14RueYF1DQUPCIKLoI4LNtIQYi%2FY64Iut0Z7W6Fc9BLgMptFq2QZ0HO3Yisk%2FpkBHQllMCRFMrXqpxKecGL7LcATIQcR7Tp8Dk7zRi1kXnwil2t8hsXzwHIvHDA1lP2%2BwE5e0RDqwAAAA%3D%3D ts playground: https://www.typescriptlang.org/play/?isolatedDeclarations=true&isolatedModules=true#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgQTgG8AoOcuAbQCJ0IJq5MBnOZmKZAcwF0AuOEgCuAWwBGaANxkKNMZiiMWbDtx4AKTAOHi0ASgHtOSLtIC+JEqEiw4MAJ5g8AITgBeYjPI06DJqyM1bVEJKGkKKmp5RX8VY15NYN0oAzjuc2kgA --- .changeset/twelve-colts-call.md | 6 +++ crates/swc_typescript/src/fast_dts/class.rs | 22 +-------- crates/swc_typescript/src/fast_dts/decl.rs | 19 +++++++- crates/swc_typescript/src/fast_dts/types.rs | 48 ++++++++++++++++++- .../src/fast_dts/util/ast_ext.rs | 30 +++++++++++- .../signature-computed-property-name.snap | 44 +++++++++++++++++ .../signature-computed-property-name.ts | 9 ++++ 7 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 .changeset/twelve-colts-call.md create mode 100644 crates/swc_typescript/tests/fixture/signature-computed-property-name.snap create mode 100644 crates/swc_typescript/tests/fixture/signature-computed-property-name.ts diff --git a/.changeset/twelve-colts-call.md b/.changeset/twelve-colts-call.md new file mode 100644 index 000000000000..2ce99f043be3 --- /dev/null +++ b/.changeset/twelve-colts-call.md @@ -0,0 +1,6 @@ +--- +swc_core: minor +swc_typescript: minor +--- + +feat(typescript): Check computed property names of ts signatures diff --git a/crates/swc_typescript/src/fast_dts/class.rs b/crates/swc_typescript/src/fast_dts/class.rs index f2d2ae18e8f2..f9dac8cfff48 100644 --- a/crates/swc_typescript/src/fast_dts/class.rs +++ b/crates/swc_typescript/src/fast_dts/class.rs @@ -8,7 +8,7 @@ use swc_ecma_ast::{ use super::{ type_ann, - util::ast_ext::{PatExt, PropNameExit}, + util::ast_ext::{MemberExprExt, PatExt, PropNameExit}, FastDts, }; @@ -17,25 +17,7 @@ impl FastDts { if let Some(super_class) = &class.super_class { let is_not_allowed = match super_class.as_ref() { Expr::Ident(_) => false, - Expr::Member(member_expr) => { - let mut object = &member_expr.obj; - loop { - match object.as_ref() { - Expr::Member(member_expr) => { - object = &member_expr.obj; - continue; - } - Expr::OptChain(opt_chain) => { - if let Some(member_expr) = opt_chain.base.as_member() { - object = &member_expr.obj; - continue; - } - } - _ => {} - } - break !object.is_ident(); - } - } + Expr::Member(member_expr) => !member_expr.get_first_object().is_ident(), _ => true, }; diff --git a/crates/swc_typescript/src/fast_dts/decl.rs b/crates/swc_typescript/src/fast_dts/decl.rs index 04423e8612e2..33c4295a64a5 100644 --- a/crates/swc_typescript/src/fast_dts/decl.rs +++ b/crates/swc_typescript/src/fast_dts/decl.rs @@ -101,12 +101,27 @@ impl FastDts { ); } } - Decl::TsInterface(_) | Decl::TsTypeAlias(_) => { + Decl::TsInterface(ts_interface) => { if let Some(internal_annotations) = self.internal_annotations.as_ref() { - decl.visit_mut_children_with(&mut InternalAnnotationTransformer::new( + ts_interface.visit_mut_children_with(&mut InternalAnnotationTransformer::new( internal_annotations, )) } + for type_element in ts_interface.body.body.iter() { + self.check_ts_signature(type_element); + } + } + Decl::TsTypeAlias(ts_type_alias) => { + if let Some(internal_annotations) = self.internal_annotations.as_ref() { + ts_type_alias.visit_mut_children_with(&mut InternalAnnotationTransformer::new( + internal_annotations, + )) + } + if let Some(ts_lit) = ts_type_alias.type_ann.as_ts_type_lit() { + for type_element in ts_lit.members.iter() { + self.check_ts_signature(type_element); + } + } } } } diff --git a/crates/swc_typescript/src/fast_dts/types.rs b/crates/swc_typescript/src/fast_dts/types.rs index e65aafab7621..09b2e24cb5ca 100644 --- a/crates/swc_typescript/src/fast_dts/types.rs +++ b/crates/swc_typescript/src/fast_dts/types.rs @@ -10,7 +10,7 @@ use super::{ inferrer::ReturnTypeInferrer, type_ann, util::{ - ast_ext::PatExt, + ast_ext::{MemberExprExt, PatExt}, types::{ts_keyword_type, ts_lit_type}, }, FastDts, @@ -315,6 +315,52 @@ impl FastDts { } } + pub(crate) fn check_ts_signature(&mut self, signature: &TsTypeElement) { + match signature { + TsTypeElement::TsPropertySignature(ts_property_signature) => { + self.report_signature_property_key( + &ts_property_signature.key, + ts_property_signature.computed, + ); + } + TsTypeElement::TsGetterSignature(ts_getter_signature) => { + self.report_signature_property_key( + &ts_getter_signature.key, + ts_getter_signature.computed, + ); + } + TsTypeElement::TsSetterSignature(ts_setter_signature) => { + self.report_signature_property_key( + &ts_setter_signature.key, + ts_setter_signature.computed, + ); + } + TsTypeElement::TsMethodSignature(ts_method_signature) => { + self.report_signature_property_key( + &ts_method_signature.key, + ts_method_signature.computed, + ); + } + _ => {} + } + } + + pub(crate) fn report_signature_property_key(&mut self, key: &Expr, computed: bool) { + if !computed { + return; + } + + let is_not_allowed = match key { + Expr::Ident(_) => false, + Expr::Member(member) => !member.get_first_object().is_ident(), + _ => !Self::is_literal(key), + }; + + if is_not_allowed { + self.signature_computed_property_name(key.span()); + } + } + pub(crate) fn tpl_to_string(&mut self, tpl: &Tpl) -> Option { if !tpl.exprs.is_empty() { return None; diff --git a/crates/swc_typescript/src/fast_dts/util/ast_ext.rs b/crates/swc_typescript/src/fast_dts/util/ast_ext.rs index 1a1f205db851..44a2768c708d 100644 --- a/crates/swc_typescript/src/fast_dts/util/ast_ext.rs +++ b/crates/swc_typescript/src/fast_dts/util/ast_ext.rs @@ -1,7 +1,9 @@ use std::borrow::Cow; use swc_atoms::Atom; -use swc_ecma_ast::{BindingIdent, Expr, Lit, MemberProp, ObjectPatProp, Pat, PropName, TsTypeAnn}; +use swc_ecma_ast::{ + BindingIdent, Expr, Lit, MemberExpr, MemberProp, ObjectPatProp, Pat, PropName, TsTypeAnn, +}; pub trait PatExt { fn get_type_ann(&self) -> &Option>; @@ -117,3 +119,29 @@ impl MemberPropExt for MemberProp { } } } + +pub trait MemberExprExt { + fn get_first_object(&self) -> &Expr; +} + +impl MemberExprExt for MemberExpr { + fn get_first_object(&self) -> &Expr { + let mut object = &self.obj; + loop { + match object.as_ref() { + Expr::Member(member_expr) => { + object = &member_expr.obj; + continue; + } + Expr::OptChain(opt_chain) => { + if let Some(member_expr) = opt_chain.base.as_member() { + object = &member_expr.obj; + continue; + } + } + _ => {} + } + break object; + } + } +} diff --git a/crates/swc_typescript/tests/fixture/signature-computed-property-name.snap b/crates/swc_typescript/tests/fixture/signature-computed-property-name.snap new file mode 100644 index 000000000000..91759a9f4e2d --- /dev/null +++ b/crates/swc_typescript/tests/fixture/signature-computed-property-name.snap @@ -0,0 +1,44 @@ +```==================== .D.TS ==================== + +export interface A { + ["foo" as string]: number; + ["bar" as string](a: number): string; +} +export type B = { + ["foo" as string]: number; + ["bar" as string](a: number): string; +}; + + +==================== Errors ==================== + x TS9014: Computed properties must be number or string literals, variables or dotted expressions with --isolatedDeclarations. + ,-[$DIR/tests/fixture/signature-computed-property-name.ts:2:1] + 1 | export interface A { + 2 | ["foo" as string]: number; + : ^^^^^^^^^^^^^^^ + 3 | ["bar" as string](a: number): string; + `---- + x TS9014: Computed properties must be number or string literals, variables or dotted expressions with --isolatedDeclarations. + ,-[$DIR/tests/fixture/signature-computed-property-name.ts:3:1] + 2 | ["foo" as string]: number; + 3 | ["bar" as string](a: number): string; + : ^^^^^^^^^^^^^^^ + 4 | } + `---- + x TS9014: Computed properties must be number or string literals, variables or dotted expressions with --isolatedDeclarations. + ,-[$DIR/tests/fixture/signature-computed-property-name.ts:7:1] + 6 | export type B = { + 7 | ["foo" as string]: number; + : ^^^^^^^^^^^^^^^ + 8 | ["bar" as string](a: number): string; + `---- + x TS9014: Computed properties must be number or string literals, variables or dotted expressions with --isolatedDeclarations. + ,-[$DIR/tests/fixture/signature-computed-property-name.ts:8:1] + 7 | ["foo" as string]: number; + 8 | ["bar" as string](a: number): string; + : ^^^^^^^^^^^^^^^ + 9 | }; + `---- + + +``` diff --git a/crates/swc_typescript/tests/fixture/signature-computed-property-name.ts b/crates/swc_typescript/tests/fixture/signature-computed-property-name.ts new file mode 100644 index 000000000000..72439e98f59e --- /dev/null +++ b/crates/swc_typescript/tests/fixture/signature-computed-property-name.ts @@ -0,0 +1,9 @@ +export interface A { + ["foo" as string]: number; + ["bar" as string](a: number): string; +} + +export type B = { + ["foo" as string]: number; + ["bar" as string](a: number): string; +};