Emacs から Ruff を使う

2023年4月29日 2023年12月11日

この記事では Emacs から Ruff を使う方法を紹介します。

Ruff

Ruff とは

Ruff は Rust で書かれた高速な Python の Linter & Formatter です。Flake8 や isort を置き換えることができます。 Ruff の詳しい紹介は、検索していただくと素晴しい記事がたくさんありますので、そちらをご覧いただくのが良いかと思います。

以下では、インストールと設定を簡単に紹介します。

Ruff のインストール

Ruff のインストール方法は公式ドキュメントにあるように pipを始めとし、様々なインストール方法があります。 私は、以前ブログ書いたように、pipx で pip でインストールできるコマンド群を管理しているので、以下のようにインストールします。

pipx install ruff

# 公式ドキュメント通り、pip でインストールするなら
# pip install ruff

Ruff の設定

Ruff は500以上のlintルールをサポートしており、isort や Flake8 等のルールも Ruff で再実装されています。また、lintルールの中には自動修正可能なものもあります。また、Black と同等の Code fomatter としての利用できるようになっています。

公式ドキュメントによると、デフォルトでは、Flake8 の E および F ルールを有効しており、使い始めたばかりであれば、デフォルトのルールセットが最適とのことです。

私はどんなルールがあるのか知りたいので、基本的にはすべてのルールを有効化して、適用しないルールを都度追加するようにしています。

lintルールの設定ファイル

Ruff は、pyproject.toml、ruff.toml、または .ruff.toml ファイルで設定します。 設定ファイルが色々なディレクトリ階層にある場合は、Ruff を適用するPythonファイルに対して、ディレクトリ階層の中で「最も近い」設定ファイルが参照されます(pyproject.toml は Ruff の設定がなければ無視されます)。

ディレクトリ階層内に設定ファイルが見つからない場合、Ruff はデフォルトの設定を使用し、ユーザ固有の設定ファイルが${config_dir}/ruff内に存在する場合、そのファイルがデフォルト設定の代わりに使用されます。${config_dir}ここに記載のあるようにOSによって異なりますので、自身の OS に従って適切に配置します。

詳しい設定ファイルの参照ルールは公式ドキュメントのpyprojecttoml discoveryをご参照ください。

私は、どのプロジェクトでもユーザ固有の設定が適用されれば良いので、以下のように設定しています。

~/.config/ruff/ruff.toml(linuxの場合)
# cache-dir
cache-dir = "~/.cache/ruff"

# Exclude a variety of commonly ignored directories.
exclude = [
    ".bzr",
    ".direnv",
    ".eggs",
    ".git",
    ".git-rewrite",
    ".hg",
    ".mypy_cache",
    ".nox",
    ".pants.d",
    ".pytype",
    ".ruff_cache",
    ".svn",
    ".tox",
    ".venv",
    "__pypackages__",
    "_build",
    "buck-out",
    "build",
    "dist",
    "node_modules",
    "venv",
]

line-length = 300
indent-width = 4


[lint]
select = ["ALL"]
ignore = ["E501", "PGH", "DJ", "D", "T20", "ERA", "C90", "ANN101", "FA102", "RUF001", "PLR0915", "S101", "S311", "S104"]

fixable = ["I", "SIM118"]
unfixable = []

[format]
# Like Black, use double quotes for strings.
quote-style = "double"

# Like Black, indent with spaces, rather than tabs.
indent-style = "space"

# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false

# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"

これで Ruff の設定は完了です。以下のような形でターミナルから Ruff を実行できます。

# lintエラーの表示
ruff check test.py

# 自動修正可能なlintエラーの修正
ruff check --fix test.py

# コードフォーマットを適用
ruff format test.py

Emacs での Ruff の利用

このようにとても便利な Ruff ですが、もちろん Emacs からも使いたいです。

私が Ruff を使って、Emacs でやりたいことは以下の2つです。

  • 現在のバッファの Linter 及び Code formatter の結果をリアルタイムで表示
  • 現在のバッファに Codo formatter を自動的に適用
  • 現在のバッファのlintエラーを自動的に修正

では、一つずつやっていきましょう。

現在のバッファの Linter 及び Code formatter の結果をリアルタイムで表示

Ruff のlintエラーを Emacs でリアルタイムに表示するパッケージは いくつかありますが、私は Flymake を利用しているので、flymake-ruff を使います。

これにより知らない記法が Flymake で表示されるので、とても勉強になり便利です。

(use-package flymake-ruff
  :ensure t
  :hook (eglot-managed-mode-hook . (lambda ()
                                     (when (derived-mode-p 'python-mode 'python-ts-mode)
                                       (flymake-ruff-load))))
  :config
  (setq flymake-ruff--default-configs '("ruff.toml" ".ruff.toml")))

現在のバッファに Codo formatter を自動的に適用

emacs-reformatterというパッケージは、stdin から読み込み、stdout に書き出す reformatter をサポートし、remormat コマンドを定義するためのシンプルなインターフェースと、オプションで保存時に自動適用するためのマイナーモードを提供しています。

そこで、ruff formatコマンドを設定することで、Ruff の formatter を保存時に自動適用することができます。

(use-package reformatter
  :hook
  (python-ts-mode-hook . ruff-format-on-save-mode)
  :config
  (reformatter-define ruff-format
    :program "ruff"
    :args `("format" "--stdin-filename" ,buffer-file-name "-")))

現在のバッファのlintエラーを自動的に修正

import順の修正や使っていないライブラリのimport文削除等、自明なlintエラーは自動的に修正したいので、以下の関数を Emacs の設定ファイルに追記します。

(defun ruff-fix-buffer ()
  "Use ruff to fix lint violations in the current buffer."
  (interactive)
  (let* ((temporary-file-directory (if (buffer-file-name)
                                       (file-name-directory (buffer-file-name))
                                     temporary-file-directory))
         (temporary-file-name-suffix (format "--%s" (if (buffer-file-name)
                                                                 (file-name-nondirectory (buffer-file-name))
                                                                "")))
         (temp-file (make-temp-file "temp-ruff-" nil temporary-file-name-suffix))
         (current-point (point)))
    (write-region (point-min) (point-max) temp-file nil)
    (shell-command-to-string (format "ruff check --fix %s" temp-file))
    (erase-buffer)
    (insert-file-contents temp-file)
    (delete-file temp-file)
    (goto-char current-point)))

このruff-fix-buffer関数を呼び出すことで、現在のバッファのlintエラーが修正されます。

また、以下の関数を定義して、before-save-hook に追加することで、バッファを保存する前に自動的にlintエラーを修正できるようにします。 なお、この関数は、major-mode が python-mode または python-ts-mode の場合にのみ実行できます。

(defun ruff-fix-before-save ()
  (interactive)
  (when (memq major-mode '(python-mode python-ts-mode))
    (ruff-fix-buffer)))

(add-hook 'before-save-hook 'ruff-fix-before-save)

上記の関数群は、ruff-fix.el として、パッケージにまとめているので、よろしければ使ってみてください。

終わりに

ここまで、読んでいただき、ありがとうございます。 これで Emacs から Ruff を使う環境を構築できました。

これから Ruff を Emacs で積極的に使っていきたいと思います。

Posted by mako
関連記事
コメント
...