How to extend TSLint rules with custom rules

How to extend your TSLint rules with your own custom rules

TSLint is an extensible static analysis tool that checks Javascript and TypeScript code for readability, maintainability, and functionality errors.

TypeScript can be integrated into build systems and editors. It also has a set of core rules built into it and configuration that allows it to be extended with custom rules.

Many libraries have leveraged this configuration to build massive and wonderful tools out of it. The most popular among these is the Codelyzer, built by Minko Gechev.

The Codelyzer tool

The Codelyzer has custom rules that provide a way to lint Angular/TypeScript projects. The library is amazing, and we too can do the same.

The tool

Here, we will learn how to add our custom rules to the TSLint.

Step 1. First, we create an empty project by typing the following commands in Command Prompt (CMD) or Powershell in Windows.


mkdir tslint-ext
cd tslint-ext
npm init -y

To install npm, refer to this documentation.


Step 2. Now, we install the dependencies.


npm i typescript tslint tsutils jest ts-jest @types/jest @types/node

Step 3. The "main" file in our package.json will be pointing to "index.js". Therefore, we create the index.js file:


touch index.js

Step 4. Open the file and add the following code.


// ./index.js

module.exports = {
    rulesDirectory: './',
}

We export the literal object as the default export. Inside it, we have a rulesDirectory property with "./". This would tell tslint that the custom lint rules are in the "./" folder relative to this file.

We will write a custom rule that will enforce pascal-cased class names and interface names.

What is PascalCase?

PascalCase is a naming convention in which the first letter of each word is capitalized.

We often use PascalCase when writing function names, class names, interface names, and other objects.

PascalCase is very helpful in distinguishing words within names, and helps developers write readable code.

Suppose we want users to configure our PascalCased class names by adding “class-name-pascal-case” to tslint.json.

To begin, let’s create a file that will implement the PascalCase rule:


touch classNamePascalCaseRule.ts

Naming conventions to follow in PascalCase

There are important conventions we have to know:

  • Rule identifiers are always kebab-cased.

  • Rule files are always camel-cased.

  • Rule files must contain the suffix Rule.

  • The exported class must always be named Rule.

  • The exported class must extend Lint.Rules.AbstractRule.

Our rule identifier is kebab-cased, i.e., “class-name-pascal-case”, so its Rule file must be named “classNamePascalCaseRule.ts” camel-cased with “Rule”, and suffixed.

Open the “classNamePascalCaseRule.ts” file and paste this code.

We need to hook up to tslint. First, we compile it using tsc.


tsc classNamePascalCaseRule.ts

This will generate classNamePascalCaseRule.js in the same dir as classNamePascalCaseRule.ts.

Now, we have to hook up our custom rule in our project. We can publish this tslint-ext to NPM and import it as a dependency in any project that we want to use the custom rule in.

Usage from NPM

Step 1. Let’s publish the tslint-ext to NPM using:


npm publish

You have to be logged in NPM, and also push tslint-ext to Github before publishing.

Step 2. Now, we can install it via:


npm i tslint-ext -D

The -D flag means to install it as a dev dependency.

Let’s say we have a project ts-prj, and we want to use the class-name-pascal-case in it.


ts-prj
    src
    ...
    node_modules
    tslint.json
    package.json
    tsconfig.json

Step 1. First, we install the tslint-ext module.


npm i tslint-ext -D

Step 2. Then, we open the tslint.json file in the ts-prj project.


{
    "extends": "tslint:recommended",
    "rules": {
        "class-name-pascal-case": true
    },
    "rulesDirectory": [
        "tslint-ext"
    ]
}

The two important settings here are that we specify the name of the dependency library in the “rulesDirectory” array property. Then we specify the rule name in the library that we want to use in the “rules” property.

Now, tslint will use the “class-name-pascal-case” when we lint the fields in the ts-prj folder.

If we have a class in our project, read the following:


// ts-prj/component.ts

export class BARCHART_COMPONENT {}

We have a class BARCHART_COMPONENT here. The name here is not pascal-cased, so when we run tslint on it we will see the error "Class name must be in pascal case" on our terminal.`

Results


$ tslint -c tslint.json component.ts

Error at component.ts 3: Class name must be in pascal case

That’s it, our rule is run.

Usage from folder

We can use the rule from the tslint-ext folder without pushing and installing it from NPM.

We can create the tslint-ext inside the ts-prj:


ts-prj
    tslint-ext/
        ...
    src/
        ...
    node_modules
    tslint.json
    package.json
    tsconfig.json

Now, the tslint.json will be this:


{
    "extends": "tslint:recommended",
    "rules": {
        "class-name-pascal-case": true
    },
    "rulesDirectory": [
        "tslint-ext"
    ]
}

If the tslint-ext project is inside the ts-prj/src folder, then the tslint.json will be this:


{
    "extends": "tslint:recommended",
    "rules": {
        "class-name-pascal-case": true
    },
    "rulesDirectory": [
        "ts-prj/src/tslint-ext"
    ]
}

Running tslint on component.ts will still succeed.


$ tslint -c tslint.json component.ts

Error at component.ts 3: Class name must be in pascal case

Conclusion

Adding custom rules to tslint is super easy. It just takes a few very careful configurations to get it right. Now, we know how popular custom linting libraries like Codelyzer work.

Free Resources

Attributions:
  1. undefined by undefined