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, 19 getAvailableActions }); 20 21 interface FunctionBracesInfo { 22 func: ArrowFunction; 23 expression: Expression | undefined; 24 returnStatement?: ReturnStatement; 25 addBraces: boolean; 26 } 27 28 function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { 29 const { file, startPosition, triggerReason } = context; 30 const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason === "invoked"); 31 if (!info) return emptyArray; 32 33 if (!isRefactorErrorInfo(info)) { 34 return [{ 35 name: refactorName, 36 description: refactorDescription, 37 actions: [ 38 info.addBraces ? addBracesAction : removeBracesAction 39 ] 40 }]; 41 } 42 43 if (context.preferences.provideRefactorNotApplicableReason) { 44 return [{ 45 name: refactorName, 46 description: refactorDescription, 47 actions: [ 48 { ...addBracesAction, notApplicableReason: info.error }, 49 { ...removeBracesAction, notApplicableReason: info.error }, 50 ] 51 }]; 52 } 53 54 return emptyArray; 55 } 56 57 function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { 58 const { file, startPosition } = context; 59 const info = getConvertibleArrowFunctionAtPosition(file, startPosition); 60 Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); 61 62 const { expression, returnStatement, func } = info; 63 64 let body: ConciseBody; 65 66 if (actionName === addBracesAction.name) { 67 const returnStatement = factory.createReturnStatement(expression); 68 body = factory.createBlock([returnStatement], /* multiLine */ true); 69 suppressLeadingAndTrailingTrivia(body); 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 suppressLeadingAndTrailingTrivia(body); 76 copyTrailingAsLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); 77 copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); 78 copyTrailingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); 79 } 80 else { 81 Debug.fail("invalid action"); 82 } 83 84 const edits = textChanges.ChangeTracker.with(context, t => { 85 t.replaceNode(file, func.body, body); 86 }); 87 88 return { renameFilename: undefined, renameLocation: undefined, edits }; 89 } 90 91 function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true, kind?: string): FunctionBracesInfo | RefactorErrorInfo | undefined { 92 const node = getTokenAtPosition(file, startPosition); 93 const func = getContainingFunction(node); 94 95 if (!func) { 96 return { 97 error: getLocaleSpecificMessage(Diagnostics.Could_not_find_a_containing_arrow_function) 98 }; 99 } 100 101 if (!isArrowFunction(func)) { 102 return { 103 error: getLocaleSpecificMessage(Diagnostics.Containing_function_is_not_an_arrow_function) 104 }; 105 } 106 107 if ((!rangeContainsRange(func, node) || rangeContainsRange(func.body, node) && !considerFunctionBodies)) { 108 return undefined; 109 } 110 111 if (refactorKindBeginsWith(addBracesAction.kind, kind) && isExpression(func.body)) { 112 return { func, addBraces: true, expression: func.body }; 113 } 114 else if (refactorKindBeginsWith(removeBracesAction.kind, kind) && isBlock(func.body) && func.body.statements.length === 1) { 115 const firstStatement = first(func.body.statements); 116 if (isReturnStatement(firstStatement)) { 117 return { func, addBraces: false, expression: firstStatement.expression, returnStatement: firstStatement }; 118 } 119 } 120 return undefined; 121 } 122} 123