Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3f59aa9
stash
Kingwl Aug 10, 2018
7671221
add surmise for return type
Kingwl Aug 14, 2018
0b1e6cc
add support for more case
Kingwl Aug 14, 2018
1f9b9c0
add more test case
Kingwl Aug 16, 2018
aca1722
add more testcase and fix all test
Kingwl Aug 20, 2018
7cfd0fb
Merge branch 'master' into returnValueSurmise
Kingwl Apr 29, 2019
cbe59bb
fix changed diagnosis
Kingwl Apr 30, 2019
48f1cca
Merge branch 'master' into returnValueSurmise
Kingwl May 8, 2019
98d7eba
fix broken test case
Kingwl May 8, 2019
537e806
add more case
Kingwl May 8, 2019
505e299
Merge branch 'master' into returnValueSurmise
Kingwl Jan 9, 2020
6fecf32
rename quickfix
Kingwl Jan 9, 2020
4c0af72
fix conflict
Kingwl Jan 9, 2020
98377f3
fix fix desc
Kingwl Jan 9, 2020
49d21bf
fix semi
Kingwl Jan 9, 2020
a2b15ed
Merge branch 'master' into returnValueSurmise
Kingwl Mar 4, 2020
eabc5a4
Merge branch 'master' into returnValueSurmise
Kingwl Mar 4, 2020
944ddf4
Avoid replace brace with paren
Kingwl Mar 18, 2020
6c88a01
Split fix all action
Kingwl Mar 18, 2020
94f845a
Add return work in same line
Kingwl Mar 18, 2020
a7f37a3
Merge branch 'master' into returnValueSurmise
Kingwl Mar 18, 2020
7fe9a82
fix test cases
Kingwl Mar 18, 2020
af9552e
rename baseline
Kingwl Mar 26, 2020
601fc5e
refactor and handle comment
Kingwl Mar 26, 2020
175cf4e
Support semi
Kingwl Mar 26, 2020
8b332fe
Merge branch 'master' into returnValueSurmise
Kingwl Mar 26, 2020
5d8355c
make helper internal
Kingwl Mar 26, 2020
522cac8
Merge branch 'master' into returnValueSurmise
Kingwl Apr 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add support for more case
  • Loading branch information
Kingwl committed Aug 14, 2018
commit 0b1e6cc6ea15df4a599d317775bfa9a52885fb78
114 changes: 58 additions & 56 deletions src/services/codefixes/returnValueSurmise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,18 @@ namespace ts.codefix {
MissingParentheses
}

