diff --git a/.changeset/fix-ts-enum-const-var-folding.md b/.changeset/fix-ts-enum-const-var-folding.md new file mode 100644 index 000000000000..230fb8e1432a --- /dev/null +++ b/.changeset/fix-ts-enum-const-var-folding.md @@ -0,0 +1,5 @@ +--- +swc_core: patch +swc_ecma_transforms_typescript: patch +--- +fix(es/typescript): Treat const variable references as enum constants diff --git a/crates/swc/tests/tsc-references/constEnum2.1.normal.js b/crates/swc/tests/tsc-references/constEnum2.1.normal.js index 3ebd779cec05..5e298eb832dc 100644 --- a/crates/swc/tests/tsc-references/constEnum2.1.normal.js +++ b/crates/swc/tests/tsc-references/constEnum2.1.normal.js @@ -7,6 +7,5 @@ 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["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..64cf8153a14d 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 = 10 - 100 * Math.floor(Math.random() % 8)] = "f", D); diff --git a/crates/swc_ecma_transforms_typescript/src/semantic.rs b/crates/swc_ecma_transforms_typescript/src/semantic.rs index 8d0e138c74ae..7dde0b3ce8fb 100644 --- a/crates/swc_ecma_transforms_typescript/src/semantic.rs +++ b/crates/swc_ecma_transforms_typescript/src/semantic.rs @@ -7,7 +7,7 @@ use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; use crate::{ retain::{should_retain_decl, IsConcrete}, shared::{enum_member_name, get_module_ident}, - ts_enum::{EnumValueComputer, TsEnumRecord, TsEnumRecordKey, TsEnumRecordValue}, + ts_enum::{EnumValueComputer, EvalCtx, TsEnumRecord, TsEnumRecordKey, TsEnumRecordValue}, }; #[derive(Debug, Default)] @@ -19,6 +19,7 @@ pub(crate) struct SemanticInfo { pub enum_record: TsEnumRecord, pub const_enum: FxHashSet, pub namespace_import_equals_usage: FxHashSet, + pub const_vars: FxHashMap, } impl SemanticInfo { @@ -48,6 +49,7 @@ pub(crate) fn analyze_program( unresolved_mark: Mark, seed_usage: FxHashSet, flow_syntax: bool, + ts_enum_is_mutable: bool, ) -> SemanticInfo { let mut analyzer = SemanticAnalyzer { unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark), @@ -60,6 +62,7 @@ pub(crate) fn analyze_program( namespace_id: None, skip_transform_info: false, flow_syntax, + ts_enum_is_mutable, }; program.visit_with(&mut analyzer); @@ -75,6 +78,7 @@ struct SemanticAnalyzer { namespace_id: Option, skip_transform_info: bool, flow_syntax: bool, + ts_enum_is_mutable: bool, } #[derive(Default)] @@ -257,6 +261,7 @@ impl SemanticAnalyzer { enum_id: &Id, default_init: &TsEnumRecordValue, record: &TsEnumRecord, + const_vars: &FxHashMap, unresolved_ctxt: SyntaxContext, flow_syntax: bool, ) -> TsEnumRecordValue { @@ -267,8 +272,10 @@ impl SemanticAnalyzer { enum_id, unresolved_ctxt, record, + const_vars, + const_enum_only: None, } - .compute(expr) + .compute(expr, EvalCtx::MEMBER) }) .filter(TsEnumRecordValue::has_value) .unwrap_or_else(|| { @@ -536,6 +543,40 @@ impl Visit for SemanticAnalyzer { } } + fn visit_var_decl(&mut self, node: &VarDecl) { + let track = !self.skip_transform_info && !node.declare && node.kind == VarDeclKind::Const; + + for decl in &node.decls { + decl.visit_with(self); + + if !track { + continue; + } + + let Pat::Ident(BindingIdent { id, type_ann: None }) = &decl.name else { + continue; + }; + let Some(init) = &decl.init else { continue }; + + if !EnumValueComputer::can_fold_shape(init) { + continue; + } + + let value = EnumValueComputer { + enum_id: &id.to_id(), + unresolved_ctxt: self.unresolved_ctxt, + record: &self.info.enum_record, + const_vars: &self.info.const_vars, + const_enum_only: self.ts_enum_is_mutable.then_some(&self.info.const_enum), + } + .compute(init.clone(), EvalCtx::CONST_INIT); + + if value.is_const() { + self.info.const_vars.insert(id.to_id(), value); + } + } + } + fn visit_ts_enum_decl(&mut self, node: &TsEnumDecl) { node.visit_children_with(self); @@ -566,6 +607,7 @@ impl Visit for SemanticAnalyzer { &id.to_id(), &default_init, &self.info.enum_record, + &self.info.const_vars, self.unresolved_ctxt, self.flow_syntax, ); @@ -655,6 +697,7 @@ mod tests { &id("E"), &TsEnumRecordValue::Void, &Default::default(), + &Default::default(), SyntaxContext::empty(), true, ); @@ -672,6 +715,7 @@ mod tests { &id("E"), &TsEnumRecordValue::from(2.0), &Default::default(), + &Default::default(), SyntaxContext::empty(), false, ); diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index 0f530f21b19b..6300ce4ba84a 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -22,7 +22,9 @@ use crate::{ retain::{should_retain_module_item, should_retain_stmt}, semantic::SemanticInfo, shared::enum_member_name, - ts_enum::{static_enum_member_name, EnumValueComputer, TsEnumRecordKey, TsEnumRecordValue}, + ts_enum::{ + static_enum_member_name, EnumValueComputer, EvalCtx, TsEnumRecordKey, TsEnumRecordValue, + }, utils::{assign_value_to_this_private_prop, assign_value_to_this_prop, Factory}, }; @@ -1112,6 +1114,8 @@ impl Transform { enum_id: &id.to_id(), unresolved_ctxt: self.unresolved_ctxt, record: &self.semantic.enum_record, + const_vars: &self.semantic.const_vars, + const_enum_only: None, }; let member_list: Vec<_> = members @@ -1133,7 +1137,7 @@ impl Transform { // references can be rewritten to runtime property // accesses. Implicit Flow enum members do not have an // initializer, so keep the semantic value as-is. - let mut recomputed = enum_computer.compute(init); + let mut recomputed = enum_computer.compute(init, EvalCtx::RECOMPUTE); if let TsEnumRecordValue::Opaque(expr) = &mut recomputed { expr.visit_mut_with(&mut RefRewriter { query: EnumMemberRefQuery { diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index a05ef5139421..a4b3165407ec 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -1,4 +1,4 @@ -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use swc_atoms::{wtf8::Wtf8Buf, Atom, Wtf8Atom}; use swc_common::{SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; @@ -97,6 +97,14 @@ pub(crate) struct EnumValueComputer<'a> { pub enum_id: &'a Id, pub unresolved_ctxt: SyntaxContext, pub record: &'a TsEnumRecord, + pub const_vars: &'a FxHashMap, + /// When `Some`, only enums in this set may be resolved through a member + /// expression. + /// + /// Mirrors the `ts_enum_is_mutable` guard in `transform.rs`: a non-const + /// enum can be reassigned at runtime, so reads of its members are not + /// compile-time constants and must stay opaque. + pub const_enum_only: Option<&'a FxHashSet>, } /// Returns a statically known enum member key without discarding lone @@ -117,13 +125,82 @@ pub(crate) fn static_enum_member_name(property: &MemberProp) -> Option } } +/// Evaluation context for [`EnumValueComputer::compute_rec`]. +/// +/// TypeScript decides enum member constness from the *syntactic* form of the +/// initializer, without type resolution. See +/// https://github.com/microsoft/TypeScript/pull/50528 and the constraints +/// described in https://github.com/evanw/esbuild/issues/4387. +#[derive(Clone, Copy)] +pub(crate) struct EvalCtx { + /// Whether a reference to a `const` binding may be resolved to its value. + /// Disabled under type syntax: `enum E { A = foo as string }` is not a + /// constant for TypeScript even when `foo` is. + allow_const_var: bool, + /// Whether type syntax makes the whole expression non-constant instead of + /// being stripped. Used when evaluating a `const` initializer, where any + /// annotation or assertion removes constness. + ts_is_opaque: bool, +} + +impl EvalCtx { + /// Context for a `const` variable initializer. + pub(crate) const CONST_INIT: Self = Self { + allow_const_var: true, + ts_is_opaque: true, + }; + /// Context for an enum member initializer. + pub(crate) const MEMBER: Self = Self { + allow_const_var: true, + ts_is_opaque: false, + }; + /// Context for re-computing a member whose value the pre-pass already + /// classified as non-constant. Resolving `const` bindings here would + /// override that verdict: the binding map is complete by this point, and + /// type assertions have already been stripped from the initializer. + pub(crate) const RECOMPUTE: Self = Self { + allow_const_var: false, + ts_is_opaque: false, + }; +} + /// https://github.com/microsoft/TypeScript/pull/50528 impl EnumValueComputer<'_> { - pub fn compute(&self, expr: Box) -> TsEnumRecordValue { - self.compute_rec(expr) + /// Whether [`Self::compute`] could fold this expression at all, decided + /// from its outermost form alone. + /// + /// `compute` takes the expression by value, so evaluating an initializer + /// means cloning it. Every form not listed here hits the `Opaque` + /// catch-all immediately, and `const` initializers in real TypeScript are + /// mostly arrow functions, object literals and calls — cloning those to + /// discard the result dominated the cost of the collection pass. + /// + /// Kept in sync with the arms of `compute_rec`. An omission here can only + /// under-fold; it can never produce a wrong value. + pub fn can_fold_shape(expr: &Expr) -> bool { + matches!( + expr, + Expr::Lit(..) + | Expr::Ident(..) + | Expr::Paren(..) + | Expr::Unary(..) + | Expr::Bin(..) + | Expr::Member(..) + | Expr::Tpl(..) + | Expr::TsAs(..) + | Expr::TsNonNull(..) + | Expr::TsTypeAssertion(..) + | Expr::TsConstAssertion(..) + | Expr::TsInstantiation(..) + | Expr::TsSatisfies(..) + ) + } + + pub fn compute(&self, expr: Box, ctx: EvalCtx) -> TsEnumRecordValue { + self.compute_rec(expr, ctx) } - fn compute_rec(&self, expr: Box) -> TsEnumRecordValue { + fn compute_rec(&self, expr: Box, ctx: EvalCtx) -> TsEnumRecordValue { match *expr { Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(s.value), Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n), @@ -155,29 +232,51 @@ impl EnumValueComputer<'_> { } } } - Expr::Paren(e) => self.compute_rec(e.expr), - Expr::Unary(e) => self.compute_unary(e), - Expr::Bin(e) => self.compute_bin(e), - Expr::Member(e) => self.compute_member(e), - Expr::Tpl(e) => self.compute_tpl(e), + // A reference to a `const` binding whose initializer is a constant + // expression. TypeScript treats it as a compile-time constant, so + // the member is emitted as a string enum instead of a reverse + // mapping. Only reachable when not under type syntax. + Expr::Ident(ref ident) if ctx.allow_const_var => self + .const_vars + .get(&ident.to_id()) + .cloned() + .unwrap_or_else(|| TsEnumRecordValue::Opaque(expr)), + Expr::Paren(e) => self.compute_rec(e.expr, ctx), + Expr::Unary(e) => self.compute_unary(e, ctx), + Expr::Bin(e) => self.compute_bin(e, ctx), + Expr::Member(e) => self.compute_member(e, ctx), + Expr::Tpl(e) => self.compute_tpl(e, ctx), // 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), + Expr::TsAs(TsAsExpr { expr: inner, .. }) + | Expr::TsNonNull(TsNonNullExpr { expr: inner, .. }) + | Expr::TsTypeAssertion(TsTypeAssertion { expr: inner, .. }) + | Expr::TsConstAssertion(TsConstAssertion { expr: inner, .. }) + | Expr::TsInstantiation(TsInstantiation { expr: inner, .. }) + | Expr::TsSatisfies(TsSatisfiesExpr { expr: inner, .. }) => { + if ctx.ts_is_opaque { + // Any annotation or assertion removes constness from a + // `const` initializer. + return TsEnumRecordValue::Opaque(inner); + } + self.compute_rec( + inner, + EvalCtx { + allow_const_var: false, + ..ctx + }, + ) + } _ => TsEnumRecordValue::Opaque(expr), } } - fn compute_unary(&self, expr: UnaryExpr) -> TsEnumRecordValue { + fn compute_unary(&self, expr: UnaryExpr, ctx: EvalCtx) -> TsEnumRecordValue { if !matches!(expr.op, op!(unary, "+") | op!(unary, "-") | op!("~")) { return TsEnumRecordValue::Opaque(expr.into()); } - let inner = self.compute_rec(expr.arg); + let inner = self.compute_rec(expr.arg, ctx); let TsEnumRecordValue::Number(num) = inner else { return TsEnumRecordValue::Opaque( @@ -198,7 +297,7 @@ impl EnumValueComputer<'_> { } } - fn compute_bin(&self, expr: BinExpr) -> TsEnumRecordValue { + fn compute_bin(&self, expr: BinExpr, ctx: EvalCtx) -> TsEnumRecordValue { let origin_expr = expr.clone(); if !matches!( expr.op, @@ -218,8 +317,8 @@ impl EnumValueComputer<'_> { return TsEnumRecordValue::Opaque(origin_expr.into()); } - let left = self.compute_rec(expr.left); - let right = self.compute_rec(expr.right); + let left = self.compute_rec(expr.left, ctx); + let right = self.compute_rec(expr.right, ctx); if expr.op == BinaryOp::Add && (left.is_string() || right.is_string()) { let mut value = Wtf8Buf::new(); @@ -267,7 +366,7 @@ impl EnumValueComputer<'_> { } } - fn compute_member(&self, expr: MemberExpr) -> TsEnumRecordValue { + fn compute_member(&self, expr: MemberExpr, _ctx: EvalCtx) -> TsEnumRecordValue { let opaque_expr = TsEnumRecordValue::Opaque(expr.clone().into()); let Some(member_name) = static_enum_member_name(&expr.prop) else { @@ -278,9 +377,18 @@ impl EnumValueComputer<'_> { return opaque_expr; }; + let enum_id = ident.to_id(); + + if self + .const_enum_only + .is_some_and(|set| !set.contains(&enum_id)) + { + return opaque_expr; + } + self.record .get(&TsEnumRecordKey { - enum_id: ident.to_id(), + enum_id, member_name, }) .cloned() @@ -288,7 +396,7 @@ impl EnumValueComputer<'_> { .unwrap_or(opaque_expr) } - fn compute_tpl(&self, expr: Tpl) -> TsEnumRecordValue { + fn compute_tpl(&self, expr: Tpl, ctx: EvalCtx) -> TsEnumRecordValue { let opaque_expr = TsEnumRecordValue::Opaque(expr.clone().into()); let Tpl { exprs, quasis, .. } = expr; @@ -304,7 +412,7 @@ impl EnumValueComputer<'_> { let mut string = Wtf8Buf::from(first_cooked); for (q, expr) in quasis_iter.zip(exprs) { - let expr = self.compute_rec(expr); + let expr = self.compute_rec(expr, ctx); if !expr.push_to_string(&mut string) { return opaque_expr; diff --git a/crates/swc_ecma_transforms_typescript/src/typescript.rs b/crates/swc_ecma_transforms_typescript/src/typescript.rs index 6a79fecf30cc..22cdc98688f3 100644 --- a/crates/swc_ecma_transforms_typescript/src/typescript.rs +++ b/crates/swc_ecma_transforms_typescript/src/typescript.rs @@ -52,6 +52,7 @@ impl Pass for TypeScript { self.unresolved_mark, mem::take(&mut self.id_usage), self.config.flow_syntax, + self.config.ts_enum_is_mutable, ); n.mutate(transform( diff --git a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_is_mutable_true.js b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_is_mutable_true.js index 0ba6cd848efd..3311246ae958 100644 --- a/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_is_mutable_true.js +++ b/crates/swc_ecma_transforms_typescript/tests/__swc_snapshots__/tests/strip.rs/ts_enum_is_mutable_true.js @@ -16,3 +16,15 @@ var H = /*#__PURE__*/ function(H) { return H; }(H || {}); console.log(2); +const j = H.A; +var J = function(J) { + J[J["A"] = j] = "A"; + return J; +}(J || {}); +console.log(J.A); +const l = 3; +var L = /*#__PURE__*/ function(L) { + L[L["A"] = 3] = "A"; + return L; +}(L || {}); +console.log(L.A); diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-const-var/input.ts b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-const-var/input.ts new file mode 100644 index 000000000000..0c6f68bf376e --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-const-var/input.ts @@ -0,0 +1,20 @@ +const s = "aThisIs"; +const n = 1; +const concat = "a" + "b"; +const chain = s; + +enum StringEnum { + A = s, + B = "bThisIs", +} + +enum NumericAutoIncrement { + A = n, + B, + C, +} + +enum ConstantExpr { + A = concat, + B = chain, +} diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-const-var/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-const-var/output.js new file mode 100644 index 000000000000..29447891a4c9 --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-const-var/output.js @@ -0,0 +1,20 @@ +const s = "aThisIs"; +const n = 1; +const concat = "a" + "b"; +const chain = s; +var StringEnum = /*#__PURE__*/ function(StringEnum) { + StringEnum["A"] = "aThisIs"; + StringEnum["B"] = "bThisIs"; + return StringEnum; +}(StringEnum || {}); +var NumericAutoIncrement = /*#__PURE__*/ function(NumericAutoIncrement) { + NumericAutoIncrement[NumericAutoIncrement["A"] = 1] = "A"; + NumericAutoIncrement[NumericAutoIncrement["B"] = 2] = "B"; + NumericAutoIncrement[NumericAutoIncrement["C"] = 3] = "C"; + return NumericAutoIncrement; +}(NumericAutoIncrement || {}); +var ConstantExpr = /*#__PURE__*/ function(ConstantExpr) { + ConstantExpr["A"] = "ab"; + ConstantExpr["B"] = "aThisIs"; + return ConstantExpr; +}(ConstantExpr || {}); diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-declarator-order/input.ts b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-declarator-order/input.ts new file mode 100644 index 000000000000..956065a3f69c --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-declarator-order/input.ts @@ -0,0 +1,7 @@ +const first = 1, second = (()=>{ + enum Nested { + A = first, + B + } + return Nested; +})(); diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-declarator-order/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-declarator-order/output.js new file mode 100644 index 000000000000..383e6aa66f8a --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-declarator-order/output.js @@ -0,0 +1,8 @@ +const first = 1, second = (()=>{ + let Nested = /*#__PURE__*/ function(Nested) { + Nested[Nested["A"] = 1] = "A"; + Nested[Nested["B"] = 2] = "B"; + return Nested; + }({}); + return Nested; +})(); diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-enum-member-init/input.ts b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-enum-member-init/input.ts new file mode 100644 index 000000000000..2b3b26fb3c8d --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-enum-member-init/input.ts @@ -0,0 +1,17 @@ +enum Str { + A = "a" +} +const fromStr = Str.A; +enum FromEnumMemberStr { + X = fromStr, + Y = "y" +} + +enum Num { + A = 1 +} +const fromNum = Num.A; +enum FromEnumMemberNum { + X = fromNum, + Y +} diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-enum-member-init/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-enum-member-init/output.js new file mode 100644 index 000000000000..0c7b6a16fdd2 --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-enum-member-init/output.js @@ -0,0 +1,20 @@ +var Str = /*#__PURE__*/ function(Str) { + Str["A"] = "a"; + return Str; +}(Str || {}); +const fromStr = "a"; +var FromEnumMemberStr = /*#__PURE__*/ function(FromEnumMemberStr) { + FromEnumMemberStr["X"] = "a"; + FromEnumMemberStr["Y"] = "y"; + return FromEnumMemberStr; +}(FromEnumMemberStr || {}); +var Num = /*#__PURE__*/ function(Num) { + Num[Num["A"] = 1] = "A"; + return Num; +}(Num || {}); +const fromNum = 1; +var FromEnumMemberNum = /*#__PURE__*/ function(FromEnumMemberNum) { + FromEnumMemberNum[FromEnumMemberNum["X"] = 1] = "X"; + FromEnumMemberNum[FromEnumMemberNum["Y"] = 2] = "Y"; + return FromEnumMemberNum; +}(FromEnumMemberNum || {}); diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-not-const/input.ts b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-not-const/input.ts new file mode 100644 index 000000000000..dcfff51304b0 --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-not-const/input.ts @@ -0,0 +1,35 @@ +const annotated: string = "s"; +const asserted = "s" as string; +const constAsserted = "s" as const; +let mutable = "s"; +var hoisted = "s"; +const { destructured } = { destructured: "s" }; +const poisoned = "s"; + +enum TypeAnnotation { + A = annotated, + B = "b", +} + +enum TypeAssertion { + A = asserted, + B = constAsserted, +} + +enum NotConstBinding { + A = mutable, + B = hoisted, + C = destructured, +} + +enum AssertionOnReference { + A = poisoned as string, + B = "b", +} + +enum ForwardReference { + A = declaredLater, + B = "b", +} + +const declaredLater = "s"; diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-not-const/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-not-const/output.js new file mode 100644 index 000000000000..9a7cfa1e0c62 --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-not-const/output.js @@ -0,0 +1,36 @@ +const annotated = "s"; +const asserted = "s"; +const constAsserted = "s"; +let mutable = "s"; +var hoisted = "s"; +const { destructured } = { + destructured: "s" +}; +const poisoned = "s"; +var TypeAnnotation = function(TypeAnnotation) { + TypeAnnotation[TypeAnnotation["A"] = annotated] = "A"; + TypeAnnotation["B"] = "b"; + return TypeAnnotation; +}(TypeAnnotation || {}); +var TypeAssertion = function(TypeAssertion) { + TypeAssertion[TypeAssertion["A"] = asserted] = "A"; + TypeAssertion[TypeAssertion["B"] = constAsserted] = "B"; + return TypeAssertion; +}(TypeAssertion || {}); +var NotConstBinding = function(NotConstBinding) { + NotConstBinding[NotConstBinding["A"] = mutable] = "A"; + NotConstBinding[NotConstBinding["B"] = hoisted] = "B"; + NotConstBinding[NotConstBinding["C"] = destructured] = "C"; + return NotConstBinding; +}(NotConstBinding || {}); +var AssertionOnReference = function(AssertionOnReference) { + AssertionOnReference[AssertionOnReference["A"] = poisoned] = "A"; + AssertionOnReference["B"] = "b"; + return AssertionOnReference; +}(AssertionOnReference || {}); +var ForwardReference = function(ForwardReference) { + ForwardReference[ForwardReference["A"] = declaredLater] = "A"; + ForwardReference["B"] = "b"; + return ForwardReference; +}(ForwardReference || {}); +const declaredLater = "s"; diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-scope/input.ts b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-scope/input.ts new file mode 100644 index 000000000000..e52ceab875ca --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-scope/input.ts @@ -0,0 +1,30 @@ +declare const ambient: string; + +const outer = "outer"; + +enum Ambient { + A = ambient, + B = "b", +} + +function inFunction() { + const inner = "inner"; + enum Local { + A = inner, + B = "b", + } + return Local; +} + +namespace NS { + const scoped = "scoped"; + export enum InNamespace { + A = scoped, + B = "b", + } +} + +enum ShadowedOuter { + A = outer, + B = "b", +} diff --git a/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-scope/output.js b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-scope/output.js new file mode 100644 index 000000000000..44646c4884ea --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/tests/fixture/issue-11715-scope/output.js @@ -0,0 +1,28 @@ +const outer = "outer"; +var Ambient = function(Ambient) { + Ambient[Ambient["A"] = ambient] = "A"; + Ambient["B"] = "b"; + return Ambient; +}(Ambient || {}); +function inFunction() { + const inner = "inner"; + let Local = /*#__PURE__*/ function(Local) { + Local["A"] = "inner"; + Local["B"] = "b"; + return Local; + }({}); + return Local; +} +(function(NS) { + const scoped = "scoped"; + (function(InNamespace) { + InNamespace["A"] = "scoped"; + InNamespace["B"] = "b"; + })(NS.InNamespace || (NS.InNamespace = {})); +})(NS || (NS = {})); +var ShadowedOuter = /*#__PURE__*/ function(ShadowedOuter) { + ShadowedOuter["A"] = "outer"; + ShadowedOuter["B"] = "b"; + return ShadowedOuter; +}(ShadowedOuter || {}); +var NS; diff --git a/crates/swc_ecma_transforms_typescript/tests/strip.rs b/crates/swc_ecma_transforms_typescript/tests/strip.rs index 1f3eab3ef3e4..b70189c448ec 100644 --- a/crates/swc_ecma_transforms_typescript/tests/strip.rs +++ b/crates/swc_ecma_transforms_typescript/tests/strip.rs @@ -2843,6 +2843,19 @@ test!( A = H.A } console.log(I.A); + const j = H.A; + enum J { + A = j + } + console.log(J.A); + const enum K { + A = 3, + } + const l = K.A; + enum L { + A = l + } + console.log(L.A); "# );