Paul Ccari I'm a frontend engineer, UI/UX designer enthusiast, and content creator who has a passion for learning, sharing, and building cool things.

Linting in TypeScript using ESLint and Prettier

7 min read 2095

TypeScript Logo Over a Pink Background

As developers, we usually start a project by adding some configurations and scripts for linting, then formatting and type checking for our codebase syntax and style.

This process is even more important when working on a team so that everyone is on the same page when it comes to codebase syntax and style. And, to ensure there are no typos or bugs when our app is in production, we should type check our code along the way.

This is where both a formatting and linting tool and TypeScript can help us automate this process.

Let’s learn how to make these tools work together in our projects. In this article, we’ll focus on using ESLint and TypeScript, and we’ll also see how to add Prettier and additional tooling that will automate this process for us.

Compiling TypeScript code

Typescript is known as a superset of JavaScript that helps us with static type checking at compile time. Using TypeScript, you’ll get a better development experience, thanks to autocompletion in your editor. TypeScript also helps keep your code in large codebases more maintainable.

First, we need a compiler to turn TypeScript code into JavaScript so that the browser will be able to read it. Let’s install a dependency using your favorite package manager:

#npm
npm install --save-dev typescript

#yarn
yarn add typescript --dev 

Upon installation, you’ll see a new entry to the devDependencies attribute of your package.json file as:

{
  "name": "Linting TypeScript with ESLint",
  "version": "1.0.0",
  "devDependencies": {
    "typescript": "^4.4.3"
  }
}

If you want to verify it’s been installed, you can run this to check the version:

npx tsc --version
# Version 4.4.3

Now let’s create TypeScript code to see the compiled code.

// src/index.ts
const favoriteFruits: string[] = ["apple", "strawberry", "orange"];

function addFruit(fruit: string) {
  favoriteFruits.push(fruit);
}

Then, we can start compiling using the command line and run:

We made a custom demo for .
No really. Click here to check it out.

npx tsc src/index.js

Right after that, we’ll see a new generated JS file located at the same directory your TypeScript file is.

// src/index.js
var favoriteFruits = ["apple", "strawberry", "orange"];

function addFruit(fruit) {
    favoriteFruits.push(fruit);
}

By default, the compiler will create JavaScript files that are side-by-side with your TypeScript source file that created it. This is not a good idea because you’ll end up mixing your build results with your source code.

So, let’s change some default compiler settings, starting from where we want to put our compiled code, which JavaScript level is targeted to be transpiled (by default: ECMAScript 3), and which files we want to compile.

There are two ways to create your Typescript compiler settings:

  1. Us the command line and call npx tsp --init, which will generate a default TS configuration file
  2. Create a file called tsconfig.json at the root directory of your project and include your settings

In this case, I’ll create the TS compiler settings manually. However, I would encourage you to go for the first option. It will create the config file with some recommended options and all options are described with comments explaining what they do.

You can modify these settings as you need to. See the full list of supported compiler options here, and you can play around in the TypeScript playground.

// tsconfig.json
{
  "compilerOptions": {
    "outDir": "dist", // where to put the compiled JS files
    "target": "ES2020", // which level of JS support to target
    "module": "CommonJS", // which system for the program AMD, UMD, System, CommonJS

    // Recommended: Compiler complains about expressions implicitly typed as 'any'
    "noImplicitAny": true, 
  },
  "include": ["src"], // which files to compile
  "exclude": ["node_modules"], // which files to skip
}

Congratulations! Now, you can start writing TypeScript by running npx tsc.

In order to make it easier to run, you can include it in your scripts. Go to package.json and add it using --watch flag to watch the files for changes. Keep in mind that everything that is described in compilerOptions can be passed on the command line using CLI flags.

// package.json
{
  "name": "Linting TypeScript with ESLint",
  "version": "1.0.0",
  "devDependencies": {
    "typescript": "^4.4.3"
  },
  "scripts": {
    "dev": "tsc --watch"
  }
}

