DEV Community

Cover image for Better linting with Typescript and custom ESLint
Sayan Mondal
Sayan Mondal

Posted on

Better linting with Typescript and custom ESLint

When writing JavaScript with an editor, there are a number of ways you can ensure your code is syntactically correct and in line with current best practices. One of the best ways to check for code integrity is setting up a linter such as ESLint

What is ESLint?

Eslint

ESLint is an open source project that helps developers find and fix problems with their JavaScript code. It's the number one JavaScript linter by downloads on npm (over 6.5 million downloads per week) and is used at companies like Microsoft, Airbnb, Netflix, and Facebook. Due to ESLint's flexibility, it's also able to help find and fix problems in JavaScript derivative languages such as Flow and TypeScript, as well as for mobile development with React Native.

Rules in ESLint are configurable, and customized rules can be defined and loaded. ESLint covers both code quality and coding style issues.


Architecture

ESLint Architecture
Taking a high level look at ESLint we are going to discuss a few important key parts which would be very useful to understand the inner core components which can be configured to achieve more with ESLint.

eslint.js is a dumb component that does nothing more than bootstraping ESLint and passing the command line arguments to the CLI(cli.js). It does not require much heavy lifting and thus is pretty small.

api.js is the main entry point of require command. It exposes an object that contains public classes to Linter, CLIEngine, RuleTester and Source Code.

init module contains the --init functionality that sets up the configuration for a end user.

cli.js takes an array of arguments and then uses eslint to execute the commands.

cli-engine module consists of CLIEngine class that is responsible to figure out the configuration files and then verify code using Linter.

linter does code verification based on the configurations provided. It does not interact with the console at all.

Learn more at the official docs


CLI Engine

It reads both configuration and source files(including eslintrc and package.json) as well as manages the environment that is passed into the Linter object.

The main method of the CLIEngine is executeOnFiles(), which accepts an array of file and directory names to run the linter on.

It does not use formatters or outputs to the console. The operation is not asynchronous as well.

Linter

The main method of the Linter object is verify() and accepts two arguments: the source text to verify and a configuration object. First the given text is parsed using a parser(which you decide, some common parsers are espree, typescript-eslint-parser, etc) and it generates back an AST(Abstract Syntax Tree).

The AST is produced with both line/column and range locations which are useful for reporting location of issues and retrieving the source text related to an AST node, respectively. The AST is then traversed from top to bottom.

At each node, the Linter object emits an event that has the same name as the node type (i.e., "Identifier", "WithStatement", etc.). On the way back up the subtree, an event is emitted with the AST type name and suffixed with ":exit", such as "Identifier:exit" - this allows rules to take action both on the way down and on the way up in the traversal.


In this blog I'll be talking about the eslint configuration we use in Litmus.

Litmus

Litmus is a framework for practicing chaos engineering in cloud-native environments. Litmus provides a chaos operator, a large set of chaos experiments on its hub, detailed documentation, and a friendly community. Litmus is very easy to use; you can also set up a very quick demo environment to install and run Litmus experiments.

If you don't already have a typescript project created you can do so by generating a boilerplate basic setup, for example this is how you can get up and running with a base react-typescript setup, similar to what we have with Litmus

npx create-react-app your-app --template typescript
Enter fullscreen mode Exit fullscreen mode

Setting up the Project

The first thing that needs to be done is to install eslint and eslint-plugin-import in your project directory to be able to initialise our project with eslint configurations and also add on further configurations later.

npm install eslint eslint-plugin-import --save-dev
Enter fullscreen mode Exit fullscreen mode

Once the package is installed we can use it to initialize an ESLint configuration by running

eslint --init
Enter fullscreen mode Exit fullscreen mode

Soon you would be prompted in an interactive CLI to choose your style guide, framework, etc following which eslint will create a .eslintrc file for you with those configurations but we are not going to follow that.

Interactive CLI

Instead we are going to create our own eslintrc file with our custom configurations.


Pre-requisites

In order to create a set of amazing custom linting rules we would require some packages which would help us get the work done in a lot less time.

