From 67ff5e337a2445d5999a12fafda3fb2de91c9020 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 15:38:31 +0000 Subject: [PATCH 1/8] Initial plan 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 2/8] 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 ba8ec48a39eff..efd6a77ce2d81 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 4b6941dc61f95..81d5b5b283a9f 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 0000000000000..65f965ac6c1c3 --- /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 0000000000000..efeabbfdbd30e --- /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 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 3/8] 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 efd6a77ce2d81..ff064d32d9180 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 81d5b5b283a9f..2e325be2fd983 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 0000000000000..9de8c43cb0ba0 --- /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 0000000000000..733a403bf2068 --- /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 0000000000000..064ac4307c5ba --- /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 +> : ^^^^^^^^^^^^^^^^^^ +} From 64358a26e1cee5ea7ae4f914a935c832d586d1df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 18:05:45 +0000 Subject: [PATCH 4/8] Fix test to properly check declaration output and remove invalid .d.ts file Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- .../enumNamespaceConstantsDeclaration.js | 39 +++++++++++++++++++ .../enumNamespaceConstantsDeclaration.ts | 2 + 2 files changed, 41 insertions(+) diff --git a/tests/baselines/reference/enumNamespaceConstantsDeclaration.js b/tests/baselines/reference/enumNamespaceConstantsDeclaration.js index 9de8c43cb0ba0..0a84260855116 100644 --- a/tests/baselines/reference/enumNamespaceConstantsDeclaration.js +++ b/tests/baselines/reference/enumNamespaceConstantsDeclaration.js @@ -56,3 +56,42 @@ var StringEnum; (function (StringEnum) { StringEnum.selected = StringEnum.Option1; })(StringEnum || (StringEnum = {})); + + +//// [enumNamespaceConstantsDeclaration.d.ts] +declare enum Foo { + bar = 0 +} +declare namespace Foo { + const baz = Foo.bar; +} +declare enum MyEnum { + First = 1, + Second = 2 +} +declare namespace MyEnum { + const value1 = MyEnum.First; + const value2 = MyEnum.Second; +} +declare enum StringEnum { + Option1 = "option1", + Option2 = "option2" +} +declare namespace StringEnum { + const selected: any; +} + + +!!!! File enumNamespaceConstantsDeclaration.d.ts differs from original emit in noCheck emit +//// [enumNamespaceConstantsDeclaration.d.ts] +=================================================================== +--- Expected The full check baseline ++++ Actual with noCheck set +@@ -16,6 +16,6 @@ + Option1 = "option1", + Option2 = "option2" + } + declare namespace StringEnum { +- const selected: any; ++ const selected = StringEnum.Option1; + } diff --git a/tests/cases/compiler/enumNamespaceConstantsDeclaration.ts b/tests/cases/compiler/enumNamespaceConstantsDeclaration.ts index efeabbfdbd30e..85de9b384bdaa 100644 --- a/tests/cases/compiler/enumNamespaceConstantsDeclaration.ts +++ b/tests/cases/compiler/enumNamespaceConstantsDeclaration.ts @@ -1,3 +1,5 @@ +// @declaration: true + // Test for constant declarations inside namespace merged with enum enum Foo { bar From bef82cbfc4bd5d46863e1e98f607df96afbc1615 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:22:10 +0000 Subject: [PATCH 5/8] Address code review feedback - revert declarations.ts changes and clean up test Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/transformers/declarations.ts | 50 +++++++++---------- .../enumNamespaceConstantsDeclaration.js | 40 +-------------- .../enumNamespaceConstantsDeclaration.symbols | 18 ------- .../enumNamespaceConstantsDeclaration.types | 29 ----------- .../enumNamespaceConstantsDeclaration.d.ts | 21 -------- .../enumNamespaceConstantsDeclaration.ts | 8 --- 6 files changed, 25 insertions(+), 141 deletions(-) delete mode 100644 tests/cases/compiler/enumNamespaceConstantsDeclaration.d.ts diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 2e325be2fd983..4b6941dc61f95 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -103,8 +103,7 @@ import { isExpandoPropertyDeclaration, isExportAssignment, isExportDeclaration, - isExpressionWithTypeArguments, - isExpression, + isExpressionWithTypeArguments, isExternalModule, isExternalModuleAugmentation, isExternalModuleIndicator, @@ -129,9 +128,8 @@ import { isObjectLiteralExpression, isOmittedExpression, isParameter, - isPrimitiveLiteralValue, - isPrivateIdentifier, - isPropertyAccessExpression, + isPrimitiveLiteralValue, + isPrivateIdentifier, isSemicolonClassElement, isSetAccessorDeclaration, isSourceFile, @@ -202,8 +200,8 @@ import { TransformationContext, Transformer, transformNodes, - tryCast, - TypeAliasDeclaration, + tryCast, + TypeAliasDeclaration, TypeNode, TypeParameterDeclaration, TypeReferenceNode, @@ -656,21 +654,21 @@ 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; } { + return canHaveLiteralInitializer(node) + && !!node.initializer + && resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer); // TODO: Make safea } - 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); + } + 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)) { @@ -1051,12 +1049,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 index 0a84260855116..491a04e6baa64 100644 --- a/tests/baselines/reference/enumNamespaceConstantsDeclaration.js +++ b/tests/baselines/reference/enumNamespaceConstantsDeclaration.js @@ -19,14 +19,7 @@ namespace MyEnum { 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 @@ -47,15 +40,6 @@ var 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 = {})); //// [enumNamespaceConstantsDeclaration.d.ts] @@ -73,25 +57,3 @@ declare namespace MyEnum { const value1 = MyEnum.First; const value2 = MyEnum.Second; } -declare enum StringEnum { - Option1 = "option1", - Option2 = "option2" -} -declare namespace StringEnum { - const selected: any; -} - - -!!!! File enumNamespaceConstantsDeclaration.d.ts differs from original emit in noCheck emit -//// [enumNamespaceConstantsDeclaration.d.ts] -=================================================================== ---- Expected The full check baseline -+++ Actual with noCheck set -@@ -16,6 +16,6 @@ - Option1 = "option1", - Option2 = "option2" - } - declare namespace StringEnum { -- const selected: any; -+ const selected = StringEnum.Option1; - } diff --git a/tests/baselines/reference/enumNamespaceConstantsDeclaration.symbols b/tests/baselines/reference/enumNamespaceConstantsDeclaration.symbols index 733a403bf2068..75251bd0d1dc9 100644 --- a/tests/baselines/reference/enumNamespaceConstantsDeclaration.symbols +++ b/tests/baselines/reference/enumNamespaceConstantsDeclaration.symbols @@ -44,22 +44,4 @@ namespace MyEnum { >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 index 064ac4307c5ba..dcc1651d7415e 100644 --- a/tests/baselines/reference/enumNamespaceConstantsDeclaration.types +++ b/tests/baselines/reference/enumNamespaceConstantsDeclaration.types @@ -67,33 +67,4 @@ namespace MyEnum { > : ^^^^^^^^^^^^^ } -// 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 -> : ^^^^^^^^^^^^^^^^^^ -} diff --git a/tests/cases/compiler/enumNamespaceConstantsDeclaration.d.ts b/tests/cases/compiler/enumNamespaceConstantsDeclaration.d.ts deleted file mode 100644 index 65f965ac6c1c3..0000000000000 --- a/tests/cases/compiler/enumNamespaceConstantsDeclaration.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -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 index 85de9b384bdaa..36ed26d25bd10 100644 --- a/tests/cases/compiler/enumNamespaceConstantsDeclaration.ts +++ b/tests/cases/compiler/enumNamespaceConstantsDeclaration.ts @@ -18,11 +18,3 @@ namespace MyEnum { 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 From 8a4d55687d3c5f9c151e4614bbe5803ad9ba6690 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 21:48:29 +0000 Subject: [PATCH 6/8] Run format tool to fix line endings Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ffc6cf9f82825..0aa146bd6fa45 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*/ undefined, NodeBuilderFlags.UseFullyQualifiedType, /*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*/ undefined, NodeBuilderFlags.UseFullyQualifiedType, /*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) : From 7fb8cdc56ec1a3e6e13815e59e6114755dee6d5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Jun 2025 04:58:10 +0000 Subject: [PATCH 7/8] Fix lint error in checker.ts parameter comment Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0aa146bd6fa45..7603fd941e8fa 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*/ undefined, NodeBuilderFlags.UseFullyQualifiedType, /*internalFlags*/ undefined, tracker) + const enumResult = type.flags & TypeFlags.EnumLike ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, /*enclosingDeclaration*/ undefined, NodeBuilderFlags.UseFullyQualifiedType, /*internalFlags*/ undefined, tracker) : type === trueType ? factory.createTrue() : type === falseType && factory.createFalse(); if (enumResult) return enumResult; const literalValue = (type as LiteralType).value; From 6b7aabbf1da9b941b5d1da838189a043f54f31f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:10:03 +0000 Subject: [PATCH 8/8] Fix enum constant declaration generation in namespaces Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b26a09b5df0f0..235196ef727fc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -51038,7 +51038,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, /*enclosingDeclaration*/ undefined, NodeBuilderFlags.UseFullyQualifiedType, /*internalFlags*/ undefined, tracker) + 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; @@ -51050,6 +51050,35 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { const type = getTypeOfSymbol(getSymbolOfDeclaration(node)); + + // Check if we're in a namespace declaration - in this case we need fully qualified enum references + let enclosingNode: Node | undefined = node; + let useFullyQualified = false; + + if (isVariableDeclaration(node) && type.flags & TypeFlags.EnumLike) { + const parentStatement = node.parent?.parent; // VariableDeclaration -> VariableDeclarationList -> VariableStatement + const moduleBlock = parentStatement?.parent; // VariableStatement -> ModuleBlock + const moduleDeclaration = moduleBlock?.parent; // ModuleBlock -> ModuleDeclaration + if (moduleDeclaration && isModuleDeclaration(moduleDeclaration)) { + // We're in a namespace - use fully qualified references for enum constants + useFullyQualified = true; + enclosingNode = undefined; + } + } + + // For enum types, use the nodeBuilder to get properly qualified references + if (type.flags & TypeFlags.EnumLike) { + const enumExpression = nodeBuilder.symbolToExpression( + type.symbol, + SymbolFlags.Value, + enclosingNode, + useFullyQualified ? NodeBuilderFlags.UseFullyQualifiedType : undefined, + /*internalFlags*/ undefined, + tracker, + ); + if (enumExpression) return enumExpression; + } + return literalTypeToNode(type as FreshableType, node, tracker); }