Bending TSLint to your needs
After reading yet another twitter thread asking about the number of RxJS operators used in a project, I thought to myself that creating a small tool that does exactly this could be a fun little project.
After some time thinking about the best possible way to accomplish this task, I had split up the task into smaller ones to make it myself easier.
I had to:
- iterate over every file
- search for RxJS operators
- keep count
- print out the result
Keeping these smaller tasks in mind I noticed TSLint could be used for the first two tasks, doing most of the heavy lifting for me. This came in handy because I also wanted to dabble with TSLint and AST, or Abstract Syntax Tree, awesome two birds with one stone!
This post recounts the process I went through to create this new utility that I call
Following the Developing TSLint rules guide from TSLint, this was a straightforward task. The first step was to create a new file
src/rules/operatorCounterRule.ts and import both
tslint. Before I could use these dependencies, they had to be installed from npm with
npm install typescript tslint. In the created file it was now possible to create a custom TSLint rule, extending from the
AbstractRule class provided by
After doing this, there was an error because the custom rule didn’t implement the
apply function. With a single click, this function could be implemented. My utility was iterating over the files and I could now already check off the first step ✔️!
If you’re familiar with RxJS you know that all of the operators are used within the
pipe function, the question was how to find all of the pipe operators. Here again, another tool could luckily help me and it’s called AST explorer.
The code you and I write can be represented as a big syntax tree with different nodes, the most common nodes being declarations, expressions, statements, and identifiers. AST, gives us a nice interactive overview of these nodes and allows us to take a detailed look at specific nodes, simply by clicking on different parts of the code. The AST Explorer helped me a lot, I didn’t have to dig through documentation text but I could simply visualize the tree with all of its properties.
To find the
pipe function I first started from the top down,
CallExpression > PropertyAccessExpression> Identifier with as name pipe. To do this I created a custom syntax walker extending from syntax walker , which gave me a lot of basic functionality to find these expressions. While implementing it in this manner, I’ve encountered some problems, e.g. with nested pipe operators in an NgRx effect. With the knowledge I’ve gained during this implementation, I also found out that it would be a whole lot easier to just query for identifiers having
pipe as name. Here again, TSLint has us covered with the
visitIdentifier function. This function gets called everytime TSLint encounters an Identifier in the AST, so the only thing I had to do was to filter out the non
pipe identifiers with a simple
With this in place, I still had to get the arguments from within the
pipe function, which are the operators I was looking for. After taking another look at the syntax tree, I noticed I could get the operator names if I started from the
pipe identifier, going two levels up and then to read the arguments property from this parent node.
The full implementation of the walker looks like this:
With the walker finished, I had to modify my
Rule to call the
OperatorCounterWalker walker for every file it encountered.
✔️, the second step is completed.
If you took a close look at the walker implementation above, you already noticed
this.addFailureAtNode we let TSLint know that we’ve encountered an error, the second parameter is the failure description. But in rxjs-operator-counter there aren’t errors to log or fix. To keep track of the operators, I used this failure bucket and used the operator name as the failure description. After this, I could iterate over all of the failures and increment the counter when I found a failure with the same operator name (failure description). To get the failure description, in this case the operator name from the failure, the function
failure.getFailure is used. I added this logic to the rule itself:
A good old fashioned
console.log is sufficient at this point to print out the results.
The problem with the code above is that it will run for every file. In other words, the results would get logged for every file and you would have to accumulate the results yourself across the different files to obtain the total amount.
To solve this problem, I had to create a TSLint library. With a library it becomes possible to call the linter programmatically, allowing me to lint all the files and accumulate the results into a total result. Following the TSLint guide, this wasn’t as big of a task as I had expected, again…
In the library, the first thing to do is to create a custom linter. In order to create the linter, I needed to load the
tsconfig.json from the user’s workspace and I also had to define a couple of options. These options consisted of defining the rules directory where the
operatorCounterRule is located and also to mention that this linter won’t fix the encountered failures. Now that the linter was created, I could get the files included in the
tsconfig.json and iterate over each one of them. At this point, the linter is created and I could access the files, the only thing left to do was to lint every file. With a simple iteration I lint file by file, by using the
lint function on the linter itself.
The last piece of the puzzle was to provide the linter configuration, which can usually be found in the
tslint.json. The only problem here is that I wanted to have a simple command that can be run without the need of adding or changing something in your application. Therefore the only solution was to create and provide this config myself. Instead of creating a
tslint.json file myself and loading it in, it was easier to do this directly in code.
And ta-da, all of the files are now linted in one go and I could copy paste the logic to calculate the result that previously has been created. The only difference is that it now would print the application’s total result once, instead of the results on a file basis.
To make it a bit easier to read, I sorted the results and used the kleur library to add some color to the printed result.
✔️ ✔️, I could check off both of the last items on the list!
This all resulted in the following command,
npx rxjs-operator-counter. Which you can run in a typescript workspace and it will show you the total amount of operators used inside of it.
Testing this command happened at two different stages. At first, I wasn’t really familiar and that’s why the tests were manual verifications. As I dug more and more into the TSLint resources and after creating the custom linter I had a eureka moment and from then on the tests could be run automatically.
For the manual tests, it’s sufficient to add the rule and the rule directory in
tslint.json as follows:
tsc command, reload VSCode and navigate to a file. Because I added a
console.log statement when an operator was found, these logs could be found in the output window scoped to
tslint. Whenever a file was modified, the process of building and re-opening VSCode had to be repeated.
This process was a bit cumbersome and I had to manually verify the output. But once the custom linter was written, I could also call the linter programmatically in the tests and verify the output.
To test the result, I had created some files in a fixtures folder. When this set up was done, I could simply start crawling the fixtures folder by providing a
tsConfig path. Oof, no more manual testing!
TSQuery allows you to query a TypeScript AST for patterns of syntax using a CSS style selector system.
TSQuery is a library made by Craig Spence which allows you to select AST nodes with a query. For rxjs-operator-counter this means I didn’t have to manually walk the AST tree myself, reducing the code to only 2 lines (not counting the class definition). I could remove the walker completely, and only had to create
RuleFailures from the corresponding nodes returned by
tsquery. It took me a while to come up with the right query for my case and I asked for a second pair of eyes to get this working. Thanks to Nicholas Jamieson for coming up with the query needed to find the operator names, which is:
As you can see, a TSQuery selector resembles a CSS selector. This makes queries easier to comprehend when you start out.
This was a fun little project and it was also the ideal project to get my feet wet with TSLint and AST. Now that I have had some contact with it, I feel a lot more confident about being able to create my own TSLint rules and linters. The functionality provided by TSLint, TSQuery, and the tooling around them are spot on! It surely made it easier in my case to get started in this unknown terrain.
If you want, feel free to use the command
npx rxjs-operator-counter inside your own projects or take a look at the code on GitHub.
- The TSLint docs
- My first encounters with TSLint: rxjs-tslint by Minko Gechev and rxjs-tslint-rules by Nicholas Jamieson
- Easier TypeScript tooling with TSQuery and Custom TSLint rules with TSQuery both by Craig Spence
- YES! I Compiled 1,000,000 TypeScript files in Under 40 Seconds. This is How. by Uri Shaked
- AST for Beginners by Kent C. Dodds
- Take a look at the results from the TSQuery selector in BigTSQuery, also made by Uri Shaked, that allows you to run TypeScript AST Queries across all public TypeScript code on GitHub.
npx ng-app-countercreated by Rustam, to count your modules, components, directives, pipes, … The code can be found on GitHub.
- dtslint: A utility built on TSLint for linting TypeScript declaration files, which also uses TSLint in a smart way.
Please consider supporting me if have you enjoyed this post and found it useful: