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

New rule no-return-await #3233

Merged
merged 5 commits into from
Oct 20, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export const rules = {
"no-misused-new": true,
"no-null-keyword": true,
"no-object-literal-type-assertion": true,
"no-return-await": true,
"no-shadowed-variable": true,
"no-string-literal": true,
"no-string-throw": true,
Expand Down
5 changes: 3 additions & 2 deletions src/configs/latest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,12 @@ export const rules = {
},

// added in v5.8
"no-duplicate-switch-case": true,
"ban-comma-operator": true,
"jsdoc-format": {
options: "check-multiline-start",
},
"ban-comma-operator": true,
"no-duplicate-switch-case": true,
"no-return-await": true,
};
// tslint:enable object-literal-sort-keys

Expand Down
116 changes: 116 additions & 0 deletions src/rules/noReturnAwaitRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* @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 { isFunctionScopeBoundary, isTryStatement } from "tsutils";
import * as ts from "typescript";

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

export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "no-return-await",
description: "Disallows unnecessary `return await`.",
rationale: Lint.Utils.dedent`
An async function always wraps the return value in a Promise.
Using \`return await\` just adds extra time before the overreaching promise is resolved without changing the semantics.
`,
optionsDescription: "Not configurable.",
options: null,
optionExamples: [true],
type: "functionality",
typescriptOnly: false,
hasFix: true,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING = "Unnecessary 'await'.";

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}

function walk(ctx: Lint.WalkContext<void>) {
return ts.forEachChild(ctx.sourceFile, function cb(node): void {
if (node.kind === ts.SyntaxKind.AwaitExpression && isUnnecessaryAwait(node)) {
const {expression} = node as ts.AwaitExpression;
const keywordStart = expression.pos - "await".length;
ctx.addFailure(
keywordStart,
expression.pos,
Rule.FAILURE_STRING,
Lint.Replacement.deleteFromTo(keywordStart, expression.getStart(ctx.sourceFile)),
);
}
return ts.forEachChild(node, cb);
});
}

function isUnnecessaryAwait(node: ts.Node): boolean {
while (true) {
const parent = node.parent!;
outer: switch (parent.kind) {
case ts.SyntaxKind.ArrowFunction:
return true;
case ts.SyntaxKind.ReturnStatement:
return !isInsideTryBlock(parent.parent!);
case ts.SyntaxKind.ParenthesizedExpression:
break;
case ts.SyntaxKind.ConditionalExpression:
if ((parent as ts.ConditionalExpression).condition === node) {
return false;
}
break;
case ts.SyntaxKind.BinaryExpression:
if ((parent as ts.BinaryExpression).right === node) {
switch ((parent as ts.BinaryExpression).operatorToken.kind) {
case ts.SyntaxKind.AmpersandAmpersandToken:
case ts.SyntaxKind.BarBarToken:
case ts.SyntaxKind.CommaToken:
break outer;
}
}
return false;
default:
return false;
}
node = parent;
}
}

function isInsideTryBlock(node: ts.Node): boolean {
while (node.parent !== undefined) {
if (isFunctionScopeBoundary(node)) {
return false;
}
if (isTryStatement(node.parent)) {
if (
// statements inside the try block always have an error handler, either catch or finally
node.parent.tryBlock === node ||
// statement inside the catch block only have an error handler if there is a finally block
node.parent.finallyBlock !== undefined && node.parent.catchClause === node
) {
return true;
}
node = node.parent.parent!;
} else {
node = node.parent;
}
}
return false;
}
80 changes: 80 additions & 0 deletions test/rules/no-return-await/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
async function foo() {
return x;
}
(async () => x);

async function allowTypeAssertion() {
return (await x)!;
return (await x as number);
return <string> await x;
}
async function allowedInsideTryCatch() {
try {
return await x;
} catch (e) {
// handle error
return x; // not allowed in catch when there is no finally
}
}
async function allowedInsideTryFinally() {
try {
return await x;
} finally {
// do cleanup, close connection for example
return x; // not allowed in finally at all
}
}
async function allowedInsideCatchFinally() {
try {
return await x;
} catch (e) {
return await x;
} finally {
return x;
}
}
async function nestedTryStatements() {
try {
try {
return await x;
} catch (e) {
return await x;
} finally {
return await foobar;
}
} catch(e) {
try {
return await x;
} catch (e) {
return await x;
} finally {
return await foobar;
}
} finally {
try {
return await x;
} catch (e) {
return await x;
} finally {
return x;
}
}
}

async function handleParens() {
return (x);
}
async function handleBinaryExpression() {
return await foo || x;
return await foo && x;
return await foo, x;
return await foo + await bar;
}
async function handleConditionalExpression() {
return await foo ? bar : baz;
}

throw await x;
await x;
return x;

94 changes: 94 additions & 0 deletions test/rules/no-return-await/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
async function foo() {
return await x;
~~~~~ [0]
}
(async () => await x);
~~~~~ [0]

async function allowTypeAssertion() {
return (await x)!;
return (await x as number);
return <string> await x;
}
async function allowedInsideTryCatch() {
try {
return await x;
} catch (e) {
// handle error
return await x; // not allowed in catch when there is no finally
~~~~~ [0]
}
}
async function allowedInsideTryFinally() {
try {
return await x;
} finally {
// do cleanup, close connection for example
return await x; // not allowed in finally at all
~~~~~ [0]
}
}
async function allowedInsideCatchFinally() {
try {
return await x;
} catch (e) {
return await x;
} finally {
return await x;
~~~~~ [0]
}
}
async function nestedTryStatements() {
try {
try {
return await x;
} catch (e) {
return await x;
} finally {
return await foobar;
}
} catch(e) {
try {
return await x;
} catch (e) {
return await x;
} finally {
return await foobar;
}
} finally {
try {
return await x;
} catch (e) {
return await x;
} finally {
return await x;
~~~~~ [0]
}
}
}

async function handleParens() {
return (await x);
~~~~~ [0]
}
async function handleBinaryExpression() {
return await foo || await x;
~~~~~ [0]
return await foo && await x;
~~~~~ [0]
return await foo, await x;
~~~~~ [0]
return await foo + await bar;
}
async function handleConditionalExpression() {
return await foo ? await bar : await baz;
~~~~~ [0]
~~~~~ [0]
}

throw await x;
await x;
return await x;
~~~~~ [0]

[0]: Unnecessary 'await'.
5 changes: 5 additions & 0 deletions test/rules/no-return-await/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"no-return-await": true
}
}