Prevent coding mistakes — write a TSLint rule specific for your own project

Coding standards are a very important part of code reviews, but difficult to execute manually.

Typescript is an amazing language used by many developers and there is a lot of tools around it. TSLint is one of them. It is a static analysis tool that checks your code for readability, maintainability, and functionality errors. It comes with a lot of generic rules, but sometimes you would like to check for something more specific to your project.

Very common mistake seen in Angular applications is importing unnecessary RxJS operators and we can automate a fix for it.

Take a look at this example:

import {map} from ‘rxjs/operators';
...
const endpoint = 'http://some-url.tld/get-list-of-ids';
this.httpClient
.get(endpoint)
.pipe(
map(response => response.id)
)
.subscribe(response => console.log(response));

Of course, this code is running smoothly and everything works, but when you take a look at the size of your application it’s bigger then it should be. It is because all RxJS operators are included.

The problem here is in the import and a simple change will load only what is necessary.

import {map} from 'rxjs/operators/map';

The change here is simple but even a better code review will not prevent that some developer can put there a short import accidentally and BUM! Your application is ~4KB bigger.

I know it’s not a huge deal but if you stack many of those 4KB, you can load much more than you need.

Let’s write a TSLint rule specific to your project!

I assume you have already a running project with Typescript and TSLint.
If not, use a new generated Angular application — it’s just a few steps.

Prepare a folder structure and all necessary files

Create a folder ./tools/tslint-rules/.

Create a ./tools/tslint-rules/tslint.json file with this content:

{
"rulesDirectory": [
"dist"
],
"rules": {
"disallow-rxjs-operators-full-import": true
}
}

You will also need a specific ./tools/tslint-rules/tsconfig.json file to build a rule:

{
"compilerOptions": {
"baseUrl": "./src",
"outDir": "./dist",
"moduleResolution": "node",
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
]
}
}

and ./tools/tslint-rules/package.json file:

{
"name": "my-tslint-rules",
"version": "0.0.0",
"license": "MIT",
"main": "tslint.json"
}

Now we can say TSLint to use our rules. We can do that with adding a section extends to main tslint.json:

{
"extends": [
"./tools/tslint-rules"
],
...
"rules": {
...
}
}

Extends section points to package.json file where is defined tslint.json file in the main.

A recommended step is also to add a generated ./tools/tslint-rules/dist folder to .gitignore and keep only original rules in typescript.

We are ready with the boilerplate and let’s write a rule.


So let’s name our rule as disallowFullImportRxjsOperatorsRule.ts

export class Rule extends Lint.Rules.AbstractRule {
...
}

You can notice that every file with a rule needs to have a suffix *Rule.ts and will be used in our tslint file as disallow-full-import-rxjs-operators.

Below you can see already implemented rule:

Now you only need to build it with typescript compiler

./node_modules/.bin/tsc -p ./tools/tslint-rules/tsconfig.json

and when you run a linter on the files with wrong imports you will see errors like

ERROR: /Users/.../src/app.service.ts[7, 1]: RxJS operators should be imported from their own scope.

Great! We were able to locate a place in our application which is incorrect. Fixing it manually is always possible but we can go even further.

Automate a replacement

The method addFailureAt() is defined as

addFailureAt(
start: number,
width: number,
failure: string,
fix?: Fix,
): void;
type Fix = Replacement | Replacement[];

You can see that the last parameter is optional and it can provide one or multiple replacements. To enable this replacement you need to run tslint command with a --fix argument.

The implementation of such a replacement can be as follows:

My implementation doesn’t need to be perfect but it serves a purpose and it works in all cases we had at Erento.

Don’t be afraid of writing your own rules.


Update:
After the discussion in following PR
https://github.com/mgechev/codelyzer/pull/498 I need to mention that this TSLint rule and a fixer is a good solution for the current version of RxJS (5.x) but it will not work in the new major release In the new version operators should be imported from rxjs/operators only.