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); }
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: