[VSCode] Ruffの設定メモ

# はじめに

VSCode 用の [Ruff extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff) の設定

# 環境

```:vscode
Version: 1.84.2 (user setup)
Commit: 1a5daa3a0231a0fbba4f14db7ec463cf99d7768e
Date: 2023-11-09T10:51:52.184Z
Electron: 25.9.2
ElectronBuildId: 24603566
Chromium: 114.0.5735.289
Node.js: 18.15.0
V8: 11.4.183.29-electron.0
OS: Windows_NT x64 10.0.22631
```

```:code --list-extensions --show-versions
charliermarsh.ruff@2023.50.0
```

# インストール

<https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff>

# Extension の設定

`Ruff` に渡せるセッティングは、以下の通り。

- `lint.args`
- `path`
- `interpreter`
- `importStrategy`
- `lint.run`
- `enable`
- `organizeImports`
- `fixAll`
- `codeAction.fixViolation.enable`
- `codeAction.disableRuleComment.enable`
- `showNotification`

さらに、ReadMe に記載がないが

- `format.args`
- `trace.server`

も渡せる。

実際に VSCode へ渡すときは、`settings.json`に以下のように記載する。

```jsonc:settings.json
{
  "ruff.lint.args": [], // ruff checkに渡す追加のコマンドライン引数。設定ファイル(ruff.tomlなど)より優先される
  "ruff.path": [], // ruffの実行パス
  "ruff.interpreter": [], // pythonの実行パス
  "ruff.importStrategy": "fromEnvironment", // "fromEnvironment": 環境にruffがインストールされている場合は、そちらを使う。 "useBundled": Extension付属のruffを使う。
  "ruff.lint.run": "onType", // "onType": 入力度にruffを実行。 "onSave":保存時にruffを実行。
  "ruff.enable": true, // ruffを有効にするか否か。
  "ruff.organizeImports": true, // source.organizeImports を実行するのにruffを使用するか否か。
  "ruff.fixAll": true, // source.fixAll を実行するのにruffを使用するか否か。
  "ruff.codeAction.fixViolation": { // Quick Fix を表示するか否か。
    "enable": true
  },
  "ruff.codeAction.disableRuleComment": { // # nopa でQuick Fixを無視するか否か。
    "enable": true
  },
  "ruff.showNotifications": "always", // ruffの通知を表示するか否か。 "off", "onError", "onWarning", "always"
  "ruff.format.args": [], // ruff formatに渡す追加のコマンドライン引数。 設定ファイル(ruff.tomlなど)より優先される
  "ruff.trace.server": "off" // "off", "message", "verbose"
}
```

使い勝手を考えると以下の設定も関連するので記載する。

```jsonc:setting.json
{
  // global 設定
  "editor.formatOnSave": true, // 保存時にフォーマットするか。
  "editor.formatOnPaste": false, // 貼り付け時にフォーマットするか。
  "editor.formatOnType": false, // 入力時にフォーマットするか。
  "editor.formatOnSaveMode": "file", // "file": 常にファイル全体をフォーマット、 "modifications": 修正された所のみフォーマット。SourceControlが必要。 "modificationsIfAvailable": 修正された所のみフォーマットを試みる。SourceControlが無い場合、ファイル全体をフォーマット。
  // "editor.defaultFormatter": "charliermarsh.ruff" // デフォルトのformatterにruffを指定。
  "editor.codeActionsOnSave": {
    "source.fixAll": true, // (デフォルトに設定されているもので)fixAllを実行。
    // "source.fixAll.ruff": true, // ruffのfixAllを明示的に使用。
    "source.organizeImports": true, // (デフォルトに設定されているもので)organizeImportsを実行。
    // "source.organizeImports.ruff": true //ruffのorganizeImportsを明示的に使用。
  },

  // python file
  "[python]": {
    "editor.formatOnSave": true,
    "editor.formatOnPaste": false,
    "editor.formatOnType": false,
    "editor.formatOnSaveMode": "file",
    "editor.defaultFormatter": "charliermarsh.ruff", // 別のものを指定するとフォーマットは指定したものになる
    "editor.codeActionsOnSave": { // 保存時に実行する
      "source.fixAll": true, // (登録した)fixAllを実行。
      // "source.fixAll.ruff": true, // ruffのfixAllを明示的に使用。
      "source.organizeImports": true, // (登録した)organizeImportsを実行。
      // "source.organizeImports.ruff": true //ruffのorganizeImportsを明示的に使用。
    }
  },
}
```

これで VSCode から`Ruff`が呼ばれるようになる。

## Ruff の設定

`Ruff`のリントやフォーマットの挙動は、

1. CLI オプション(コマンドライン引数)
2. 設定ファイル

の順で設定できる。

CLI オプションは、VSCode を使用するとほとんど使わないと思うが、`settings.json`の

```jsonc:settings.json
{
  "ruff.lint.args": [],
  "ruff.format.args": []
}
```

で設定できる。

以下のように、`キーワード=値`のように渡さないといけない事に注意。

```jsonc:settings.json
{
 "ruff.lint.args": [
    "--config=<PATH>",
    "--ignore=['F401']"
  ],
}
```

## 設定ファイル

設定ファイルの名前は、

- `pyproject.toml`
- `ruff.toml`
- `.ruff.toml`

で指定する。

`pyproject.toml`と、`ruff.toml`・`.ruff.toml`は、テーブル名の記述が若干違うので注意。

toml ファイルの探索順は、以下の通り。

1. `--config=<Path>`で指定したファイル。
   `settings.json`の`ruff.lint.args`か`ruff.format.args`に指定する。
2. py ファイルと同じフォルダ
3. 親のフォルダ
4. プロジェクトルートフォルダ
5. `$config_dir\ruff`

グローバル設定を指定したい場合は、`$config_dir\ruff`に置く。

`$config_dir`がどこかと言うと以下のようになる。

| OS      | Path                                  | example                                       |
| ------- | ------------------------------------- | --------------------------------------------- |
| Linux   | `$XDG_CONFIG_HOME` or `$HOME/.config` | `/home/UserName/.config`                      |
| macOS   | `$HOME/Library/Application Support`   | `/Users/UserName/Library/Application Support` |
| Windows | `{FOLDERID_RoamingAppData}`           | `C:\Users\UserName\AppData\Roaming`           |

UserName は、実際のユーザ名に置き換える。
`{FOLDERID_RoamingAppData}`は、`$env:APPDATA`のようだ。

つまり、Windows の場合は、
`$env:APPDATA\ruff`(`C:\Users\UserName\AppData\Roaming\ruff`)
に置く

## デフォルト設定

`ruff.toml`のデフォルト設定は以下の通り。

```toml:ruff.toml
# 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",
]

# Same as Black.
line-length = 88
indent-width = 4

# Assume Python 3.8
target-version = "py38"

[lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`)  codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E4", "E7", "E9", "F"]
ignore = []

# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

[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"
```

詳しくは、

- [リントのルール](https://docs.astral.sh/ruff/rules/)
- [Ruff の設定](https://docs.astral.sh/ruff/settings/)

を参照。

## 1行の文字数とインデントの長さの設定

`ruff.toml`に、以下のように記載する事で指定出来る。

```toml:ruff.toml
line-length = 88
indent-width = 4
```


## リントのルール

設定ファイルでリントのルールを設定する場合、`select`で指定する。
追加で指定したい場合は、`extended-select`を使用する。
`extended-select`で指定したものが上書きされるのか、`select`に競合しない場合にのみ適用されるのか、どちらになるか具体的な記載が無いので分からない。
また、`ignore`で特定のルールを無視させる事も出来る。

```toml:ruff.toml
[lint]
select = []
extend-select =[]
ignore = []
```

指定できるルールとしては、以下のようなものがある。

- `E`: `pycodestyle`のルール
- `F`: `Pyflakes`のルール
- `UP`: `pyupgrade`のルール
- `B`: `flake8-bugbear`のルール
- `SIM`: `flake8-simplify`のルール
- `ANN`: `flake8-annotations`のルール
- `I`: `isort`のルール
- `ALL`: 全てのルールを適用する。ただし、競合するものは、無効になる。

また、`F401`のようにすると対象のルールのみを指定できる。

## AutoFix のルール

`fixable` に指定したルールは、`Ruff` に修正される。
`unfixable` に指定したルールは、修正されない。

```toml:ruff.toml
[lint]
fixable = []
extend-fixable =[]
unfixable = []
extend-unfixable=[]
```

`Ruff` のフォーマッターと競合するために、設定しない方が良いリンタールール。

- `tab-indentation` (`W191`)
- `indentation-with-invalid-multiple` (`E111`)
- `indentation-with-invalid-multiple-comment` (`E114`)
- `over-indented` (`E117`)
- `indent-with-spaces` (`D206`)
- `triple-single-quotes` (`D300`)
- `bad-quotes-inline-string` (`Q000`)
- `bad-quotes-multiline-string` (`Q001`)
- `bad-quotes-docstring` (`Q002`)
- `avoidable-escaped-quote` (`Q003`)
- `missing-trailing-comma` (`COM812`)
- `prohibited-trailing-comma` (`COM819`)
- `single-line-implicit-string-concatenation` (`ISC001`)
- `multi-line-implicit-string-concatenation` (`ISC002`)

同じく競合するために設定しない方が良い`isort`ルール

- `force-single-line`
- `force-wrap-aliases`
- `lines-after-imports`
- `lines-between-types`
- `split-on-trailing-comma`

# Python コメントでの Ruff の制御

## リントの制御

`# noqa`をインラインに入れると、その行では警告を出さない。
`# noqa: {code}`をインラインに入れると、その行では`{code}`に対して警告を出さない。
`# ruff: noqa`をファイルに記述すると、そのファイルでは警告を出さない。
`# ruff: noqa: {code}`をファイルに記述すると、そのファイルでは`{code}`に対して警告を出さない。
`RUF100`を lint に設定すると不必要な`# noqa`に対して警告を出してくれる。

```python:例
# Ignore F841.
x = 1  # noqa: F841

# Ignore E741 and F841.
i = 1  # noqa: E741, F841

# Ignore _all_ violations.
x = 1  # noqa
```

## フォーマットの制御

`# fmt: off`と`# fmt: on`で囲むことで文単位(statement level)でフォーマットを抑制できる。
式単位(expression level)では効かない

```python
# fmt: off
[
    '1',
    '2',
]
# fmt: on
```

preceding statement, case header, decorator, function definition, or class definition が対象の場合は、`# fmt: skip`を使う。

```
# fmt: skip comments suppress formatting for a preceding statement, case header, decorator, function definition, or class definition:
```

```python
if True:
    pass
elif False: # fmt: skip
    pass

@Test
@Test2 # fmt: skip
def test(): ...

a = [1, 2, 3, 4, 5] # fmt: skip

def test(a, b, c, d, e, f) -> int: # fmt: skip
    pass
```

# エラーメッセージの確認

`output` panelを開き、`OutputChannel`を`Ruff`に合わせると`Ruff`からのメッセージが表示される。
動作がおかしいと思ったらここを見る。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194349/0a90b14d-a913-68e0-2606-1b26672d5238.png)


