mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-24 20:44:53 -05:00
fix(41405): allow using property access as reference to function (#41429)
This commit is contained in:
@@ -65,7 +65,7 @@ namespace ts.codefix {
|
||||
const isInJavascript = isInJSFile(functionToConvert);
|
||||
const setOfExpressionsToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker);
|
||||
const functionToConvertRenamed = renameCollidingVarNames(functionToConvert, checker, synthNamesMap);
|
||||
const returnStatements = functionToConvertRenamed.body && isBlock(functionToConvertRenamed.body) ? getReturnStatementsWithPromiseHandlers(functionToConvertRenamed.body) : emptyArray;
|
||||
const returnStatements = functionToConvertRenamed.body && isBlock(functionToConvertRenamed.body) ? getReturnStatementsWithPromiseHandlers(functionToConvertRenamed.body, checker) : emptyArray;
|
||||
const transformer: Transformer = { checker, synthNamesMap, setOfExpressionsToReturn, isInJSFile: isInJavascript };
|
||||
if (!returnStatements.length) {
|
||||
return;
|
||||
@@ -90,10 +90,10 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function getReturnStatementsWithPromiseHandlers(body: Block): readonly ReturnStatement[] {
|
||||
function getReturnStatementsWithPromiseHandlers(body: Block, checker: TypeChecker): readonly ReturnStatement[] {
|
||||
const res: ReturnStatement[] = [];
|
||||
forEachReturnStatement(body, ret => {
|
||||
if (isReturnStatementWithFixablePromiseHandler(ret)) res.push(ret);
|
||||
if (isReturnStatementWithFixablePromiseHandler(ret, checker)) res.push(ret);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
@@ -374,13 +374,14 @@ namespace ts.codefix {
|
||||
case SyntaxKind.NullKeyword:
|
||||
// do not produce a transformed statement for a null argument
|
||||
break;
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
case SyntaxKind.Identifier: // identifier includes undefined
|
||||
if (!argName) {
|
||||
// undefined was argument passed to promise handler
|
||||
break;
|
||||
}
|
||||
|
||||
const synthCall = factory.createCallExpression(getSynthesizedDeepClone(func as Identifier), /*typeArguments*/ undefined, isSynthIdentifier(argName) ? [argName.identifier] : []);
|
||||
const synthCall = factory.createCallExpression(getSynthesizedDeepClone(func as Identifier | PropertyAccessExpression), /*typeArguments*/ undefined, isSynthIdentifier(argName) ? [argName.identifier] : []);
|
||||
if (shouldReturn(parent, transformer)) {
|
||||
return maybeAnnotateAndReturn(synthCall, parent.typeArguments?.[0]);
|
||||
}
|
||||
@@ -410,7 +411,7 @@ namespace ts.codefix {
|
||||
for (const statement of funcBody.statements) {
|
||||
if (isReturnStatement(statement)) {
|
||||
seenReturnStatement = true;
|
||||
if (isReturnStatementWithFixablePromiseHandler(statement)) {
|
||||
if (isReturnStatementWithFixablePromiseHandler(statement, transformer.checker)) {
|
||||
refactoredStmts = refactoredStmts.concat(getInnerTransformationBody(transformer, [statement], prevArgName));
|
||||
}
|
||||
else {
|
||||
@@ -432,7 +433,7 @@ namespace ts.codefix {
|
||||
seenReturnStatement);
|
||||
}
|
||||
else {
|
||||
const innerRetStmts = isFixablePromiseHandler(funcBody) ? [factory.createReturnStatement(funcBody)] : emptyArray;
|
||||
const innerRetStmts = isFixablePromiseHandler(funcBody, transformer.checker) ? [factory.createReturnStatement(funcBody)] : emptyArray;
|
||||
const innerCbBody = getInnerTransformationBody(transformer, innerRetStmts, prevArgName);
|
||||
|
||||
if (innerCbBody.length > 0) {
|
||||
@@ -536,6 +537,9 @@ namespace ts.codefix {
|
||||
else if (isIdentifier(funcNode)) {
|
||||
name = getMapEntryOrDefault(funcNode);
|
||||
}
|
||||
else if (isPropertyAccessExpression(funcNode) && isIdentifier(funcNode.name)) {
|
||||
name = getMapEntryOrDefault(funcNode.name);
|
||||
}
|
||||
|
||||
// return undefined argName when arg is null or undefined
|
||||
// eslint-disable-next-line no-in-operator
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace ts {
|
||||
return !isAsyncFunction(node) &&
|
||||
node.body &&
|
||||
isBlock(node.body) &&
|
||||
hasReturnStatementWithPromiseHandler(node.body) &&
|
||||
hasReturnStatementWithPromiseHandler(node.body, checker) &&
|
||||
returnsPromise(node, checker);
|
||||
}
|
||||
|
||||
@@ -129,25 +129,25 @@ namespace ts {
|
||||
return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator;
|
||||
}
|
||||
|
||||
function hasReturnStatementWithPromiseHandler(body: Block): boolean {
|
||||
return !!forEachReturnStatement(body, isReturnStatementWithFixablePromiseHandler);
|
||||
function hasReturnStatementWithPromiseHandler(body: Block, checker: TypeChecker): boolean {
|
||||
return !!forEachReturnStatement(body, statement => isReturnStatementWithFixablePromiseHandler(statement, checker));
|
||||
}
|
||||
|
||||
export function isReturnStatementWithFixablePromiseHandler(node: Node): node is ReturnStatement & { expression: CallExpression } {
|
||||
return isReturnStatement(node) && !!node.expression && isFixablePromiseHandler(node.expression);
|
||||
export function isReturnStatementWithFixablePromiseHandler(node: Node, checker: TypeChecker): node is ReturnStatement & { expression: CallExpression } {
|
||||
return isReturnStatement(node) && !!node.expression && isFixablePromiseHandler(node.expression, checker);
|
||||
}
|
||||
|
||||
// Should be kept up to date with transformExpression in convertToAsyncFunction.ts
|
||||
export function isFixablePromiseHandler(node: Node): boolean {
|
||||
export function isFixablePromiseHandler(node: Node, checker: TypeChecker): boolean {
|
||||
// ensure outermost call exists and is a promise handler
|
||||
if (!isPromiseHandler(node) || !node.arguments.every(isFixablePromiseArgument)) {
|
||||
if (!isPromiseHandler(node) || !node.arguments.every(arg => isFixablePromiseArgument(arg, checker))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ensure all chained calls are valid
|
||||
let currentNode = node.expression;
|
||||
while (isPromiseHandler(currentNode) || isPropertyAccessExpression(currentNode)) {
|
||||
if (isCallExpression(currentNode) && !currentNode.arguments.every(isFixablePromiseArgument)) {
|
||||
if (isCallExpression(currentNode) && !currentNode.arguments.every(arg => isFixablePromiseArgument(arg, checker))) {
|
||||
return false;
|
||||
}
|
||||
currentNode = currentNode.expression;
|
||||
@@ -171,7 +171,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// should be kept up to date with getTransformationBody in convertToAsyncFunction.ts
|
||||
function isFixablePromiseArgument(arg: Expression): boolean {
|
||||
function isFixablePromiseArgument(arg: Expression, checker: TypeChecker): boolean {
|
||||
switch (arg.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
@@ -179,8 +179,16 @@ namespace ts {
|
||||
visitedNestedConvertibleFunctions.set(getKeyFromNode(arg as FunctionLikeDeclaration), true);
|
||||
// falls through
|
||||
case SyntaxKind.NullKeyword:
|
||||
case SyntaxKind.Identifier: // identifier includes undefined
|
||||
return true;
|
||||
case SyntaxKind.Identifier:
|
||||
case SyntaxKind.PropertyAccessExpression: {
|
||||
const symbol = checker.getSymbolAtLocation(arg);
|
||||
if (!symbol) {
|
||||
return false;
|
||||
}
|
||||
return checker.isUndefinedSymbol(symbol) ||
|
||||
some(skipAlias(symbol, checker).declarations, d => isFunctionLike(d) || hasInitializer(d) && !!d.initializer && isFunctionLike(d.initializer));
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -539,6 +539,7 @@ function [#|f|]():Promise<void | Response> {
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_NoRes3", `
|
||||
function [#|f|]():Promise<void | Response> {
|
||||
return fetch('https://typescriptlang.org').catch(rej => console.log(rej));
|
||||
@@ -600,6 +601,7 @@ function [#|f|]():Promise<void> {
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_ResRef", `
|
||||
function [#|f|]():Promise<boolean> {
|
||||
return fetch('https://typescriptlang.org').then(res);
|
||||
@@ -609,6 +611,75 @@ function res(result){
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_ResRef1", `
|
||||
class Foo {
|
||||
public [#|method|](): Promise<boolean> {
|
||||
return fetch('a').then(this.foo);
|
||||
}
|
||||
|
||||
private foo(res) {
|
||||
return res.ok;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_ResRef2", `
|
||||
class Foo {
|
||||
public [#|method|](): Promise<Response> {
|
||||
return fetch('a').then(this.foo);
|
||||
}
|
||||
|
||||
private foo = res => res;
|
||||
}
|
||||
`);
|
||||
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_ResRef3", `
|
||||
const res = (result) => {
|
||||
return result.ok;
|
||||
}
|
||||
function [#|f|](): Promise<boolean> {
|
||||
return fetch('https://typescriptlang.org').then(res);
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef1", `
|
||||
const res = 1;
|
||||
function [#|f|]() {
|
||||
return fetch('https://typescriptlang.org').then(res);
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef2", `
|
||||
class Foo {
|
||||
private foo = 1;
|
||||
public [#|method|](): Promise<boolean> {
|
||||
return fetch('a').then(this.foo);
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef3", `
|
||||
const res = undefined;
|
||||
function [#|f|]() {
|
||||
return fetch('https://typescriptlang.org').then(res);
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef4", `
|
||||
class Foo {
|
||||
private foo = undefined;
|
||||
public [#|method|](): Promise<boolean> {
|
||||
return fetch('a').then(this.foo);
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal", `
|
||||
function [#|f|]():Promise<void> {
|
||||
return fetch('https://typescriptlang.org').then(res);
|
||||
@@ -618,6 +689,19 @@ function res(result){
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal1", `
|
||||
class Foo {
|
||||
public [#|method|](): Promise<void> {
|
||||
return fetch('a').then(this.foo);
|
||||
}
|
||||
|
||||
private foo(res) {
|
||||
console.log(res);
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_NoBrackets", `
|
||||
function [#|f|]():Promise<void> {
|
||||
return fetch('https://typescriptlang.org').then(result => console.log(result));
|
||||
|
||||
Reference in New Issue
Block a user