RuboCop と共存してくれそうなフォーマッターを探す旅

# はじめに

RuboCop はオートフォーマットが貧弱なので、JavaScript で言う所の ESLint + Prettier みたいに RuboCop と何かフォーマッターを組み合わせて運用したくなりました。
で、探してみたところ Rufo と Prettier が使えそうだということで、それについて以下に記述します。

ちなみに、[平成.rb #6](https://heiseirb.connpass.com/event/138210/)にてこの内容の発表した時のスライドは[こちら](https://slides.com/hey_cube/formatter_with_rubocop)です。

# そもそもフォーマッター要る？

> では、そういう問題がありながら、そもそもなぜ、コーディングに規約を設けようなどと思うのか。その目的の1つは「誰も、自分の書いたコードを「私物化」できないようにする」ということです。みんなが、自分の担当部分のコードを好き勝手な形式で書くと、どうしてもその箇所はその人のコードになってしまいます。逆に、その箇所も均一な形式で書いてあれば、私物化という事態にはなりにくいでしょう。その他、開発者がアンチパターンを使ってしまうことでしょう実（原文ママ）、ありがちなバグを防ぐという目的もあります。コーディング規約に従うことで、プロジェクトの全体としての進行を円滑にし、開発速度を一定に保ちやすくします。ただし重要なのは、プロジェクトに参加する全員が同じ規約に従わなくてはならないということです。―他の開発者が皆4タブのインデントを使用しているのに、1人だけ3タブのインデントを使ったのでは意味がありません。
>
> コーディング規約の遵守に役立つツールは多数存在します。そうしたツールには、規約をドキュメント化する機能、規約からの逸脱を報告する機能などがあります。ただし、そういうツールを使えば問題が全て解決するわけではありません。コーディング規約は可能な限り、自動的、かつ強制的に守られるようにすべきでしょう。

[コーディング規約を自動化する | プログラマが知るべき97のこと](https://xn--97-273ae6a4irb6e2hsoiozc2g4b8082p.com/%E3%82%A8%E3%83%83%E3%82%BB%E3%82%A4/%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84%E3%82%92%E8%87%AA%E5%8B%95%E5%8C%96%E3%81%99%E3%82%8B/)より引用

以上より、フォーマッターは必要だと（少なくとも僕は）考えます。
ただし、これはあくまで「チーム開発をする上では」必要という話であり、個人や極少人数での開発では不要という判断もあり得ると思います。
また、Ruby のコードフォーマットはかくあるべしといった言説を主張したいわけでもありません。

# RuboCop とは

> RuboCop is a Ruby static code analyzer and code formatter. Out of the box it will enforce many of the guidelines outlined in the community Ruby Style Guide.

[rubocop-hq/rubocop](https://github.com/rubocop-hq/rubocop)より引用

RuboCop は静的にコードを解析してルールに違反した書き方を指摘してくれるツールです。
世の Ruby プロダクトの多くは既に RuboCop を導入しているのではないでしょうか。

# Rufo vs Prettier

今僕がお仕事で書いているプロダクトに Rufo ないし Prettier を導入した様子を以下に記します。
なお、諸々の情報は 2019/07/04 現在のものです。

## 前提

- Ruby on Rails のプロダクトに導入する
  - rb ファイル数： 162
  - rb ファイル行数： 8088
  - RuboCop 0.66 が導入済み
- プロダクトメンバーの使っているエディターでフォーマットしたい
  - 具体的には VSCode と RubyMine
- `db/schema.rb` など一部のファイルはフォーマットしたくない

## Rufo

### Rufo とは

> Rufo is as an opinionated ruby formatter, intended to be used via the command line as a text-editor plugin, to autoformat files on save or on demand.
> 
> Unlike the best known Ruby formatter RuboCop, Rufo offers little in the way of configuration. Like other language formatters such as gofmt, prettier, and autopep8, we strive to find a "one true format" for Ruby code, and make sure your code adheres to it, with zero config where possible.
> 
> RuboCop does much more than just format code though, so feel free to run them both!

[ruby-formatter/rufo](https://github.com/ruby-formatter/rufo)より引用

Rufo は設定なしで *one true format* を提供してくれる Ruby 用フォーマッターです。
思想的に Prettier に近いことを自称しており、RuboCop との使い分けもユースケースとして想定しているようです。

### 導入方法

[rubocop-config-rufo](https://github.com/xinminlabs/rubocop-config-rufo)を使うことで RuboCop のルールを Rufo に合わせます。

```Gemfile
gem 'rubocop-config-rufo'
```

```.rubocop.yml
inherit_gem:
  rubocop-config-rufo: rubocop.yml
```

CircleCI 上でフォーマットされているかを確認したいので、 `.circleci/config.yml` に以下を追記します。

```.circleci/config.yml
- run:
    name: run rufo check
    command: bundle exec rufo --check app config db/seeds.rb db/seeds lib spec Gemfile Rakefile
```

`--check` 以降がやたらと長いですが、これは `db/schema.rb` をフォーマット対象から除外しようとしたらこうなりました。
これは煩雑なので、以下のように rake task を作っても良いかも知れません。

```rufo.rake
# frozen_string_literal: true

require "bundler/setup"
require "rufo"

namespace :rufo do
  desc "Rufo に違反していないか確認する"
  task :check do
    Rufo::Command.run(["--check", "app", "config", "db/seeds.rb", "db/seeds", "lib", "spec", "Gemfile", "Rakefile"])
  end

  desc "Rufo に従って整形する"
  task :format do
    Rufo::Command.run(["app", "config", "db/seeds.rb", "db/seeds", "lib", "spec", "Gemfile", "Rakefile"])
  end
end
```

#### エディターの設定

大体のエディターには導入できそうな気配があります（VSCode 以外は未検証）。
https://github.com/ruby-formatter/rufo#editor-support
https://medium.com/@sugumura/rufo-formated-on-rubymine-2024c1e6ea3f

VSCode に導入する場合、設定ファイルに以下を追記すれば良さそうです。

```.vscode/extensions.json
{
  "recommendations": [
    "rebornix.ruby",
    "misogi.ruby-rubocop",
    "mbessey.vscode-rufo"
  ],
  "unwantedRecommendations": []
}
```

```.vscode/settings.json
{
  "ruby.lint": {
    "rubocop": true
  },
  "[ruby]": {
    "editor.defaultFormatter": "mbessey.vscode-rufo"
  }
}
```

### ルールの印象

- 文字列が全て `"` になるのは意見が分かれそう
- hash のけつカンマ強制も意見が分かれそう
- 一部のインデントの入り方がよくわからない
  - こういうの↓

```ruby
  scope :of_month, ->(yearmonth) {
          of_period *DateParser.parse_yearmonth(yearmonth)
        }
```

### 導入した所感

#### 強み

- 自前でルールを書かなくて良い
- 早い
  - `bundle exec rufo app config db/seeds.rb db/seeds lib spec Gemfile Rakefile  0.87s user 0.25s system 69% cpu 1.619 total`
- Gemfile や Rakefile も整形してくれる

#### 弱み

- rubocop-config-rufo の開発がそんなに活発じゃなさそう
  - スター数は2（うち1つは僕がさっき押した）
- オートフォーマットして欲しくないファイルがある場合はコマンドライン引数で頑張るしかない

## Prettier

### Prettier とは

> \# What is Prettier?
> 
> \* An opinionated code formatter
> \* Supports many languages
> \* Integrates with most editors
> \* Has few options

[Prettier・Opinionated Code Formatter](https://prettier.io/)より引用

Prettier はフロントエンド界隈で勢いのあるフォーマッターです。
コードの整形を自動で行うことで、コードフォーマットに対して割く労力を抑えることを目的の一つとしています。
Prettier 本体は Ruby 未対応ですが、有志による Ruby プラグインが作られています。

> @prettier/plugin-ruby is a prettier plugin for the Ruby programming language (versions 2.5 and above). prettier is an opinionated code formatter that supports multiple languages and integrates with most editors. The idea is to eliminate discussions of style in code review and allow developers to get back to thinking about code design instead.

[prettier/plugin-ruby](https://github.com/prettier/plugin-ruby)より引用

### 導入方法

[rubocop-config-prettier](https://github.com/xinminlabs/rubocop-config-prettier)を使うことで RuboCop のルールを Prettier に合わせます。

```Gemfile
gem 'rubocop-config-prettier'
```

```.rubocop.yml
inherit_gem:
  rubocop-config-prettier: config/rubocop.yml
```

CircleCI 上でフォーマットされているかを確認したいので、 `.circleci/config.yml` に以下を追記します。

```.circleci/config.yml
      - run:
          name: run prettier
          command: bundle exec rbprettier --check '**/*.rb'
```

こちらは `.prettierignore` ファイルを用意することで特定のファイルをフォーマット対象から除外することができます。

```
db/schema.rb
vendor/**/*
```

#### エディターの設定

VSCode には導入できそうです（未検証）。
https://qiita.com/koshilife/items/c112c57673721e3d0015

RubyMine には Prettier 自体は入れられそうですが、ユーザープラグインも使えるかがちょっとわからないです。
もしかしたら使えないかも。
https://pleiades.io/help/ruby/prettier.html

### ルールの印象

- 内容のない class とかを1行にされるのは意見が分かれそう
- 後置 `if` / `unless` を許してくれないのは意見が分かれそう
- 一部のインデントの入り方がよくわからない
  - Rufo と同じ？か？
- Hash や block を結構複数行にしたがるっぽい

### 導入した所感

#### 強み

- 自前でルールを書かなくて良い
- `.prettierignore` が使える
  - オートフォーマットして欲しくないファイルの除外が簡単

#### 弱み

- rubocop-config-prettier の開発がそんなに活発じゃなさそう
  - スター数は7（うち1つは僕がさっき押した）
- 遅い
  - `bundle exec rbprettier --write '**/*.rb'  34.48s user 7.43s system 97% cpu 42.908 total`
- .rb ファイル以外は（多分）フォーマットしてくれない
- RuboCop 0.68 以降じゃないと動かない
  - RuboCop は破壊的変更がしょっちゅうある gem なのであんまりアップデートしたくない

## 結論

比較検討した結果、プロダクトには Rufo を正式に導入しました。
トップレベルに新しいディレクトリを追加した時に rake task を編集する必要がありそうですが、そもそもトップレベルに新しいディレクトリ作ることなんてことはそうそうないと思われます。
rubocop-config-rufo の将来性も気になるところではありますが、最悪それを基に自前でルールを記述することも十分可能だと考えています。

今回比較してみて、Rufo / Prettier (plugin-ruby) 共にまだまだこれから、という印象を受けました。
まして rubocop-config-xxxxx に至ってはそもそも認知もあまりされていないようです。
リンターとフォーマッターを併用するというアイデア自体が新しいので、これから徐々に広まっていくものと思われます。

それでは、良き Ruby 開発ライフを！