mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-16 15:45:27 -05:00
Quick fix for functions lacking return expressions (#26434)
* stash * add surmise for return type * add support for more case * add more test case * add more testcase and fix all test * fix changed diagnosis * fix broken test case * add more case * rename quickfix * fix conflict * fix fix desc * fix semi * Avoid replace brace with paren * Split fix all action * Add return work in same line * fix test cases * rename baseline * refactor and handle comment * Support semi * make helper internal
This commit is contained in:
@@ -574,9 +574,7 @@ namespace ts {
|
||||
},
|
||||
getApparentType,
|
||||
getUnionType,
|
||||
isTypeAssignableTo: (source, target) => {
|
||||
return isTypeAssignableTo(source, target);
|
||||
},
|
||||
isTypeAssignableTo,
|
||||
createAnonymousType,
|
||||
createSignature,
|
||||
createSymbol,
|
||||
|
||||
@@ -5621,6 +5621,31 @@
|
||||
"category": "Message",
|
||||
"code": 95110
|
||||
},
|
||||
"Add a return statement": {
|
||||
"category": "Message",
|
||||
"code": 95111
|
||||
},
|
||||
"Remove block body braces": {
|
||||
"category": "Message",
|
||||
"code": 95112
|
||||
},
|
||||
"Wrap the following body with parentheses which should be an object literal": {
|
||||
"category": "Message",
|
||||
"code": 95113
|
||||
},
|
||||
"Add all missing return statement": {
|
||||
"category": "Message",
|
||||
"code": 95114
|
||||
},
|
||||
"Remove all incorrect body block braces": {
|
||||
"category": "Message",
|
||||
"code": 95115
|
||||
},
|
||||
"Wrap all object literal with parentheses": {
|
||||
"category": "Message",
|
||||
"code": 95116
|
||||
},
|
||||
|
||||
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
|
||||
"category": "Error",
|
||||
"code": 18004
|
||||
|
||||
@@ -2057,6 +2057,26 @@ namespace ts {
|
||||
: node;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function updateFunctionLikeBody(declaration: FunctionLikeDeclaration, body: Block): FunctionLikeDeclaration {
|
||||
switch (declaration.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
return createFunctionDeclaration(declaration.decorators, declaration.modifiers, declaration.asteriskToken, declaration.name, declaration.typeParameters, declaration.parameters, declaration.type, body);
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
return createMethod(declaration.decorators, declaration.modifiers, declaration.asteriskToken, declaration.name, declaration.questionToken, declaration.typeParameters, declaration.parameters, declaration.type, body);
|
||||
case SyntaxKind.GetAccessor:
|
||||
return createGetAccessor(declaration.decorators, declaration.modifiers, declaration.name, declaration.parameters, declaration.type, body);
|
||||
case SyntaxKind.SetAccessor:
|
||||
return createSetAccessor(declaration.decorators, declaration.modifiers, declaration.name, declaration.parameters, body);
|
||||
case SyntaxKind.Constructor:
|
||||
return createConstructor(declaration.decorators, declaration.modifiers, declaration.parameters, body);
|
||||
case SyntaxKind.FunctionExpression:
|
||||
return createFunctionExpression(declaration.modifiers, declaration.asteriskToken, declaration.name, declaration.typeParameters, declaration.parameters, declaration.type, body);
|
||||
case SyntaxKind.ArrowFunction:
|
||||
return createArrowFunction(declaration.modifiers, declaration.typeParameters, declaration.parameters, declaration.type, declaration.equalsGreaterThanToken, body);
|
||||
}
|
||||
}
|
||||
|
||||
export function createClassDeclaration(
|
||||
decorators: readonly Decorator[] | undefined,
|
||||
modifiers: readonly Modifier[] | undefined,
|
||||
|
||||
208
src/services/codefixes/returnValueCorrect.ts
Normal file
208
src/services/codefixes/returnValueCorrect.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "returnValueCorrect";
|
||||
const fixIdAddReturnStatement = "fixAddReturnStatement";
|
||||
const fixIdRemoveBlockBodyBrace = "fixRemoveBlockBodyBrace";
|
||||
const fixIdWrapTheBlockWithParen = "fixWrapTheBlockWithParen";
|
||||
const errorCodes = [
|
||||
Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value.code,
|
||||
Diagnostics.Type_0_is_not_assignable_to_type_1.code,
|
||||
Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code
|
||||
];
|
||||
|
||||
enum ProblemKind {
|
||||
MissingReturnStatement,
|
||||
MissingParentheses
|
||||
}
|
||||
|
||||
interface MissingReturnInfo {
|
||||
kind: ProblemKind.MissingReturnStatement;
|
||||
declaration: FunctionLikeDeclaration;
|
||||
expression: Expression;
|
||||
statement: Statement;
|
||||
commentSource: Node;
|
||||
}
|
||||
|
||||
interface MissingParenInfo {
|
||||
kind: ProblemKind.MissingParentheses;
|
||||
declaration: ArrowFunction;
|
||||
expression: Expression;
|
||||
statement: Statement;
|
||||
commentSource: Node;
|
||||
}
|
||||
|
||||
type Info = MissingReturnInfo | MissingParenInfo;
|
||||
|
||||
registerCodeFix({
|
||||
errorCodes,
|
||||
fixIds: [fixIdAddReturnStatement, fixIdRemoveBlockBodyBrace, fixIdWrapTheBlockWithParen],
|
||||
getCodeActions: context => {
|
||||
const { program, sourceFile, span: { start }, errorCode } = context;
|
||||
const info = getInfo(program.getTypeChecker(), sourceFile, start, errorCode);
|
||||
if (!info) return undefined;
|
||||
|
||||
if (info.kind === ProblemKind.MissingReturnStatement) {
|
||||
return append(
|
||||
[getActionForfixAddReturnStatement(context, info.expression, info.statement)],
|
||||
isArrowFunction(info.declaration) ? getActionForfixRemoveBlockBodyBrace(context, info.declaration, info.expression, info.commentSource): undefined);
|
||||
}
|
||||
else {
|
||||
return [getActionForfixWrapTheBlockWithParen(context, info.declaration, info.expression)];
|
||||
}
|
||||
},
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const info = getInfo(context.program.getTypeChecker(), diag.file, diag.start, diag.code);
|
||||
if (!info) return undefined;
|
||||
|
||||
switch (context.fixId) {
|
||||
case fixIdAddReturnStatement:
|
||||
addReturnStatement(changes, diag.file, info.expression, info.statement);
|
||||
break;
|
||||
case fixIdRemoveBlockBodyBrace:
|
||||
if (!isArrowFunction(info.declaration)) return undefined;
|
||||
removeBlockBodyBrace(changes, diag.file, info.declaration, info.expression, info.commentSource, /* withParen */ false);
|
||||
break;
|
||||
case fixIdWrapTheBlockWithParen:
|
||||
if (!isArrowFunction(info.declaration)) return undefined;
|
||||
wrapBlockWithParen(changes, diag.file, info.declaration, info.expression);
|
||||
break;
|
||||
default:
|
||||
Debug.fail(JSON.stringify(context.fixId));
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
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)) {
|
||||
return {
|
||||
declaration,
|
||||
kind: ProblemKind.MissingReturnStatement,
|
||||
expression: firstStatement.expression,
|
||||
statement: firstStatement,
|
||||
commentSource: firstStatement.expression
|
||||
};
|
||||
}
|
||||
else if (isLabeledStatement(firstStatement) && isExpressionStatement(firstStatement.statement)) {
|
||||
const node = createObjectLiteral([createPropertyAssignment(firstStatement.label, firstStatement.statement.expression)]);
|
||||
if (checkFixedAssignableTo(checker, declaration, node, expectType, isFunctionType)) {
|
||||
return isArrowFunction(declaration) ? {
|
||||
declaration,
|
||||
kind: ProblemKind.MissingParentheses,
|
||||
expression: node,
|
||||
statement: firstStatement,
|
||||
commentSource: firstStatement.statement.expression
|
||||
} : {
|
||||
declaration,
|
||||
kind: ProblemKind.MissingReturnStatement,
|
||||
expression: node,
|
||||
statement: firstStatement,
|
||||
commentSource: firstStatement.statement.expression
|
||||
};
|
||||
}
|
||||
}
|
||||
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: ProblemKind.MissingReturnStatement,
|
||||
expression: node,
|
||||
statement: firstStatement,
|
||||
commentSource: firstBlockStatement
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 getInfo(checker: TypeChecker, sourceFile: SourceFile, position: number, errorCode: number): Info | undefined {
|
||||
const node = getTokenAtPosition(sourceFile, position);
|
||||
if (!node.parent) return undefined;
|
||||
|
||||
const declaration = findAncestor(node.parent, isFunctionLikeDeclaration);
|
||||
switch (errorCode) {
|
||||
case Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value.code:
|
||||
if (!declaration || !declaration.body || !declaration.type || !rangeContainsRange(declaration.type, node)) return undefined;
|
||||
return getFixInfo(checker, declaration, checker.getTypeFromTypeNode(declaration.type), /* isFunctionType */ false);
|
||||
case Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code:
|
||||
if (!declaration || !isCallExpression(declaration.parent) || !declaration.body) return undefined;
|
||||
const pos = declaration.parent.arguments.indexOf(<Expression>declaration);
|
||||
const type = checker.getContextualTypeForArgumentAtIndex(declaration.parent, pos);
|
||||
if (!type) return undefined;
|
||||
return getFixInfo(checker, declaration, type, /* isFunctionType */ true);
|
||||
case Diagnostics.Type_0_is_not_assignable_to_type_1.code:
|
||||
if (!isDeclarationName(node) || !isVariableLike(node.parent) && !isJsxAttribute(node.parent)) return undefined;
|
||||
const initializer = getVariableLikeInitializer(node.parent);
|
||||
if (!initializer || !isFunctionLikeDeclaration(initializer) || !initializer.body) return undefined;
|
||||
return getFixInfo(checker, initializer, checker.getTypeAtLocation(node.parent), /* isFunctionType */ true);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getVariableLikeInitializer(declaration: VariableLikeDeclaration): Expression | undefined {
|
||||
switch (declaration.kind) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
case SyntaxKind.Parameter:
|
||||
case SyntaxKind.BindingElement:
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
return declaration.initializer;
|
||||
case SyntaxKind.JsxAttribute:
|
||||
return declaration.initializer && (isJsxExpression(declaration.initializer) ? declaration.initializer.expression : undefined);
|
||||
case SyntaxKind.ShorthandPropertyAssignment:
|
||||
case SyntaxKind.PropertySignature:
|
||||
case SyntaxKind.EnumMember:
|
||||
case SyntaxKind.JSDocPropertyTag:
|
||||
case SyntaxKind.JSDocParameterTag:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function addReturnStatement(changes: textChanges.ChangeTracker, sourceFile: SourceFile, expression: Expression, statement: Statement) {
|
||||
suppressLeadingAndTrailingTrivia(expression);
|
||||
const probablyNeedSemi = probablyUsesSemicolons(sourceFile);
|
||||
changes.replaceNode(sourceFile, statement, createReturn(expression), {
|
||||
leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude,
|
||||
trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude,
|
||||
suffix: probablyNeedSemi ? ";" : undefined
|
||||
});
|
||||
}
|
||||
|
||||
function removeBlockBodyBrace(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: ArrowFunction, expression: Expression, commentSource: Node, withParen: boolean) {
|
||||
const newBody = (withParen || needsParentheses(expression)) ? createParen(expression) : expression;
|
||||
suppressLeadingAndTrailingTrivia(commentSource);
|
||||
copyComments(commentSource, newBody);
|
||||
|
||||
changes.replaceNode(sourceFile, declaration.body, newBody);
|
||||
}
|
||||
|
||||
function wrapBlockWithParen(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: ArrowFunction, expression: Expression) {
|
||||
changes.replaceNode(sourceFile, declaration.body, createParen(expression));
|
||||
}
|
||||
|
||||
function getActionForfixAddReturnStatement(context: CodeFixContext, expression: Expression, statement: Statement) {
|
||||
const changes = textChanges.ChangeTracker.with(context, t => addReturnStatement(t, context.sourceFile, expression, statement));
|
||||
return createCodeFixAction(fixId, changes, Diagnostics.Add_a_return_statement, fixIdAddReturnStatement, Diagnostics.Add_all_missing_return_statement);
|
||||
}
|
||||
|
||||
function getActionForfixRemoveBlockBodyBrace(context: CodeFixContext, declaration: ArrowFunction, expression: Expression, commentSource: Node) {
|
||||
const changes = textChanges.ChangeTracker.with(context, t => removeBlockBodyBrace(t, context.sourceFile, declaration, expression, commentSource, /* withParen */ false));
|
||||
return createCodeFixAction(fixId, changes, Diagnostics.Remove_block_body_braces, fixIdRemoveBlockBodyBrace, Diagnostics.Remove_all_incorrect_body_block_braces);
|
||||
}
|
||||
|
||||
function getActionForfixWrapTheBlockWithParen(context: CodeFixContext, declaration: ArrowFunction, expression: Expression) {
|
||||
const changes = textChanges.ChangeTracker.with(context, t => wrapBlockWithParen(t, context.sourceFile, declaration, expression));
|
||||
return createCodeFixAction(fixId, changes, Diagnostics.Wrap_the_following_body_with_parentheses_which_should_be_an_object_literal, fixIdWrapTheBlockWithParen, Diagnostics.Wrap_all_object_literal_with_parentheses);
|
||||
}
|
||||
}
|
||||
@@ -64,10 +64,6 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
|
||||
return { renameFilename: undefined, renameLocation: undefined, edits };
|
||||
}
|
||||
|
||||
function needsParentheses(expression: Expression) {
|
||||
return isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.CommaToken || isObjectLiteralExpression(expression);
|
||||
}
|
||||
|
||||
function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number): Info | undefined {
|
||||
const node = getTokenAtPosition(file, startPosition);
|
||||
const func = getContainingFunction(node);
|
||||
|
||||
@@ -491,27 +491,6 @@ namespace ts.refactor.convertParamsToDestructuredObject {
|
||||
}
|
||||
}
|
||||
|
||||
function copyComments(sourceNode: Node, targetNode: Node) {
|
||||
const sourceFile = sourceNode.getSourceFile();
|
||||
const text = sourceFile.text;
|
||||
if (hasLeadingLineBreak(sourceNode, text)) {
|
||||
copyLeadingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
else {
|
||||
copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
copyTrailingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
|
||||
function hasLeadingLineBreak(node: Node, text: string) {
|
||||
const start = node.getFullStart();
|
||||
const end = node.getStart();
|
||||
for (let i = start; i < end; i++) {
|
||||
if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getParameterName(paramDeclaration: ValidParameterDeclaration) {
|
||||
return getTextOfIdentifierOrLiteral(paramDeclaration.name);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"codefixes/importFixes.ts",
|
||||
"codefixes/fixImplicitThis.ts",
|
||||
"codefixes/fixSpelling.ts",
|
||||
"codefixes/returnValueCorrect.ts",
|
||||
"codefixes/fixAddMissingMember.ts",
|
||||
"codefixes/fixAddMissingNewOperator.ts",
|
||||
"codefixes/fixCannotFindModule.ts",
|
||||
|
||||
@@ -2273,6 +2273,27 @@ namespace ts {
|
||||
addEmitFlagsRecursively(node, EmitFlags.NoTrailingComments, getLastChild);
|
||||
}
|
||||
|
||||
export function copyComments(sourceNode: Node, targetNode: Node) {
|
||||
const sourceFile = sourceNode.getSourceFile();
|
||||
const text = sourceFile.text;
|
||||
if (hasLeadingLineBreak(sourceNode, text)) {
|
||||
copyLeadingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
else {
|
||||
copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
copyTrailingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
|
||||
function hasLeadingLineBreak(node: Node, text: string) {
|
||||
const start = node.getFullStart();
|
||||
const end = node.getStart();
|
||||
for (let i = start; i < end; i++) {
|
||||
if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addEmitFlagsRecursively(node: Node, flag: EmitFlags, getChild: (n: Node) => Node | undefined) {
|
||||
addEmitFlags(node, flag);
|
||||
const child = getChild(node);
|
||||
@@ -2367,6 +2388,11 @@ namespace ts {
|
||||
return idx === -1 ? -1 : idx + 1;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function needsParentheses(expression: Expression) {
|
||||
return isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.CommaToken || isObjectLiteralExpression(expression);
|
||||
}
|
||||
|
||||
export function getContextualTypeFromParent(node: Expression, checker: TypeChecker): Type | undefined {
|
||||
const { parent } = node;
|
||||
switch (parent.kind) {
|
||||
|
||||
Reference in New Issue
Block a user