Once you get started with TypeScript in a new or old project, it’s important to set standards for code going forward. This is why I add linting and formatting support to TypeScript projects via ESLint and Prettier. In this article, I’ll show you how this is done and why it’s important.

Wait, linting? Don’t I have TypeScript for that?

While the TypeScript compiler will enforce code correctness via its static type checking, it doesn’t do a few important things. Specifically, it checks your code to see if it compiles. A linter, in turn checks to see if code follows best practices in the JavaScript ecosystem.

Think about these two like a spell checker and a grammar checker. The following “sentence” would pass a spell checker:

Frog cloud red molecule ambivalent forty two.

A grammar checker would look at the above and think that I was losing my mind (and possibly be correct).

In much the same way, TypeScript compiler just checks that your code makes sense syntactically. A linter looks to augment that by telling you things like:

  • Don’t use the any keyword
  • Don’t declare marker (empty) interfaces
  • Use camelCase naming notation
  • Use a consistent string format
  • Don’t use ts-ignore

Essentially, a linter keeps you honest by challenging some of the questionable things you might add to your code over time. This can be especially handy when bringing new developers onto a team or project.

Linters are by their very nature opinionated, but these opinions can be changed. By using configuration files (as we’ll see later on) and including rulesets of your choosing, you have control over exactly how picky your linter is.

Introducing ESLint

ESLint is a very popular JavaScript and TypeScript linter that can parse JavaScript and TypeScript code and output warnings and errors based on the severity of the various rules violated.

Incidentally, ESLint will output an error code if it encounters any error-level severity rule violations. This can be used as part of your build pipeline to not accept new commits including rule violations, for example.

In the past, I’ve recommended using TSLint for TypeScript linting, but in early 2019 Palantir announced they were deprecating TSLint in favor of ESLint. Because of this, I advocate for migrating off of TSLint eventually in existing projects and using ESLint from the start for new projects.

Let’s install ESLint into an existing TypeScript application that does not use linting currently and see how that process works.

Getting Started with NPM

I’m going to start with my code from my Migrating to TypeScript article. This is a simple TypeScript application free from any single page application complexities. No Angular, React, Vue, or even JQuery to focus on – just the TypeScript and its generated JavaScript code.

This code is available on GitHub and we will be starting from the migrateEnd tag if you want to follow along.

For the purposes of this article, I’m going to be using Node Package Manager (NPM) to manage my dependencies and build steps. This article assumes you’ve already installed it on your machine. If that’s not the case, go ahead and install the latest LTS version of NPM.

Since NPM isn’t configured in my sample repository, I’m going to run npm init from the command line to create a new package.json file. NPM init will ask you a series of questions, all of which have default options listed in parentheses which can be accepted by hitting enter. Regardless of your selections, a package.json file will be created.

Mine looks like this:

Bear in mind that a lot of this only matters if you want to publish your package online, which we will not be doing in this article.

Note the scripts section of this document. Each entry in this list can be run via npm run [scriptname]. NPM actually has all the tools needed to compose fairly complicated multi-step build processes.

