From 5971e08b3387187c58a351b65bd821d7ebecb074 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 16:06:40 +0000 Subject: [PATCH] Fix enum namespace constants declaration generation Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 2 +- src/compiler/transformers/declarations.ts | 43 ++------ .../enumNamespaceConstantsDeclaration.js | 58 +++++++++++ .../enumNamespaceConstantsDeclaration.symbols | 65 ++++++++++++ .../enumNamespaceConstantsDeclaration.types | 99 +++++++++++++++++++ 5 files changed, 232 insertions(+), 35 deletions(-) create mode 100644 tests/baselines/reference/enumNamespaceConstantsDeclaration.js create mode 100644 tests/baselines/reference/enumNamespaceConstantsDeclaration.symbols create mode 100644 tests/baselines/reference/enumNamespaceConstantsDeclaration.types diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index efd6a77ce2d..ff064d32d91 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -50963,7 +50963,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { - const enumResult = type.flags & TypeFlags.EnumLike ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, /*internalFlags*/ undefined, tracker) + const enumResult = type.flags & TypeFlags.EnumLike ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, /*enclosing*/ undefined, NodeBuilderFlags.UseFullyQualifiedType, /*internalFlags*/ undefined, tracker) : type === trueType ? factory.createTrue() : type === falseType && factory.createFalse(); if (enumResult) return enumResult; const literalValue = (type as LiteralType).value; diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 81d5b5b283a..2e325be2fd9 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -657,22 +657,9 @@ export function transformDeclarations(context: TransformationContext): Transform } function shouldPrintWithInitializer(node: Node): node is CanHaveLiteralInitializer & { initializer: Expression; } { - if (!canHaveLiteralInitializer(node) || !node.initializer || !resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer)) { - return false; - } - - // Check if the initializer is a property access to an enum member (e.g., Foo.bar) - // In this case, don't print with initializer - let it get a type annotation instead - const unwrappedInitializer = unwrapParenthesizedExpression(node.initializer); - if (isPropertyAccessExpression(unwrappedInitializer)) { - const constantValue = resolver.getConstantValue(unwrappedInitializer); - // If it has a constant value (meaning it's an enum member reference), use type instead of initializer - if (constantValue !== undefined) { - return false; - } - } - - return true; + return canHaveLiteralInitializer(node) + && !!node.initializer + && resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer); // TODO: Make safea } function ensureNoInitializer(node: CanHaveLiteralInitializer) { @@ -681,18 +668,6 @@ export function transformDeclarations(context: TransformationContext): Transform if (!isPrimitiveLiteralValue(unwrappedInitializer)) { reportInferenceFallback(node); } - - // Check if the initializer is a property access to an enum member (e.g., Foo.bar) - // In this case, don't print with initializer - let it fall back to type annotation - if (isPropertyAccessExpression(unwrappedInitializer)) { - const constantValue = resolver.getConstantValue(unwrappedInitializer); - // If it has a constant value (meaning it's an enum member reference), - // don't use initializer and let it get a type instead - if (constantValue !== undefined) { - return undefined; - } - } - return resolver.createLiteralConstValue(getParseTreeNode(node, canHaveLiteralInitializer)!, symbolTracker); } return undefined; @@ -1076,12 +1051,12 @@ export function transformDeclarations(context: TransformationContext): Transform const oldWithinObjectLiteralType = suppressNewDiagnosticContexts; let shouldEnterSuppressNewDiagnosticsContextContext = (input.kind === SyntaxKind.TypeLiteral || input.kind === SyntaxKind.MappedType) && input.parent.kind !== SyntaxKind.TypeAliasDeclaration; - // Emit methods which are private as properties with no type information - if (isMethodDeclaration(input) || isMethodSignature(input)) { - if (hasEffectiveModifier(input, ModifierFlags.Private)) { - if (input.symbol && input.symbol.declarations && input.symbol.declarations[0] !== input) return; // Elide all but the first overload - return cleanup(factory.createPropertyDeclaration(ensureModifiers(input), input.name, /*questionOrExclamationToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); - } + // Emit methods which are private as properties with no type information + if (isMethodDeclaration(input) || isMethodSignature(input)) { + if (hasEffectiveModifier(input, ModifierFlags.Private)) { + if (input.symbol && input.symbol.declarations && input.symbol.declarations[0] !== input) return; // Elide all but the first overload + return cleanup(factory.createPropertyDeclaration(ensureModifiers(input), input.name, /*questionOrExclamationToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); + } } if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { diff --git a/tests/baselines/reference/enumNamespaceConstantsDeclaration.js b/tests/baselines/reference/enumNamespaceConstantsDeclaration.js new file mode 100644 index 00000000000..9de8c43cb0b --- /dev/null +++ b/tests/baselines/reference/enumNamespaceConstantsDeclaration.js @@ -0,0 +1,58 @@ +//// [tests/cases/compiler/enumNamespaceConstantsDeclaration.ts] //// + +//// [enumNamespaceConstantsDeclaration.ts] +// Test for constant declarations inside namespace merged with enum +enum Foo { + bar +} +namespace Foo { + export const baz = Foo.bar; +} + +// Multiple enum members +enum MyEnum { + First = 1, + Second = 2 +} +namespace MyEnum { + export const value1 = MyEnum.First; + export const value2 = MyEnum.Second; +} + +// String enum +enum StringEnum { + Option1 = "option1", + Option2 = "option2" +} +namespace StringEnum { + export const selected = StringEnum.Option1; +} + +//// [enumNamespaceConstantsDeclaration.js] +// Test for constant declarations inside namespace merged with enum +var Foo; +(function (Foo) { + Foo[Foo["bar"] = 0] = "bar"; +})(Foo || (Foo = {})); +(function (Foo) { + Foo.baz = Foo.bar; +})(Foo || (Foo = {})); +// Multiple enum members +var MyEnum; +(function (MyEnum) { + MyEnum[MyEnum["First"] = 1] = "First"; + MyEnum[MyEnum["Second"] = 2] = "Second"; +})(MyEnum || (MyEnum = {})); +(function (MyEnum) { + MyEnum.value1 = MyEnum.First; + MyEnum.value2 = MyEnum.Second; +})(MyEnum || (MyEnum = {})); +// String enum +var StringEnum; +(function (StringEnum) { + StringEnum["Option1"] = "option1"; + StringEnum["Option2"] = "option2"; +})(StringEnum || (StringEnum = {})); +(function (StringEnum) { + StringEnum.selected = StringEnum.Option1; +})(StringEnum || (StringEnum = {})); diff --git a/tests/baselines/reference/enumNamespaceConstantsDeclaration.symbols b/tests/baselines/reference/enumNamespaceConstantsDeclaration.symbols new file mode 100644 index 00000000000..733a403bf20 --- /dev/null +++ b/tests/baselines/reference/enumNamespaceConstantsDeclaration.symbols @@ -0,0 +1,65 @@ +//// [tests/cases/compiler/enumNamespaceConstantsDeclaration.ts] //// + +=== enumNamespaceConstantsDeclaration.ts === +// Test for constant declarations inside namespace merged with enum +enum Foo { +>Foo : Symbol(Foo, Decl(enumNamespaceConstantsDeclaration.ts, 0, 0), Decl(enumNamespaceConstantsDeclaration.ts, 3, 1)) + + bar +>bar : Symbol(Foo.bar, Decl(enumNamespaceConstantsDeclaration.ts, 1, 10)) +} +namespace Foo { +>Foo : Symbol(Foo, Decl(enumNamespaceConstantsDeclaration.ts, 0, 0), Decl(enumNamespaceConstantsDeclaration.ts, 3, 1)) + + export const baz = Foo.bar; +>baz : Symbol(baz, Decl(enumNamespaceConstantsDeclaration.ts, 5, 16)) +>Foo.bar : Symbol(bar, Decl(enumNamespaceConstantsDeclaration.ts, 1, 10)) +>Foo : Symbol(Foo, Decl(enumNamespaceConstantsDeclaration.ts, 0, 0), Decl(enumNamespaceConstantsDeclaration.ts, 3, 1)) +>bar : Symbol(bar, Decl(enumNamespaceConstantsDeclaration.ts, 1, 10)) +} + +// Multiple enum members +enum MyEnum { +>MyEnum : Symbol(MyEnum, Decl(enumNamespaceConstantsDeclaration.ts, 6, 1), Decl(enumNamespaceConstantsDeclaration.ts, 12, 1)) + + First = 1, +>First : Symbol(MyEnum.First, Decl(enumNamespaceConstantsDeclaration.ts, 9, 13)) + + Second = 2 +>Second : Symbol(MyEnum.Second, Decl(enumNamespaceConstantsDeclaration.ts, 10, 14)) +} +namespace MyEnum { +>MyEnum : Symbol(MyEnum, Decl(enumNamespaceConstantsDeclaration.ts, 6, 1), Decl(enumNamespaceConstantsDeclaration.ts, 12, 1)) + + export const value1 = MyEnum.First; +>value1 : Symbol(value1, Decl(enumNamespaceConstantsDeclaration.ts, 14, 16)) +>MyEnum.First : Symbol(First, Decl(enumNamespaceConstantsDeclaration.ts, 9, 13)) +>MyEnum : Symbol(MyEnum, Decl(enumNamespaceConstantsDeclaration.ts, 6, 1), Decl(enumNamespaceConstantsDeclaration.ts, 12, 1)) +>First : Symbol(First, Decl(enumNamespaceConstantsDeclaration.ts, 9, 13)) + + export const value2 = MyEnum.Second; +>value2 : Symbol(value2, Decl(enumNamespaceConstantsDeclaration.ts, 15, 16)) +>MyEnum.Second : Symbol(Second, Decl(enumNamespaceConstantsDeclaration.ts, 10, 14)) +>MyEnum : Symbol(MyEnum, Decl(enumNamespaceConstantsDeclaration.ts, 6, 1), Decl(enumNamespaceConstantsDeclaration.ts, 12, 1)) +>Second : Symbol(Second, Decl(enumNamespaceConstantsDeclaration.ts, 10, 14)) +} + +// String enum +enum StringEnum { +>StringEnum : Symbol(StringEnum, Decl(enumNamespaceConstantsDeclaration.ts, 16, 1), Decl(enumNamespaceConstantsDeclaration.ts, 22, 1)) + + Option1 = "option1", +>Option1 : Symbol(StringEnum.Option1, Decl(enumNamespaceConstantsDeclaration.ts, 19, 17)) + + Option2 = "option2" +>Option2 : Symbol(StringEnum.Option2, Decl(enumNamespaceConstantsDeclaration.ts, 20, 24)) +} +namespace StringEnum { +>StringEnum : Symbol(StringEnum, Decl(enumNamespaceConstantsDeclaration.ts, 16, 1), Decl(enumNamespaceConstantsDeclaration.ts, 22, 1)) + + export const selected = StringEnum.Option1; +>selected : Symbol(selected, Decl(enumNamespaceConstantsDeclaration.ts, 24, 16)) +>StringEnum.Option1 : Symbol(Option1, Decl(enumNamespaceConstantsDeclaration.ts, 19, 17)) +>StringEnum : Symbol(StringEnum, Decl(enumNamespaceConstantsDeclaration.ts, 16, 1), Decl(enumNamespaceConstantsDeclaration.ts, 22, 1)) +>Option1 : Symbol(Option1, Decl(enumNamespaceConstantsDeclaration.ts, 19, 17)) +} diff --git a/tests/baselines/reference/enumNamespaceConstantsDeclaration.types b/tests/baselines/reference/enumNamespaceConstantsDeclaration.types new file mode 100644 index 00000000000..064ac4307c5 --- /dev/null +++ b/tests/baselines/reference/enumNamespaceConstantsDeclaration.types @@ -0,0 +1,99 @@ +//// [tests/cases/compiler/enumNamespaceConstantsDeclaration.ts] //// + +=== enumNamespaceConstantsDeclaration.ts === +// Test for constant declarations inside namespace merged with enum +enum Foo { +>Foo : Foo +> : ^^^ + + bar +>bar : Foo.bar +> : ^^^^^^^ +} +namespace Foo { +>Foo : typeof Foo +> : ^^^^^^^^^^ + + export const baz = Foo.bar; +>baz : Foo.bar +> : ^^^^^^^ +>Foo.bar : Foo +> : ^^^ +>Foo : typeof Foo +> : ^^^^^^^^^^ +>bar : Foo +> : ^^^ +} + +// Multiple enum members +enum MyEnum { +>MyEnum : MyEnum +> : ^^^^^^ + + First = 1, +>First : MyEnum.First +> : ^^^^^^^^^^^^ +>1 : 1 +> : ^ + + Second = 2 +>Second : MyEnum.Second +> : ^^^^^^^^^^^^^ +>2 : 2 +> : ^ +} +namespace MyEnum { +>MyEnum : typeof MyEnum +> : ^^^^^^^^^^^^^ + + export const value1 = MyEnum.First; +>value1 : MyEnum.First +> : ^^^^^^^^^^^^ +>MyEnum.First : MyEnum.First +> : ^^^^^^^^^^^^ +>MyEnum : typeof MyEnum +> : ^^^^^^^^^^^^^ +>First : MyEnum.First +> : ^^^^^^^^^^^^ + + export const value2 = MyEnum.Second; +>value2 : MyEnum.Second +> : ^^^^^^^^^^^^^ +>MyEnum.Second : MyEnum.Second +> : ^^^^^^^^^^^^^ +>MyEnum : typeof MyEnum +> : ^^^^^^^^^^^^^ +>Second : MyEnum.Second +> : ^^^^^^^^^^^^^ +} + +// String enum +enum StringEnum { +>StringEnum : StringEnum +> : ^^^^^^^^^^ + + Option1 = "option1", +>Option1 : StringEnum.Option1 +> : ^^^^^^^^^^^^^^^^^^ +>"option1" : "option1" +> : ^^^^^^^^^ + + Option2 = "option2" +>Option2 : StringEnum.Option2 +> : ^^^^^^^^^^^^^^^^^^ +>"option2" : "option2" +> : ^^^^^^^^^ +} +namespace StringEnum { +>StringEnum : typeof StringEnum +> : ^^^^^^^^^^^^^^^^^ + + export const selected = StringEnum.Option1; +>selected : any +>StringEnum.Option1 : StringEnum.Option1 +> : ^^^^^^^^^^^^^^^^^^ +>StringEnum : typeof StringEnum +> : ^^^^^^^^^^^^^^^^^ +>Option1 : StringEnum.Option1 +> : ^^^^^^^^^^^^^^^^^^ +}