Skip to content

Commit

Permalink
Add rxjs-suffix-subjects rule + tests (#77)
Browse files Browse the repository at this point in the history
* Add rxjsSuffixSubjectsRule.ts

* Add fixture.ts.lint for rxjs=-suffix-subjects

* Add tslint.json for rxjs-suffix-subjects

* Add tsconfig.json for rxjs-suffix-subjects

* change callExpression var name

copied from sample code, change name to currentIdentifier` instead to reflect the actual use.

* Fix some minor things in fixture.ts.lint

variable names

* fix the rule description

* remove name option 

currently not needed, can add later

* make SUFFIX private

* fix linting issues in build

* Remove function and method options + other fixes

Also allow `subject` as a name, to avoid enforcing `subject` to be `subjectSubject`

* Add case where subjects is name

* remove function and method options

* remove trailing whitespace

* make regex dynamic and add edge cases

`subject` will not result in a `subjectSubject` failure, i.e. the suffix is case insensitive

* add different suffix test

* add tsconfig for diff suffix set

* add tslint.json file for diff suffix set

* add options-false test

* add tsconfig

* add tslint json for options false

* add test for unset options test

* add tsconfig

* add tslint json for unset options

* add test for default

* add tsconfig

* add tslint.json for default

* update tsconfig file

* delete

* delete

* delete

* remove class properties

* fix lint issues
  • Loading branch information
maggie-x authored and cartant committed Feb 7, 2019
1 parent a5a7c75 commit ea34c24
Show file tree
Hide file tree
Showing 13 changed files with 502 additions and 0 deletions.
126 changes: 126 additions & 0 deletions source/rules/rxjsSuffixSubjectsRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* @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 { tsquery } from "@phenomnomnominal/tsquery";
import * as Lint from "tslint";
import * as ts from "typescript";
import { couldBeType } from "../support/util";

const defaultTypesRegExp = /^EventEmitter$/;

export class Rule extends Lint.Rules.TypedRule {
public static metadata: Lint.IRuleMetadata = {
description:
"Ensures subjects are suffixed with suffix specified by `suffix` option.",
options: {
properties: {
parameters: { type: "boolean" },
properties: { type: "boolean" },
suffix: { type: "string" },
types: { type: "object" },
variables: { type: "boolean" }
},
type: "object"
},
optionsDescription: Lint.Utils.dedent`
An optional object with optional \`parameters\`, \`properties\` and \`variables\` properties.
The properies are booleans and determine whether or not subjects of that particular expression need a suffix.
\`parameters\`, \`properties\` and \`variables\` default to \`true\`, and the default suffix is 'Subject'.
The object also has optional \`types\` properties which are themselves
objects containing keys that are regular expressions and values that are booleans -
indicating whether suffixing is required for particular types.`,
requiresTypeInfo: true,
ruleName: '"rxjs-suffix-subjects"',
type: "style",
typescriptOnly: true
};

public applyWithProgram(
sourceFile: ts.SourceFile,
program: ts.Program
): Lint.RuleFailure[] {
const failures: Lint.RuleFailure[] = [];
const typeChecker = program.getTypeChecker();

let SUFFIX = "Subject";
const types: { regExp: RegExp; validate: boolean }[] = [];
let validateOptions = {
parameters: true,
properties: true,
variables: true
};
const FAILURE_MESSAGE = (identifier: string) =>
`Subject '${identifier}' must be suffixed with '${SUFFIX}'.`;

// get configurations
const { ruleArguments } = this.getOptions();
const [options] = ruleArguments;
if (options) {
SUFFIX = options.suffix;

if (options.types) {
Object.entries(options.types).forEach(
([key, validate]: [string, boolean]) => {
types.push({ regExp: new RegExp(key), validate });
}
);
} else {
types.push({ regExp: defaultTypesRegExp, validate: false });
}
validateOptions = { ...validateOptions, ...options };
} else {
types.push({ regExp: defaultTypesRegExp, validate: false });
}
const suffixRegex = new RegExp(`${SUFFIX}\\$?$`, "i");
let identifiers: ts.Node[] = [];

if (validateOptions.parameters) {
identifiers = identifiers.concat(
tsquery(sourceFile, `Parameter > Identifier`)
);
}

if (validateOptions.properties) {
identifiers = identifiers.concat(
tsquery(
sourceFile,
`:matches(PropertyAssignment, PropertyDeclaration, PropertySignature, GetAccessor, SetAccessor) > Identifier`
)
);
}

if (validateOptions.variables) {
identifiers = identifiers.concat(
tsquery(sourceFile, `VariableDeclaration > Identifier`)
);
}

identifiers.forEach(identifier => {
const currentIdentifier = identifier.parent as ts.Identifier;
const type = typeChecker.getTypeAtLocation(currentIdentifier);
const text = identifier.getText();
if (!suffixRegex.test(text) && couldBeType(type, "Subject")) {
for (let i = 0; i < types.length; ++i) {
const { regExp, validate } = types[i];
if (couldBeType(type, regExp) && !validate) {
return;
}
}

failures.push(
new Lint.RuleFailure(
sourceFile,
identifier.getStart(),
identifier.getStart() + identifier.getWidth(),
FAILURE_MESSAGE(text),
this.ruleName
)
);
}
});
return failures;
}
}
69 changes: 69 additions & 0 deletions test/v6/fixtures/suffix-subjects/default/fixture.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { EventEmitter } from '@angular/core';
import * as Rx from 'rxjs';

