Typescript Airbnb Style Guide Optimized for Prettier in One JSON File

Last Updated on April 26, 2022 by David

The Airbnb Style Guide is one of the most popular and comprehensive set of ESlint rules. Sadly there is no official version for TypeScript, but there is eslint-config-airbnb-typescript. It does all the painstaking work of making the Airbnb linting rules compatible and optimized for TypeScript and TypeScript ESLint.

While you can install and set things up the usual way using NPM, if you are using Prettier things get very complex and the dependencies pile up fast.

Why Bother Reducing the Airbnb Style Guide to One File?

Being allergic to massive amounts of dependencies I concluded that with a few little mods I could reduce the entire config down to one .eslintrc.json file which was a challenge I could not resist. While you can’t take advantage of any automatic NPM updates, it is much easier to edit and share a single file.

The results are at the end of this article, but let me share how to generate the JSON file yourself. You might want to make an updated one in the future, or just enjoy making the file so you can delete 1,811 redundant files and dance around your computer waving your hands in the air. That is 1,811 unwanted files that the eslint-config-airbnb-typescript and eslint-plugin-import packages install that you won’t ever have to think about again.

After a suggestion on https://www.reddit.com/r/typescript/ I have started to work on automating this using the amazing Deno. It is very much alpha test level at the moment, but is mostly working: ESLint Config Octopus

How to Reduce 1,811 Files into One JSON File

First, install everything we need.

mkdir extract-rules
cd extract-rules

npm init -y
npm install eslint-config-airbnb-typescript \
    eslint-plugin-import \
    @typescript-eslint/eslint-plugin \
    @typescript-eslint/parser \
    eslint-config-prettier \
    typescript \
    eslint \
    --save-dev

and then add the following to package.json so ESLint will use the eslint-config-airbnb-typescript config :

  "eslintConfig": {
    "extends": [
      "airbnb-typescript/base"
    ]
  },

and add this script:

  "scripts": {
    "eslint-check": "eslint --print-config app.js | eslint-config-prettier-check"
  },

The CLI tool needs to point to a sample JS file, here we use app.js, more details of that are here. Now we can extract the entire set of rules into a file:

npx eslint --print-config example.js > output.json

Now some manual labour!

  1. Remove excess properties from rules that are replaced by @typescript versions thanks to eslint-config-airbnb-typescript (these are all at the start of the file). These have all been turned off, and are just a waste of space.
  2. Remove all the rules that use the eslint-plugin-import plugin (one dependency too many, and VS Code deals with much of that anyway)
  3. Remove rules that typescript/eslint eslint-recommended overrides. These are rules that are not needed in TypeScript as they are taken care of by the compiler or VS Code language server. This is usually done automatically, but as we are adding the entire rule-set afterwards we will, unfortunately, turn them back on again unless we remove them. See here.
  4. Run eslint-check to detect all the rules that will conflict with Prettier and remove them. As recommended I also changed the following:
  "curly": [
    "error",
    "all"
  ],
  "no-confusing-arrow": [
    "error",
    {
      "allowParens": false
    }
  ],
  "prefer-arrow-callback": [
    "off",
    {
      "allowNamedFunctions": false,
      "allowUnboundThis": true
    }
  ]

https://github.com/prettier/eslint-config-prettier#curly

https://github.com/prettier/eslint-config-prettier#no-confusing-arrow

https://github.com/prettier/eslint-config-prettier#arrow-body-style-and-prefer-arrow-callback

You can simply copy/paste the rules into your package.json and you have the entire Airbnb set of rules with zero dependencies! Here is an example of my config in package.json:

  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^2.7.0",
    "@typescript-eslint/parser": "^2.7.0",
    "eslint": "^6.6.0",
    "typescript": "^3.7.2"
  },
"eslintConfig": {
    "extends": [
      "eslint:recommended",
      "plugin:@typescript-eslint/eslint-recommended",
      "plugin:@typescript-eslint/recommended",
      "plugin:@typescript-eslint/recommended-requiring-type-checking"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
      "project": "./tsconfig.json"
    },
    "plugins": [
      "@typescript-eslint",
    ],
    "rules": {}
  }

The Result

Here are the rules as created by the steps I outlined… these were generated from the 14.0.0 / 2019-08-09 release:

{
  "rules": {
    "brace-style": ["off"],
    "camelcase": ["off"],
    "@typescript-eslint/camelcase": [
      "error",
      {
        "properties": "never",
        "ignoreDestructuring": false
      }
    ],
    "func-call-spacing": ["off"],
    "indent": ["off"],
    "no-array-constructor": ["off"],
    "@typescript-eslint/no-array-constructor": ["error"],
    "no-empty-function": ["off"],
    "@typescript-eslint/no-empty-function": [
      "error",
      {
        "allow": ["arrowFunctions", "functions", "methods"]
      }
    ],
    "no-extra-parens": ["off"],
    "@typescript-eslint/no-extra-parens": [
      "off",
      "all",
      {
        "conditionalAssign": true,
        "nestedBinaryExpressions": false,
        "returnAssign": false,
        "ignoreJSX": "all",
        "enforceForArrowConditionals": false
      }
    ],
    "no-magic-numbers": ["off"],
    "@typescript-eslint/no-magic-numbers": [
      "off",
      {
        "ignore": [],
        "ignoreArrayIndexes": true,
        "enforceConst": true,
        "detectObjects": false
      }
    ],
    "no-unused-expressions": ["off"],
    "@typescript-eslint/no-unused-expressions": [
      "error",
      {
        "allowShortCircuit": false,
        "allowTernary": false,
        "allowTaggedTemplates": false
      }
    ],
    "no-unused-vars": ["off"],
    "@typescript-eslint/no-unused-vars": [
      "error",
      {
        "vars": "all",
        "args": "after-used",
        "ignoreRestSiblings": true
      }
    ],
    "no-use-before-define": ["off"],
    "@typescript-eslint/no-use-before-define": [
      "error",
      {
        "functions": true,
        "classes": true,
        "variables": true
      }
    ],
    "no-useless-constructor": ["off"],
    "@typescript-eslint/no-useless-constructor": ["error"],
    "quotes": ["off"],
    "semi": ["off"],
    "strict": ["error", "never"],
    "constructor-super": ["error"],
    "no-class-assign": ["error"],
    "no-confusing-arrow": [
      "error",
      {
        "allowParens": false
      }
    ],
    "no-const-assign": ["off"],
    "no-dupe-class-members": ["off"],
    "no-duplicate-imports": ["off"],
    "no-new-symbol": ["off"],
    "no-restricted-imports": [
      "off",
      {
        "paths": [],
        "patterns": []
      }
    ],
    "no-this-before-super": ["off"],
    "no-useless-computed-key": ["error"],
    "no-useless-rename": [
      "error",
      {
        "ignoreDestructuring": false,
        "ignoreImport": false,
        "ignoreExport": false
      }
    ],
    "no-var": ["error"],
    "object-shorthand": [
      "error",
      "always",
      {
        "ignoreConstructors": false,
        "avoidQuotes": true
      }
    ],
    "prefer-arrow-callback": [
      "off",
      {
        "allowNamedFunctions": false,
        "allowUnboundThis": true
      }
    ],
    "prefer-const": [
      "error",
      {
        "destructuring": "any",
        "ignoreReadBeforeAssign": true
      }
    ],
    "prefer-destructuring": [
      "error",
      {
        "VariableDeclarator": {
          "array": false,
          "object": true
        },
        "AssignmentExpression": {
          "array": true,
          "object": false
        }
      },
      {
        "enforceForRenamedProperties": false
      }
    ],
    "prefer-numeric-literals": ["error"],
    "prefer-reflect": ["off"],
    "prefer-rest-params": ["error"],
    "prefer-spread": ["error"],
    "prefer-template": ["error"],
    "require-yield": ["error"],
    "sort-imports": [
      "off",
      {
        "ignoreCase": false,
        "ignoreDeclarationSort": false,
        "ignoreMemberSort": false,
        "memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
      }
    ],
    "symbol-description": ["error"],
    "init-declarations": ["off"],
    "no-catch-shadow": ["off"],
    "no-delete-var": ["error"],
    "no-label-var": ["error"],
    "no-restricted-globals": [
      "error",
      "isFinite",
      "isNaN",
      "addEventListener",
      "blur",
      "close",
      "closed",
      "confirm",
      "defaultStatus",
      "defaultstatus",
      "event",
      "external",
      "find",
      "focus",
      "frameElement",
      "frames",
      "history",
      "innerHeight",
      "innerWidth",
      "length",
      "location",
      "locationbar",
      "menubar",
      "moveBy",
      "moveTo",
      "name",
      "onblur",
      "onerror",
      "onfocus",
      "onload",
      "onresize",
      "onunload",
      "open",
      "opener",
      "opera",
      "outerHeight",
      "outerWidth",
      "pageXOffset",
      "pageYOffset",
      "parent",
      "print",
      "removeEventListener",
      "resizeBy",
      "resizeTo",
      "screen",
      "screenLeft",
      "screenTop",
      "screenX",
      "screenY",
      "scroll",
      "scrollbars",
      "scrollBy",
      "scrollTo",
      "scrollX",
      "scrollY",
      "self",
      "status",
      "statusbar",
      "stop",
      "toolbar",
      "top"
    ],
    "no-shadow": ["error"],
    "no-shadow-restricted-names": ["error"],
    "no-undef": ["off"],
    "no-undef-init": ["error"],
    "no-undefined": ["off"],
    "array-bracket-newline": ["off", "consistent"],
    "array-element-newline": [
      "off",
      {
        "multiline": true,
        "minItems": 3
      }
    ],
    "capitalized-comments": [
      "off",
      "never",
      {
        "line": {
          "ignorePattern": ".*",
          "ignoreInlineComments": true,
          "ignoreConsecutiveComments": true
        },
        "block": {
          "ignorePattern": ".*",
          "ignoreInlineComments": true,
          "ignoreConsecutiveComments": true
        }
      }
    ],
    "consistent-this": ["off"],
    "func-name-matching": [
      "off",
      "always",
      {
        "includeCommonJSModuleExports": false,
        "considerPropertyDescriptor": true
      }
    ],
    "func-names": ["warn"],
    "func-style": ["off", "expression"],
    "id-blacklist": ["off"],
    "id-length": ["off"],
    "id-match": ["off"],
    "jsx-quotes": ["off", "prefer-double"],
    "line-comment-position": [
      "off",
      {
        "position": "above",
        "ignorePattern": "",
        "applyDefaultPatterns": true
      }
    ],
    "lines-between-class-members": [
      "error",
      "always",
      {
        "exceptAfterSingleLine": false
      }
    ],
    "lines-around-comment": ["off"],
    "lines-around-directive": [
      "error",
      {
        "before": "always",
        "after": "always"
      }
    ],
    "max-depth": ["off", 4],
    "max-len": [
      "error",
      100,
      2,
      {
        "ignoreUrls": true,
        "ignoreComments": false,
        "ignoreRegExpLiterals": true,
        "ignoreStrings": true,
        "ignoreTemplateLiterals": true
      }
    ],
    "max-lines": [
      "off",
      {
        "max": 300,
        "skipBlankLines": true,
        "skipComments": true
      }
    ],
    "max-lines-per-function": [
      "off",
      {
        "max": 50,
        "skipBlankLines": true,
        "skipComments": true,
        "IIFEs": true
      }
    ],
    "max-nested-callbacks": ["off"],
    "max-params": ["off", 3],
    "max-statements": ["off", 10],
    "max-statements-per-line": [
      "off",
      {
        "max": 1
      }
    ],
    "multiline-comment-style": ["off", "starred-block"],
    "multiline-ternary": ["off", "never"],
    "new-cap": [
      "error",
      {
        "newIsCap": true,
        "newIsCapExceptions": [],
        "capIsNew": false,
        "capIsNewExceptions": [
          "Immutable.Map",
          "Immutable.Set",
          "Immutable.List"
        ],
        "properties": true
      }
    ],
    "newline-after-var": ["off"],
    "newline-before-return": ["off"],
    "no-bitwise": ["error"],
    "no-continue": ["error"],
    "no-inline-comments": ["off"],
    "no-lonely-if": ["error"],
    "no-mixed-operators": [
      "error",
      {
        "groups": [
          ["%", "**"],
          ["%", "+"],
          ["%", "-"],
          ["%", "*"],
          ["%", "/"],
          ["/", "*"],
          ["&", "|", "<<", ">>", ">>>"],
          ["==", "!=", "===", "!=="],
          ["&&", "||"]
        ],
        "allowSamePrecedence": false
      }
    ],
    "no-multi-assign": ["error"],
    "no-negated-condition": ["off"],
    "no-nested-ternary": ["error"],
    "no-new-object": ["error"],
    "no-plusplus": ["error"],
    "no-restricted-syntax": [
      "error",
      {
        "selector": "ForInStatement",
        "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array."
      },
      {
        "selector": "ForOfStatement",
        "message": "iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations."
      },
      {
        "selector": "LabeledStatement",
        "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand."
      },
      {
        "selector": "WithStatement",
        "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize."
      }
    ],
    "no-ternary": ["off"],
    "no-underscore-dangle": [
      "error",
      {
        "allow": [],
        "allowAfterThis": false,
        "allowAfterSuper": false,
        "enforceInMethodNames": true
      }
    ],
    "no-unneeded-ternary": [
      "error",
      {
        "defaultAssignment": false
      }
    ],
    "one-var": ["error", "never"],
    "operator-assignment": ["error", "always"],
    "padding-line-between-statements": ["off"],
    "prefer-object-spread": ["error"],
    "require-jsdoc": ["off"],
    "sort-keys": [
      "off",
      "asc",
      {
        "caseSensitive": false,
        "natural": true
      }
    ],
    "sort-vars": ["off"],
    "spaced-comment": [
      "error",
      "always",
      {
        "line": {
          "exceptions": ["-", "+"],
          "markers": ["=", "!"]
        },
        "block": {
          "exceptions": ["-", "+"],
          "markers": ["=", "!", ":", "::"],
          "balanced": true
        }
      }
    ],
    "wrap-regex": ["off"],
    "callback-return": ["off"],
    "global-require": ["error"],
    "handle-callback-err": ["off"],
    "no-buffer-constructor": ["error"],
    "no-mixed-requires": ["off", false],
    "no-new-require": ["error"],
    "no-path-concat": ["error"],
    "no-process-env": ["off"],
    "no-process-exit": ["off"],
    "no-restricted-modules": ["off"],
    "no-sync": ["off"],
    "for-direction": ["error"],
    "getter-return": [
      "off",
      {
        "allowImplicit": true
      }
    ],
    "no-async-promise-executor": ["error"],
    "no-await-in-loop": ["error"],
    "no-compare-neg-zero": ["error"],
    "no-cond-assign": ["error", "always"],
    "no-console": ["warn"],
    "no-constant-condition": ["warn"],
    "no-control-regex": ["error"],
    "no-debugger": ["error"],
    "no-dupe-args": ["off"],
    "no-dupe-keys": ["off"],
    "no-duplicate-case": ["error"],
    "no-empty": ["error"],
    "no-empty-character-class": ["error"],
    "no-ex-assign": ["error"],
    "no-extra-boolean-cast": ["error"],
    "no-func-assign": ["error"],
    "no-inner-declarations": ["error"],
    "no-invalid-regexp": ["error"],
    "no-irregular-whitespace": ["error"],
    "no-misleading-character-class": ["error"],
    "no-obj-calls": ["error"],
    "no-prototype-builtins": ["error"],
    "no-regex-spaces": ["error"],
    "no-sparse-arrays": ["error"],
    "no-template-curly-in-string": ["error"],
    "no-unreachable": ["off"],
    "no-unsafe-finally": ["error"],
    "no-unsafe-negation": ["error"],
    "no-negated-in-lhs": ["off"],
    "require-atomic-updates": ["off"],
    "use-isnan": ["error"],
    "valid-jsdoc": ["off"],
    "valid-typeof": [
      "off",
      {
        "requireStringLiterals": true
      }
    ],
    "accessor-pairs": ["off"],
    "array-callback-return": [
      "error",
      {
        "allowImplicit": true
      }
    ],
    "block-scoped-var": ["error"],
    "complexity": ["off", 11],
    "class-methods-use-this": [
      "error",
      {
        "exceptMethods": []
      }
    ],
    "consistent-return": ["error"],
    "curly": ["error", "all"],
    "default-case": [
      "error",
      {
        "commentPattern": "^no default$"
      }
    ],
    "dot-notation": [
      "error",
      {
        "allowKeywords": true,
        "allowPattern": ""
      }
    ],
    "eqeqeq": [
      "error",
      "always",
      {
        "null": "ignore"
      }
    ],
    "guard-for-in": ["error"],
    "max-classes-per-file": ["error", 1],
    "no-alert": ["warn"],
    "no-caller": ["error"],
    "no-case-declarations": ["error"],
    "no-div-regex": ["off"],
    "no-else-return": [
      "error",
      {
        "allowElseIf": false
      }
    ],
    "no-empty-pattern": ["error"],
    "no-eq-null": ["off"],
    "no-eval": ["error"],
    "no-extend-native": ["error"],
    "no-extra-bind": ["error"],
    "no-extra-label": ["error"],
    "no-fallthrough": ["error"],
    "no-global-assign": [
      "error",
      {
        "exceptions": []
      }
    ],
    "no-native-reassign": ["off"],
    "no-implicit-coercion": [
      "off",
      {
        "boolean": false,
        "number": true,
        "string": true,
        "allow": []
      }
    ],
    "no-implicit-globals": ["off"],
    "no-implied-eval": ["error"],
    "no-invalid-this": ["off"],
    "no-iterator": ["error"],
    "no-labels": [
      "error",
      {
        "allowLoop": false,
        "allowSwitch": false
      }
    ],
    "no-lone-blocks": ["error"],
    "no-loop-func": ["error"],
    "no-multi-str": ["error"],
    "no-new": ["error"],
    "no-new-func": ["error"],
    "no-new-wrappers": ["error"],
    "no-octal": ["error"],
    "no-octal-escape": ["error"],
    "no-param-reassign": [
      "error",
      {
        "props": true,
        "ignorePropertyModificationsFor": [
          "acc",
          "accumulator",
          "e",
          "ctx",
          "req",
          "request",
          "res",
          "response",
          "$scope",
          "staticContext"
        ]
      }
    ],
    "no-proto": ["error"],
    "no-redeclare": ["off"],
    "no-restricted-properties": [
      "error",
      {
        "object": "arguments",
        "property": "callee",
        "message": "arguments.callee is deprecated"
      },
      {
        "object": "global",
        "property": "isFinite",
        "message": "Please use Number.isFinite instead"
      },
      {
        "object": "self",
        "property": "isFinite",
        "message": "Please use Number.isFinite instead"
      },
      {
        "object": "window",
        "property": "isFinite",
        "message": "Please use Number.isFinite instead"
      },
      {
        "object": "global",
        "property": "isNaN",
        "message": "Please use Number.isNaN instead"
      },
      {
        "object": "self",
        "property": "isNaN",
        "message": "Please use Number.isNaN instead"
      },
      {
        "object": "window",
        "property": "isNaN",
        "message": "Please use Number.isNaN instead"
      },
      {
        "property": "__defineGetter__",
        "message": "Please use Object.defineProperty instead."
      },
      {
        "property": "__defineSetter__",
        "message": "Please use Object.defineProperty instead."
      },
      {
        "object": "Math",
        "property": "pow",
        "message": "Use the exponentiation operator (**) instead."
      }
    ],
    "no-return-assign": ["error", "always"],
    "no-return-await": ["error"],
    "no-script-url": ["error"],
    "no-self-assign": [
      "error",
      {
        "props": true
      }
    ],
    "no-self-compare": ["error"],
    "no-sequences": ["error"],
    "no-throw-literal": ["error"],
    "no-unmodified-loop-condition": ["off"],
    "no-unused-labels": ["error"],
    "no-useless-call": ["off"],
    "no-useless-catch": ["error"],
    "no-useless-concat": ["error"],
    "no-useless-escape": ["error"],
    "no-useless-return": ["error"],
    "no-void": ["error"],
    "no-warning-comments": [
      "off",
      {
        "terms": ["todo", "fixme", "xxx"],
        "location": "start"
      }
    ],
    "no-with": ["error"],
    "prefer-promise-reject-errors": [
      "error",
      {
        "allowEmptyReject": true
      }
    ],
    "prefer-named-capture-group": ["off"],
    "radix": ["error"],
    "require-await": ["off"],
    "require-unicode-regexp": ["off"],
    "vars-on-top": ["error"],
    "yoda": ["error"]
  }
}

I suggest tightening up your code by turning on these rules too:

{
  // Limit Cyclomatic Complexity
  "complexity": ["error", 11],
  // Enforce a maximum depth that blocks can be nested
  "max-depth": ["error", 4],
  // Enforce a maximum file length
  "max-lines": [
    "error",
    {
      "max": 300,
      "skipBlankLines": true,
      "skipComments": true
    }
  ],
  // Enforce a maximum function length
  "max-lines-per-function": [
    "error",
    {
      "max": 50,
      "skipBlankLines": true,
      "skipComments": true,
      "IIFEs": true
    }
  ],
  // Enforce a maximum depth that callbacks can be nested
  "max-nested-callbacks": ["error", 3],
  // Enforce a maximum number of parameters in function definitions
  "max-params": ["error", 3],
  // Enforce a maximum number of statements allowed per line
  "max-statements": ["error", 10]
}

Leave a Reply

Your email address will not be published. Required fields are marked *