How to use ESLint with TypeScript

Last updated Dec 19th, 2021
ESLint is a JavaScript linter that you can use to lint either TypeScript or JavaScript code. In this post, we'll walk through how to set up linting in your project.

Intro

Formatting is one of several concerns in the efforts to write clean code. There's a lot of other stuff we should be concerned about as well, but formatting is one of those things that we can set up right off the bat and establish a standard for our project.

ESLint and TSLint

ESLint is a JavaScript linter that enables you to enforce a set of style, formatting, and coding standards for your codebase. It looks at your code, and tells you when you're not following the standard that you set in place.

You may have also heard of TSLint, the TypeScript equivalent. In 2019, the team behind TSLint decided that they would no longer support it. The reason, primarily, is because ESLint exists, and there was a lot of duplicate code between projects with the same intended purpose.

That being said, there are some really awesome TSLint packages out there that, if you would like to use, you can- but just understand that they're not being supported anymore.

So onwards into 2020 and beyond, we're going to continue to look to ESLint for all of our TypeScript (and JavaScript) linting needs!

Prerequisites

Here's what you need to have in order to follow along:

Installation and setup

Run the following commands to setup ESLint in your TypeScript project.

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Create an .eslintrc file.

touch .eslintrc

In it, use the following starter config.

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ]
}

Ignoring files we don't want to lint

Create an .eslintignore in order to prevent ESLint from linting stuff we don't want it to.

touch .eslintignore

Then add the things we want to ignore. In the following code sample, we're ignoring the dist/ folder that contains the compiled TypeScript code. If you're compiling your TypeScript code to a different folder, make sure to use that instead of dist. You should be able to find this in your .tsconfig (see the previous guide).

node_modules
dist

Adding a lint script

In your project package.json, lets add a lint script in order to lint all TypeScript code.

{
  "scripts": {
    ...
    "lint": "eslint . --ext .ts",
  }
}

Ready to try it out? Let's run the following command.

npm run lint

For me, since I'm continuing with the previous tutorial, and since my src folder only has a single index.ts in it that prints out some text with console.log(), I don't see anything after I run the command.

src/index.ts
console.log('Hello world!')

What if we wanted to disallow console.log statements in our code?

Rules

There are three modes for a rule in eslint: off, warn, and error.

  • "off" means 0 (turns the rule off completely)
  • "warn" means 1 (turns the rule on but won't make the linter fail)
  • "error" means 2 (turns the rule on and will make the linter fail)

Adding a rule

In .eslintrc, add a new attribute to the json object called "rules".

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": { }}

Rules get added as keys of this rules attribute, and you can normally find the eslint base rules here on their website via the Rules docs.

We want to add the no-console rule, so here is an example of how we can make the linter fail (throw a mean error code) if it encounters a console.log statement with the no-console rule set to error.

We update the .eslintrc

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": { 
    "no-console": 2 // Remember, this means error!  }
}

And then run the linter again.

npm run lint

And we should get an angry linting result.

/simple-typescript-starter/src/index.ts

  2:1  error  Unexpected console statement  no-console
    ✖ 1 problem (1 error, 0 warnings)

And if we wanted, we could turn the rule off by setting it to 0 - off.

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {     "no-console": 0 
  }
}

Run the linter.

npm run lint

...and silence.

Using rules in a real-life project

There's a reason that all three of these modes exist. When you set the linter to off for a rule, it's because you and your team probably don't care about that rule in a particular base configuration you're using (we're currently using the ESLint recommended config but you can also go and use Shopify's, Facebook's or several other companies' configs as starting points instead).

When you set the rule to error - 2, it means that you don't want the code that breaks your coding conventions to even make it into the repo at all. I think this is a great act of professionalism and empathy towards the codebase, your fellow teammates, and future maintainers. A popular approach to actually enforce code conventions with this tool is to set up your project with a tool like Husky so that when a teammate tries to commit code or push code, you can tell your linter to check the code first before the operation executes. It's a great habit, though sometimes, if the rules are overly restrictive, it can slow down and annoy your teammates.

To remedy overly restrictive rules, the warn - 1 setting means that yes, you want you and your team to adhere to that rule, but you don't want it to prevent you from moving forward.

Adding a plugin (features)

ESLint also allows you to add one-off features to your config. These are known as plugins.

Here's a fun one. It's called no-loops.

Check out this list of other awesome-eslint plugins and configs.

