Dev Diary 2020.03.27 – ESLint Config - davidhorm/wavelength GitHub Wiki

Motivation

As a person sheltering at home because of COVID-19, I want to be able to play Wavelength with my friends online.

As a developer, I want to have steps to start up a React project with my favorite toolchain.

Dev Diary - React + TypeScript + ESLint + Prettier

There are a specific list of tools I want to work with when I'm building a single-page application:

  • Wiki - for a Dev Diary, so I can pick up back where I left off.
  • React - Because one-way data flow is easier to maintain then two-way binding.
  • TypeScript - Because it'll enforce what you think you're doing in JavaScript.
  • ESLint - So all my projects use the same "grammar" and enforce best practices.
  • Prettier - So all my projects are formatted the same.
  • Storybook - For living tech spec documentation, and component gallery.
  • Jest & React Testing Library - For unit testing.

I've seen list of steps on how to integrate all of these tools together, but sometimes they're out of date, or not really applicable when integrating one with another. So here's my list of steps, but with links to the original source for you to reference in case of any updates.

Followed the installation instructions for Create React App Adding Typescript

npx create-react-app wavelength --template typescript
cd wavelength
yarn add typescript @types/node @types/react @types/react-dom @types/jest

Hm. I might've seen a message about how @types/react and @types/react-dom might've already been installed by CRA TS template. Will look into that later.

Next create the wavelength.wiki project within GitHub. Clone that project alongside the wavelength folder, add it to the wavelength.code-workspace file.

I've seen lists try to to configure ESLint manually, but I think using its --init feature is easiest. ESLint Installation and Usage

yarn add eslint --dev
npx eslint --init
? How would you like to use ESLint? To check syntax, find problems, and enforce code style
? What type of modules does your project use? JavaScript modules (import/export)
? Which framework does your project use? React
? Does your project use TypeScript? Yes
? Where does your code run? Browser, Node
? How would you like to define a style for your project? Use a popular style guide
? Which style guide do you want to follow? Airbnb: https://github.com/airbnb/javascript
? What format do you want your config file to be in? JavaScript
? Would you like to install them now with npm? Yes

TODO: npm audit fix at your own risk

This creates the .eslintrc.js which is the main thing I care about. But here's the thing: eslint-config-airbnb isn't for TypeScript. But here's the other thing: there's an Airbnb's ESLint config with TypeScript support, eslint-config-airbnb-typescript, but don't need the other eslint-plugin-* packages listed in that site because they were already installed with the eslint --init.

yarn add --dev eslint-config-airbnb-typescript

I want to be able to run yarn lint, and have all the lint rules in .eslintrc.js.

Update the scripts property of package.json file.

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
-   "eject": "react-scripts eject"
+   "eject": "react-scripts eject",
+   "lint": "eslint --ext .js,.jsx,.ts,.tsx ./"
  },

Update the extends and parserOptions properties of .eslintrc.js file with details found in eslint-config-airbnb-typescript. Note: You can remove the original extends items because of https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md#eslint-configs. Also because TypeScript *.d.ts files have triple slash comments, lets make that exception in the rules property, using the spaced-comment rule options. NOTE: final eslint config here

  extends: [
-   'plugin:react/recommended',
-   'airbnb',
+   'airbnb-typescript',
+   'airbnb/hooks',
  ],
  parserOptions: {
+   project: './tsconfig.json',
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2018,
    sourceType: 'module',
  },
  rules: {
+   'spaced-comment': ['error', 'always', { 'markers': ['/'] }]
  },
};

You can run yarn lint and see a bunch of errors and warnings with the CRA default install. We'll ignore these for now.

Both ESLint, and Prettier have opinions on how the code should be formatted. Rules can conflict. But luckily, there's a way to easily disable the ESLint rules that conflict with the recommended Prettier rules. Prettier integrating with linters with recommended configuration

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

And then per the eslint-config-prettier Installation, we can extend other prettier plugins to deactivate the other conflicting ESLint plugin rules. We have to make sure these rules always stay at the bottom of the extends property.

  "extends": [
    ...
+   'plugin:prettier/recommended',
+   'prettier/@typescript-eslint',
+   'prettier/react',
  ]

Then create .prettierrc.js for Prettier configuration file at the root, and using the non-default Prettier settings I like:

module.exports = {
  printWidth: 120,
  singleQuote: true,
  quoteProps: 'consistent',
};

Now run yarn lint --fix to clean up some of the linting errors. Now since Prettier has a way to turn off eslint-plugin-unicorn, lets add that.

yarn add --dev eslint-plugin-unicorn

Update the extends and rules (to make it more React friendly rules) properties of .eslintrc.js file with the following:

  extends: [
    ...
+   'plugin:unicorn/recommended',
    ...
  ],
  rules: {
    ...
+   'unicorn/filename-case': 0, // React has their own way of naming things
+   'unicorn/prevent-abbreviations': [ 'error', { 'whitelist': { 'env': true } } ], // Allow react-app-env.d.ts file name
  },

