1/* @internal */ 2namespace ts.refactor.addOrRemoveBracesToArrowFunction { 3 const refactorName = "Add or remove braces in an arrow function"; 4 const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message; 5 6 const addBracesAction = { 7 name: "Add braces to arrow function", 8 description: Diagnostics.Add_braces_to_arrow_function.message, 9 kind: "refactor.rewrite.arrow.braces.add", 10 }; 11 const removeBracesAction = { 12 name: "Remove braces from arrow function", 13 description: Diagnostics.Remove_braces_from_arrow_function.message, 14 kind: "refactor.rewrite.arrow.braces.remove" 15 }; 16 registerRefactor(refactorName, { 17 kinds: [removeBracesAction.kind], 18 getEditsForAction: getRefactorEditsToRemoveFunctionBraces, 19 getAvailableActions: getRefactorActionsToRemoveFunctionBraces 20 }); 21 22 interface FunctionBracesInfo { 23 func: ArrowFunction; 24 expression: Expression | undefined; 25 returnStatement?: ReturnStatement; 26 addBraces: boolean; 27 } 28 29 function getRefactorActionsToRemoveFunctionBraces(context: RefactorContext): readonly ApplicableRefactorInfo[] { 30 const { file, startPosition, triggerReason } = context; 31 const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason === "invoked"); 32 if (!info) return emptyArray; 33 34 if (!isRefactorErrorInfo(info)) { 35 return [{ 36 name: refactorName, 37 description: refactorDescription, 38 actions: [ 39 info.addBraces ? addBracesAction : removeBracesAction 40 ] 41 }]; 42 } 43 44 if (context.preferences.provideRefactorNotApplicableReason) { 45 return [{ 46 name: refactorName, 47 description: refactorDescription, 48 actions: [ 49 { ...addBracesAction, notApplicableReason: info.error }, 50 { ...removeBracesAction, notApplicableReason: info.error }, 51 ] 52 }]; 53 } 54 55 return emptyArray; 56 } 57 58 function getRefactorEditsToRemoveFunctionBraces(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { 59 const { file, startPosition } = context; 60 const info = getConvertibleArrowFunctionAtPosition(file, startPosition); 61 Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); 62 63 const { expression, returnStatement, func } = info; 64 65 let body: ConciseBody; 66 67 if (actionName === addBracesAction.name) { 68 const returnStatement = factory.createReturnStatement(expression); 69 body = factory.createBlock([returnStatement], /* multiLine */ true); 70 copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true); 71 } 72 else if (actionName === removeBracesAction.name && returnStatement) { 73 const actualExpression = expression || factory.createVoidZero(); 74 body = needsParentheses(actualExpression) ? factory.createParenthesizedExpression(actualExpression) : actualExpression; 75 copyTrailingAsLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); 76 copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); 77 copyTrailingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); 78 } 79 else { 80 Debug.fail("invalid action"); 81 } 82 83 const edits = textChanges.ChangeTracker.with(context, t => { 84 t.replaceNode(file, func.body, body); 85 }); 86 87 return { renameFilename: undefined, renameLocation: undefined, edits }; 88 } 89 90 function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true, kind?: string): FunctionBracesInfo | RefactorErrorInfo | undefined { 91 const node = getTokenAtPosition(file, startPosition); 92 const func = getContainingFunction(node); 93 94 if (!func) { 95 return { 96 error: getLocaleSpecificMessage(Diagnostics.Could_not_find_a_containing_arrow_function) 97 }; 98 } 99 100 if (!isArrowFunction(func)) { 101 return { 102 error: getLocaleSpecificMessage(Diagnostics.Containing_function_is_not_an_arrow_function) 103 }; 104 } 105 106 if ((!rangeContainsRange(func, node) || rangeContainsRange(func.body, node) && !considerFunctionBodies)) { 107 return undefined; 108 } 109 110 if (refactorKindBeginsWith(addBracesAction.kind, kind) && isExpression(func.body)) { 111 return { func, addBraces: true, expression: func.body }; 112 } 113 else if (refactorKindBeginsWith(removeBracesAction.kind, kind) && isBlock(func.body) && func.body.statements.length === 1) { 114 const firstStatement = first(func.body.statements); 115 if (isReturnStatement(firstStatement)) { 116 return { func, addBraces: false, expression: firstStatement.expression, returnStatement: firstStatement }; 117 } 118 } 119 return undefined; 120 } 121} 122