Let’s add our own script definitions to this to support transpiling TypeScript to JavaScript:

  "scripts": {
    "transpile": "tsc",
    "build": "npm run transpile",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Here we have a new transpile step that can be executed via npm run transpile. This will in turn execute tsc to transpile the TypeScript to JavaScript. Admittedly this is far more keystrokes to get the same effect, but the upside of this is that we can start to compose multi-part builds.

I also define the build step, which we’ll use to run multiple steps in a single command. In this case the build step simply runs the transpile step, but we’ll see this expand in the next section as we add in ESLint.

Installing ESLint

Next, lets set up ESLint and linting process. We’ll use npm to install the development dependency on ESLint by running the following command:

npm i -D typescript eslint eslint-config-typescript

Here the i refers to the install command and -D instructs NPM to save the dependency in package.json as a development-only dependency.

This will download the needed dependencies into your node_modules folder and write entries to your package.json based on the versions of dependencies pulled. For me, at the time of writing this article, these versions were pulled:

  "devDependencies": {
    "eslint": "^6.7.1",
    "eslint-config-typescript": "^3.0.0",
    "typescript": "^3.7.2"
  }

This essentially instructs any future npm i command to look for these dependencies or something more recent (indicated by the ^ character).

Configuring ESLint for TypeScript

Now that ESlint is installed, let’s configure it. To do this, run eslint --init.

You should get a prompt asking you how you want ESLint to behave. For our purposes, we don’t want it to enforce code style, so select check syntax and find problems.

Next, ESLint will ask you what type of JavaScript module structures you use. In my case, I kept things simple by not using modules for my last article, so I’ll select none of these.

After that, ESLint asks if you use a single page application framework. My sample is just plain-old-JavaScript interacting with the DOM, so I’ll choose none of these again.

Next, ESLint asks if we use TypeScript. Boy, do we! That’s an easy yes.

Next ESLint asks what environment our code runs in. In this case, the code is meant to run in the browser, so the default choices are fine.

Finally, ESLint asks how we want to store our settings. You can pick whichever option you prefer, but for the purposes of this article I’ll be going with JSON because it’s the most natural to me when working with TypeScript files and packages.json already.

ESLint may ask you if you want to install additional dependencies based on which choices you chose. Say yes.

With that, your ESLint settings are configured and stored in an .eslintrc.json file that looks something like this:

Analyzing TypeScript with ESLint

Cool! So how do we use this?

Well, the syntax is somewhat complex and I don’t love to type it every time, but it looks something like eslint --ext .ts [pathtosource].

Here we’re saying “run ESLint on only TypeScript files, and look in the specified directory and subdirectories”. Since my sample app has its typescript files in the root directory, I run eslint --ext .ts . to specify the current directory holds my source.

Running this on my sample code yields some violations:

Well, that’s great! We wanted to find potentially questionable behavior, so we know linting is doing its job.

Lets save ourselves a bit of time for next time by updating our package.json scripts:

    "lint": "eslint --ext .ts .",
    "build": "npm run lint && npm run transpile",

Here we define a new step just for linting and we expand our build step via the && operator to have it call the lint step, wait for the result, and then call the transpile step only if the lint step executes without error.

Now we can just run npm run build to lint and transpile our TypeScript down to JavaScript.

Fixing ESLint Issues

But what about our errors? We need to fix those, for certain.

In this case, my errors are false positives. That is, I’m relying on click handlers in my HTML to call to the functions defined in my JavaScript and my linter has no idea that this is what I’m doing.

When you think about it, though, the relationship between that JavaScript function and the HTML file isn’t going to be obvious to a new developer on the project working on something else and they could easily delete, rename, or move functions around not understanding the full impact of that choice.

To guard against that – and solve my ESLint warnings – I’m going to show you an example of fixing this.

In my HTML file, I remove the relevant onclick="addTestCase()" line and instead modify my code behind to grab the button by its ID and then set the onclick handler in code:

Note: if the indentation, quotes, and/or braces in the snippet above cause you brain trauma, I apologize, it’s to prove a point and will be resolved later.

This resolves one of our errors. We could follow the same pattern to resolve the other issues, or I could use the opportunity to show you how to ignore false-positives.


Let’s say I disagree with the no-unused-vars rule. I shouldn’t since it’s a very good rule, but I might want to tweak its severity down to a warning or disable it entirely.

I do this by going into the eslintrc.json file and declaring my hatred for the rule by adding its identifier to the rules collection in the file as follows:

    "rules": {
        "no-unused-vars": "warn"
    }

If I don’t want errors or warnings, I could set it instead to off. Please don’t do this. This is a good rule and should be an error or a warning, but this illustrates how to disable rules you disagree with.

Since I agree with the rule, I actually don’t want to disable it or make it a non-error, so I’m going to disable it on a line-by-line basis inside of my code instead. That makes a lot more sense with single cases of false positives.

I do this via the disable-next-line syntax:

// eslint-disable-next-line no-unused-vars
function deleteTestCase(id: number) {
  // my logic here
}

Whatever way you want to handle it, this gives us the tools that we need to get down to 0 errors in ESLint and a passing npm run build command.

Coding with Style using Prettier

So, the linter catches code issues, but it clearly doesn’t care what kind of crazy indentation styling I choose. This is a problem in a production application where code standards are important.

Thankfully, a tool called Prettier helps with not only detecting, but also automatically fixing styling issues. This standard set of formatting rules takes the debate out of code review and lets you focus on what really matters – the actual code.

Sure, the styles might not be exactly what you would have gone with on your own, but you soon learn to read the code style and even think in it.

Installing and Configuring Prettier

While Prettier can operate as a stand-alone tool, it functions much more smoothly when integrated directly into ESLint. That way, Prettier will execute any time linting occurs.

We’ll start by installing the development dependencies for the Prettier ESLint plugin via the following command:

npm i -D prettier eslint-plugin-prettier eslint-config-prettier

Once that completes, we’ll need to modify our eslintrc.json file so that it knows about Prettier.

In the extends section, add the following two entries:

"prettier/@typescript-eslint",
"plugin:prettier/recommended"

Now, when I run my lint or build tasks via npm, I get a raft of failures around indentation, quotes, etc. This is now enforcing a style and rejecting files that do not meet it.

Well, that’s annoying and not extremely helpful in the real world. What would be more helpful would be if Prettier could automatically format my files properly. Thankfully, it can.

All we have to do is modify our lint script in package.json to add --fix to the command line arguments like so:

"lint": "eslint --fix --ext .ts ."

Once we re-run that, our source files are automatically modified to be correctly formatted.

This is possible because some ESLint rules have automatic fixes defined which ESLint can apply automatically if you specify the --fix flag.

This isn’t just limited to formatting, either – some trivial JavaScript and TypeScript rules provide automatic fixes, meaning the very act of checking your code for serious issues can fix minor issues automatically, while enforcing a consistent style throughout your codebase.

Recommendations

Give NPM, ESLint, and Prettier a shot to see how you like working with them.

If you’re curious about ESLint’s rules or configuration, take a look at the TypeScript recommended ruleset for details on the individual rules, their default settings, and how you can customize their behavior.

My finding is that TypeScript mixed with NPM, ESLint, and Prettier offers the right amount of standards enforcement and consistency to help teams scale in JavaScript development, which is really the point of wanting to use TypeScript to begin with.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.