Front End web development is notorious for technology churn. The reality is that Front-End developers come from a wide variety of backgrounds and experiences. Without special care, a medium to large-sized project can rapidly devolve into a hodge-podge of different coding styles and paradigms. Even worse, the phenomenon of “bikeshedding” can waste precious developer time and sometimes even cause hurt feelings over trivial matters such as code formatting.

The truth is there is no substitute for careful code reviews. Skillful code reviews can:

  1. bring a team together,
  2. make a project seem like a single cohesive whole,
  3. prevent huge amounts of technical debt; and,
  4. anticipate and prevent many problems before they happen.

Unfortunately, code reviews don’t scale. The beauty of linting is that it can automate low-level and mundane aspects of code reviews, and free up the time of actual human code reviews to focus on more impactful issues: the bigger picture. As a project manager or team lead, it’s your responsibility to look into custom linting and see if it’s appropriate for your project.

I made this GitHub repo to demonstrate a fresh Create React App (Typescript template) application with customized linting rules added.

What is Linting?

Lint is the name for undesirable bits of fiber and fluff found in sheep’s wool. It was first used in a programming sense as the name for a particular program that flagged suspicious and non-portable constructs (likely to be bugs) in C language source code. In a modern sense, linting is any programmatic analysis of code that can detect potential programs or defects or “code smells”. While the most advanced kinds of linting can use Machine Learning, even traditional rule-based linting can be extremely effective.

Linting is not perfect. If overly strict, it can throw up unnecessary and distracting roadblocks and slow development. On the other hand, a far more common problem is that the lack of linting incurs increasingly severe costs as a project grows.

The good news is that even somewhat strict linting rules really shouldn’t inconvenience a programmer too much. With ESLint, it is easy to disable a linting error or warning by adding a special comment just above the offending line of code. When used well, linting can suggest real improvements to code, but it is not meant to be taken too rigidly. If the programmers use too many disable comments, you may want to evaluate if your linting rules are too strict. On the other hand, if there are no disable comments in your codebase, perhaps you can consider dialing it up a notch and making your lint rules a bit more strict.

Warning: don’t make enemies with your programmers: for each new linting rule you introduce, work hard to get their buy-in. It’s often easier to start more strict and get laxer as needed than the other way around.

Advanced linting pyramid of code quality
Pyramid of code quality for Front-End web development

How is linting different from code formatting?

In the early days, there was not much difference. Linters suggested fixes or even automatically fixed code formatting issues. While sometimes better than nothing, I never found linters to be great at code formatting. Everything changed circa 2017 when a new tool emerged that handled code formatting better than anything else before it. This new code formatting tool is called Prettier and the secret to its success is that it compiles the source code to an AST (abstract syntax tree) and then back to source code again. This means the same code ALWAYS formats the same way, no matter how you entered it originally. Prettier is inherently better at code formatting.

I consider it best practice to let prettier handle ALL code formatting issues (and fix them automatically) and let the linter focus on alerting the programmer to higher-level issues. Examples of infamous code formatting Holy Wars include spaces vs tabs and single quotes vs. double-quotes. Stop fighting and make a prettier configuration file that settles it, at least for your specific project. And, if you ever change your mind, you can automatically fix it across the entire project if you want to. No one should have to break up because of spaces vs. tabs ever again, like in this hilarious scene from Silicon Valley:

Why is linting important for Typescript projects?

Typescript is new for many developers, thus linting rules can help guide developers toward better practices. Typescript by itself is great at enforcing a certain kind of discipline (types!) onto Javascript developers. Unfortunately, it can be easy to use Typescript carelessly, or less strictly, than is optimal. Typescript specific linting rules can make code more clear. For example, a Typescript specific rule called “explicit-function-return-type” requires that the return type of a function be made explicit in code, rather than rely upon inference which Typescript allows. Some lint-like Typescript rules can be set in the tsconfig.json configuration file. But there are many, many more useful rules available in ESLint and ESLint Typescript plugins. It should be mentioned that there was once a Typescript specific linter called TSLint, but it’s now mostly deprecated in favor of ESLint combined with Typescript plugins.

Many of the tsconfig compiler options (e.g. “strict”) seem very lint like: https://www.typescriptlang.org/docs/handbook/compiler-options.html

Doesn’t create-react-app already support linting out-of-the-box?

