lsegal / yard

YARD is a Ruby Documentation tool. The Y stands for "Yay!"
http://yardoc.org
MIT License
1.92k stars 394 forks source link

Error on case statement parsing #1536

Open kddnewton opened 4 months ago

kddnewton commented 4 months ago

Steps to reproduce

if foo
end

case
when :def
  if foo
  end
end

Actual Output

[debug]: Parsing ["doc", "test.rb"] with `ruby` parser
[debug]: Parsing test.rb
.../gems/yard-0.9.35/lib/yard/parser/ruby/ast_node.rb:274:in `first': cannot get the first element of beginless range (RangeError)
    from .../gems/yard-0.9.35/lib/yard/parser/ruby/ast_node.rb:274:in `line'
    from .../gems/yard-0.9.35/lib/yard/parser/ruby/ruby_parser.rb:630:in `block in insert_comments'
    from .../gems/yard-0.9.35/lib/yard/parser/ruby/ast_node.rb:212:in `traverse'
    from .../gems/yard-0.9.35/lib/yard/parser/ruby/ruby_parser.rb:621:in `insert_comments'
    from .../gems/yard-0.9.35/lib/yard/parser/ruby/ruby_parser.rb:60:in `parse'
    from .../gems/yard-0.9.35/lib/yard/parser/ruby/ruby_parser.rb:17:in `parse'
    from .../gems/yard-0.9.35/lib/yard/parser/source_parser.rb:442:in `parse'
    from .../gems/yard-0.9.35/lib/yard/parser/source_parser.rb:46:in `block in parse'
    from .../gems/yard-0.9.35/lib/yard/logging.rb:82:in `capture'
    from .../gems/yard-0.9.35/lib/yard/parser/source_parser.rb:45:in `parse'
    from .../gems/yard-0.9.35/lib/yard/parser/source_parser.rb:371:in `parse_in_order'
    from .../gems/yard-0.9.35/lib/yard/parser/source_parser.rb:114:in `block in parse'
    from .../gems/yard-0.9.35/lib/yard/logging.rb:182:in `enter_level'
    from .../gems/yard-0.9.35/lib/yard/parser/source_parser.rb:113:in `parse'
    from .../gems/yard-0.9.35/lib/yard.rb:20:in `parse'
    from .../gems/yard-0.9.35/lib/yard/cli/yardoc.rb:259:in `block in run'
    from .../gems/yard-0.9.35/lib/yard/serializers/yardoc_serializer.rb:56:in `lock_for_writing'
    from .../gems/yard-0.9.35/lib/yard/registry_store.rb:202:in `lock_for_writing'
    from .../gems/yard-0.9.35/lib/yard/registry.rb:210:in `lock_for_writing'
    from .../gems/yard-0.9.35/lib/yard/cli/yardoc.rb:258:in `run'
    from .../gems/yard-0.9.35/lib/yard/cli/command.rb:14:in `run'
    from .../gems/yard-0.9.35/lib/yard/cli/command_parser.rb:72:in `run'
    from .../gems/yard-0.9.35/lib/yard/cli/command_parser.rb:54:in `run'
    from .../gems/yard-0.9.35/bin/yard:13:in `<top (required)>'
    from .../bin/yard:25:in `load'
    from .../bin/yard:25:in `<main>'

Expected Output

Parses successfully.

Environment details:

MSP-Greg commented 4 months ago

I checked this with Ruby 3.3. & 3.2, and they also have the problem.

Refresher on ranges, 'endless' ranges were added in Ruby 2.6, 'beginless' ranges were added in Ruby 2.7. Range#first and Range#last will throw errors if the range is 'beginless' or 'endless', respectively. Both begin and end return nil instead of raising an error. I think the original purpose was to make it easier to slice arrays.

The following patch seems to fix the errors, but I haven't had time to check further, nor write any tests...

diff --git a/lib/yard/parser/ruby/ast_node.rb b/lib/yard/parser/ruby/ast_node.rb
index c9deb3ed..ccfa9b6c 100644
--- a/lib/yard/parser/ruby/ast_node.rb
+++ b/lib/yard/parser/ruby/ast_node.rb
@@ -271,7 +271,7 @@ module YARD

         # @return [Fixnum] the starting line number of the node
         def line
-          line_range && line_range.first
+          line_range && (line_range.begin || line_range.end)
         end

         # @return [String] the first line of source represented by the node.
@@ -345,8 +345,8 @@ module YARD
           elsif !children.empty?
             f = children.first
             l = children.last
-            self.line_range = Range.new(f.line_range.first, l.line_range.last)
-            self.source_range = Range.new(f.source_range.first, l.source_range.last)
+            self.line_range = Range.new(f.line_range.begin, l.line_range.end)
+            self.source_range = Range.new(f.source_range.begin, l.source_range.end)
           elsif @fallback_line || @fallback_source
             self.line_range = @fallback_line
             self.source_range = @fallback_source
diff --git a/lib/yard/parser/ruby/ruby_parser.rb b/lib/yard/parser/ruby/ruby_parser.rb
index a7ba5fab..b8eaa7c2 100644
--- a/lib/yard/parser/ruby/ruby_parser.rb
+++ b/lib/yard/parser/ruby/ruby_parser.rb
@@ -627,11 +627,13 @@ module YARD
             end

             # check upwards from line before node; check node's line at the end
-            ((node.line - 1).downto(node.line - 2).to_a + [node.line]).each do |line|
-              comment = @comments[line]
-              if comment && !comment.empty?
-                add_comment(line, node)
-                break
+            if (n_l = node.line)
+              ((n_l - 1).downto(n_l - 2).to_a + [n_l]).each do |line|
+                comment = @comments[line]
+                if comment && !comment.empty?
+                  add_comment(line, node)
+                  break
+                end
               end
             end