capture thisArg of optionalChaining in parens (#35494)

Fixes: #35476
This commit is contained in:
Klaus Meinhardt 2021-03-04 04:35:17 +01:00 committed by GitHub
parent 8a81a6722d
commit 15e69acc20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 52 additions and 3 deletions

View File

@ -21,11 +21,15 @@ namespace ts {
return node;
}
switch (node.kind) {
case SyntaxKind.CallExpression: {
const updated = visitNonOptionalCallExpression(node as CallExpression, /*captureThisArg*/ false);
Debug.assertNotNode(updated, isSyntheticReference);
return updated;
}
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.CallExpression:
if (node.flags & NodeFlags.OptionalChain) {
const updated = visitOptionalExpression(node as OptionalChain, /*captureThisArg*/ false, /*isDelete*/ false);
if (isOptionalChain(node)) {
const updated = visitOptionalExpression(node, /*captureThisArg*/ false, /*isDelete*/ false);
Debug.assertNotNode(updated, isSyntheticReference);
return updated;
}
@ -94,6 +98,15 @@ namespace ts {
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
return visitOptionalExpression(node, captureThisArg, /*isDelete*/ false);
}
if (isParenthesizedExpression(node.expression) && isOptionalChain(skipParentheses(node.expression))) {
// capture thisArg for calls of parenthesized optional chains like `(foo?.bar)()`
const expression = visitNonOptionalParenthesizedExpression(node.expression, /*captureThisArg*/ true, /*isDelete*/ false);
const args = visitNodes(node.arguments, visitor, isExpression);
if (isSyntheticReference(expression)) {
return setTextRange(factory.createFunctionCallCall(expression.expression, expression.thisArg, args), node);
}
return factory.updateCallExpression(node, expression, /*typeArguments*/ undefined, args);
}
return visitEachChild(node, visitor, context);
}

View File

@ -188,4 +188,11 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[1], 2);
assert.strictEqual(result.output[2], result.o);
});
it("(o?.f)()", async () => {
const result = evaluator.evaluateTypeScript(`
export const foo = { bar() { return this } };
export const output = (foo?.bar)();
`);
assert.strictEqual(result.output, result.foo);
});
});

View File

@ -0,0 +1,18 @@
//// [parentheses.ts]
declare const o1: ((...args: any[]) => number);
declare const o2: { b: (...args: any[]) => number };
declare const o3: { b: ((...args: any[]) => (...args: any[]) => number) };
declare const o4: { b: ((...args: any[]) => { c: (...args: any[]) => number } ) };
(o1)(o1 ?? 1);
(o2?.b)(o1 ?? 1);
(o3?.b())(o1 ?? 1);
(o4?.b().c)(o1 ?? 1);
//// [parentheses.js]
var _a;
(o1)(o1 !== null && o1 !== void 0 ? o1 : 1);
(o2 === null || o2 === void 0 ? void 0 : o2.b).call(o2, o1 !== null && o1 !== void 0 ? o1 : 1);
(o3 === null || o3 === void 0 ? void 0 : o3.b())(o1 !== null && o1 !== void 0 ? o1 : 1);
(o4 === null || o4 === void 0 ? void 0 : (_a = o4.b()).c).call(_a, o1 !== null && o1 !== void 0 ? o1 : 1);

View File

@ -0,0 +1,11 @@
// @noTypesAndSymbols: true
declare const o1: ((...args: any[]) => number);
declare const o2: { b: (...args: any[]) => number };
declare const o3: { b: ((...args: any[]) => (...args: any[]) => number) };
declare const o4: { b: ((...args: any[]) => { c: (...args: any[]) => number } ) };
(o1)(o1 ?? 1);
(o2?.b)(o1 ?? 1);
(o3?.b())(o1 ?? 1);
(o4?.b().c)(o1 ?? 1);