Skip to content

Commit

Permalink
feat(rule): Add no-unsafe-takeuntil rule.
Browse files Browse the repository at this point in the history
  • Loading branch information
cartant committed May 26, 2018
1 parent 6f231ee commit 80e11e0
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 0 deletions.
110 changes: 110 additions & 0 deletions source/rules/rxjsNoUnsafeTakeuntilRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* @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
*/
/*tslint:disable:no-use-before-declare*/

import * as Lint from "tslint";
import * as ts from "typescript";
import * as tsutils from "tsutils";
import { couldBeType, isReferenceType } from "../support/util";

export class Rule extends Lint.Rules.TypedRule {

public static metadata: Lint.IRuleMetadata = {
description: "Disallows the application of operators after takeUntil.",
options: null,
optionsDescription: "Not configurable.",
requiresTypeInfo: true,
ruleName: "rxjs-no-unsafe-takeuntil",
type: "functionality",
typescriptOnly: true
};

public static FAILURE_STRING = "Applying operators after takeUntil is forbidden";

public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {

return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program));
}
}

class Walker extends Lint.ProgramAwareRuleWalker {

protected visitCallExpression(node: ts.CallExpression): void {

const { expression: propertyAccessExpression } = node;
if (tsutils.isPropertyAccessExpression(propertyAccessExpression)) {

const { expression: identifier } = propertyAccessExpression;
if (tsutils.isIdentifier(identifier)) {

const propertyName = propertyAccessExpression.name.getText();
const identifierText = identifier.getText();
const typeChecker = this.getTypeChecker();
const type = typeChecker.getTypeAtLocation(identifier);

if (isReferenceType(type) && couldBeType(type.target, "Observable")) {

switch (propertyName) {
case "takeUntil":
this.walkPatchedOperators(node, propertyAccessExpression.name);
break;
case "pipe":
this.walkPipedOperators(node);
break;
default:
break;
}
}
}
}

super.visitCallExpression(node);
}

private walkPatchedOperators(node: ts.Node, identifier: ts.Identifier): void {

let name: ts.Identifier | undefined = undefined;
for (let parent = node.parent; parent; parent = parent.parent) {
if (tsutils.isCallExpression(parent)) {
if (name) {
if (name.getText() === "pipe") {
this.walkPipedOperators(parent, identifier);
} else {
const typeChecker = this.getTypeChecker();
const type = typeChecker.getTypeAtLocation(parent);
if (isReferenceType(type) && couldBeType(type.target, "Observable")) {
this.addFailureAtNode(identifier, Rule.FAILURE_STRING);
return;
}
}
}
} else if (tsutils.isPropertyAccessExpression(parent)) {
name = parent.name;
} else {
break;
}
}
}

private walkPipedOperators(node: ts.CallExpression, identifier: ts.Identifier | null = null): void {

if (identifier) {
if (node.arguments.length > 0) {
this.addFailureAtNode(identifier, Rule.FAILURE_STRING);
}
} else {
node.arguments.forEach((arg, index) => {
if (tsutils.isCallExpression(arg)) {
const { expression } = arg;
if (tsutils.isIdentifier(expression) && (expression.getText() === "takeUntil")) {
if (index < (node.arguments.length - 1)) {
this.addFailureAtNode(expression, Rule.FAILURE_STRING);
}
}
}
});
}
}
}
26 changes: 26 additions & 0 deletions test/v5/fixtures/no-unsafe-takeuntil/default/fixture.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Observable } from "rxjs/Observable";
import { combineLatest } from "rxjs/observable/combineLatest";
import { of } from "rxjs/observable/of";
import { switchMap, takeUntil } from "rxjs/operators";

import "rxjs/add/operator/combineLatest";
import "rxjs/add/operator/switchMap";
import "rxjs/add/operator/takeUntil";

const a = of("a");
const b = of("b");
const c = of("c");
const d = of("d");

