When it comes to linting TypeScript code, there are two major linting options to choose from: TSLint and ESLint. TSLint is a linter that can only be used for TypeScript, while ESLint supports both JavaScript and TypeScript.
In the TypeScript 2019 Roadmap, the TypeScript core team explains that ESLint has a more performant architecture than TSLint and that they will only be focusing on ESLint when providing editor linting integration for TypeScript. For that reason, I would recommend using ESLint for linting TypeScript projects.
Setting up ESLint to work with TypeScript
First, install all the required dev dependencies:
1yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --dev
If using create-react-app
to bootstrap a project, eslint
is already included as a dependency through react-scripts
, and therefore it is not necessary to explicitly install it with yarn
.
eslint
: The core ESLint linting library@typescript-eslint/parser
: The parser that will allow ESLint to lint TypeScript code@typescript-eslint/eslint-plugin
: A plugin that contains a bunch of ESLint rules that are TypeScript specific
Next, add an .eslintrc.js
configuration file in the root project directory. Here is a sample configuration for a TypeScript project:
1module.exports = {2 parser: "@typescript-eslint/parser", // Specifies the ESLint parser3 parserOptions: {4 ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features5 sourceType: "module" // Allows for the use of imports6 },7 extends: [8 "plugin:@typescript-eslint/recommended" // Uses the recommended rules from the @typescript-eslint/eslint-plugin9 ],10 rules: {11 // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs12 // e.g. "@typescript-eslint/explicit-function-return-type": "off",13 }14};
I prefer using a JavaScript file for the .eslintrc
file (instead of a JSON file) as it supports comments that can be used to better describe rules.
If using TypeScript with React, the eslint-plugin-react
dev dependency should be installed and the following configuration can be used:
1module.exports = {2 parser: "@typescript-eslint/parser", // Specifies the ESLint parser3 parserOptions: {4 ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features5 sourceType: "module", // Allows for the use of imports6 ecmaFeatures: {7 jsx: true // Allows for the parsing of JSX8 }9 },10 settings: {11 react: {12 version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use13 }14 },15 extends: [16 "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react17 "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin18 ],19 rules: {20 // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs21 // e.g. "@typescript-eslint/explicit-function-return-type": "off",22 },23};
Ultimately it's up to you to decide what rules you would like to extend from and which ones to use within the rules
object in your .eslintrc.js
file.
Adding Prettier to the mix
What works well along with ESLint is prettier, which does a great job at handling code formatting. Install the required dev dependencies to get prettier working with ESLint:
1yarn add prettier eslint-config-prettier eslint-plugin-prettier --dev
prettier
: The core prettier libraryeslint-config-prettier
: Disables ESLint rules that might conflict with prettiereslint-plugin-prettier
: Runs prettier as an ESLint rule
In order to configure prettier, a .prettierrc.js
file is required at the root project directory. Here is a sample .prettierrc.js
file:
1module.exports = {2 semi: true,3 trailingComma: "all",4 singleQuote: true,5 printWidth: 120,6 tabWidth: 47};
Next, the .eslintrc.js
file needs to be updated:
1module.exports = {2 parser: "@typescript-eslint/parser", // Specifies the ESLint parser3 parserOptions: {4 ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features5 sourceType: "module", // Allows for the use of imports6 ecmaFeatures: {7 jsx: true // Allows for the parsing of JSX8 }9 },10 settings: {11 react: {12 version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use13 }14 },15 extends: [16 "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react17 "plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin18 "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.19 ],20 rules: {21 // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs22 // e.g. "@typescript-eslint/explicit-function-return-type": "off",23 },24};
Make sure that plugin:prettier/recommended
is the last configuration in the extends
array
The advantage of having prettier setup as an ESLint rule using eslint-plugin-prettier
is that code can automatically be fixed using ESLint's --fix
option.
Automatically Fix Code in VS Code
For a good developer experience, it's useful to setup your editor to automatically run ESLint's automatic fix command (i.e. eslint --fix
) whenever a file is saved. Since i'm using VS Code, here is the config required in the settings.json
file in VS Code to get automatic fixing whenever saving a file:
1{2 "editor.codeActionsOnSave": {3 "source.fixAll.eslint": true4 },5}
Run ESLint with the CLI
A useful command to add to the package.json
scripts is a lint
command that will run ESLint.
1{2 "scripts": {3 "lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix"4 }5}
The above script can be run from the command line using npm run lint
or yarn lint
. This command will run ESLint through all the .js
, .ts
, and .tsx
(used with React) files. Any ESLint errors that can be automatically fixed will be fixed with this command, but any other errors will be printed out in the command line.
Even if ESLint doesn't report any errors, it doesn't necessarily mean that the TypeScript compiler won't report any type errors. To verify that your code has type errors, the tsc --noEmit
command can used.
Preventing ESLint and formatting errors from being committed
To ensure all files committed to git don't have any linting or formatting errors, there is a tool called lint-staged
that can be used. lint-staged
allows to run linting commands on files that are staged to be committed. When lint-staged
is used in combination with husky
, the linting commands specified with lint-staged
can be executed to staged files on pre-commit (if unfamiliar with git hooks, read about them here).
To configure lint-staged
and husky
, add the following configuration to the package.json
file:
1{2 "husky": {3 "hooks": {4 "pre-commit": "lint-staged"5 }6 },7 "lint-staged": {8 "*.{js,ts,tsx}": [9 "eslint --fix"10 ]11 }12}
The above configuration will run lint-staged
when a user tries to commit code to git. lint-staged
will then run ESLint on any staged files with .js
, .ts
, and .tsx
extensions. Any errors that can be fixed automatically will be fixed and added to the current commit. However, if there are any linting errors that cannot be fixed automatically, the commit will fail and the errors will need to be manually fixed before trying to commit the code again.
Unfortunately it is not enough to only rely on lint-staged
and husky
to prevent linting errors since the git hooks can be by-passed if a user commits uses the --no-verify
flag. Therefore, it is also recommended to run a command on a continuous integration (CI) server that will verify that there are no linting errors. That command should look like the following:
1eslint '*/**/*.{js,ts,tsx}' --quiet
Notice the above command doesn't pass the --fix
command to the eslint
CLI since we want the command to fail if there are any sort of errors. We do not want the CI automatically fixing lint errors since that would indicate that there is commited code that does not pass the linting checks.
That's how you can lint and format a TypeScript project using ESLint and Prettier 😎