Skip to content

Commit

Permalink
Allowed parenthesis bodies for one-line promise-function-async functi…
Browse files Browse the repository at this point in the history
…ons (palantir#4765)

* Allowed parenthesis bodies for one-line promise-function-async functions

Fixes palantir#4757.

* Used tsutils intead of ts for new parenthesized expression method

Gets me every time...
  • Loading branch information
Josh Goldberg authored Jul 27, 2019
1 parent 9924e7a commit af24731
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 23 deletions.
53 changes: 30 additions & 23 deletions src/rules/promiseFunctionAsyncRule.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit af24731

Please sign in to comment.