Skip to content

Commit e07e2e0

Browse files
authored
Merge pull request #23423 from Kingwl/add-braces
add support for add or remove braces to arrow function
2 parents 7df8131 + 0d730c0 commit e07e2e0

31 files changed

+408
-16
lines changed

Diff for: src/compiler/diagnosticMessages.json

+12
Original file line numberDiff line numberDiff line change
@@ -4402,5 +4402,17 @@
44024402
"Convert named imports to namespace import": {
44034403
"category": "Message",
44044404
"code": 95057
4405+
},
4406+
"Add or remove braces in an arrow function": {
4407+
"category": "Message",
4408+
"code": 95058
4409+
},
4410+
"Add braces to arrow function": {
4411+
"category": "Message",
4412+
"code": 95059
4413+
},
4414+
"Remove braces from arrow function": {
4415+
"category": "Message",
4416+
"code": 95060
44054417
}
44064418
}

Diff for: src/harness/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
"../services/refactors/extractSymbol.ts",
126126
"../services/refactors/generateGetAccessorAndSetAccessor.ts",
127127
"../services/refactors/moveToNewFile.ts",
128+
"../services/refactors/addOrRemoveBracesToArrowFunction.ts",
128129
"../services/sourcemaps.ts",
129130
"../services/services.ts",
130131
"../services/breakpoints.ts",

Diff for: src/server/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"../services/refactors/extractSymbol.ts",
121121
"../services/refactors/generateGetAccessorAndSetAccessor.ts",
122122
"../services/refactors/moveToNewFile.ts",
123+
"../services/refactors/addOrRemoveBracesToArrowFunction.ts",
123124
"../services/sourcemaps.ts",
124125
"../services/services.ts",
125126
"../services/breakpoints.ts",

Diff for: src/server/tsconfig.library.json

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
"../services/refactors/extractSymbol.ts",
127127
"../services/refactors/generateGetAccessorAndSetAccessor.ts",
128128
"../services/refactors/moveToNewFile.ts",
129+
"../services/refactors/addOrRemoveBracesToArrowFunction.ts",
129130
"../services/sourcemaps.ts",
130131
"../services/services.ts",
131132
"../services/breakpoints.ts",

Diff for: src/services/codefixes/convertFunctionToEs6Class.ts

-16
Original file line numberDiff line numberDiff line change
@@ -202,22 +202,6 @@ namespace ts.codefix {
202202
}
203203
}
204204

205-
function copyComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile) {
206-
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, (pos, end, kind, htnl) => {
207-
if (kind === SyntaxKind.MultiLineCommentTrivia) {
208-
// Remove leading /*
209-
pos += 2;
210-
// Remove trailing */
211-
end -= 2;
212-
}
213-
else {
214-
// Remove leading //
215-
pos += 2;
216-
}
217-
addSyntheticLeadingComment(targetNode, kind, sourceFile.text.slice(pos, end), htnl);
218-
});
219-
}
220-
221205
function getModifierKindFromSource(source: Node, kind: SyntaxKind): ReadonlyArray<Modifier> | undefined {
222206
return filter(source.modifiers, modifier => modifier.kind === kind);
223207
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/* @internal */
2+
namespace 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+
const addBracesActionName = "Add braces to arrow function";
6+
const removeBracesActionName = "Remove braces from arrow function";
7+
const addBracesActionDescription = Diagnostics.Add_braces_to_arrow_function.message;
8+
const removeBracesActionDescription = Diagnostics.Remove_braces_from_arrow_function.message;
9+
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
10+
11+
interface Info {
12+
func: ArrowFunction;
13+
expression: Expression | undefined;
14+
returnStatement?: ReturnStatement;
15+
addBraces: boolean;
16+
}
17+
18+
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
19+
const { file, startPosition } = context;
20+
const info = getConvertibleArrowFunctionAtPosition(file, startPosition);
21+
if (!info) return undefined;
22+
23+
return [{
24+
name: refactorName,
25+
description: refactorDescription,
26+
actions: [
27+
info.addBraces ?
28+
{
29+
name: addBracesActionName,
30+
description: addBracesActionDescription
31+
} : {
32+
name: removeBracesActionName,
33+
description: removeBracesActionDescription
34+
}
35+
]
36+
}];
37+
}
38+
39+
function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined {
40+
const { file, startPosition } = context;
41+
const info = getConvertibleArrowFunctionAtPosition(file, startPosition);
42+
if (!info) return undefined;
43+
44+
const { expression, returnStatement, func } = info;
45+
46+
let body: ConciseBody;
47+
if (actionName === addBracesActionName) {
48+
const returnStatement = createReturn(expression);
49+
body = createBlock([returnStatement], /* multiLine */ true);
50+
suppressLeadingAndTrailingTrivia(body);
51+
copyComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true);
52+
}
53+
else if (actionName === removeBracesActionName && returnStatement) {
54+
const actualExpression = expression || createVoidZero();
55+
body = needsParentheses(actualExpression) ? createParen(actualExpression) : actualExpression;
56+
suppressLeadingAndTrailingTrivia(body);
57+
copyComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
58+
}
59+
else {
60+
Debug.fail("invalid action");
61+
}
62+
63+
const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func.body, body));
64+
return { renameFilename: undefined, renameLocation: undefined, edits };
65+
}
66+
67+
function needsParentheses(expression: Expression) {
68+
return isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.CommaToken || isObjectLiteralExpression(expression);
69+
}
70+
71+
function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number): Info | undefined {
72+
const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
73+
const func = getContainingFunction(node);
74+
if (!func || !isArrowFunction(func) || (!rangeContainsRange(func, node) || rangeContainsRange(func.body, node))) return undefined;
75+
76+
if (isExpression(func.body)) {
77+
return {
78+
func,
79+
addBraces: true,
80+
expression: func.body
81+
};
82+
}
83+
else if (func.body.statements.length === 1) {
84+
const firstStatement = first(func.body.statements);
85+
if (isReturnStatement(firstStatement)) {
86+
return {
87+
func,
88+
addBraces: false,
89+
expression: firstStatement.expression,
90+
returnStatement: firstStatement
91+
};
92+
}
93+
}
94+
return undefined;
95+
}
96+
}