no-loops is a plugin that will enable you to enforce a convention specifying that for and while loops are illegal and that you should use functions like map and forEach instead.

Install it like this.

npm install --save-dev eslint-plugin-no-loops

And then update your .eslintrc with no-loops in the "plugins" array, and add the rule to the "rules" attribute like so.

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint",
    "no-loops"  ],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    "no-console": 1,
    "no-loops/no-loops": 2  }
}

Now if we update our code to include a for loop...

src/index.ts
console.log('Hello world!');

for (let i = 0; i < 10; i++) {
  console.log(i)
}

And run the lint command again...

npm run lint

We'll see the errors that restricts loops appear.

/simple-typescript-starter/src/index.ts
  2:1   warning  Unexpected console statement                   no-console
  2:1   error    'console' is not defined                       no-undef
  4:1   error    loops are not allowed                          no-loops/no-loops  5:3   warning  Unexpected console statement                   no-console
  5:3   error    'console' is not defined                       no-undef
  5:17  error    Missing semicolon                              babel/semi
  5:17  error    Missing semicolon                              semi
  6:2   error    Newline required at end of file but not found  eol-last

✖ 8 problems (6 errors, 2 warnings)
  3 errors and 0 warnings potentially fixable with the `--fix` option.

Extending a different base config

Let's say that you wanted to start with a different base config- Shopify's, for example.

Here's how to do that.

Looking at the readme, we need to install it by running:

npm install eslint-plugin-shopify --save-dev

Update our .eslintrc

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "plugin:shopify/esnext"  ],
  "rules": {
    "no-console": 1
  }
}

You can add several base configs to your project by including them in the array, though you may end up seeeing the same linting rules twice or more.

Now when we run the lint command again with npm run lint, we can see a few errors reported based on the base Shopify config and our no-console rule.

/simple-typescript-starter/src/index.ts
  2:1   warning  Unexpected console statement  no-console
  2:1   error    'console' is not defined      no-undef
  2:28  error    Missing semicolon             babel/semi
  2:28  error    Missing semicolon             semi

✖ 4 problems (3 errors, 1 warning)
  2 errors and 0 warnings potentially fixable with the `--fix` option.

Fixing linted code with ESLint

You might have noticed that at the end of the error message, it says "2 errors and 0 warnings potentially fixable with the --fix option."

You can run ESLint and tell it to fix things that it's able to fix at the same time.

Using the --fix option, let's add a new script to our package.json called lint-and-fix.

{
  "scripts": {
    ...
    "lint": "eslint . --ext .ts",
    "lint-and-fix": "eslint . --ext .ts --fix"  },
}

Running this command against our code, I expect that it will put a semicolon on the console.log statement that we had.

Let's run it.

npm run lint-and-fix

The result is that less errors are reported. We don't see the error about semi-colons anymore.

/simple-typescript-starter/src/index.ts
  2:1  warning  Unexpected console statement  no-console
  2:1  error    'console' is not defined      no-undef

Because sure enough, the linter added the semi-colon.

src/index.ts
console.log('Hello world!');

That's really awesome. But what if we don't want to run the linter all the time to fix our code? What if there was a way that we could, while coding, have it automatically format things based on our conventions?

We can! With Prettier. Read the next article, "How to use Prettier with ESLint and TypeScript in VSCode".



Discussion

Liked this? Sing it loud and proud 👨‍🎤.



Stay in touch!



About the author

Khalil Stemmler,
Software Essentialist ⚡

I'm Khalil. I turn code-first developers into confident crafters without having to buy, read & digest hundreds of complex programming books. Using Software Essentialism, my philosophy of software design, I coach developers through boredom, impostor syndrome, and a lack of direction to master software design and architecture. Mastery though, is not the end goal. It is merely a step towards your Inward Pull.



View more in TypeScript



You may also enjoy...

A few more related articles

Why You Have Spaghetti Code
Code that gets worse instead of better over time results from too much divergence & little convergence.
Reality → Perception → Definition → Action (Why Language Is Vital As a Developer)
As developers, we are primarily abstractionists and problem decomposers. Our task is to use language to decompose problems, turnin...
The Code-First Developer
As you improve as a developer, you tend to move through the 5 Phases of Craftship. In this article, we'll discuss the first phase:...
Object Stereotypes
The six object stereotypes act as building blocks - stereotypical elements - of any design.