-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rxjs-suffix-subjects rule + tests (#77)
* 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
Showing
13 changed files
with
502 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
69
test/v6/fixtures/suffix-subjects/diff-suffix-set/fixture.ts.lint
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
13
test/v6/fixtures/suffix-subjects/diff-suffix-set/tsconfig.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
16
test/v6/fixtures/suffix-subjects/diff-suffix-set/tslint.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
58
test/v6/fixtures/suffix-subjects/options-false/fixture.ts.lint
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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$); | ||
} | ||
|
Oops, something went wrong.