Migrating a TSLint Rule to ESLint with @typescript-eslint
Because TSLint is getting deprecated, we have to find a new way to write our TSLint rules. Luckily for us, there a lot of contributors working on an alternative, typescript-eslint. As the name implies, it's using ESLint to lint your TypeScript code. Internally, it uses a custom parser to turn the code into an Abstract Syntax Tree, allowing us to write our beloved rules.
Dependencies link
As most of the development, it all starts by installing dependencies.
Project setup link
The folder structure stays the same, we still have a folder called rules
.
But there is a small difference with the file names. Instead of using lowerPascalCase ngrxActionHygieneRule
, we're now using the kebab style action-hygiene
to create the rule files. You can also notice that the rules aren't prefixed anymore, nor are they using the Rule
suffix.
Creating a rule link
Instead of creating a class and extending it from the TypeRule
class to create a rule, we use the RuleCreator
method.
This method can be imported from @typescript-eslint/experimental-utils
.
This utility method expects that you pass the rule name, some metadata, the default options, and lastly a create
method.
These options to configure your rules can be compared with the static metadata
property of a TSLint rule and its apply
methods.
Rewriting a rule implementation link
I've found that writing rules in ESLint is a more pleasant experience, and most of them are also easier to write in my opinion. Previously with TSLint, we had to traverse the AST ourselves, which was hard sometimes and it usually was tedious work to do.
For easier access to the AST nodes ESLint provides esquery, to query nodes via selectors which can be compared to CSS selectors. By using selectors, we can for example select siblings and use attribute conditions to query the nodes we're looking for. Because of this a multiline rule could be rewritten and reduced to a single line.
format_quoteAs a side note, there is a TSLint equivalent, tsquery from Craig Spence which has a similar API
Don't worry if you're not into using selectors - although they're really cool! - you can fallback to the visit methods, with the benefit that the nodes are already having the correct type. Just be aware that some node types have a slightly different name, compared to their TSLint equivalent.
Adding a failure link
To add a failure we can simply execute the context.report
method, pass it a node, a message id and, optional data.
The message id must be configured in the meta data of the rule. The data is used to replace the placeholders, between double curly braces {{ propertyName }}
, in the message.
For example, the following config and report will translate to "Action type 'LOAD_CUSTOMERS' does not follow the good action hygiene practice, use "[Source] Event" to define action types".
Because the typescript-eslint project is written in TypeScript, your rule and its configuration are completely typesafe. If you make a typo while writing the message id, the compiler will not compile and throw an error at you. This is also the case if the rule can be configured, which comes in handy while writing tests for configurable rules.
Failures with fixers link
To include a fix for a rule violation, you can provide a fixer to the report method:
All of the fixer methods are documented, see the docs for more info.
Writing Tests link
At first, I was a little bit disappointed because I liked the approach TSLint took with this, but the ESLint approach start to grow on me after a while.
With TSLint, a test looked like this, where you could just write your code in a *.ts.lint
file, and underline the failures.
With ESLint, the tests feel more comfortable with other tests that you've written. You will have to create a test runner, configure it, and then you will be able to run the rule with valid and invalid cases.
format_quoteYou can also create your own test runner, like angular-eslint did in test-helper.ts to be able to use squigglies in test cases
Utility methods link
There are (currently) no guard utility methods available, and this is what I miss the most. For now, it's easy to write them ourselves, but it would be great to have them packaged in the library as these will frequently be used in most projects.
Adding documentation to a rule link
Having the opportunity to point to the docs was something I didn't know that I missed with TSLint.
The RuleCreator
has a built-in way to point to the documentation of a rule. In fact, the callback to the docs while creating the rule is required. This goes without a saying, but this is a great developer experience and is helpful to your users.
Most of the docs I've seen have more information about the violation, explain why it's a violation, and have invalid and valid code snippets. See the docs of Testing Library for an example.
Tools link
The tools I use to write rules remain the same.
I usually start out in AST Explorer, just like before, or as a replacement to TSQuery Playground.
Make sure to have the @typescript-eslint/parser
chosen in the settings.
Resources link
- The typescript-eslint monorepo
- The angular-eslint project; a port from codelyzer
- The eslint-plugin-rxjs; a port from rxjs-tslint-rules
- The ESLint docs
- Convert your TSLint config to ESLint with https://github.com/typescript-eslint/tslint-to-eslint-config
Feel free to update this blog post on GitHub, thanks in advance!
Join My Newsletter (WIP)
Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.
Support me
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.