First things first, you need to make sure you already have typescript installed in your project and your project is following a typescript template based setup with a tsconfig file.

NOTE: You could also use these eslint rules and configurations without typescript but the packages/parsers might be a little different for you

A few dev-dependencies/peer-dependencies that would be required for this particular setup are -

  • eslint-config-airbnb
  • eslint-config-prettier
  • eslint-plugin-import
  • eslint-plugin-jsx-a11y
  • eslint-plugin-prettier
  • eslint-plugin-react
  • eslint-plugin-react-hooks
  • prettier
  • @typescript-eslint/eslint-plugin
  • @typescript-eslint/parser

Since writing efficient code and following best practices are a key to achieving quality code we prefer following the Airbnb style guide for cleaner code.

In order to be able to put them to use in our project we need to install some peer dependencies which need to have an exact version number in order to function properly, let's do that first before jumping into the next one.

npx install-peerdeps --dev eslint-config-airbnb
Enter fullscreen mode Exit fullscreen mode

NOTE: In order to use install-peerdeps we would require eslint-plugin-import which we'd already installed earlier

Once the style guide and its peer-dependencies are set we also need to add the parsers, eslint-plugin for typescript as well as prettier for eslint to make our linted code check for correct code formatting.

npm install --save-dev  prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-prettier eslint-plugin-prettier
Enter fullscreen mode Exit fullscreen mode

Creating our own .eslintrc

Create a .eslintrc.json file at the root of your project, this is where we would be writing our own configuration for our project.

Parser

Let's put the packages we had previously installed to use and assign our eslint a parser.

"parser": "@typescript-eslint/parser"
Enter fullscreen mode Exit fullscreen mode

This parser would leverage TypeScript ESTree to allow for ESLint to lint TypeScript source code.



Extend Configuration

Since we are following Airbnb style guide to achieve best practices and cleaner code we need to let our linter know about this. We can extend our linter's configuration in order to extend its functionality

 "extends": ["airbnb"]
Enter fullscreen mode Exit fullscreen mode

Although eslint formats our code it does not go far enough. The code does not look very nice. This is where the wheat is separated from the chaff. ESLint is primarily intended for code quality. Prettier, as the name implies, makes your code Prettier. With added extensibility of Prettier our linter would let us know when a certain code format is incorrect in term of airbnb style guide and also the set of custom rules.

  "extends": ["airbnb", "plugin:prettier/recommended", "prettier/react"]
Enter fullscreen mode Exit fullscreen mode

ESLint Plugin

Next we move on to adding the eslint-plugin.

"plugins": ["@typescript-eslint"]
Enter fullscreen mode Exit fullscreen mode

This eslint plugin provides an already configured list of linting rules which you can use right out of the box. Take a look at the rules here. They provide a list of recommended rules which includes naming-convention, no-this-alias, etc which are very handy.

To be able to use them in our project we need to add them in "rules". Some of the typescript-eslint rules which are extensively used in Litmus are -

"rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/no-useless-constructor": "error"
}
Enter fullscreen mode Exit fullscreen mode


Modifying Rules

Just these two rules won't do for a codebase like ours which requires quick a lot of checks to achieve top quality code integrity. So let's add some more.

"rules": {
    "jsx-a11y/href-no-hash": ["off"],
    "react/jsx-props-no-spreading": ["off"],
    "react/jsx-filename-extension": [
      "warn",
      { "extensions": [".js", ".jsx", ".ts", ".tsx"] }
    ],
    "no-console": [
      "error",
      {
        "allow": ["warn", "error"]
      }
    ],
    "multiline-ternary": 0,
    "no-unused-vars": "off",
    "no-shadow": 0,
    "@typescript-eslint/no-unused-vars": "error",
    "no-useless-constructor": "off",
    "@typescript-eslint/no-useless-constructor": "error",
    "react/prop-types": 0,
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "js": "never",
        "jsx": "never",
        "ts": "never",
        "tsx": "never"
      }
    ],
    "max-len": [
      "warn",
      {
        "code": 80,
        "tabWidth": 2,
        "comments": 80,
        "ignoreComments": false,
        "ignoreTrailingComments": true,
        "ignoreUrls": true,
        "ignoreStrings": true,
        "ignoreTemplateLiterals": true,
        "ignoreRegExpLiterals": true
      }
    ]
}
Enter fullscreen mode Exit fullscreen mode