Yes, and a design philosophy of create-react-app is that it is equipped to handle the basic needs of a wide variety of projects with minimal to no customization. But the fact of the matter is, larger projects will benefit from a more comprehensive linting solution. Until recently, create-react-app specifically disallowed any changes to eslint configuration, and you were stuck with what was provided. Or you had to go through crazy hacks (such as “ejecting”) to do even the simplest customization to the linting rules.

As of create-react-app 3.1.1 and above, you can override the default lint configuration by setting an environment variable called EXTEND_ESLINT. With this set to true, your own custom ESLint configuration file will be used as you develop your React app. 😎

How to set up custom linting on a fresh Create React App project

First, we need to install some dependencies:

npm i -S @typescript-eslint/eslint-plugin @typescript-eslint/parser cross-env eslint eslint-config-prettier eslint-config-react eslint-plugin-prettier eslint-plugin-sonarjs prettier

In package.json, I’ve defined my npm scripts like this:

  "scripts": {
    "start": "env-cmd react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "lint": "eslint --ext .ts --ext .tsx .",
    "lint-warn": "eslint --ext .ts --ext .tsx . -c .eslintrc.warn.json",
    "eject": "react-scripts eject"
  },

Create a .prettierrc.json file:

{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2
}

If you use VSCode, create this .vscode/settings.json file:

{
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],
  "[javascriptreact]": {
    "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true
    }
  },
  "editor.formatOnSave": true,
  "[typescript]": {
    "editor.formatOnSave": false,
    "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true
    }
  },
  "[typescriptreact]": {
    "editor.formatOnSave": false,
    "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true
    }
  },
  "editor.renderWhitespace": "boundary",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

This will run the code formatter on save.

For any file you don’t want linted (such as serviceWorker.ts), add this line to the top:

/* eslint-disable */

(or create a .eslintignore file)

And here is an example of an eslint config file, which I named .eslintrc.json:

{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "plugin:sonarjs/recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier/@typescript-eslint",
    "plugin:prettier/recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  },
  "rules": {
    "react/prop-types": ["off"],
    "sonarjs/cognitive-complexity": ["error", 5],
    "max-lines-per-function": ["error", 40]
  }
}

This will be run against your code base whenever you execute the `start` or `lint` npm scripts.

The rules section is for any custom rules you might want to add or override. I strongly recommend taking a close look at “cognitive complexity” and other rules put forward by sonarjs: https://github.com/SonarSource/eslint-plugin-sonarjs

A popular lint configuration can be found at Airbnb. Check it out and see if it meets your project’s needs: https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/rules/react.js

For your warning lint configuration file, which I call .eslintrc.warn.json, you can make it more strict. This can be helpful for trying to identify possible linting issues, e.g., code smells that don’t rise to the level of an actual linting error.

{
  "parser": "@typescript-eslint/parser",
  "extends": [".eslintrc.json"],
  "rules": {
    "sonarjs/cognitive-complexity": ["warn", 4],
    "max-lines-per-function": ["warn", 30]
  }
}

To run this on your codebase, run the “lint-warn” npm script.

What about Prop Types?

You may have noticed that I disabled the Prop Types ESLint rule. In my opinion, Typescript makes Prop Types mostly unnecessary, but you should know they are fundamentally different. Prop Types validate props at run time, whereas Typescript validates props at compile time. If your component gets props that cannot be validated at compile time (e.g., from API calls) then it might be appropriate to validate at run time using Prop Types. However, in my opinion, run-time checking should happen as far upstream as possible, e.g., just after the API call inside the API code, because it’s an API issue, not a display component issue. See https://stackoverflow.com/questions/41746028/proptypes-in-a-typescript-react-application

Here is how you would type an optional prop with Typescript:

interface GreeterProps {
  name?: string;
}
 
const Greeter: React.FC<GreeterProps> = 
  ({ name }) => <div>hello {name}lt;/div>;

If the prop were mandatory, you would omit the “?” in the interface declaration.

Summary

Now you have the power to custom lint your create-react-app Typescript project. Please use the power wisely. To save time, adopt a well-known lint rule set. Or feel free to dig in and add or override individual rules as needed. At their best, good linting rules can inspire developers to look a little more closely at their code, and that’s often a very good thing.
If you have any questions at all, please engage with us via comments on this blog post, or reach out to us here.