Skip to content

Commit

Permalink
feat: Add no-unsafe-subject-next rule.
Browse files Browse the repository at this point in the history
  • Loading branch information
cartant committed Apr 24, 2020
1 parent e08b41d commit 4af5d45
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,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/issues/115/tslint.json\"",
"test:debug": "tslint --test \"./test/v6/fixtures/no-unsafe-subject-next/**/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\"",
Expand Down
79 changes: 79 additions & 0 deletions source/rules/rxjsNoUnsafeSubjectNextRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* @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 { tsquery } from "@phenomnomnominal/tsquery";
import * as Lint from "tslint";
import * as tsutils from "tsutils";
import * as ts from "typescript";
import * as peer from "../support/peer";
import { couldBeType, isReferenceType, isUnionType } from "../support/util";

export class Rule extends Lint.Rules.TypedRule {
public static metadata: Lint.IRuleMetadata = {
deprecationMessage: peer.v5 ? peer.v5NotSupportedMessage : undefined,
description: "Disallows unsafe optional `next` calls.",
options: null,
optionsDescription: "Not configurable.",
requiresTypeInfo: true,
ruleName: "rxjs-no-unsafe-subject-next",
type: "functionality",
typescriptOnly: true
};

public static FAILURE_STRING = "Unsafe optional next calls are 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="next"]`
);
callExpressions.forEach(node => {
const callExpression = node as ts.CallExpression;
const { arguments: args } = callExpression;
if (args.length === 0) {
if (tsutils.isPropertyAccessExpression(callExpression.expression)) {
const { expression, name } = callExpression.expression;
const type = typeChecker.getTypeAtLocation(expression);
if (isReferenceType(type) && couldBeType(type, "Subject")) {
const [typeArg] = typeChecker.getTypeArguments(type);
if (tsutils.isTypeFlagSet(typeArg, ts.TypeFlags.Any)) {
return;
}
if (tsutils.isTypeFlagSet(typeArg, ts.TypeFlags.Unknown)) {
return;
}
if (tsutils.isTypeFlagSet(typeArg, ts.TypeFlags.Void)) {
return;
}
if (
isUnionType(typeArg) &&
typeArg.types.some(t =>
tsutils.isTypeFlagSet(t, ts.TypeFlags.Void)
)
) {
return;
}
failures.push(
new Lint.RuleFailure(
sourceFile,
name.getStart(),
name.getStart() + name.getWidth(),
Rule.FAILURE_STRING,
this.ruleName
)
);
}
}
}
});
return failures;
}
}
31 changes: 31 additions & 0 deletions test/v6/fixtures/no-unsafe-subject-next/default/fixture.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ReplaySubject, Subject } from "rxjs";

const a = new Subject<number>();
a.next(42);
a.next();
~~~~ [no-unsafe-subject-next]

const b = new Subject<void>();
b.next();

const c = new ReplaySubject<number>();
c.next(42);
c.next();
~~~~ [no-unsafe-subject-next]

const d = new Subject<number | void>();
d.next(42);
d.next();

const e = new Subject<any>();
e.next(42);
e.next();

const f = new Subject<unknown>();
f.next(42);
f.next();

const g = new Subject<void>();
g.next();

[no-unsafe-subject-next]: Unsafe optional next calls are forbidden
13 changes: 13 additions & 0 deletions test/v6/fixtures/no-unsafe-subject-next/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-subject-next/default/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"defaultSeverity": "error",
"jsRules": {},
"rules": {
"rxjs-no-unsafe-subject-next": { "severity": "error" }
},
"rulesDirectory": "../../../../../build/rules"
}

0 comments on commit 4af5d45

Please sign in to comment.