From af91f1a18ef900a59d8f7118281abc1fa124c6c7 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Mon, 6 Apr 2026 20:34:34 +0800 Subject: [PATCH 01/16] fix(es/typescript): Handle TypeScript expressions in enum transformation --- .../src/transform.rs | 17 +++++++++++------ .../src/ts_enum.rs | 8 ++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index 46746efd9298..af69bc9f5e9d 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -1074,17 +1074,22 @@ impl Transform { return FoldedDecl::Empty; } - let opaque = member_list - .iter() - .any(|item| matches!(item.value, TsEnumRecordValue::Opaque(..))); + let namespace_export = self.namespace_id.is_some() && is_export; + let iife = !is_first || namespace_export; + + let mut opaque = false; let stmts = member_list .into_iter() .filter(|item| !ts_enum_safe_remove || !item.is_const()) - .map(|item| item.build_assign(&id.to_id())); + .map(|mut item| { + if let TsEnumRecordValue::Opaque(ref mut expr) = item.value { + opaque = true; + expr.visit_mut_with(self); + } - let namespace_export = self.namespace_id.is_some() && is_export; - let iife = !is_first || namespace_export; + item.build_assign(&id.to_id()) + }); let body = if !iife { let return_stmt: Stmt = ReturnStmt { diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index ff483b56b463..9c6d101f6565 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -155,6 +155,14 @@ impl EnumValueComputer<'_> { Expr::Bin(e) => self.compute_bin(e), Expr::Member(e) => self.compute_member(e), Expr::Tpl(e) => self.compute_tpl(e), + // Handle TypeScript type expressions by stripping them + // and computing the inner expression + Expr::TsAs(TsAsExpr { expr, .. }) + | Expr::TsNonNull(TsNonNullExpr { expr, .. }) + | Expr::TsTypeAssertion(TsTypeAssertion { expr, .. }) + | Expr::TsConstAssertion(TsConstAssertion { expr, .. }) + | Expr::TsInstantiation(TsInstantiation { expr, .. }) + | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => self.compute_rec(expr), _ => TsEnumRecordValue::Opaque(expr), } } From c9ea5571877c2fec836b1040ac7cedd993c17164 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Mon, 6 Apr 2026 21:37:37 +0800 Subject: [PATCH 02/16] chore: update test cases --- .../strip.rs/ts_enum_with_nested_class.js | 8 ++++ .../strip.rs/ts_enum_with_nested_enum.js | 10 +++++ .../strip.rs/ts_enum_with_opaque_expr.js | 4 ++ .../strip.rs/ts_enum_with_type_assertion.js | 5 +++ .../tests/fixture/issue-3001/1/output.js | 2 +- .../tests/fixture/issue-3001/2/output.js | 2 +- .../tests/strip.rs | 37 +++++++++++++++++++ 7 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_class.js create mode 100644 crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_enum.js create mode 100644 crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_opaque_expr.js create mode 100644 crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_type_assertion.js diff --git a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_class.js b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_class.js new file mode 100644 index 000000000000..0a5ef373af29 --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_class.js @@ -0,0 +1,8 @@ +var Foo = function(Foo) { + Foo[Foo["a"] = (class { + constructor(b){ + this.b = b; + } + }, 0)] = "a"; + return Foo; +}(Foo || {}); diff --git a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_enum.js b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_enum.js new file mode 100644 index 000000000000..d058bd1f7b83 --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_enum.js @@ -0,0 +1,10 @@ +var Foo = function(Foo) { + Foo[Foo["a"] = (()=>{ + (function(Bar) { + Bar["a"] = "a"; + Bar["b"] = "b"; + })(Bar); + return 0; + })()] = "a"; + return Foo; +}(Foo || {}); diff --git a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_opaque_expr.js b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_opaque_expr.js new file mode 100644 index 000000000000..7cf03824fe03 --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_opaque_expr.js @@ -0,0 +1,4 @@ +var Foo = function(Foo) { + Foo[Foo["a"] = foo('x')] = "a"; + return Foo; +}(Foo || {}); diff --git a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_type_assertion.js b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_type_assertion.js new file mode 100644 index 000000000000..cf0db8fbfd00 --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_type_assertion.js @@ -0,0 +1,5 @@ +var RefType = /*#__PURE__*/ function(RefType) { + RefType["property"] = "11"; + RefType["event"] = "22"; + return RefType; +}(RefType || {}); diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/1/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/1/output.js index 43efb1aa886a..e3096d2bb3e8 100644 --- a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/1/output.js +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/1/output.js @@ -3,7 +3,7 @@ var Foo = function(Foo) { Foo[Foo["a"] = 0] = "a"; Foo[Foo["b"] = 0] = "b"; Foo[Foo["c"] = 1] = "c"; - Foo[Foo["d"] = 1 + Foo.c * x] = "d"; + Foo[Foo["d"] = 1 + 1 * x] = "d"; Foo[Foo["e"] = 2 * Foo.d] = "e"; Foo[Foo["f"] = Foo.e * 10] = "f"; return Foo; diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js index 827575257ba9..bf872efbdd86 100644 --- a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js @@ -17,6 +17,6 @@ var Baz = function(Baz) { Baz[Baz["a"] = 0] = "a"; Baz[Baz["b"] = 1] = "b"; // @ts-ignore - Baz[Baz["x"] = Baz.a.toString()] = "x"; + Baz[Baz["x"] = 0..toString()] = "x"; return Baz; }(Baz || {}); diff --git a/crates/swc_ecma_transforms_typescript/tests/strip.rs b/crates/swc_ecma_transforms_typescript/tests/strip.rs index 73845810526a..1f3eab3ef3e4 100644 --- a/crates/swc_ecma_transforms_typescript/tests/strip.rs +++ b/crates/swc_ecma_transforms_typescript/tests/strip.rs @@ -353,6 +353,43 @@ to!( }" ); +to!( + ts_enum_with_type_assertion, + "enum RefType { + property = '11' as any, + event = '22' as any, +}" +); + +to!( + ts_enum_with_opaque_expr, + "enum Foo { + a = foo('x' as any), +}" +); + +to!( + ts_enum_with_nested_class, + "enum Foo { + a = (class { + constructor(public b: string) { } + }, 0) +}" +); + +to!( + ts_enum_with_nested_enum, + "enum Foo { + a = (() => { + enum Bar { + a = 'a', + b = 'b', + } + return 0; + })(), +}" +); + to!(module_01, "module 'foo'{ }"); to!(declare_01, "declare var env: FOO"); From 891439e255826852476a51a9cc457dbebaa614f2 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 00:07:42 +0800 Subject: [PATCH 03/16] refactor(es/typescript): simplify enum value computation and transform logic --- .../src/transform.rs | 30 +++++++++++-------- .../src/ts_enum.rs | 6 +--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index af69bc9f5e9d..231643b8f99d 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -22,7 +22,7 @@ use crate::{ retain::{should_retain_module_item, should_retain_stmt}, semantic::SemanticInfo, shared::enum_member_id_atom, - ts_enum::{TsEnumRecordKey, TsEnumRecordValue}, + ts_enum::{EnumValueComputer, TsEnumRecordKey, TsEnumRecordValue}, utils::{assign_value_to_this_private_prop, assign_value_to_this_prop, Factory}, }; @@ -1063,7 +1063,16 @@ impl Transform { member_name: name.clone(), }; - let value = self.semantic.enum_record.get(&key).unwrap().clone(); + let mut value = self.semantic.enum_record.get(&key).unwrap().clone(); + + if let TsEnumRecordValue::Opaque(expr) = &mut value { + *expr = m.init.unwrap(); + expr.visit_mut_with(&mut EnumValueComputer { + enum_id: &id.to_id(), + unresolved_ctxt: self.unresolved_ctxt, + record: &self.semantic.enum_record, + }); + } EnumMemberItem { span, name, value } }) @@ -1074,22 +1083,17 @@ impl Transform { return FoldedDecl::Empty; } - let namespace_export = self.namespace_id.is_some() && is_export; - let iife = !is_first || namespace_export; - - let mut opaque = false; + let opaque = member_list + .iter() + .any(|item| matches!(item.value, TsEnumRecordValue::Opaque(..))); let stmts = member_list .into_iter() .filter(|item| !ts_enum_safe_remove || !item.is_const()) - .map(|mut item| { - if let TsEnumRecordValue::Opaque(ref mut expr) = item.value { - opaque = true; - expr.visit_mut_with(self); - } + .map(|item| item.build_assign(&id.to_id())); - item.build_assign(&id.to_id()) - }); + let namespace_export = self.namespace_id.is_some() && is_export; + let iife = !is_first || namespace_export; let body = if !iife { let return_stmt: Stmt = ReturnStmt { diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index 9c6d101f6565..c325686ed5bb 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -112,11 +112,7 @@ pub(crate) struct EnumValueComputer<'a> { /// https://github.com/microsoft/TypeScript/pull/50528 impl EnumValueComputer<'_> { pub fn compute(&mut self, expr: Box) -> TsEnumRecordValue { - let mut expr = self.compute_rec(expr); - if let TsEnumRecordValue::Opaque(expr) = &mut expr { - expr.visit_mut_with(self); - } - expr + self.compute_rec(expr) } fn compute_rec(&self, expr: Box) -> TsEnumRecordValue { From 96965d9dce43b15f4c5ff86f469e046aae51bed4 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 00:08:13 +0800 Subject: [PATCH 04/16] chore: update test cases --- .../tests/__swc_snapshots__/tests/strip.rs/issue_6219.js | 2 +- .../tests/strip.rs/ts_enum_with_nested_enum.js | 5 +++-- .../tests/fixture/issue-3001/1/output.js | 2 +- .../tests/fixture/issue-3001/2/output.js | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/issue_6219.js b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/issue_6219.js index bdf7b73520d3..0ad742466698 100644 --- a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/issue_6219.js +++ b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/issue_6219.js @@ -1,4 +1,4 @@ var A = function(A) { - A[A["a"] = a] = "a"; + A[A["a"] = A.a] = "a"; return A; }(A || {}); diff --git a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_enum.js b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_enum.js index d058bd1f7b83..28f661ae8134 100644 --- a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_enum.js +++ b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_with_nested_enum.js @@ -1,9 +1,10 @@ var Foo = function(Foo) { Foo[Foo["a"] = (()=>{ - (function(Bar) { + let Bar = /*#__PURE__*/ function(Bar) { Bar["a"] = "a"; Bar["b"] = "b"; - })(Bar); + return Bar; + }({}); return 0; })()] = "a"; return Foo; diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/1/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/1/output.js index e3096d2bb3e8..43efb1aa886a 100644 --- a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/1/output.js +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/1/output.js @@ -3,7 +3,7 @@ var Foo = function(Foo) { Foo[Foo["a"] = 0] = "a"; Foo[Foo["b"] = 0] = "b"; Foo[Foo["c"] = 1] = "c"; - Foo[Foo["d"] = 1 + 1 * x] = "d"; + Foo[Foo["d"] = 1 + Foo.c * x] = "d"; Foo[Foo["e"] = 2 * Foo.d] = "e"; Foo[Foo["f"] = Foo.e * 10] = "f"; return Foo; diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js index bf872efbdd86..bdbb9c0ae632 100644 --- a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js @@ -2,7 +2,7 @@ var x = 10; var Foo = function(Foo) { Foo[Foo["a"] = 10] = "a"; Foo[Foo["b"] = 10] = "b"; - Foo[Foo["c"] = 10 + x] = "c"; + Foo[Foo["c"] = Foo.b + x] = "c"; Foo[Foo["d"] = Foo.c] = "d"; return Foo; }(Foo || {}); @@ -17,6 +17,6 @@ var Baz = function(Baz) { Baz[Baz["a"] = 0] = "a"; Baz[Baz["b"] = 1] = "b"; // @ts-ignore - Baz[Baz["x"] = 0..toString()] = "x"; + Baz[Baz["x"] = Baz.a.toString()] = "x"; return Baz; }(Baz || {}); From 64335328f7a7f30183be4e58f692549e733e9619 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 00:31:20 +0800 Subject: [PATCH 05/16] chore: update test cases --- crates/swc/tests/tsc-references/constEnum2.1.normal.js | 2 +- crates/swc/tests/tsc-references/constEnum2.2.minified.js | 2 +- crates/swc/tests/tsc-references/enumBasics.1.normal.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/swc/tests/tsc-references/constEnum2.1.normal.js b/crates/swc/tests/tsc-references/constEnum2.1.normal.js index 3ebd779cec05..798553f9cf97 100644 --- a/crates/swc/tests/tsc-references/constEnum2.1.normal.js +++ b/crates/swc/tests/tsc-references/constEnum2.1.normal.js @@ -6,7 +6,7 @@ var CONST = 9000 % 2; var D = function(D) { D[D["e"] = 199 * Math.floor(Math.random() * 1000)] = "e"; - D[D["f"] = 10 - 100 * Math.floor(Math.random() % 8)] = "f"; + D[D["f"] = D.d - 100 * Math.floor(Math.random() % 8)] = "f"; D[D["g"] = CONST] = "g"; return D; }(D || {}); diff --git a/crates/swc/tests/tsc-references/constEnum2.2.minified.js b/crates/swc/tests/tsc-references/constEnum2.2.minified.js index 01cbd3326368..69edab31f883 100644 --- a/crates/swc/tests/tsc-references/constEnum2.2.minified.js +++ b/crates/swc/tests/tsc-references/constEnum2.2.minified.js @@ -1,2 +1,2 @@ //// [constEnum2.ts] -var D, D1 = ((D = D1 || {})[D.e = 199 * Math.floor(1000 * Math.random())] = "e", D[D.f = 10 - 100 * Math.floor(Math.random() % 8)] = "f", D[D.g = 0] = "g", D); +var D, D1 = ((D = D1 || {})[D.e = 199 * Math.floor(1000 * Math.random())] = "e", D[D.f = D.d - 100 * Math.floor(Math.random() % 8)] = "f", D[D.g = 0] = "g", D); diff --git a/crates/swc/tests/tsc-references/enumBasics.1.normal.js b/crates/swc/tests/tsc-references/enumBasics.1.normal.js index ec873d6774a4..779301336708 100644 --- a/crates/swc/tests/tsc-references/enumBasics.1.normal.js +++ b/crates/swc/tests/tsc-references/enumBasics.1.normal.js @@ -26,7 +26,7 @@ var E2 = /*#__PURE__*/ function(E2) { var E3 = function(E3) { E3[E3["X"] = 'foo'.length] = "X"; E3[E3["Y"] = 7] = "Y"; - E3[E3["Z"] = +"foo"] = "Z"; + E3[E3["Z"] = +'foo'] = "Z"; return E3; }(E3 || {}); // Enum with constant members followed by computed members From fd25b4152339af6b152ac0bcc1ea1b0a0fc8fa95 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 19:03:40 +0800 Subject: [PATCH 06/16] fix(typescript): Handle enum member references correctly --- .../src/resolver/mod.rs | 23 ++++--- .../src/transform.rs | 59 ++++++++++++++-- .../src/ts_enum.rs | 67 +++++++------------ 3 files changed, 92 insertions(+), 57 deletions(-) diff --git a/crates/swc_ecma_transforms_base/src/resolver/mod.rs b/crates/swc_ecma_transforms_base/src/resolver/mod.rs index cc82f5a18aab..ab88d85411a5 100644 --- a/crates/swc_ecma_transforms_base/src/resolver/mod.rs +++ b/crates/swc_ecma_transforms_base/src/resolver/mod.rs @@ -1258,15 +1258,20 @@ impl VisitMut for Resolver<'_> { self.modify(&mut decl.id, DeclKind::Lexical); self.with_child(ScopeKind::Block, |child| { - // add the enum member names as declared symbols for this scope - // Ex. `enum Foo { a, b = a }` - let member_names = decl.members.iter().filter_map(|m| match &m.id { - TsEnumMemberId::Ident(id) => Some((id.sym.clone(), DeclKind::Lexical)), - TsEnumMemberId::Str(_) => None, - #[cfg(swc_ast_unknown)] - _ => None, - }); - child.current.declared_symbols.extend(member_names); + // Intentionally do not predeclare enum members in the resolver. + // Enum initializers may reference other members, including quoted names whose + // text is a valid identifier: + // + // ```TypeScript + // enum E { + // A = "A", + // "B" = "B", + // C = (() => { console.log(A, B); })(), + // } + // ``` + // + // We keep those references unresolved here so the TypeScript enum transform + // can rewrite them using semantic.enum_record. decl.members.visit_mut_with(child); }); diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index 231643b8f99d..91697b14b7b6 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -22,7 +22,7 @@ use crate::{ retain::{should_retain_module_item, should_retain_stmt}, semantic::SemanticInfo, shared::enum_member_id_atom, - ts_enum::{EnumValueComputer, TsEnumRecordKey, TsEnumRecordValue}, + ts_enum::{TsEnumRecordKey, TsEnumRecordValue}, utils::{assign_value_to_this_private_prop, assign_value_to_this_prop, Factory}, }; @@ -1052,6 +1052,13 @@ impl Transform { && !is_export && !self.semantic.exported_binding.contains_key(&id.to_id()); + let member_names = self + .semantic + .enum_record + .keys() + .map(|k| k.member_name.clone()) + .collect(); + let member_list: Vec<_> = members .into_iter() .map(|m| { @@ -1067,10 +1074,12 @@ impl Transform { if let TsEnumRecordValue::Opaque(expr) = &mut value { *expr = m.init.unwrap(); - expr.visit_mut_with(&mut EnumValueComputer { - enum_id: &id.to_id(), - unresolved_ctxt: self.unresolved_ctxt, - record: &self.semantic.enum_record, + expr.visit_mut_with(&mut RefRewriter { + query: EnumMemberRefQuery { + enum_id: &id.to_id(), + member_names: &member_names, + unresolved_ctxt: self.unresolved_ctxt, + }, }); } @@ -1826,6 +1835,46 @@ impl QueryRef for ExportQuery { } } +struct EnumMemberRefQuery<'a> { + enum_id: &'a Id, + member_names: &'a FxHashSet, + unresolved_ctxt: SyntaxContext, +} + +impl QueryRef for EnumMemberRefQuery<'_> { + fn query_ref(&self, ident: &Ident) -> Option> { + if ident.ctxt == self.unresolved_ctxt && self.member_names.contains(&ident.sym) { + Some( + self.enum_id + .clone() + .make_member(ident.clone().into()) + .into(), + ) + } else { + None + } + } + + fn query_lhs(&self, ident: &Ident) -> Option> { + self.query_ref(ident) + } + + fn query_jsx(&self, ident: &Ident) -> Option { + if ident.ctxt == self.unresolved_ctxt && self.member_names.contains(&ident.sym) { + Some( + JSXMemberExpr { + span: DUMMY_SP, + obj: JSXObject::Ident(self.enum_id.clone().into()), + prop: ident.clone().into(), + } + .into(), + ) + } else { + None + } + } +} + struct EnumMemberItem { span: Span, name: Atom, diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index c325686ed5bb..517c1b4d3d46 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -6,7 +6,6 @@ use swc_ecma_utils::{ number::{JsNumber, ToJsString}, ExprFactory, }; -use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; #[inline] fn atom_from_wtf8_atom(value: &Wtf8Atom) -> Atom { @@ -119,33 +118,36 @@ impl EnumValueComputer<'_> { match *expr { Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(atom_from_wtf8_atom(&s.value)), Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n.value.into()), - Expr::Ident(Ident { ctxt, sym, .. }) - if &*sym == "NaN" && ctxt == self.unresolved_ctxt => + Expr::Ident(Ident { ctxt, ref sym, .. }) + if *sym == "NaN" && ctxt == self.unresolved_ctxt => { TsEnumRecordValue::Number(f64::NAN.into()) } - Expr::Ident(Ident { ctxt, sym, .. }) - if &*sym == "Infinity" && ctxt == self.unresolved_ctxt => + Expr::Ident(Ident { ctxt, ref sym, .. }) + if *sym == "Infinity" && ctxt == self.unresolved_ctxt => { TsEnumRecordValue::Number(f64::INFINITY.into()) } - Expr::Ident(ref ident) => self - .record - .get(&TsEnumRecordKey { - enum_id: self.enum_id.clone(), - member_name: ident.sym.clone(), - }) - .cloned() - .map(|value| match value { - TsEnumRecordValue::String(..) | TsEnumRecordValue::Number(..) => value, - _ => TsEnumRecordValue::Opaque( - self.enum_id - .clone() - .make_member(ident.clone().into()) - .into(), - ), - }) - .unwrap_or_else(|| TsEnumRecordValue::Opaque(expr)), + Expr::Ident(ref ident @ Ident { ctxt, ref sym, .. }) + if ctxt == self.unresolved_ctxt => + { + self.record + .get(&TsEnumRecordKey { + enum_id: self.enum_id.clone(), + member_name: sym.clone(), + }) + .cloned() + .map(|value| match value { + TsEnumRecordValue::String(..) | TsEnumRecordValue::Number(..) => value, + _ => TsEnumRecordValue::Opaque( + self.enum_id + .clone() + .make_member(ident.clone().into()) + .into(), + ), + }) + .unwrap_or_else(|| TsEnumRecordValue::Opaque(expr)) + } Expr::Paren(e) => self.compute_rec(e.expr), Expr::Unary(e) => self.compute_unary(e), Expr::Bin(e) => self.compute_bin(e), @@ -321,24 +323,3 @@ impl EnumValueComputer<'_> { TsEnumRecordValue::String(string.into()) } } - -impl VisitMut for EnumValueComputer<'_> { - noop_visit_mut_type!(); - - fn visit_mut_expr(&mut self, expr: &mut Expr) { - expr.visit_mut_children_with(self); - - let Expr::Ident(ident) = expr else { return }; - - if self.record.contains_key(&TsEnumRecordKey { - enum_id: self.enum_id.clone(), - member_name: ident.sym.clone(), - }) { - *expr = self - .enum_id - .clone() - .make_member(ident.clone().into()) - .into(); - } - } -} From d3b6400a181f729f2f386ecbefecba48dbae9c37 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 19:03:48 +0800 Subject: [PATCH 07/16] chore: update test cases --- .../tests/fixture/issues-11xxx/11761/input/.swcrc | 13 +++++++++++++ .../swc/tests/fixture/issues-11xxx/11761/input/a.ts | 6 ++++++ .../swc/tests/fixture/issues-11xxx/11761/input/b.ts | 4 ++++ .../swc/tests/fixture/issues-11xxx/11761/input/c.ts | 10 ++++++++++ .../tests/fixture/issues-11xxx/11761/output/a.ts | 6 ++++++ .../tests/fixture/issues-11xxx/11761/output/b.ts | 5 +++++ .../tests/fixture/issues-11xxx/11761/output/c.ts | 11 +++++++++++ 7 files changed, 55 insertions(+) create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/input/.swcrc create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/input/a.ts create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/input/b.ts create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/input/c.ts create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/output/a.ts create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/output/b.ts create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/output/c.ts diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/input/.swcrc b/crates/swc/tests/fixture/issues-11xxx/11761/input/.swcrc new file mode 100644 index 000000000000..3842b809b59a --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/input/.swcrc @@ -0,0 +1,13 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": false + }, + "target": "es2024" + }, + "module": { + "type": "es6" + }, + "isModule": true +} diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/input/a.ts b/crates/swc/tests/fixture/issues-11xxx/11761/input/a.ts new file mode 100644 index 000000000000..81577b37e8da --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/input/a.ts @@ -0,0 +1,6 @@ +export enum RefType { + property = "11" as any, + event = "22" as any, +} + +console.log(RefType.property, RefType.event); diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/input/b.ts b/crates/swc/tests/fixture/issues-11xxx/11761/input/b.ts new file mode 100644 index 000000000000..7cde4c2c50b7 --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/input/b.ts @@ -0,0 +1,4 @@ +enum E { + A = ((B: number) => B)(2), + B = 1, +} diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/input/c.ts b/crates/swc/tests/fixture/issues-11xxx/11761/input/c.ts new file mode 100644 index 000000000000..48d313510044 --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/input/c.ts @@ -0,0 +1,10 @@ +enum E { + A, + B, + C, + D = ((C) => { + console.log(A, B, C, F); + return 2; + })(), + F = "F", +} diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/output/a.ts b/crates/swc/tests/fixture/issues-11xxx/11761/output/a.ts new file mode 100644 index 000000000000..54b8ee72b71a --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/output/a.ts @@ -0,0 +1,6 @@ +export var RefType = /*#__PURE__*/ function(RefType) { + RefType["property"] = "11"; + RefType["event"] = "22"; + return RefType; +}({}); +console.log("11", "22"); diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/output/b.ts b/crates/swc/tests/fixture/issues-11xxx/11761/output/b.ts new file mode 100644 index 000000000000..2b23f5fc226c --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/output/b.ts @@ -0,0 +1,5 @@ +var E = function(E) { + E[E["A"] = ((B)=>B)(2)] = "A"; + E[E["B"] = 1] = "B"; + return E; +}(E || {}); diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/output/c.ts b/crates/swc/tests/fixture/issues-11xxx/11761/output/c.ts new file mode 100644 index 000000000000..614e656345b9 --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/output/c.ts @@ -0,0 +1,11 @@ +var E = function(E) { + E[E["A"] = 0] = "A"; + E[E["B"] = 1] = "B"; + E[E["C"] = 2] = "C"; + E[E["D"] = ((C)=>{ + console.log(E.A, E.B, C, E.F); + return 2; + })()] = "D"; + E["F"] = "F"; + return E; +}(E || {}); From fd53909b1e4d31ad8319c496a5eb63aeaa3bdb9a Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 20:23:59 +0800 Subject: [PATCH 08/16] fix: clippy issue --- crates/swc_ecma_transforms_typescript/src/ts_enum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index 517c1b4d3d46..647d70d50f73 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -110,7 +110,7 @@ pub(crate) struct EnumValueComputer<'a> { /// https://github.com/microsoft/TypeScript/pull/50528 impl EnumValueComputer<'_> { - pub fn compute(&mut self, expr: Box) -> TsEnumRecordValue { + pub fn compute(&self, expr: Box) -> TsEnumRecordValue { self.compute_rec(expr) } From 977d1f75d492b4091d8db86921b42503966df6c9 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 21:50:56 +0800 Subject: [PATCH 09/16] fix(es/typescript): handle enum member names in resolver --- crates/swc_ecma_transforms_base/src/resolver/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/swc_ecma_transforms_base/src/resolver/mod.rs b/crates/swc_ecma_transforms_base/src/resolver/mod.rs index ab88d85411a5..0f69b825553e 100644 --- a/crates/swc_ecma_transforms_base/src/resolver/mod.rs +++ b/crates/swc_ecma_transforms_base/src/resolver/mod.rs @@ -1272,6 +1272,19 @@ impl VisitMut for Resolver<'_> { // // We keep those references unresolved here so the TypeScript enum transform // can rewrite them using semantic.enum_record. + child.current.mark = self.config.unresolved_mark; + // add the enum member names as declared symbols for this scope + // Ex. `enum Foo { a, b = a }` + let member_names = decl.members.iter().filter_map(|m| match &m.id { + TsEnumMemberId::Ident(id) => Some((id.sym.clone(), DeclKind::Lexical)), + TsEnumMemberId::Str(s) => s + .value + .as_atom() + .map(|atom| (atom.clone(), DeclKind::Lexical)), + #[cfg(swc_ast_unknown)] + _ => None, + }); + child.current.declared_symbols.extend(member_names); decl.members.visit_mut_with(child); }); From fa963a610f60134fd4c989f87f16311b6c23a847 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 21:51:28 +0800 Subject: [PATCH 10/16] chore: update test cases --- crates/swc/tests/fixture/issues-11xxx/11761/input/d.ts | 5 +++++ crates/swc/tests/fixture/issues-11xxx/11761/output/d.ts | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/input/d.ts create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/output/d.ts diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/input/d.ts b/crates/swc/tests/fixture/issues-11xxx/11761/input/d.ts new file mode 100644 index 000000000000..32748c5993c9 --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/input/d.ts @@ -0,0 +1,5 @@ +const A = 100; +enum E { + A = foo(), + B = A, +} diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/output/d.ts b/crates/swc/tests/fixture/issues-11xxx/11761/output/d.ts new file mode 100644 index 000000000000..37e1a9870dfa --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/output/d.ts @@ -0,0 +1,6 @@ +const A = 100; +var E = function(E) { + E[E["A"] = foo()] = "A"; + E[E["B"] = E.A] = "B"; + return E; +}(E || {}); From d2c98c1acc2a16c2e260a696910e0de6ef6c0f29 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 21:57:13 +0800 Subject: [PATCH 11/16] fix: apply copilot suggestions --- crates/swc_ecma_transforms_typescript/src/ts_enum.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index 647d70d50f73..601130c9c29b 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -119,12 +119,12 @@ impl EnumValueComputer<'_> { Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(atom_from_wtf8_atom(&s.value)), Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n.value.into()), Expr::Ident(Ident { ctxt, ref sym, .. }) - if *sym == "NaN" && ctxt == self.unresolved_ctxt => + if sym == "NaN" && ctxt == self.unresolved_ctxt => { TsEnumRecordValue::Number(f64::NAN.into()) } Expr::Ident(Ident { ctxt, ref sym, .. }) - if *sym == "Infinity" && ctxt == self.unresolved_ctxt => + if sym == "Infinity" && ctxt == self.unresolved_ctxt => { TsEnumRecordValue::Number(f64::INFINITY.into()) } From 930359506d408063599f12308091d7f018425957 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 22:01:05 +0800 Subject: [PATCH 12/16] fix: apply copilot suggestions --- crates/swc_ecma_transforms_typescript/src/transform.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index 91697b14b7b6..07e00dcee0d5 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -1056,6 +1056,7 @@ impl Transform { .semantic .enum_record .keys() + .filter(|k| k.enum_id == id.to_id()) .map(|k| k.member_name.clone()) .collect(); From 3d75291b7c84bd154a73b025153ee9ecfa1f5fe0 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 7 Apr 2026 23:33:00 +0800 Subject: [PATCH 13/16] fix: apply copilot suggestions --- .../fixture/issues-11xxx/11761/input/e.ts | 4 ++ .../fixture/issues-11xxx/11761/output/e.ts | 5 +++ .../src/resolver/mod.rs | 8 ++-- .../src/ts_enum.rs | 45 +++++++------------ 4 files changed, 29 insertions(+), 33 deletions(-) create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/input/e.ts create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/output/e.ts diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/input/e.ts b/crates/swc/tests/fixture/issues-11xxx/11761/input/e.ts new file mode 100644 index 000000000000..5d22d09f826c --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/input/e.ts @@ -0,0 +1,4 @@ +enum E { + Infinity = 1, + B = Infinity, +} diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/output/e.ts b/crates/swc/tests/fixture/issues-11xxx/11761/output/e.ts new file mode 100644 index 000000000000..c22cc9434a22 --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/output/e.ts @@ -0,0 +1,5 @@ +var E = /*#__PURE__*/ function(E) { + E[E["Infinity"] = 1] = "Infinity"; + E[E["B"] = 1] = "B"; + return E; +}(E || {}); diff --git a/crates/swc_ecma_transforms_base/src/resolver/mod.rs b/crates/swc_ecma_transforms_base/src/resolver/mod.rs index 0f69b825553e..a34fe3b6ab4a 100644 --- a/crates/swc_ecma_transforms_base/src/resolver/mod.rs +++ b/crates/swc_ecma_transforms_base/src/resolver/mod.rs @@ -1258,7 +1258,7 @@ impl VisitMut for Resolver<'_> { self.modify(&mut decl.id, DeclKind::Lexical); self.with_child(ScopeKind::Block, |child| { - // Intentionally do not predeclare enum members in the resolver. + // Predeclare enum members in a child scope marked with `unresolved_mark`. // Enum initializers may reference other members, including quoted names whose // text is a valid identifier: // @@ -1270,8 +1270,10 @@ impl VisitMut for Resolver<'_> { // } // ``` // - // We keep those references unresolved here so the TypeScript enum transform - // can rewrite them using semantic.enum_record. + // This keeps references like `A`, `B`, and `b = a` in the unresolved + // context instead of resolving them to the enum's lexical scope, so the + // TypeScript enum transform can rewrite them later using + // `semantic.enum_record`. child.current.mark = self.config.unresolved_mark; // add the enum member names as declared symbols for this scope // Ex. `enum Foo { a, b = a }` diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index 601130c9c29b..84ce5370df92 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -2,10 +2,7 @@ use rustc_hash::FxHashMap; use swc_atoms::{atom, Atom, Wtf8Atom}; use swc_common::{SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; -use swc_ecma_utils::{ - number::{JsNumber, ToJsString}, - ExprFactory, -}; +use swc_ecma_utils::number::{JsNumber, ToJsString}; #[inline] fn atom_from_wtf8_atom(value: &Wtf8Atom) -> Atom { @@ -118,35 +115,23 @@ impl EnumValueComputer<'_> { match *expr { Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(atom_from_wtf8_atom(&s.value)), Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n.value.into()), - Expr::Ident(Ident { ctxt, ref sym, .. }) - if sym == "NaN" && ctxt == self.unresolved_ctxt => - { - TsEnumRecordValue::Number(f64::NAN.into()) - } - Expr::Ident(Ident { ctxt, ref sym, .. }) - if sym == "Infinity" && ctxt == self.unresolved_ctxt => - { - TsEnumRecordValue::Number(f64::INFINITY.into()) - } - Expr::Ident(ref ident @ Ident { ctxt, ref sym, .. }) - if ctxt == self.unresolved_ctxt => - { - self.record + Expr::Ident(ref ident) if ident.ctxt == self.unresolved_ctxt => { + if let Some(value) = self + .record .get(&TsEnumRecordKey { enum_id: self.enum_id.clone(), - member_name: sym.clone(), - }) - .cloned() - .map(|value| match value { - TsEnumRecordValue::String(..) | TsEnumRecordValue::Number(..) => value, - _ => TsEnumRecordValue::Opaque( - self.enum_id - .clone() - .make_member(ident.clone().into()) - .into(), - ), + member_name: ident.sym.clone(), }) - .unwrap_or_else(|| TsEnumRecordValue::Opaque(expr)) + .filter(|value| value.is_const()) + { + value.clone() + } else { + match ident.sym.as_ref() { + "Infinity" => TsEnumRecordValue::Number(f64::INFINITY.into()), + "NaN" => TsEnumRecordValue::Number(f64::NAN.into()), + _ => TsEnumRecordValue::Opaque(expr), + } + } } Expr::Paren(e) => self.compute_rec(e.expr), Expr::Unary(e) => self.compute_unary(e), From 1062344c892fcfd0aeea98d0fede44e9aafe6304 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Wed, 8 Apr 2026 01:33:56 +0800 Subject: [PATCH 14/16] fix(es/typescript): correct enum inline --- .../tsc-references/constEnum2.1.normal.js | 2 +- .../tsc-references/constEnum2.2.minified.js | 2 +- .../src/transform.rs | 18 +++++++++++++++--- .../tests/fixture/issue-3001/2/output.js | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/swc/tests/tsc-references/constEnum2.1.normal.js b/crates/swc/tests/tsc-references/constEnum2.1.normal.js index 798553f9cf97..3ebd779cec05 100644 --- a/crates/swc/tests/tsc-references/constEnum2.1.normal.js +++ b/crates/swc/tests/tsc-references/constEnum2.1.normal.js @@ -6,7 +6,7 @@ var CONST = 9000 % 2; var D = function(D) { D[D["e"] = 199 * Math.floor(Math.random() * 1000)] = "e"; - D[D["f"] = D.d - 100 * Math.floor(Math.random() % 8)] = "f"; + D[D["f"] = 10 - 100 * Math.floor(Math.random() % 8)] = "f"; D[D["g"] = CONST] = "g"; return D; }(D || {}); diff --git a/crates/swc/tests/tsc-references/constEnum2.2.minified.js b/crates/swc/tests/tsc-references/constEnum2.2.minified.js index 69edab31f883..01cbd3326368 100644 --- a/crates/swc/tests/tsc-references/constEnum2.2.minified.js +++ b/crates/swc/tests/tsc-references/constEnum2.2.minified.js @@ -1,2 +1,2 @@ //// [constEnum2.ts] -var D, D1 = ((D = D1 || {})[D.e = 199 * Math.floor(1000 * Math.random())] = "e", D[D.f = D.d - 100 * Math.floor(Math.random() % 8)] = "f", D[D.g = 0] = "g", D); +var D, D1 = ((D = D1 || {})[D.e = 199 * Math.floor(1000 * Math.random())] = "e", D[D.f = 10 - 100 * Math.floor(Math.random() % 8)] = "f", D[D.g = 0] = "g", D); diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index 07e00dcee0d5..319b08ecf436 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -22,7 +22,7 @@ use crate::{ retain::{should_retain_module_item, should_retain_stmt}, semantic::SemanticInfo, shared::enum_member_id_atom, - ts_enum::{TsEnumRecordKey, TsEnumRecordValue}, + ts_enum::{EnumValueComputer, TsEnumRecordKey, TsEnumRecordValue}, utils::{assign_value_to_this_private_prop, assign_value_to_this_prop, Factory}, }; @@ -1060,6 +1060,12 @@ impl Transform { .map(|k| k.member_name.clone()) .collect(); + let enum_computer = EnumValueComputer { + enum_id: &id.to_id(), + unresolved_ctxt: self.unresolved_ctxt, + record: &self.semantic.enum_record, + }; + let member_list: Vec<_> = members .into_iter() .map(|m| { @@ -1074,14 +1080,20 @@ impl Transform { let mut value = self.semantic.enum_record.get(&key).unwrap().clone(); if let TsEnumRecordValue::Opaque(expr) = &mut value { - *expr = m.init.unwrap(); - expr.visit_mut_with(&mut RefRewriter { + let e = m.init.unwrap(); + // [TODO]: We have computed twice for TsEnumRecordValue::Opaque case. + // Try to avoid this if it causes performance issue. + let TsEnumRecordValue::Opaque(mut e) = enum_computer.compute(e) else { + unreachable!(); + }; + e.visit_mut_with(&mut RefRewriter { query: EnumMemberRefQuery { enum_id: &id.to_id(), member_names: &member_names, unresolved_ctxt: self.unresolved_ctxt, }, }); + *expr = e; } EnumMemberItem { span, name, value } diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js index bdbb9c0ae632..827575257ba9 100644 --- a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-3001/2/output.js @@ -2,7 +2,7 @@ var x = 10; var Foo = function(Foo) { Foo[Foo["a"] = 10] = "a"; Foo[Foo["b"] = 10] = "b"; - Foo[Foo["c"] = Foo.b + x] = "c"; + Foo[Foo["c"] = 10 + x] = "c"; Foo[Foo["d"] = Foo.c] = "d"; return Foo; }(Foo || {}); From 00d05585e9cec8984ecc6de515529e8a6f01b242 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Wed, 8 Apr 2026 01:51:11 +0800 Subject: [PATCH 15/16] fix --- .../fixture/issues-11xxx/11761/input/f.ts | 4 +++ .../fixture/issues-11xxx/11761/output/f.ts | 5 ++++ .../tsc-references/enumBasics.1.normal.js | 2 +- .../src/ts_enum.rs | 28 ++++++++++++------- 4 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/input/f.ts create mode 100644 crates/swc/tests/fixture/issues-11xxx/11761/output/f.ts diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/input/f.ts b/crates/swc/tests/fixture/issues-11xxx/11761/input/f.ts new file mode 100644 index 000000000000..069e9edb5f31 --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/input/f.ts @@ -0,0 +1,4 @@ +enum E { + Infinity = foo(), + B = Infinity, +} diff --git a/crates/swc/tests/fixture/issues-11xxx/11761/output/f.ts b/crates/swc/tests/fixture/issues-11xxx/11761/output/f.ts new file mode 100644 index 000000000000..ba1c066ef299 --- /dev/null +++ b/crates/swc/tests/fixture/issues-11xxx/11761/output/f.ts @@ -0,0 +1,5 @@ +var E = function(E) { + E[E["Infinity"] = foo()] = "Infinity"; + E[E["B"] = E.Infinity] = "B"; + return E; +}(E || {}); diff --git a/crates/swc/tests/tsc-references/enumBasics.1.normal.js b/crates/swc/tests/tsc-references/enumBasics.1.normal.js index 779301336708..ec873d6774a4 100644 --- a/crates/swc/tests/tsc-references/enumBasics.1.normal.js +++ b/crates/swc/tests/tsc-references/enumBasics.1.normal.js @@ -26,7 +26,7 @@ var E2 = /*#__PURE__*/ function(E2) { var E3 = function(E3) { E3[E3["X"] = 'foo'.length] = "X"; E3[E3["Y"] = 7] = "Y"; - E3[E3["Z"] = +'foo'] = "Z"; + E3[E3["Z"] = +"foo"] = "Z"; return E3; }(E3 || {}); // Enum with constant members followed by computed members diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index 84ce5370df92..6da0c929d5fa 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -2,7 +2,10 @@ use rustc_hash::FxHashMap; use swc_atoms::{atom, Atom, Wtf8Atom}; use swc_common::{SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; -use swc_ecma_utils::number::{JsNumber, ToJsString}; +use swc_ecma_utils::{ + number::{JsNumber, ToJsString}, + ExprFactory, +}; #[inline] fn atom_from_wtf8_atom(value: &Wtf8Atom) -> Atom { @@ -116,15 +119,20 @@ impl EnumValueComputer<'_> { Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(atom_from_wtf8_atom(&s.value)), Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n.value.into()), Expr::Ident(ref ident) if ident.ctxt == self.unresolved_ctxt => { - if let Some(value) = self - .record - .get(&TsEnumRecordKey { - enum_id: self.enum_id.clone(), - member_name: ident.sym.clone(), - }) - .filter(|value| value.is_const()) - { - value.clone() + if let Some(value) = self.record.get(&TsEnumRecordKey { + enum_id: self.enum_id.clone(), + member_name: ident.sym.clone(), + }) { + if value.is_const() { + value.clone() + } else { + TsEnumRecordValue::Opaque( + self.enum_id + .clone() + .make_member(ident.clone().into()) + .into(), + ) + } } else { match ident.sym.as_ref() { "Infinity" => TsEnumRecordValue::Number(f64::INFINITY.into()), From c7f2b8b8de4254b2c84b55ce5cd5a391b24e9c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Wed, 8 Apr 2026 07:47:09 +0200 Subject: [PATCH 16/16] Create witty-mangos-swim.md --- .changeset/witty-mangos-swim.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/witty-mangos-swim.md diff --git a/.changeset/witty-mangos-swim.md b/.changeset/witty-mangos-swim.md new file mode 100644 index 000000000000..f09901c667e7 --- /dev/null +++ b/.changeset/witty-mangos-swim.md @@ -0,0 +1,7 @@ +--- +swc_ecma_transforms_typescript: patch +swc_ecma_transforms_base: patch +swc_core: patch +--- + +fix(es/typescript): Handle TypeScript expressions in enum transformation