Skip to content

Fix private identifier fields generating errors in class expression declarations #62155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 31, 2025

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Jul 31, 2025

This PR fixes a regression introduced in TypeScript 5.9 where private fields using ECMAScript syntax (#field) were being silently excluded from class expression type literals in declaration files, while traditional private/protected fields generated proper error messages.

Issue

When compiling a class expression with private fields to a declaration file, the behavior was inconsistent:

// Source code
export const ClassExpression = class {
    private traditionalPrivate = 0;  // Generated error TS4094
    #ecmaScriptPrivate = 1;          // Silently excluded, no error
    public value = 2;
};

This inconsistency meant that ECMAScript private fields could potentially slip through without proper validation.

Root Cause

The createTypeNodesFromResolvedType function in checker.ts had inconsistent handling for class expressions converted to type literals (WriteClassExpressionAsTypeLiteral flag):

  • Traditional private/protected fields: Generated error but continued processing
  • ECMAScript private identifier fields: Silently skipped with continue

Solution

Updated the implementation to treat both types of private fields consistently by:

  1. Using isPrivateIdentifierSymbol() for reliable detection of ECMAScript private fields
  2. Generating the same error message for both traditional and ECMAScript private fields: "Property '{field}' of exported anonymous class type may not be private or protected"
  3. Removing the silent skip behavior to ensure consistent error reporting
// Before: Inconsistent behavior
if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected)) {
    // Generate error for traditional private/protected
}
if (isPrivateIdentifier(propertySymbol.valueDeclaration.name)) {
    continue; // Silent skip for ECMAScript private fields
}

// After: Consistent behavior  
if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected)) {
    // Generate error for traditional private/protected
}
if (isPrivateIdentifierSymbol(propertySymbol)) {
    // Generate same error for ECMAScript private fields
}

Result

Both types of private fields now generate consistent error messages, ensuring:

  • Proper validation of all private members in class expressions
  • Consistent developer experience regardless of private field syntax
  • Prevention of invalid TypeScript syntax in declaration files

The fix maintains existing behavior for traditional private fields while bringing ECMAScript private fields in line with the same error reporting standards.

Fixes #62153.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits July 31, 2025 15:21
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] regression: Private fields appear in type literal in declarations (5.9) Jul 31, 2025
@Copilot Copilot AI requested a review from RyanCavanaugh July 31, 2025 15:27
Copilot finished work on behalf of RyanCavanaugh July 31, 2025 15:27
@@ -7610,6 +7610,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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
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

…class expressions

Co-authored-by: weswigham <2932786+weswigham@users.noreply.github.com>
@Copilot Copilot AI changed the title Fix private fields appearing in type literals in declarations Jul 31, 2025
@Copilot Copilot AI requested a review from weswigham July 31, 2025 19:36
Copilot finished work on behalf of weswigham July 31, 2025 19:36
@weswigham weswigham marked this pull request as ready for review July 31, 2025 22:18
@Copilot Copilot AI review requested due to automatic review settings July 31, 2025 22:18
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a regression in TypeScript 5.9 where ECMAScript private fields (#field) in class expressions were silently excluded from declaration files, while traditional private/protected fields properly generated error messages. The fix ensures consistent error reporting for both types of private members in exported anonymous class types.

Key changes:

  • Added proper error reporting for ECMAScript private identifier fields in class expressions
  • Updated the type checker to use isPrivateIdentifierSymbol() for reliable detection
  • Ensured both traditional and ECMAScript private fields generate the same TS4094 error

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/compiler/checker.ts Added error reporting logic for ECMAScript private identifier fields in class expressions
tests/cases/compiler/privateFieldsInClassExpressionDeclaration.ts New test case covering both instance and static private fields
tests/baselines/reference/privateFieldsInClassExpressionDeclaration.errors.txt Expected error output showing consistent TS4094 errors for all private fields
tests/baselines/reference/privateFieldsInClassExpressionDeclaration.js Expected JavaScript compilation output
tests/baselines/reference/privateFieldsInClassExpressionDeclaration.types Expected type information output
tests/baselines/reference/privateFieldsInClassExpressionDeclaration.symbols Expected symbol information output
@weswigham weswigham merged commit 39c5d01 into main Jul 31, 2025
32 checks passed
@weswigham weswigham deleted the copilot/fix-62153 branch July 31, 2025 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants