Skip to content

Commit eac2180

Browse files
authored
Be more tolerant with private identifier parsing, issue more targeted errors, and support private identifiers in forgotten 'this' codefix (#36188)
* Support private identifiers in forgotten this codefix * Parse invalid private identifiers as identifiers and issue targeted errors * Update codefix * Remove accidentally deleted newline
1 parent 770fbcb commit eac2180

20 files changed

+94
-93
lines changed

src/compiler/parser.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ namespace ts {
14031403
// An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues
14041404
// with magic property names like '__proto__'. The 'identifiers' object is used to share a single string instance for
14051405
// each identifier in order to reduce memory consumption.
1406-
function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage): Identifier {
1406+
function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier {
14071407
identifierCount++;
14081408
if (isIdentifier) {
14091409
const node = <Identifier>createNode(SyntaxKind.Identifier);
@@ -1417,6 +1417,11 @@ namespace ts {
14171417
return finishNode(node);
14181418
}
14191419

1420+
if (token() === SyntaxKind.PrivateIdentifier) {
1421+
parseErrorAtCurrentToken(privateIdentifierDiagnosticMessage || Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
1422+
return createIdentifier(/*isIdentifier*/ true);
1423+
}
1424+
14201425
// Only for end of file because the error gets reported incorrectly on embedded script tags.
14211426
const reportAtCurrentPosition = token() === SyntaxKind.EndOfFileToken;
14221427

@@ -1430,8 +1435,8 @@ namespace ts {
14301435
return createMissingNode<Identifier>(SyntaxKind.Identifier, reportAtCurrentPosition, diagnosticMessage || defaultMessage, msgArg);
14311436
}
14321437

1433-
function parseIdentifier(diagnosticMessage?: DiagnosticMessage): Identifier {
1434-
return createIdentifier(isIdentifier(), diagnosticMessage);
1438+
function parseIdentifier(diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier {
1439+
return createIdentifier(isIdentifier(), diagnosticMessage, privateIdentifierDiagnosticMessage);
14351440
}
14361441

14371442
function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier {
@@ -1627,9 +1632,9 @@ namespace ts {
16271632
return isIdentifier() && !isHeritageClauseExtendsOrImplementsKeyword();
16281633
}
16291634
case ParsingContext.VariableDeclarations:
1630-
return isIdentifierOrPattern();
1635+
return isIdentifierOrPrivateIdentifierOrPattern();
16311636
case ParsingContext.ArrayBindingElements:
1632-
return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isIdentifierOrPattern();
1637+
return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isIdentifierOrPrivateIdentifierOrPattern();
16331638
case ParsingContext.TypeParameters:
16341639
return isIdentifier();
16351640
case ParsingContext.ArrayLiteralMembers:
@@ -2613,7 +2618,7 @@ namespace ts {
26132618

26142619
function isStartOfParameter(isJSDocParameter: boolean): boolean {
26152620
return token() === SyntaxKind.DotDotDotToken ||
2616-
isIdentifierOrPattern() ||
2621+
isIdentifierOrPrivateIdentifierOrPattern() ||
26172622
isModifierKind(token()) ||
26182623
token() === SyntaxKind.AtToken ||
26192624
isStartOfType(/*inStartOfParameter*/ !isJSDocParameter);
@@ -2633,7 +2638,7 @@ namespace ts {
26332638

26342639
// FormalParameter [Yield,Await]:
26352640
// BindingElement[?Yield,?Await]
2636-
node.name = parseIdentifierOrPattern();
2641+
node.name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_cannot_be_used_as_parameters);
26372642
if (getFullWidth(node.name) === 0 && !node.modifiers && isModifierKind(token())) {
26382643
// in cases like
26392644
// 'use strict'
@@ -3436,6 +3441,7 @@ namespace ts {
34363441
case SyntaxKind.LessThanToken:
34373442
case SyntaxKind.AwaitKeyword:
34383443
case SyntaxKind.YieldKeyword:
3444+
case SyntaxKind.PrivateIdentifier:
34393445
// Yield/await always starts an expression. Either it is an identifier (in which case
34403446
// it is definitely an expression). Or it's a keyword (either because we're in
34413447
// a generator or async function, or in strict mode (or both)) and it started a yield or await expression.
@@ -5801,18 +5807,21 @@ namespace ts {
58015807
return finishNode(node);
58025808
}
58035809

5804-
function isIdentifierOrPattern() {
5805-
return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken || isIdentifier();
5810+
function isIdentifierOrPrivateIdentifierOrPattern() {
5811+
return token() === SyntaxKind.OpenBraceToken
5812+
|| token() === SyntaxKind.OpenBracketToken
5813+
|| token() === SyntaxKind.PrivateIdentifier
5814+
|| isIdentifier();
58065815
}
58075816

5808-
function parseIdentifierOrPattern(): Identifier | BindingPattern {
5817+
function parseIdentifierOrPattern(privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier | BindingPattern {
58095818
if (token() === SyntaxKind.OpenBracketToken) {
58105819
return parseArrayBindingPattern();
58115820
}
58125821
if (token() === SyntaxKind.OpenBraceToken) {
58135822
return parseObjectBindingPattern();
58145823
}
5815-
return parseIdentifier();
5824+
return parseIdentifier(/*diagnosticMessage*/ undefined, privateIdentifierDiagnosticMessage);
58165825
}
58175826

58185827
function parseVariableDeclarationAllowExclamation() {
@@ -5821,7 +5830,7 @@ namespace ts {
58215830

58225831
function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration {
58235832
const node = <VariableDeclaration>createNode(SyntaxKind.VariableDeclaration);
5824-
node.name = parseIdentifierOrPattern();
5833+
node.name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_are_not_allowed_in_variable_declarations);
58255834
if (allowExclamation && node.name.kind === SyntaxKind.Identifier &&
58265835
token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) {
58275836
node.exclamationToken = parseTokenNode<Token<SyntaxKind.ExclamationToken>>();

src/services/codefixes/fixForgottenThisPropertyAccess.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace ts.codefix {
44
const didYouMeanStaticMemberCode = Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.code;
55
const errorCodes = [
66
Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code,
7+
Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies.code,
78
didYouMeanStaticMemberCode,
89
];
910
registerCodeFix({
@@ -24,11 +25,16 @@ namespace ts.codefix {
2425
}),
2526
});
2627

27-
interface Info { readonly node: Identifier; readonly className: string | undefined; }
28+
interface Info {
29+
readonly node: Identifier | PrivateIdentifier;
30+
readonly className: string | undefined;
31+
}
32+
2833
function getInfo(sourceFile: SourceFile, pos: number, diagCode: number): Info | undefined {
2934
const node = getTokenAtPosition(sourceFile, pos);
30-
if (!isIdentifier(node)) return undefined;
31-
return { node, className: diagCode === didYouMeanStaticMemberCode ? getContainingClass(node)!.name!.text : undefined };
35+
if (isIdentifier(node)) {
36+
return { node, className: diagCode === didYouMeanStaticMemberCode ? getContainingClass(node)!.name!.text : undefined };
37+
}
3238
}
3339

3440
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { node, className }: Info): void {
Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
tests/cases/conformance/classes/members/privateNames/privateNameNotAllowedOutsideClass.ts(1,7): error TS1134: Variable declaration expected.
2-
tests/cases/conformance/classes/members/privateNames/privateNameNotAllowedOutsideClass.ts(1,12): error TS1134: Variable declaration expected.
3-
tests/cases/conformance/classes/members/privateNames/privateNameNotAllowedOutsideClass.ts(1,14): error TS1134: Variable declaration expected.
1+
tests/cases/conformance/classes/members/privateNames/privateNameNotAllowedOutsideClass.ts(1,7): error TS18029: Private identifiers are not allowed in variable declarations.
42

53

6-
==== tests/cases/conformance/classes/members/privateNames/privateNameNotAllowedOutsideClass.ts (3 errors) ====
4+
==== tests/cases/conformance/classes/members/privateNames/privateNameNotAllowedOutsideClass.ts (1 errors) ====
75
const #foo = 3;
86
~~~~
9-
!!! error TS1134: Variable declaration expected.
10-
~
11-
!!! error TS1134: Variable declaration expected.
12-
~
13-
!!! error TS1134: Variable declaration expected.
7+
!!! error TS18029: Private identifiers are not allowed in variable declarations.
148

tests/baselines/reference/privateNameNotAllowedOutsideClass.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@ const #foo = 3;
44

55
//// [privateNameNotAllowedOutsideClass.js]
66
"use strict";
7-
const ;
8-
3;
7+
const #foo = 3;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
=== tests/cases/conformance/classes/members/privateNames/privateNameNotAllowedOutsideClass.ts ===
22
const #foo = 3;
3-
No type information for this code.
4-
No type information for this code.
3+
>#foo : Symbol(#foo, Decl(privateNameNotAllowedOutsideClass.ts, 0, 5))
4+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
=== tests/cases/conformance/classes/members/privateNames/privateNameNotAllowedOutsideClass.ts ===
22
const #foo = 3;
3+
>#foo : 3
34
>3 : 3
45

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,28): error TS1005: ']' expected.
2-
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,28): error TS2300: Duplicate identifier '#bar'.
3-
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,28): error TS2717: Subsequent property declarations must have the same type. Property '#bar' must be of type 'number', but here has type 'any'.
4-
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,32): error TS1005: ';' expected.
5-
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,34): error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
6-
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,36): error TS7008: Member '3' implicitly has an 'any' type.
2+
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,28): error TS7005: Variable '#bar' implicitly has an 'any' type.
3+
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,32): error TS1005: ',' expected.
4+
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,34): error TS1134: Variable declaration expected.
5+
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(7,36): error TS1134: Variable declaration expected.
76
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(9,28): error TS2339: Property '#bar' does not exist on type 'C'.
8-
tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts(11,1): error TS1128: Declaration or statement expected.
97

108

11-
==== tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts (8 errors) ====
9+
==== tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts (6 errors) ====
1210
class C {
1311
foo = 3;
1412
#bar = 3;
@@ -18,24 +16,18 @@ tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAcces
1816
const badForNow: C[#bar] = 3; // Error
1917
~~~~
2018
!!! error TS1005: ']' expected.
21-
!!! related TS1007 tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts:4:20: The parser expected to find a '}' to match the '{' token here.
2219
~~~~
23-
!!! error TS2300: Duplicate identifier '#bar'.
24-
~~~~
25-
!!! error TS2717: Subsequent property declarations must have the same type. Property '#bar' must be of type 'number', but here has type 'any'.
26-
!!! related TS6203 tests/cases/conformance/classes/members/privateNames/privateNamesAndIndexedAccess.ts:3:5: '#bar' was also declared here.
20+
!!! error TS7005: Variable '#bar' implicitly has an 'any' type.
2721
~
28-
!!! error TS1005: ';' expected.
22+
!!! error TS1005: ',' expected.
2923
~
30-
!!! error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
24+
!!! error TS1134: Variable declaration expected.
3125
~
32-
!!! error TS7008: Member '3' implicitly has an 'any' type.
26+
!!! error TS1134: Variable declaration expected.
3327
// will never use this syntax, already taken:
3428
const badAlways: C["#bar"] = 3; // Error
3529
~~~~~~
3630
!!! error TS2339: Property '#bar' does not exist on type 'C'.
3731
}
3832
}
39-
~
40-
!!! error TS1128: Declaration or statement expected.
4133

tests/baselines/reference/privateNamesAndIndexedAccess.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ class C {
1414

1515
//// [privateNamesAndIndexedAccess.js]
1616
"use strict";
17-
var _bar, _bar_1;
17+
var _bar;
1818
class C {
1919
constructor() {
2020
this.foo = 3;
21-
_bar_1.set(this, 3);
22-
_bar_1.set(this, void 0);
23-
// will never use this syntax, already taken:
24-
this.badAlways = 3; // Error
21+
_bar.set(this, 3);
2522
const ok = 3;
2623
// not supported yet, could support in future:
27-
const badForNow;
24+
const badForNow, #bar;
25+
3; // Error
26+
// will never use this syntax, already taken:
27+
const badAlways = 3; // Error
2828
}
2929
}
30-
_bar = new WeakMap(), _bar_1 = new WeakMap();
30+
_bar = new WeakMap();

tests/baselines/reference/privateNamesAndIndexedAccess.symbols

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class C {
66
>foo : Symbol(C.foo, Decl(privateNamesAndIndexedAccess.ts, 0, 9))
77

88
#bar = 3;
9-
>#bar : Symbol(C.#bar, Decl(privateNamesAndIndexedAccess.ts, 1, 12), Decl(privateNamesAndIndexedAccess.ts, 6, 27))
9+
>#bar : Symbol(C.#bar, Decl(privateNamesAndIndexedAccess.ts, 1, 12))
1010

1111
constructor () {
1212
const ok: C["foo"] = 3;
@@ -17,12 +17,11 @@ class C {
1717
const badForNow: C[#bar] = 3; // Error
1818
>badForNow : Symbol(badForNow, Decl(privateNamesAndIndexedAccess.ts, 6, 13))
1919
>C : Symbol(C, Decl(privateNamesAndIndexedAccess.ts, 0, 0))
20-
>#bar : Symbol(C.#bar, Decl(privateNamesAndIndexedAccess.ts, 1, 12), Decl(privateNamesAndIndexedAccess.ts, 6, 27))
21-
>3 : Symbol(C[3], Decl(privateNamesAndIndexedAccess.ts, 6, 34))
20+
>#bar : Symbol(#bar, Decl(privateNamesAndIndexedAccess.ts, 6, 27))
2221

2322
// will never use this syntax, already taken:
2423
const badAlways: C["#bar"] = 3; // Error
25-
>badAlways : Symbol(C.badAlways, Decl(privateNamesAndIndexedAccess.ts, 6, 37))
24+
>badAlways : Symbol(badAlways, Decl(privateNamesAndIndexedAccess.ts, 8, 13))
2625
>C : Symbol(C, Decl(privateNamesAndIndexedAccess.ts, 0, 0))
2726
}
2827
}

tests/baselines/reference/privateNamesAndIndexedAccess.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ class C {
1818
// not supported yet, could support in future:
1919
const badForNow: C[#bar] = 3; // Error
2020
>badForNow : C[]
21-
>#bar : number
22-
>3 : any
21+
>#bar : any
22+
>3 : 3
2323

2424
// will never use this syntax, already taken:
2525
const badAlways: C["#bar"] = 3; // Error

0 commit comments

Comments
 (0)