Skip to content

Commit f84b2d2

Browse files
joeywattsDanielRosenwasser
authored andcommitted
Parse error on private identifier optional chain (microsoft#35987)
Previously, this error was reported in the checker, so JS files with checkJs: false were not erroring on this invalid syntax.
1 parent 9fbcdb1 commit f84b2d2

8 files changed

+74
-8
lines changed

src/compiler/checker.ts

-4
Original file line numberDiff line numberDiff line change
@@ -23234,10 +23234,6 @@ namespace ts {
2323423234
const assignmentKind = getAssignmentTargetKind(node);
2323523235
const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType);
2323623236
if (isPrivateIdentifier(right)) {
23237-
if (isOptionalChain(node)) {
23238-
grammarErrorOnNode(right, Diagnostics.An_optional_chain_cannot_contain_private_identifiers);
23239-
return anyType;
23240-
}
2324123237
checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet);
2324223238
}
2324323239
const isAnyLike = isTypeAny(apparentType) || apparentType === silentNeverType;

src/compiler/parser.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -4680,10 +4680,12 @@ namespace ts {
46804680
const propertyAccess = <PropertyAccessExpression>createNode(SyntaxKind.PropertyAccessExpression, expression.pos);
46814681
propertyAccess.expression = expression;
46824682
propertyAccess.questionDotToken = questionDotToken;
4683-
// checker will error on private identifiers in optional chains, so don't have to catch them here
46844683
propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true);
46854684
if (questionDotToken || expression.flags & NodeFlags.OptionalChain) {
46864685
propertyAccess.flags |= NodeFlags.OptionalChain;
4686+
if (isPrivateIdentifier(propertyAccess.name)) {
4687+
parseErrorAtRange(propertyAccess.name, Diagnostics.An_optional_chain_cannot_contain_private_identifiers);
4688+
}
46874689
}
46884690
return finishNode(propertyAccess);
46894691
}

tests/baselines/reference/privateIdentifierChain.1.symbols

+3
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@ class A {
1919
}
2020
constructor() {
2121
this?.#b; // Error
22+
>this?.#b : Symbol(A.#b, Decl(privateIdentifierChain.1.ts, 1, 9))
2223
>this : Symbol(A, Decl(privateIdentifierChain.1.ts, 0, 0))
2324

2425
this?.a.#b; // Error
26+
>this?.a.#b : Symbol(A.#b, Decl(privateIdentifierChain.1.ts, 1, 9))
2527
>this?.a : Symbol(A.a, Decl(privateIdentifierChain.1.ts, 0, 9))
2628
>this : Symbol(A, Decl(privateIdentifierChain.1.ts, 0, 0))
2729
>a : Symbol(A.a, Decl(privateIdentifierChain.1.ts, 0, 9))
2830

2931
this?.getA().#b; // Error
32+
>this?.getA().#b : Symbol(A.#b, Decl(privateIdentifierChain.1.ts, 1, 9))
3033
>this?.getA : Symbol(A.getA, Decl(privateIdentifierChain.1.ts, 2, 11))
3134
>this : Symbol(A, Decl(privateIdentifierChain.1.ts, 0, 0))
3235
>getA : Symbol(A.getA, Decl(privateIdentifierChain.1.ts, 2, 11))

tests/baselines/reference/privateIdentifierChain.1.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@ class A {
1717
}
1818
constructor() {
1919
this?.#b; // Error
20-
>this?.#b : any
20+
>this?.#b : A | undefined
2121
>this : this
2222

2323
this?.a.#b; // Error
24-
>this?.a.#b : any
24+
>this?.a.#b : A | undefined
2525
>this?.a : A | undefined
2626
>this : this
2727
>a : A | undefined
2828

2929
this?.getA().#b; // Error
30-
>this?.getA().#b : any
30+
>this?.getA().#b : A | undefined
3131
>this?.getA() : A | undefined
3232
>this?.getA : (() => A) | undefined
3333
>this : this
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests/cases/conformance/classes/members/privateNames/privateNameUncheckedJsOptionalChain.js(4,15): error TS18030: An optional chain cannot contain private identifiers.
2+
tests/cases/conformance/classes/members/privateNames/privateNameUncheckedJsOptionalChain.js(5,15): error TS18030: An optional chain cannot contain private identifiers.
3+
4+
5+
==== tests/cases/conformance/classes/members/privateNames/privateNameUncheckedJsOptionalChain.js (2 errors) ====
6+
class C {
7+
#bar;
8+
constructor () {
9+
this?.#foo;
10+
~~~~
11+
!!! error TS18030: An optional chain cannot contain private identifiers.
12+
this?.#bar;
13+
~~~~
14+
!!! error TS18030: An optional chain cannot contain private identifiers.
15+
}
16+
}
17+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== tests/cases/conformance/classes/members/privateNames/privateNameUncheckedJsOptionalChain.js ===
2+
class C {
3+
>C : Symbol(C, Decl(privateNameUncheckedJsOptionalChain.js, 0, 0))
4+
5+
#bar;
6+
>#bar : Symbol(C.#bar, Decl(privateNameUncheckedJsOptionalChain.js, 0, 9))
7+
8+
constructor () {
9+
this?.#foo;
10+
>this : Symbol(C, Decl(privateNameUncheckedJsOptionalChain.js, 0, 0))
11+
12+
this?.#bar;
13+
>this?.#bar : Symbol(C.#bar, Decl(privateNameUncheckedJsOptionalChain.js, 0, 9))
14+
>this : Symbol(C, Decl(privateNameUncheckedJsOptionalChain.js, 0, 0))
15+
}
16+
}
17+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
=== tests/cases/conformance/classes/members/privateNames/privateNameUncheckedJsOptionalChain.js ===
2+
class C {
3+
>C : C
4+
5+
#bar;
6+
>#bar : any
7+
8+
constructor () {
9+
this?.#foo;
10+
>this?.#foo : any
11+
>this : this
12+
13+
this?.#bar;
14+
>this?.#bar : any
15+
>this : this
16+
}
17+
}
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @allowJs: true
2+
// @checkJs: false
3+
// @noEmit: true
4+
// @Filename: privateNameUncheckedJsOptionalChain.js
5+
// @target: es2015
6+
7+
class C {
8+
#bar;
9+
constructor () {
10+
this?.#foo;
11+
this?.#bar;
12+
}
13+
}

0 commit comments

Comments
 (0)