• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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