WordPress VIP ESLint plugin
This is an ESLint plugin to provide WordPress VIP's (internal) JavaScript and TypeScript coding standards. It is inspired by and borrows from @wordpress/eslint-plugin.
Installation
Install eslint and @automattic/eslint-plugin-wpvip to your project.
npm install --save-dev eslint @automattic/eslint-plugin-wpvip
ESLint 9 (^9.39.1) and ESLint 10 are supported.
Optional integrations are auto-detected when your project also installs typescript, jest, react, or prettier. These packages are declared as optional peer dependencies so consumers can opt in to the stacks they actually use.
ESLint 10
eslint-plugin-jsx-a11y and eslint-plugin-react work with ESLint 10 (this plugin shims eslint-plugin-react with @eslint/compat), but they have not yet published releases that declare ESLint 10 in their peer dependencies. To avoid ERESOLVE errors with npm, add these overrides to your project's package.json. $eslint refers to your project's own eslint entry, so eslint must be listed directly in devDependencies (or dependencies):
"overrides": {
"eslint-plugin-jsx-a11y": { "eslint": "$eslint" },
"eslint-plugin-react": { "eslint": "$eslint" }
}
Upgrading to 2.0
2.0 adds ESLint 10 support and contains these breaking changes:
eslint-plugin-importis replaced byeslint-plugin-import-x. It is registered under theimportnamespace, so rule IDs such asimport/orderandeslint-disablecomments are unchanged. However:- Settings now use the
import-x/prefix.import/*settings (import/resolver,import/internal-regex,import/core-modules, …) are ignored; rename them, e.g.import/resolver→import-x/resolver. - Do not register
eslint-plugin-importunderimportyourself; ESLint will throwCannot redefine plugin "import". import/enforce-node-protocol-usagedoes not exist ineslint-plugin-import-x; remove it from your config.eslint-plugin-importis no longer installed with this package. If your config requires it, add it to your owndevDependencies.
- Settings now use the
- JavaScript files are parsed with ESLint's default parser (espree) instead of
@babel/eslint-parser. Your project's Babel config is no longer applied, so syntax that only Babel understands (e.g. Flow or legacy decorators) is not supported, andparserOptions.babelOptions/requireConfigFileare ignored. The ECMAScript version follows ESLint's default (latest), so new built-in globals (e.g.SuppressedError,Iterator) are recognized. - Deprecated core formatting rules moved to
@stylistic/eslint-plugin. ESLint is removing its formatting rules, so theformatting,javascriptandreactconfigs now use@stylistic/*rules configured with the same options (e.g.indent→@stylistic/indent,func-call-spacing→@stylistic/function-call-spacing). Rename any overrides oreslint-disablecomments for these rules; overriding the old core names no longer has any effect.no-new-symbolis replaced byno-new-native-nonconstructor.- Results are not always identical:
@stylisticfixes bugs in the frozen core rules. Notably,@stylistic/indentnow checks lines whose indentation mixes tabs and spaces (coreindentskipped them), such as a closing}/)/]indented with tabs plus two spaces, and indents chained assignments (a = b = c) differently. This only matters if the formatting rules are active, i.e. you don't use theprettierconfig, which turns them off. - This package registers
@stylistic/eslint-pluginunder@stylistic. If your config also registers it, make sure npm resolves the same copy (a compatible version range), or ESLint will throwCannot redefine plugin "@stylistic".
- Results are not always identical:
- The
jsdocconfig no longer enablesjsdoc/newline-after-description, which was removed fromeslint-plugin-jsdocand made the config fail to load. - ESLint
^9.39.1or^10.0.0is required (previously^9.7.0). - Node.js
^22.22.2 || >=24.15.0is required, including with ESLint 9 (Node.js 20 is end-of-life).eslint-plugin-jsdoc65 requires these versions;@stylistic/eslint-pluginis ESM-only and is loaded withrequire(), which needs Node's built-inrequire(esm)support, and@eslint/compat(used to supporteslint-plugin-reacton ESLint 10) needs Node.js 20.19+ / 22.13+.
Contributing
See CONTRIBUTING.md for details on development, testing, publishing, etc.
Configuration
Create an eslint.config.cjs file. The .cjs extension makes Node.js treat it as CommonJS even if your package.json has "type": "module". The examples in this README use this file:
const wpvip = require( '@automattic/eslint-plugin-wpvip' );
module.exports = [ ...wpvip.configs.recommended ];
And that's it! It works automatically with most JavaScript and TypeScript projects.
The typescript, testing, react, and prettier parts of recommended are loaded only when the corresponding package is installed. If you need those optional configs resolved up front rather than when recommended is loaded, require init first. It uses the same detection as recommended, so optional integrations whose package is not installed are still skipped:
require( '@automattic/eslint-plugin-wpvip/init' );
Code editors that are configured to work with ESLint will automatically pick up the rules and flag any errors or warnings.
If your project uses only JavaScript, you do not need to install the optional peers. If you use the modular typescript, testing, react, or prettier configs directly, install the corresponding package in your project first.
To skip files or paths, add a config object with ignores (for example { ignores: [ 'dist/**' ] }) to the array.
Package scripts can be useful to run linting and formatting commands automatically. Here are some suggested scripts for your project's package.json—only copy the ones that are useful to you. The cmd: scripts help you compose commands without repeating verbose CLI arguments.
{
"scripts": {
"cmd:format": "prettier '**/*.(js|json|jsx|md|ts|tsx|yml|yaml)'",
"cmd:lint": "eslint",
"format": "npm run cmd:format -- --write",
"format:check": "npm run cmd:format -- --check",
"lint": "npm run cmd:lint .",
"lint:fix": "npm run cmd:lint . -- --fix",
"lint:ignore-warnings": "npm run cmd:lint . -- --quiet"
}
}
Note: ESLint reads ignore patterns from the ignores key in eslint.config.cjs; to reuse .gitignore, see includeIgnoreFile. Prettier automatically ignores files listed in .prettierignore or you can target .gitignore using --ignore-path.
Recommended config
The "recommended" config includes rules for JavaScript, TypeScript, Jest, and React, including rules related to formatting and white space. It is intended to be strict! Opinionated defaults keep our codebases consistent and reduce the friction we experience when context-switching between projects.
Of course, this recommended config may not be ideal for every project, so feel free to "build your own" using the available modular configs. The recommended config is equivalent to:
const wpvip = require( '@automattic/eslint-plugin-wpvip' );
module.exports = [
...wpvip.configs.javascript,
...wpvip.configs.formatting,
...wpvip.configs.typescript, // when "typescript" is installed
...wpvip.configs.testing, // when "jest" is installed
...wpvip.configs.react, // when "react" is installed
...wpvip.configs.prettier, // when "prettier" is installed
];
Note that the order of configs can matter, since they can contain overrides. It is particularly important to add the prettier config last.
Prettier
Install WP Prettier v3.x to benefit from additional formatting rules:
npm i --save-dev --save-exact "prettier@npm:[email protected]"
This repo also provides a Prettier config, which you can use with the following .prettierrc:
"@automattic/eslint-plugin-wpvip/prettierrc"
For maximum benefit, see Prettier's documentation on enabling format-on-save in your editor. This enables you to concentrate on coding while Prettier handles formatting.
Editorconfig
Editorconfig provides additional formatting rules and works well with Prettier. Copy the .editorconfig file from this repo into your project.
CLI
The cli config allows certain behaviors that are usually against best practice but are useful in a codebase that produces a CLI tool:
module.exports = [ ...wpvip.configs.recommended, ...wpvip.configs.cli ];
If your project is not a CLI tool but spawns child processes occasionally, you probably don't need this config. Instead, disable those rules only where necessary and include an explanation:
// Running a fixed, trusted command.
// eslint-disable-next-line security/detect-child-process
const { execSync } = require( 'child_process' );
Note that console.log is still forbidden (see here for an explanation). If you're writing one-off Node scripts, you can disable the rule per file:
/* eslint-disable no-console */
JSDoc
JSDoc is considered optional, especially compared to better alternatives like TypeScript and OpenAPI documentation. If you want to enforce the use of JSDoc, use the jsdoc config:
module.exports = [ ...wpvip.configs.recommended, ...wpvip.configs.jsdoc ];
Note that rules that require @param and @return types are relaxed in TypeScript files.
"Weak" configs
This plugin provides a few so-called "weak" configs for legacy codebases that are working to transition to stronger standards. These configs downgrade select rules from the recommended config to warnings. Warnings will still be visible in code editors and other outputs but will not fail continous integration workflows.
These configs are intended for temporary use and should not be used long-term. We also do not recommend the use of tools like eslines to ignore errors or warnings. While the intention is to prevent large-scale changes and transition slowly to stronger standards, the effect is usually that the transition stalls and eventually stops completely.
Three "weak" configs are available: weak-javascript, weak-typescript, and weak-testing. While pull requests on this project are always welcome, please carefully consider whether adding rules to these configs is truly necessary. Ideally, we work to remove rules from these configs until they are no longer needed.