Creating Your Own TSLint Rules

If you’re using TypeScript, you probably lint your code with TSLint. It has tons of useful presets that can be easily configured to your liking. Perhaps, though, you want to dig a bit deeper and create rules of your own. In this post, I’ll walk you through the steps I took to create my first TSLint rule.

I want to create a rule that requires arrow functions to only have one parameter. This probably isn’t desired in most cases, but I like the idea of either forcing the use of partially applied functions, or of passing a single object containing all the values the function needs. (Argument destructuring is one of my favorite features of ES6.)

It’s worth saying that my exploration was primarily driven by Plantir’s documentation. I also made heavy use of my editor’s jump-to-definition functionality to view some of the underlying interfaces of classes.

Setup

For this example, I decided to put my rule into a ts-rules directory inside my project’s root directory. Following the conventions tslint requires, the rule I’ll create is named using camelCase, written in TypeScript, and transpiled to JavaScript using tsc. So the file will be located at ts-rules/noMultiArgFunctionsRule.ts, and we can add it to our tslint.json as the rule no-multi-arg-functions. It can be transpiled using tsc ts-rules/noMultiArgFunctionsRule.ts.

Walkers

Walkers are one of the basic units for adding your own rules. They visit each node in your code’s Abstract Syntax Tree, and when they recognize the element, they call a corresponding vist* function. Examples of these include visibBlock, visitForStatement, etc. For instance, if the walker were to visit a return statement, it would call visitReturnStatement, assuming you implemented it in your walker.

Each of these vist* functions takes a node of the corresponding type, which means that your editor should allow you to view its declaration or use autocomplete on it. I found this very helpful when experimenting. The function implementation can then either create a failure or not.

Hello World (of Linting Rules)

To create a rule, you must create an instance of Lint.Rules.AbstractRule. This class should implement an apply method that can apply your specific walker to a file. Figuring I should start with our friend the arrow function, I added the following code:


import * as ts from "typescript";
import * as Lint from "tslint";

export class Rule extends Lint.Rules.AbstractRule {
  public static FAILURE_STRING = "no multi-arg functions allowed";

  public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
    return this.applyWithWalker(
      new NoMultiArgFunctionsWalker(sourceFile, this.getOptions())
    );
  }
}

class NoMultiArgFunctionsWalker extends Lint.RuleWalker {
  public visitArrowFunction(node: ts.ArrowFunction) {
    if (node.parameters.length > 1) {
      this.addFailure(
        this.createFailure(node.getStart(), node.getEnd(), Rule.FAILURE_STRING)
      );
    }
    super.visitArrowFunction(node);
  }
}

This is largely a copy of what the Plantir’s documentation shows, but it’s a good starting point. After writing this, you’ll need to transpile to JavaScript. Then add the the rule to your tsconfig.json.

Next, run TSLint, and you should see messages like the following that correspond to any arrow functions in your code:
ERROR: src/lint-me.ts[3, 17]: no multi-arg functions allowed. Pretty cool!

Raising Errors on Multiple Arguments

After running tsc to transpile it to JavaScript, we can run yarn lint (or whatever your linting command is). You should see errors on all of your arrow functions, which is expected. Now let’s see if we can require that functions only allow a maximum of one param.

The node parameter of visitArrowFunction contains a parameters property, which, as you may imagine, has a length. So if we want to limit our functions to require one (or no) arguments, we can update the addFailure part of the rule to look like this:


    if (node.parameters.length > 1) {
      this.addFailure(
        this.createFailure(node.getStart(), node.getEnd(), Rule.FAILURE_STRING)
      );
    }

Running tsc and yarn lint again, we can see that we indeed fail the test with arrow functions accepting multiple arguments, but not with those accepting 0 or 1.

Conclusions

I found it incredibly easy to add my own linting rules. Thus far, my only concern is that I’m not automating the process of transpiling the rules. I’m sure this can be easily remedied with a simple yarn script.

I have a bunch more ideas for linting rules I’d like to implement, but I’d like to hear from others. What rules are you interested in? Why?