⚙️ Migrate Angular app to ESLint with Prettier, AirBnB Styleguide, Husky and lint-staged
Tags: angular, javascript, webdev, tutorial
Published: 2020-05-12 20:55:14

---
description: How to migrate Angular TypeScript app from TSLint to ESLint, add AirBnB Styleguide, add Prettier, configure Git hooks and VS Code.
---
As you know, [Palantir decided](https://medium.com/palantir/tslint-in-2019-1a144c2317a9) to deprecate TSLint and focus on the improvement of TypeScript support in ESLint in order to avoid the developing of similar tools.

Although Angular 10 [will not be migrated to ESLint](https://github.com/angular/angular-cli/issues/13732#issuecomment-612982932), I decided to migrate some projects to ESLint and at the same time revise style guides and add some new tools. This post is about how to migrate Angular TypeScript app from TSLint to ESLint, add Prettier, configure Git hooks and VS Code.

## Prettier & ESLint
ESLint is a tool for static code analysis. Rules in ESLint fall into two groups:
- **Formatting** — to transform code in consistent style: string length, commas, semicolons and other.
- **Code quality** — to search and fix problem code patterns: unnecessary code, errors.

Prettier is an opinionated code formatter that can automatically format code on file save with no configuration.

The question I am interested in: why do we need to use Prettier together with ESLint, if ESLint can do all the things by itself? The answer is pretty easy — Prettier formats code much better. It removes all formatting and reprints code in the consistent style from scratch. This allows developers to forget about formatting the code and not waste time discussing the code style on code reviews.

For example, we have this long string of code:
```javascript
const example = ['1', 'long string', 'another string', 0123456789, '1', 'long string', 'another string'];
```

If we try to format this string with ESLint, it just throws an error in console:
```bash
eslint example.ts --fix

output:
error    This line has a length of 105. Maximum allowed is 80
```

This example shows that linters don't always help with code formatting. And so developers format code by themselves in different ways, depending on their personal consideration.

If we save and format the file with Prettier, the string will be reprinted to:
```javascript
const example = [
  '1',
  'long string',
  'another string',
  0123456789,
  '1',
  'long string',
  'another string'
];
```

Prettier provides consistent formatting style through a whole code base. Therefore it must be used together with ESLint. However, we have to configure them so that they don't conflict with each other.

## Configuring ESLint
ESLint works with parsers that transform code into **AST** (Abstract Syntax Tree) for software processing and plugins, which contain rules, for example, recommended rules for linting TypeScript or rules from style guides.

### Dependency installation
To migrate Angular app to ESLint we will use these dependencies:
- `@angular-eslint/builder` — Angular CLI Builder to run ESLint for Angular apps with standart command `ng lint`,
- `@angular-eslint/eslint-plugin` — plugin with rules for linting Angular apps,
- `@angular-eslint/template-parser` — parser, which in conjunction with `@angular/compiler` makes it possible to write and use rules for linting Angular templates,
- `@angular-eslint/eslint-plugin-template` — plugin, which in conjunction with `@angular-eslint/template-parser`, run rules to lint Angular templates,
- `@typescript-eslint/parser` — plugin to parse TypeScript code,
- `@typescript-eslint/eslint-plugin` — plugin, which run rules to lint TypeScript.

To install them just run:
```bash
ng add @angular-eslint/schematics
```

At the moment, not all ESLint rules from basic TSLint configuration by Codelyzer have equivalents in `@typescript-eslint` and `@angular-eslint`, but most of them are already there. You can track current state of rules development in official monorepos [Angular ESLint](https://github.com/angular-eslint/angular-eslint) and [TypeScript ESLint](https://github.com/typescript-eslint/typescript-eslint).

### Configuring
We have installed everything we need to lint Angular app, now we can start configuring ESLint. Let's create a configuration file `.eslintrc.js` in app root and add [recommended settings](https://github.com/angular-eslint/angular-eslint/blob/master/packages/integration-tests/fixtures/angular-cli-workspace/.eslintrc.json) from Angular ESLint:

```javascript
module.exports = {
  root: true,
  overrides: [
    {
      files: ["*.ts"],
      parserOptions: {
        project: [
          "tsconfig.*?.json",
          "e2e/tsconfig.json"
        ],
        createDefaultProgram: true
      },
      extends: ["plugin:@angular-eslint/recommended"],
      rules: {
        ...
      }
    },
    {
      files: ["*.component.html"],
      extends: ["plugin:@angular-eslint/template/recommended"],
      rules: {
        "max-len": ["error", { "code": 140 }]
      }
    },
    {
      files: ["*.component.ts"],
      extends: ["plugin:@angular-eslint/template/process-inline-templates"]
    }
  ]
}
```

> You can create config in different extensions: JS, JSON and YAML. In JavaScript you can write comments.

`plugin:@angular-eslint/recommended` containes rules for three plugins: `@typescript-eslint/eslint-plugin`, `@angular-eslint/eslint-plugin` and `@angular-eslint/eslint-plugin-template`.

### Update ng lint command
Also we need to update `ng lint` command in `angular.json` to run `@angular-eslint/builder`:

```json
"lint": {
  "builder": "@angular-eslint/builder:lint",
  "options": {
    "lintFilePatterns": [
      "src/**/*.ts",
      "src/**/*.component.html"
    ]
  }
},
```

The basic setup is ready. Now, to start ESLint you just need to run `ng lint`.

## Install additional ESLint Plugins

If you want to install another plugin for ESLint, for example, to lint Jasmine spec files, install appropriate npm-package:
```bash
npm install eslint-plugin-jasmine --save-dev
```

And add a new block of rules in "overrides" for files with `*.spec.ts` extension:

```javascript
overrides: [
  ...,
  {
    files: ['src/**/*.spec.ts', 'src/**/*.d.ts'],
    parserOptions: {
      project: './src/tsconfig.spec.json',
    },
    // Jasmine rules
    extends: ['plugin:jasmine/recommended'],
    // Plugin to run Jasmine rules
    plugins: ['jasmine'],
    env: { jasmine: true },
    // Turn off 'no-unused-vars' rule
    rules: {
      '@typescript-eslint/no-unused-vars': 'off'
    }
  }
],
```
You can add this way any plugin to your ESLint configuration.

## Add Style Guides rules
For better code base consistency, let's choose and add to ESLint config rules from one of popular style guides:
- [AirBnB](https://github.com/airbnb/javascript): most popular and strict from these three, requires trailing commas and semicolons,
- [Google](https://github.com/google/gts): has a lot in common with AirBnB but less strict, requires JSDoc.
- [StandartJS](https://github.com/standard/eslint-config-standard-with-typescript): forbids using of trailing commas and semicolons.

Choose style guide that fit better to your team requirements. You can try each style guide on one of your projects, looking at what errors are showed by the linter and make a decision which rules you agree with.

> Choose TypeScript realization of style guides rules, because rules for JavaScript can work wrong with TypeScript.

For example, let's add to ESLint config rules from AirBnB Styleguide. To do this we need to install config with AirBnB rules for TypeScript and plugin to work with import/export syntax:

```bash
npm install eslint-plugin-import eslint-config-airbnb-typescript --save-dev
```

In order not to change top level settings, add AirBnB Styleguide rules in a block of the `['*.ts']` rules in "overrides" property:

```javascript
module.exports = {
  ...,
  overrides: [
    {
      files: ["*.ts"],
      parserOptions: {
        project: [
          "tsconfig.*?.json",
          "e2e/tsconfig.json"
        ],
        createDefaultProgram: true
      },
      extends: [
        "plugin:@angular-eslint/recommended",
        // AirBnB Styleguide rules
        'airbnb-typescript/base'
      ],
      rules: {
        ...
      }
    },
    ...
  ]
}
```

If you want to use another style guide, create a new block of the rules in "overrides" property with style guide rules and necessary parser to run them as in an example. 

### Rules customization
If you want to turn off or redefine some of the rules, you can do it in "rules" property:
```javascript
module.exports = {
  ...,
  overrides: [
    {
      files: ["*.ts"],
      parserOptions: {
        project: [
          "tsconfig.*?.json",
          "e2e/tsconfig.json"
        ],
        createDefaultProgram: true
      },
      extends: [
        "plugin:@angular-eslint/recommended",
        // AirBnB Styleguide rules
        'airbnb-typescript/base'
      ],
      rules: {
        // Custom rules
        'import/no-unresolved': 'off',
        'import/prefer-default-export': 'off',
        'class-methods-use-this': 'off',
        'lines-between-class-members': 'off',
        '@typescript-eslint/unbound-method': [
          'error',
          {
            ignoreStatic: true,
          }
        ]
      }
    },
    ...
  ]
}
```

## Configuring Prettier
To add Prettier in ESLint config, we need to install Prettier itself, plugin with Prettier rules and config that turns off all rules that conflict with Prettier:

```bash
npm i prettier eslint-config-prettier eslint-plugin-prettier --save-dev
```

In ESLint config "overrides" in the bottom of "extends" property in the block of rules for files with `.ts` extension add Prettier settings:

```javascript
module.exports = {
  ...,
  overrides: [
    {
      files: ["*.ts"],
      parserOptions: {
        project: [
          "tsconfig.*?.json",
          "e2e/tsconfig.json"
        ],
        createDefaultProgram: true
      },
      extends: [
        "plugin:@angular-eslint/recommended",
        // AirBnB Styleguide rules
        'airbnb-typescript/base',
        // Settings for Prettier
        'prettier/@typescript-eslint',
        'plugin:prettier/recommended'
      ],
      rules: {
        ...
      }
    },
    ...
  ]
}
```

Configuration for Prettier should always be in the bottom of "extends" property, to turn off all the previous rules, which can conflict with Prettier.

`prettier/@typescript-eslint` turns off rules from `@typescript-eslint`, which can conflict with Prettier, and `plugin:prettier/recommended` does three things:
- enables `eslint-plugin-prettier`,
- marks `prettier/prettier` rules as `"error"`,
- adds Prettier formatting rules `eslint-config-prettier`.


### Prettier config
Prettier can format files with no configuration but for AirBnB code guide we need to specify some settings. Create `.prettierrc.js` in app root folder:

```javascript
module.exports = {
  trailingComma: "all",
  tabWidth: 2,
  semi: true,
  singleQuote: true,
  bracketSpacing: true,
  printWidth: 100
};
```

This configuration will be used by ESLint and by Prettier if you want to run it separately. You can format your code with Prettier itself with `prettier --write .` or with Prettier Plugin for VS Code.

## Configuring VS Code
VS Code can highlight errors which are found by ESLint and fix them on file save. To enable this feature install [ESLint plugin](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) for VS Code and create a file with configuration for workspace `.vscode/settings.json`:
```json
  "eslint.validate": [ "javascript", "typescript", "html"],

  "eslint.options": {
    "extensions": [".js", ".ts", "html"]
  },

  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
  },
```

You can also install [Prettier plugin](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) for VS Code to format files with `shift + option + f` with Prettier.

## Configuring Git Hooks
Git Hooks are scripts, which Git calls on certain events: commit, push, recieve.

With Hooks we can run linting on commit creation to reduce errors falls into pull requests. For better work with Git Hooks install [Husky](https://github.com/typicode/husky), and to lint only staged files (it is necessary on big projects where linting can be very long) [lint-staged](https://github.com/okonet/lint-staged):

```bash
npm i husky lint-staged --save-dev
```

Add new settings for this plugins in `package.json`:

```json
"scripts": {
  ...,
},
"husky": {
  "hooks": {
    "pre-commit": "lint-staged --relative"
  }
},
"lint-staged": {
  "*.{js,ts}": [
     "eslint --fix"
  ]
},
```

Lint-staged sends to the called command array of staged files. `ng lint` can't accept arrays of files and to use it we have to write an additional handler script. Or we can just run ESLint like in this example. You can use this solution for precommits, and `ng lint` for linting all files of project, for example, in CI pipeline.

## Final thoughts
In the future versions of Angular ESLint will be out of the box. Now ESLint configuring requires some additional actions, some of the rules don't have equivalent in ESLint and Angular ESLint monorepo is in alpha version. So, whether to migrate to ESLint or not — is up to you.

However, code guides, additional rules, Prettier, Husky and lint-staged you should install by yourself. I hope this article give you understanding of how this things work together. You can find configured Angular project on [Github](https://github.com/bzvyagintsev/angular-boilerplate).

Linters set up can look like a very trivial task but include some important organizational questions: what style guide to choose, what the rules do plugins include and how they work together. It is worth spending your time on this process, because it will save a lot of time on discussing code style on code reviews, provide code base consistency and reduce number or errors sent in pull requests.

Thank you for reading! Don't hesitate to leave a comment if you have any questions or additions.