ESLint - JavaScript linter
The ESLint website describes the tool as follows:
ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs.
ESLint catches JavaScript mistakes before they reach a browser, such as a
broken import, an unused variable or a console.log() call. Vortex
comes with a pre-configured ESLint
ruleset for
Drupal projects, including Prettier, so formatting
problems show up in the same check as any other mistake.
This page is for developers who write JavaScript in custom modules or the custom theme. It shows how to run ESLint and fix what it reports, what the shipped configuration enforces and how to ignore a rule. For problems with ESLint itself, see the ESLint repository.
In Vortex, the root lint-js script lints only the custom modules in
web/modules/custom. The custom theme has its own eslint.config.mjs and
package.json, so it can move to a separate repository with its tooling.
ahoy lint-fe lints both.
Usage
Check for violations
To check the code, run 1 of the following commands:
- Ahoy
- Docker Compose
# Lint all front-end code: Twig, JavaScript and CSS.
ahoy lint-fe
# Lint only JavaScript in custom modules.
ahoy cli "npm run lint-js"
docker compose exec cli npm run lint-js
Confirm that the check passed: ESLint lists no problems, and echo $?
prints 0 right after the command.
Fix violations
ESLint's --fix flag repairs many violations automatically. The Prettier
integration reformats the code in the same pass. To apply both, run 1 of the
following commands:
- Ahoy
- Docker Compose
# Fix all front-end lint issues.
ahoy lint-fe-fix
# Fix only JavaScript issues in custom modules.
ahoy cli "npm run lint-fix-js"
docker compose exec cli npm run lint-fix-js
Configuration
See the ESLint configuration reference.
All global configuration lives in the
eslint.config.mjs
file.
By default, ESLint checks against the following presets:
@eslint/jsrecommended - the rules ESLint itself shipseslint-plugin-import-xrecommended - import correctness: modules that resolve, names the module exports and no duplicate importseslint-plugin-jsdocrecommended - documentation block correctnesseslint-plugin-prettierrecommended - Prettier integration for code formattingeslint-plugin-ymlrecommended - linting for the YAML files in custom moduleseslint-plugin-no-jquery- the full rule set, discouraging jQuery where the browser has an equivalent
The configuration mirrors the one Drupal core uses for its own JavaScript, so the 2 configurations stay comparable as core changes.
The import rules come from eslint-plugin-import-x, a fork of the
eslint-plugin-import plugin that Drupal core uses. Their names start with
import-x/, for example import-x/no-mutable-exports.
On top of the presets, the configuration sets the following rules:
| Rule | Level | Effect |
|---|---|---|
prettier/prettier | Error | Reports every difference from the Prettier formatting. |
no-console | Error | Reports console calls, because they print to every visitor's browser. |
operator-linebreak | Error | Requires an operator other than ? or : at the end of a wrapped line. |
yml/indent | Error | Requires a 2-space indent in YAML files. |
max-nested-callbacks | Warning | Reports callbacks nested more than 3 levels deep. |
no-unused-vars | Warning | Reports unused variables. |
no-plusplus | Warning | Reports ++ and --, except in the final expression of a for loop. |
import-x/no-mutable-exports | Warning | Reports exports declared with let or var. |
jsdoc/tag-lines | Warning | Requires 1 blank line between a block's description and its first tag. |
jsdoc/require-jsdoc | Off | Stops requiring a documentation block on every function. |
consistent-return | Off | Not checked. |
no-param-reassign | Off | Not checked. |
no-prototype-builtins | Off | Not checked. |
no-underscore-dangle | Off | Not checked. |
no-unexpected-multiline | Off | Not checked. |
The jsdoc settings also prefer @return to @returns and @prop to
@property, matching the tag names Drupal uses.
The configuration parses code as ECMAScript 2020. It declares the browser and Node.js globals, plus read-only globals for Drupal and the libraries it loads:
{
languageOptions: {
ecmaVersion: 2020,
globals: {
...globals.browser,
...globals.node,
Drupal: 'readonly',
drupalSettings: 'readonly',
drupalTranslations: 'readonly',
jQuery: 'readonly',
_: 'readonly',
Cookies: 'readonly',
Backbone: 'readonly',
htmx: 'readonly',
loadjs: 'readonly',
Shepherd: 'readonly',
Sortable: 'readonly',
once: 'readonly',
CKEditor5: 'readonly',
CKEDITOR: 'readonly',
tabbable: 'readonly',
transliterate: 'readonly',
bodyScrollLock: 'readonly',
FloatingUIDOM: 'readonly',
},
},
}
Files ending in .test.js also get the Jest globals and skip the
jsdoc/check-tag-names rule. Their callbacks can nest 5 levels deep
instead of 3.
The --max-warnings=0 flag in the root lint-js script makes warnings fail
the check too:
{
"scripts": {
"lint-js": "eslint web/modules/custom --max-warnings=0 --no-error-on-unmatched-pattern"
}
}
To add or remove targets, change the paths in the lint-js and
lint-fix-js scripts in the root package.json. The following lint-js
script also lints web/sites/default:
{
"scripts": {
"lint-js": "eslint web/modules/custom web/sites/default --max-warnings=0 --no-error-on-unmatched-pattern"
}
}
The custom theme runs ESLint from its own package.json and
eslint.config.mjs. Its presets, rules and globals match the root
configuration. The theme setup differs only in the following 3 ways:
- The theme configuration ignores only
node_modules,buildand minified files. - The theme configuration has no
.test.jsoverride. - The theme's
lint-jsscript checksjs/**/*.jswithout the minified files and omits--max-warnings=0, so warnings don't fail the theme check.
Prettier integration
eslint-plugin-prettier runs Prettier inside ESLint, so formatting problems
fail the same check as any other violation.
Besides the widths in Line width, the root .prettierrc.json
sets the following options:
singleQuoteset totrue, so strings use single quotessemiset totrue, so statements end with a semicolontrailingCommaset toall, so a list that wraps across lines ends with a comma wherever the syntax allows itpluginsset to@homer0/prettier-plugin-jsdoc, which formats documentation blocksjsdocReplaceTagsSynonymsset tofalse, so the plugin keeps a tag synonym such as@argas writtenjsdocUseTypeScriptTypesCasingset tofalse, so the plugin keeps the casing of basic types such asStringandObjectas written
Line width
Vortex sets printWidth to 160 in
.prettierrc.json,
up from the Prettier default of 80, so a statement stays on 1 line until
it's longer than 160 characters. The jsdocPrintWidth option of the
@homer0/prettier-plugin-jsdoc plugin holds documentation blocks to 80
columns, the comment width of the Drupal PHP coding standards.
CSS has its own override: a printWidth of 10000 and double quotes, so
Prettier never wraps a declaration. Prettier formats CSS only when something
runs it directly, such as an editor on save. Within Vortex,
Stylelint lints the stylesheets.
The custom theme ships a second .prettierrc.json with the same settings,
so the theme can move to a separate repository with its tooling. The root
and the theme each ship a .prettierignore that excludes node_modules
and *.yml files from Prettier.
Ignore violations
Global ignoring
To ignore paths globally, list them in the ignores entry of the
eslint.config.mjs
file. The shipped list skips dependencies, core and contributed code,
uploaded files, build output and minified scripts:
{
ignores: [
'node_modules/**/*',
'vendor/**/*',
'web/core/**/*',
'web/libraries/**/*',
'web/modules/contrib/**/*',
'web/profiles/contrib/**/*',
'web/themes/contrib/**/*',
'web/sites/*/files/**/*',
'**/build/**/*',
'**/*.min.js',
],
}
Inline ignoring
To ignore all ESLint rules in a file, add this comment to the file header:
/* eslint-disable */
To ignore specific rules in a file, add this comment to the file header:
/* eslint-disable no-console, no-unused-vars */
To ignore rules for a block of code, put it between a disable and an
enable comment:
/* eslint-disable no-console */
console.log('Debug information');
/* eslint-enable no-console */
To ignore rules for the next line only, add this comment on the line before it:
// eslint-disable-next-line no-console
console.log('Single debug statement');
To ignore rules for a single line, add this comment at the end of it:
console.log('Debug'); // eslint-disable-line no-console
Continuous integration
ESLint runs in the lint job of the continuous integration
pipeline and fails the build on
violations. CI lints the custom modules and the custom theme in separate
steps. It skips the theme step when
VORTEX_FRONTEND_BUILD_SKIP
is set to 1. On GitHub Actions, the theme step reads
VORTEX_FRONTEND_BUILD_SKIP from a repository variable and ignores .env.
Each step runs the lint script, which runs ESLint and then Stylelint and
stops at the first failure, so an ESLint failure stops Stylelint from
running in that step.
Ignore failures
To let the build pass anyway, set the VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE
CI variable to 1. ESLint still runs and prints its violations in the job
log. The variable is shared with Stylelint, which runs in
the same step.