Writing custom TSLint rules from scratch
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
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.
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.
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:
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 animport
definitionisMethodDeclaration
- checks if a statement has amethod
definitionisArrowFunction
- checks if a statement has anarrow function
definitionisIfStatement
- checks if a statement hasif
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.
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
- Developing TSLint rules official documentation: https://palantir.github.io/tslint/develop/custom-rules
- My GitHub project with the boilerplate rule: https://github.com/andrewborisov/creating-custom-ts-rule
- My GitHub project with the implementation of my own rule https://github.com/andrewborisov/module-imports-order-ts-rule