Skip to content

Commit

Permalink
This enhancement allows a user to add the -s flag if they want to acc…
Browse files Browse the repository at this point in the history
…ess a methods origin definition. It allows for chaining of multiple esses to further go up the classes as needed. (#770)
  • Loading branch information
paulreece authored Nov 28, 2023
1 parent 412ab26 commit eec1329
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 8 deletions.
11 changes: 9 additions & 2 deletions lib/irb/cmd/show_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,18 @@ def execute(str = nil)
puts "Error: Expected a string but got #{str.inspect}"
return
end

source = SourceFinder.new(@irb_context).find_source(str)
if str.include? " -s"
str, esses = str.split(" -")
s_count = esses.count("^s").zero? ? esses.size : 1
source = SourceFinder.new(@irb_context).find_source(str, s_count)
else
source = SourceFinder.new(@irb_context).find_source(str)
end

if source
show_source(source)
elsif s_count
puts "Error: Couldn't locate a super definition for #{str}"
else
puts "Error: Couldn't locate a definition for #{str}"
end
Expand Down
30 changes: 24 additions & 6 deletions lib/irb/source_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def initialize(irb_context)
@irb_context = irb_context
end

def find_source(signature)
def find_source(signature, s_count = nil)
context_binding = @irb_context.workspace.binding
case signature
when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
Expand All @@ -26,14 +26,13 @@ def find_source(signature)
when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
owner = eval(Regexp.last_match[:owner], context_binding)
method = Regexp.last_match[:method]
if owner.respond_to?(:instance_method)
methods = owner.instance_methods + owner.private_instance_methods
file, line = owner.instance_method(method).source_location if methods.include?(method.to_sym)
end
return unless owner.respond_to?(:instance_method)
file, line = method_target(owner, s_count, method, "owner")
when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
receiver = eval(Regexp.last_match[:receiver] || 'self', context_binding)
method = Regexp.last_match[:method]
file, line = receiver.method(method).source_location if receiver.respond_to?(method, true)
return unless receiver.respond_to?(method, true)
file, line = method_target(receiver, s_count, method, "receiver")
end
if file && line && File.exist?(file)
Source.new(file: file, first_line: line, last_line: find_end(file, line))
Expand All @@ -60,5 +59,24 @@ def find_end(file, first_line)
end
first_line
end

def method_target(owner_receiver, s_count, method, type)
case type
when "owner"
target_method = owner_receiver.instance_method(method)
return target_method.source_location unless s_count
when "receiver"
if s_count
target_method = owner_receiver.class.instance_method(method)
else
target_method = method
return owner_receiver.method(method).source_location
end
end
s_count.times do |s|
target_method = target_method.super_method if target_method
end
target_method.nil? ? nil : target_method.source_location
end
end
end
138 changes: 138 additions & 0 deletions test/irb/test_cmd.rb
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,144 @@ def test_show_source_method
assert_match(%r[/irb\/init\.rb], out)
end

def test_show_source_method_s
code = <<~RUBY
class Baz
def foo
end
end
class Bar < Baz
def foo
super
end
end
RUBY
File.write("#{@tmpdir}/bazbar.rb", code)
out, err = execute_lines(
"irb_load '#{@tmpdir}/bazbar.rb'\n",
"show_source Bar#foo -s",
)
assert_match(%r[bazbar.rb:2\n\n def foo\n end\n\n=> nil\n], out)
File.delete("#{@tmpdir}/bazbar.rb")
end

def test_show_source_method_multiple_s
code = <<~RUBY
class Baz
def fob
end
end
class Bar < Baz
def fob
super
end
end
class Bob < Bar
def fob
super
end
end
RUBY
File.write("#{@tmpdir}/bazbarbob.rb", code)
out, err = execute_lines(
"irb_load '#{@tmpdir}/bazbarbob.rb'\n",
"show_source Bob#fob -ss",
)
assert_match(%r[bazbarbob.rb:2\n\n def fob\n end\n\n=> nil\n], out)
File.delete("#{@tmpdir}/bazbarbob.rb")
end

def test_show_source_method_no_instance_method
code = <<~RUBY
class Baz
end
class Bar < Baz
def fee
super
end
end
RUBY
File.write("#{@tmpdir}/bazbar.rb", code)
out, err = execute_lines(
"irb_load '#{@tmpdir}/bazbar.rb'\n",
"show_source Bar#fee -s",
)
assert_match(%r[Error: Couldn't locate a super definition for Bar#fee\n], out)
File.delete("#{@tmpdir}/bazbar.rb")
end

def test_show_source_method_exceeds_super_chain
code = <<~RUBY
class Baz
def fow
end
end
class Bar < Baz
def fow
super
end
end
RUBY
File.write("#{@tmpdir}/bazbar.rb", code)
out, err = execute_lines(
"irb_load '#{@tmpdir}/bazbar.rb'\n",
"show_source Bar#fow -ss",
)
assert_match(%r[Error: Couldn't locate a super definition for Bar#fow\n], out)
File.delete("#{@tmpdir}/bazbar.rb")
end

def test_show_source_method_accidental_characters
code = <<~RUBY
class Baz
def fol
end
end
class Bar < Baz
def fol
super
end
end
RUBY
File.write("#{@tmpdir}/bazbar.rb", code)
out, err = execute_lines(
"irb_load '#{@tmpdir}/bazbar.rb'\n",
"show_source Bar#fol -sddddd",
)

assert_match(%r[bazbar.rb:2\n\n def fol\n end\n\n=> nil\n], out)
File.delete("#{@tmpdir}/bazbar.rb")
end

def test_show_source_receiver_super
code = <<~RUBY
class Baz
def fot
end
end
class Bar < Baz
def fot
super
end
end
RUBY
File.write("#{@tmpdir}/bazbar.rb", code)
out, err = execute_lines(
"irb_load '#{@tmpdir}/bazbar.rb'\n",
"bar = Bar.new",
"show_source bar.fot -s"
)
assert_match(%r[bazbar.rb:2\n\n def fot\n end\n\n=> nil\n], out)
File.delete("#{@tmpdir}/bazbar.rb")
end

def test_show_source_string
out, err = execute_lines(
"show_source 'IRB.conf'\n",
Expand Down

0 comments on commit eec1329

Please sign in to comment.