interface Info {
interface MissingReturnInfo {
kind: FixKind.MissingReturnStatement;
declaration: FunctionLikeDeclaration;
fixKind: FixKind;
}

interface MissingParenthesesInfo {
kind: FixKind.MissingParentheses;
declaration: ArrowFunction;
}

type Info = MissingReturnInfo | MissingParenthesesInfo;

registerCodeFix({
errorCodes,
fixIds: [fixIdAddReturnStatement, fixIdRemoveBlockBodyBrace, fixIdReplaceBraceWithParen, fixIdWrapTheBlockWithParen],
Expand All @@ -29,7 +36,7 @@ namespace ts.codefix {
const info = getInfo(program.getTypeChecker(), sourceFile, start);
if (!info) return undefined;

if (info.fixKind === FixKind.MissingReturnStatement) {
if (info.kind === FixKind.MissingReturnStatement) {
return [
getActionForfixAddReturnStatement(context, info.declaration),
getActionForfixRemoveBlockBodyBrace(context, info.declaration),
Expand All @@ -55,7 +62,7 @@ namespace ts.codefix {
removeBlockBodyBrace(changes, diag.file, info.declaration, /* withParen */ true);
break;
case fixIdWrapTheBlockWithParen:
wrapBlockWithParen(changes, diag.file, info.declaration);
wrapBlockWithParen(changes, diag.file, <ArrowFunction>info.declaration);
break;
default:
Debug.fail(JSON.stringify(context.fixId));
Expand All @@ -82,61 +89,43 @@ namespace ts.codefix {
}
}

function getFixInfo(checker: TypeChecker, declaration: FunctionLikeDeclaration, expectType: Type): Info | 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)) {
const maybeFunction = updateFunctionLikeBody(declaration, createBlock([createReturn(firstStatement.expression)]));
const maybeType = checker.getTypeAtLocation(maybeFunction);
if (checker.isTypeAssignableTo(maybeType, expectType)) {
if (isExpressionStatement(firstStatement) && checkFixedAssignableTo(checker, declaration, firstStatement.expression, expectType, isFunctionType)) {
return {
declaration,
kind: FixKind.MissingReturnStatement
};
}
else if (isArrowFunction(declaration) && isLabeledStatement(firstStatement) && isExpressionStatement(firstStatement.statement)) {
const node = createObjectLiteral([createPropertyAssignment(firstStatement.label, firstStatement.statement.expression)]);
if (checkFixedAssignableTo(checker, declaration, node, expectType, isFunctionType)) {
return {
declaration,
fixKind: FixKind.MissingReturnStatement
kind: FixKind.MissingParentheses
};
}
}
else if (isLabeledStatement(firstStatement) && isExpressionStatement(firstStatement.statement)) {
const maybeFunction = updateFunctionLikeBody(declaration, createBlock([createReturn(
createObjectLiteral([createPropertyAssignment(firstStatement.label, firstStatement.statement.expression)])
)]));
const maybeType = checker.getTypeAtLocation(maybeFunction);
if (checker.isTypeAssignableTo(maybeType, expectType)) {
return {
declaration,
fixKind: FixKind.MissingParentheses
};
else if (isBlock(firstStatement) && length(firstStatement.statements) === 1) {
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)) {
return {
declaration,
kind: FixKind.MissingReturnStatement
};
}
}
}

return undefined;
}

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

const firstStatement = first(declaration.body.statements);
if (isExpressionStatement(firstStatement)) {
const actualType = checker.getTypeAtLocation(firstStatement.expression);
if (checker.isTypeAssignableTo(actualType, expectType)) {
return {
declaration,
fixKind: FixKind.MissingReturnStatement
};
}
}
else if (isLabeledStatement(firstStatement) && isExpressionStatement(firstStatement.statement)) {
const node = createObjectLiteral([createPropertyAssignment(firstStatement.label, firstStatement.statement.expression)]);
const type = checker.getTypeAtLocation(node);
if (checker.isTypeAssignableTo(type, expectType)) {
return {
declaration,
fixKind: FixKind.MissingParentheses
};
}
}

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rbuckton is this going to cause any trouble? Here, the type-checker is going to jump into synthetic nodes.

}

function getInfo(checker: TypeChecker, sourceFile: SourceFile, position: number): Info | undefined {
Expand All @@ -146,41 +135,54 @@ namespace ts.codefix {
const declaration = findAncestor(node.parent, isFunctionLikeDeclaration);
if (declaration) {
if (declaration.body && declaration.type && rangeContainsRange(declaration.type, node)) {
// check by type annotation
return getFixInfoFromTypeAnnotation(checker, declaration, checker.getTypeFromTypeNode(declaration.type));
return getFixInfo(checker, declaration, checker.getTypeFromTypeNode(declaration.type), /* isFunctionType */ false);
}
else if (isCallExpression(declaration.parent) && declaration.body) {
const pos = declaration.parent.arguments.indexOf(<Expression>declaration);
const type = checker.getContextualTypeForArgumentAtIndex(declaration.parent, pos);
if (!type) return undefined;
return getFixInfo(checker, declaration, type);
return getFixInfo(checker, declaration, type, /* isFunctionType */ true);
}
}
else if (isDeclarationName(node) && isVariableDeclaration(node.parent) && node.parent.initializer && node.parent.type) {
const initializer = skipParentheses(node.parent.initializer);
if (!isFunctionLikeDeclaration(initializer) || !initializer.body) return undefined;
return getFixInfo(checker, initializer, checker.getTypeFromTypeNode(node.parent.type));
return getFixInfo(checker, initializer, checker.getTypeFromTypeNode(node.parent.type), /* isFunctionType */ true);
}
return Debug.fail("unknow pattern");
}

function getReturnExpression (stmt: Statement) {
if (isExpressionStatement(stmt)) {
return stmt.expression;
}
Debug.fail("unknow pattern");
else if (isBlock(stmt) && length(stmt.statements) === 1) {
const block = first(stmt.statements);
if (isLabeledStatement(block) && isExpressionStatement(block.statement)) {
return block.statement.expression;
}
}

return Debug.fail("unknow statement");
}

function addReturnStatement(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: FunctionLikeDeclaration) {
const body = cast(declaration.body, isBlock);
const firstStatement = cast(first(body.statements), isExpressionStatement);
changes.replaceNode(sourceFile, firstStatement, createReturn(firstStatement.expression));
const firstStatement = first(body.statements);
changes.replaceNode(sourceFile, firstStatement, createReturn(getReturnExpression(firstStatement)));
}

function removeBlockBodyBrace(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: FunctionLikeDeclaration, withParen: boolean) {
const body = cast(declaration.body, isBlock);
const expression = cast(first(body.statements), isExpressionStatement).expression;
const expression = getReturnExpression(first(body.statements));
changes.replaceNode(sourceFile, body, withParen ? createParen(expression) : expression);
}

function wrapBlockWithParen(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: FunctionLikeDeclaration) {
function wrapBlockWithParen(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: ArrowFunction) {
const body = cast(declaration.body, isBlock);
const labeledStatement = cast(first(body.statements), isLabeledStatement);
const expression = cast(labeledStatement.statement, isExpressionStatement).expression;
changes.replaceNode(sourceFile, body, createParen(createObjectLiteral([createPropertyAssignment(labeledStatement.label, expression)])));
changes.replaceNode(sourceFile, declaration.body, createParen(createObjectLiteral([createPropertyAssignment(labeledStatement.label, expression)])));
}

function getActionForfixAddReturnStatement(context: CodeFixContext, declaration: FunctionLikeDeclaration) {
Expand All @@ -198,7 +200,7 @@ namespace ts.codefix {
return createCodeFixAction(fixId, changes, Diagnostics.Replace_braces_with_parentheses, fixIdReplaceBraceWithParen, Diagnostics.Surmise_all_return_value);
}

function getActionForfixWrapTheBlockWithParen(context: CodeFixContext, declaration: FunctionLikeDeclaration) {
function getActionForfixWrapTheBlockWithParen(context: CodeFixContext, declaration: ArrowFunction) {
const changes = textChanges.ChangeTracker.with(context, t => wrapBlockWithParen(t, context.sourceFile, declaration));
return createCodeFixAction(fixId, changes, Diagnostics.Wrap_this_block_with_parentheses, fixIdWrapTheBlockWithParen, Diagnostics.Surmise_all_return_value);
}
Expand Down
15 changes: 15 additions & 0 deletions tests/cases/fourslash/codeFixSurmiseReturnValue11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />
//// interface A {
//// bar: string
//// }
////
//// function Foo (): A {
//// { bar: '123' }
//// }

verify.codeFixAvailable([
{ description: 'Add a return statement' },
Comment thread
amcasey marked this conversation as resolved.
{ description: 'Remove block body braces' },
{ description: 'Replace braces with parentheses' },
{ description: 'Remove unused label' },
]);
16 changes: 16 additions & 0 deletions tests/cases/fourslash/codeFixSurmiseReturnValue12.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
//// interface A {
//// bar: string
//// }
////
//// function Foo (a: () => A) { a() }
//// Foo(() => {
//// { bar: '123' }
//// })

verify.codeFixAvailable([
{ description: 'Add a return statement' },
{ description: 'Remove block body braces' },
{ description: 'Replace braces with parentheses' },
{ description: 'Remove unused label' },
]);
15 changes: 15 additions & 0 deletions tests/cases/fourslash/codeFixSurmiseReturnValue13.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />
//// interface A {
//// bar: string
//// }
////
//// const a: () => A = () => {
//// { bar: '1' }
//// }

verify.codeFixAvailable([
{ description: 'Add a return statement' },
{ description: 'Remove block body braces' },
{ description: 'Replace braces with parentheses' },
{ description: 'Remove unused label' },
]);
13 changes: 13 additions & 0 deletions tests/cases/fourslash/codeFixSurmiseReturnValue14.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// interface A {
//// bar: string
//// }
////
//// const a: () => A = () => {
//// bar: '1'
//// }

verify.codeFixAvailable([
{ description: 'Wrap this block with parentheses' },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're offering this fix because we believe the user intended to write an object literal. If that's the case, perhaps we should say "object literal", rather than "block".

{ description: 'Remove unused label' },
]);
12 changes: 12 additions & 0 deletions tests/cases/fourslash/codeFixSurmiseReturnValue15.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference path='fourslash.ts' />
//// interface A {
//// bar: string
//// }
////
//// function Foo (a: () => A) { a() }
//// Foo(() => { bar: '1' })

verify.codeFixAvailable([
{ description: 'Wrap this block with parentheses' },
{ description: 'Remove unused label' },
]);