diff --git a/package.json b/package.json index d6e42a8f..09048961 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "test": "yarn run lint && yarn run test:build && yarn run test:mocha && yarn run test:tslint-v5 && yarn run test:tslint-v6 && yarn run test:tslint-v6-compat", "test:build": "yarn run test:clean && tsc -p tsconfig.json", "test:clean": "rimraf build", - "test:debug": "tslint --test ./test/v6/fixtures/no-exposed-subjects/**/tslint.json", + "test:debug": "tslint --test ./test/v6/fixtures/no-async-subscribe/**/tslint.json", "test:issues": "yarn run test:clean && tsc -p tsconfig.json && tslint --test ./test/v6/fixtures/issues/**/tslint.json", "test:mocha": "mocha build/**/*-spec.js", "test:tslint-v5": "yarn --cwd ./test/v5 install && yarn --cwd ./test/v5 upgrade && tslint --test ./test/v5/fixtures/**/tslint.json", diff --git a/source/rules/rxjsNoAsyncSubscribeRule.ts b/source/rules/rxjsNoAsyncSubscribeRule.ts new file mode 100644 index 00000000..d74f8201 --- /dev/null +++ b/source/rules/rxjsNoAsyncSubscribeRule.ts @@ -0,0 +1,65 @@ +/** + * @license Use of this source code is governed by an MIT-style license that + * can be found in the LICENSE file at https://github.com/cartant/rxjs-tslint-rules + */ + +import * as Lint from "tslint"; +import * as ts from "typescript"; +import * as tsutils from "tsutils"; +import { tsquery } from "@phenomnomnominal/tsquery"; +import { couldBeType } from "../support/util"; + +export class Rule extends Lint.Rules.TypedRule { + + public static metadata: Lint.IRuleMetadata = { + description: "Disallows async functions to subscribe.", + options: null, + optionsDescription: "Not configurable.", + requiresTypeInfo: false, + ruleName: "rxjs-no-async-subscribe", + type: "functionality", + typescriptOnly: false + }; + + public static FAILURE_STRING = "Passing async functions to subscribe is forbidden"; + + public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + + const failures: Lint.RuleFailure[] = []; + const typeChecker = program.getTypeChecker(); + + const callExpressions = tsquery( + sourceFile, + `CallExpression[expression.name.text="subscribe"]` + ); + callExpressions.forEach(node => { + const callExpression = node as ts.CallExpression; + if (tsutils.isPropertyAccessExpression(callExpression.expression)) { + const propertyAccessExpression = callExpression.expression; + const { arguments: args } = callExpression; + let observable: boolean | undefined = undefined; + args.forEach(arg => { + if (tsutils.isArrowFunction(arg) || tsutils.isFunctionExpression(arg)) { + const modifier = tsutils.getModifier(arg, ts.SyntaxKind.AsyncKeyword); + if (modifier) { + if (observable === undefined) { + const type = typeChecker.getTypeAtLocation(propertyAccessExpression.expression); + observable = couldBeType(type, "Observable"); + } + if (observable) { + failures.push(new Lint.RuleFailure( + sourceFile, + modifier.getStart(), + modifier.getStart() + modifier.getWidth(), + Rule.FAILURE_STRING, + this.ruleName + )); + } + } + } + }); + } + }); + return failures; + } +} diff --git a/test/v6/fixtures/no-async-subscribe/default/fixture.ts.lint b/test/v6/fixtures/no-async-subscribe/default/fixture.ts.lint new file mode 100644 index 00000000..9dc05618 --- /dev/null +++ b/test/v6/fixtures/no-async-subscribe/default/fixture.ts.lint @@ -0,0 +1,9 @@ +import { of } from "rxjs"; + +of("a").subscribe(async () => { + ~~~~~ [no-async-subscribe] + return await "a"; +}); +of("b").subscribe(() => {}); + +[no-async-subscribe]: Passing async functions to subscribe is forbidden diff --git a/test/v6/fixtures/no-async-subscribe/default/tsconfig.json b/test/v6/fixtures/no-async-subscribe/default/tsconfig.json new file mode 100644 index 00000000..690be78e --- /dev/null +++ b/test/v6/fixtures/no-async-subscribe/default/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "lib": ["es2015"], + "noEmit": true, + "paths": { + "rxjs": ["../../node_modules/rxjs"] + }, + "skipLibCheck": true, + "target": "es5" + }, + "include": ["fixture.ts"] +} diff --git a/test/v6/fixtures/no-async-subscribe/default/tslint.json b/test/v6/fixtures/no-async-subscribe/default/tslint.json new file mode 100644 index 00000000..b6b6083f --- /dev/null +++ b/test/v6/fixtures/no-async-subscribe/default/tslint.json @@ -0,0 +1,8 @@ +{ + "defaultSeverity": "error", + "jsRules": {}, + "rules": { + "rxjs-no-async-subscribe": { "severity": "error" } + }, + "rulesDirectory": "../../../../../build/rules" +}