What is ESLint?

One of the most popular tools for linting is ESLint, which will analyze your code to not only find potential bugs, but also to improve your code quality by defining coding conventions, then enforcing them automatically. Let’s see how to install ESLint into our TypeScript project.

First, install the following dependencies to your devDependencies:

npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
  • eslint: ESLint core library
  • @typescript-eslint/parser: parser that allows ESLint to understand TypeScript code
  • @typescript-eslint/eslint-plugin: plugin with a set of recommended TypeScript rules

Similar to Typescript compiler settings, you can either use the command line to generate a configuration file using the --init flag from ESLint or create it manually. Either way, it’s mandatory to have your ESLint configuration file.

Let’s start using the CLI:

npx eslint --init

Next, you will see some questions that allow you to adjust to your preferences:

  • How would you like to use ESLint?
  • What type of modules does your project use?
  • Which framework does your project use?
  • Does your project use TypeScript?
  • Where does your code run?
  • How would you like to define a style for your project?

If you already have your favorite settings in place, you can create an .eslintrc file at the root directory and paste it into your config.

Using ESLint for linting

Use the following starter config and explore the full list of rules for your ESLint settings.

// .eslintrc
{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 12,
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint"],
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
  "rules": {

  },
  "env": {
    "browser": true,
    "es2021": true
  },
}
  • parser: this tells ESLint to run the code through a parser when analyzing the code
  • parserOptions: specifies what JS language options you want to support, such as the version of ECMAScript syntax you want to use
  • plugins: this is where you define plugins to use
  • extends: tells ESLint what configuration is set to extend from. The order matters, as the last extend option will override the previous ones in any conflicting configurations
  • env: which environments your code will run in

When we are adding an ESLint rule, we’re overriding it from the configuration defined in the extends list. Let’s add a couple of rules to see how it works.

// .eslintrc
{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 12,
    "sourceType": "module",
  },
  "plugins": ["@typescript-eslint"],
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],

  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    // to enforce using type for object type definitions, can be type or interface 
    "@typescript-eslint/consistent-type-definitions": ["error", "type"], 
  },

  "env": {
    "browser": true,
    "es2021": true
  }
}

In short, the first rule we are applying is assigned to a value of error, but error is not the only value we can assign — we have three options:

  • off or 0: turn off the rule completely
  • warn or 1: treat the rule as a warning, but it won’t fail when running a linter
  • error or 2: treat the rule as an error. it will fail when running a linter

Note: In some ESLint rules like the second rule, you would need to set additional options so you can use an array literal syntax.

Now you can add a lint script into your package.json. I added an ext flag that is used to specify JavaScript file extensions so that we can include a TypeScript files extension (by default, it’s .js).

// package.json
{
  "name": "Linting TypeScript with ESLint",
  "version": "1.0.0",
  "scripts": {
    "dev": "tsc --watch",
    "lint": "eslint --ext .js,.ts .",
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^4.33.0",
    "@typescript-eslint/parser": "^4.33.0",
    "eslint": "^7.32.0",
    "typescript": "^4.4.3"
  }
}

You’ll find that some files don’t need to be linted at all, such as your dist folder, so you can prevent linting by creating an .eslintignore file and adding folders or files you want to ignore.

node_modules
dist

This often matches with your .gitignore file content, so, to have a single source of truth, you can create a script using the --ignore-path flag:

// package.json
{
  // ...
  "scripts": {
    "lint": "eslint --ignore-path .eslintignore --ext .js,.ts ."
   },
  // ...
}

Now you’re ready to go! I suggest you integrate ESLint to whatever editor you use. If that’s VSCode, go to the extension and install the ESLint extension.

Once you’ve installed and enabled it, you’ll see what errors you’ve made in your code without running the script that is underlining with a red line.

Note: You’ll see that the ESLint error message is printed inline in the editor, that’s another extension called Error Lens Extension, which highlights the entire line and shows the error message immediately instead of hovering with the pointer to see it.

