Typescript Airbnb Style Guide Optimized for Prettier in One JSON File

If you are looking to lint your JavaScript, then the Airbnb Style Guide is the best by far! Now you have made the sensible decision to use TypeScript, you may well be wanting all those wonderful linting rules back in your project. Thanks to a few different open-source projects now you can.

A massive shout out to the amazing eslint-config-airbnb-typescript project for the painstaking work of making the Airbnb linting rules compatible and optimized for TypeScript for use with TypeScript ESLint.

Why Bother Reducing the Airbnb Style Guide to One File?

Being allergic to massive amounts of dependencies I had a look at this project and concluded that with a few little mods I could reduce it all down to one JSON file… which was a challenge I could not resist. I realize I now can’t take advantage of any updates, but on the flip side, it is much easier to edit and share.

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!

How to Reduce 1,811 Files into One JSON File

First, install everything we need, you can use npm instead if you enjoy thousands of duplicated files polluting your hard drive:

mkdir extract-rules
cd extract-rules

pnpm init -y
pnpm 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:

pnpx 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 ruleset 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 to tighten up your code you turn on these rules to their default values 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 Comment

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