lsegal / yard

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

Right hand assignment inside case/when/end causes cannot get the first element of beginless range (RangeError) #1434

Open DimaD opened 2 years ago

DimaD commented 2 years ago

Yards AST parser raises cannot get the first element of beginless range (RangeError) when using right-hand assignment and restructuring.

I have tried using restructuring in different contexts and only the use from the examples below inside of the case statement causes an error.

Steps to reproduce

Example 1 - causes an error

case 1
when 1
  [1] => [a]
end

Example 2 - causes an error

case 1
when 1
  {a: 1} => {a:}
end

Example 3 - no error The error does not occur in every context, I was only able to trigger it inside the case/when statement.

def m
  [1] => [a]
end

Actual Output

% be yardoc --debug broken-yard.rb                                            
[debug]: Parsing ["broken-yard.rb"] with `ruby` parser
[debug]: Parsing broken-yard.rb
bundler: failed to load command: yardoc (/Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/bin/yardoc)
/Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/ruby/ast_node.rb:274:in `first': cannot get the first element of beginless range (RangeError)
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/ruby/ast_node.rb:274:in `line'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:624:in `block in insert_comments'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/ruby/ast_node.rb:212:in `traverse'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:615:in `insert_comments'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:60:in `parse'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:17:in `parse'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:442:in `parse'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:46:in `block in parse'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/logging.rb:82:in `capture'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:45:in `parse'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:371:in `parse_in_order'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:114:in `block in parse'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/logging.rb:182:in `enter_level'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:113:in `parse'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard.rb:20:in `parse'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/cli/yardoc.rb:259:in `block in run'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/serializers/yardoc_serializer.rb:56:in `lock_for_writing'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/registry_store.rb:202:in `lock_for_writing'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/registry.rb:210:in `lock_for_writing'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/cli/yardoc.rb:258:in `run'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/lib/yard/cli/command.rb:14:in `run'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/gems/yard-0.9.27/bin/yardoc:13:in `<top (required)>'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/bin/yardoc:23:in `load'
    from /Users/testuser/src/yard_examples/vendor/bundle/ruby/3.0.0/bin/yardoc:23:in `<top (required)>'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/cli/exec.rb:58:in `load'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/cli/exec.rb:58:in `kernel_load'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/cli/exec.rb:23:in `run'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/cli.rb:478:in `exec'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/cli.rb:31:in `dispatch'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/cli.rb:25:in `start'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/libexec/bundle:49:in `block in <top (required)>'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/3.0.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'
    from /Users/testuser/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/libexec/bundle:37:in `<top (required)>'
    from /Users/testuser/.rbenv/versions/3.0.3/bin/bundle:23:in `load'
    from /Users/testuser/.rbenv/versions/3.0.3/bin/bundle:23:in `<main>'

Expected Output

I expect no errors and no docs produced from the example

Files:           0
Modules:         0 (    0 undocumented)
Classes:         0 (    0 undocumented)
Constants:       0 (    0 undocumented)
Attributes:      0 (    0 undocumented)
Methods:         0 (    0 undocumented)
 100.00% documented

Environment details:

I have read the Contributing Guide.

akimd commented 2 years ago

I have a similar failure, but with a different pattern (heavily stripped down)

    case [self, other]
    in [MetaType[id], *]
      if other in MetaType[id2]
      end
    end
/opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ast_node.rb:274:in `first': cannot get the first element of beginless range (RangeError)
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ast_node.rb:274:in `line'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:624:in `block in insert_comments'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ast_node.rb:212:in `traverse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:615:in `insert_comments'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:60:in `parse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:17:in `parse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:442:in `parse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:46:in `block in parse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/logging.rb:82:in `capture'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:45:in `parse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:371:in `parse_in_order'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:114:in `block in parse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/logging.rb:182:in `enter_level'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/source_parser.rb:113:in `parse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard.rb:20:in `parse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/cli/yardoc.rb:259:in `block in run'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/serializers/yardoc_serializer.rb:56:in `lock_for_writing'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/registry_store.rb:202:in `lock_for_writing'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/registry.rb:210:in `lock_for_writing'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/cli/yardoc.rb:258:in `run'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/cli/command.rb:14:in `run'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/bin/yardoc:13:in `<top (required)>'
    from /opt/local/bin/yardoc:25:in `load'
    from /opt/local/bin/yardoc:25:in `<main>'
akimd commented 2 years ago

I believe the proper fix is in lib/yard/parser/ruby/ast_node.rb to replace:

        def line
          line_range && line_range.first
        end

by

        def line
          line_range && line_range.begin
        end

since begin always returns the "first" element, as nil if it does not exist (while first dies if begin is nil).

However in that case yardoc dies farther:

/opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:624:in `block in insert_comments': undefined method `-' for nil:NilClass (NoMethodError)

            ((node.line - 1).downto(node.line - 2).to_a + [node.line]).each do |line|
                        ^
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ast_node.rb:212:in `traverse'
    from /opt/local/lib/ruby3.1/gems/3.1.0/gems/yard-0.9.27/lib/yard/parser/ruby/ruby_parser.rb:615:in `insert_comments'

so it looks like after all it does not like line to return nil, contrary to what the && seems to imply. So I made it

        def line
          line_range && line_range.begin || 1
        end

and this time yardoc finishes properly.

akimd commented 2 years ago

It turns out I also need to change reset_line_info in the same file:

            self.line_range = Range.new(f.line_range.begin || 1, l.line_range.last)
            self.source_range = Range.new(f.source_range.begin || 1, l.source_range.last)

instead of using .first.