Skip to content

Commit

Permalink
Address false positive cases for return value codefix
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed May 7, 2020
1 parent e4950b2 commit 0c78eff
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 7 deletions.
4 changes: 2 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3568,9 +3568,9 @@ namespace ts {
/* @internal */ createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): Type;
/* @internal */ createSignature(
declaration: SignatureDeclaration,
typeParameters: TypeParameter[] | undefined,
typeParameters: readonly TypeParameter[] | undefined,
thisParameter: Symbol | undefined,
parameters: Symbol[],
parameters: readonly Symbol[],
resolvedReturnType: Type,
typePredicate: TypePredicate | undefined,
minArgumentCount: number,
Expand Down
46 changes: 41 additions & 5 deletions src/services/codefixes/returnValueCorrect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,18 @@ namespace ts.codefix {
}),
});

function createObjectTypeFromLabeledExpression(checker: TypeChecker, label: Identifier, expression: Expression) {
const member = checker.createSymbol(SymbolFlags.Property, label.escapedText);
member.type = checker.getTypeAtLocation(expression);
const members = createSymbolTable([member]);
return checker.createAnonymousType(/*symbol*/ undefined, members, [], [], /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
}

function getFixInfo(checker: TypeChecker, declaration: FunctionLikeDeclaration, expectType: Type, isFunctionType: boolean): Info | undefined {
if (!declaration.body || !isBlock(declaration.body) || length(declaration.body.statements) !== 1) return undefined;

const firstStatement = first(declaration.body.statements);
if (isExpressionStatement(firstStatement) && checkFixedAssignableTo(checker, declaration, firstStatement.expression, expectType, isFunctionType)) {
if (isExpressionStatement(firstStatement) && checkFixedAssignableTo(checker, declaration, checker.getTypeAtLocation(firstStatement.expression), expectType, isFunctionType)) {
return {
declaration,
kind: ProblemKind.MissingReturnStatement,
Expand All @@ -87,7 +94,8 @@ namespace ts.codefix {
}
else if (isLabeledStatement(firstStatement) && isExpressionStatement(firstStatement.statement)) {
const node = createObjectLiteral([createPropertyAssignment(firstStatement.label, firstStatement.statement.expression)]);
if (checkFixedAssignableTo(checker, declaration, node, expectType, isFunctionType)) {
const nodeType = createObjectTypeFromLabeledExpression(checker, firstStatement.label, firstStatement.statement.expression);
if (checkFixedAssignableTo(checker, declaration, nodeType, expectType, isFunctionType)) {
return isArrowFunction(declaration) ? {
declaration,
kind: ProblemKind.MissingParentheses,
Expand All @@ -107,7 +115,8 @@ namespace ts.codefix {
const firstBlockStatement = first(firstStatement.statements);
if (isLabeledStatement(firstBlockStatement) && isExpressionStatement(firstBlockStatement.statement)) {
const node = createObjectLiteral([createPropertyAssignment(firstBlockStatement.label, firstBlockStatement.statement.expression)]);
if (checkFixedAssignableTo(checker, declaration, node, expectType, isFunctionType)) {
const nodeType = createObjectTypeFromLabeledExpression(checker, firstBlockStatement.label, firstBlockStatement.statement.expression);
if (checkFixedAssignableTo(checker, declaration, nodeType, expectType, isFunctionType)) {
return {
declaration,
kind: ProblemKind.MissingReturnStatement,
Expand All @@ -122,8 +131,35 @@ namespace ts.codefix {
return undefined;
}

function checkFixedAssignableTo(checker: TypeChecker, declaration: FunctionLikeDeclaration, expr: Expression, type: Type, isFunctionType: boolean) {
return checker.isTypeAssignableTo(checker.getTypeAtLocation(isFunctionType ? updateFunctionLikeBody(declaration, createBlock([createReturn(expr)])) : expr), type);
function checkFixedAssignableTo(checker: TypeChecker, declaration: FunctionLikeDeclaration, exprType: Type, type: Type, isFunctionType: boolean) {
if (isFunctionType) {
const sig = checker.getSignatureFromDeclaration(declaration);
if (sig) {
if (hasModifier(declaration, ModifierFlags.Async)) {
exprType = checker.createPromiseType(exprType);
}
const newSig = checker.createSignature(
declaration,
sig.typeParameters,
sig.thisParameter,
sig.parameters,
exprType,
/*typePredicate*/ undefined,
sig.minArgumentCount,
sig.flags);
exprType = checker.createAnonymousType(
/*symbol*/ undefined,
createSymbolTable(),
[newSig],
[],
/*stringIndexInfo*/ undefined,
/*numberIndexInfo*/ undefined);
}
else {
exprType = checker.getAnyType();
}
}
return checker.isTypeAssignableTo(exprType, type);
}

function getInfo(checker: TypeChecker, sourceFile: SourceFile, position: number, errorCode: number): Info | undefined {
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/codeFixAwaitInSyncFunction10.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//// await Promise.resolve('foo');
////}

debugger;
verify.codeFix({
index: 2,
description: "Add async modifier to containing function",
Expand Down

0 comments on commit 0c78eff

Please sign in to comment.