# 追加されるコマンドと関連コマンド

- `Ruff: Fix all auto-fixable problems`

```json
{
  "key": "",
  "command": "ruff.executeAutofix"
}
```

- `Ruff: Fix all auto-fixable problems`

```json
{
  "key": "",
  "command": "ruff.executeAutofix"
}
```

- `Ruff: Format document`

```json
{
  "key": "",
  "command": "ruff.executeFormat"
}
```

- `Ruff: Format imports`

```json
{
  "key": "",
  "command": "ruff.executeOrganizeImports"
}
```

- `Ruff: Restart Server`

```json
{
  "key": "",
  "command": "ruff.restart"
}
```

- `Auto Fix...`

```json
{
  "key": "shift+alt+oem_period",
  "command": "editor.action.autoFix",
  "when": "textInputFocus && !editorReadonly && supportedCodeAction =~ /(\\s|^)quickfix\\b/"
}
```

- `Quick Fix...`

```json
{
  "key": "ctrl+oem_period",
  "command": "editor.action.quickFix",
  "when": "editorHasCodeActionsProvider && textInputFocus && !editorReadonly"
}
```

- `Fix All`

```json
{
  "key": "",
  "command": "editor.action.fixAll"
}
```

- `Format Document`

```json
{
  "key": "shift+alt+f",
  "command": "editor.action.formatDocument",
  "when": "editorHasDocumentFormattingProvider && editorTextFocus && !editorReadonly && !inCompositeEditor"
}
```

- `Organize Imports`

```json
{
  "key": "shift+alt+o",
  "command": "editor.action.organizeImports",
  "when": "textInputFocus && !editorReadonly && supportedCodeAction =~ /(\\s|^)source\\.organizeImports\\b/"
}
```

---

# 自分の設定

メモとして、とりあえず設定した内容を記載して置く。
`select = ["ALL"]`にして、`ignore`で調整するのが良さそう。

```toml:ruff.toml
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",
]

indent-width = 2
line-length = 120

target-version = "py311"

[lint]
ignore = ["D", "ANN", "ERA001", "T201", "FBT003", "G004", "PLR0913", "RUF100", "E741"]
select = ["ALL"]

# print (T201): printがあると警告を出す
# commented-out-code (ERA001)
# flake8-annotations (ANN): アノテーション全般
# pydocstyle (D): docstring 全般
# boolean-positional-value-in-call (FBT003)
# logging-f-string (G004)
# too-many-arguments (PLR0913)
# unused-noqa (RUF100)
# ambiguous-variable-name (E741)

fixable = ["ALL"]
unfixable = ["F401"]

dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

[format]
indent-style = "space"
quote-style = "double"

skip-magic-trailing-comma = false # list等の最後の要素の後のコンマを削除しない。

line-ending = "lf" # "auto" | "lf" | "cr-lf" | "native"
```