💡 Also see my page: "General DevOps and Dev Tools"
Enforcing coding standards
The usual way to enforce coding standards, such as how to use JSDoc commenting, is through a linting extension, such as eslint
for JS, or tslint
for TypeScript. These extensions are configurable, so you can tell it what to look for and how strict it should be.
Helpful links:
However, note that if these are installed as "extensions" they only enforce rules for yourself and devs that have it installed. If you are trying to enforce standards across many devs, across a repo, you might want to make the linter a dependency and then have it run as a build step and/or git hook.
TSLint vs ESLint
Based on what Palantir has publicly said, everyone should try to use ESLint over TSLint moving forward.
ESlint has also made it clear that this will be the way forward for TypeScript.
XO Notes:
XO - General Use
In general, XO is mostly a set of thought-out (opinionated) default settings for ESLint, so you don't have to mess with a bunch of config files.
This also means that adjusting XO really usually means just adjusting ESLint rules, so make sure to check out my eslint notes, as well as the official docs and rules.
If you want just the ESLint config files that match XO, you can find them here
XO - Config file
Easiest way is to add to your package.json
, under xo: {}
block. Or, run npm init xo
, which does the same thing for you.
You also have the option of:
.xo-config
(JSON).xo-config.json
(JSON).xo-config.js
(JS Module)xo.config.js
(JS Module)
For details, see the config section of the readme.
Warning: The main XO VSCode extension does not contribute JSON Schema intellisense for any configs.
XO - Ignoring Lines or Files
You can ignore a specific line by using the ESLint disable comment trick (since XO uses ESLint), but you must target a specific rule to disable. For example, to disable XO(no-unused-vars)
for one line:
// eslint-disable-next-line no-unused-vars
const neverUsed = 123;
For ignoring entire files, you have a few options:
- Add to
.gitignore
and/or.eslintignore
--ignore
CLI option- Add glob patterns to
ignores
config option:ignores: []
TSLint notes:
TSLint - Overriding rules:
See: rule-flags docs.
-
Temporarily disable on next line:
-
/* tslint:disable-next-line */
- Or
// @ts-ignore
-
-
Disable rule
/* tslint:disable:{rule} */
ESLint notes:
ESLint - Install
# NPM
npm install eslint --save-dev
# Yarn
yarn add eslint --dev
ESLint - Config file
See Configuring ESLint for details, and this section for precedence of config files.
Summary of options:
-
.eslintrc.*
.eslintrc.json
(comments allowed).eslintrc
(same as above).eslintrc.js
(export config object).eslintrc.yaml
|.eslintrc.yml
package.json
->eslintConfig: {}
You can use
npx eslint --init
to auto-create a.eslintrc
file.
ESLint - Config File - Intellisense and Autocomplete
If you are using the .eslintrc.js
option, you need to annotate the type of the config object for intellisense to kick in:
/** @type {import('eslint').Linter.Config} */
module.exports = {
env: {
browser: true,
}
//,
// ...
}
⚠ Warning: Due to incomplete types exported by ESLint core, this actually has worse type checking than using the JSON based options (since that pulls from published schema instead of types). Even using
@types/eslint
doesn't look much better 🤷♀️
For any .json
config files, if you aren't getting intellisense (due to non-standard filename), you can specify the $schema
manually:
{
"$schema": "https://json.schemastore.org/eslintrc",
"env": {
"browser": true
}
}
ESLint - Peer dependencies
A lot of eslint config packages require peer-dependencies that are not automatically installed when you do NPM install. For example, the eslint-config-airbnb
needs a lot of peer dependencies to be manually installed, or else it will not work.
See my notes on NPM / node for details on auto-installing peer-dependencies.
ESLint - TypeScript
- REMEMBER: install
@types/node
for node support - Make sure typescript is installed locally (
yarn add typescript --dev
) -
Add the parser (
@typescript-eslint/parser
) and the plugin (@typescript-eslint/eslint-plugin
) dependencies-
Update the ESlint config to use them:
{ "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint/eslint-plugin"] }
-
-
You might need to explicitly add TypeScript as a language for ESlint to validate:
- In
.vscode/settings.json
->"eslint.validate": ["javascript", "javascriptreact", "typescript"]
- In
-
If you want ESlint to recognize and read your TSConfig file, you might need to tweak some settings in your
eslintrc
config:parserOptions: {"project": "./tsconfig.json"}
-
If you use import or exports, turn on module support:
parserOptions: {"sourceType": "module"}
If you get "Parsing error: ImportDeclaration should appear when the mode is ES6 and in the module context" error, first try making sure you have "sourceType" set to "module" under
parserOptions
(see this for details). If this doesn't fix it, it could be because you are importing a type out of a type definition file (e.g.@types/...
), which the current parser + eslint can have a hard time handling. There are several issues around this, here and here.
Some extra guides:
- https://mhartington.io/post/typescript-eslint-setup/
- https://javascriptplayground.com/typescript-eslint/
- https://43081j.com/2019/02/using-eslint-with-typescript
ESLint - Misc troubleshooting
-
Parsing error: The keyword '___' is reserved
(where keyword is a native JS thing likeconst
orimport
)-
Make sure you have
ecmaVersion
set. For example:// .eslintrc.json { "extends": ["prettier"], "parserOptions": { "ecmaVersion": 2018 } }
-
-
Parsing error: 'import' and 'export' may appear only with 'sourceType: module'
- Setting
parserOptions: { "sourceType": "module" }
is often enough to fix it (with the right ecmaVersion set) - Can also be fixed by using
babel-eslint
- Setting
-
Parsing error: Unexpected token <
-
Are you using React? If so, you need to take a few steps:
- Make sure
eslint-plugin-react
is installed (yarn add --dev eslint-plugin-react
) - Add plugin to eslint config:
"plugins": ["react"]
- Add JSX support to eslint config:
"parserOptions" -> "ecmaFeatures" -> "jsx": true
- Make sure
-
-
Something else / linting not kicking in
- Very helpful: check what is showing up in the Output panel of VSCode, after selecting "ESLint" from the dropdown.
ESLint - Issues with plugins
This can be tricky to troubleshoot. If you are on version 6, you could always try pinning ES to version 5 - see this thead and this one.
Another thing to try is to make sure you are not mixing dependency locations - e.g. ESLint and all its plugins and dependencies need to be installed locally (through package.json) or globally (npm install -g
), but not both. And with v6+, it looks like you should only install locally.
Another common issue is completely unrelated, but often throws errors about plugins - issues with a multi-root workspace - see below:
ESLint - Multi-root workspace / monorepos
I recently had a heck of a time figuring out why ESLint was throwing all kinds of errors that didn't make sense - primarily the:
"Failed to load plugin 'vue' declared in 'frontend.eslintrc.js': Cannot find module 'eslint-plugin-vue'
That error actually had nothing to do with the plugin itself, but everything to do with the workspace file structure. I had what one might call a multi-root or monorepo structure, where major distinct projects (with separate node_modules and package.json) are in the same workspace. My structure looked like:
-
.
(project root)./frontend
./backend
It turns out for this kind of setup, running eslint against a file will work fine from the command line, but running from VSCode / extension will fail, because it doesn't understand the separation.
The solution is simple: the eslint.workingDirectories
setting.
In {project-root}/.vscode/settings.json
, I should have had:
{
"eslint.workingDirectories": [
"./frontend",
"./backend"
]
}
You can omit a directory if it is not configured for eslint. Or, if eslint is having trouble with imports, use changeProcessCWD
(last resort):
{
"eslint.workingDirectories": [
{
"directory": "./frontend",
"changeProcessCWD": true
}
]
}
ESLint - Overriding rules
There are multiple ways to modify eslint rules.
Here is the full list of rules.
ESLint - Modifying the actual config files
See this section about the difference between all the different config file types.
If you you just to turn a rule off, just set the key/pair value to "off".
{
"rules": {
"prefer-arrow-callbacks": "off"
}
}
ESLint - Non-global Overrides
If you just need to suppress the errors in a single spot or file, you can use inline comments.
For example, to disable / ignore for an entire file, you can put this at the top of the file:
/* eslint-disable */
Or, for a specific line, // eslint-disable-line
or // eslint-disable-next-line
.
You can also target specific rules:
// Example is function with too many params - violates `max-params` rule
// eslint-disable-next-line max-params
function tooManyParams(alpha, beta, charlie, delta, echo, foxtrot, golf) {
//
}
ESLint - AirBnB Config Setup
-
Install config and peer dependencies
-
React:
npx install-peerdeps --dev eslint-config-airbnb
- If above doesn't work, see alternatives
-
Non-React:
npx install-peerdeps --dev eslint-config-airbnb-base
- If above doesn't work, see alternatives
-
For above commands, use
--yarn
flag if using yarn.
-
-
Add the airbnb config to the
extends
option in your eslint config file-
If you are using prettier, make sure those rules come last:
- Example:
"extends": ["airbnb-base", "plugin:prettier/recommended"]
- Example:
-
ESLint + Prettier - Sample Workflow
-
Install ESlint, Prettier, Config, and Plugin
-
yarn add --dev eslint prettier eslint-config-prettier eslint-plugin-prettier
- OR
npm install --save-dev eslint prettier eslint-config-prettier eslint-plugin-prettier
-
-
Setup config file for ESlint
-
Create it
-
echo $'{\n "extends": ["plugin:prettier/recommended"]\n}' > .eslintrc.json
- OR
npx eslint --init
and then manually add
-
-
OPTIONAL: Set logical defaults
- You usually want to tweak
parserOptions -> ecmaVersion
if you are using any "newer" features, such as theconst
keyword "env": { "browser": true }
- You usually want to tweak
-
-
Tweak defaults to desired
-
For formatting rules, you should really make changes to Prettier directly in Prettier config files, not ESLint
touch .prettierrc
-
Modify
.prettierrc
:{ "tabWidth": 4, "useTabs": true, "singleQuote": true, "endOfLine": "lf" }
-
Optionally, could set some of these settings in
.vscode/settings.json
too- Example:
"editor.insertSpaces": false
- Example:
- Pure ESLint changes can go under
.eslintrc.json
->rules: {}
-
-
OPTIONAL: Integrate with other ESLint configs
-
Reminder: Prettier is really concerned with code formatting (spacing, indenting, etc.), and not linting rules (no-unused-vars, etc.)
- They try to make this clear, but when combining with ESLint, it can be easy to forget that you still need to add pure lint rules to get more value out of ESLint
-
The most popular option is to combine with
eslint-config-airbnb
- See my guide for that here
-
-
OPTIONAL: Integrate into dev pipeline
-
You can have VSCode auto-format with prettier every time you hit save:
-
.vscode/settings.json
:// For more granular formatOnSave, nest under per-language settings with `"[javascript]": { ... }` "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode",
-
-
You can add formatting and/or linting as build or test step
{ "lint": "eslint", "lint:fix": "eslint --fix" }
You might need to tweak these to target specific glob patterns or file types, depending on how other config files are setup
Also, note thateslint --fix
will actually use Prettier for formatting if you have followed the above steps correctly. This is preferred overprettier --write
-
You can add formatting and/or linting as pre-commit hooks
- Tons of guides out there.
husky
is the go-to for running git hooks - prettier docs
-
Bare-bones instructions:
yarn add --dev husky
-
package.json
:"husky": { "hooks": { // call your lint and format script entries below "pre-commit": "___" } }
- Tons of guides out there.
-
You can call your lint script entry from your CI/CD pipeline, to check code quality on every push
- This ensures that code that violates a linting rule can't make it past, even if a developer has a broken dev environment and their local machine is not catching things
-
Prettier Notes
Prettier - Common CLI Commands
📘 Docs: CLI
-
Format and write out changes
prettier --write {inputGlobOrFiles}
-
Examples:
prettier --write .
prettier --write index.js
prettier --write "src/**/*.js"
-
Check only, and don't write out changes
prettier --check {inputGlobOrFiles}
- Since this will emit an exit code, you can use this as part of a commit hook, CI/CD pipeline, etc.
Prettier - Config file
Options:
.prettierrc
package.json
->prettier: {}
.prettierrc.json
|.prettierrc.yml
|.prettierrc.yaml
-
.prettierrc.js
|.prettierrc.config.js
- Must export object with config
.prettierrc.toml
-
Less Documented:
.vscode/settings.json
- Prefixed with
prettier
: e.g.{ "prettier.tabWidth": 4 }
- NOTE: This really only affects the Prettier VSCode extension - not the core dependency / engine itself
- Prefixed with
Prettier - Troubleshooting
-
Actually getting Prettier to work
- Make sure you have setup a config (see config)
- Make sure you have IDE extension installed and enabled (at least for workspace)
-
Make sure you have dependencies installed
npm install --global prettier
yarn global add prettier
- Or, manage with repo
package.json
file
-
Getting the linters to kick-in
- You might have noticed that, even with the Prettier IDE extension and dependency installed, you still don't get inline problems reported
-
Prettier stopped shipping with a built-in linter, so you always need to install a linter and have prettier use it
- See my own notes above on Prettier + ESLint
- See Docs: Integrating with Linters and prettier-vscode Docs -> Linter Integration
-
Still having issues? Try (assuming using ESLint):
- Checking the prettier output panel in VSCode
- Checking the eslint output panel in VSCode
- Make sure you have all dependencies and peer-dependencies installed for Prettier + ESLint configs
-
Make sure you have
eslint-plugin-prettier
installed and configured correctly in your eslint config.- For example, usually you want
"prettier"
in theplugins
array, andplugin:prettier/recommended
(which requires theeslint-config-prettier
dependency) in theextends
array. - See the eslint-plugin-prettier docs for detailed instructions
- For example, usually you want
- Last resort: If you are using
eslint
, try modifying the VSCode setting:eslint.validate: ["javascript", ...]
to make sure your language is there
-
Prettier is not respecting config settings
- Check that you don't have a local config file that is overriding another (e.g., a
.pretterrc
overriding a VSCode setting) -
If you are in a multi-project directory and/or with nested project(s), make sure that the config file is at the right level.
- If you are using Prettier as a VSCode extension, in order to get it to work properly with a nested setup, you might need to create a workspace, which has both the root folder open, as well as the project sub-folder
- Check that you don't have a local config file that is overriding another (e.g., a
Prettier - Other Resources and Guides
- Testing: Checkout the prettier playground
- Config options: prettier options
This is a really good comprehensive guide on using Prettier with VSCode, including with CI steps.
This is another good guide on ESLint + Prettier.
MDN even has a guide on ESLint + Prettier.
This guide, covers ESLint + Prettier, plus how you can combine it with the AirBnB style rules.
Prettier - Disabling
Main docs - "Ignoring Code"
-
Whole files
.prettierignore
file--ignore-path
CLI option
-
Section of code
- JS / TS:
// prettier-ignore
- JSX:
{/* prettier-ignore */}
- HTML:
<!-- prettier-ignore -->
- CSS:
/* prettier-ignore */
- Markdown:
<!-- prettier-ignore -->
- GraphQL:
# prettier-ignore
- Handlebars:
{{! prettier-ignore }}
- JS / TS:
💡 - Tip: You can use re-use your
.gitignore
as Prettier's ignore rule set by using the--ignore-path
😞 - Prettier's ignore defaults and config options are somewhat weak, and there are current requests to improve them.
JSON Schema
Ever wondered how VSCode provides auto-complete and intellisense for standard config files, like tsconfig.json
, or if you have the right extensions installed, .eslintrc.json
, .prettierrc
, etc.? Welcome to the magical world of JSON Schema!
The TLDR of this is that JSON Schema is a standardized way to write (with JSON) a specification for how a certain JSON file type should always look. It is powerful in that it can be used for both autocomplete and intellisense (providing hints to the user on what certain fields are for), as well as validation.
Resources
-
Main website: json-schema.org
-
Public repository of schemas - schemastore.org
- Github repo: schemastore/schemastore
-
Online Tools:
-
Great intros:
- Meuleman: JSON-schemas are awesome
-
json-schema.org: Getting Started: Step-by-Step
- Very simple example schema: address.schema.json
JSON Schema Resolver in VSCode
There are multiple ways in which VSCode resolves which Schema to apply against an open JSON file - the main docs covering this are here. To summarize though, the main options are:
- In the file itself, via the
$schema
attribute (path) -
In VSCode
settings.json
, underjson.schemas
:json.schemas: Array<{ fileMatch: Array<fileGlobs>, url: schemaPath }>
-
Through a VSCode extension, by contributing through
jsonValidation
:jsonValidation: Array<{ fileMatch: fileGlob, url: schemaPath }>
Note: For the
$schema
orurl
value, the path to the schema file does not have to be local; many extensions and plugins use a public URL, such ashttp://json.schemastore.org/eslintrc
Here are some practical examples:
-
Here is how the
vscode-eslint
extension contributes schemas- This is why
.eslintrc
has intellisense if the extension is installed - Here is the schema that gets merged with NPM for
package.json
- specifically for theeslintConfig
key
- This is why
- Here is a similar contribution from
prettier-vscode
Bundlers
See separate cheatsheet - Bundlers