Diff for: src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
"refactors/extractSymbol.ts",
118118
"refactors/generateGetAccessorAndSetAccessor.ts",
119119
"refactors/moveToNewFile.ts",
120+
"refactors/addOrRemoveBracesToArrowFunction.ts",
120121
"sourcemaps.ts",
121122
"services.ts",
122123
"breakpoints.ts",

Diff for: src/services/utilities.ts

+16
Original file line numberDiff line numberDiff line change
@@ -1720,6 +1720,22 @@ namespace ts {
17201720
return lastPos;
17211721
}
17221722

1723+
export function copyComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
1724+
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, (pos, end, kind, htnl) => {
1725+
if (kind === SyntaxKind.MultiLineCommentTrivia) {
1726+
// Remove leading /*
1727+
pos += 2;
1728+
// Remove trailing */
1729+
end -= 2;
1730+
}
1731+
else {
1732+
// Remove leading //
1733+
pos += 2;
1734+
}
1735+
addSyntheticLeadingComment(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl);
1736+
});
1737+
}
1738+
17231739
function indexInTextChange(change: string, name: string): number {
17241740
if (startsWith(change, name)) return 0;
17251741
// Add a " " to avoid references inside words
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => a + 1;
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Add braces to arrow function",
9+
actionDescription: "Add braces to arrow function",
10+
newContent: `const foo = a => {
11+
return a + 1;
12+
};`,
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return (1, 2, 3); };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => (1, 2, 3);`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return 1, 2, 3; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => (1, 2, 3);`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return "foo"; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => "foo";`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return null; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => null;`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return undefined; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => undefined;`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return void 0; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => void 0;`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return {}; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => ({});`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return `abc{a}`; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => \`abc{a}\`;`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return `abc`; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => \`abc\`;`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return a; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => a;`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => ({ a: 1 });
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Add braces to arrow function",
9+
actionDescription: "Add braces to arrow function",
10+
newContent: `const foo = a => {
11+
return ({ a: 1 });
12+
};`,
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => {
4+
//// // return comment
5+
//// return a;
6+
//// };
7+
8+
goTo.select("a", "b");
9+
edit.applyRefactor({
10+
refactorName: "Add or remove braces in an arrow function",
11+
actionName: "Remove braces from arrow function",
12+
actionDescription: "Remove braces from arrow function",
13+
newContent: `const foo = a => /* return comment*/ a;`,
14+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = /*a*/a/*b*/ => { return; };
4+
5+
goTo.select("a", "b");
6+
edit.applyRefactor({
7+
refactorName: "Add or remove braces in an arrow function",
8+
actionName: "Remove braces from arrow function",
9+
actionDescription: "Remove braces from arrow function",
10+
newContent: `const foo = a => void 0;`,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const /*a*/foo/*b*/ = /*c*/(/*d*//*e*/aa/*f*/aa, /*g*/b/*h*/) /*i*//*j*/ /*k*/=>/*l*/ /*m*/{/*n*/ /*o*/return/*p*/ 1; };
4+
5+
goTo.select("a", "b");
6+
verify.not.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
7+
8+
goTo.select("c", "d");
9+
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
10+
11+
goTo.select("e", "f");
12+
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
13+
14+
goTo.select("g", "h");
15+
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
16+
17+
goTo.select("i", "j");
18+
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
19+
20+
goTo.select("k", "l");
21+
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
22+
23+
goTo.select("m", "n");
24+
verify.not.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
25+
26+
goTo.select("o", "p");
27+
verify.not.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")

0 commit comments

Comments
 (0)