Changes
- ## Introduction
- To ensure a consistent code style for JavaScript code, we use [ESLint](https://eslint.org/). The workflow is similar to [integrating rubocop](https://makandracards.com/makandra/400093-integrating-or-upgrading-makandra-rubocop) for Ruby code.
- ## 1. Adding the gem to an existing code base
- You can add the following lines to your `package.json` under `devDependencies`:
- +
- ```json
-"devDependencies": {- "eslint": "^8.7.0",- "eslint-config-standard": "^16.0.3",-"eslint-plugin-import": "^2.25.4",-"eslint-plugin-node": "^11.1.0",-"eslint-plugin-promise": "^6.0.0"- }-```-This should work for node 12+.-Then run `yarn install`.--Add an `.eslintrc.js` file, for example:-```js-module.exports = {- env: {-browser: true,-es2021: true,- +"devDependencies": {
- +"@eslint/js": "x",
- + "@stylistic/eslint-plugin": "x",
- +"eslint": "x",
- + "eslint-plugin-import": "x",
- + "globals": "x",
- +}
- +```
- +
- +Then run `yarn install` and add a `eslint.config.mjs` file:
- +
- +```js
- +import js from '@eslint/js'
- +import globals from 'globals'
- +import stylistic from '@stylistic/eslint-plugin'
- +import { defineConfig } from 'eslint/config'
- +
- +export default defineConfig([
- + {
- + files: ['**/*.{js,mjs,cjs}'],
- + plugins: {
- + js,
- + '@stylistic': stylistic,
- + },
- + extends: [
- + 'js/recommended',
- + ],
- + languageOptions: {
- + globals: {
- + ...globals.browser,
- + up: 'readonly',
- + process: 'readonly',
- + require: 'readonly',
- + },
- + },
- + rules: {
- + 'no-unused-vars': ['error', {
- + 'varsIgnorePattern': '^_',
- + 'argsIgnorePattern': '^_',
- + 'caughtErrorsIgnorePattern': '^_',
- + }],
- + '@stylistic/arrow-spacing': ['error', { before: true, after: true }],
- + '@stylistic/block-spacing': ['error', 'always'],
- + '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
- + '@stylistic/comma-dangle': ['error', {
- + 'arrays': 'always-multiline',
- + 'objects': 'always-multiline',
- + 'imports': 'always-multiline',
- + 'exports': 'always-multiline',
- + 'functions': 'always-multiline',
- + 'importAttributes': 'always-multiline',
- + 'dynamicImports': 'always-multiline',
- + 'enums': 'always-multiline',
- + 'generics': 'always-multiline',
- + 'tuples': 'always-multiline'
- + }],
- + '@stylistic/comma-spacing': ['error', { before: false, after: true }],
- + '@stylistic/comma-style': ['error', 'last'],
- + '@stylistic/computed-property-spacing': ['error', 'never', { enforceForClassMembers: true }],
- + '@stylistic/dot-location': ['error', 'property'],
- + '@stylistic/eol-last': 'error',
- + '@stylistic/generator-star-spacing': ['error', { before: true, after: true }],
- + '@stylistic/indent': ['error', 2, {
- + SwitchCase: 1,
- + VariableDeclarator: 1,
- + outerIIFEBody: 1,
- + MemberExpression: 1,
- + FunctionDeclaration: { parameters: 1, body: 1 },
- + FunctionExpression: { parameters: 1, body: 1 },
- + CallExpression: { arguments: 1 },
- + ArrayExpression: 1,
- + ObjectExpression: 1,
- + ImportDeclaration: 1,
- + flatTernaryExpressions: false,
- + ignoreComments: false,
- + ignoredNodes: [
- + 'TemplateLiteral *',
- + 'JSXElement',
- + 'JSXElement > *',
- + 'JSXAttribute',
- + 'JSXIdentifier',
- + 'JSXNamespacedName',
- + 'JSXMemberExpression',
- + 'JSXSpreadAttribute',
- + 'JSXExpressionContainer',
- + 'JSXOpeningElement',
- + 'JSXClosingElement',
- + 'JSXFragment',
- + 'JSXOpeningFragment',
- + 'JSXClosingFragment',
- + 'JSXText',
- + 'JSXEmptyExpression',
- + 'JSXSpreadChild'
- + ],
- + offsetTernaryExpressions: true
- + }],
- + '@stylistic/key-spacing': ['error', { beforeColon: false, afterColon: true }],
- + '@stylistic/keyword-spacing': ['error', { before: true, after: true }],
- + '@stylistic/lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }],
- + '@stylistic/multiline-ternary': ['error', 'always-multiline'],
- + '@stylistic/new-parens': 'error',
- + '@stylistic/no-extra-parens': ['error', 'functions'],
- + '@stylistic/no-floating-decimal': 'error',
- + '@stylistic/no-mixed-operators': ['error', {
- + groups: [
- + ['==', '!=', '===', '!==', '>', '>=', '<', '<='],
- + ['&&', '||'],
- + ['in', 'instanceof']
- + ],
- + allowSamePrecedence: true
- + }],
- + '@stylistic/no-mixed-spaces-and-tabs': 'error',
- + '@stylistic/no-multi-spaces': 'error',
- + '@stylistic/no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }],
- + '@stylistic/no-tabs': 'error',
- + '@stylistic/no-trailing-spaces': 'error',
- + '@stylistic/no-whitespace-before-property': 'error',
- + '@stylistic/object-curly-newline': ['error', { multiline: true, consistent: true }],
- + '@stylistic/object-curly-spacing': ['error', 'always'],
- + '@stylistic/operator-linebreak': ['error', 'after', { overrides: { '?': 'before', ':': 'before', '|>': 'before' } }],
- + '@stylistic/quote-props': ['error', 'as-needed'],
- + '@stylistic/quotes': ['error', 'single', { avoidEscape: true, allowTemplateLiterals: 'never' }],
- + '@stylistic/rest-spread-spacing': ['error', 'never'],
- + '@stylistic/semi': ['error', 'never'],
- + '@stylistic/semi-spacing': ['error', { before: false, after: true }],
- + '@stylistic/space-before-blocks': ['error', 'always'],
- + '@stylistic/space-before-function-paren': ['error', {
- + 'anonymous': 'never',
- + 'named': 'never',
- + 'asyncArrow': 'always'
- + }],
- + '@stylistic/space-in-parens': ['error', 'never'],
- + '@stylistic/space-infix-ops': 'error',
- + '@stylistic/space-unary-ops': ['error', { words: true, nonwords: false }],
- + '@stylistic/spaced-comment': ['error', 'always', {
- + line: { markers: ['*package', '!', '/', ',', '='] },
- + block: { balanced: true, markers: ['*package', '!', ',', ':', '::', 'flow-include'], exceptions: ['*'] }
- + }],
- + '@stylistic/template-curly-spacing': ['error', 'never'],
- + '@stylistic/template-tag-spacing': ['error', 'never'],
- + '@stylistic/wrap-iife': ['error', 'any', { functionPrototypeMethods: true }],
- + '@stylistic/yield-star-spacing': ['error', 'both'],
- + },
- },
-parserOptions: {-ecmaVersion: 12,-sourceType: 'module',-- +{
- + files: ['app/webpack/specs/**/*.{js,mjs,cjs}'],
- +rules: {
- + 'no-undef': 'off',
- + },
- +
- },
- ignorePatterns: [-'/coverage/',-'/node_modules/',-'/tmp/',-...- ],-globals: {-up: 'readonly',-- +{
- +ignores: [
- + 'app/assets/builds/',
- + 'coverage/',
- + 'node_modules/',
- + 'public/assets/',
- + 'vendor/ruby',
- + 'vendor/npm',
- + 'vendor/asset-libs/',
- + 'tmp/',
- + ]
- +
- },
- extends: [- 'standard',-],-- +])
- +```
- +
- +
- +Notes:
- +
- +- Stylistic rulesfor ESLint are imported from the dedicated package [@stylistic/eslint-plugin](https://eslint.style/rules).
- +- In previous setups we used [eslint-config-standard](https://github.com/standard/eslint-config-standard) package. This package is not maintained anymore, therefore we extracted the [most important rules](https://github.com/standard/eslint-config-standard/blob/60d408b04723fb7cd743f6be7c5c01bb3f729139/src/index.ts) directly into the `eslint.config.mjs`.
- +- The [globals](https://github.com/sindresorhus/globals) package gives you a predefined list of global variables in a browser. Other global variables like `require` (from webpack) need to be added manually.
- +- The `ignores` settings differs on your project setup. See the templates below for each common setups.
- +
- +**webpack & propshaft**:
- +
- +```js
- +{
- + ignores: [
- + 'app/assets/builds/',
- + 'coverage/',
- + 'node_modules/',
- + 'public/assets/',
- + 'vendor/ruby',
- + 'vendor/npm',
- + 'vendor/asset-libs/',
- + 'tmp/',
- + ]
- +
- }
- ```
-This config is based on the [standardjs.com ruleset](https://github.com/standard/eslint-config-standard) and has been adjusted slightly.-> [NOTE]-> Things you define on `window` to make them accessible from anywhere like `up` or `_` might lead to `no-undef` errors. Add them to `globals` so they won't trigger a warning.-----Example `ignorePatterns` for a project with **webpack**:- +**webpack & precompiled_assets**:
- +
- +
- ```js
-ignorePatterns: [- '/coverage/',- '/node_modules/',- '/public/packs/',- '/public/packs-test/',- '/tmp/',-],- +{
- +ignores: [
- +'/coverage/',
- +'/node_modules/',
- +'/public/packs/',
- +'/public/packs-test/',
- +'/tmp/',
- + ]
- +}
- ```
-----Example `ignorePatterns` for a project with **esbuild**:- +**esbuild & precompiled_assets**:
- +
- +
- ```js
-ignorePatterns: [- '/app/assets/builds/',- '/coverage/',- '/node_modules/',- '/public/assets/',- '/public/assets-test/',- '/tmp/',-],- +{
- +ignores: [
- +'/app/assets/builds/',
- +'/coverage/',
- +'/node_modules/',
- +'/public/assets/',
- +'/public/assets-test/',
- +'/tmp/',
- + ]
- +}
- ```
- +
- ## 2. Report all existing offenses and exclude them initially
- +
- Add an RSpec test, which runs ESLint (also during a full RSpec test run), e.g. in `spec/eslint_spec.rb`:
- ```ruby
- require 'open3'
- RSpec.describe 'eslint' do
- it 'is not offended' do
- stdout, stderr, status = Open3.capture3('yarn', 'run', 'eslint', '.')
- failure_message = [stderr, stdout].reject(&:empty?).join("\n\n")
- expect(status.success?).to eq(true), failure_message
- end
- end
- ```
-Run the test or simply run `yarn run eslint .` from your console to check all files.-Now you should get a list of all existing offenses, for which you should disable the rules in your `.eslint.rc.js` at first:- +Run the test or simply run `yarn run eslint .` from your console to check all files.Now you should get a list of all existing offenses, for which you should disable the rules in your `.eslint.rc.js` at first:
- +
- +
- +
- ```js
- rules: {
- 'comma-dangle': 'off',- 'padded-blocks': 'off',- 'space-before-function-paren': 'off',- 'prefer-const': 'off',- + '@stylistic/comma-dangle': ['error', 'never'],
- + '@stylistic/space-before-function-paren': ['error', 'never'],
- ...
- },
- ```
- This is only a temporary measure. We fix each offense in the next step.
- ## 3. Fixing each offense per commit
- +
- Now you should fix each offense in an own commit.
- 1. Remove one of the disabled rules
- 2. Run your spec or `yarn run eslint .`
- 3. Fix all offenses, either manually or with the auto-correct `yarn run eslint . --fix`
- 4. Check if tests are still green and everything works as expected
- 5. Commit the changes with a message like `[#180343876] Fix ESLint offense for rule "comma-dangle"`
- To run a single file, use `yarn run eslint path/to/some_file.js`.
- ### 3.1 Disable and configure specific rules
- +
- You can use comments to [disable specific rules in your JavaScript code.](https://eslint.org/docs/user-guide/configuring/rules#disabling-rules).
- +
- Or you could use your own configuration for specific rules, like:
- +
- ```js
- rules: {
- 'padded-blocks': 'off',- 'space-before-function-paren': [-'error', {-anonymous: 'never',-named: 'never',-asyncArrow: 'always',- },-],- + '@stylistic/comma-dangle': ['error', 'never'],
- + '@stylistic/space-before-function-paren': ['error', {
- +'anonymous': 'never',
- +'named': 'never',
- +'asyncArrow': 'always'
- + }],
- },
- ```
- ### 3.2 Disable specific rules for a lot of files
-If you have a lot of files for which you want to disable a specific rule, e.g. Jasmine specs, you could use `overrides` in your `.eslintrc.js`:- +
- +If you have a lot of files for which you want to disable a specific rule, e.g. Jasmine specs, you could use `overrides` in your `eslint.config.mjs`:
- +
- +
- ```js
- overrides: [
- {
- files: ['app/webpack/backend/index.js', 'app/webpack/frontend/index.js'],
- rules: {
- 'import/first': 'off',- + 'import/first': ['error', 'never'],
- },
- },
- {
- files: ['app/webpack/specs/**/*.js'],
- rules: {
- 'no-undef': 'off',- + 'no-undef': ['error', 'never'],
- },
- },
- ],
- ```
- +
- With that you don't need to add a disabling comment to each file.
- ## 4. Finish the integration
- +
- In the end you have a long commit list, first for integrating ESLint and then for fixing each offense, e.g.:
- ```
- ...
- [#180343876] Fix ESLint offense for rule "camelcase"
- [#180343876] Fix ESLint offense for rule "space-before-blocks"
- [#180343876] Fix ESLint offense for rule "no-useless-return"
- [#180343876] Add ESLint without any offenses
- ```
Posted by Emanuel to makandra dev (2025-08-25 14:48)