Migrating and configuring Eslint with Angular 11
Tags: angular, frontend, eslint, webdev
Published: 2020-11-30 16:47:13

In this post we will walk through migrating and configuring an Angular 11 project to utilize `ESLint` and as a bonus add the `Prettier formatter`.

**[21/01/2021 Update]:** *See the Prettier session.*
**[05/02/2021 Update]:** *Fixed Prettier parser error in HTML, see the Prettier configuration.*
**[27/02/2021 Update]:** *Updated ESLint config to work with `eslint-config-prettier 8.x`*

# Introduction

With [Angular 11 release](https://blog.angular.io/version-11-of-angular-now-available-74721b7952f7) it was announced that the [TSlint](https://palantir.github.io/tslint/) (deprecated in 2019) linter was to be replaced by [ESLint](https://eslint.org) and there was a [3rd-party solution](https://github.com/angular-eslint/angular-eslint) to help with the migration as well as specific Angular linting rules for `ESLint`.

I will use a new project generated by the Angular CLI v11.0.2 as example, though it should be very straightforward to migrate an already existing project provided it doesn't use other tools that integrates with `TSlint`. The team at `eslint-angular` made a very good job of automating the process.

# Migration

To do the migration we first need to install the `convert-tslint-to-eslint` schematic. Run the following in the root folder of your project(s):
```
ng add @angular-eslint/schematics
```
Now you have to choose the project you want to migrate. Then run the schematic as follows, replacing the `{{}}` bit for your project name:
```
ng g @angular-eslint/schematics:convert-tslint-to-eslint {{YOUR_PROJECT_NAME_GOES_HERE}}
```
What the schematics will do is look at the chosen project's `tslint.json` and try to match your TSlint rules with ESLint rules in a new file `.eslintrc.json`, adjust your Angular configurations to use ESLint instead of TSlint as well as replace `tslint:disable` comments to their ESLint equivalent.
**Pay attention to your terminal output, any rules that it can't match or if it needed to install any additional dependencies will be shown there.**

And that's it, the migration should be over. If you are feeling brave you can delete the `tslint.json` file and uninstall both `tslint` and `codelyzer` from your project or test to see if it works and delete them later!

# Customizing ESLint

If you already had customized your TSlint rules, then the schematics should have taken care of converting them to ESLint equivalents. However if it couldn't do it or if you don't like the current rules, you can easily modify your configurations. First let's take a look at how ESLint configurations are structured.

### ESLint configuration structure

ESLint allows for heavy customization. It allows for plugins, different parsers, overrides, extending from others configurations defined elsewhere and more. I will cover the basics to allow us to understand what we are doing and if you want to learn more feel free to look at the [docs](https://eslint.org/docs/user-guide/configuring).

Let's take a look at the configuration that was generated from a new CLI project:
```json
.eslintrc.json
{
  "root": true,
  "ignorePatterns": [
    "projects/**/*"
  ],
  "overrides": [
    {
      "files": [
        "*.ts"
      ],
      "parserOptions": {
        "project": [
          "tsconfig.json",
          "e2e/tsconfig.json"
        ],
        "createDefaultProgram": true
      },
      "extends": [
        "plugin:@angular-eslint/ng-cli-compat",
        "plugin:@angular-eslint/ng-cli-compat--formatting-add-on",
        "plugin:@angular-eslint/template/process-inline-templates"
      ],
      "rules": {
        "@angular-eslint/component-selector": [
          "error",
          {
            "type": "element",
            "prefix": "app",
            "style": "kebab-case"
          }
        ],
        "@angular-eslint/directive-selector": [
          "error",
          {
            "type": "attribute",
            "prefix": "app",
            "style": "camelCase"
          }
        ]
      }
    },
    {
      "files": [
        "*.html"
      ],
      "extends": [
        "plugin:@angular-eslint/template/recommended"
      ],
      "rules": {}
    }
  ]
}
```

Notice that most of the configuration is inside the `overrides` field. This is because in an Angular project we have Typescript and HTML files. So each file type that we want to lint will need different parsers and plugins. To avoid conflicts, ESLint provides us with the `overrides` field to allows us to separate the rules for different file types (notice the `*.html` and `*.ts` in the `files` array of each entry of the `overrides` array).

Another important field to look at is the `extends` field. It allows us to utilize configurations defined elsewhere inside this file. These other configuration files can be created by us or installed via npm or from the internet in general. A very popular configuration is the [AirBnB's](https://www.npmjs.com/package/eslint-config-airbnb-typescript) one.

In my configuration above, we see configurations that come with the `@angular-eslint` plugin: `"plugin:@angular-eslint/ng-cli-compat"` and `"plugin:@angular-eslint/ng-cli-compat--formatting-add-on"`. These two configurations were created to make it easy for the `@angular-eslint` team to do the automatic matching of TSlint rules and ESLint ones. I find them weak, for example: it won't warn or show as error unused variables. If we only want to change a few rules, then we can just use the `rules` field. I want a more drastic change so I will utilize other configurations such as `@angular-eslint/recommended`, which `@angular-eslint` team [recommends](https://github.com/angular-eslint/angular-eslint/tree/master/packages/eslint-plugin/src/configs).

### Changing the configurations

First I will remove both `"plugin:@angular-eslint/ng-cli-compat"` and `"plugin:@angular-eslint/ng-cli-compat--formatting-add-on"` from the `"extends"` field and add the `"plugin:@angular-eslint/recommended"`. **Make sure you are making the modifications in the Typescript entry of `overrides`**.

For now, our configuration looks like this:

```json
"extends": [
        "plugin:@angular-eslint/recommended",
        "plugin:@angular-eslint/template/process-inline-templates"
      ],
```

The standard Typescript rules, parser and configurations for ESLint with Typescript comes from [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint). The migration schematics already installed it for us, since `@angular-eslint` uses it under the hood. I will then extends the following configurations: `eslint:recommended`, `plugin:@typescript-eslint/recommended` and `plugin:@typescript-eslint/recommended-requiring-type-checking`. You can see what these configs rules are in these links: [eslint:recommended](https://eslint.org/docs/rules/), [typescript-eslint/recommended](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/src/configs/recommended.ts) and [typescript-eslint/recommended-requiring-type-checking](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/src/configs/recommended-requiring-type-checking.ts), but a brief explanation is that `eslint:recommended` adds some basic rules such as [no unused variables](https://eslint.org/docs/rules/no-unused-vars), `typescript-eslint/recommended` disables some conflicting rules from `eslint:recommended` for usage with Typescript and adds some general Typescript rules, at last `typescript-eslint/recommended-requiring-type-checking` adds some types rules. The configuration looks like this:
```json
"extends": [
        "plugin:@angular-eslint/recommended",
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "plugin:@angular-eslint/template/process-inline-templates"
      ],
```

**The order matters**. If we had included `typescript-recommended` before `eslint:recommended`, then the conflicting rules would be enabled.

### Test the configuration

Check to see if everything is working. For example, in your configuration above, the [no unused variables](https://eslint.org/docs/rules/no-unused-vars) is enabled, so open a Typescript file and create a new variable and check if the linting works.

![linting test](https://dev-to-uploads.s3.amazonaws.com/i/lz70evddibpbvjl5hn8j.png)

In the image above, I'm using VSCode editor, you can install an [extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) on it so that it runs the linter inside the editor and show errors while you type.

If you would like to change specific rules, you can do so at the `"rule"` entry.

# Bonus: Adding Prettier

**[21/01/2021 Update]:** *There are problems with the inline-templates plugin and prettier, see this [issue](https://github.com/angular-eslint/angular-eslint/issues/105). If you use inline-templates, then I would recommend changing to external templates or don't do the prettier integration for now.*

First of all, what is [Prettier](https://prettier.io)? It is an opinionated code formatter. And the best of all is that you can enable it to run when ESLint lints your code or in your CI pipeline! No more declined PRs because of bad formatting, just agree on a set of rules with your team and let it do the formatting for you.

### Installing dependencies

We will need to add 3 dependencies (as dev dependencies) to our project: `prettier`, `eslint-config-prettier` and `eslint-plugin-prettier`. 
```
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
```
They are needed for doing the formatting but also disabling some formatting rules of ESLint so that there are no conflicts between Prettier and ESLint.

### Integrating Prettier and ESLint

**[27/02/2021 Update]:** *In `eslint-config-prettier` version `8`, there is no need to extend `prettier/@typescript-eslint` anymore. If you are in a version below `8`, just add the entry before `plugin:prettier/recommended`.*

Now on the `.eslintrc.json` file, we just need to add the plugins to our `"extends"` field:
```json
"extends": [
        "plugin:@angular-eslint/recommended",
        "plugin:@angular-eslint/template/process-inline-templates",
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "plugin:prettier/recommended"
      ],
```

If you want to enable the formatting in your `.html` files, then you need to add these two new lines in the HTML entry of the `"overrides"` field.

```json
"extends": [
        "plugin:@angular-eslint/template/recommended",
        "plugin:prettier/recommended"
      ],
```

**IMPORTANT**: The prettier entries should be at the end of the `"extends"` array and in the order above. This is so that the prettier config disables ESLint rules that conflicts with its own rules.

### Optional: Customizing Prettier

Although Prettier is opinionated and comes with defaults, you can do some customizations. For that we need to create a `.prettierrc` file (you can also create the file as `.js` or `.json`) in the root folder and put the configurations that we want. You can see the options [here](https://prettier.io/docs/en/options.html).

My current options are:
```json
{
    "tabWidth": 4,
    "useTabs": true,
    "semi": true,
    "singleQuote": false,
    "quoteProps": "as-needed",
    "trailingComma": "none",
    "bracketSpacing": true,
    "arrowParens": "always",
    "overrides": [
        {
            "files": "*.component.html",
            "options": {
                "parser": "angular"
            }
        },
        {
            "files": "*.html",
            "options": {
                "parser": "html"
            }
        }
    ]
}
```

**[05/02/2021 Update]:** For some reason, Prettier wasn't able to decide a parser for `*.component.html` files. To solve this the `overrides` section above was added to the `.prettierrc` to force it to use a parser. Thanks @singhshubham97 for pointing this out.

# Final configuration
```json
{
  "root": true,
  "ignorePatterns": [
    "projects/**/*"
  ],
  "overrides": [
    {
      "files": [
        "*.ts"
      ],
      "parserOptions": {
        "project": [
          "tsconfig.json",
          "e2e/tsconfig.json"
        ],
        "createDefaultProgram": true
      },
      "extends": [
        "plugin:@angular-eslint/recommended",
        "plugin:@angular-eslint/template/process-inline-templates",
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "plugin:prettier/recommended"
      ],
      "rules": {
        "@angular-eslint/component-selector": [
          "error",
          {
            "type": "element",
            "prefix": "app",
            "style": "kebab-case"
          }
        ],
        "@angular-eslint/directive-selector": [
          "error",
          {
            "type": "attribute",
            "prefix": "app",
            "style": "camelCase"
          }
        ]
      }
    },
    {
      "files": [
        "*.html"
      ],
      "extends": [
        "plugin:@angular-eslint/template/recommended",
        "plugin:prettier/recommended"
      ],
      "rules": {}
    }
  ]
}
```

# Conclusion

In this article we saw how to migrate a TSlint Angular project to an ESLint one. We did just some basic customization, we could have added linting for `.css` or `.scss` files or specific linting for your `.spec.ts` files. I recommend you taking a look at the ESLint ecosystem and configuring it to your liking!