25

A lot of projects I work on install eslint as a dev dependency, with a custom set of eslint plugins. Right now, flycheck uses the globally installed version of eslint rather than the version of eslint installed with each project.

I would like to make flycheck point to node_modules/eslint/bin/eslint.js instead. The full path should depend on the path of the current buffer's file so I can be working on multiple projects at the same time with each buffer using a potentially different eslint executable.

Is this possible? Any suggestions for how I would go about doing this?

pcardune
  • 353
  • 3
  • 6

6 Answers6

35

You can programmatically change flycheck-javascript-eslint-executable, e.g.

(defun my/use-eslint-from-node-modules ()
  (let* ((root (locate-dominating-file
                (or (buffer-file-name) default-directory)
                "node_modules"))
         (eslint
          (and root
               (expand-file-name "node_modules/.bin/eslint"
                                 root))))
    (when (and eslint (file-executable-p eslint))
      (setq-local flycheck-javascript-eslint-executable eslint))))

(add-hook 'flycheck-mode-hook #'my/use-eslint-from-node-modules)

This code looks for a node_modules directory in any parent of the buffer's directory and configures Flycheck to use an eslint executable from that directory if any exists.

bignose
  • 627
  • 3
  • 15
  • I wish my emacs-fu were this good. Thanks for the answer! – pcardune Apr 04 '16 at 16:35
  • 2
    If you have global-flycheck-mode set, then I recommend `(and eslint (file-executable-p eslint))` I was encountering errors otherwise trying to, i.e. edit my .emacs file – Jason Dufair Jun 14 '16 at 15:31
  • Here's what you can do if you need an inline hook (and avoid adding custom functions) : http://emacs-fu.blogspot.in/2008/12/hooks.html – Shivek Khurana Dec 06 '17 at 16:12
  • 1
    security note: only open files in trusted folders/projects; you will auto-execute any malicions eslint binary – maxy May 14 '18 at 09:21
  • 3
    I would use the path `"node_modules/.bin/eslint"`, just in case eslint ever changes their directory structure. – Cody Reichert Jun 25 '18 at 03:01
4

I don't have enough reputation to comment on the accepted solution. But I made a variation of it, that works well with nested packages (for example using lerna). This is the exact same code, however it looks up the parent node_modules folder recursively until it finds an eslint binary, and uses it. This way, you can have a lerna project have only one eslint configuration, shared between all sub-packages.

(defun my/use-eslint-from-node-modules ()
  (let ((root (locate-dominating-file
               (or (buffer-file-name) default-directory)
               (lambda (dir)
                 (let ((eslint (expand-file-name "node_modules/eslint/bin/eslint.js" dir)))
                  (and eslint (file-executable-p eslint)))))))
    (when root
      (let ((eslint (expand-file-name "node_modules/eslint/bin/eslint.js" root)))
        (setq-local flycheck-javascript-eslint-executable eslint)))))
(add-hook 'flycheck-mode-hook #'my/use-eslint-from-node-modules)
Soreine
  • 41
  • 1
0

I'm on windows and this wasn't working for me, I think the file-executable-p or perhaps the flycheck internals were failing to locate the correct file. As when its working it is pointing to the .cmd file and not the .js file.

I found a comment in flycheck-eslint does not use version of eslint installed in JavaScript project and their blog post to use directory variables instead.

In the root directory of the project create a file .dir-locals.el

((nil . ((eval . (progn
                   (add-to-list 'exec-path (concat (locate-dominating-file default-directory ".dir-locals.el") "node_modules/.bin/")))))))

Now when I visit the javascript file flycheck-verify-setup correctly finds the file <dir>/node_modules/.bin/eslint.cmd

Bae
  • 212
  • 1
  • 10
0

My previous solution was a pain to get working across different projects.

I've modified the accepted answer to hard code windows stupidities.

(defun my/use-eslint-from-node-modules ()
  (let* ((root (locate-dominating-file
                (or (buffer-file-name) default-directory)
                "node_modules"))
         (eslint (and root
                      (expand-file-name "node_modules/.bin/eslint.cmd"
                                        root))))
    (when (and eslint (file-exists-p eslint))
      (setq-local flycheck-javascript-eslint-executable eslint))))
Bae
  • 212
  • 1
  • 10
0

Here's a solution that combines Bae's and lunaryorn's answers using a dir-locals.el file in the root directory of the project (or, more specifically, in the directory that contains the node_modules/ folder where the eslint binary lives). Create said dir-locals.el file with the following content.

((js2-mode
   (flycheck-checker . javascript-eslint)
   (eval . (setq-local flycheck-javascript-eslint-executable
             (concat (locate-dominating-file default-directory
                       ".dir-locals.el") "node_modules/.bin/eslint")))))

Here I'm assuming you are using the js2-mode emacs major mode to edit javascript files. If that's not the case, change that line accordingly. The second line of the file makes the javascript-eslint syntax checker the only one to be used within this project. The third line sets the variable flycheck-javascript-eslint-executable to <root-dir>/node_modules/.bin/eslint. In this way we don't modify emacs' exec-path.

Ricardo
  • 131
  • 3
0

I've tried a few different variations of this and never found exactly what I wanted. I've finally leveled up enough in Emacs lisp to modify it to my liking. What my version does is look for a local eslint and if one is not found it falls back to a globally installed one:

(defun configure-web-mode-flycheck-checkers ()
    ;; See if there is a node_modules directory
    (let* ((root (locate-dominating-file
                  (or (buffer-file-name) default-directory)
                  "node_modules"))
           (eslint (or (and root
                            ;; Try the locally installed eslint
                            (expand-file-name "node_modules/eslint/bin/eslint.js" root))

                       ;; Try the global installed eslint
                       (concat (string-trim (shell-command-to-string "npm config get prefix")) "/bin/eslint"))))

      (when (and eslint (file-executable-p eslint))
        (setq-local flycheck-javascript-eslint-executable eslint)))

    (if eslint
        (flycheck-select-checker 'javascript-eslint)))
Ricky Nelson
  • 121
  • 4