Skip to content

Commit

Permalink
promise-function-async: add options to specify function types to chec…
Browse files Browse the repository at this point in the history
…k. (palantir#3807)

This is useful if you only want your function and method declarations
to warn, or in other words, one doesn't want each arrow function that
is passed to Promise#then() or Promise#catch() to be async.
  • Loading branch information
blair authored and suchanlee committed Apr 7, 2018
1 parent 8e13390 commit 375de6d
Showing 1 changed file with 69 additions and 17 deletions.
86 changes: 69 additions & 17 deletions src/rules/promiseFunctionAsyncRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,32 @@ import { hasModifier } from "tsutils";
import * as ts from "typescript";
import * as Lint from "../index";

const OPTION_FUNCTION_DECLARATION = "check-function-declaration";
const OPTION_FUNCTION_EXPRESSION = "check-function-expression";
const OPTION_ARROW_FUNCTION = "check-arrow-function";
const OPTION_METHOD_DECLARATION = "check-method-declaration";

const KIND_FOR_OPTION: { [arg: string]: number } = {
[OPTION_FUNCTION_DECLARATION]: ts.SyntaxKind.FunctionDeclaration,
[OPTION_FUNCTION_EXPRESSION]: ts.SyntaxKind.FunctionExpression,
[OPTION_ARROW_FUNCTION]: ts.SyntaxKind.ArrowFunction,
[OPTION_METHOD_DECLARATION]: ts.SyntaxKind.MethodDeclaration,
};

type EnabledSyntaxKinds = ReadonlySet<number>;

function parseOptions(ruleArguments: string[]): EnabledSyntaxKinds {
if (ruleArguments.length === 0) {
ruleArguments = Object.keys(KIND_FOR_OPTION);
}

const enabledKinds = new Set<number>();
for (const arg of ruleArguments) {
enabledKinds.add(KIND_FOR_OPTION[arg]);
}
return enabledKinds;
}

export class Rule extends Lint.Rules.TypedRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
Expand All @@ -29,10 +55,32 @@ export class Rule extends Lint.Rules.TypedRule {
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.
If no optional arguments are provided then all function types are checked,
otherwise the specific function types are checked:
* \`"${OPTION_FUNCTION_DECLARATION}"\` check function declarations.
* \`"${OPTION_FUNCTION_EXPRESSION}"\` check function expressions.
* \`"${OPTION_ARROW_FUNCTION}"\` check arrow functions.
* \`"${OPTION_METHOD_DECLARATION}"\` check method declarations.
`,
optionsDescription: "Not configurable.",
options: null,
optionExamples: [true],
options: {
type: "array",
items: {
type: "string",
enum: [
OPTION_FUNCTION_DECLARATION,
OPTION_FUNCTION_EXPRESSION,
OPTION_ARROW_FUNCTION,
OPTION_METHOD_DECLARATION,
],
},
minLength: 0,
maxLength: 4,
},
optionExamples: [true,
[true, OPTION_FUNCTION_DECLARATION, OPTION_METHOD_DECLARATION]],
type: "typescript",
typescriptOnly: false,
requiresTypeInfo: true,
Expand All @@ -42,24 +90,28 @@ export class Rule extends Lint.Rules.TypedRule {
public static FAILURE_STRING = "functions that return promises must be async";

public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker());
return this.applyWithFunction(sourceFile, walk, parseOptions(this.ruleArguments), program.getTypeChecker());
}
}

function walk(ctx: Lint.WalkContext<void>, tc: ts.TypeChecker) {
return ts.forEachChild(ctx.sourceFile, function cb(node): void {
switch (node.kind) {
case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.FunctionDeclaration:
if ((node as ts.FunctionLikeDeclaration).body === undefined) {
break;
}
// falls through
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
if (!hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword) && returnsPromise(node as ts.FunctionLikeDeclaration, tc)) {
ctx.addFailure(node.getStart(ctx.sourceFile), (node as ts.FunctionLikeDeclaration).body!.pos, Rule.FAILURE_STRING);
}
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)) {
switch (node.kind) {
case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.FunctionDeclaration:
if ((node as ts.FunctionLikeDeclaration).body === undefined) {
break;
}
// falls through
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
if (!hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword)
&& returnsPromise(node as ts.FunctionLikeDeclaration, tc)) {
ctx.addFailure(node.getStart(sourceFile), (node as ts.FunctionLikeDeclaration).body!.pos, Rule.FAILURE_STRING);
}
}
}
return ts.forEachChild(node, cb);
});
Expand Down

0 comments on commit 375de6d

Please sign in to comment.