Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ import {
isPrivateIdentifier,
isPrivateIdentifierClassElementDeclaration,
isPrivateIdentifierPropertyAccessExpression,
isPrivateIdentifierSymbol,
isPropertyAccessEntityNameExpression,
isPropertyAccessExpression,
isPropertyAccessOrQualifiedName,
Expand Down Expand Up @@ -7610,6 +7611,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) {
context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. This should be an error here, like a private. This is also much more verbose than needed. Try this instead:

Suggested change
// Skip ECMAScript private identifier fields (e.g., #field) in class expression type literals
if (isPrivateIdentifierSymbol(propertySymbol) && context.tracker.reportPrivateInBaseOfClassExpression) {
context.tracker.reportPrivateInBaseOfClassExpression(idText((propertySymbol.valueDeclaration! as NamedDeclaration).name! as PrivateIdentifier));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied your suggestion! The implementation now uses isPrivateIdentifierSymbol and generates the same error as traditional private/protected fields instead of silently skipping them. This makes the behavior much more consistent and provides proper error reporting. Commit: b8ca24c

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)) {
context.out.truncated = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//// [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);
Original file line number Diff line number Diff line change
@@ -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))

};
Original file line number Diff line number Diff line change
@@ -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"
> : ^^^^^^^^^

};
15 changes: 15 additions & 0 deletions tests/cases/compiler/privateFieldsInClassExpressionDeclaration.ts
Original file line number Diff line number Diff line change
@@ -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";
};
Loading