Repository: tmm1/ripper-tags (Ruby)

Issue:
Chokes on `private attr_reader :x`
Given a class like this in a project:

```ruby
class A
  private attr_reader :x
end
```

Running `ripper-tags -R` will die in the Vim formatter with:

```
/var/lib/gems/2.7.0/gems/ripper-tags-0.9.0/lib/ripper-tags/vim_formatter.rb:104:in `fetch': key not found: :name (KeyError)
```

The tag object in question is missing the `:name` property:

```
{
  :kind=>"method",
  :line=>2,
  :language=>"Ruby",
  :path=>"a.rb",
  :pattern=>"  private attr_reader :x",
  :access=>"private",
  :full_name=>"A#",
  :class=>"A"
}
```

I thought this might be related to the version of Ruby that I'm using, but when I checked the AST returned by `ripper` with a couple of different Ruby versions, it looks like they're very close to being the same (albeit not identical; see below). Additionally, I actually tried running `ripper-tags` under both versions and got the same exception both times.

Here's Ruby 3.1.2 (ie. with `pp Ripper.sexp('class A; private attr_reader :x; end')`):

```
[:program,
 [[:class,
   [:const_ref, [:@const, "A", [1, 6]]],
   nil,
   [:bodystmt,
    [[:void_stmt],
     [:command,
      [:@ident, "private", [1, 9]],
      [[:command,
        [:@ident, "attr_reader", [1, 17]],
        [:args_add_block, [[:symbol_literal, [:symbol, [:@ident, "x", [1, 30]]]]], false]]]]],
    nil,
    nil,
    nil]]]]
```

vs Ruby 2.7.4 (ie. with `pp Ripper.sexp('class A; private attr_reader :x; end')`):

```
[:program,
 [[:class,
   [:const_ref, [:@const, "A", [1, 6]]],
   nil,
   [:bodystmt,
    [[:void_stmt],
     [:command,
      [:@ident, "private", [1, 9]],
      [[:command,
        [:@ident, "attr_reader", [1, 17]],
        [:args_add_block,
         [[:symbol_literal, [:symbol, [:@ident, "x", [1, 30]]]]],
         false]]]]],
    nil,
    nil,
    nil]]]]
```

The workaround for now is to rewrite the code, breaking it into distinct calls on separate lines:

```ruby
class A
  attr_reader :x
  private :x
end
```

Somewhat tangentially, older Ruby only likes the rewritten form, because — at least on 2.7.4 — `attr_reader` returns `nil` and `private` complains about being given `nil` instead of a symbol or string, but it is still _syntactically_ valid, at least insofar as `ripper` accepts it. In 3.1.4 `attr_reader` returns `[:x]` and `private` is happy with that.


Discussion:
Thank you for reporting! 🙇 



Patch:
diff --git a/lib/ripper-tags/parser.rb b/lib/ripper-tags/parser.rb
index eff3a2b..023778d 100644
--- a/lib/ripper-tags/parser.rb
+++ b/lib/ripper-tags/parser.rb
@@ -193,17 +193,21 @@ def on_method_add_arg(call, args)
         klass = name == access ? nil : 'self'
         procedure = :def_with_access
 
-        if args[1][0].is_a?(String)
-          procedure = :redefine_access
-          method_name = args[1][0]
+        if args[1][0].is_a?(Array)
+          args[1].inject([]) do |gen, def_args|
+            if def_args[1].is_a?(String)
+              gen << [procedure, klass, def_args[1], access, line]
+            else
+              gen
+            end
+          end
+        elsif args[1][0].is_a?(String)
+          [:redefine_access, klass, args[1][0], access, line]
         elsif args[1][1] == 'self'
-          method_name = args[1][2]
+          [procedure, klass, args[1][2], access, line]
         else
-          klass = nil
-          method_name = args[1][1]
+          [procedure, nil, args[1][1], access, line]
         end
-
-        [procedure, klass, method_name, access, line]
       when "module_function"
         access = "public"
         klass = "self"


Test patch:
diff --git a/test/test_ripper_tags.rb b/test/test_ripper_tags.rb
index e5f7ea6..5d94084 100644
--- a/test/test_ripper_tags.rb
+++ b/test/test_ripper_tags.rb
@@ -203,6 +203,25 @@ def def() end
       assert_equal 'method',   tags.find{ |t| t[:name] == 'abc' }[:kind]
       assert_equal nil,        tags.find{ |t| t[:name] == 'def' }[:access]
     end
+    
+    %w(private public protected).each do |visibility|
+      tags = extract(<<-EOC)
+        class Test
+          #{visibility} attr_reader :a, :b
+          attr_reader :c
+        end
+      EOC
+
+      assert_equal 'a',        tags[1][:name]
+      assert_equal visibility, tags[1][:access]
+      assert_equal 'method',   tags[1][:kind]
+      assert_equal 'b',        tags[2][:name]
+      assert_equal visibility, tags[2][:access]
+      assert_equal 'method',   tags[2][:kind]
+      assert_equal 'c',        tags[3][:name]
+      assert_equal nil,        tags[3][:access]
+      assert_equal 'method',   tags[3][:kind]
+    end
 
     %w(private_class_method public_class_method).each do |visibility|
       tags = extract(<<-EOC)