From e47e4b8f78392d526f9ccd7924ba5c521597e1d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 15:56:10 +0000 Subject: [PATCH] Investigate enum namespace declaration issue in declarations transformer Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 6 +- src/compiler/transformers/declarations.ts | 63 +++++++++++++------ .../enumNamespaceConstantsDeclaration.d.ts | 21 +++++++ .../enumNamespaceConstantsDeclaration.ts | 26 ++++++++ 4 files changed, 95 insertions(+), 21 deletions(-) create mode 100644 tests/cases/compiler/enumNamespaceConstantsDeclaration.d.ts create mode 100644 tests/cases/compiler/enumNamespaceConstantsDeclaration.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ba8ec48a39e..efd6a77ce2d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -50962,9 +50962,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } - 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) - : type === trueType ? factory.createTrue() : type === falseType && factory.createFalse(); + 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) + : type === trueType ? factory.createTrue() : type === falseType && factory.createFalse(); if (enumResult) return enumResult; const literalValue = (type as LiteralType).value; return typeof literalValue === "object" ? factory.createBigIntLiteral(literalValue) : diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 4b6941dc61f..81d5b5b283a 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -103,7 +103,8 @@ import { isExpandoPropertyDeclaration, isExportAssignment, isExportDeclaration, - isExpressionWithTypeArguments, + isExpressionWithTypeArguments, + isExpression, isExternalModule, isExternalModuleAugmentation, isExternalModuleIndicator, @@ -128,8 +129,9 @@ import { isObjectLiteralExpression, isOmittedExpression, isParameter, - isPrimitiveLiteralValue, - isPrivateIdentifier, + isPrimitiveLiteralValue, + isPrivateIdentifier, + isPropertyAccessExpression, isSemicolonClassElement, isSetAccessorDeclaration, isSourceFile, @@ -200,8 +202,8 @@ import { TransformationContext, Transformer, transformNodes, - tryCast, - TypeAliasDeclaration, + tryCast, + TypeAliasDeclaration, TypeNode, TypeParameterDeclaration, TypeReferenceNode, @@ -654,21 +656,46 @@ export function transformDeclarations(context: TransformationContext): Transform return newParam; } - function shouldPrintWithInitializer(node: Node): node is CanHaveLiteralInitializer & { initializer: Expression; } { - return canHaveLiteralInitializer(node) - && !!node.initializer - && resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer); // TODO: Make safea + 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; } - function ensureNoInitializer(node: CanHaveLiteralInitializer) { - if (shouldPrintWithInitializer(node)) { - const unwrappedInitializer = unwrapParenthesizedExpression(node.initializer); - if (!isPrimitiveLiteralValue(unwrappedInitializer)) { - reportInferenceFallback(node); - } - return resolver.createLiteralConstValue(getParseTreeNode(node, canHaveLiteralInitializer)!, symbolTracker); - } - return undefined; + function ensureNoInitializer(node: CanHaveLiteralInitializer) { + if (shouldPrintWithInitializer(node)) { + const unwrappedInitializer = unwrapParenthesizedExpression(node.initializer); + 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; } function ensureType(node: VariableDeclaration | ParameterDeclaration | BindingElement | PropertyDeclaration | PropertySignature | ExportAssignment | SignatureDeclaration, ignorePrivate?: boolean): TypeNode | undefined { if (!ignorePrivate && hasEffectiveModifier(node, ModifierFlags.Private)) { diff --git a/tests/cases/compiler/enumNamespaceConstantsDeclaration.d.ts b/tests/cases/compiler/enumNamespaceConstantsDeclaration.d.ts new file mode 100644 index 00000000000..65f965ac6c1 --- /dev/null +++ b/tests/cases/compiler/enumNamespaceConstantsDeclaration.d.ts @@ -0,0 +1,21 @@ +declare enum Foo { + bar = 0 +} +declare namespace Foo { + const baz = bar; +} +declare enum MyEnum { + First = 1, + Second = 2 +} +declare namespace MyEnum { + const value1 = First; + const value2 = Second; +} +declare enum StringEnum { + Option1 = "option1", + Option2 = "option2" +} +declare namespace StringEnum { + const selected: any; +} diff --git a/tests/cases/compiler/enumNamespaceConstantsDeclaration.ts b/tests/cases/compiler/enumNamespaceConstantsDeclaration.ts new file mode 100644 index 00000000000..efeabbfdbd3 --- /dev/null +++ b/tests/cases/compiler/enumNamespaceConstantsDeclaration.ts @@ -0,0 +1,26 @@ +// 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; +} \ No newline at end of file