diff --git a/src/rules/noFloatingPromisesRule.ts b/src/rules/noFloatingPromisesRule.ts new file mode 100644 index 00000000000..1e09445675d --- /dev/null +++ b/src/rules/noFloatingPromisesRule.ts @@ -0,0 +1,77 @@ +/** + * @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 "../lint"; + +export class Rule extends Lint.Rules.TypedRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "no-floating-promises", + description: "Promises returned by functions must be handled appropriately.", + optionsDescription: "Not configurable.", + options: null, + optionExamples: ["true"], + type: "functionality", + requiresTypeInfo: true, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING = "Promises must be handled appropriately"; + + public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + return this.applyWithWalker(new NoFloatingPromisesWalker(sourceFile, this.getOptions(), program)); + } +} + +class NoFloatingPromisesWalker extends Lint.ProgramAwareRuleWalker { + private static allowedParentKinds = { + [ts.SyntaxKind.CallExpression]: true, + [ts.SyntaxKind.VariableDeclaration]: true + }; + + public visitCallExpression(node: ts.CallExpression): void { + this.checkCallExpression(node); + super.visitCallExpression(node); + } + + private checkCallExpression(node: ts.CallExpression) { + if (this.kindCanContainPromise(node.parent.kind)) { + return; + } + + const typeChecker = this.getTypeChecker(); + const type = typeChecker.getTypeAtLocation(node); + + if (this.symbolIsPromise(type.symbol)) { + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + } + } + + private symbolIsPromise(symbol: ts.Symbol) { + if (!symbol) { + return false; + } + + return symbol.name === "Promise"; + } + + private kindCanContainPromise(kind: ts.SyntaxKind) { + return !!NoFloatingPromisesWalker.allowedParentKinds[kind]; + } +} diff --git a/test/rules/no-floating-promises/test.ts.lint b/test/rules/no-floating-promises/test.ts.lint new file mode 100644 index 00000000000..cc488a69dcd --- /dev/null +++ b/test/rules/no-floating-promises/test.ts.lint @@ -0,0 +1,58 @@ +class Promise { } + +function returnsPromiseFunction() { + return new Promise(); +} + +const returnsPromiseVariable = () => new Promise(); + +class ReturnsPromiseClass { + returnsPromiseMemberFunction() { + return new Promise(); + } + + returnsPromiseMemberVariable = () => new Promise(); +} + +let a = returnsPromiseFunction(); +let b = returnsPromiseVariable(); +let c = new ReturnsPromiseClass().returnsPromiseMemberFunction(); +let d = new ReturnsPromiseClass().returnsPromiseMemberVariable(); + +returnsPromiseFunction(); +~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +returnsPromiseVariable(); +~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +new ReturnsPromiseClass().returnsPromiseMemberFunction(); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] +new ReturnsPromiseClass().returnsPromiseMemberVariable(); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +(function () { + let a = returnsPromiseFunction(); + + returnsPromiseFunction(); + ~~~~~~~~~~~~~~~~~~~~~~~~ [0] +})(); + +(() => { + let a = returnsPromiseFunction(); + + returnsPromiseFunction(); + ~~~~~~~~~~~~~~~~~~~~~~~~ [0] +})(); + +[].push(returnsPromiseFunction()); + +while (returnsPromiseFunction()); + ~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +for (let i = 0; i < returnsPromiseFunction(); i= 1); + ~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +for (let i in returnsPromiseFunction()); + ~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +[0]: Promises must be handled appropriately diff --git a/test/rules/no-floating-promises/tslint.json b/test/rules/no-floating-promises/tslint.json new file mode 100644 index 00000000000..2f3715da4c5 --- /dev/null +++ b/test/rules/no-floating-promises/tslint.json @@ -0,0 +1,8 @@ +{ + "linterOptions": { + "typeCheck": true + }, + "rules": { + "no-floating-promises": true + } +} \ No newline at end of file