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

Allowed parenthesis bodies for one-line promise-function-async functions #4765

Merged
merged 3 commits into from Jul 27, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 30 additions & 23 deletions src/rules/promiseFunctionAsyncRule.ts
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

import { hasModifier, isCallExpression } from "tsutils";
import * as tsutils from "tsutils";
import * as ts from "typescript";

import * as Lint from "../index";
Expand Down Expand Up @@ -102,34 +102,41 @@ export class Rule extends Lint.Rules.TypedRule {
function walk(ctx: Lint.WalkContext<EnabledSyntaxKinds>, tc: ts.TypeChecker) {
const { sourceFile, options } = ctx;
return ts.forEachChild(sourceFile, function cb(node): void {
if (options.has(node.kind)) {
const declaration = node as ts.FunctionLikeDeclaration;
switch (node.kind) {
case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.FunctionDeclaration:
if (declaration.body === undefined) {
break;
}
// falls through
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
if (
!hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword) &&
returnsPromise(declaration, tc) &&
!isCallExpression(declaration.body as ts.Expression)
) {
ctx.addFailure(
node.getStart(sourceFile),
(node as ts.FunctionLikeDeclaration).body!.pos,
Rule.FAILURE_STRING,
);
}
if (options.has(node.kind) && isFunctionLikeWithBody(node)) {
if (
!tsutils.hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword) &&
!isCallExpressionBody(node.body) &&
returnsPromise(node, tc)
) {
ctx.addFailure(node.getStart(sourceFile), node.body.pos, Rule.FAILURE_STRING);
}
}
return ts.forEachChild(node, cb);
});
}

function isFunctionLikeWithBody(
node: ts.Node,
): node is ts.FunctionLikeDeclaration & { body: ts.Node } {
switch (node.kind) {
case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.FunctionDeclaration:
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
return (node as ts.FunctionLikeDeclaration).body !== undefined;
}

return false;
}

function isCallExpressionBody(body: ts.Node) {
while (tsutils.isParenthesizedExpression(body)) {
body = body.expression;
}

return tsutils.isCallExpression(body);
}

function returnsPromise(node: ts.FunctionLikeDeclaration, tc: ts.TypeChecker): boolean {
const type = tc.getReturnTypeOfSignature(tc.getTypeAtLocation(node).getCallSignatures()[0]);
return type.symbol !== undefined && type.symbol.name === "Promise";
Expand Down
4 changes: 4 additions & 0 deletions test/rules/promise-function-async/test.ts.lint
Expand Up @@ -38,6 +38,8 @@ const asyncPromiseArrowFunctionB = async () => new Promise<void>();

// non-'async' non-'Promise'-returning arrow functions are allowed
const nonAsyncNonPromiseArrowFunction = (n: number) => n;
const nonAsyncNonPromiseArrowFunctionParenthesisOne = (n: number) => (n);
const nonAsyncNonPromiseArrowFunctionParenthesisTwo = (n: number) => ((n));

class Test {
public nonAsyncPromiseMethodA(p: Promise<void>) {
Expand Down Expand Up @@ -65,6 +67,8 @@ class Test {

// single statement lamda functions that delegate to another promise-returning function are allowed
public delegatingMethod = () => this.asyncPromiseMethodB(1);
public delegatingMethodParenthesisOne = () => (this.asyncPromiseMethodB(1));
public delegatingMethodParenthesisTwo = () => ((this.asyncPromiseMethodB(1)));
}

[0]: functions that return promises must be async