const variableSubject$ = new Rx.Subject<any>();
const variable$ = new Rx.Subject<any>();
~~~~~~~~~ [suffix-subjects % ("variable$")]

const mockObject = {
objFieldSubject$: new Rx.Subject<any>(),
objField$: new Rx.Subject<any>(),
~~~~~~~~~ [suffix-subjects % ("objField$")]
}

class Mock {
private submitSubject$ = new Rx.Subject<void>();
private _submit$ = new Rx.Subject<void>();
~~~~~~~~ [suffix-subjects % ("_submit$")]
private events = new EventEmitter();
private subject = new Rx.Subject<any>();

public property$ = new Rx.Subject<void>();
~~~~~~~~~ [suffix-subjects % ("property$")]

noFinnishSubject = new Rx.Subject<void>();
noFinnish = new Rx.Subject<void>();
~~~~~~~~~ [suffix-subjects % ("noFinnish")]

constructor(
private _openStream$: Rx.Subject<any>,
~~~~~~~~~~~~ [suffix-subjects % ("_openStream$")]
private _openStreamSubject$: Rx.Subject<any>

) {
console.log(this.submitSubject$);
console.log(this.submit$);
console.log(this.property$);
}

public mock(): Rx.Subject<any> {
return new Rx.Subject<any>();
}

get submit$(): Rx.Subject<void> {
~~~~~~~ [suffix-subjects % ("submit$")]

return this._submit$;
}

set submit$(xSubject$: Rx.Subject<void>) {
~~~~~~~ [suffix-subjects % ("submit$")]
this._submit$ = xSubject$;
}
}

interface mockInterface {
interfaceField$: Rx.Subject<any>;
~~~~~~~~~~~~~~~ [suffix-subjects % ("interfaceField$")]
}

function fooCorrect(xSubject$: Rx.Subject<any>) {
console.log(xSubject$);
}

function fooWrong(x$: Rx.Subject<any>) {
~~ [suffix-subjects % ("x$")]
console.log(x$);
}

[suffix-subjects]: Subject '%s' must be suffixed with 'Subject'.
14 changes: 14 additions & 0 deletions test/v6/fixtures/suffix-subjects/default/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

{
"compilerOptions": {
"baseUrl": ".",
"lib": ["es2015"],
"noEmit": true,
"paths": {
"rxjs": ["../../node_modules/rxjs"]
},
"skipLibCheck": true,
"target": "es5"
},
"include": ["fixture.ts"]
}
16 changes: 16 additions & 0 deletions test/v6/fixtures/suffix-subjects/default/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"defaultSeverity": "error",
"jsRules": {},
"rules": {
"rxjs-suffix-subjects": {
"options": [{
"parameters": true,
"properties": true,
"variables": true,
"suffix": "Subject"
}],
"severity": "error"
}
},
"rulesDirectory": "../../../../../build/rules"
}
69 changes: 69 additions & 0 deletions test/v6/fixtures/suffix-subjects/diff-suffix-set/fixture.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { EventEmitter } from '@angular/core';
import * as Rx from 'rxjs';

