Merge pull request #23423 from Kingwl/add-braces

add support for add or remove braces to arrow function
This commit is contained in:
Mohamed Hegazy 2018-06-11 13:09:38 -07:00 committed by GitHub
commit e07e2e0e1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 408 additions and 16 deletions

View File

@ -4402,5 +4402,17 @@
"Convert named imports to namespace import": {
"category": "Message",
"code": 95057
},
"Add or remove braces in an arrow function": {
"category": "Message",
"code": 95058
},
"Add braces to arrow function": {
"category": "Message",
"code": 95059
},
"Remove braces from arrow function": {
"category": "Message",
"code": 95060
}
}

View File

@ -125,6 +125,7 @@
"../services/refactors/extractSymbol.ts",
"../services/refactors/generateGetAccessorAndSetAccessor.ts",
"../services/refactors/moveToNewFile.ts",
"../services/refactors/addOrRemoveBracesToArrowFunction.ts",
"../services/sourcemaps.ts",
"../services/services.ts",
"../services/breakpoints.ts",

View File

@ -120,6 +120,7 @@
"../services/refactors/extractSymbol.ts",
"../services/refactors/generateGetAccessorAndSetAccessor.ts",
"../services/refactors/moveToNewFile.ts",
"../services/refactors/addOrRemoveBracesToArrowFunction.ts",
"../services/sourcemaps.ts",
"../services/services.ts",
"../services/breakpoints.ts",

View File

@ -126,6 +126,7 @@
"../services/refactors/extractSymbol.ts",
"../services/refactors/generateGetAccessorAndSetAccessor.ts",
"../services/refactors/moveToNewFile.ts",
"../services/refactors/addOrRemoveBracesToArrowFunction.ts",
"../services/sourcemaps.ts",
"../services/services.ts",
"../services/breakpoints.ts",

View File

@ -202,22 +202,6 @@ namespace ts.codefix {
}
}
function copyComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile) {
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, (pos, end, kind, htnl) => {
if (kind === SyntaxKind.MultiLineCommentTrivia) {
// Remove leading /*
pos += 2;
// Remove trailing */
end -= 2;
}
else {
// Remove leading //
pos += 2;
}
addSyntheticLeadingComment(targetNode, kind, sourceFile.text.slice(pos, end), htnl);
});
}
function getModifierKindFromSource(source: Node, kind: SyntaxKind): ReadonlyArray<Modifier> | undefined {
return filter(source.modifiers, modifier => modifier.kind === kind);
}

View File

@ -0,0 +1,96 @@
/* @internal */
namespace ts.refactor.addOrRemoveBracesToArrowFunction {
const refactorName = "Add or remove braces in an arrow function";
const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message;
const addBracesActionName = "Add braces to arrow function";
const removeBracesActionName = "Remove braces from arrow function";
const addBracesActionDescription = Diagnostics.Add_braces_to_arrow_function.message;
const removeBracesActionDescription = Diagnostics.Remove_braces_from_arrow_function.message;
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
interface Info {
func: ArrowFunction;
expression: Expression | undefined;
returnStatement?: ReturnStatement;
addBraces: boolean;
}
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
const { file, startPosition } = context;
const info = getConvertibleArrowFunctionAtPosition(file, startPosition);
if (!info) return undefined;
return [{
name: refactorName,
description: refactorDescription,
actions: [
info.addBraces ?
{
name: addBracesActionName,
description: addBracesActionDescription
} : {
name: removeBracesActionName,
description: removeBracesActionDescription
}
]
}];
}
function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined {
const { file, startPosition } = context;
const info = getConvertibleArrowFunctionAtPosition(file, startPosition);
if (!info) return undefined;
const { expression, returnStatement, func } = info;
let body: ConciseBody;
if (actionName === addBracesActionName) {
const returnStatement = createReturn(expression);
body = createBlock([returnStatement], /* multiLine */ true);
suppressLeadingAndTrailingTrivia(body);
copyComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true);
}
else if (actionName === removeBracesActionName && returnStatement) {
const actualExpression = expression || createVoidZero();
body = needsParentheses(actualExpression) ? createParen(actualExpression) : actualExpression;
suppressLeadingAndTrailingTrivia(body);
copyComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
}
else {
Debug.fail("invalid action");
}
const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func.body, body));
return { renameFilename: undefined, renameLocation: undefined, edits };
}
function needsParentheses(expression: Expression) {
return isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.CommaToken || isObjectLiteralExpression(expression);
}
function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number): Info | undefined {
const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
const func = getContainingFunction(node);
if (!func || !isArrowFunction(func) || (!rangeContainsRange(func, node) || rangeContainsRange(func.body, node))) return undefined;
if (isExpression(func.body)) {
return {
func,
addBraces: true,
expression: func.body
};
}
else if (func.body.statements.length === 1) {
const firstStatement = first(func.body.statements);
if (isReturnStatement(firstStatement)) {
return {
func,
addBraces: false,
expression: firstStatement.expression,
returnStatement: firstStatement
};
}
}
return undefined;
}
}