ESLint Error Message

Another feature of ESLint is that it can automatically fix code when you hover and right-click Quick fix or you hit command and +.

Quick Fix

Manually fixing all of the errors that have broken your rules can be tedious, but you can run the following command that will tell ESLint to fix what it can:

npm run lint -- --fix

Tip: You can pass parameters using double dashes -- for npm scripts, which will be received as parameters for the script that npm executes.

npm run <command> [-- <args>]

What is Prettier?

Prettier is a well-known code formatter that supports a variety of different programming languages that helps us avoid spending time on formatting manually and sets our code style.

Nowadays, it’s common to use ESLint and Prettier at the same time, so let’s add Prettier to our project:

npm install --save-dev prettier

Compared to ESLint, Prettier doesn’t need a config file, which means that you can run and use it straight away. In case you want to set your config, you need to create a file called .prettierrc where you can define your format options.

You can take a look at the full list of format options and can play around in the Prettier Playground.

// .prettierrc
{
  "semi": false, // Specify if you want to print semicolons at the end of statements
  "singleQuote": true, // If you want to use single quotes
  "arrowParens": "avoid" // Include parenthesis around a sole arrow function parameter
}

Next, we are going to start formatting our code using Prettier in the command line.

npx prettier --write src/index.ts
# src/index.ts 37ms

I added a write flag to overwrite the TypeScript file, otherwise, it won’t overwrite it and will only log the formatted code in your CLI.

Let’s add the Prettier command to our scripts, just as we did for TypeScript and ESLint. Let’s also support all files that end in .ts, .js, and .json, and ignore the same files and directories as gitignore (or you can create a file .prettierignore):

// package.json

{
  // ...
  "scripts": {
    "dev": "tsc --watch",
    "lint": "eslint --ext .js,.ts .",
    "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\""
  },
  // ...
}

Now, you can run the npm run format command to format and fix all your code. But what if we would like to format the code right after saving your files?

That’s possible! Go to extension and look for Prettier Extension and make sure it’s enabled. Once it’s enabled, we need a few things to configure in VSCode.

You can open up your command palette (Command + Shift + P) and look for Preferences: Open Settings (JSON). Then you’ll need to change your editor default formatter and add an extra config to format code when you save your files:

// settings.json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  ...
}

Preview Using Prettier Extension

Avoiding conflicts when working with ESLint and Prettier

You’ll likely run into an issue when a Prettier and ESLint rule overlap. You will try to auto-format your code, but it will show you some conflicts with ESLint.

The best solution here is to use eslint-config-prettier to disable all ESLint rules that are irrelevant to code formatting, as Prettier is already good at it.

npm install --save-dev eslint-config-prettier

With that installed, let’s go to the .eslintrc file and prettier at the end of your extends list to disable any other previous rules from other plugins.

// .eslintrc
{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 12,
    "sourceType": "module",
  },
  "plugins": ["@typescript-eslint"],
  // HERE
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],

  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/consistent-type-definitions": ["error", "type"],
  },

  "env": {
    "browser": true,
    "es2021": true
  }
}

That’s it! Now you know how to use these static testing tools effectively. It’s great to have some automation for specific tasks like linting, formatting, and type checking.

Conclusion

Using TypeScript and ESLint together can boost our confidence in our code. It helps us prevent bugs and can save us time in the long run. I recommend you try using TypeScript and ESLint for a better developer experience for you and your entire team the next time you’re building cool things.

Writing a lot of TypeScript? Watch the recording of our recent TypeScript meetup to learn about writing more readable code.

TypeScript brings type safety to JavaScript. There can be a tension between type safety and readable code. Watch the recording for a deep dive on some new features of TypeScript 4.4.

Paul Ccari I'm a frontend engineer, UI/UX designer enthusiast, and content creator who has a passion for learning, sharing, and building cool things.

Leave a Reply