flycheck icon indicating copy to clipboard operation
flycheck copied to clipboard

Enable npx with eslint

Open dmitry-saritasa opened this issue 6 years ago • 21 comments

This is portion of my ~/.spacemacs

(syntax-checking :variables
            flycheck-javascript-eslint-executable "/home/dmitry/code/admin-vue/node_modules/.bin/eslint"
            flycheck-select-checker 'javascript-eslint)

And it works properly, however I want to replace it with local npx call, and If I do so, I get javascript-eslint immediately disabled.

(syntax-checking :variables
            flycheck-javascript-eslint-executable "npx eslint"
            flycheck-select-checker 'javascript-eslint)

emacs version: 25.3.1

flycheck version: 20180311.1014

flycheck-verify-setup

Syntax checkers for buffer metric.js in js2-mode:

  javascript-eslint (disabled)
    - may enable:  Automatically disabled!
    - executable:  Not found
    - config file: missing or incorrect

  javascript-jshint
    - may enable:         yes
    - executable:         Found at /usr/bin/jshint
    - configuration file: Not found

  javascript-standard
    - may enable: Automatically disabled!
    - executable: Not found

Flycheck Mode is enabled. Use M-m u C-c ! x to enable disabled checkers.

--------------------

Flycheck version: 32snapshot (package: 20180311.1014)
Emacs version:    25.3.1
System:           x86_64-redhat-linux-gnu
Window system:    x

npx eslint --print-config . displays proper output, no errors.

dmitry-saritasa avatar Mar 11 '18 22:03 dmitry-saritasa

Thanks for the request; sorry for the delay.

The problem here is that flycheck starts looking for a binary called "npx eslint" (with a space in the name). The right way to do it would look like this:

(with-eval-after-load 'flycheck
  (push "npx" (flycheck-checker-get 'javascript-eslint 'command)))

(note that in that case you wouldn't want to set flycheck-javascript-eslint-executable separately).

Can you try this and report?

cpitclaudel avatar Mar 16 '18 03:03 cpitclaudel

Thanks @cpitclaudel, tried it, got this

Error (use-package): flycheck/:init: Symbol’s function definition is void: \(setf\ flycheck-checker-get\)

dmitry-saritasa avatar Mar 17 '18 02:03 dmitry-saritasa

Ouch, sorry. It's the usual with-eval-after-load issue. Please try this:

(with-eval-after-load 'flycheck
  (eval '(push "npx" (flycheck-checker-get 'javascript-eslint 'command))))

cpitclaudel avatar Mar 17 '18 05:03 cpitclaudel

After this change I got no errors, and in flycheck-verify-setup I got this

  javascript-eslint (disabled)
    - may enable:  Automatically disabled!
    - executable:  Found at /usr/bin/npx
    - config file: missing or incorrect

dmitry-saritasa avatar Mar 17 '18 20:03 dmitry-saritasa

Ah, I know what the issue is. The config file check in flycheck-eslint-config-exists-p will just call npx --print-config now, instead of npx eslint --print-config.

For things to work, I think you'd need to also adjust flycheck-eslint-config-exists-p to call npx eslint. Let me know if you need help with that.

cpitclaudel avatar Mar 17 '18 21:03 cpitclaudel

This is much easier to do that I described above, thanks to a feature that I had entirely forgotten about:

flycheck-command-wrapper-function is a variable defined in ‘flycheck.el’.
Its value is ‘identity’

  This variable may be risky if used as a file-local variable.

Documentation:
Function to modify checker commands before execution.

The value of this option is a function which is given a list
containing the full command of a syntax checker after
substitution through ‘flycheck-substitute-argument’ but before
execution.  The function may return a new command for Flycheck to
execute.

The default value is ‘identity’ which does not change the
command.  You may provide your own function to run Flycheck
commands through ‘bundle exec’, ‘nix-shell’ or similar wrappers.

Which means that the following is enough:

(defun my-flycheck-npx-wrapper (command)
  (cons "npx" command))

(defun my-js-mode-hook ()
  (setq-local flycheck-command-wrapper-function #'my-flycheck-npx-wrapper))

(add-hook 'js-mode-hook #'my-js-mode-hook)

Sorry for the extended brainfart.

cpitclaudel avatar Mar 19 '18 03:03 cpitclaudel

Hi @cpitclaudel thank you! Will try tomorrow and report, appreciate the help! L)

dmitry-saritasa avatar Mar 20 '18 05:03 dmitry-saritasa

@cpitclaudel this is what I have got

Syntax checkers for buffer main.js in js2-mode:

  javascript-eslint (disabled)
    - may enable:  Automatically disabled!
    - executable:  Found at /usr/bin/eslint
    - config file: missing or incorrect
(spacemacs|use-package-add-hook js2-mode
  :post-init
  (setq-local flycheck-command-wrapper-function #'my-flycheck-npx-wrapper)
  (setq flycheck-select-checker 'javascript-eslint))

I even tried your code 1:1 (except for js2-mode) and got the same flycheck verify setup output

Some debugging info:

(print flycheck-command-wrapper-function) <ctrl-j>
> identity
(print flycheck-select-checker) <ctrl-j>
> javascript-eslint

I then tried this

(spacemacs|use-package-add-hook js2-mode
  :post-init
  (setq flycheck-command-wrapper-function
      (lambda (command)
        (append '("npx") command)))
  (setq flycheck-select-checker 'javascript-eslint))

and after that print flycheck-command-wrapper-function gives that lambda command, but output for flycheck verify setup is still the same.

Maybe the wrapper function is not invoked if eslint is found globally?

Any ideas?

Maybe the solution is to look around flycheck-executable-find?

dmitry-saritasa avatar Mar 23 '18 18:03 dmitry-saritasa

Sorry, I don't know anything about spacemacs :/ Can you confirm that the hook thing that I showed above sets the wrapper properly?

(Btw, that flycheck-select-checker looks suspicious: that variable doesn't exist AFAIK)

cpitclaudel avatar Mar 23 '18 23:03 cpitclaudel

I'm also dealing with frustration around this.

The wrapper suggested wraps the command in npx, but I still need the eslint executable somewhere in exec-path to begin with to enable the checker.

If I add node_modules/.bin to my exec-path then the checker is enabled, but then I also don't really need the npx wrapper - the real checker works fine even though flycheck-compile fails (it's in exec-path but not $PATH and flycheck-compile shells out).

joewreschnig avatar Mar 31 '18 20:03 joewreschnig

The wrapper suggested wraps the command in npx, but I still need the eslint executable somewhere in exec-path to begin with to enable the checker.

Ah, right, because the :enabled check doesn't take the npx wrapping into account. Snap.

The only way we have fore this is to customize flycheck-executable-find as well. What does npx report is eslint is missing?

We could also add a Flycheck setting to use npx.

(it's in exec-path but not $PATH and flycheck-compile shells out).

Yuck yuck. Can you open a separate issue about this?

cpitclaudel avatar May 02 '18 23:05 cpitclaudel

@cpitclaudel

I'd love to help with this. Is there anything specific I can do to help move this forward?

scotttrinh avatar May 11 '18 15:05 scotttrinh

What does npx report is eslint is missing?

$ npx --version
9.7.1
$ npx --no-install eslint ; echo $?
not found: eslint
127
$ npx --no-install --quiet eslint ; echo $?
127

(Without --no-install, it installs the package for the duration of the command, which is probably never what Flycheck users want.)

Yarn has a similar script wrapper:

$ yarn --version
1.6.0
$ yarn --no-color --offline --silent run eslint ; echo $?
error Command "eslint" not found. Did you mean "tslint"?                                                                                                                       
1
$ yarn --no-color --offline --silent run asdf ; echo $?
error Command "asdf" not found.                                                                                                                                                
1

joewreschnig avatar May 12 '18 09:05 joewreschnig

npx --no-install --quiet eslint ; echo $?

Thanks, this is useful. Are there other commonly used wrappers besides yarn and npx? If not, the best might be to add support for these two, specifically.

cpitclaudel avatar Jun 09 '18 12:06 cpitclaudel

yeah those two are the most commonly used, I think it is enough to add support only for them. Btw has been any progress on this issue?

danielpza avatar Feb 24 '20 01:02 danielpza

As it is some variation of what other people are doing I use this and it has served me well for all sorts of npm/yarn based projects.

  (defun flycheck-node_modules-executable-find (executable)
      (or
       (let* ((base (locate-dominating-file buffer-file-name "node_modules"))
              (cmd  (if base (expand-file-name (concat "node_modules/.bin/" executable)  base))))
         (if (and cmd (file-exists-p cmd))
             cmd))
       (flycheck-default-executable-find executable)))

    (defun my-node_modules-flycheck-hook ()
      (setq-local flycheck-executable-find #'flycheck-node_modules-executable-find))

    (add-hook 'js2-mode-hook 'my-node_modules-flycheck-hook)
    (add-hook 'js-mode-hook 'my-node_modules-flycheck-hook)
    (add-hook 'web-mode-hook 'my-node_modules-flycheck-hook)
...

thomasf avatar Feb 26 '20 09:02 thomasf

I guess that at least in theory npx is better because it uses npm's knowledge about where paths are instead of explicitly stating them in elisp, what I wonder is if yarn in some cases might know something that npm doesnt'?

This is after all javascript/node ecosystem where every other god darn tool has it's own idea about how to do the same thing.

thomasf avatar Feb 26 '20 09:02 thomasf

What kind of environments do people use for javascript development in Emacs these days ? What variables do the corresponding packages set? If someone wants to write the code, we can definitely add a setting in Flycheck to toggle between npx, yarn, and a plain call to eslint. We'd just have a customizable variable whose setter changes the :command of the eslint checker.

cpitclaudel avatar Mar 20 '20 19:03 cpitclaudel

Thinking more about this, a part of the problem is that we run the command through flycheck-command-wrapper-function after checking whether the executable exists. That variable was introduced by @svenkeidel a long long time ago for the nix case; @svenkeidel , how do things work with nix? Isn't it a problem that we check whether command xyz exists before invoking the command wrapper?

In the meantime, we can either add separate code that sets exec-path, bypassing npx, or add support for npx in a separate variable.

cpitclaudel avatar Mar 20 '20 20:03 cpitclaudel

One solution is to install eslint_d globally and set flycheck-javascript-eslint-executable to "eslint_d" instead of "eslint". eslint_d is a daemon for ESLint that uses the version of ESLint that's installed in the current working directory's node_modules.

scalisi avatar Mar 12 '21 00:03 scalisi

As a workaround, I am simply providing in ~/.local/bin a wrapper for eslint:

#!/bin/sh

exec yarn --offline --silent run eslint "$@"

vincentbernat avatar Apr 08 '22 20:04 vincentbernat