RuboCop のドキュメントに example を追加してみる

## 対象読者

- OSS に Pull-Request を投げてみたい人


## なにこれ

OSS にPull-Requestを送ってみたいけど、どうやったらいいのかわからなくて躊躇している人も多いのではないでしょうか。
この記事では、[RuboCop](https://github.com/bbatsov/rubocop)のドキュメントを更新するPull-Requestを作成する方法について解説します。

10月は[Hacktoverfest](https://hacktoberfest.digitalocean.com/)が開催されることもあり、Pull-Requestを送る絶好の機会です。
Pull-Request にチャンレジしてみてはいかがでしょうか。


## RuboCop?

RuboCop とは、Ruby の静的解析ツールです。
例えば、以下のようなスタイルの崩れたコードへ警告を出して自動的に修正したり、


```ruby
def foo
     bar ( baz)
  end
```


以下のような危険なコードに対して警告を出したりします。


```ruby
def foo
  bar
  return
  baz # 絶対に実行されない！
end
```



詳しくは、レポジトリのREADMEをご覧ください。

https://github.com/bbatsov/rubocop



## example?

では、タイトルの「ドキュメントにexampleを追加してみる」というのはどのようなことでしょうか？

RuboCop では、ひとつひとつのCop(検出する項目の単位)毎に、ドキュメントが書かれています。
ドキュメントは、GitHub レポジトリの`manual/`ディレクトリ下か、 http://rubocop.readthedocs.io/en/latest/ で見ることができます。今回は、リビジョンを指定することができるGitHubレポジトリの方を見ます。
例えば、先程の"絶対に到達できないコード"のドキュメントを見てみましょう。


> ## Lint/UnreachableCode
> 
> Enabled by default | Supports autocorrection
> --- | ---
> Enabled | No
> 
> This cop checks for unreachable code.
> The check are based on the presence of flow of control
> statement in non-final position in *begin*(implicit) blocks.
> 
> ### Example
> 
> ```ruby
> # bad
> 
> def some_method
>   return
>   do_something
> end
> 
> # bad
> 
> def some_method
>   if cond
>     return
>   else
>     return
>   end
>   do_something
> end
> ```
> ```ruby
> # good
> 
> def some_method
>   do_something
> end
> ```
>
> https://github.com/bbatsov/rubocop/blob/08877fe14c6a3af12a10691f38b63d1d9e70ecf2/manual/cops_lint.md#lintunreachablecode


このように、ドキュメントには

- デフォルトで enabled か、auto-correction をサポートしているか
- Cop の簡単な説明
- 良いコード、悪いコードの実例

が書かれています。


ですが、中にはコード例が書かれていないCopもあります。例えば、`Layout/SpaceInsideParens` cop はドキュメントに example が書かれていません。

> ## Layout/SpaceInsideParens
> 
> Enabled by default | Supports autocorrection
> --- | ---
> Enabled | Yes
> 
> Checks for spaces inside ordinary round parentheses.
> 
> ### References
> 
> * [https://github.com/bbatsov/ruby-style-guide#no-spaces-braces](https://github.com/bbatsov/ruby-style-guide#no-spaces-braces)
>
> https://github.com/bbatsov/rubocop/blob/08877fe14c6a3af12a10691f38b63d1d9e70ecf2/manual/cops_layout.md#layoutspaceinsideparens


example のないドキュメントは、パット見で何をするCopなのかがわからないため、とても不便です。
そのため、このようなドキュメントにexampleを追加していきたいと思っています。
この`Layout/SpaceInsideParens`を例に、どうやったらexampleを追加できるのかを解説します。


## ドキュメントはどこに書く？


実は先程の`manual/cops_layout.md`などは、自動生成されたファイルです。
そのためドキュメントを書く際には、`manual/`ディレクトリ下のファイルを直接編集するのではなく、オリジナルとなるファイルを編集する必要があります。

ドキュメントのオリジナルは、Copのソースコード内にコメントとして書かれています。
例えば、先程の`Lint/UnreachableCode`のソースコードは、`lib/rubocop/cop/lint/unreachable_code.rb`に存在します。


> ```ruby
> # frozen_string_literal: true
> 
> module RuboCop
>   module Cop
>     module Lint
>       # This cop checks for unreachable code.
>       # The check are based on the presence of flow of control
>       # statement in non-final position in *begin*(implicit) blocks.
>       #
>       # @example
>       #
>       #   # bad
>       #
>       #   def some_method
>       #     return
>       #     do_something
>       #   end
>       #
>       #   # bad
>       #
>       #   def some_method
>       #     if cond
>       #       return
>       #     else
>       #       return
>       #     end
>       #     do_something
>       #   end
>       #
>       # @example
>       #
>       #   # good
>       #
>       #   def some_method
>       #     do_something
>       #   end
>       class UnreachableCode < Cop
> ```
> 
> https://github.com/bbatsov/rubocop/blob/70bc212b7da7175628b04f65d68c30686911a60a/lib/rubocop/cop/lint/unreachable_code.rb#L1-L37


そして、ソースコードの中のコメントに、ドキュメントと同じものが書かれています。
ですので、example を追加する際はこのコードを編集していくことになります。


## example の追加の仕方


では、example が存在しなかった`Layout/SpaceInsideParens` Cop に example を追加してみましょう。
この Cop のコメントは、以下のようになっています。



> ```ruby
> # frozen_string_literal: true
> 
> module RuboCop
>   module Cop
>     module Layout
>       # Checks for spaces inside ordinary round parentheses.
>       class SpaceInsideParens < Cop
> ```
> 
> https://github.com/bbatsov/rubocop/blob/70bc212b7da7175628b04f65d68c30686911a60a/lib/rubocop/cop/layout/space_inside_parens.rb#L1-L7


コメントが1文しかありませんね。ここに example を追加しましょう。


### どうやって Cop の動きを知る？

example を書くためには、その Cop の動きを知る必要があります。
コメントに書いてある文面だけからは動きが推測しづらい場合もあると思います。
そのような場合は、その Cop のテストを見ると良いでしょう。

`Layout/SpaceInsideParens`のテストは、`spec/rubocop/cop/layout/space_inside_parens_spec.rb`に存在します。ソースコードと対応した名前になっているので、探しやすいと思います。
そのうちの一部を抜粋します。


> ```ruby
> describe RuboCop::Cop::Layout::SpaceInsideParens do
>   subject(:cop) { described_class.new }
> 
>   it 'registers an offense for spaces inside parens' do
>     expect_offense(<<-RUBY.strip_indent)
>       f( 3)
>         ^ Space inside parentheses detected.
>       g = (a + 3 )
>                 ^ Space inside parentheses detected.
>     RUBY
>   end
>   # ...
> end
> ```
> 
> https://github.com/bbatsov/rubocop/blob/08877fe14c6a3af12a10691f38b63d1d9e70ecf2/spec/rubocop/cop/layout/space_inside_parens_spec.rb#L1-L14


このようにテストを見ることで、どのようなコードに対して RuboCop が警告を出すのかを把握することが出来ます。
今回はこのテストケースをそのまま example として使用しましょう。

またテストだけでは物足りない場合は、実際に RuboCop を実行してみることをおすすめします。



### example の書式


example は、YARD の書式に沿って書きます。
`@example` というブロックの中に、Rubyのコード例を書いていきます。
またコード例には "bad" な例と "good" な例の両方を記述すると良いでしょう。

実際に書くと、こんな感じになるでしょう。

```ruby
module RuboCop
  module Cop
    module Layout
      # Checks for spaces inside ordinary round parentheses.
      #
      # @example
      #   # bad
      #   f( 3)
      #   g = (a + 3 )
      #
      #   # good
      #   f(3)
      #   g = (a + 3)
```

インデントの数に注意してください。`@example`は`#`から数えてスペースがひとつ、コードの部分はスペースが3つ必要です。


また、設定項目がある Cop の場合にはそれぞれの設定項目における動作例が書かれていると良いでしょう。
例えば、`Style/Alias`のドキュメントは以下のようになっています。


> ```
> # This cop enforces the use of either `#alias` or `#alias_method`
> # depending on configuration.
> # It also flags uses of `alias :symbol` rather than `alias bareword`.
> #
> # @example
> #
> #   # EnforcedStyle: prefer_alias
> #
> #   # good
> #   alias bar foo
> #
> #   # bad
> #   alias_method :bar, :foo
> #   alias :bar :foo
> #
> # @example
> #
> #   # EnforcedStyle: prefer_alias_method
> #
> #   # good
> #   alias_method :bar, :foo
> #
> #   # bad
> #   alias bar foo
> ```
> 
> https://github.com/bbatsov/rubocop/blob/f54c3c35db70f26a399fc9bad413d72c19b310a1/lib/rubocop/cop/style/alias.rb#L6-L29


Cop に設定項目があるかどうかは、[config/default.yml](https://github.com/bbatsov/rubocop/blob/master/config/default.yml) を見ることで判断できます。
例えば、`Style/Alias`は設定項目があるので、`config/default.yml`に記載があります。

> ```yaml
> Style/Alias:
>   EnforcedStyle: prefer_alias
>   SupportedStyles:
>     - prefer_alias
>     - prefer_alias_method
> ```
>
> https://github.com/bbatsov/rubocop/blob/master/config/default.yml#L654-L658


### ファイルの自動生成

このままでは`manual/`ディレクトリ下のファイルが同期されていません。
そのため、ソースコードの変更を`manual/`ディレクトリ下にも反映する必要があります。

RuboCop ではそれを行うための rake task を用意しています。

```bash
$ bundle exec rake generate_cops_documentation
```

上記のタスクを実行することで、`manual/`下にも変更が反映されます。


### テストの実行

殆どの場合、ドキュメントを変更しただけでコードが壊れることがないでしょう。
そのため、Pull-Request を投げる前にわざわざテストを実行する必要はほとんどありません(心配な人は、`bundle exec rake`してください)。

ですが、コメントの変更だけでも CI が落ちる可能性はあります。
何故ならば、RuboCop は自身のコードに対しても RuboCop を実行しているためです。例えば、`Metrics/LineLength`のような Cop はコメントの行のみの変更でも警告が発生しうる可能性があります。

そのため、念の為に RuboCop を実行しておくと良いでしょう。RuboCop プロジェクトに対する RuboCop の実行は、`bundle exec rake internal_investigation`で行うことが出来ます。


### Pull-Request を送る

あとは、変更をコミットして、forkして、Pull-Request を送るだけです。

実際に`Layout/SpaceInsideParens`の example を追加する Pull-Request はこちらになります。
https://github.com/bbatsov/rubocop/pull/4827


## まとめ

RuboCop のドキュメントに example を追加する方法について紹介しました。
簡単に OSS に貢献が出来るので、ぜひ取り組んでみてはいかがでしょうか？
何かわからないことがあれば、この記事のコメントや[私のTwitter](https://twitter.com/p_ck_)まで気軽に質問をしてください。


また、Rubyの日本語ドキュメントである「るりま」でも、コードの example を追加するプロジェクトが走っています。
[るりまサンプルコード追加プロジェクト月次報告 #ruby_polisher](http://tbpgr.hatenablog.com/entry/2017/10/02/010000)
もし興味があればそちらもご覧ください。


## Document がない Cop

最後に、現時点(2017/10/05)でドキュメントがない Cop の一覧を添付しておきます。
このリストは雑なワンライナーを書いて取得したので、もしかしたら間違いがあるかも知れません。その場合はご容赦ください。


```bash
# リストを生成するワンライナー
$ ruby -ractive_support -ractive_support/core_ext -e 'cops=`rubocop --show-cops`.scan(/^\w+\/\w+/); examples = `git grep @example`.scan(%r!\w+/\w+\.rb!).map{|e|e[%r!^(.+)\.rb!, 1].camelize.gsub("::",?/)}; puts cops-examples'
```

- Layout/CommentIndentation
- Layout/EmptyLines
- Layout/EndOfLine
- Layout/IndentArray
- Layout/IndentHash
- Layout/InitialIndentation
- Layout/SpaceAfterSemicolon
- Layout/SpaceAroundEqualsInParameterDefault
- Layout/SpaceAroundOperators
- Layout/SpaceBeforeComma
- Layout/SpaceInsideBlockBraces
- Layout/SpaceInsideBrackets
- Layout/SpaceInsideHashLiteralBraces
- Layout/Tab
- Layout/TrailingBlankLines
- Layout/TrailingWhitespace
- Lint/ScriptPermission
- Lint/Syntax
- Lint/UnneededDisable
- Metrics/AbcSize
- Metrics/BlockLength
- Metrics/BlockNesting
- Metrics/ClassLength
- Metrics/CyclomaticComplexity
- Metrics/LineLength
- Metrics/MethodLength
- Metrics/ModuleLength
- Metrics/ParameterLists
- Naming/AsciiIdentifiers
- Naming/ClassAndModuleCamelCase
- Naming/ConstantName
- Naming/FileName
- Naming/MethodName
- Naming/VariableName
- Performance/FixedSize
- Performance/RangeInclude
- Rails/ActionFilter
- Rails/Exit
- Rails/HasAndBelongsToMany
- Rails/Output
- Rails/Validation
- Security/JSONLoad
- Security/YAMLLoad
- Style/ArrayJoin
- Style/AsciiComments
- Style/Attr
- Style/BarePercentLiterals
- Style/BeginBlock
- Style/BlockComments
- Style/BlockDelimiters
- Style/CaseEquality
- Style/CharacterLiteral
- Style/ClassAndModuleChildren
- Style/ClassCheck
- Style/ClassVars
- Style/CollectionMethods
- Style/ColonMethodCall
- Style/CommentAnnotation
- Style/Copyright
- Style/DefWithParentheses
- Style/Documentation
- Style/EmptyLiteral
- Style/Encoding
- Style/EndBlock
- Style/FlipFlop
- Style/For
- Style/FormatString
- Style/FrozenStringLiteralComment
- Style/GlobalVars
- Style/IfUnlessModifier
- Style/IfWithSemicolon
- Style/MethodDefParentheses
- Style/MultilineTernaryOperator
- Style/NegatedWhile
- Style/NestedTernaryOperator
- Style/Not
- Style/NumericLiteralPrefix
- Style/OneLineConditional
- Style/ParenthesesAroundCondition
- Style/PercentQLiterals
- Style/PerlBackrefs
- Style/Proc
- Style/RedundantSelf
- Style/RescueModifier
- Style/Semicolon
- Style/Send
- Style/SignalException
- Style/SingleLineBlockParams
- Style/SingleLineMethods
- Style/SpecialGlobalVars
- Style/StringLiterals
- Style/StringMethods
- Style/TrivialAccessors
- Style/UnlessElse
- Style/UnneededCapitalW
- Style/UnneededPercentQ
- Style/VariableInterpolation
- Style/WhenThen
- Style/WhileUntilDo
- Style/WhileUntilModifier