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

[New Rule] add noAsyncWithoutAwait rule #3945

Merged
merged 15 commits into from
Jun 16, 2019
Merged
1 change: 1 addition & 0 deletions src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const rules = {
// "import-blacklist": no sensible default
"label-position": true,
"no-arg": true,
"no-async-without-await": true,
"no-bitwise": true,
"no-conditional-assignment": true,
"no-console": true,
Expand Down
51 changes: 51 additions & 0 deletions src/rules/code-examples/noAsyncWithoutAwait.examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license
* Copyright 2018 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 Lint from "../../index";

export const codeExamples = [
{
config: Lint.Utils.dedent`
"rules": { "no-async-without-await": true }
`,
description: "Do not use the async keyword if it is not needed",
fail: Lint.Utils.dedent`
async function f() {
fetch();
}

async function f() {
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
async function g() {
await h();
}
}
`,
pass: Lint.Utils.dedent`
async function f() {
await fetch();
}

const f = async () => {
await fetch();
};

const f = async () => {
return 'value';
};
`,
},
];
140 changes: 140 additions & 0 deletions src/rules/noAsyncWithoutAwaitRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* @license
* Copyright 2018 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 tsutils from "tsutils";
import * as ts from "typescript";

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

import { codeExamples } from "./code-examples/noAsyncWithoutAwait.examples";

type FunctionNodeType =
| ts.ArrowFunction
| ts.FunctionDeclaration
| ts.MethodDeclaration
| ts.FunctionExpression;

export class Rule extends Lint.Rules.AbstractRule {
public static FAILURE_STRING =
"Functions marked async must contain an await or return statement.";

public static metadata: Lint.IRuleMetadata = {
codeExamples,
description: Rule.FAILURE_STRING,
hasFix: false,
optionExamples: [true],
options: null,
optionsDescription: "Not configurable.",
/* tslint:disable:max-line-length */
rationale: Lint.Utils.dedent`
Marking a function as \`async\` without using \`await\` or returning a value inside it can lead to an unintended promise return and a larger transpiled output.
Often the function can be synchronous and the \`async\` keyword is there by mistake.
Return statements are allowed as sometimes it is desirable to wrap the returned value in a Promise.`,
/* tslint:enable:max-line-length */
ruleName: "no-async-without-await",
type: "functionality",
typescriptOnly: false,
};

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

function walk(context: Lint.WalkContext) {
const reportFailureIfAsyncFunction = (node: FunctionNodeType) => {
const asyncModifier = getAsyncModifier(node);
if (asyncModifier !== undefined) {
context.addFailureAt(
asyncModifier.getStart(),
asyncModifier.getEnd() - asyncModifier.getStart(),
Rule.FAILURE_STRING,
);
}
};

const addFailureIfAsyncFunctionHasNoAwait = (node: FunctionNodeType) => {
if (node.body === undefined) {
reportFailureIfAsyncFunction(node);
return;
}

if (
!isShortArrowReturn(node) &&
!functionBlockHasAwait(node.body) &&
!functionBlockHasReturn(node.body)
) {
reportFailureIfAsyncFunction(node);
}
};

return ts.forEachChild(context.sourceFile, function visitNode(node): void {
if (
tsutils.isArrowFunction(node) ||
tsutils.isFunctionDeclaration(node) ||
tsutils.isFunctionExpression(node) ||
tsutils.isMethodDeclaration(node)
) {
addFailureIfAsyncFunctionHasNoAwait(node);
}

return ts.forEachChild(node, visitNode);
});
}

const getAsyncModifier = (node: ts.Node) => {
if (node.modifiers !== undefined) {
return node.modifiers.find(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword);
}

return undefined;
};

const isReturn = (node: ts.Node): boolean => node.kind === ts.SyntaxKind.ReturnKeyword;

const functionBlockHasAwait = (node: ts.Node): boolean => {
if (tsutils.isAwaitExpression(node)) {
return true;
}

if (
node.kind === ts.SyntaxKind.ArrowFunction ||
node.kind === ts.SyntaxKind.FunctionDeclaration
) {
return false;
}

return node.getChildren().some(functionBlockHasAwait);
};

const functionBlockHasReturn = (node: ts.Node): boolean => {
if (isReturn(node)) {
return true;
}

if (
node.kind === ts.SyntaxKind.ArrowFunction ||
node.kind === ts.SyntaxKind.FunctionDeclaration
) {
return false;
}

return node.getChildren().some(functionBlockHasReturn);
};

const isShortArrowReturn = (node: FunctionNodeType) =>
node.kind === ts.SyntaxKind.ArrowFunction && node.body.kind !== ts.SyntaxKind.Block;
122 changes: 122 additions & 0 deletions test/rules/no-async-without-await/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
async function a(){
eranshabi marked this conversation as resolved.
Show resolved Hide resolved
~~~~~ [0]
let b = 1;
console.log(b);
}

async function a(){
let b = 1;
await console.log(b);
}

async function a(){
let b = 1;
console.log(await b());
}

async function a(){
~~~~~ [0]
let b = 1;
let c = async () => {
await fetch();
};
}

async function a(){
~~~~~ [Functions marked async must contain an await or return statement.]
let b = 1;
async function f() {
await fetch();
};
}

function a(){
let b = 1;
async function f() {
~~~~~ [Functions marked async must contain an await or return statement.]
fetch();
};
}

const a = async () => {
~~~~~ [Functions marked async must contain an await or return statement.]
let b = 1;
console.log(b);
}

class A {
async b() {
~~~~~ [Functions marked async must contain an await or return statement.]
console.log(1);
}
}

class A {
async b() {
await b();
}
}

class A {
public a = async function b() {
await b();
}
}

class A {
public a = async function b() {
~~~~~ [Functions marked async must contain an await or return statement.]
b();
}
}

class A {
public a = async () => {
await b();
}
}

class A {
public a = async () => {
~~~~~ [Functions marked async must contain an await or return statement.]
b();
}
}

class A {
public a = async () => 1;
}

async () => {
await a();
class A {
async b() {
~~~~~ [Functions marked async must contain an await or return statement.]
console.log(1);
}
}
};

async function a() {
let b = 1;
return b;
}

let a = async () => 1;

async function a() {
~~~~~ [Functions marked async must contain an await or return statement.]
let b = 1;
let a = () => {
return 1;
}
}

async function foo;
~~~~~ [Functions marked async must contain an await or return statement.]

function * foo() {
return 1;
}

[0]: Functions marked async must contain an await or return statement.
5 changes: 5 additions & 0 deletions test/rules/no-async-without-await/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"no-async-without-await": true
}
}