動機
FlycheckでESLintを使っていると、グローバルインストールされた eslint コマンドではなく、各プロジェクトにインストールされた node_modules/.bin/eslint を使いたくなるときがあります。
実現
npm bin コマンドの出力を exec-path に追加することで実現します(npm bin コマンドは普通、そのプロジェクトの node_modules/.bin の絶対パスを出力します)。
下記のEmacs Lispコードを init.el に追加してください。
(defun my/use-node-modules-bin ()
(let* ((local-path (replace-regexp-in-string
"[\r\n]+$" "" (shell-command-to-string "npm bin"))))
(setq-local exec-path (cons local-path exec-path))))
(add-hook 'flycheck-mode-hook #'my/use-node-modules-bin)
別解(2017-10-25 追記)
add-node-modules-path という素晴らしいパッケージを見つけましたので、これを使いましょう。
exec-path をバッファローカルに変更してくれます。
これでもう flycheck-executable-find をカスタマイズする必要はありません。
別解(2017-07-21 追記)
-
npm binは Node.js プロセスを立ち上げるので遅い ⇒node_modules固定で Emacs Lisp で実装する -
exec-pathを更新するのではなく、Flycheck が提供するflycheck-executable-find変数を使う
(defun my/find-npm-command (command)
(let* ((dirname "node_modules")
(root (locate-dominating-file default-directory dirname)))
(if root (concat
(file-name-as-directory root)
(file-name-as-directory dirname)
(file-name-as-directory ".bin")
command))))
(defun my/executable-find (command)
(let* ((file-path (my/find-npm-command command)))
(if (and file-path (file-executable-p file-path))
file-path (executable-find command))))
(setq flycheck-executable-find #'my/executable-find)
最終解(2017-07-22 追記) ⇒最終解ではないです
これが一番シンプルな解(かも)。
このQiitaの記事にあるように、あらかじめ PATH を設定しておいて、
export PATH="./node_modules/.bin:${PATH}"
# Or
pathmunge ./node_modules/.bin
exec-path-from-shellを使って PATH を Emacs に読み込ませるようにすればよいです。
こうすることで、上記にあるような Flycheck への設定はすべて不要になります。
セキュリティ上問題あり(2017-07-24 追記)
…と書きましたが、
export PATH="./node_modules/.bin:${PATH}"
は、
export PATH="${PATH}:./node_modules/.bin"
とするのが、セキュリティ上正しいです。
理由は、仮に ./node_modules/.bin/touch というファイルが存在した場合、OSコマンドの touch をオーバーライドして悪意のあるコマンドを実行することができるからです。
(詳しくは
https://github.com/npm/npm/issues/957#issuecomment-237064313 )
ただ PATH の一番最後に ./node_modules/.bin を置いてしまうと、グローバルインストールした npm パッケージの方がローカルインストールしたパッケージより優先されてしまうという点が悩ましいです。 ![]()
やはり Flycheck に設定した方が、影響範囲が限定されるので安全かもしれません。