DEV Community

Cover image for ⚙️ Migrate Angular app to ESLint with Prettier, AirBnB Styleguide, Husky and lint-staged
Bogdan Zvyagintsev
Bogdan Zvyagintsev

Posted on • Updated on

⚙️ Migrate Angular app to ESLint with Prettier, AirBnB Styleguide, Husky and lint-staged

As you know, Palantir decided 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, 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:

const example = ['1', 'long string', 'another string', 0123456789, '1', 'long string', 'another string'];
Enter fullscreen mode Exit fullscreen mode

If we try to format this string with ESLint, it just throws an error in console:

eslint example.ts --fix

output:
error    This line has a length of 105. Maximum allowed is 80
Enter fullscreen mode Exit fullscreen mode

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:

const example = [
  '1',
  'long string',
  'another string',
  0123456789,
  '1',
  'long string',
  'another string'
];
Enter fullscreen mode Exit fullscreen mode

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:

ng add @angular-eslint/schematics
Enter fullscreen mode Exit fullscreen mode

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 and 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 from Angular ESLint:

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"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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:

"lint": {
  "builder": "@angular-eslint/builder:lint",
  "options": {
    "lintFilePatterns": [
      "src/**/*.ts",
      "src/**/*.component.html"
    ]
  }
},
Enter fullscreen mode Exit fullscreen mode

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:

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

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

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'
    }
  }
],
Enter fullscreen mode Exit fullscreen mode

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: most popular and strict from these three, requires trailing commas and semicolons,
  • Google: has a lot in common with AirBnB but less strict, requires JSDoc.
  • StandartJS: 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:

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

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

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: {
        ...
      }
    },
    ...
  ]
}
Enter fullscreen mode Exit fullscreen mode

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:

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,
          }
        ]
      }
    },
    ...
  ]
}
Enter fullscreen mode Exit fullscreen mode

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:

npm i prettier eslint-config-prettier eslint-plugin-prettier --save-dev
Enter fullscreen mode Exit fullscreen mode

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

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: {
        ...
      }
    },
    ...
  ]
}
Enter fullscreen mode Exit fullscreen mode

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:

module.exports = {
  trailingComma: "all",
  tabWidth: 2,
  semi: true,
  singleQuote: true,
  bracketSpacing: true,
  printWidth: 100
};
Enter fullscreen mode Exit fullscreen mode

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 for VS Code and create a file with configuration for workspace .vscode/settings.json:

  "eslint.validate": [ "javascript", "typescript", "html"],

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

  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
  },
Enter fullscreen mode Exit fullscreen mode

You can also install Prettier plugin 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, and to lint only staged files (it is necessary on big projects where linting can be very long) lint-staged:

npm i husky lint-staged --save-dev
Enter fullscreen mode Exit fullscreen mode

Add new settings for this plugins in package.json:

"scripts": {
  ...,
},
"husky": {
  "hooks": {
    "pre-commit": "lint-staged --relative"
  }
},
"lint-staged": {
  "*.{js,ts}": [
     "eslint --fix"
  ]
},
Enter fullscreen mode Exit fullscreen mode

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.

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.

Oldest comments (17)

Collapse
 
omgitsshark profile image
omgitsshark

Nice! Great work!

Collapse
 
bzvyagintsev profile image
Bogdan Zvyagintsev

Thanks! 💪

Collapse
 
calleufuzi profile image
Calleu Fuzi

Nice! Ty for that Tutotial. Help me a lot.

Collapse
 
bzvyagintsev profile image
Bogdan Zvyagintsev

I'm glad it helped!

Collapse
 
yaircohendev profile image
Yair Cohen

Great tutorial, definitely helped me. Thank you.

Collapse
 
bzvyagintsev profile image
Bogdan Zvyagintsev

Thanks for your comment :)

Collapse
 
tmmschmit profile image
tmmschmit

Hi, I followed your tutorial with Angular 10, and I got an error on the first line of each files :

Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: src/index.html.
The extension for the file (.html) is non-standard. You should add "parserOptions.extraFileExtensions" to your config.eslint
Enter fullscreen mode Exit fullscreen mode

or

Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: src/app/app.component.ts.
The file must be included in at least one of the projects provided.eslint
Enter fullscreen mode Exit fullscreen mode

Any idea? Thanks!

Collapse
 
tmmschmit profile image
tmmschmit

Ok, we just have to wait :-)

github.com/angular-eslint/angular-...

Collapse
 
bzvyagintsev profile image
Bogdan Zvyagintsev

👍

Collapse
 
mayankkalbhor profile image
Mayank K

Seems lot of work and libraries we need to install to achieve this. That gives me a thought is it worth it

Collapse
 
bzvyagintsev profile image

For basic ESLint setup you can run schematics, change builder and copy ESLint config from official Angular ESLint repo.

Collapse
 
yohandah profile image
Yohan Dahmani

Amazing tutorial thanks Bogdan !!!! Helped me a lot trough my process.

You mention that ESLint will be out-of-the-box in future versions of Angular and sadly this is not the case !

"That being said, it’s most likely that in future the CLI will not providing a linting solution out of the box, and it will be up to the users to choose a builder similar to how it's currently done for ng deploy."
github.com/angular/angular-cli/iss...

Collapse
 
bzvyagintsev profile image
Bogdan Zvyagintsev

Thanks for your comment and link to the discussion! I think @angular-eslint will be a common choice in the future.

Collapse
 
baluditor profile image
Baluditor

Finally a complete step by step easy to follow guide on how to achieve this. Thanks, Bogdan.

Collapse
 
bzvyagintsev profile image
Bogdan Zvyagintsev

I'm glad it helped!

Collapse
 
makcim392 profile image
Makcim

Thank you for your guide, I've followed several guides but none seemed to help me. You've been very helpful

Collapse
 
bzvyagintsev profile image
Bogdan Zvyagintsev

I'm happy that my article is still helpful, I need to update it :)