From 49bfe02f3083b4ae9ccc8ea044df43f710d55eb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:00:48 +0000 Subject: [PATCH 1/5] Initial plan From d30b0df7dcd37420fb4d478b3cef23a29fd764f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:21:13 +0000 Subject: [PATCH 2/5] Fix private fields appearing in class expression type literals Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 18 +++++++++++------- ...rivateFieldsInClassExpressionDeclaration.ts | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 tests/cases/compiler/privateFieldsInClassExpressionDeclaration.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 77f35376da785..f71c43ce7fc95 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7603,13 +7603,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { continue; } i++; - if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { - if (propertySymbol.flags & SymbolFlags.Prototype) { - continue; - } - if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { - context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); - } + if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { + if (propertySymbol.flags & SymbolFlags.Prototype) { + continue; + } + if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); + } + // Skip ECMAScript private identifier fields (e.g., #field) in class expression type literals + if (propertySymbol.valueDeclaration && isNamedDeclaration(propertySymbol.valueDeclaration) && isPrivateIdentifier(propertySymbol.valueDeclaration.name)) { + continue; + } } if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { context.out.truncated = true; diff --git a/tests/cases/compiler/privateFieldsInClassExpressionDeclaration.ts b/tests/cases/compiler/privateFieldsInClassExpressionDeclaration.ts new file mode 100644 index 0000000000000..1b46bf769ac4c --- /dev/null +++ b/tests/cases/compiler/privateFieldsInClassExpressionDeclaration.ts @@ -0,0 +1,15 @@ +// @target: ES2015 +// @declaration: true + +export const ClassExpression = class { + #context = 0; + #method() { return 42; } + public value = 1; +}; + +// Additional test with static private fields +export const ClassExpressionStatic = class { + static #staticPrivate = "hidden"; + #instancePrivate = true; + public exposed = "visible"; +}; \ No newline at end of file From 95a3539eb9ff122846f3c4eb906643ccd4d387ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:25:49 +0000 Subject: [PATCH 3/5] Update baselines and format fixes for private field declaration fix Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 22 +++---- ...ivateFieldsInClassExpressionDeclaration.js | 57 +++++++++++++++++++ ...FieldsInClassExpressionDeclaration.symbols | 31 ++++++++++ ...teFieldsInClassExpressionDeclaration.types | 55 ++++++++++++++++++ 4 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/privateFieldsInClassExpressionDeclaration.js create mode 100644 tests/baselines/reference/privateFieldsInClassExpressionDeclaration.symbols create mode 100644 tests/baselines/reference/privateFieldsInClassExpressionDeclaration.types diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f71c43ce7fc95..cd3cb72694995 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7603,17 +7603,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { continue; } i++; - if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { - if (propertySymbol.flags & SymbolFlags.Prototype) { - continue; - } - if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { - context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); - } - // Skip ECMAScript private identifier fields (e.g., #field) in class expression type literals - if (propertySymbol.valueDeclaration && isNamedDeclaration(propertySymbol.valueDeclaration) && isPrivateIdentifier(propertySymbol.valueDeclaration.name)) { - continue; - } + if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { + if (propertySymbol.flags & SymbolFlags.Prototype) { + continue; + } + if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); + } + // Skip ECMAScript private identifier fields (e.g., #field) in class expression type literals + if (propertySymbol.valueDeclaration && isNamedDeclaration(propertySymbol.valueDeclaration) && isPrivateIdentifier(propertySymbol.valueDeclaration.name)) { + continue; + } } if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { context.out.truncated = true; diff --git a/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.js b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.js new file mode 100644 index 0000000000000..d02eff87c585a --- /dev/null +++ b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.js @@ -0,0 +1,57 @@ +//// [tests/cases/compiler/privateFieldsInClassExpressionDeclaration.ts] //// + +//// [privateFieldsInClassExpressionDeclaration.ts] +export const ClassExpression = class { + #context = 0; + #method() { return 42; } + public value = 1; +}; + +// Additional test with static private fields +export const ClassExpressionStatic = class { + static #staticPrivate = "hidden"; + #instancePrivate = true; + public exposed = "visible"; +}; + +//// [privateFieldsInClassExpressionDeclaration.js] +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +var _instances, _context, _method, _a, _b, _ClassExpressionStatic_staticPrivate, _ClassExpressionStatic_instancePrivate; +export const ClassExpression = (_a = class { + constructor() { + _instances.add(this); + _context.set(this, 0); + this.value = 1; + } + }, + _context = new WeakMap(), + _instances = new WeakSet(), + _method = function _method() { return 42; }, + _a); +// Additional test with static private fields +export const ClassExpressionStatic = (_b = class { + constructor() { + _ClassExpressionStatic_instancePrivate.set(this, true); + this.exposed = "visible"; + } + }, + _ClassExpressionStatic_instancePrivate = new WeakMap(), + __setFunctionName(_b, "ClassExpressionStatic"), + _ClassExpressionStatic_staticPrivate = { value: "hidden" }, + _b); + + +//// [privateFieldsInClassExpressionDeclaration.d.ts] +export declare const ClassExpression: { + new (): { + value: number; + }; +}; +export declare const ClassExpressionStatic: { + new (): { + exposed: string; + }; +}; diff --git a/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.symbols b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.symbols new file mode 100644 index 0000000000000..395e587de77bd --- /dev/null +++ b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.symbols @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/privateFieldsInClassExpressionDeclaration.ts] //// + +=== privateFieldsInClassExpressionDeclaration.ts === +export const ClassExpression = class { +>ClassExpression : Symbol(ClassExpression, Decl(privateFieldsInClassExpressionDeclaration.ts, 0, 12)) + + #context = 0; +>#context : Symbol(ClassExpression.#context, Decl(privateFieldsInClassExpressionDeclaration.ts, 0, 38)) + + #method() { return 42; } +>#method : Symbol(ClassExpression.#method, Decl(privateFieldsInClassExpressionDeclaration.ts, 1, 17)) + + public value = 1; +>value : Symbol(ClassExpression.value, Decl(privateFieldsInClassExpressionDeclaration.ts, 2, 28)) + +}; + +// Additional test with static private fields +export const ClassExpressionStatic = class { +>ClassExpressionStatic : Symbol(ClassExpressionStatic, Decl(privateFieldsInClassExpressionDeclaration.ts, 7, 12)) + + static #staticPrivate = "hidden"; +>#staticPrivate : Symbol(ClassExpressionStatic.#staticPrivate, Decl(privateFieldsInClassExpressionDeclaration.ts, 7, 44)) + + #instancePrivate = true; +>#instancePrivate : Symbol(ClassExpressionStatic.#instancePrivate, Decl(privateFieldsInClassExpressionDeclaration.ts, 8, 37)) + + public exposed = "visible"; +>exposed : Symbol(ClassExpressionStatic.exposed, Decl(privateFieldsInClassExpressionDeclaration.ts, 9, 28)) + +}; diff --git a/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.types b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.types new file mode 100644 index 0000000000000..2377b1ba09167 --- /dev/null +++ b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.types @@ -0,0 +1,55 @@ +//// [tests/cases/compiler/privateFieldsInClassExpressionDeclaration.ts] //// + +=== privateFieldsInClassExpressionDeclaration.ts === +export const ClassExpression = class { +>ClassExpression : typeof ClassExpression +> : ^^^^^^^^^^^^^^^^^^^^^^ +>class { #context = 0; #method() { return 42; } public value = 1;} : typeof ClassExpression +> : ^^^^^^^^^^^^^^^^^^^^^^ + + #context = 0; +>#context : number +> : ^^^^^^ +>0 : 0 +> : ^ + + #method() { return 42; } +>#method : () => number +> : ^^^^^^^^^^^^ +>42 : 42 +> : ^^ + + public value = 1; +>value : number +> : ^^^^^^ +>1 : 1 +> : ^ + +}; + +// Additional test with static private fields +export const ClassExpressionStatic = class { +>ClassExpressionStatic : typeof ClassExpressionStatic +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>class { static #staticPrivate = "hidden"; #instancePrivate = true; public exposed = "visible";} : typeof ClassExpressionStatic +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + static #staticPrivate = "hidden"; +>#staticPrivate : string +> : ^^^^^^ +>"hidden" : "hidden" +> : ^^^^^^^^ + + #instancePrivate = true; +>#instancePrivate : boolean +> : ^^^^^^^ +>true : true +> : ^^^^ + + public exposed = "visible"; +>exposed : string +> : ^^^^^^ +>"visible" : "visible" +> : ^^^^^^^^^ + +}; From b8ca24cec4d8f392b140c276c729c78653fa0e22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:33:20 +0000 Subject: [PATCH 4/5] Apply @weswigham's suggestion: Generate errors for private fields in class expressions Co-authored-by: weswigham <2932786+weswigham@users.noreply.github.com> --- src/compiler/checker.ts | 18 +++++------ ...ldsInClassExpressionDeclaration.errors.txt | 31 +++++++++++++++++++ ...ivateFieldsInClassExpressionDeclaration.js | 13 -------- 3 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 tests/baselines/reference/privateFieldsInClassExpressionDeclaration.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cd3cb72694995..859b7d5c5d6a9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -707,9 +707,10 @@ import { isPartOfTypeQuery, isPlainJsFile, isPrefixUnaryExpression, - isPrivateIdentifier, - isPrivateIdentifierClassElementDeclaration, - isPrivateIdentifierPropertyAccessExpression, + isPrivateIdentifier, + isPrivateIdentifierClassElementDeclaration, + isPrivateIdentifierPropertyAccessExpression, + isPrivateIdentifierSymbol, isPropertyAccessEntityNameExpression, isPropertyAccessExpression, isPropertyAccessOrQualifiedName, @@ -7607,12 +7608,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (propertySymbol.flags & SymbolFlags.Prototype) { continue; } - if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { - context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); - } - // Skip ECMAScript private identifier fields (e.g., #field) in class expression type literals - if (propertySymbol.valueDeclaration && isNamedDeclaration(propertySymbol.valueDeclaration) && isPrivateIdentifier(propertySymbol.valueDeclaration.name)) { - continue; + if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); + } + if (isPrivateIdentifierSymbol(propertySymbol) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(idText((propertySymbol.valueDeclaration! as NamedDeclaration).name! as PrivateIdentifier)); } } if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { diff --git a/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.errors.txt b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.errors.txt new file mode 100644 index 0000000000000..c5c1abb1bc2d5 --- /dev/null +++ b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.errors.txt @@ -0,0 +1,31 @@ +privateFieldsInClassExpressionDeclaration.ts(1,14): error TS4094: Property '#context' of exported anonymous class type may not be private or protected. +privateFieldsInClassExpressionDeclaration.ts(1,14): error TS4094: Property '#method' of exported anonymous class type may not be private or protected. +privateFieldsInClassExpressionDeclaration.ts(8,14): error TS4094: Property '#instancePrivate' of exported anonymous class type may not be private or protected. +privateFieldsInClassExpressionDeclaration.ts(8,14): error TS4094: Property '#staticPrivate' of exported anonymous class type may not be private or protected. + + +==== privateFieldsInClassExpressionDeclaration.ts (4 errors) ==== + export const ClassExpression = class { + ~~~~~~~~~~~~~~~ +!!! error TS4094: Property '#context' of exported anonymous class type may not be private or protected. +!!! related TS9027 privateFieldsInClassExpressionDeclaration.ts:1:14: Add a type annotation to the variable ClassExpression. + ~~~~~~~~~~~~~~~ +!!! error TS4094: Property '#method' of exported anonymous class type may not be private or protected. +!!! related TS9027 privateFieldsInClassExpressionDeclaration.ts:1:14: Add a type annotation to the variable ClassExpression. + #context = 0; + #method() { return 42; } + public value = 1; + }; + + // Additional test with static private fields + export const ClassExpressionStatic = class { + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS4094: Property '#instancePrivate' of exported anonymous class type may not be private or protected. +!!! related TS9027 privateFieldsInClassExpressionDeclaration.ts:8:14: Add a type annotation to the variable ClassExpressionStatic. + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS4094: Property '#staticPrivate' of exported anonymous class type may not be private or protected. +!!! related TS9027 privateFieldsInClassExpressionDeclaration.ts:8:14: Add a type annotation to the variable ClassExpressionStatic. + static #staticPrivate = "hidden"; + #instancePrivate = true; + public exposed = "visible"; + }; \ No newline at end of file diff --git a/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.js b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.js index d02eff87c585a..c801596e5ffab 100644 --- a/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.js +++ b/tests/baselines/reference/privateFieldsInClassExpressionDeclaration.js @@ -42,16 +42,3 @@ export const ClassExpressionStatic = (_b = class { __setFunctionName(_b, "ClassExpressionStatic"), _ClassExpressionStatic_staticPrivate = { value: "hidden" }, _b); - - -//// [privateFieldsInClassExpressionDeclaration.d.ts] -export declare const ClassExpression: { - new (): { - value: number; - }; -}; -export declare const ClassExpressionStatic: { - new (): { - exposed: string; - }; -}; From 2543e40596e92fcca7acf6bb4a425e473be7973d Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Thu, 31 Jul 2025 21:46:03 +0000 Subject: [PATCH 5/5] Update Baselines, Applied Lint Fixes, and/or Formatted --- src/compiler/checker.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 859b7d5c5d6a9..75be36474222f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -707,9 +707,9 @@ import { isPartOfTypeQuery, isPlainJsFile, isPrefixUnaryExpression, - isPrivateIdentifier, - isPrivateIdentifierClassElementDeclaration, - isPrivateIdentifierPropertyAccessExpression, + isPrivateIdentifier, + isPrivateIdentifierClassElementDeclaration, + isPrivateIdentifierPropertyAccessExpression, isPrivateIdentifierSymbol, isPropertyAccessEntityNameExpression, isPropertyAccessExpression, @@ -7608,11 +7608,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (propertySymbol.flags & SymbolFlags.Prototype) { continue; } - if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { - context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); - } - if (isPrivateIdentifierSymbol(propertySymbol) && context.tracker.reportPrivateInBaseOfClassExpression) { - context.tracker.reportPrivateInBaseOfClassExpression(idText((propertySymbol.valueDeclaration! as NamedDeclaration).name! as PrivateIdentifier)); + if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); + } + if (isPrivateIdentifierSymbol(propertySymbol) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(idText((propertySymbol.valueDeclaration! as NamedDeclaration).name! as PrivateIdentifier)); } } if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) {