diff --git a/.changeset/calm-pumpkins-explain.md b/.changeset/calm-pumpkins-explain.md new file mode 100644 index 000000000000..78fc4288271a --- /dev/null +++ b/.changeset/calm-pumpkins-explain.md @@ -0,0 +1,5 @@ +--- +swc_typescript: patch +--- + +fix(typescript): Collect usages in extend clauses of classes and interfaces diff --git a/crates/swc_typescript/src/fast_dts/class.rs b/crates/swc_typescript/src/fast_dts/class.rs index f9dac8cfff48..5d76c60b9658 100644 --- a/crates/swc_typescript/src/fast_dts/class.rs +++ b/crates/swc_typescript/src/fast_dts/class.rs @@ -8,19 +8,14 @@ use swc_ecma_ast::{ use super::{ type_ann, - util::ast_ext::{MemberExprExt, PatExt, PropNameExit}, + util::ast_ext::{ExprExit, PatExt, PropNameExit}, FastDts, }; impl FastDts { pub(crate) fn transform_class(&mut self, class: &mut Class) { if let Some(super_class) = &class.super_class { - let is_not_allowed = match super_class.as_ref() { - Expr::Ident(_) => false, - Expr::Member(member_expr) => !member_expr.get_first_object().is_ident(), - _ => true, - }; - + let is_not_allowed = super_class.get_root_ident().is_none(); if is_not_allowed { self.extends_clause_expression(super_class.span()); } diff --git a/crates/swc_typescript/src/fast_dts/enum.rs b/crates/swc_typescript/src/fast_dts/enum.rs index 2edae24ef9c4..c1e45a7bfc72 100644 --- a/crates/swc_typescript/src/fast_dts/enum.rs +++ b/crates/swc_typescript/src/fast_dts/enum.rs @@ -126,6 +126,16 @@ impl FastDts { None } } + Expr::OptChain(opt_chain) => { + let member = opt_chain.base.as_member()?; + let ident = member.obj.as_ident()?; + if &ident.sym == enum_name { + let name = member.prop.static_name()?; + prev_members.get(name).cloned() + } else { + None + } + } _ => None, } } diff --git a/crates/swc_typescript/src/fast_dts/types.rs b/crates/swc_typescript/src/fast_dts/types.rs index 09b2e24cb5ca..5a5e95074307 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::{MemberExprExt, PatExt}, + ast_ext::{ExprExit, PatExt}, types::{ts_keyword_type, ts_lit_type}, }, FastDts, @@ -351,8 +351,7 @@ impl FastDts { } let is_not_allowed = match key { - Expr::Ident(_) => false, - Expr::Member(member) => !member.get_first_object().is_ident(), + Expr::Ident(_) | Expr::Member(_) | Expr::OptChain(_) => key.get_root_ident().is_none(), _ => !Self::is_literal(key), }; 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 44a2768c708d..10e90955afeb 100644 --- a/crates/swc_typescript/src/fast_dts/util/ast_ext.rs +++ b/crates/swc_typescript/src/fast_dts/util/ast_ext.rs @@ -2,9 +2,27 @@ use std::borrow::Cow; use swc_atoms::Atom; use swc_ecma_ast::{ - BindingIdent, Expr, Lit, MemberExpr, MemberProp, ObjectPatProp, Pat, PropName, TsTypeAnn, + BindingIdent, Expr, Ident, Lit, MemberProp, ObjectPatProp, Pat, PropName, TsTypeAnn, }; +pub trait ExprExit { + fn get_root_ident(&self) -> Option<&Ident>; +} + +impl ExprExit for Expr { + fn get_root_ident(&self) -> Option<&Ident> { + match self { + Expr::Member(member_expr) => member_expr.obj.get_root_ident(), + Expr::Ident(ident) => Some(ident), + Expr::OptChain(opt_chain_expr) => opt_chain_expr + .base + .as_member() + .and_then(|member_expr| member_expr.obj.get_root_ident()), + _ => None, + } + } +} + pub trait PatExt { fn get_type_ann(&self) -> &Option>; fn set_type_ann(&mut self, type_anno: Option>); @@ -119,29 +137,3 @@ 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/src/fast_dts/visitors/type_usage.rs b/crates/swc_typescript/src/fast_dts/visitors/type_usage.rs index 16a284b644b8..d057e0ee2b19 100644 --- a/crates/swc_typescript/src/fast_dts/visitors/type_usage.rs +++ b/crates/swc_typescript/src/fast_dts/visitors/type_usage.rs @@ -12,6 +12,8 @@ use swc_ecma_ast::{ }; use swc_ecma_visit::{Visit, VisitWith}; +use crate::fast_dts::util::ast_ext::ExprExit; + pub struct TypeUsageAnalyzer<'a> { graph: DiGraph, nodes: FxHashMap, @@ -99,14 +101,14 @@ impl TypeUsageAnalyzer<'_> { impl Visit for TypeUsageAnalyzer<'_> { fn visit_ts_property_signature(&mut self, node: &TsPropertySignature) { - if let Some(ident) = node.key.as_ident() { + if let Some(ident) = node.key.get_root_ident() { self.add_edge(ident.to_id(), true); } node.visit_children_with(self); } fn visit_ts_expr_with_type_args(&mut self, node: &TsExprWithTypeArgs) { - if let Some(ident) = node.expr.as_ident() { + if let Some(ident) = node.expr.get_root_ident() { self.add_edge(ident.to_id(), true); } node.visit_children_with(self); @@ -238,7 +240,7 @@ impl Visit for TypeUsageAnalyzer<'_> { fn visit_class(&mut self, node: &Class) { if let Some(super_class) = &node.super_class { - if let Some(ident) = super_class.as_ident() { + if let Some(ident) = super_class.get_root_ident() { self.add_edge(ident.to_id(), true); } } diff --git a/crates/swc_typescript/tests/fixture/signature-computed-property-name.snap b/crates/swc_typescript/tests/fixture/signature-computed-property-name.snap index 91759a9f4e2d..f1e950243727 100644 --- a/crates/swc_typescript/tests/fixture/signature-computed-property-name.snap +++ b/crates/swc_typescript/tests/fixture/signature-computed-property-name.snap @@ -8,6 +8,9 @@ export type B = { ["foo" as string]: number; ["bar" as string](a: number): string; }; +export type C = { + [D?.b]: number; +}; ==================== Errors ==================== diff --git a/crates/swc_typescript/tests/fixture/signature-computed-property-name.ts b/crates/swc_typescript/tests/fixture/signature-computed-property-name.ts index 72439e98f59e..60e3dc86cfb1 100644 --- a/crates/swc_typescript/tests/fixture/signature-computed-property-name.ts +++ b/crates/swc_typescript/tests/fixture/signature-computed-property-name.ts @@ -7,3 +7,7 @@ export type B = { ["foo" as string]: number; ["bar" as string](a: number): string; }; + +export type C = { + [D?.b]: number; +}; diff --git a/crates/swc_typescript/tests/fixture/type-usage.snap b/crates/swc_typescript/tests/fixture/type-usage.snap new file mode 100644 index 000000000000..ee0b74e40a99 --- /dev/null +++ b/crates/swc_typescript/tests/fixture/type-usage.snap @@ -0,0 +1,16 @@ +```==================== .D.TS ==================== + +import type * as Member from "some-path/my_module"; +export interface IMember extends Member.C<"SimpleEntity"> { +} +import type * as Ident from "some-path/my_module"; +export interface IIdent extends Ident { +} +import * as Paren from "some-path/my_module"; +export declare class CParen extends Paren { +} +import * as OptChain from "some-path/my_module"; +export declare class COptChain extends OptChain?.C<"SimpleEntity"> { +} + + diff --git a/crates/swc_typescript/tests/fixture/type-usage.ts b/crates/swc_typescript/tests/fixture/type-usage.ts new file mode 100644 index 000000000000..9d94cbe25e6e --- /dev/null +++ b/crates/swc_typescript/tests/fixture/type-usage.ts @@ -0,0 +1,11 @@ +import type * as Member from "some-path/my_module"; +export interface IMember extends Member.C<"SimpleEntity"> {} + +import type * as Ident from "some-path/my_module"; +export interface IIdent extends Ident {} + +import * as Paren from "some-path/my_module"; +export class CParen extends Paren {} + +import * as OptChain from "some-path/my_module"; +export class COptChain extends OptChain?.C<"SimpleEntity"> {}