You can customise your configuration by modifying the rules section. You will need to input key -> value pairs, where the key is the name of the rule.

The values Error, Off, Warn have a specific meaning when it comes to configuring the rules.

“Error” will strictly request you to change the portion of code which does not comply with the rules specified, “Warn” will only provide you a warning but you can still continue with your code, and “Off” would simply not check for that particular rule in your code base.


Environments

To specify environments in a configuration file, we use the env key and specify the environments we want to enable by setting each to true.

"env": {
    "browser": true,
    "es6": true
}
Enter fullscreen mode Exit fullscreen mode

Here we are enabling the browser and es6 environments.

NOTE: env is required for global variables. ESLint will enable the globals for the env variables we select.



Settings

Modifying the default eslint settings to our own needs

"settings": {
    "import/extensions": [".js", ".jsx", ".ts", ".tsx"],
    "import/parsers": {
      "@typescript-eslint/parser": [".ts", ".tsx"]
    },
    "import/resolver": {
      "node": {
        "extensions": [".js", ".jsx", ".ts", ".tsx"]
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Adding import/extensions to support linting of ES2015+ (ES6+) import/export syntax, and prevent issues with misspelling of file paths and import names.

We also need to import/require files with extension .ts/.tsx, Use paths defined in tsconfig.json and resolve @types/* definitions over plain .js, .ts

Here is the whole .eslintrc file at a glance

{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": ["airbnb", "plugin:prettier/recommended", "prettier/react"],
  "env": {
    "browser": true,
    "es6": true
  },
  "settings": {
    "import/extensions": [".js", ".jsx", ".ts", ".tsx"],
    "import/parsers": {
      "@typescript-eslint/parser": [".ts", ".tsx"]
    },
    "import/resolver": {
      "node": {
        "extensions": [".js", ".jsx", ".ts", ".tsx"]
      }
    }
  },
  "rules": {
    "jsx-a11y/href-no-hash": ["off"],
    "react/jsx-props-no-spreading": ["off"],
    "react/jsx-filename-extension": [
      "warn",
      { "extensions": [".js", ".jsx", ".ts", ".tsx"] }
    ],
    "no-console": [
      "error",
      {
        "allow": ["warn", "error"]
      }
    ],
    "multiline-ternary": 0,
    "no-unused-vars": "off",
    "no-shadow": 0,
    "@typescript-eslint/no-unused-vars": "error",
    "no-useless-constructor": "off",
    "@typescript-eslint/no-useless-constructor": "error",
    "react/prop-types": 0,
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "js": "never",
        "jsx": "never",
        "ts": "never",
        "tsx": "never"
      }
    ],
    "max-len": [
      "warn",
      {
        "code": 80,
        "tabWidth": 2,
        "comments": 80,
        "ignoreComments": false,
        "ignoreTrailingComments": true,
        "ignoreUrls": true,
        "ignoreStrings": true,
        "ignoreTemplateLiterals": true,
        "ignoreRegExpLiterals": true
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Voila, you have successfully created an eslint config file which can check for unused variables, unused import statements, extra long comments, etc. You can ofcourse extend these and play around to create something out of your own. We welcome everyone to comment and let us know about what/how we can improve these linting configure to achieve more! Every suggestion is appreciated.


Are you an SRE or a Kubernetes enthusiast? Does Chaos Engineering excite you?
Join Our Community On Slack For Detailed Discussion, Feedback & Regular Updates On Chaos Engineering For Kubernetes: https://kubernetes.slack.com/messages/CNXNB0ZTN
(#litmus channel on the Kubernetes workspace)


Check out the Litmus Chaos GitHub repo and do share your feedback: https://github.com/litmuschaos/litmus
Submit a pull request if you identify any necessary changes.

Don't forget to share these resources with someone who you think might benefit from these. Peace out. ✌🏼

Top comments (0)