RipperでRubyのコードを解析したい

# やりたいこと
- メソッドのコメントにannotationを書いておき、そのannotationが付いたのはどのクラスのどのメソッドなのか、を知りたい。
- そしてその結果を[Okuribito](https://github.com/muramurasan/okuribito)に渡して使われていないメソッドを監視したい。

---

# Ripperを試してみる

- Rubyに付属するRubyパーサ
- Rubyのコードを読ませると、`on_xxx`のイベントを受けられる。
- 上から読んでいってイベントが上がってくる Scanner 
- 解析して結果を教えてくれる Parser 

---

# イベントの種別

```ruby
irb(main):002:0> require 'ripper'
=> true
irb(main):003:0>
irb(main):004:0> Ripper::SCANNER_EVENTS
=> [:CHAR, :__end__, :backref, :backtick, :comma, :comment, :const, :cvar, :embdoc, :embdoc_beg, :embdoc_end, :embexpr_beg, :embexpr_end, :embvar, :float, :gvar, :heredoc_beg, :heredoc_end, :ident, :ignored_nl, :imaginary, :int, :ivar, :kw, :label, :label_end, :lbrace, :lbracket, :lparen, :nl, :op, :period, :qsymbols_beg, :qwords_beg, :rational, :rbrace, :rbracket, :regexp_beg, :regexp_end, :rparen, :semicolon, :sp, :symbeg, :symbols_beg, :tlambda, :tlambeg, :tstring_beg, :tstring_content, :tstring_end, :words_beg, :words_sep]
```

---

# 以下が見やすい。
https://github.com/svenfuchs/ripper2ruby/blob/303d7ac4dfc2d8dbbdacaa6970fc41ff56b31d82/notes/scanner_events

---

# パースされるテストコード
```ruby
class Target
  # @okuribito
  def self.class_method
    puts 1
  end

  # @okuribito
  def instance_method
    puts 2
  end

  def self.active_class_method
    puts 3
  end

  def active_instance_method
    puts 4
  end
end
```

---

# Scan
```ruby
require 'ripper'
require 'cgi'

class ScanTest < Ripper::Filter
  def on_default(event, tok, f)
    # 有効にすると全部得られます。
    # puts "#{__method__} #{tok} #{event}"
  end

  def on_ident(tok, f)
    puts "#{__method__} #{tok}"
  end

  def on_comment(tok, f)
    puts "#{__method__} #{tok}"
  end
end

puts ScanTest.new(ARGF).parse('')
```

---

```sh
ruby parser.rb target.rb
on_comment # @okuribito
on_ident class_method
on_ident puts
on_comment # okuribito
on_ident instance_method
on_ident puts
on_ident active_class_method
on_ident puts
on_ident active_instance_method
on_ident puts
```
---

# コメントは得られたがメソッドやクラスはすべて`on_ident`でくるため判別不可能。

---

# Parser
```ruby
pp Ripper.sexp_raw(ARGF.read)
```

```sh
[:program,
 [:stmts_add,
  [:stmts_new],
  [:class,
   [:const_ref, [:@const, "Target", [1, 6]]],
   nil,
   [:bodystmt,
    [:stmts_add,
     [:stmts_add,
      [:stmts_add,
       [:stmts_add,
        [:stmts_add, [:stmts_new], [:void_stmt]],
        [:defs,
         [:var_ref, [:@kw, "self", [3, 6]]],
         [:@period, ".", [3, 10]],
         [:@ident, "class_method", [3, 11]],
         [:params, nil, nil, nil, nil, nil, nil, nil],
         [:bodystmt,
          [:stmts_add,
           [:stmts_new],
           [:command,
            [:@ident, "puts", [4, 4]],
            [:args_add_block,
             [:args_add, [:args_new], [:@int, "1", [4, 9]]],
             false]]],
          nil,
          nil,
          nil]]],
       [:def,
        [:@ident, "instance_method", [8, 6]],
        [:params, nil, nil, nil, nil, nil, nil, nil],
        [:bodystmt,
         [:stmts_add,
          [:stmts_new],
          [:command,
           [:@ident, "puts", [9, 4]],
           [:args_add_block,
            [:args_add, [:args_new], [:@int, "2", [9, 9]]],
            false]]],
         nil,
         nil,
         nil]]],
      [:defs,
       [:var_ref, [:@kw, "self", [12, 6]]],
       [:@period, ".", [12, 10]],
       [:@ident, "active_class_method", [12, 11]],
       [:params, nil, nil, nil, nil, nil, nil, nil],
       [:bodystmt,
        [:stmts_add,
         [:stmts_new],
         [:command,
          [:@ident, "puts", [13, 4]],
          [:args_add_block,
           [:args_add, [:args_new], [:@int, "3", [13, 9]]],
           false]]],
        nil,
        nil,
        nil]]],
     [:def,
      [:@ident, "active_instance_method", [16, 6]],
      [:params, nil, nil, nil, nil, nil, nil, nil],
      [:bodystmt,
       [:stmts_add,
        [:stmts_new],
        [:command,
         [:@ident, "puts", [17, 4]],
         [:args_add_block,
          [:args_add, [:args_new], [:@int, "4", [17, 9]]],
          false]]],
       nil,
       nil,
       nil]]],
    nil,
    nil,
    nil]]]]
```

---

# コメントは得られなかった…。

---

# To be continued...
rdocではできてるので、rdocのコードを追ってみる。

rdocは`Ripper::Filter`でやっている様子。
なるほど、人間が読んでいくのと同じような感じで読んでいくのだな…

- https://github.com/ruby/rdoc/blob/93d18de1fc5a355f85fd35eacf78f24487dfdb53/lib/rdoc/parser/ripper_state_lex.rb
- https://github.com/ruby/rdoc/blob/master/lib/rdoc/parser/ruby.rb#L343