Checkout https://github.com/dustinspecker/awesome-eslint and install/config rules of interest.

yarn add --dev eslint-plugin-eslint-plugin eslint-plugin-eslint-comments eslint-plugin-jsdoc eslint-plugin-jest eslint-plugin-sonarjs eslint-plugin-no-loops

Hm, when I try to disable the sonarjs/cognitive-complexity then it complains that the rule doesn't exist when I yarn start. But here's one answer. https://github.com/SonarSource/eslint-plugin-sonarjs/issues/122. I couldn't get it to work, so I'll just leave that rule alone for now.

Create .vscode/settings.json file with the following content:

{
  "editor.codeActionsOnSave": {
    "source.organizeImports": true,
    "source.fixAll": true,
  }
}

Note: normally I'd want settings in the wavelength.code-workspace file, but I can't get "source.fixAll": true to work in there.

.eslintrc.js

Added comments to .eslintrc.js file to keep track of what rules and why.

module.exports = {
  env: {
    browser: true, // allow browser global variables
    es6: true,
    node: true, // allow Node.js global variables and Node.js scoping
    jest: true, // allow Jest global variables
  },
  extends: [
    /**
     * Airbnb's ESLint config with TypeScript support
     * https://github.com/iamturns/eslint-config-airbnb-typescript#i-wish-this-config-would-support-
     * 
     * Contains and turns on the following ESLint Configs:
     * 
     * eslint-plugin-import - ESLint plugin with rules that help validate proper imports.
     * https://github.com/benmosher/eslint-plugin-import#rules
     * 
     * eslint-plugin-jsx-a11y - Static AST checker for a11y rules on JSX elements.
     * https://github.com/evcohen/eslint-plugin-jsx-a11y#supported-rules
     * 
     * eslint-plugin-react - Recommended React specific linting rules for ESLint
     * https://github.com/yannickcr/eslint-plugin-react#recommended
     * 
     * eslint-plugin-react-hooks - This ESLint plugin enforces the Rules of Hooks.
     * https://reactjs.org/docs/hooks-rules.html
     */
    'airbnb-typescript',
    'airbnb/hooks',

    /**
     * Various awesome ESLint rules.
     * https://github.com/sindresorhus/eslint-plugin-unicorn#rules
     */
    'plugin:unicorn/recommended',

    /**
     * An ESLint plugin for linting ESLint plugins
     * https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin#supported-rules
     */
    'plugin:eslint-plugin/recommended',

    /**
     * Best practices when disabling ESLint rules
     * https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/
     */
    'plugin:eslint-comments/recommended',

    /**
     * JSDoc linting rules for ESLint.
     * https://github.com/gajus/eslint-plugin-jsdoc#configuration
     */
    'plugin:jsdoc/recommended',

    /**
     * 'plugin:jest/recommended' plugin exports a recommended configuration that enforces good testing practices.
     * 'plugin:jest/style' adds some stylistic rules, such as `prefer-to-be-null`, which enforces usage of `toBeNull` over `toBe(null)`.
     * https://github.com/jest-community/eslint-plugin-jest#rules
     */
    'plugin:jest/recommended',
    'plugin:jest/style',

    /**
     * SonarJS rules for ESLint to detect bugs and suspicious patterns in your code.
     * https://github.com/SonarSource/eslint-plugin-sonarjs#eslint-plugin-sonarjs----
     */
    'plugin:sonarjs/recommended',

    /**
     * THESE PRETTIER RULES SHOULD ALWAYS BE LAST CONFIG IN EXTENDS ARRAY!
     * Turns off all ESLint rules that are unnecessary or might conflict with Prettier.
     * https://github.com/prettier/eslint-config-prettier
     */
    'plugin:prettier/recommended',
    'prettier/@typescript-eslint',
    'prettier/react',
    'prettier/unicorn',
  ],
  globals: {
    Atomics: 'readonly',
    SharedArrayBuffer: 'readonly',
  },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: './tsconfig.json',
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2018,
    sourceType: 'module',
  },
  plugins: [
    '@typescript-eslint',
    'eslint-plugin',
    'jest',
    'jsdoc',
    'jsx-a11y',
    'no-loops',
    'react',
    'react-hooks',
    'sonarjs',
  ],
  rules: {
    'spaced-comment': ['error', 'always', { 'markers': ['/'] }], // Allow triple slash comments used in *.d.ts files
    'unicorn/filename-case': 0, // React has their own way of naming things
    'unicorn/prevent-abbreviations': [ 'error', { 'whitelist': { 'env': true } } ], // Allow react-app-env.d.ts file name
    "no-loops/no-loops": 2, // https://github.com/buildo/eslint-plugin-no-loops#why
    "eslint-comments/disable-enable-pair": ["error", {"allowWholeFile": true}], // Allow disabling for the whole file
  },
  settings: {
    jsdoc: {
      mode: 'typescript' // Allows @template. https://github.com/gajus/eslint-plugin-jsdoc#mode
    }
  }
};