Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Add arrow-return-shorthand rule #1972

Merged
merged 6 commits into from
Jan 7, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/_data/rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,18 @@
"type": "style",
"typescriptOnly": false
},
{
"ruleName": "prefer-arrow-shorthand-return",
"description": "Suggests to convert `() => { return x; }` to `() => x`.",
"optionsDescription": "Not configurable.",
"options": null,
"optionExamples": [
"[true]",
"[true, \"multiline\"]"
],
"type": "functionality",
"typescriptOnly": false
},
{
"ruleName": "prefer-const",
"description": "Requires that variable declarations use `const` instead of `let` if possible.",
Expand Down
14 changes: 14 additions & 0 deletions docs/rules/prefer-arrow-shorthand-return/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
ruleName: prefer-arrow-shorthand-return
description: 'Suggests to convert `() => { return x; }` to `() => x`.'
optionsDescription: Not configurable.
options: null
optionExamples:
- '[true]'
- '[true, "multiline"]'
type: functionality
typescriptOnly: false
layout: rule
title: 'Rule: prefer-arrow-shorthand-return'
optionsJSON: 'null'
---
4 changes: 2 additions & 2 deletions src/language/walker/syntaxWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class SyntaxWalker {
this.walkChildren(node);
}

protected visitArrowFunction(node: ts.FunctionLikeDeclaration) {
protected visitArrowFunction(node: ts.ArrowFunction) {
this.walkChildren(node);
}

Expand Down Expand Up @@ -361,7 +361,7 @@ export class SyntaxWalker {
break;

case ts.SyntaxKind.ArrowFunction:
this.visitArrowFunction(node as ts.FunctionLikeDeclaration);
this.visitArrowFunction(node as ts.ArrowFunction);
break;

case ts.SyntaxKind.BinaryExpression:
Expand Down
2 changes: 1 addition & 1 deletion src/rules/arrowParensRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ArrowParensWalker extends Lint.RuleWalker {
this.avoidOnSingleParameter = this.hasOption(BAN_SINGLE_ARG_PARENS);
}

public visitArrowFunction(node: ts.FunctionLikeDeclaration) {
public visitArrowFunction(node: ts.ArrowFunction) {
if (node.parameters.length === 1 && node.typeParameters === undefined) {
const parameter = node.parameters[0];

Expand Down
2 changes: 1 addition & 1 deletion src/rules/cyclomaticComplexityRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class CyclomaticComplexityWalker extends Lint.RuleWalker {
super(sourceFile, options);
}

protected visitArrowFunction(node: ts.FunctionLikeDeclaration) {
protected visitArrowFunction(node: ts.ArrowFunction) {
this.startFunction();
super.visitArrowFunction(node);
this.endFunction(node);
Expand Down
2 changes: 1 addition & 1 deletion src/rules/noUnusedExpressionRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export class NoUnusedExpressionWalker extends Lint.RuleWalker {
this.expressionIsUnused = true;
}

public visitArrowFunction(node: ts.FunctionLikeDeclaration) {
public visitArrowFunction(node: ts.ArrowFunction) {
super.visitArrowFunction(node);
this.expressionIsUnused = true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/rules/oneLineRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class OneLineWalker extends Lint.RuleWalker {
super.visitConstructorDeclaration(node);
}

public visitArrowFunction(node: ts.FunctionLikeDeclaration) {
public visitArrowFunction(node: ts.ArrowFunction) {
const body = node.body;
if (body != null && body.kind === ts.SyntaxKind.Block) {
const arrowToken = Lint.childOfKind(node, ts.SyntaxKind.EqualsGreaterThanToken);
Expand Down
105 changes: 105 additions & 0 deletions src/rules/preferArrowShorthandReturnRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* @license
* Copyright 2017 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as ts from "typescript";

import * as Lint from "../index";

const OPTION_MULTILINE = "multiline";

export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please add hasFix: true to the metadata?

ruleName: "prefer-arrow-shorthand-return",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for changing this up again -- I actually think we can simplify the name further and put "shorthand" at the end to be consistent with object-literal-shorthand. how about "arrow-return-shorthand"?

description: "Suggests to convert `() => { return x; }` to `() => x`.",
optionsDescription: "Not configurable.",
options: null,
optionExamples: [
`[true]`,
`[true, "${OPTION_MULTILINE}"]`,
],
type: "functionality",
typescriptOnly: false,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING =
"This arrow function body can be simplified by omitting the curly braces and the keyword 'return'.";

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
}
}

class Walker extends Lint.RuleWalker {
public visitArrowFunction(node: ts.ArrowFunction) {
if (node.body && node.body.kind === ts.SyntaxKind.Block) {
const expr = getSimpleReturnExpression(node.body as ts.Block);
if (expr !== undefined && (this.hasOption(OPTION_MULTILINE) || !this.isMultiline(node.body))) {
this.addFailureAtNode(node.body, Rule.FAILURE_STRING, this.createArrowFunctionFix(node, node.body as ts.Block, expr));
}
}

super.visitArrowFunction(node);
}

private isMultiline(node: ts.Node): boolean {
const getLine = (position: number) => this.getLineAndCharacterOfPosition(position).line;
return getLine(node.getEnd()) > getLine(node.getStart());
}

private createArrowFunctionFix(arrowFunction: ts.FunctionLikeDeclaration, body: ts.Block, expr: ts.Expression): Lint.Fix | undefined {
const text = this.getSourceFile().text;
const statement = expr.parent!;
const returnKeyword = Lint.childOfKind(statement, ts.SyntaxKind.ReturnKeyword)!;
const arrow = Lint.childOfKind(arrowFunction, ts.SyntaxKind.EqualsGreaterThanToken)!;
const openBrace = Lint.childOfKind(body, ts.SyntaxKind.OpenBraceToken)!;
const closeBrace = Lint.childOfKind(body, ts.SyntaxKind.CloseBraceToken)!;
const semicolon = Lint.childOfKind(statement, ts.SyntaxKind.SemicolonToken);

const anyComments = hasComments(arrow) || hasComments(openBrace) || hasComments(statement) || hasComments(returnKeyword) ||
hasComments(expr) || (semicolon && hasComments(semicolon)) || hasComments(closeBrace);
return anyComments ? undefined : this.createFix(
// Object literal must be wrapped in `()`
...(expr.kind === ts.SyntaxKind.ObjectLiteralExpression ? [
this.appendText(expr.getStart(), "("),
this.appendText(expr.getEnd(), ")"),
] : []),
// " {"
deleteFromTo(arrow.end, openBrace.end),
// "return "
deleteFromTo(statement.getStart(), expr.getStart()),
// " }" (may include semicolon)
deleteFromTo(expr.end, closeBrace.end),
);

function hasComments(node: ts.Node): boolean {
return ts.getTrailingCommentRanges(text, node.getEnd()) !== undefined;
}
}
}

/** Given `{ return x; }`, return `x`. */
function getSimpleReturnExpression(block: ts.Block): ts.Expression | undefined {
return block.statements.length === 1 && block.statements[0].kind === ts.SyntaxKind.ReturnStatement
? (block.statements[0] as ts.ReturnStatement).expression
: undefined;
}

function deleteFromTo(start: number, end: number): Lint.Replacement {
return new Lint.Replacement(start, end - start, "");
}
2 changes: 1 addition & 1 deletion src/rules/spaceBeforeFunctionParenRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class FunctionWalker extends Lint.RuleWalker {
this.cacheOptions();
}

protected visitArrowFunction(node: ts.FunctionLikeDeclaration): void {
protected visitArrowFunction(node: ts.ArrowFunction): void {
const option = this.getOption("asyncArrow");
const syntaxList = Lint.childOfKind(node, ts.SyntaxKind.SyntaxList)!;
const isAsyncArrow = syntaxList.getStart() === node.getStart() && syntaxList.getText() === "async";
Expand Down
2 changes: 1 addition & 1 deletion src/rules/trailingCommaRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class TrailingCommaWalker extends Lint.RuleWalker {
super.visitArrayLiteralExpression(node);
}

public visitArrowFunction(node: ts.FunctionLikeDeclaration) {
public visitArrowFunction(node: ts.ArrowFunction) {
this.lintChildNodeWithIndex(node, 1);
super.visitArrowFunction(node);
}
Expand Down
2 changes: 1 addition & 1 deletion src/rules/typedefRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class TypedefWalker extends Lint.RuleWalker {
super.visitFunctionExpression(node);
}

public visitArrowFunction(node: ts.FunctionLikeDeclaration) {
public visitArrowFunction(node: ts.ArrowFunction) {
const location = (node.parameters != null) ? node.parameters.end : null;

if (location != null
Expand Down
2 changes: 1 addition & 1 deletion src/rules/whitespaceRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class WhitespaceWalker extends Lint.SkippableTokenAwareRuleWalker {
});
}

public visitArrowFunction(node: ts.FunctionLikeDeclaration) {
public visitArrowFunction(node: ts.ArrowFunction) {
this.checkEqualsGreaterThanTokenInNode(node);
super.visitArrowFunction(node);
}
Expand Down
23 changes: 23 additions & 0 deletions test/rules/prefer-arrow-shorthand-return/default/test.js.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copy of test.ts.lint

// Invalid:
(() => 0);
(() => ({ x: 1 }));
(() => {
return 0;
});

// Valid:
(() => 0);
(() => {});
(() => { throw 0; })
(() => { const x = 0; return x; });

// No fix if there's a comment.
(() => /**/ { return 0; });
(() => { /**/ return 0; });
(() => { return /**/ 0; });
(() => { return 0 /**/ });
(() => { return 0 /**/; });
(() => { return 0; /**/ });

32 changes: 32 additions & 0 deletions test/rules/prefer-arrow-shorthand-return/default/test.js.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copy of test.ts.lint

// Invalid:
(() => { return 0; });
~~~~~~~~~~~~~ [0]
(() => { return { x: 1 } });
~~~~~~~~~~~~~~~~~~~ [0]
(() => {
return 0;
});

// Valid:
(() => 0);
(() => {});
(() => { throw 0; })
(() => { const x = 0; return x; });

// No fix if there's a comment.
(() => /**/ { return 0; });
~~~~~~~~~~~~~ [0]
(() => { /**/ return 0; });
~~~~~~~~~~~~~~~~~~ [0]
(() => { return /**/ 0; });
~~~~~~~~~~~~~~~~~~ [0]
(() => { return 0 /**/ });
~~~~~~~~~~~~~~~~~ [0]
(() => { return 0 /**/; });
~~~~~~~~~~~~~~~~~~ [0]
(() => { return 0; /**/ });
~~~~~~~~~~~~~~~~~~ [0]

[0]: This arrow function body can be simplified by omitting the curly braces and the keyword 'return'.
21 changes: 21 additions & 0 deletions test/rules/prefer-arrow-shorthand-return/default/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Invalid:
(() => 0);
(() => ({ x: 1 }));
(() => {
return 0;
});

// Valid:
(() => 0);
(() => {});
(() => { throw 0; })
(() => { const x = 0; return x; });

// No fix if there's a comment.
(() => /**/ { return 0; });
(() => { /**/ return 0; });
(() => { return /**/ 0; });
(() => { return 0 /**/ });
(() => { return 0 /**/; });
(() => { return 0; /**/ });

30 changes: 30 additions & 0 deletions test/rules/prefer-arrow-shorthand-return/default/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Invalid:
(() => { return 0; });
~~~~~~~~~~~~~ [0]
(() => { return { x: 1 } });
~~~~~~~~~~~~~~~~~~~ [0]
(() => {
return 0;
});

// Valid:
(() => 0);
(() => {});
(() => { throw 0; })
(() => { const x = 0; return x; });

// No fix if there's a comment.
(() => /**/ { return 0; });
~~~~~~~~~~~~~ [0]
(() => { /**/ return 0; });
~~~~~~~~~~~~~~~~~~ [0]
(() => { return /**/ 0; });
~~~~~~~~~~~~~~~~~~ [0]
(() => { return 0 /**/ });
~~~~~~~~~~~~~~~~~ [0]
(() => { return 0 /**/; });
~~~~~~~~~~~~~~~~~~ [0]
(() => { return 0; /**/ });
~~~~~~~~~~~~~~~~~~ [0]

[0]: This arrow function body can be simplified by omitting the curly braces and the keyword 'return'.
8 changes: 8 additions & 0 deletions test/rules/prefer-arrow-shorthand-return/default/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"rules": {
"prefer-arrow-shorthand-return": true
},
"jsRules": {
"prefer-arrow-shorthand-return": true
}
}
20 changes: 20 additions & 0 deletions test/rules/prefer-arrow-shorthand-return/multiline/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Invalid:
(() => 0);
(() => ({ x: 1 }));
(() =>
0);

// Valid:
(() => 0);
(() => {});
(() => { throw 0; })
(() => { const x = 0; return x; });

// No fix if there's a comment.
(() => /**/ { return 0; });
(() => { /**/ return 0; });
(() => { return /**/ 0; });
(() => { return 0 /**/ });
(() => { return 0 /**/; });
(() => { return 0; /**/ });