Writing custom TSLint rules from scratch

Andrei Borisov
4 min readMay 14, 2019

--

Typescript and TSLint

A lot of us are using Typescript in our projects, and of course, everyone wants to follow code style easily and keep their code clear. In this case, tslint package can help us; this powerful tool allows us to customize, track and follow standard code style rules, such as max-line-width, single-quotemarks, ordered-imports and so on. However, what if you need your own, custom rule? Let’s see how to implement that one.

Necessary tools

Let’s create an empty project with the following packages: typescript, tslint, tsutil, jest, ts-jest, @types/jest and @types/node. I’ll install them via npm:

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

Why do we need jest, ts-jest and @types/jest? Actually, you can manage without them, but it’s much easier and faster to develop custom rules using TDD instead of running your rules on real files.

Configuration

Let’s create a folder for our custom rules, let’s name it custom-ts-rules, then we should create and configure our tslint.json

tslint.json

In this configuration, I’ve added several useful rules, and most importantly I’ve specified the directory for custom rules in rulesDirectory. Also, as you can see, I’ve defined my custom rule module-imports-order with specific options.

Then we should create a typescript file for our rule. It’s crucial to define name in camelCase and end it with Rule, for me, it will be moduleImportsOrderRule.ts. Rules are referenced in tslint.json with their kebab-cased identifier, so "module-imports-order": [true, [...options]] would configure the rule.

So finally, let’s add a script to package.json to build our rule. The parameter -lib es6 is required to work with es6 features. Besides that, I’ve defined jest options to test our custom ts-lint rule.

package.json

TDD

As I mentioned above, it’s much easier to use TDD when you’re implementing your custom ts-lint rules. To run jest unit-tests I’ve written some useful function, which receives the code to lint and a rule name, then picks rule options from tsconfig.json and rulesDirectory path, after that it returns the linter’s result.

lint-helper.ts

Now we able to write moduleImportsOrder.test.ts to test our custom ts-lint rule.

Implementing the rule

In moduleImportsOrderRule.ts we need to add some boilerplate:

moduleImportsOrderRule.ts

Let’s briefly discover what did I do. At first, I’ve written a class Rule which extends from Lint.Rules.AbstractRule, it’s ts-lint rule development convention, you always have to do it. Then, I’ve defined FAILURE_STRING, which will be shown in our error hint. After that, I’ve applied the walker class with parameters. That’ll be enough for a basic rule.

At second, I’ve written a class Walker which extends Lint.AbstractWalker, there’s also Lint.RuleWalker, but now it’s deprecated due to slow work speed.

Now let’s see deeply into Walker logic, there we able to use a method walk which receives the full source file to implement your own walker logic. sourceFile looks like a js-object with statements; each statement implies a separated line of code.

To help you parse source file, there’s a package tsutils with a bunch of useful utilities, such as:

  • isImportDeclaration - checks if a statement has an import definition
  • isMethodDeclaration - checks if a statement has a method definition
  • isArrowFunction - checks if a statement has an arrow function definition
  • isIfStatement - checks if a statement has if condition definition

The full list of utils you can find here, unfortunately, without descriptions.

After you parsed statements and defined that any of them have an error or errors, you should use built-in method this.addFailureAtNode(statement, failure); which adds failure to specific code row.

As you can see below I’ve written unit-testes, then I’ve implemented my rule logic.

Testing moduleImportsOrderRule.ts

When it successfully passes your testes, it would be ready to build. After you’ve developed rule you have to build to js-file, it’s crucial because tslint cannot use custom rules written directly on typescript.

tsc custom-ts-rules/moduleImportsOrder.ts --lib es6

Then it’ll be ready to use.

Conclusion

I hope this article will help you to understand how to implement your own TSLint rules. I recommend you to choose something easy, but annoying at code reviews. For my team and me, it was the order of the imports; we use a specific sequence “from far to close”.

1. node_modules
2. @/utils/
3. @/modules/
4. @/components/
5. ../
6. ./
7. ../icon.svg
8. ./style.scss

So I’ve tested, successfully developed this one and published it to npm.

Links

--

--