DEV Community

Cover image for Using ESLint and Prettier in a TypeScript Project
Robert Cooper
Robert Cooper

Posted on • Edited on • Originally published at robertcooper.me

Using ESLint and Prettier in a TypeScript Project

Originally posted on my blog.

When it comes to linting TypeScript code, there are two major linting options to choose from: TSLint and ESLint. TSLint is a linter than 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:

yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --dev

Next, add an .eslintrc.js configuration file in the root project directory. Here is a sample configuration for a TypeScript project:

module.exports =  {
  parser:  '@typescript-eslint/parser',  // Specifies the ESLint parser
  extends:  [
    'plugin:@typescript-eslint/recommended',  // Uses the recommended rules from the @typescript-eslint/eslint-plugin
  ],
 parserOptions:  {
    ecmaVersion:  2018,  // Allows for the parsing of modern ECMAScript features
    sourceType:  'module',  // Allows for the use of imports
  },
  rules:  {
    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
    // e.g. "@typescript-eslint/explicit-function-return-type": "off",
  },
};

Note: 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:

module.exports =  {
  parser:  '@typescript-eslint/parser',  // Specifies the ESLint parser
  extends:  [
    'plugin:react/recommended',  // Uses the recommended rules from @eslint-plugin-react
    'plugin:@typescript-eslint/recommended',  // Uses the recommended rules from @typescript-eslint/eslint-plugin
  ],
  parserOptions:  {
  ecmaVersion:  2018,  // Allows for the parsing of modern ECMAScript features
  sourceType:  'module',  // Allows for the use of imports
  ecmaFeatures:  {
    jsx:  true,  // Allows for the parsing of JSX
  },
  },
  rules:  {
    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
    // e.g. "@typescript-eslint/explicit-function-return-type": "off",
  },
  settings:  {
    react:  {
      version:  'detect',  // Tells eslint-plugin-react to automatically detect the version of React to use
    },
  },
};

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:

yarn add prettier eslint-config-prettier eslint-plugin-prettier --dev

In order to configure prettier, a .prettierrc.js file is required at the root project directory. Here is a sample .prettierrc.js file:

module.exports =  {
  semi:  true,
  trailingComma:  'all',
  singleQuote:  true,
  printWidth:  120,
  tabWidth:  4,
};

Next, the .eslintrc.js file needs to be updated:

