Better linting with Typescript and custom ESLint
Tags: beginners, typescript, litmuschaos, react
Published: 2020-08-12 08:45:09

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](https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmichelenasti.com%2Fimages%2Feslint.png&f=1&nofb=1)

[**ESLint**](https://eslint.org/) 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](https://flow.org/) and [TypeScript](https://www.typescriptlang.org/), as well as for mobile development with [React Native](https://reactnative.dev/).

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](https://eslint.org/docs/developer-guide/architecture/dependency.svg)
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](https://eslint.org/docs/developer-guide/architecture)

---

#### **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](https://res.cloudinary.com/practicaldev/image/fetch/s--jU91rNXg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9a1qur50nxb8hlp8w0nk.png)

Litmus is a framework for practicing chaos engineering in [cloud-native environments](https://dev.to/umamukkara/chaos-engineering-for-cloud-native-systems-2fjn). Litmus provides a chaos operator, a large set of chaos experiments on its [hub](https://hub.litmuschaos.io/), detailed [documentation](https://docs.litmuschaos.io/docs/getstarted/), and a friendly [community](https://litmuschaos.io/). Litmus is very easy to use; you can also set up a very [quick demo environment](https://dev.to/uditgaurav/get-started-with-litmuschaos-in-minutes-4ke1) 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
```

# **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
```
Once the package is installed we can use it to initialize an ESLint configuration by running 
```
eslint --init
```
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](https://scotch-res.cloudinary.com/image/upload/v1564425552/cubil4tc2ra35g4avhvk.png)

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](https://airbnb.io/javascript/react/) 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
```

>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
```

---

# **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"
```
This parser would leverage TypeScript ESTree to allow for ESLint to lint TypeScript source code. 

---

<p align="center">
  <img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.eskadenia.com%2FPortals%2FPortal1%2FUpload%2FBlock%2FImage%2Fconfiguration-settings.jpg&f=1&nofb=1" width="200" />

#### **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"]
```
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"]
```

---

#### **ESLint Plugin**
Next we move on to adding the eslint-plugin.
```
"plugins": ["@typescript-eslint"]
```
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](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin). 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"
}
```

---

<p align="center">
  <img src="https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2Fbocceballpro.com%2Fwp-content%2Fuploads%2F2015%2F09%2Frules.jpg&f=1&nofb=1" width="200" />

#### **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
      }
    ]
}
```
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
}
```
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.

---

<p align="center">
  <img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn1.iconfinder.com%2Fdata%2Ficons%2Fflat-web-browser%2F100%2Fsettings-512.png&f=1&nofb=1" width="200
" />

#### **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"]
      }
    }
  }
```
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
      }
    ]
  }
}
```

---

# **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.

<p align="center">
  <img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fgetfullyfunded.com%2Fwp-content%2Fuploads%2F2015%2F12%2Fthank-you-2.jpg&f=1&nofb=1" width="300" />

Are you an [SRE](https://en.wikipedia.org/wiki/Site_Reliability_Engineering) or a [Kubernetes](https://kubernetes.io/) enthusiast? Does [Chaos Engineering](https://en.wikipedia.org/wiki/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)

<p align="center">
  <img src="https://i.ibb.co/XtpJ3Wz/Litmus.png" width="1500" />

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

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