const e = a.takeUntil(d).switchMap(_ => b).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]
const f = a.takeUntil(d).combineLatest(b, c).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]
const g = a.takeUntil(d).pipe(s => combineLatest(s, b, c)).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]
const h = a.pipe(takeUntil(d), switchMap(_ => b)).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]
const i = a.pipe(takeUntil(d), s => combineLatest(s, b, c)).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]

[no-unsafe-takeuntil]: Applying operators after takeUntil is forbidden
13 changes: 13 additions & 0 deletions test/v5/fixtures/no-unsafe-takeuntil/default/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"baseUrl": ".",
"lib": ["es2015"],
"noEmit": true,
"paths": {
"rxjs": ["../../node_modules/rxjs"]
},
"skipLibCheck": true,
"target": "es5"
},
"include": ["fixture.ts"]
}
8 changes: 8 additions & 0 deletions test/v5/fixtures/no-unsafe-takeuntil/default/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"defaultSeverity": "error",
"jsRules": {},
"rules": {
"rxjs-no-unsafe-takeuntil": { "severity": "error" }
},
"rulesDirectory": "../../../../../build/rules"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Observable } from "rxjs/Observable";
import { combineLatest } from "rxjs/observable/combineLatest";
import { of } from "rxjs/observable/of";
import { switchMap, takeUntil } from "rxjs/operators";

import "rxjs/add/operator/combineLatest";
import "rxjs/add/operator/switchMap";
import "rxjs/add/operator/takeUntil";

const a = of("a");
const b = of("b");
const c = of("c");
const d = of("d");

const e = a.takeUntil(d).switchMap(_ => b).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]
const f = a.takeUntil(d).combineLatest(b, c).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]
const g = a.takeUntil(d).pipe(s => combineLatest(s, b, c)).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]
const h = a.pipe(takeUntil(d), switchMap(_ => b)).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]
const i = a.pipe(takeUntil(d), s => combineLatest(s, b, c)).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]

[no-unsafe-takeuntil]: Applying operators after takeUntil is forbidden
13 changes: 13 additions & 0 deletions test/v6-compat/fixtures/no-unsafe-takeuntil/default/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"baseUrl": ".",
"lib": ["es2015"],
"noEmit": true,
"paths": {
"rxjs": ["../../node_modules/rxjs"]
},
"skipLibCheck": true,
"target": "es5"
},
"include": ["fixture.ts"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"defaultSeverity": "error",
"jsRules": {},
"rules": {
"rxjs-no-unsafe-takeuntil": { "severity": "error" }
},
"rulesDirectory": "../../../../../build/rules"
}
14 changes: 14 additions & 0 deletions test/v6/fixtures/no-unsafe-takeuntil/default/fixture.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { combineLatest, of } from "rxjs";
import { switchMap, takeUntil } from "rxjs/operators";

const a = of("a");
const b = of("b");
const c = of("c");
const d = of("d");

const e = a.pipe(takeUntil(d), switchMap(_ => b)).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]
const f = a.pipe(takeUntil(d), s => combineLatest(s, b, c)).subscribe();
~~~~~~~~~ [no-unsafe-takeuntil]

[no-unsafe-takeuntil]: Applying operators after takeUntil is forbidden
13 changes: 13 additions & 0 deletions test/v6/fixtures/no-unsafe-takeuntil/default/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"baseUrl": ".",
"lib": ["es2015"],
"noEmit": true,
"paths": {
"rxjs": ["../../node_modules/rxjs"]
},
"skipLibCheck": true,
"target": "es5"
},
"include": ["fixture.ts"]
}
8 changes: 8 additions & 0 deletions test/v6/fixtures/no-unsafe-takeuntil/default/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"defaultSeverity": "error",
"jsRules": {},
"rules": {
"rxjs-no-unsafe-takeuntil": { "severity": "error" }
},
"rulesDirectory": "../../../../../build/rules"
}

0 comments on commit 80e11e0

Please sign in to comment.