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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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; /**/ });

Loading