• 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,
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