From d053915329a8b9d43767b1188432d01ed26a2c99 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 15 Jun 2019 18:39:53 -0400 Subject: [PATCH 1/2] Allowed parenthesis bodies for one-line promise-function-async functions Fixes #4757. --- src/rules/promiseFunctionAsyncRule.ts | 51 +++++++++++-------- .../rules/promise-function-async/test.ts.lint | 4 ++ 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/rules/promiseFunctionAsyncRule.ts b/src/rules/promiseFunctionAsyncRule.ts index 60f1fa9a607..03c8af8dfc6 100644 --- a/src/rules/promiseFunctionAsyncRule.ts +++ b/src/rules/promiseFunctionAsyncRule.ts @@ -102,34 +102,41 @@ export class Rule extends Lint.Rules.TypedRule { function walk(ctx: Lint.WalkContext, 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 ( + !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 (ts.isParenthesizedExpression(body)) { + body = body.expression; + } + + return 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"; diff --git a/test/rules/promise-function-async/test.ts.lint b/test/rules/promise-function-async/test.ts.lint index f3bc9e6bec6..c85dc8c7139 100644 --- a/test/rules/promise-function-async/test.ts.lint +++ b/test/rules/promise-function-async/test.ts.lint @@ -38,6 +38,8 @@ const asyncPromiseArrowFunctionB = async () => new Promise(); // 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) { @@ -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 From b2940301f91a199878a14a3d3693e1e9e296a97a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 16 Jul 2019 11:24:20 -0700 Subject: [PATCH 2/2] Used tsutils intead of ts for new parenthesized expression method Gets me every time... --- src/rules/promiseFunctionAsyncRule.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rules/promiseFunctionAsyncRule.ts b/src/rules/promiseFunctionAsyncRule.ts index 03c8af8dfc6..00129f28fee 100644 --- a/src/rules/promiseFunctionAsyncRule.ts +++ b/src/rules/promiseFunctionAsyncRule.ts @@ -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"; @@ -104,7 +104,7 @@ function walk(ctx: Lint.WalkContext, tc: ts.TypeChecker) { return ts.forEachChild(sourceFile, function cb(node): void { if (options.has(node.kind) && isFunctionLikeWithBody(node)) { if ( - !hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword) && + !tsutils.hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword) && !isCallExpressionBody(node.body) && returnsPromise(node, tc) ) { @@ -130,11 +130,11 @@ function isFunctionLikeWithBody( } function isCallExpressionBody(body: ts.Node) { - while (ts.isParenthesizedExpression(body)) { + while (tsutils.isParenthesizedExpression(body)) { body = body.expression; } - return isCallExpression(body); + return tsutils.isCallExpression(body); } function returnsPromise(node: ts.FunctionLikeDeclaration, tc: ts.TypeChecker): boolean {