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

Commit

Permalink
Rule: Promise-returning methods must be async (#1779)
Browse files Browse the repository at this point in the history
  • Loading branch information
buu700 authored and nchen63 committed Nov 27, 2016
1 parent f1df7b7 commit 5c7cf4a
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 0 deletions.
89 changes: 89 additions & 0 deletions src/rules/promiseFunctionAsyncRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @license
* Copyright 2016 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 ts from "typescript";
import * as Lint from "../index";

export class Rule extends Lint.Rules.TypedRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "promise-function-async",
description: "Requires any function or method that returns a promise to be marked async.",
rationale: Lint.Utils.dedent`
Ensures that each function is only capable of 1) returning a rejected promise, or 2)
throwing an Error object. In contrast, non-\`async\` \`Promise\`-returning functions
are technically capable of either. This practice removes a requirement for consuming
code to handle both cases.
`,
optionsDescription: "Not configurable.",
options: null,
optionExamples: ["true"],
type: "typescript",
typescriptOnly: false,
requiresTypeInfo: true,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING = "functions that return promises must be async";

public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
return this.applyWithWalker(new PromiseAsyncWalker(sourceFile, this.getOptions(), program));
}
}

class PromiseAsyncWalker extends Lint.ProgramAwareRuleWalker {
public visitArrowFunction(node: ts.ArrowFunction) {
this.test(node);
super.visitArrowFunction(node);
}

public visitFunctionDeclaration(node: ts.FunctionDeclaration) {
this.test(node);
super.visitFunctionDeclaration(node);
}

public visitFunctionExpression(node: ts.FunctionExpression) {
this.test(node);
super.visitFunctionExpression(node);
}

public visitMethodDeclaration(node: ts.MethodDeclaration) {
this.test(node);
super.visitMethodDeclaration(node);
}

private test(node: ts.SignatureDeclaration & { body?: ts.Node}) {
const tc = this.getTypeChecker();

const signature = tc.getTypeAtLocation(node).getCallSignatures()[0];
const returnType = tc.typeToString(tc.getReturnTypeOfSignature(signature));

const isAsync = Lint.hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword);
const isPromise = returnType.indexOf("Promise<") === 0;

const signatureEnd = node.body ?
node.body.getStart() - node.getStart() - 1 :
node.getWidth()
;

if (isAsync || !isPromise) {
return;
}

this.addFailure(this.createFailure(node.getStart(), signatureEnd, Rule.FAILURE_STRING));
}
}
67 changes: 67 additions & 0 deletions test/rules/promise-function-async/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
declare class Promise<T>{}

const nonAsyncPromiseFunctionExpressionA = function(p: Promise<void>) { return p; };
~~~~~~~~~~~~~~~~~~~~~~~~~~ [0]

const nonAsyncPromiseFunctionExpressionB = function() { return new Promise<void>(); };
~~~~~~~~~~ [0]

// 'async' 'Promise'-returning function expressions are allowed
const asyncPromiseFunctionExpressionA = async function(p: Promise<void>) { return p; };
const asyncPromiseFunctionExpressionB = async function() { return new Promise<void>(); };

// non-'async' non-'Promise'-returning function expressions are allowed
const nonAsyncNonPromiseFunctionExpression = function(n: number) { return n; };

function nonAsyncPromiseFunctionDeclarationA(p: Promise<void>) { return p; }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0]

function nonAsyncPromiseFunctionDeclarationB() { return new Promise<void>(); }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0]

// 'async' 'Promise'-returning function declarations are allowed
async function asyncPromiseFunctionDeclarationA(p: Promise<void>) { return p; }
async function asyncPromiseFunctionDeclarationB() { return new Promise<void>(); }

// non-'async' non-'Promise'-returning function declarations are allowed
function nonAsyncNonPromiseFunctionDeclaration(n: number) { return n; }

const nonAsyncPromiseArrowFunctionA = (p: Promise<void>) => p;
~~~~~~~~~~~~~~~~~~~~~ [0]

const nonAsyncPromiseArrowFunctionB = () => new Promise<void>();
~~~~~ [0]

// 'async' 'Promise'-returning arrow functions are allowed
const asyncPromiseArrowFunctionA = async (p: Promise<void>) => p;
const asyncPromiseArrowFunctionB = async () => new Promise<void>();

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

class Test {
public nonAsyncPromiseMethodA(p: Promise<void>) {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0]
return p;
}

public nonAsyncPromiseMethodB() {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0]
return new Promise<void>();
}

// 'async' 'Promise'-returning methods are allowed
public async asyncPromiseMethodA(p: Promise<void>) {
return p;
}
public async asyncPromiseMethodB() {
return new Promise<void>();
}

// non-'async' non-'Promise'-returning methods are allowed
public nonAsyncNonPromiseMethod(n: number) {
return n;
}
}

[0]: functions that return promises must be async
8 changes: 8 additions & 0 deletions test/rules/promise-function-async/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"linterOptions": {
"typeCheck": true
},
"rules": {
"promise-function-async": true
}
}

0 comments on commit 5c7cf4a

Please sign in to comment.