Skip to content

Commit d6d2643

Browse files
authored
Add rule to auto-paren optional chain in normal prop or element access (microsoft#50156)
1 parent c82c9a9 commit d6d2643

11 files changed

+158
-19
lines changed

src/compiler/factory/nodeFactory.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,7 +1188,7 @@ namespace ts {
11881188
// @api
11891189
function createDecorator(expression: Expression) {
11901190
const node = createBaseNode<Decorator>(SyntaxKind.Decorator);
1191-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
1191+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false);
11921192
node.transformFlags |=
11931193
propagateChildFlags(node.expression) |
11941194
TransformFlags.ContainsTypeScript |
@@ -2325,7 +2325,7 @@ namespace ts {
23252325
// @api
23262326
function createPropertyAccessExpression(expression: Expression, name: string | Identifier | PrivateIdentifier) {
23272327
const node = createBaseExpression<PropertyAccessExpression>(SyntaxKind.PropertyAccessExpression);
2328-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
2328+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false);
23292329
node.name = asName(name);
23302330
node.transformFlags =
23312331
propagateChildFlags(node.expression) |
@@ -2357,7 +2357,7 @@ namespace ts {
23572357
function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier | PrivateIdentifier) {
23582358
const node = createBaseExpression<PropertyAccessChain>(SyntaxKind.PropertyAccessExpression);
23592359
node.flags |= NodeFlags.OptionalChain;
2360-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
2360+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true);
23612361
node.questionDotToken = questionDotToken;
23622362
node.name = asName(name);
23632363
node.transformFlags |=
@@ -2385,7 +2385,7 @@ namespace ts {
23852385
// @api
23862386
function createElementAccessExpression(expression: Expression, index: number | Expression) {
23872387
const node = createBaseExpression<ElementAccessExpression>(SyntaxKind.ElementAccessExpression);
2388-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
2388+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false);
23892389
node.argumentExpression = asExpression(index);
23902390
node.transformFlags |=
23912391
propagateChildFlags(node.expression) |
@@ -2415,7 +2415,7 @@ namespace ts {
24152415
function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) {
24162416
const node = createBaseExpression<ElementAccessChain>(SyntaxKind.ElementAccessExpression);
24172417
node.flags |= NodeFlags.OptionalChain;
2418-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
2418+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true);
24192419
node.questionDotToken = questionDotToken;
24202420
node.argumentExpression = asExpression(index);
24212421
node.transformFlags |=
@@ -2441,7 +2441,7 @@ namespace ts {
24412441
// @api
24422442
function createCallExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) {
24432443
const node = createBaseExpression<CallExpression>(SyntaxKind.CallExpression);
2444-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
2444+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false);
24452445
node.typeArguments = asNodeArray(typeArguments);
24462446
node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray));
24472447
node.transformFlags |=
@@ -2476,7 +2476,7 @@ namespace ts {
24762476
function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) {
24772477
const node = createBaseExpression<CallChain>(SyntaxKind.CallExpression);
24782478
node.flags |= NodeFlags.OptionalChain;
2479-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
2479+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true);
24802480
node.questionDotToken = questionDotToken;
24812481
node.typeArguments = asNodeArray(typeArguments);
24822482
node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray));
@@ -2535,7 +2535,7 @@ namespace ts {
25352535
// @api
25362536
function createTaggedTemplateExpression(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) {
25372537
const node = createBaseExpression<TaggedTemplateExpression>(SyntaxKind.TaggedTemplateExpression);
2538-
node.tag = parenthesizerRules().parenthesizeLeftSideOfAccess(tag);
2538+
node.tag = parenthesizerRules().parenthesizeLeftSideOfAccess(tag, /*optionalChain*/ false);
25392539
node.typeArguments = asNodeArray(typeArguments);
25402540
node.template = template;
25412541
node.transformFlags |=
@@ -3085,7 +3085,7 @@ namespace ts {
30853085
// @api
30863086
function createExpressionWithTypeArguments(expression: Expression, typeArguments: readonly TypeNode[] | undefined) {
30873087
const node = createBaseNode<ExpressionWithTypeArguments>(SyntaxKind.ExpressionWithTypeArguments);
3088-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
3088+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false);
30893089
node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments);
30903090
node.transformFlags |=
30913091
propagateChildFlags(node.expression) |
@@ -3125,7 +3125,7 @@ namespace ts {
31253125
// @api
31263126
function createNonNullExpression(expression: Expression) {
31273127
const node = createBaseExpression<NonNullExpression>(SyntaxKind.NonNullExpression);
3128-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
3128+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false);
31293129
node.transformFlags |=
31303130
propagateChildFlags(node.expression) |
31313131
TransformFlags.ContainsTypeScript;
@@ -3146,7 +3146,7 @@ namespace ts {
31463146
function createNonNullChain(expression: Expression) {
31473147
const node = createBaseExpression<NonNullChain>(SyntaxKind.NonNullExpression);
31483148
node.flags |= NodeFlags.OptionalChain;
3149-
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
3149+
node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true);
31503150
node.transformFlags |=
31513151
propagateChildFlags(node.expression) |
31523152
TransformFlags.ContainsTypeScript;
@@ -5824,7 +5824,7 @@ namespace ts {
58245824
}
58255825
else if (getEmitFlags(callee) & EmitFlags.HelperName) {
58265826
thisArg = createVoidZero();
5827-
target = parenthesizerRules().parenthesizeLeftSideOfAccess(callee);
5827+
target = parenthesizerRules().parenthesizeLeftSideOfAccess(callee, /*optionalChain*/ false);
58285828
}
58295829
else if (isPropertyAccessExpression(callee)) {
58305830
if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) {
@@ -5871,7 +5871,7 @@ namespace ts {
58715871
else {
58725872
// for `a()` target is `a` and thisArg is `void 0`
58735873
thisArg = createVoidZero();
5874-
target = parenthesizerRules().parenthesizeLeftSideOfAccess(expression);
5874+
target = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false);
58755875
}
58765876

58775877
return { target, thisArg };

src/compiler/factory/parenthesizerRules.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ namespace ts {
319319
* Wraps an expression in parentheses if it is needed in order to use the expression for
320320
* property or element access.
321321
*/
322-
function parenthesizeLeftSideOfAccess(expression: Expression): LeftHandSideExpression {
322+
function parenthesizeLeftSideOfAccess(expression: Expression, optionalChain?: boolean): LeftHandSideExpression {
323323
// isLeftHandSideExpression is almost the correct criterion for when it is not necessary
324324
// to parenthesize the expression before a dot. The known exception is:
325325
//
@@ -328,7 +328,8 @@ namespace ts {
328328
//
329329
const emittedExpression = skipPartiallyEmittedExpressions(expression);
330330
if (isLeftHandSideExpression(emittedExpression)
331-
&& (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression as NewExpression).arguments)) {
331+
&& (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression as NewExpression).arguments)
332+
&& (optionalChain || !isOptionalChain(emittedExpression))) {
332333
// TODO(rbuckton): Verify whether this assertion holds.
333334
return expression as LeftHandSideExpression;
334335
}

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7347,7 +7347,7 @@ namespace ts {
73477347
parenthesizeBranchOfConditionalExpression(branch: Expression): Expression;
73487348
parenthesizeExpressionOfExportDefault(expression: Expression): Expression;
73497349
parenthesizeExpressionOfNew(expression: Expression): LeftHandSideExpression;
7350-
parenthesizeLeftSideOfAccess(expression: Expression): LeftHandSideExpression;
7350+
parenthesizeLeftSideOfAccess(expression: Expression, optionalChain?: boolean): LeftHandSideExpression;
73517351
parenthesizeOperandOfPostfixUnary(operand: Expression): LeftHandSideExpression;
73527352
parenthesizeOperandOfPrefixUnary(operand: Expression): UnaryExpression;
73537353
parenthesizeExpressionsOfCommaDelimitedList(elements: readonly Expression[]): NodeArray<Expression>;

tests/baselines/reference/optionalChainWithInstantiationExpression1(target=es2020).js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ a?.b.d
1616

1717

1818
//// [optionalChainWithInstantiationExpression1.js]
19-
a?.b.d;
19+
(a?.b).d;
2020
a?.b.d;

tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ const foo = new Foo();
1010

1111
/*a1*/(/*a2*/foo.m as any/*a3*/)/*a4*/?.();
1212
/*b1*/(/*b2*/<any>foo.m/*b3*/)/*b4*/?.();
13-
13+
14+
// https://github.com/microsoft/TypeScript/issues/50148
15+
(foo?.m as any).length;
16+
(<any>foo?.m).length;
17+
(foo?.["m"] as any).length;
18+
(<any>foo?.["m"]).length;
1419

1520
//// [optionalChainingInTypeAssertions.js]
1621
var _a, _b, _c, _d;
@@ -22,3 +27,8 @@ const foo = new Foo();
2227
(_b = foo.m) === null || _b === void 0 ? void 0 : _b.call(foo);
2328
/*a1*/ (_c = /*a2*/ foo.m /*a3*/ /*a4*/) === null || _c === void 0 ? void 0 : _c.call(foo);
2429
/*b1*/ (_d = /*b2*/ foo.m /*b3*/ /*b4*/) === null || _d === void 0 ? void 0 : _d.call(foo);
30+
// https://github.com/microsoft/TypeScript/issues/50148
31+
(foo === null || foo === void 0 ? void 0 : foo.m).length;
32+
(foo === null || foo === void 0 ? void 0 : foo.m).length;
33+
(foo === null || foo === void 0 ? void 0 : foo["m"]).length;
34+
(foo === null || foo === void 0 ? void 0 : foo["m"]).length;

tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).symbols

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,22 @@ const foo = new Foo();
3030
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
3131
>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
3232

33+
// https://github.com/microsoft/TypeScript/issues/50148
34+
(foo?.m as any).length;
35+
>foo?.m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
36+
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
37+
>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
38+
39+
(<any>foo?.m).length;
40+
>foo?.m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
41+
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
42+
>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
43+
44+
(foo?.["m"] as any).length;
45+
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
46+
>"m" : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
47+
48+
(<any>foo?.["m"]).length;
49+
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
50+
>"m" : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
51+

tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).types

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,40 @@ const foo = new Foo();
4343
>foo : Foo
4444
>m : () => void
4545

46+
// https://github.com/microsoft/TypeScript/issues/50148
47+
(foo?.m as any).length;
48+
>(foo?.m as any).length : any
49+
>(foo?.m as any) : any
50+
>foo?.m as any : any
51+
>foo?.m : () => void
52+
>foo : Foo
53+
>m : () => void
54+
>length : any
55+
56+
(<any>foo?.m).length;
57+
>(<any>foo?.m).length : any
58+
>(<any>foo?.m) : any
59+
><any>foo?.m : any
60+
>foo?.m : () => void
61+
>foo : Foo
62+
>m : () => void
63+
>length : any
64+
65+
(foo?.["m"] as any).length;
66+
>(foo?.["m"] as any).length : any
67+
>(foo?.["m"] as any) : any
68+
>foo?.["m"] as any : any
69+
>foo?.["m"] : () => void
70+
>foo : Foo
71+
>"m" : "m"
72+
>length : any
73+
74+
(<any>foo?.["m"]).length;
75+
>(<any>foo?.["m"]).length : any
76+
>(<any>foo?.["m"]) : any
77+
><any>foo?.["m"] : any
78+
>foo?.["m"] : () => void
79+
>foo : Foo
80+
>"m" : "m"
81+
>length : any
82+

tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ const foo = new Foo();
1010

1111
/*a1*/(/*a2*/foo.m as any/*a3*/)/*a4*/?.();
1212
/*b1*/(/*b2*/<any>foo.m/*b3*/)/*b4*/?.();
13-
13+
14+
// https://github.com/microsoft/TypeScript/issues/50148
15+
(foo?.m as any).length;
16+
(<any>foo?.m).length;
17+
(foo?.["m"] as any).length;
18+
(<any>foo?.["m"]).length;
1419

1520
//// [optionalChainingInTypeAssertions.js]
1621
class Foo {
@@ -21,3 +26,8 @@ foo.m?.();
2126
foo.m?.();
2227
/*a1*/ /*a2*/ foo.m /*a3*/ /*a4*/?.();
2328
/*b1*/ /*b2*/ foo.m /*b3*/ /*b4*/?.();
29+
// https://github.com/microsoft/TypeScript/issues/50148
30+
(foo?.m).length;
31+
(foo?.m).length;
32+
(foo?.["m"]).length;
33+
(foo?.["m"]).length;

tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).symbols

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,22 @@ const foo = new Foo();
3030
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
3131
>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
3232

33+
// https://github.com/microsoft/TypeScript/issues/50148
34+
(foo?.m as any).length;
35+
>foo?.m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
36+
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
37+
>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
38+
39+
(<any>foo?.m).length;
40+
>foo?.m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
41+
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
42+
>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
43+
44+
(foo?.["m"] as any).length;
45+
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
46+
>"m" : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
47+
48+
(<any>foo?.["m"]).length;
49+
>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5))
50+
>"m" : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11))
51+

tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).types

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,40 @@ const foo = new Foo();
4343
>foo : Foo
4444
>m : () => void
4545

46+
// https://github.com/microsoft/TypeScript/issues/50148
47+
(foo?.m as any).length;
48+
>(foo?.m as any).length : any
49+
>(foo?.m as any) : any
50+
>foo?.m as any : any
51+
>foo?.m : () => void
52+
>foo : Foo
53+
>m : () => void
54+
>length : any
55+
56+
(<any>foo?.m).length;
57+
>(<any>foo?.m).length : any
58+
>(<any>foo?.m) : any
59+
><any>foo?.m : any
60+
>foo?.m : () => void
61+
>foo : Foo
62+
>m : () => void
63+
>length : any
64+
65+
(foo?.["m"] as any).length;
66+
>(foo?.["m"] as any).length : any
67+
>(foo?.["m"] as any) : any
68+
>foo?.["m"] as any : any
69+
>foo?.["m"] : () => void
70+
>foo : Foo
71+
>"m" : "m"
72+
>length : any
73+
74+
(<any>foo?.["m"]).length;
75+
>(<any>foo?.["m"]).length : any
76+
>(<any>foo?.["m"]) : any
77+
><any>foo?.["m"] : any
78+
>foo?.["m"] : () => void
79+
>foo : Foo
80+
>"m" : "m"
81+
>length : any
82+

tests/cases/conformance/expressions/optionalChaining/optionalChainingInTypeAssertions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ const foo = new Foo();
1111

1212
/*a1*/(/*a2*/foo.m as any/*a3*/)/*a4*/?.();
1313
/*b1*/(/*b2*/<any>foo.m/*b3*/)/*b4*/?.();
14+
15+
// https://github.com/microsoft/TypeScript/issues/50148
16+
(foo?.m as any).length;
17+
(<any>foo?.m).length;
18+
(foo?.["m"] as any).length;
19+
(<any>foo?.["m"]).length;

0 commit comments

Comments
 (0)