Ran into this issue while writing parameterized tests that include an optional block argument. Simplest implementation of the problem:
require "method_source"
a = [
{block: ->(e) { e << %i[a c] }},
]
puts a.first[:block].source
Output:
/Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source.rb:29:in `rescue in source_helper': Could not parse source for #<Proc:0x0000000100999ef8 ./source.rb:4 (lambda)>: (eval):2: syntax error, unexpected ',', expecting end-of-input (MethodSource::SourceNotFoundError)
...block: ->(e) { e << %i[a c] }},
... ^
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source.rb:23:in `source_helper'
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source.rb:110:in `source'
from ./source.rb:7:in `<main>'
/Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source/code_helpers.rb:71:in `eval': (eval):2: syntax error, unexpected ',', expecting end-of-input (SyntaxError)
...block: ->(e) { e << %i[a c] }},
... ^
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source/code_helpers.rb:71:in `block in complete_expression?'
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source/code_helpers.rb:70:in `catch'
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source/code_helpers.rb:70:in `complete_expression?'
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source/code_helpers.rb:97:in `block in extract_first_expression'
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source/code_helpers.rb:95:in `each'
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source/code_helpers.rb:95:in `extract_first_expression'
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source/code_helpers.rb:30:in `expression_at'
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source.rb:27:in `source_helper'
from /Users/me/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/method_source-1.0.0/lib/method_source.rb:110:in `source'
from ./source.rb:7:in `<main>'
Possible fix
I recognize this is a major change for the gem, and may not be possible due to the variety of ruby implementations this supports, but the parser gem does an excellent job turning any source file into an AST. The AST can easily be searched for proc/lambda/def calls, and the line numbers and source are easily accessible from any matching node.
This is the workaround I'm using for now to get Proc#source working for my case. I can adapt this and create a pull request for method_source if the maintainers feel that changing the parser is a good idea.
require "dry/core/cache"
require "parser/current"
module ProcSource
extend Dry::Core::Cache
extend AST::Sexp
class SourceNotFound < StandardError; end
# parse a ruby source file and return the AST; result is cached
def self.parse(path)
fetch_or_store(path) do
source_buffer = Parser::Source::Buffer.new(path).read
parser = Parser::CurrentRuby.new
parser.diagnostics.all_errors_are_fatal = true
parser.diagnostics.ignore_warnings = true
parser.parse(source_buffer)
end
end
PROC_NODES = [
s(:send, nil, :lambda),
s(:send, nil, :proc),
s(:send, s(:const, nil, :Proc), :new),
]
module Helpers
def source
file, line = source_location
root = ProcSource.parse(file)
queue = [root]
until queue.empty?
node = queue.shift
next unless node.is_a?(Parser::AST::Node)
queue.unshift(*node.children)
next unless node.type == :block
next unless node.loc.line == line
# verify the first child is a send node
ch = node.children.first
next unless ch.is_a?(Parser::AST::Node)
next unless ch.type == :send
# verify we're calling lambda, proc, or Proc.new
next unless ProcSource::PROC_NODES.include?(ch)
return node.loc.expression.source
end
raise SourceNotFound, "unable to find source for %p" % self
end
end
Proc.prepend(Helpers)
end
Issue
Ran into this issue while writing parameterized tests that include an optional block argument. Simplest implementation of the problem:
Output:
Possible fix
I recognize this is a major change for the gem, and may not be possible due to the variety of ruby implementations this supports, but the
parser
gem does an excellent job turning any source file into an AST. The AST can easily be searched for proc/lambda/def calls, and the line numbers and source are easily accessible from any matching node.This is the workaround I'm using for now to get Proc#source working for my case. I can adapt this and create a pull request for method_source if the maintainers feel that changing the parser is a good idea.