View File

@ -117,6 +117,7 @@
"refactors/extractSymbol.ts",
"refactors/generateGetAccessorAndSetAccessor.ts",
"refactors/moveToNewFile.ts",
"refactors/addOrRemoveBracesToArrowFunction.ts",
"sourcemaps.ts",
"services.ts",
"breakpoints.ts",

View File

@ -1720,6 +1720,22 @@ namespace ts {
return lastPos;
}
export function copyComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, (pos, end, kind, htnl) => {
if (kind === SyntaxKind.MultiLineCommentTrivia) {
// Remove leading /*
pos += 2;
// Remove trailing */
end -= 2;
}
else {
// Remove leading //
pos += 2;
}
addSyntheticLeadingComment(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl);
});
}
function indexInTextChange(change: string, name: string): number {
if (startsWith(change, name)) return 0;
// Add a " " to avoid references inside words

View File

@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => a + 1;
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Add braces to arrow function",
actionDescription: "Add braces to arrow function",
newContent: `const foo = a => {
return a + 1;
};`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return (1, 2, 3); };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => (1, 2, 3);`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return 1, 2, 3; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => (1, 2, 3);`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return "foo"; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => "foo";`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return null; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => null;`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return undefined; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => undefined;`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return void 0; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => void 0;`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return {}; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => ({});`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return `abc{a}`; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => \`abc{a}\`;`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return `abc`; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => \`abc\`;`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return a; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => a;`,
});

View File

@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => ({ a: 1 });
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Add braces to arrow function",
actionDescription: "Add braces to arrow function",
newContent: `const foo = a => {
return ({ a: 1 });
};`,
});

View File

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => {
//// // return comment
//// return a;
//// };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => /* return comment*/ a;`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => void 0;`,
});

View File

@ -0,0 +1,27 @@
/// <reference path='fourslash.ts' />
//// const /*a*/foo/*b*/ = /*c*/(/*d*//*e*/aa/*f*/aa, /*g*/b/*h*/) /*i*//*j*/ /*k*/=>/*l*/ /*m*/{/*n*/ /*o*/return/*p*/ 1; };
goTo.select("a", "b");
verify.not.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
goTo.select("c", "d");
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
goTo.select("e", "f");
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
goTo.select("g", "h");
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
goTo.select("i", "j");
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
goTo.select("k", "l");
verify.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
goTo.select("m", "n");
verify.not.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")
goTo.select("o", "p");
verify.not.refactorAvailable("Add or remove braces in an arrow function", "Remove braces from arrow function")

View File

@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />
//// const /*a*/foo/*b*/ = /*c*/()/*d*/ /*e*//*f*/ /*g*/=>/*h*/ /*i*/1/*j*/;
goTo.select("a", "b");
verify.not.refactorAvailable("Add or remove braces in an arrow function", "Add braces to arrow function")
goTo.select("c", "d");
verify.refactorAvailable("Add or remove braces in an arrow function", "Add braces to arrow function")
goTo.select("e", "f");
verify.refactorAvailable("Add or remove braces in an arrow function", "Add braces to arrow function")
goTo.select("g", "h");
verify.refactorAvailable("Add or remove braces in an arrow function", "Add braces to arrow function")
goTo.select("i", "j");
verify.not.refactorAvailable("Add or remove braces in an arrow function", "Add braces to arrow function")

View File

@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => 1;
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Add braces to arrow function",
actionDescription: "Add braces to arrow function",
newContent: `const foo = a => {
return 1;
};`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return a + 1; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => a + 1;`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return { a: 1 }; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => ({ a: 1 });`,
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { return 1; };
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Remove braces from arrow function",
actionDescription: "Remove braces from arrow function",
newContent: `const foo = a => 1;`,
});

View File

@ -0,0 +1,6 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => { };
goTo.select("a", "b");
verify.not.refactorAvailable("Add or remove braces in an arrow function");

View File

@ -0,0 +1,9 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => {
//// const b = 1;
//// return a + b;
//// };
goTo.select("a", "b");
verify.not.refactorAvailable("Add or remove braces in an arrow function");

View File

@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// const foo = /*a*/a/*b*/ => (1, 2, 3);
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Add or remove braces in an arrow function",
actionName: "Add braces to arrow function",
actionDescription: "Add braces to arrow function",
newContent: `const foo = a => {
return (1, 2, 3);
};`,
});