From d6d26430c7a64d3a740a7705db6ad63a8bd1c59c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 3 Aug 2022 17:17:26 -0400 Subject: [PATCH] Add rule to auto-paren optional chain in normal prop or element access (#50156) --- src/compiler/factory/nodeFactory.ts | 26 ++++++------- src/compiler/factory/parenthesizerRules.ts | 5 ++- src/compiler/types.ts | 2 +- ...InstantiationExpression1(target=es2020).js | 2 +- ...ChainingInTypeAssertions(target=es2015).js | 12 +++++- ...ingInTypeAssertions(target=es2015).symbols | 19 ++++++++++ ...iningInTypeAssertions(target=es2015).types | 37 +++++++++++++++++++ ...ChainingInTypeAssertions(target=esnext).js | 12 +++++- ...ingInTypeAssertions(target=esnext).symbols | 19 ++++++++++ ...iningInTypeAssertions(target=esnext).types | 37 +++++++++++++++++++ .../optionalChainingInTypeAssertions.ts | 6 +++ 11 files changed, 158 insertions(+), 19 deletions(-) diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 71a9a80c510..d84e733f781 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -1188,7 +1188,7 @@ namespace ts { // @api function createDecorator(expression: Expression) { const node = createBaseNode(SyntaxKind.Decorator); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); node.transformFlags |= propagateChildFlags(node.expression) | TransformFlags.ContainsTypeScript | @@ -2325,7 +2325,7 @@ namespace ts { // @api function createPropertyAccessExpression(expression: Expression, name: string | Identifier | PrivateIdentifier) { const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); node.name = asName(name); node.transformFlags = propagateChildFlags(node.expression) | @@ -2357,7 +2357,7 @@ namespace ts { function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier | PrivateIdentifier) { const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); node.questionDotToken = questionDotToken; node.name = asName(name); node.transformFlags |= @@ -2385,7 +2385,7 @@ namespace ts { // @api function createElementAccessExpression(expression: Expression, index: number | Expression) { const node = createBaseExpression(SyntaxKind.ElementAccessExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); node.argumentExpression = asExpression(index); node.transformFlags |= propagateChildFlags(node.expression) | @@ -2415,7 +2415,7 @@ namespace ts { function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { const node = createBaseExpression(SyntaxKind.ElementAccessExpression); node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); node.questionDotToken = questionDotToken; node.argumentExpression = asExpression(index); node.transformFlags |= @@ -2441,7 +2441,7 @@ namespace ts { // @api function createCallExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { const node = createBaseExpression(SyntaxKind.CallExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); node.typeArguments = asNodeArray(typeArguments); node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); node.transformFlags |= @@ -2476,7 +2476,7 @@ namespace ts { function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { const node = createBaseExpression(SyntaxKind.CallExpression); node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); node.questionDotToken = questionDotToken; node.typeArguments = asNodeArray(typeArguments); node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); @@ -2535,7 +2535,7 @@ namespace ts { // @api function createTaggedTemplateExpression(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) { const node = createBaseExpression(SyntaxKind.TaggedTemplateExpression); - node.tag = parenthesizerRules().parenthesizeLeftSideOfAccess(tag); + node.tag = parenthesizerRules().parenthesizeLeftSideOfAccess(tag, /*optionalChain*/ false); node.typeArguments = asNodeArray(typeArguments); node.template = template; node.transformFlags |= @@ -3085,7 +3085,7 @@ namespace ts { // @api function createExpressionWithTypeArguments(expression: Expression, typeArguments: readonly TypeNode[] | undefined) { const node = createBaseNode(SyntaxKind.ExpressionWithTypeArguments); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); node.transformFlags |= propagateChildFlags(node.expression) | @@ -3125,7 +3125,7 @@ namespace ts { // @api function createNonNullExpression(expression: Expression) { const node = createBaseExpression(SyntaxKind.NonNullExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); node.transformFlags |= propagateChildFlags(node.expression) | TransformFlags.ContainsTypeScript; @@ -3146,7 +3146,7 @@ namespace ts { function createNonNullChain(expression: Expression) { const node = createBaseExpression(SyntaxKind.NonNullExpression); node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); node.transformFlags |= propagateChildFlags(node.expression) | TransformFlags.ContainsTypeScript; @@ -5824,7 +5824,7 @@ namespace ts { } else if (getEmitFlags(callee) & EmitFlags.HelperName) { thisArg = createVoidZero(); - target = parenthesizerRules().parenthesizeLeftSideOfAccess(callee); + target = parenthesizerRules().parenthesizeLeftSideOfAccess(callee, /*optionalChain*/ false); } else if (isPropertyAccessExpression(callee)) { if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) { @@ -5871,7 +5871,7 @@ namespace ts { else { // for `a()` target is `a` and thisArg is `void 0` thisArg = createVoidZero(); - target = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + target = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); } return { target, thisArg }; diff --git a/src/compiler/factory/parenthesizerRules.ts b/src/compiler/factory/parenthesizerRules.ts index c824c13fd16..b09f1826efd 100644 --- a/src/compiler/factory/parenthesizerRules.ts +++ b/src/compiler/factory/parenthesizerRules.ts @@ -319,7 +319,7 @@ namespace ts { * Wraps an expression in parentheses if it is needed in order to use the expression for * property or element access. */ - function parenthesizeLeftSideOfAccess(expression: Expression): LeftHandSideExpression { + function parenthesizeLeftSideOfAccess(expression: Expression, optionalChain?: boolean): LeftHandSideExpression { // isLeftHandSideExpression is almost the correct criterion for when it is not necessary // to parenthesize the expression before a dot. The known exception is: // @@ -328,7 +328,8 @@ namespace ts { // const emittedExpression = skipPartiallyEmittedExpressions(expression); if (isLeftHandSideExpression(emittedExpression) - && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression as NewExpression).arguments)) { + && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression as NewExpression).arguments) + && (optionalChain || !isOptionalChain(emittedExpression))) { // TODO(rbuckton): Verify whether this assertion holds. return expression as LeftHandSideExpression; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2281c7dd06e..f7567053c89 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -7347,7 +7347,7 @@ namespace ts { parenthesizeBranchOfConditionalExpression(branch: Expression): Expression; parenthesizeExpressionOfExportDefault(expression: Expression): Expression; parenthesizeExpressionOfNew(expression: Expression): LeftHandSideExpression; - parenthesizeLeftSideOfAccess(expression: Expression): LeftHandSideExpression; + parenthesizeLeftSideOfAccess(expression: Expression, optionalChain?: boolean): LeftHandSideExpression; parenthesizeOperandOfPostfixUnary(operand: Expression): LeftHandSideExpression; parenthesizeOperandOfPrefixUnary(operand: Expression): UnaryExpression; parenthesizeExpressionsOfCommaDelimitedList(elements: readonly Expression[]): NodeArray; diff --git a/tests/baselines/reference/optionalChainWithInstantiationExpression1(target=es2020).js b/tests/baselines/reference/optionalChainWithInstantiationExpression1(target=es2020).js index 86f3e9f5209..33459ce82ad 100644 --- a/tests/baselines/reference/optionalChainWithInstantiationExpression1(target=es2020).js +++ b/tests/baselines/reference/optionalChainWithInstantiationExpression1(target=es2020).js @@ -16,5 +16,5 @@ a?.b.d //// [optionalChainWithInstantiationExpression1.js] -a?.b.d; +(a?.b).d; a?.b.d; diff --git a/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).js b/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).js index 1c3ccbc1d61..889de6a8a6e 100644 --- a/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).js +++ b/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).js @@ -10,7 +10,12 @@ const foo = new Foo(); /*a1*/(/*a2*/foo.m as any/*a3*/)/*a4*/?.(); /*b1*/(/*b2*/foo.m/*b3*/)/*b4*/?.(); - + +// https://github.com/microsoft/TypeScript/issues/50148 +(foo?.m as any).length; +(foo?.m).length; +(foo?.["m"] as any).length; +(foo?.["m"]).length; //// [optionalChainingInTypeAssertions.js] var _a, _b, _c, _d; @@ -22,3 +27,8 @@ const foo = new Foo(); (_b = foo.m) === null || _b === void 0 ? void 0 : _b.call(foo); /*a1*/ (_c = /*a2*/ foo.m /*a3*/ /*a4*/) === null || _c === void 0 ? void 0 : _c.call(foo); /*b1*/ (_d = /*b2*/ foo.m /*b3*/ /*b4*/) === null || _d === void 0 ? void 0 : _d.call(foo); +// https://github.com/microsoft/TypeScript/issues/50148 +(foo === null || foo === void 0 ? void 0 : foo.m).length; +(foo === null || foo === void 0 ? void 0 : foo.m).length; +(foo === null || foo === void 0 ? void 0 : foo["m"]).length; +(foo === null || foo === void 0 ? void 0 : foo["m"]).length; diff --git a/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).symbols b/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).symbols index 3c84c092176..dbd5aa85506 100644 --- a/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).symbols +++ b/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).symbols @@ -30,3 +30,22 @@ const foo = new Foo(); >foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) >m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) +// https://github.com/microsoft/TypeScript/issues/50148 +(foo?.m as any).length; +>foo?.m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) +>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) +>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) + +(foo?.m).length; +>foo?.m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) +>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) +>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) + +(foo?.["m"] as any).length; +>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) +>"m" : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) + +(foo?.["m"]).length; +>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) +>"m" : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) + diff --git a/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).types b/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).types index 2df0f69dbc5..e59bb8956fa 100644 --- a/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).types +++ b/tests/baselines/reference/optionalChainingInTypeAssertions(target=es2015).types @@ -43,3 +43,40 @@ const foo = new Foo(); >foo : Foo >m : () => void +// https://github.com/microsoft/TypeScript/issues/50148 +(foo?.m as any).length; +>(foo?.m as any).length : any +>(foo?.m as any) : any +>foo?.m as any : any +>foo?.m : () => void +>foo : Foo +>m : () => void +>length : any + +(foo?.m).length; +>(foo?.m).length : any +>(foo?.m) : any +>foo?.m : any +>foo?.m : () => void +>foo : Foo +>m : () => void +>length : any + +(foo?.["m"] as any).length; +>(foo?.["m"] as any).length : any +>(foo?.["m"] as any) : any +>foo?.["m"] as any : any +>foo?.["m"] : () => void +>foo : Foo +>"m" : "m" +>length : any + +(foo?.["m"]).length; +>(foo?.["m"]).length : any +>(foo?.["m"]) : any +>foo?.["m"] : any +>foo?.["m"] : () => void +>foo : Foo +>"m" : "m" +>length : any + diff --git a/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).js b/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).js index 454b3962c57..bbe403f97c7 100644 --- a/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).js +++ b/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).js @@ -10,7 +10,12 @@ const foo = new Foo(); /*a1*/(/*a2*/foo.m as any/*a3*/)/*a4*/?.(); /*b1*/(/*b2*/foo.m/*b3*/)/*b4*/?.(); - + +// https://github.com/microsoft/TypeScript/issues/50148 +(foo?.m as any).length; +(foo?.m).length; +(foo?.["m"] as any).length; +(foo?.["m"]).length; //// [optionalChainingInTypeAssertions.js] class Foo { @@ -21,3 +26,8 @@ foo.m?.(); foo.m?.(); /*a1*/ /*a2*/ foo.m /*a3*/ /*a4*/?.(); /*b1*/ /*b2*/ foo.m /*b3*/ /*b4*/?.(); +// https://github.com/microsoft/TypeScript/issues/50148 +(foo?.m).length; +(foo?.m).length; +(foo?.["m"]).length; +(foo?.["m"]).length; diff --git a/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).symbols b/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).symbols index 3c84c092176..dbd5aa85506 100644 --- a/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).symbols +++ b/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).symbols @@ -30,3 +30,22 @@ const foo = new Foo(); >foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) >m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) +// https://github.com/microsoft/TypeScript/issues/50148 +(foo?.m as any).length; +>foo?.m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) +>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) +>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) + +(foo?.m).length; +>foo?.m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) +>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) +>m : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) + +(foo?.["m"] as any).length; +>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) +>"m" : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) + +(foo?.["m"]).length; +>foo : Symbol(foo, Decl(optionalChainingInTypeAssertions.ts, 4, 5)) +>"m" : Symbol(Foo.m, Decl(optionalChainingInTypeAssertions.ts, 0, 11)) + diff --git a/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).types b/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).types index 2df0f69dbc5..e59bb8956fa 100644 --- a/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).types +++ b/tests/baselines/reference/optionalChainingInTypeAssertions(target=esnext).types @@ -43,3 +43,40 @@ const foo = new Foo(); >foo : Foo >m : () => void +// https://github.com/microsoft/TypeScript/issues/50148 +(foo?.m as any).length; +>(foo?.m as any).length : any +>(foo?.m as any) : any +>foo?.m as any : any +>foo?.m : () => void +>foo : Foo +>m : () => void +>length : any + +(foo?.m).length; +>(foo?.m).length : any +>(foo?.m) : any +>foo?.m : any +>foo?.m : () => void +>foo : Foo +>m : () => void +>length : any + +(foo?.["m"] as any).length; +>(foo?.["m"] as any).length : any +>(foo?.["m"] as any) : any +>foo?.["m"] as any : any +>foo?.["m"] : () => void +>foo : Foo +>"m" : "m" +>length : any + +(foo?.["m"]).length; +>(foo?.["m"]).length : any +>(foo?.["m"]) : any +>foo?.["m"] : any +>foo?.["m"] : () => void +>foo : Foo +>"m" : "m" +>length : any + diff --git a/tests/cases/conformance/expressions/optionalChaining/optionalChainingInTypeAssertions.ts b/tests/cases/conformance/expressions/optionalChaining/optionalChainingInTypeAssertions.ts index 3140c2d6ca5..cd92258d3e2 100644 --- a/tests/cases/conformance/expressions/optionalChaining/optionalChainingInTypeAssertions.ts +++ b/tests/cases/conformance/expressions/optionalChaining/optionalChainingInTypeAssertions.ts @@ -11,3 +11,9 @@ const foo = new Foo(); /*a1*/(/*a2*/foo.m as any/*a3*/)/*a4*/?.(); /*b1*/(/*b2*/foo.m/*b3*/)/*b4*/?.(); + +// https://github.com/microsoft/TypeScript/issues/50148 +(foo?.m as any).length; +(foo?.m).length; +(foo?.["m"] as any).length; +(foo?.["m"]).length; \ No newline at end of file