const variablesbjt$ = new Rx.Subject<any>();
const variable$ = new Rx.Subject<any>();
~~~~~~~~~ [suffix-subjects % ("variable$")]

const mockObject = {
objFieldsbjt$: new Rx.Subject<any>(),
objField$: new Rx.Subject<any>(),
~~~~~~~~~ [suffix-subjects % ("objField$")]
}

class Mock {
private submitsbjt$ = new Rx.Subject<void>();
private _submit$ = new Rx.Subject<void>();
~~~~~~~~ [suffix-subjects % ("_submit$")]
private events = new EventEmitter();
private sbjt = new Rx.Subject<any>();

public property$ = new Rx.Subject<void>();
~~~~~~~~~ [suffix-subjects % ("property$")]

noFinnishsbjt = new Rx.Subject<void>();
noFinnish = new Rx.Subject<void>();
~~~~~~~~~ [suffix-subjects % ("noFinnish")]

constructor(
private _openStream$: Rx.Subject<any>,
~~~~~~~~~~~~ [suffix-subjects % ("_openStream$")]
private _openStreamsbjt$: Rx.Subject<any>

) {
console.log(this.submitsbjt$);
console.log(this.submit$);
console.log(this.property$);
}

public mock(): Rx.Subject<any> {
return new Rx.Subject<any>();
}

get submit$(): Rx.Subject<void> {
~~~~~~~ [suffix-subjects % ("submit$")]

return this._submit$;
}

set submit$(xsbjt$: Rx.Subject<void>) {
~~~~~~~ [suffix-subjects % ("submit$")]
this._submit$ = xsbjt$;
}
}

interface mockInterface {
interfaceField$: Rx.Subject<any>;
~~~~~~~~~~~~~~~ [suffix-subjects % ("interfaceField$")]
}

function fooCorrect(xsbjt$: Rx.Subject<any>) {
console.log(xsbjt$);
}

function fooWrong(x$: Rx.Subject<any>) {
~~ [suffix-subjects % ("x$")]
console.log(x$);
}

[suffix-subjects]: Subject '%s' must be suffixed with 'sbjt'.
13 changes: 13 additions & 0 deletions test/v6/fixtures/suffix-subjects/diff-suffix-set/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"]
}
16 changes: 16 additions & 0 deletions test/v6/fixtures/suffix-subjects/diff-suffix-set/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"defaultSeverity": "error",
"jsRules": {},
"rules": {
"rxjs-suffix-subjects": {
"options": [{
"parameters": true,
"properties": true,
"variables": true,
"suffix": "sbjt"
}],
"severity": "error"
}
},
"rulesDirectory": "../../../../../build/rules"
}
58 changes: 58 additions & 0 deletions test/v6/fixtures/suffix-subjects/options-false/fixture.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { EventEmitter } from '@angular/core';
import * as Rx from 'rxjs';

const variableSubject$ = new Rx.Subject<any>();
const variable$ = new Rx.Subject<any>();

const mockObject = {
objFieldSubject$: new Rx.Subject<any>(),
objField$: new Rx.Subject<any>(),
}

class Mock {
private submitSubject$ = new Rx.Subject<void>();
private _submit$ = new Rx.Subject<void>();
private events = new EventEmitter();
private subject = new Rx.Subject<any>();

public property$ = new Rx.Subject<void>();

noFinnishSubject = new Rx.Subject<void>();
noFinnish = new Rx.Subject<void>();

constructor(
private _openStream$: Rx.Subject<any>,
private _openStreamSubject$: Rx.Subject<any>

) {
console.log(this.submitSubject$);
console.log(this.submit$);
console.log(this.property$);
}

public mock(): Rx.Subject<any> {
return new Rx.Subject<any>();
}

get submit$(): Rx.Subject<void> {

return this._submit$;
}

set submit$(xSubject$: Rx.Subject<void>) {
this._submit$ = xSubject$;
}
}

interface mockInterface {
interfaceField$: Rx.Subject<any>;
}

function fooCorrect(xSubject$: Rx.Subject<any>) {
console.log(xSubject$);
}

function fooWrong(x$: Rx.Subject<any>) {
console.log(x$);
}

Loading

0 comments on commit ea34c24

Please sign in to comment.