diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4ddec362457b4..da214d08e9808 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -574,9 +574,7 @@ namespace ts { }, getApparentType, getUnionType, - isTypeAssignableTo: (source, target) => { - return isTypeAssignableTo(source, target); - }, + isTypeAssignableTo, createAnonymousType, createSignature, createSymbol, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d1e13c777529d..f53ef4cb1d35c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 diff --git a/src/compiler/factoryPublic.ts b/src/compiler/factoryPublic.ts index 49f7f6255c7f0..f0aae0daf938b 100644 --- a/src/compiler/factoryPublic.ts +++ b/src/compiler/factoryPublic.ts @@ -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, diff --git a/src/services/codefixes/returnValueCorrect.ts b/src/services/codefixes/returnValueCorrect.ts new file mode 100644 index 0000000000000..947b9933e302e --- /dev/null +++ b/src/services/codefixes/returnValueCorrect.ts @@ -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(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); + } +} diff --git a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts index 89a03c3c23989..7f423f4df0555 100644 --- a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts +++ b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts @@ -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); diff --git a/src/services/refactors/convertParamsToDestructuredObject.ts b/src/services/refactors/convertParamsToDestructuredObject.ts index 803a767761090..dcb0d520f9f87 100644 --- a/src/services/refactors/convertParamsToDestructuredObject.ts +++ b/src/services/refactors/convertParamsToDestructuredObject.ts @@ -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); } diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 9d1b8b44052fd..117461bc1900c 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -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", diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 677e79235d7e5..3d958a5bad6a9 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2269,6 +2269,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); @@ -2363,6 +2384,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) { diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction10.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction10.ts index 42e0bc5be780f..3518b017050ee 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction10.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction10.ts @@ -5,9 +5,10 @@ ////} verify.codeFix({ + index: 2, description: "Add async modifier to containing function", newFileContent: `const f: () => Promise = async () => { await Promise.resolve('foo'); -}`, +}` }); diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction11.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction11.ts index bc7b17f8db5b4..1cfce8eda2e39 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction11.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction11.ts @@ -6,9 +6,10 @@ // should not change type if it's incorrectly set verify.codeFix({ + index: 2, description: "Add async modifier to containing function", newFileContent: `const f: string = async () => { await Promise.resolve('foo'); -}`, +}` }); diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction12.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction12.ts index ee694a80e9f26..4e780d4b57dbc 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction12.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction12.ts @@ -5,6 +5,7 @@ ////} verify.codeFix({ + index: 1, description: "Add async modifier to containing function", newFileContent: `const f: () => Promise> = async function() { diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction13.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction13.ts index 06f54d29eeb68..57fb74a4fe091 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction13.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction13.ts @@ -5,6 +5,7 @@ ////} verify.codeFix({ + index: 2, description: "Add async modifier to containing function", newFileContent: `const f: () => Promise = async () => { diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction14.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction14.ts index c798af5f5abd6..7b9ca8c7239a7 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction14.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction14.ts @@ -5,6 +5,7 @@ ////} verify.codeFix({ + index: 1, description: "Add async modifier to containing function", newFileContent: `const f = async function(): Promise { diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction15.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction15.ts index a2c6f7dcb1ad9..47f474ecb0c61 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction15.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction15.ts @@ -5,6 +5,7 @@ ////} verify.codeFix({ + index: 2, description: "Add async modifier to containing function", newFileContent: `const f = async (): Promise => { diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction8.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction8.ts index 7c43add3edd21..726927b77b793 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction8.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction8.ts @@ -5,6 +5,7 @@ ////} verify.codeFix({ + index: 1, description: "Add async modifier to containing function", newFileContent: `async function f(): Promise { diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction9.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction9.ts index f93603e69c457..652a669832e32 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction9.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction9.ts @@ -7,6 +7,7 @@ ////} verify.codeFix({ + index: 1, description: "Add async modifier to containing function", newFileContent: `class Foo { diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue1.ts b/tests/cases/fourslash/codeFixCorrectReturnValue1.ts new file mode 100644 index 0000000000000..4dc5bdfc68386 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue1.ts @@ -0,0 +1,9 @@ +/// + +//// function Foo (): number { +//// 1 +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue10.ts b/tests/cases/fourslash/codeFixCorrectReturnValue10.ts new file mode 100644 index 0000000000000..cdbf416984d58 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue10.ts @@ -0,0 +1,8 @@ +/// + +//// const a: ((() => number) | (() => undefined)) = () => { 1 } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' } +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue11.ts b/tests/cases/fourslash/codeFixCorrectReturnValue11.ts new file mode 100644 index 0000000000000..a57c05134959c --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue11.ts @@ -0,0 +1,13 @@ +/// +//// interface A { +//// bar: string +//// } +//// +//// function Foo (): A { +//// { bar: '123' } +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove unused label' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue12.ts b/tests/cases/fourslash/codeFixCorrectReturnValue12.ts new file mode 100644 index 0000000000000..d242e4f4cf87e --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue12.ts @@ -0,0 +1,15 @@ +/// +//// 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: 'Remove unused label' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue13.ts b/tests/cases/fourslash/codeFixCorrectReturnValue13.ts new file mode 100644 index 0000000000000..a1e2cc553ccac --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue13.ts @@ -0,0 +1,18 @@ +/// +//// interface A { +//// bar: string +//// } +//// +//// const a: () => A = () => { +//// { bar: '1' } +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' }, + { description: 'Remove unused label' }, +]); + +interface A { + bar: string +} diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue14.ts b/tests/cases/fourslash/codeFixCorrectReturnValue14.ts new file mode 100644 index 0000000000000..0f602eb1a139d --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue14.ts @@ -0,0 +1,13 @@ +/// +//// interface A { +//// bar: string +//// } +//// +//// const a: () => A = () => { +//// bar: '1' +//// } + +verify.codeFixAvailable([ + { description: 'Wrap the following body with parentheses which should be an object literal' }, + { description: 'Remove unused label' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue15.ts b/tests/cases/fourslash/codeFixCorrectReturnValue15.ts new file mode 100644 index 0000000000000..6b6280cec4a0d --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue15.ts @@ -0,0 +1,12 @@ +/// +//// interface A { +//// bar: string +//// } +//// +//// function Foo (a: () => A) { a() } +//// Foo(() => { bar: '1' }) + +verify.codeFixAvailable([ + { description: 'Wrap the following body with parentheses which should be an object literal' }, + { description: 'Remove unused label' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue16.ts b/tests/cases/fourslash/codeFixCorrectReturnValue16.ts new file mode 100644 index 0000000000000..bacb9a373b2e0 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue16.ts @@ -0,0 +1,13 @@ +/// + +//// interface A { +//// bar: string +//// } +//// +//// function Foo (a: () => A) { a() } +//// Foo(() => { bar: '1' }) + +verify.codeFixAvailable([ + { description: 'Wrap the following body with parentheses which should be an object literal' }, + { description: 'Remove unused label' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue17.ts b/tests/cases/fourslash/codeFixCorrectReturnValue17.ts new file mode 100644 index 0000000000000..2572b478e1756 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue17.ts @@ -0,0 +1,14 @@ +/// + +//// interface A { +//// bar: string +//// } +//// +//// function Foo (): A { +//// bar: '123' +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove unused label' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue18.ts b/tests/cases/fourslash/codeFixCorrectReturnValue18.ts new file mode 100644 index 0000000000000..ffa038a48fc5c --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue18.ts @@ -0,0 +1,17 @@ +/// + +//@Filename: file.tsx +//// declare module JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// } +//// interface ElementAttributesProperty { props; } +//// } +//// class Comp { props: { t: () => number } } +//// var x = { 1 }} />; + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' }, + { description: `Infer type of 'props' from usage` } +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue19.ts b/tests/cases/fourslash/codeFixCorrectReturnValue19.ts new file mode 100644 index 0000000000000..a8726a5ed6f17 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue19.ts @@ -0,0 +1,20 @@ +/// + +//@Filename: file.tsx +//// declare module JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// } +//// interface ElementAttributesProperty { props; } +//// } +//// interface A { +//// bar: string +//// } +//// class Comp { props: { t: () => number } } +//// var x = { bar: '1' }} />; + +verify.codeFixAvailable([ + { description: 'Wrap the following body with parentheses which should be an object literal' }, + { description: `Infer type of 'props' from usage` }, + { description: 'Remove unused label' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue2.ts b/tests/cases/fourslash/codeFixCorrectReturnValue2.ts new file mode 100644 index 0000000000000..9d361067c4ca5 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue2.ts @@ -0,0 +1,13 @@ +/// + +//// interface A { +//// foo: number +//// } + +//// function Foo (): A { +//// ({ foo: 1 }) +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue20.ts b/tests/cases/fourslash/codeFixCorrectReturnValue20.ts new file mode 100644 index 0000000000000..f552edaee6713 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue20.ts @@ -0,0 +1,23 @@ +/// + +//@Filename: file.tsx +//// declare module JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// } +//// interface ElementAttributesProperty { props; } +//// } +//// interface A { +//// bar: string +//// } +//// class Comp { props: { t: () => number } } +//// var x = { +//// { bar: '1' } +//// }} />; + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' }, + { description: `Infer type of 'props' from usage` }, + { description: 'Remove unused label' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue21.ts b/tests/cases/fourslash/codeFixCorrectReturnValue21.ts new file mode 100644 index 0000000000000..1a248f77909f2 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue21.ts @@ -0,0 +1,14 @@ +/// + +//// interface A { +//// a: () => number +//// } +//// +//// let b: A = { +//// a: () => { 1 } +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue22.ts b/tests/cases/fourslash/codeFixCorrectReturnValue22.ts new file mode 100644 index 0000000000000..372513592afb8 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue22.ts @@ -0,0 +1,16 @@ +/// + +//// interface A { +//// a: () => number +//// } +//// +//// function Foo (): A { +//// return { +//// a: () => { 1 } +//// } +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue23.ts b/tests/cases/fourslash/codeFixCorrectReturnValue23.ts new file mode 100644 index 0000000000000..14fde02533799 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue23.ts @@ -0,0 +1,10 @@ +/// + +//// class Foo { +//// bar: () => number = () => { 1 } +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' } +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue24.ts b/tests/cases/fourslash/codeFixCorrectReturnValue24.ts new file mode 100644 index 0000000000000..6cc9e52e4d0d9 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue24.ts @@ -0,0 +1,12 @@ +/// + +//// function Foo (a: () => number) { a() } +//// Foo(() => { /* leading */ 1 /* trailing */ }) + +verify.codeFix({ + description: "Add a return statement", + index: 0, + newFileContent: +`function Foo (a: () => number) { a() } +Foo(() => { /* leading */ return 1 /* trailing */ })` +}) \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue25.ts b/tests/cases/fourslash/codeFixCorrectReturnValue25.ts new file mode 100644 index 0000000000000..3d7f750367305 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue25.ts @@ -0,0 +1,12 @@ +/// + +//// function Foo (a: () => number) { a() } +//// Foo(() => { /* leading */ 1 /* trailing */ }) + +verify.codeFix({ + description: "Remove block body braces", + index: 1, + newFileContent: +`function Foo (a: () => number) { a() } +Foo(() => /* leading */ 1 /* trailing */)` +}) \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue26.ts b/tests/cases/fourslash/codeFixCorrectReturnValue26.ts new file mode 100644 index 0000000000000..36f9ff9fa3b13 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue26.ts @@ -0,0 +1,23 @@ +/// +//// interface A { +//// bar: string; +//// } +//// +//// function Foo (a: () => A) { a(); } +//// Foo(() => { +//// { bar: '123'; } +//// }) + +verify.codeFix({ + description: "Add a return statement", + index: 0, + newFileContent: +`interface A { + bar: string; +} + +function Foo (a: () => A) { a(); } +Foo(() => { + return { bar: '123' }; +})` +}) diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue3.ts b/tests/cases/fourslash/codeFixCorrectReturnValue3.ts new file mode 100644 index 0000000000000..364da21f42dfc --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue3.ts @@ -0,0 +1,13 @@ +/// + +//// interface A { +//// foo: number +//// } + +//// function Foo (): A | number { +//// 1 +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue4.ts b/tests/cases/fourslash/codeFixCorrectReturnValue4.ts new file mode 100644 index 0000000000000..e1270bee3f361 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue4.ts @@ -0,0 +1,7 @@ +/// + +//// function Foo (): any { +//// 1 +//// } + +verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue5.ts b/tests/cases/fourslash/codeFixCorrectReturnValue5.ts new file mode 100644 index 0000000000000..de457636368eb --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue5.ts @@ -0,0 +1,7 @@ +/// + +//// function Foo (): void { +//// undefined +//// } + +verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue6.ts b/tests/cases/fourslash/codeFixCorrectReturnValue6.ts new file mode 100644 index 0000000000000..693145f8c61b8 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue6.ts @@ -0,0 +1,9 @@ +/// + +//// function Foo (): undefined { +//// undefined +//// } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue7.ts b/tests/cases/fourslash/codeFixCorrectReturnValue7.ts new file mode 100644 index 0000000000000..bd0bfd3b343b3 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue7.ts @@ -0,0 +1,9 @@ +/// + +//// function Foo (a: () => number) { a() } +//// Foo(() => { 1 }) + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' } +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue8.ts b/tests/cases/fourslash/codeFixCorrectReturnValue8.ts new file mode 100644 index 0000000000000..cde3ab2e42ff6 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue8.ts @@ -0,0 +1,9 @@ +/// + +//// function Foo (a: (() => number) | (() => undefined) ) { a() } +//// Foo(() => { 1 }) + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' } +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue9.ts b/tests/cases/fourslash/codeFixCorrectReturnValue9.ts new file mode 100644 index 0000000000000..104fd785f00a1 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue9.ts @@ -0,0 +1,8 @@ +/// + +//// const a: () => number = () => { 1 } + +verify.codeFixAvailable([ + { description: 'Add a return statement' }, + { description: 'Remove block body braces' } +]); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue_all1.ts b/tests/cases/fourslash/codeFixCorrectReturnValue_all1.ts new file mode 100644 index 0000000000000..e14f31e5e8b9c --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue_all1.ts @@ -0,0 +1,121 @@ +/// + +//// interface A { +//// bar: string +//// } +//// +//// function foo1 (_a: () => number ) { } +//// foo1(() => { +//// 1 +//// }) +//// function foo2 (_a: () => A) { } +//// foo2(() => { +//// { bar: '1' } +//// }) +//// foo2(() => { +//// bar: '1' +//// }) +//// function foo3 (_a: () => A | number) { } +//// foo3(() => { +//// 1 +//// }) +//// foo3(() => { +//// bar: '1' +//// }) +//// +//// function bar1 (): number { +//// 1 +//// } +//// function bar2 (): A { +//// { bar: '1' } +//// } +//// function bar3 (): A { +//// bar: '1' +//// } +//// function bar4 (): A | number { +//// 1 +//// } +//// function bar5(): A | number { +//// bar: '1' +//// } +// +//// const baz1: () => number = () => { +//// 1 +//// } +//// const baz2: () => A = () => { +//// { bar: '1' } +//// } +//// const baz3: () => A = () => { +//// bar: '1' +//// } +//// const baz4: ((() => number) | (() => A)) = () => { +//// 1 +//// } +//// const baz5: ((() => number) | (() => A)) = () => { +//// bar: '1' +//// } +//// const baz6: () => number = () => { 1 } +//// +//// const test: { a: () => A } = { a: () => { bar: '1' } } + +verify.codeFixAll({ + fixId: "fixAddReturnStatement", + fixAllDescription: "Add all missing return statement", + newFileContent: +`interface A { + bar: string +} + +function foo1 (_a: () => number ) { } +foo1(() => { + return 1 +}) +function foo2 (_a: () => A) { } +foo2(() => { + return { bar: '1' } +}) +foo2(() => { + return { bar: '1' } +}) +function foo3 (_a: () => A | number) { } +foo3(() => { + return 1 +}) +foo3(() => { + return { bar: '1' } +}) + +function bar1 (): number { + return 1 +} +function bar2 (): A { + return { bar: '1' } +} +function bar3 (): A { + return { bar: '1' } +} +function bar4 (): A | number { + return 1 +} +function bar5(): A | number { + return { bar: '1' } +} +const baz1: () => number = () => { + return 1 +} +const baz2: () => A = () => { + return { bar: '1' } +} +const baz3: () => A = () => { + return { bar: '1' } +} +const baz4: ((() => number) | (() => A)) = () => { + return 1 +} +const baz5: ((() => number) | (() => A)) = () => { + return { bar: '1' } +} +const baz6: () => number = () => { return 1 } + +const test: { a: () => A } = { a: () => { return { bar: '1' } } }`, +}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue_all2.ts b/tests/cases/fourslash/codeFixCorrectReturnValue_all2.ts new file mode 100644 index 0000000000000..e3b0d1c0b1d39 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue_all2.ts @@ -0,0 +1,99 @@ +/// + +//// interface A { +//// bar: string +//// } +//// +//// function foo1 (_a: () => number ) { } +//// foo1(() => { +//// 1 +//// }) +//// function foo2 (_a: () => A) { } +//// foo2(() => { +//// { bar: '1' } +//// }) +//// foo2(() => { +//// bar: '1' +//// }) +//// function foo3 (_a: () => A | number) { } +//// foo3(() => { +//// 1 +//// }) +//// foo3(() => { +//// bar: '1' +//// }) +//// +//// function bar1 (): number { +//// 1 +//// } +//// function bar2 (): A { +//// { bar: '1' } +//// } +//// function bar3 (): A { +//// bar: '1' +//// } +//// function bar4 (): A | number { +//// 1 +//// } +//// function bar5(): A | number { +//// bar: '1' +//// } +// +//// const baz1: () => number = () => { +//// 1 +//// } +//// const baz2: () => A = () => { +//// { bar: '1' } +//// } +//// const baz3: () => A = () => { +//// bar: '1' +//// } +//// const baz4: ((() => number) | (() => A)) = () => { +//// 1 +//// } +//// const baz5: ((() => number) | (() => A)) = () => { +//// bar: '1' +//// } +//// +//// const test: { a: () => A } = { a: () => { bar: '1' } } + +verify.codeFixAll({ + fixId: "fixRemoveBlockBodyBrace", + fixAllDescription: "Remove all incorrect body block braces", + newFileContent: +`interface A { + bar: string +} + +function foo1 (_a: () => number ) { } +foo1(() => 1) +function foo2 (_a: () => A) { } +foo2(() => ({ bar: '1' })) +foo2(() => ({ bar: '1' })) +function foo3 (_a: () => A | number) { } +foo3(() => 1) +foo3(() => ({ bar: '1' })) + +function bar1 (): number { + 1 +} +function bar2 (): A { + { bar: '1' } +} +function bar3 (): A { + bar: '1' +} +function bar4 (): A | number { + 1 +} +function bar5(): A | number { + bar: '1' +} +const baz1: () => number = () => 1 +const baz2: () => A = () => ({ bar: '1' }) +const baz3: () => A = () => ({ bar: '1' }) +const baz4: ((() => number) | (() => A)) = () => 1 +const baz5: ((() => number) | (() => A)) = () => ({ bar: '1' }) + +const test: { a: () => A } = { a: () => ({ bar: '1' }) }`, +}); diff --git a/tests/cases/fourslash/codeFixCorrectReturnValue_all3.ts b/tests/cases/fourslash/codeFixCorrectReturnValue_all3.ts new file mode 100644 index 0000000000000..0d4806b19c18c --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectReturnValue_all3.ts @@ -0,0 +1,99 @@ +/// + +//// interface A { +//// bar: string +//// } +//// +//// function foo1 (_a: () => number ) { } +//// foo1(() => { +//// 1 +//// }) +//// function foo2 (_a: () => A) { } +//// foo2(() => { +//// { bar: '1' } +//// }) +//// foo2(() => { +//// bar: '1' +//// }) +//// function foo3 (_a: () => A | number) { } +//// foo3(() => { +//// 1 +//// }) +//// foo3(() => { +//// bar: '1' +//// }) +//// +//// function bar1 (): number { +//// 1 +//// } +//// function bar2 (): A { +//// { bar: '1' } +//// } +//// function bar3 (): A { +//// bar: '1' +//// } +//// function bar4 (): A | number { +//// 1 +//// } +//// function bar5(): A | number { +//// bar: '1' +//// } +// +//// const baz1: () => number = () => { +//// 1 +//// } +//// const baz2: () => A = () => { +//// { bar: '1' } +//// } +//// const baz3: () => A = () => { +//// bar: '1' +//// } +//// const baz4: ((() => number) | (() => A)) = () => { +//// 1 +//// } +//// const baz5: ((() => number) | (() => A)) = () => { +//// bar: '1' +//// } +//// +//// const test: { a: () => A } = { a: () => { bar: '1' } } + +verify.codeFixAll({ + fixId: "fixWrapTheBlockWithParen", + fixAllDescription: "Wrap all object literal with parentheses", + newFileContent: +`interface A { + bar: string +} + +function foo1 (_a: () => number ) { } +foo1(() => (1)) +function foo2 (_a: () => A) { } +foo2(() => ({ bar: '1' })) +foo2(() => ({ bar: '1' })) +function foo3 (_a: () => A | number) { } +foo3(() => (1)) +foo3(() => ({ bar: '1' })) + +function bar1 (): number { + 1 +} +function bar2 (): A { + { bar: '1' } +} +function bar3 (): A { + bar: '1' +} +function bar4 (): A | number { + 1 +} +function bar5(): A | number { + bar: '1' +} +const baz1: () => number = () => (1) +const baz2: () => A = () => ({ bar: '1' }) +const baz3: () => A = () => ({ bar: '1' }) +const baz4: ((() => number) | (() => A)) = () => (1) +const baz5: ((() => number) | (() => A)) = () => ({ bar: '1' }) + +const test: { a: () => A } = { a: () => ({ bar: '1' }) }`, +});