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

Commit 0807692

Browse files
eranshabiJosh Goldberg
authored and
Josh Goldberg
committed
[New Rule] add noAsyncWithoutAwait rule (#3945)
* add noAsyncWithoutAwait rule * fix lint * code review updates * allow return as well as await * remove unneeded lint ignore * improve rationale & fix performance issue * fixes according to review * finish fixing according to review * Initial feedback cleanups * Refactored to walk function
1 parent d7163e1 commit 0807692

File tree

5 files changed

+319
-0
lines changed

5 files changed

+319
-0
lines changed

src/configs/all.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export const rules = {
107107
// "import-blacklist": no sensible default
108108
"label-position": true,
109109
"no-arg": true,
110+
"no-async-without-await": true,
110111
"no-bitwise": true,
111112
"no-conditional-assignment": true,
112113
"no-console": true,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* @license
3+
* Copyright 2018 Palantir Technologies, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import * as Lint from "../../index";
19+
20+
export const codeExamples = [
21+
{
22+
config: Lint.Utils.dedent`
23+
"rules": { "no-async-without-await": true }
24+
`,
25+
description: "Do not use the async keyword if it is not needed",
26+
fail: Lint.Utils.dedent`
27+
async function f() {
28+
fetch();
29+
}
30+
31+
async function f() {
32+
async function g() {
33+
await h();
34+
}
35+
}
36+
`,
37+
pass: Lint.Utils.dedent`
38+
async function f() {
39+
await fetch();
40+
}
41+
42+
const f = async () => {
43+
await fetch();
44+
};
45+
46+
const f = async () => {
47+
return 'value';
48+
};
49+
`,
50+
},
51+
];

src/rules/noAsyncWithoutAwaitRule.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/**
2+
* @license
3+
* Copyright 2018 Palantir Technologies, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import * as tsutils from "tsutils";
19+
import * as ts from "typescript";
20+
21+
import * as Lint from "../index";
22+
23+
import { codeExamples } from "./code-examples/noAsyncWithoutAwait.examples";
24+
25+
type FunctionNodeType =
26+
| ts.ArrowFunction
27+
| ts.FunctionDeclaration
28+
| ts.MethodDeclaration
29+
| ts.FunctionExpression;
30+
31+
export class Rule extends Lint.Rules.AbstractRule {
32+
public static FAILURE_STRING =
33+
"Functions marked async must contain an await or return statement.";
34+
35+
public static metadata: Lint.IRuleMetadata = {
36+
codeExamples,
37+
description: Rule.FAILURE_STRING,
38+
hasFix: false,
39+
optionExamples: [true],
40+
options: null,
41+
optionsDescription: "Not configurable.",
42+
/* tslint:disable:max-line-length */
43+
rationale: Lint.Utils.dedent`
44+
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.
45+
Often the function can be synchronous and the \`async\` keyword is there by mistake.
46+
Return statements are allowed as sometimes it is desirable to wrap the returned value in a Promise.`,
47+
/* tslint:enable:max-line-length */
48+
ruleName: "no-async-without-await",
49+
type: "functionality",
50+
typescriptOnly: false,
51+
};
52+
53+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
54+
return this.applyWithFunction(sourceFile, walk);
55+
}
56+
}
57+
58+
function walk(context: Lint.WalkContext) {
59+
const reportFailureIfAsyncFunction = (node: FunctionNodeType) => {
60+
const asyncModifier = getAsyncModifier(node);
61+
if (asyncModifier !== undefined) {
62+
context.addFailureAt(
63+
asyncModifier.getStart(),
64+
asyncModifier.getEnd() - asyncModifier.getStart(),
65+
Rule.FAILURE_STRING,
66+
);
67+
}
68+
};
69+
70+
const addFailureIfAsyncFunctionHasNoAwait = (node: FunctionNodeType) => {
71+
if (node.body === undefined) {
72+
reportFailureIfAsyncFunction(node);
73+
return;
74+
}
75+
76+
if (
77+
!isShortArrowReturn(node) &&
78+
!functionBlockHasAwait(node.body) &&
79+
!functionBlockHasReturn(node.body)
80+
) {
81+
reportFailureIfAsyncFunction(node);
82+
}
83+
};
84+
85+
return ts.forEachChild(context.sourceFile, function visitNode(node): void {
86+
if (
87+
tsutils.isArrowFunction(node) ||
88+
tsutils.isFunctionDeclaration(node) ||
89+
tsutils.isFunctionExpression(node) ||
90+
tsutils.isMethodDeclaration(node)
91+
) {
92+
addFailureIfAsyncFunctionHasNoAwait(node);
93+
}
94+
95+
return ts.forEachChild(node, visitNode);
96+
});
97+
}
98+
99+
const getAsyncModifier = (node: ts.Node) => {
100+
if (node.modifiers !== undefined) {
101+
return node.modifiers.find(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword);
102+
}
103+
104+
return undefined;
105+
};
106+
107+
const isReturn = (node: ts.Node): boolean => node.kind === ts.SyntaxKind.ReturnKeyword;
108+
109+
const functionBlockHasAwait = (node: ts.Node): boolean => {
110+
if (tsutils.isAwaitExpression(node)) {
111+
return true;
112+
}
113+
114+
if (
115+
node.kind === ts.SyntaxKind.ArrowFunction ||
116+
node.kind === ts.SyntaxKind.FunctionDeclaration
117+
) {
118+
return false;
119+
}
120+
121+
return node.getChildren().some(functionBlockHasAwait);
122+
};
123+
124+
const functionBlockHasReturn = (node: ts.Node): boolean => {
125+
if (isReturn(node)) {
126+
return true;
127+
}
128+
129+
if (
130+
node.kind === ts.SyntaxKind.ArrowFunction ||
131+
node.kind === ts.SyntaxKind.FunctionDeclaration
132+
) {
133+
return false;
134+
}
135+
136+
return node.getChildren().some(functionBlockHasReturn);
137+
};
138+
139+
const isShortArrowReturn = (node: FunctionNodeType) =>
140+
node.kind === ts.SyntaxKind.ArrowFunction && node.body.kind !== ts.SyntaxKind.Block;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
async function a(){
2+
~~~~~ [0]
3+
let b = 1;
4+
console.log(b);
5+
}
6+
7+
async function a(){
8+
let b = 1;
9+
await console.log(b);
10+
}
11+
12+
async function a(){
13+
let b = 1;
14+
console.log(await b());
15+
}
16+
17+
async function a(){
18+
~~~~~ [0]
19+
let b = 1;
20+
let c = async () => {
21+
await fetch();
22+
};
23+
}
24+
25+
async function a(){
26+
~~~~~ [Functions marked async must contain an await or return statement.]
27+
let b = 1;
28+
async function f() {
29+
await fetch();
30+
};
31+
}
32+
33+
function a(){
34+
let b = 1;
35+
async function f() {
36+
~~~~~ [Functions marked async must contain an await or return statement.]
37+
fetch();
38+
};
39+
}
40+
41+
const a = async () => {
42+
~~~~~ [Functions marked async must contain an await or return statement.]
43+
let b = 1;
44+
console.log(b);
45+
}
46+
47+
class A {
48+
async b() {
49+
~~~~~ [Functions marked async must contain an await or return statement.]
50+
console.log(1);
51+
}
52+
}
53+
54+
class A {
55+
async b() {
56+
await b();
57+
}
58+
}
59+
60+
class A {
61+
public a = async function b() {
62+
await b();
63+
}
64+
}
65+
66+
class A {
67+
public a = async function b() {
68+
~~~~~ [Functions marked async must contain an await or return statement.]
69+
b();
70+
}
71+
}
72+
73+
class A {
74+
public a = async () => {
75+
await b();
76+
}
77+
}
78+
79+
class A {
80+
public a = async () => {
81+
~~~~~ [Functions marked async must contain an await or return statement.]
82+
b();
83+
}
84+
}
85+
86+
class A {
87+
public a = async () => 1;
88+
}
89+
90+
async () => {
91+
await a();
92+
class A {
93+
async b() {
94+
~~~~~ [Functions marked async must contain an await or return statement.]
95+
console.log(1);
96+
}
97+
}
98+
};
99+
100+
async function a() {
101+
let b = 1;
102+
return b;
103+
}
104+
105+
let a = async () => 1;
106+
107+
async function a() {
108+
~~~~~ [Functions marked async must contain an await or return statement.]
109+
let b = 1;
110+
let a = () => {
111+
return 1;
112+
}
113+
}
114+
115+
async function foo;
116+
~~~~~ [Functions marked async must contain an await or return statement.]
117+
118+
function * foo() {
119+
return 1;
120+
}
121+
122+
[0]: Functions marked async must contain an await or return statement.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"rules": {
3+
"no-async-without-await": true
4+
}
5+
}

0 commit comments

Comments
 (0)