module.exports =  {
  parser:  '@typescript-eslint/parser',  // Specifies the ESLint parser
  extends:  [
    'plugin:@typescript-eslint/recommended',  // Uses the recommended rules from the @typescript-eslint/eslint-plugin
    'prettier/@typescript-eslint',  // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
    'plugin:prettier/recommended',  // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
  ],
  parserOptions:  {
    ecmaVersion:  2018,  // Allows for the parsing of modern ECMAScript features
    sourceType:  'module',  // Allows for the use of imports
  },
};

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 Fixing Code (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:

"eslint.autoFixOnSave":  true,
"eslint.validate":  [
  "javascript",
  "javascriptreact",
  {"language":  "typescript",  "autoFix":  true  },
  {"language":  "typescriptreact",  "autoFix":  true  }
],

If you've also set the editor.formatOnSave option to true in your settings.json, you'll need to add the following config to prevent running 2 formatting commands on save for JavaScript and TypeScript files:

"editor.formatOnSave":  true,
"[javascript]":  {
  "editor.formatOnSave":  false,
},
"[javascriptreact]":  {
  "editor.formatOnSave":  false,
},
"[typescript]":  {
  "editor.formatOnSave":  false,
},
"[typescriptreact]":  {
  "editor.formatOnSave":  false,
},

And there you have it. That's how you can lint a TypeScript project using ESLint. If you want to make sure all the files you commit to git pass the ESLint checks, take a look at lint-staged, which can run ESLint on files being commited.

Top comments (51)

Collapse
 
gyandeeps profile image
Gyandeep Singh • Edited

Just curious about your opinion on if you need to use a linter to check code if you use typescript in strict mode. For styles, for sure using prettier makes sens to me.

Collapse
 
robertcoopercode profile image
Robert Cooper • Edited

Good question. Using a linter allows you to check on a lot of stuff that isn't covered by the TypeScript compiler. For example, if you take a look at some of these ESLint rules for TypeScript code, you'll find a lot of rules related to code style that can be enforced with a linter.

Here are a few examples:

  • @typescript-eslint/explicit-function-return-type: Require explicit return types on functions and class methods
  • @typescript-eslint/generic-type-naming: Enforces naming of generic type variables
  • @typescript-eslint/no-array-constructor: Disallow generic Array constructors
  • @typescript-eslint/no-unused-vars: Disallow unused variables
Collapse
 
gyandeeps profile image
Gyandeep Singh

all of the rules above are again kinda of stylistic (last one is already covered directly by typescript). If something is wrong, typescript will tell u during compile regardless whether you specify function return type explicitly or not.. I also depend on computer types in typescript (maybe thats why i think like this for linter with strict mode)

Thread Thread
 
robertcoopercode profile image
Robert Cooper • Edited

Well if you don't mind having the code written in a project not conform to code style rules, then a linter is of no value. You just might have a large variance in the way code is structured/ordered, variables are named, and other code style related things.

When I'm working on a project, I prefer to look at the code base and have it look like it was all written by the same person. A linter helps achieve this in my opinion.

Thread Thread
 
braposo profile image
Bernardo Raposo

And there's still things like no explicit any that can be enforced by the linter.

Thread Thread
 
ca0v profile image
Corey Alix

This is also a tsconfig option. What use is eslint really? Tsconfig.json and .prettierrc cover most cases as far as I can tell. Is there one compelling use case for eslint?

Thread Thread
 
robertcoopercode profile image
Robert Cooper

There are many eslint rules that aren’t covered by tsconfig options.

Collapse
 
bitttttten profile image
bitten

I ran a problem today with this setup. If I run prettier --single-quote --write then eslint --fix, it will fail because of this line:

                    {state === PENDING
                        ? Array(10)
                              .fill(true)

prettier wants the above, but eslint wants:

                    {state === PENDING
                        ? Array(10)
                            .fill(true)

(so 28 spaces instead of 30).

Any tips? I am kinda stuck.

Collapse
 
robertcoopercode profile image
Robert Cooper

You need to run prettier through ESLint, otherwise you'll get these kind of conflicts.

Collapse
 
bitttttten profile image
bitten

It seems that vscode is running prettier when formatting on save, rather than eslint! I will look into it on Monday when I back with my laptop :P

Thread Thread
 
robertcoopercode profile image
Robert Cooper

You need to make sure to turn off the formatOnSave in VSCode for your JavaScript and Typescript files. Here are the settings:

"editor.formatOnSave": true,
"[javascript]": {
    "editor.formatOnSave": false,
  },
  "[javascriptreact]": {
    "editor.formatOnSave": false,
  },
  "[typescript]": {
    "editor.formatOnSave": false,
  },
  "[typescriptreact]": {
    "editor.formatOnSave": false,
  },

Then to make sure you get ESLint to fix problems on save, make sure you have the following settings in VS Code:

"eslint.autoFixOnSave": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {"language": "typescript", "autoFix": true },
    {"language": "typescriptreact", "autoFix": true }
  ],
Thread Thread
 
bitttttten profile image
bitten • Edited

Thanks! But there is still an issue with it :( It seems like a conflict between eslint(prettier/prettier) and eslint(@typescript-eslint/indent). I also feel like eslint is running twice or something. For now I have set:

    rules: {
        '@typescript-eslint/indent': 'off',
    }

Here's my full eslint config file:

module.exports = {
    parser: '@typescript-eslint/parser',
    extends: [
        'plugin:@typescript-eslint/recommended',
        'plugin:prettier/recommended',
        'plugin:react/recommended',
    ],
    parserOptions: {
        ecmaVersion: 2018,
        sourceType: 'module',
        ecmaFeatures: {
            jsx: true,
        },
        project: './tsconfig.json',
        tsconfigRootDir: './',
    },
    settings: {
        react: {
            version: 'detect',
        },
    },
    rules: {
        '@typescript-eslint/interface-name-prefix': 'always',
        '@typescript-eslint/no-explicit-any': 'always',
        '@typescript-eslint/explicit-function-return-type': 'off',
        '@typescript-eslint/no-non-null-assertion': 'off',
        '@typescript-eslint/no-use-before-define': 'off',
        '@typescript-eslint/member-delimiter-style': {
            delimiter: 'none',
            requireLast: true,
        },
    }
}

VSCode User settings: hastebin.com/jucodoheqi.json
VSCode Workspace settings: hastebin.com/anenanupuh.json

Thread Thread
 
robertcoopercode profile image
Robert Cooper

I think your extends array should be the following (I've added comments explaining things):

    extends: [
        'plugin:@typescript-eslint/recommended', // uses typescript-specific linting rules
        'plugin:react/recommended', // uses react-specific linting rules
        'plugin:prettier/recommended', // enables eslint-plugin-prettier and eslint-config-prettier
        'prettier/react', // disables react-specific linting rules that conflict with prettier
    ],
Thread Thread
 
bitttttten profile image
bitten • Edited

Still didn't seem to fix the issue! Although I also ran into this last week, which I have not been able to solve. Something must be wrong with my setup somewhere haha. Check it out:

Prettier wants this:

export default function Heading(
    props: HTMLProps<HTMLHeadingElement> & {
        as?: AsTags
        withComponent?: HeadingTags
    },
) /* .... */

But typescript fails to compile because of the missing comma after as?: AsTags as it wants as?: AsTags, but prettier removes it ._.

Thanks for providing the snippets though. If you can shed any light on this one I would appreciate it a ton :3

Collapse
 
tonyfung99 profile image
Tony Fung

Hello, I have encountered an error that my eslint config conflict with the prettier one. Currently, I am trying to make my interface using comma as the delimiter, however, prettier enforce me to delete it. Is there any way to disable the prettier restriction on this rule?

My rule in .eslintrc

  '@typescript-eslint/member-delimiter-style': [
            'warn',
            {
                multiline: {
                    delimiter: 'comma',
                    requireLast: true,
                },
                singleline: {
                    delimiter: 'comma',
                    requireLast: false,
                },
            },
        ],
Enter fullscreen mode Exit fullscreen mode

.prettierrc

{
    "parser": "typescript",
    "trailingComma": "all",
    "semi": false,
    "singleQuote": true,
    "printWidth": 120,
    "tabWidth": 4    
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
rplaha0390 profile image
Raman

Did you find a solution to this? I have also run into the same issue? Thanks

Collapse
 
bitttttten profile image
bitten • Edited

In VSCode I get a ts error like this photo (thepracticaldev.s3.amazonaws.com/i...) but if I run eslint by itself it does not report on it.

pwd$ ./node_modules/.bin/eslint --fix src/
pwd$
Enter fullscreen mode Exit fullscreen mode

So my pre commit hooks do not bail out correctly as they always seem to pass. Surely I must be missing something?

Collapse
 
robertcoopercode profile image
Robert Cooper

ESlint doesn't check for TypeScript errors, so if you want to check for TypeScript errors in your pre commit hook, you'll need to run the tsc --noEmit command in addition to the ESLint command.

Here's a pre commit command that you could use:

"husky": {
    "hooks": {
        "pre-commit": "tsc --noEmit && eslint --fix src/"
    }
},
Enter fullscreen mode Exit fullscreen mode

However, I would suggest using lint-staged so you don't have to run the eslint command on all the files in your src/, but rather only run it on files that are staged to be committed. This will save you time.

"husky": {
    "hooks": {
        "pre-commit": "tsc --noEmit && lint-staged"
    }
},
"lint-staged": {
    "*.{js,ts,tsx,jsx}": [
        "eslint . --fix",
        "git add"
    ]
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
bitttttten profile image
bitten

Oh well that makes total sense. I though eslint with ts was doing tslint, and I thought tslint would do these kind of checks.

Many thanks, and yeah, I went with the section option:

    "lint-staged": {
        "src/**/*.{tsx,js}": [
            "prettier --single-quote --write",
            "eslint --fix",
            "git add"
        ]
    },
Thread Thread
 
dance2die profile image
Sung M. Kim

Thank you Robert for the easy-to-follow article & bitten for adding the prettier option.

I was able to get the eslint & prettier set up for my pet project after following this post & the comments.

cshooks / hooks

List of all React hooks using data structures and algorithms

cshooks

All Contributors Known Vulnerabilities

A collection of React Hooks using Computer Science (CS) data structures & algorithms.

Purpose

Mainly to learn CS data structure & algorithms
(and also implement'em in TypeScript).

Hopefully some of the arcane data structures & algorithms help you as well.

Implemented

useTrie

Returns a Trie data structure, which is used to save a list of words to search in memory efficient manner & provide a type-ahead (not yet implemented) functionality.

Contributors

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!


Collapse
 
ardennl profile image
Arden de Raaij • Edited

Thanks for the great article. I was just busy on writing something similar on getting Gatsby set-up with TypeScript, ESLint, and Prettier. The set-up is pretty similar as well!

Never mind the question below, it's the eslint.validatesetting in VS code :D, I should've taken a note from your article ;).

Question for reference:

I wonder if you've encountered the same thing though: In VSCode, the editor doesn't seem to show me errors from my ESLint file with this setup, whether they're an extension of rules or added explicitly, just the TypeScript errors. Of course, when linting the errors _do show up. Any ideas on how to fix this?_

Collapse
 
robertcoopercode profile image
Robert Cooper

Just took a peak at your article. Great write up 👍🏼

Collapse
 
wordythebyrd profile image
Andrew Byrd 🐦

I'm glad this was linked because the article was missing the crucial step of adding --ext js,jsx,ts,tsx to the npm run script.

Collapse
 
ardennl profile image
Arden de Raaij

Thanks so much Robert! Hope I've got some time to publish part 2 soon

Collapse
 
crenshaw_dev profile image
Michael Crenshaw

Apparently .eslintrc (as a JSON file) can contain JS-style comments. That's helpful to me, because Visual Studio 2017 seems happiest with JSON rather than JS.

Collapse
 
robertcoopercode profile image
Robert Cooper

Ah interesting, thanks for pointing that out Michael.

Collapse
 
brokenthorn profile image
Paul-Sebastian Manole • Edited

Thanks! It works perfectly!

Here's what I ended up with, since your examples don't build up (I mean the React config is lost after just being introduced :P):

module.exports = {
  parser: "@typescript-eslint/parser", // Allows ESLint to lint TypeScript.
  extends: [
    "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
    "plugin:@typescript-eslint/recommended", // A plugin that contains a bunch of ESLint rules that are TypeScript specific.
    'prettier/@typescript-eslint',  // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
    'plugin:prettier/recommended',  // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
  ],
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: "module",
    ecmaFeatures: {
      jsx: true, // Allows for the parsing of JSX
    },
  },
  env: {
    es6: true,
    browser: true,
    node: true,
  },
  rules: {
    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
    // e.g. "@typescript-eslint/explicit-function-return-type": "off",
  },
  settings: {
    react: {
      version: "detect", // Tells eslint-plugin-react to automatically detect the version of React to use
    },
  },
};

And of course, I disabled VSCode's onsave formatter, thanks for that catch! Could have driven me mad... :)

Collapse
 
dance2die profile image
Sung M. Kim

Thank you, Robert. This was quite educational.

Especially because I've recently moved to TSLint without knowing about the the TypeScript 2019 roadmap and being a TypeScript newbie :).

Collapse
 
robertcoopercode profile image
Robert Cooper

Ya, I was using TSLint before recently switching to ESLint for TypeScript. TSLint works well, but I found myself missing certain rules that were available in ESLint, but not TSLint.

Collapse
 
nmiddleweek profile image
Nick Middleweek

Hello, thanks for this article, it was super useful. I'm trying to find the answer to the question "Does it matter in which order the plugins are listed in the extends config array?"... Here's what I've ended up with, but I've noticed it's different to others on this thread. Thanks

"extends": [
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint",
    "google"
  ],
Collapse
 
zalithka profile image
Andre Greeff

I'm battling a little bit with this one.. In addition to the typical src/ folder, my project also has a scripts/ folder which contains custom JS based build/test/release helper scripts to run with Node.

I placed an .eslintrc file in the project root with the TypeScript configuration, which appears to be working fine, but regardless of what I do in the scripts/.eslintrc file, the linter keeps trying to apply my TypeScript rules to my JaveScript files.

Isn't ESLint supposed to use the closest config file when linting subfolders? Is there any way to explicitly tell it do so?