Alexander Gromnitsky's Blog

ESLint's flat config and Emacs

Latest update:

I expected to use this "new" eslint as follows:

  1. have a user-wide eslint installation with a personal default configuration;
  2. given #1, to be able to run eslint in any project (mine or not) to check it against my personal rules;
  3. if a project has node_modules/.bin/eslint script (implying there is a config file in the project's root directory), running node_modules/.bin/eslint should ignore the global config and use the local one;
  4. Flycheck in Emacs should automatically execute either node_modules/.bin/eslint (if it exists) or the global version otherwise.

Naïvely installing eslint via 'npm i -g' won't do much good since any plugins must still be installed locally.

Hence, I created a directory ~/lib/dotfiles/eslint solely for a "global", user-wide installation & configuration:

|-- eslint*
|-- eslint.config.mjs
|-- node_modules/
|   |-- .bin/
|   |   `-- eslint -> ../eslint/bin/eslint.js*
|   `-- eslint/
`-- package.json

where eslint is a shell script, symlinked from a directory in PATH:

$ stat -c%N `which eslint`
'/home/alex/bin/eslint' -> '/home/alex/lib/dotfiles/eslint/eslint'
$ cat eslint
#!/bin/sh

__dir__=$(dirname "$(readlink -f "$0")")
"$__dir__"/node_modules/.bin/eslint -c "$__dir__"/eslint.config.mjs "$@"

and eslint.config.mjs has common rules for .js files and files without extensions:

import globals from 'globals'
import js from '@eslint/js'
import react from 'eslint-plugin-react'

export default [
  js.configs.recommended,
  {
    rules: {
      "no-unused-vars": [ "warn", { "argsIgnorePattern": "^_" } ],
      …
    },
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
        …
      }
    }
  },
  {
    files: ["**/!(*.*)"],
    ignores: ["**/{Makefile,Rakefile,Gemfile,LICENSE,README}"],
  },
  …
]

If you don't care about any custom configurations that arrive with almost any JS project, you may stop here. Otherwise, we need to instruct Emacs where to search for eslint executable.

(defun my--npm-exec-path()
  (let ((npm-root (string-trim
                   (shell-command-to-string "npm root 2>/dev/null"))))
    (when (not (string= "" npm-root))
      (make-local-variable 'exec-path)
      (add-to-list 'exec-path (file-name-concat npm-root ".bin"))
      )))
(add-hook 'js-mode-hook 'my--npm-exec-path)
(add-hook 'js-mode-hook 'flycheck-mode)

This tells the editor to invoke my--npm-exec-path function every time Emacs opens a .js file. The function runs npm root to guess the proper path for node_modules directory and prepends Emacs' internal exec-path list with the path where the eslint script might be installed. Iff such a script is found by Flycheck, our global ~/lib/dotfiles/eslint/eslint.config.mjs is ignored.


Tags: ойті
Authors: ag