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 it was announced that the TSlint (deprecated in 2019) linter was to be replaced by ESLint and there was a 3rd-party solution 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.
Let's take a look at the configuration that was generated from a new CLI project:
.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 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.
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:
"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. 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, typescript-eslint/recommended and typescript-eslint/recommended-requiring-type-checking, but a brief explanation is that eslint:recommended
adds some basic rules such as no unused variables, 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:
"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 is enabled, so open a Typescript file and create a new variable and check if the linting works.
In the image above, I'm using VSCode editor, you can install an extension 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. 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? 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:
"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.
"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.
My current options are:
{
"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
{
"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!
Top comments (51)
Hello Giovanni ! Thank you for this complete, up-to-date and beginner-friendly tutorial !
I followed the whole tutorial, but here :
"Test the configuration
Check to see if everything is working. For example, in your configuration above, the no unused variables is enabled, so open a Typescript file and create a new variable and check if the linting works."
I installed ESLint VSCode extension wrote an unused var in order to test my config (same as yours), and it didn't get red, so I couldn't check if the linting works . Maybe you could know why it didn't work for me ?
Thanks a lot,
Ben
Hi Ben, thanks for the compliments!
Hmm, thats odd, try running the
ng lint
command. If it works then there is some setting in the extension that you need to see (probably the lint on save one). If it doesn't then reply and I will be glad to helpHi Giovanni, thank you for your quick reply !
When I run ng lint, I do have errors and warnings, and unused vars belong to the warning category - that might be the reason why it doesn't get red when I write an unused var. That's weird anyway, because I just followed your tuto.
Yeah, in the config above unused vars is a warning. You can make it an error in the
rules
section, see this. I don't think you will need to disable the core ESLint rule since@typescript-eslint/recommended
already does this, so just add the"@typescript-eslint/no-unused-vars": ["error"]
lineHi, after eslint setup should we delete the lint
@angular-eslint/schematics": "1.0.0",
from package.json? Otherwise when npm install it warns that TSLINT is deprecated.
Finding which package depends on it:
npm ls tslint
-- @angular-eslint/schematics@1.0.0
-- tslint-to-eslint-config@2.0.1`-- tslint@6.1.3
tkx
I don't think it would cause any problems to uninstall the schematics after the transition.
Hi,
Once everything has been configured, is there a way to run it, or add it to a github hook?
Before, I could run
npm lint
, where that would runng lint
and I could use that as needed. I tried runningeslint
from the terminal, but it did not give me any errors. (I know I have errors though, bc Webstorm is letting me know)solution - in angular.json, change
"builder" : "@angular-devkit/build-angular:tslint"
to"builder": "@angular-eslint/builder:lint"
Show! but SLint couldn't find the config "prettier/@typescript-eslint" to extend from. Please check that the name of the config is correct. How solve this issue ?
Did you install the dependencies
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
?Hi, yes, i did...
eslintrc.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",
"prettier",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
command
$ eslint --color -c .eslintrc.json --ext .ts ./src/
result
Oops! Something went wrong! :(
ESLint: 7.20.0
ESLint couldn't find the config "prettier/@typescript-eslint" to extend from. Please check that the name of the config is correct.
The config "prettier/@typescript-eslint" was referenced from the config file in ".eslintrc.json".
If you still have problems, please stop by eslint.org/chat/help to chat with the team.
log
0 info it worked if it ends with ok
1 verbose cli [
1 verbose cli 'C:\Program Files\nodejs\node.exe',
1 verbose cli 'C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js',
1 verbose cli 'run',
1 verbose cli 'eslint'
1 verbose cli ]
2 info using npm@6.14.11
3 info using node@v14.15.5
4 verbose run-script [ 'preeslint', 'eslint', 'posteslint' ]
5 info lifecycle app@1.0.0~preeslint: app@1.0.0
6 info lifecycle app@1.0.0~eslint: app@1.0.0
7 verbose lifecycle app@1.0.0~eslint: unsafe-perm in lifecycle true
8 verbose lifecycle app@1.0.0~eslint: PATH: C:\Program Files\nodejs\node_modules\npm\node_modules\npm-lifecycle\node-gyp-bin;D:\SISTEMAS\CLOUDCLASS\cloudclass-app\node_modules.bin;C:\Program Files\PowerShell\7;C:\Program Files (x86)\Common Files\Intel\Shared Libraries\redist\intel64\compiler;C:\Program Files\Python27\;C:\Program Files\Python27\Scripts;C:\Windows;C:\Windows\system32;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\ProgramData\chocolatey\bin;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;C:\Program Files (x86)\Firebird\Firebird_2_5\bin;D:\SSL\BIN;D:\SSH;D:\SSH\tools;D:\SVN;D:\UTIL;D:\DLL;C:\Program Files\dotnet\;C:\Program Files\Git LFS;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\Program Files\Common Files\Tom Sawyer Software\10.0.0;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\PowerShell\7-preview\preview;C:\Program Files\Java\bin;C:\Program Files\Docker\Docker\resources\bin;C:\ProgramData\DockerDesktop\version-bin;C:/Program Files (x86)/CS DEVICES/SatCare/Biblioteca de funções;C:\Program Files\PowerShell\7\;C:\Program Files\nodejs\;C:\Program Files\Git\cmd;C:\Users\dener\AppData\Local\Microsoft\WindowsApps;C:\Users\dener\AppData\Local\Programs\Microsoft VS Code Insiders\bin;C:\Program Files\kdiff3;C:\Users\dener.dotnet\tools;C:\Users\dener\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\dener\AppData\Local\Microsoft\WindowsApps;C:\Users\dener\AppData\Roaming\DBeaverData\drivers\clients\postgresql\win\12;C:\Users\dener.dotnet\tools;d:\harbour\bin;d:\bcc\bin;;C:\Program Files (x86)\CS DEVICES\SatCare\Biblioteca de funções;C:\Users\dener\AppData\Roaming\npm
9 verbose lifecycle app@1.0.0~eslint: CWD: D:\SISTEMAS\CLOUDCLASS\cloudclass-app
10 silly lifecycle app@1.0.0~eslint: Args: [ '/d /s /c', 'eslint --color -c .eslintrc.json --ext .ts ./src/' ]
11 silly lifecycle app@1.0.0~eslint: Returned: code: 2 signal: null
12 info lifecycle app@1.0.0~eslint: Failed to exec eslint script
13 verbose stack Error: app@1.0.0 eslint:
eslint --color -c .eslintrc.json --ext .ts ./src/
13 verbose stack Exit status 2
13 verbose stack at EventEmitter. (C:\Program Files\nodejs\node_modules\npm\node_modules\npm-lifecycle\index.js:332:16)
13 verbose stack at EventEmitter.emit (events.js:315:20)
13 verbose stack at ChildProcess. (C:\Program Files\nodejs\node_modules\npm\node_modules\npm-lifecycle\lib\spawn.js:55:14)
13 verbose stack at ChildProcess.emit (events.js:315:20)
13 verbose stack at maybeClose (internal/child_process.js:1048:16)
13 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:288:5)
14 verbose pkgid app@1.0.0
15 verbose cwd D:\SISTEMAS\CLOUDCLASS\cloudclass-app
16 verbose Windows_NT 10.0.21318
17 verbose argv "C:\Program Files\nodejs\node.exe" "C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js" "run" "eslint"
18 verbose node v14.15.5
19 verbose npm v6.14.11
20 error code ELIFECYCLE
21 error errno 2
22 error app@1.0.0 eslint:
eslint --color -c .eslintrc.json --ext .ts ./src/
22 error Exit status 2
23 error Failed at the app@1.0.0 eslint script.
23 error This is probably not a problem with npm. There is likely additional logging output above.
24 verbose exit [ 2, true ]
if comment "prettier/@typescript-eslint", works fine
I have the same problem and I install these dependendencies
@stativka @denernun
I discovered the problem,
eslint-config-prettier
was updated yesterday and they changed somethings. Now you only need to extendplugin:prettier/recommended
. So just remove both entries ofprettier/@typescript-eslint
from the config file and it should work.Thank you for your prompt reply and for this article!
this did it for me , i must have missed the other dependencies
Hi, thanks for the informative and very helpful post :)
when I run the ng lint command I get errors that relate to type any of the angular form's value
Thrown error: Unsafe member access [curr] on an any value - @typescript-eslint/no-unsafe-member-access
Do I need to change any eslint - typescript configuration in order to avoid this type of errors?
error log
code snippet
eslintrc.json
great article. I did something similar to my angular12 projetct. But i don't think
plugin:@typescript-eslint
is required anymore. angular-eslint alone works nicely for me since there is no tslint in angular 12 anywaysGreat article. However...
"plugin:@angular-eslint/template/process-inline-templates"
seems to extract an inline template and feed it to the html linter
The prettier extension for *.html ("plugin:prettier/recommended") produces errors regarding the code formatting. The indicated error position is wrong and you won't be able to resolve this anyhow inside your inline template.
Either use external templates or stop using the process-inline-templates extension. It does not seem to kill all linting as I still noticed a warning for a missing closing tag. What exactly you do loose, I don't know.
This issue seems related:
github.com/angular-eslint/angular-...
Yeah, I don't use inline templates so I didn't notice anything related to that, I will link this issue in the article. Thanks for the head up
Is it possible to run this migration without
ng
? I have an existing Angular project withoutng
. Retrofitting anng
workspace seems like a science project.Automatically I dont think so because of the schematics. You can however do the migration full manual, see the repo
Agree with the other person about .scss requires a link!
SCSS is difficult to find ESLINT that works in Angular. For example:
a {
@include flexbox(row)
height: 100%;
padding: 20px;
border-radius: 3px;
}
The above should be picked up in linting (missing semicolon on flexbox) but ESLINT doesn't.
If I open the file vscode-prettier or eslint IS picking it up.
I have tried (npmjs.com/package/eslint-plugin-cs...) but doesn't appear to trigger:
"overrides": [
{
// TODO: this isn't triggering on flexbox semicolon errors
"files": "*.scss",
"plugins": ["css-modules"],
"extends": ["plugin:css-modules/recommended"],
"rules": {
"css-modules/no-unused-class": [2, { "camelCase": true }],
"css-modules/no-undef-class": [2, { "camelCase": true }]
}
},
Unless I miss something, it looks like this change removes the TSlint requirement of semi-colons on the end of lines? And mis-aligned statements (whitespace)? Is there a way to get this back?
This config enforces semi-colons at the end of lines and should fix alignment with Prettier. Did you run the eslint command or are trying to see in your editor? In the latter case, install the eslint extension to allow for the editor to lint when you save etc
This is what I had to add to my eslintrc.json to get the behavior I wanted:
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
"error",
"single",
{
"allowTemplateLiterals": true
}
],
"@typescript-eslint/semi": [
"error",
"always"
],
(copied from ./node_modules/@angular-eslint/eslint-plugin/dist/configs/ng-cli-compat--formatting-add-on.json)
I didn’t install the prettier part (I don’t like it’s default settings and some I can’t configure). I have the eslint extension in VSCode but it does not show an error if I don’t have a semi. Before I made the changes you suggest for the “recommended” settings, it does show a message for missing semi’s. Once I made the changes, that setting is not there. I had to copy the rule from the “all” rules into my eslint config to get it to work. I also could not get it to mark mis-aligned lines (though formatting the document fixes that)
Fair enough, maybe eslint-angular is disabling these rules since I know that at least the
semi
one is enabled by@typescript-eslint/recommended
, will check it out later. Glad you could figure it out tho!As far as I can see, looking through the rules,
semi
is not enabled by@typescript-eslint/recommended
which extends:
the
no-extra-semi
rule just warns if there is more than one semi-colon (;;
)Some comments may only be visible to logged-in visitors. Sign in to view all comments.