# frozen_string_literal: true describe "show-source" do # rubocop:disable Metrics/BlockLength def define_persistent_class(file, class_body) file.puts(class_body) file.close require(file.path) end before do @o = Object.new def @o.sample_method :sample end Object.remove_const :Test if Object.const_defined? :Test Object.const_set(:Test, Module.new) end after do Pad.clear end it "should output a method's source" do expect(pry_eval(binding, 'show-source @o.sample_method')).to match(/def @o.sample/) end it "should output help" do expect(pry_eval('show-source -h')).to match(/Usage:\s+show-source/) end it "should output a method's source with line numbers" do expect(pry_eval(binding, 'show-source -l @o.sample_method')) .to match(/\d+: def @o.sample/) end it "should output a method's source with line numbers starting at 1" do expect(pry_eval(binding, 'show-source -b @o.sample_method')) .to match(/1: def @o.sample/) end it "should output a method's source if inside method and no name given" do def @o.sample pry_eval(binding, 'show-source') end docs = @o.sample expect(docs).to match(/def @o.sample/) end it "should output a method's source inside method using the -l switch" do def @o.sample pry_eval(binding, 'show-source -l') end docs = @o.sample expect(docs).to match(/def @o.sample/) end it "should find methods even if there are spaces in the arguments" do def @o.foo(*_bars) @foo = "Mr flibble" self end out = pry_eval(binding, "show-source @o.foo('bar', 'baz bam').foo") expect(out).to match(/Mr flibble/) end it "should find methods even if the object overrides method method" do _c = Class.new do def method 98 end end expect(pry_eval(binding, "show-source _c.new.method")).to match(/98/) end it "should not show the source when a non-extant method is requested" do _c = Class.new do def method 98 end end expect(mock_pry(binding, "show-source _c#wrongmethod")) .to match(/Couldn't locate/) end it "doesn't show the source and deliver an error message without exclamation point" do _c = Class.new error_message = "Error: Couldn't locate a definition for _c#wrongmethod\n" expect(mock_pry(binding, "show-source _c#wrongmethod")).to eq(error_message) end it "should find instance_methods if the class overrides instance_method" do _c = Class.new do def method 98 end def self.instance_method 789 end end expect(pry_eval(binding, "show-source _c#method")).to match(/98/) end it "should find instance methods with self#moo" do _c = Class.new do def moo "ve over!" end end expect(pry_eval(binding, "cd _c", "show-source self#moo")).to match(/ve over/) end it "should not find instance methods with self.moo" do _c = Class.new do def moo "ve over!" end end expect { pry_eval(binding, 'cd _c', 'show-source self.moo') } .to raise_error(Pry::CommandError, /Couldn't locate/) end it "should find normal methods with self.moo" do _c = Class.new do def self.moo "ve over!" end end expect(pry_eval(binding, 'cd _c', 'show-source self.moo')).to match(/ve over/) end it "should not find normal methods with self#moo" do _c = Class.new do def self.moo "ve over!" end end expect { pry_eval(binding, 'cd _c', 'show-source self#moo') } .to raise_error(Pry::CommandError, /Couldn't locate/) end it "should find normal methods (i.e non-instance methods) by default" do _c = Class.new do def self.moo "ve over!" end end expect(pry_eval(binding, "cd _c", "show-source moo")).to match(/ve over/) end it "should find instance methods if no normal methods available" do _c = Class.new do def moo "ve over!" end end expect(pry_eval(binding, "cd _c", "show-source moo")).to match(/ve over/) end describe "with -e option" do before do class FooBar def bar :bar end end end after do Object.remove_const(:FooBar) end it "shows the source code for the returned value as Ruby" do ReplTester.start target: binding do input 'show-source -e FooBar.new' output(/class FooBar/) end end end it "should raise a CommandError when super method doesn't exist" do def @o.foo(*bars); end expect { pry_eval(binding, "show-source --super @o.foo") } .to raise_error(Pry::CommandError, /No superclass found/) end it "should output the source of a method defined inside Pry" do out = pry_eval("def dyn_method\n:test\nend", 'show-source dyn_method') expect(out).to match(/def dyn_method/) Object.remove_method :dyn_method end it 'should output source for an instance method defined inside pry' do pry_tester.tap do |t| t.eval "class Test::A\n def yo\n end\nend" expect(t.eval('show-source Test::A#yo')).to match(/def yo/) end end it 'should output source for a repl method defined using define_method' do pry_tester.tap do |t| t.eval "class Test::A\n define_method(:yup) {}\nend" expect(t.eval('show-source Test::A#yup')).to match(/define_method\(:yup\)/) end end it "should output the source of a command defined inside Pry" do command_definition = %( Pry.config.commands.command "hubba-hubba" do puts "that's what she said!" end ) out = pry_eval(command_definition, 'show-source hubba-hubba') expect(out).to match(/what she said/) Pry.config.commands.delete "hubba-hubba" end context "when there's no source code but the comment exists" do before do class Foo # Bingo. def bar; end end allow_any_instance_of(Pry::Method).to receive(:source).and_return(nil) end after do Object.remove_const(:Foo) end it "outputs zero line numbers" do out = pry_eval('show-source Foo#bar') expect(out).to match(/ Owner:\sFoo .+ Number\sof\slines:\s0 .+ \*\*\sWarning:\sCannot\sfind\scode\sfor\s'bar'\s\(source_location\sis\snil\) /mx) end end describe "finding super methods with help of `--super` switch" do before do class Foo def foo(*_bars) :super_wibble end end end after do Object.remove_const(:Foo) end it "finds super methods with explicit method argument" do o = Foo.new def o.foo(*_bars) :wibble end expect(pry_eval(binding, "show-source --super o.foo")).to match(/:super_wibble/) end it "finds super methods without explicit method argument" do o = Foo.new def o.foo(*bars) @foo = :wibble pry_eval(binding, 'show-source --super') end expect(o.foo).to match(/:super_wibble/) end it "finds super methods with multiple --super " do o = Foo.new o.extend( Module.new do def foo :nibble end end ) def o.foo(*bars) @foo = :wibble pry_eval(binding, 'show-source --super --super') end expect(o.foo).to match(/:super_wibble/) end end describe "on sourcable objects" do it "should output source defined inside pry" do pry_tester.tap do |t| t.eval "hello = proc { puts 'hello world!' }" expect(t.eval("show-source hello")).to match(/proc \{ puts/) end end it "should output source for procs/lambdas stored in variables" do _hello = proc { puts 'hello world!' } expect(pry_eval(binding, 'show-source _hello')).to match(/proc \{ puts/) end it "should output source for procs/lambdas stored in constants" do HELLO = proc { puts 'hello world!' } expect(pry_eval(binding, "show-source HELLO")).to match(/proc \{ puts/) Object.remove_const(:HELLO) end it "should output source for method objects" do def @o.hi puts 'hi world' end _meth = @o.method(:hi) expect(pry_eval(binding, "show-source _meth")).to match(/puts 'hi world'/) end describe "on variables that shadow methods" do before do @t = pry_tester.eval(unindent(<<-SHADOWED_VAR)) class ::TestHost def hello hello = proc { ' smile ' } _foo = hello pry_tester(binding) end end ::TestHost.new.hello SHADOWED_VAR end after { Object.remove_const(:TestHost) } it "source of variable takes precedence over method that is being shadowed" do source = @t.eval('show-source hello') expect(source).not_to match(/def hello/) expect(source).to match(/proc \{ ' smile ' \}/) end it "source of method being shadowed should take precedence over variable if given self.meth_name syntax" do expect(@t.eval('show-source self.hello')).to match(/def hello/) end end end describe "on variable or constant" do before do class TestHost def hello "hi there" end end end after { Object.remove_const(:TestHost) } it "outputs source of its class if variable doesn't respond to source_location" do _test_host = TestHost.new expect(pry_eval(binding, 'show-source _test_host')) .to match(/class TestHost\n.*def hello/) end it "outputs source of its class if constant doesn't respond to source_location" do TEST_HOST = TestHost.new expect(pry_eval(binding, 'show-source TEST_HOST')) .to match(/class TestHost\n.*def hello/) Object.remove_const(:TEST_HOST) end end describe "on modules" do before do class ShowSourceTestSuperClass def alpha; end end class ShowSourceTestClass < ShowSourceTestSuperClass def alpha; end end module ShowSourceTestSuperModule def alpha; end end module ShowSourceTestModule include ShowSourceTestSuperModule def alpha; end end ShowSourceTestClassWeirdSyntax = Class.new do def beta; end end ShowSourceTestModuleWeirdSyntax = Module.new do def beta; end end end after do Object.remove_const :ShowSourceTestSuperClass Object.remove_const :ShowSourceTestClass Object.remove_const :ShowSourceTestClassWeirdSyntax Object.remove_const :ShowSourceTestSuperModule Object.remove_const :ShowSourceTestModule Object.remove_const :ShowSourceTestModuleWeirdSyntax end describe "basic functionality, should find top-level module definitions" do it 'should show source for a class' do expect(pry_eval('show-source ShowSourceTestClass')) .to match(/class ShowSourceTestClass.*?def alpha/m) end it 'should show source for a super class' do expect(pry_eval('show-source -s ShowSourceTestClass')) .to match(/class ShowSourceTestSuperClass.*?def alpha/m) end it 'should show source for a module' do expect(pry_eval('show-source ShowSourceTestModule')) .to match(/module ShowSourceTestModule/) end it 'should show source for an ancestor module' do expect(pry_eval('show-source -s ShowSourceTestModule')) .to match(/module ShowSourceTestSuperModule/) end it 'should show source for a class when Const = Class.new syntax is used' do expect(pry_eval('show-source ShowSourceTestClassWeirdSyntax')) .to match(/ShowSourceTestClassWeirdSyntax = Class.new/) end it 'should show source for a super class when Const = Class.new syntax is used' do expect(pry_eval('show-source -s ShowSourceTestClassWeirdSyntax')) .to match(/class Object/) end it 'should show source for a module when Const = Module.new syntax is used' do expect(pry_eval('show-source ShowSourceTestModuleWeirdSyntax')) .to match(/ShowSourceTestModuleWeirdSyntax = Module.new/) end end before do pry_eval(unindent(<<-CLASSES)) class Dog def woof end end class TobinaMyDog < Dog def woof end end CLASSES end after do Object.remove_const :Dog Object.remove_const :TobinaMyDog end describe "in REPL" do it 'should find class defined in repl' do expect(pry_eval('show-source TobinaMyDog')).to match(/class TobinaMyDog/) end it 'should find superclass defined in repl' do expect(pry_eval('show-source -s TobinaMyDog')).to match(/class Dog/) end end it 'should lookup module name with respect to current context' do temporary_constants(:AlphaClass, :BetaClass) do class BetaClass def alpha; end end class AlphaClass class BetaClass def beta; end end end expect(pry_eval(AlphaClass, 'show-source BetaClass')).to match(/def beta/) end end it 'should lookup nested modules' do temporary_constants(:AlphaClass) do class AlphaClass class BetaClass def beta; end end end expect(pry_eval('show-source AlphaClass::BetaClass')).to match(/class Beta/) end end # note that pry assumes a class is only monkey-patched at most # ONCE per file, so will not find multiple monkeypatches in the # SAME file. describe "show-source -a" do let(:tempfile) { Tempfile.new(%w[pry .rb]) } context "when there are instance method monkeypatches in different files" do before do define_persistent_class(tempfile, <<-CLASS) class TestClass def alpha; end end CLASS class TestClass def beta; end end end after do Object.remove_const(:TestClass) tempfile.unlink end it "shows the source for all monkeypatches" do result = pry_eval('show-source TestClass -a') expect(result).to match(/def alpha/) expect(result).to match(/def beta/) end end context "when there are class method monkeypatches in different files" do before do define_persistent_class(tempfile, <<-CLASS) class TestClass def self.alpha; end end CLASS class TestClass def self.beta; end end end after do Object.remove_const(:TestClass) tempfile.unlink end it "shows the source for all monkeypatches" do result = pry_eval('show-source TestClass -a') expect(result).to match(/def self.alpha/) expect(result).to match(/def self.beta/) end end context "when there are class-eval monkeypatches in different files" do let(:tempfile) { Tempfile.new(%w[pry .rb]) } before do define_persistent_class(tempfile, <<-CLASS) class TestClass def self.alpha; end end CLASS TestClass.class_eval do def class_eval_method :bing end end end after do Object.remove_const(:TestClass) tempfile.unlink end it "shows the source for all monkeypatches" do result = pry_eval('show-source TestClass -a') expect(result).to match(/def class_eval_method/) end it "ignores -a because object is not a module" do result = pry_eval('show-source TestClass#class_eval_method -a') expect(result).to match(/bing/) end end context "when there are instance-eval monkeypatches in different files" do let(:tempfile) { Tempfile.new(%w[pry .rb]) } before do define_persistent_class(tempfile, <<-CLASS) class TestClass def self.alpha; end end CLASS TestClass.instance_eval do def instance_eval_method :bing end end end after do Object.remove_const(:TestClass) tempfile.unlink end it "shows the source for all monkeypatches" do result = pry_eval('show-source TestClass -a') expect(result).to match(/def instance_eval_method/) end end context "when -a is not used and there are multiple monkeypatches" do let(:tempfile) { Tempfile.new(%w[pry .rb]) } before do define_persistent_class(tempfile, <<-CLASS) class TestClass def self.alpha; end end CLASS class TestClass def beta; end end end after do Object.remove_const(:TestClass) tempfile.unlink end it "mentions available monkeypatches" do result = pry_eval('show-source TestClass') expect(result).to match(/available monkeypatches/) end end context "when -a is not used and there's only one candidate for the class" do before do # alpha class TestClass def alpha; end end end after do Object.remove_const(:TestClass) end it "doesn't mention anything about monkeypatches" do result = pry_eval('show-source TestClass') expect(result).not_to match(/available monkeypatches/) end end end describe "when show-source is invoked without a method or class argument" do before do module TestHost class M def alpha; end def beta; end end module C end module D def self.invoked_in_method pry_eval(binding, 'show-source') end end end end after do Object.remove_const(:TestHost) end describe "inside a module" do it 'should display module source by default' do out = pry_eval(TestHost::M, 'show-source') expect(out).to match(/class M/) expect(out).to match(/def alpha/) expect(out).to match(/def beta/) end it 'should be unable to find module source if no methods defined' do expect { pry_eval(TestHost::C, 'show-source') } .to raise_error(Pry::CommandError, /Couldn't locate/) end it( 'displays method code (rather than class) if Pry started inside ' \ 'method binding' ) do out = TestHost::D.invoked_in_method expect(out).to match(/invoked_in_method/) expect(out).not_to match(/module D/) end it 'should display class source when inside instance' do out = pry_eval(TestHost::M.new, 'show-source') expect(out).to match(/class M/) expect(out).to match(/def alpha/) expect(out).to match(/def beta/) end it 'should allow options to be passed' do out = pry_eval(TestHost::M, 'show-source -b') expect(out).to match(/\d:\s*class M/) expect(out).to match(/\d:\s*def alpha/) expect(out).to match(/\d:\s*def beta/) end describe "should skip over broken modules" do before do module BabyDuck module Muesli binding.eval("def a; end", "dummy.rb", 1) binding.eval("def b; end", "dummy.rb", 2) binding.eval("def c; end", "dummy.rb", 3) end module Muesli def d; end def e; end end end end after do Object.remove_const(:BabyDuck) end it 'should return source for first valid module' do out = pry_eval('show-source BabyDuck::Muesli') expect(out).to match(/def d; end/) expect(out).not_to match(/def a; end/) end end describe "monkey-patched C modules" do # Monkey-patch Array and add 15 methods, so its internal rank is # high enough to make this definition primary. class Array 15.times do |i| define_method(:"doge#{i}") do :"doge#{i}" end end end describe "when current context is a C object" do it "should display a warning, and not monkey-patched definition" do out = pry_eval([1, 2, 3], 'show-source') expect(out).not_to match(/doge/) expect(out).to match(/Pry cannot display the information/) end it "recommends to use the --all switch when other candidates are found" do out = pry_eval([], 'show-source') expect(out).to match(/'--all' switch/i) end end describe "when current context is something other than a C object" do it "should display a candidate, not a warning" do out = pry_eval('show-source Array') expect(out).to match(/doge/) expect(out).not_to match(/warning/i) end end end end end end describe "on commands" do let(:default_commands) { Pry.config.commands } let(:command_set) do Pry::CommandSet.new { import Pry::Commands } end before { Pry.config.commands = command_set } after { Pry.config.commands = default_commands } describe "block commands" do it 'should show source for an ordinary command' do command_set.command('foo', :body_of_foo) {} expect(pry_eval(binding, 'show-source foo')).to match(/:body_of_foo/) end it "should output source of commands using special characters" do command_set.command('!%$', 'I gots the yellow fever') {} expect(pry_eval(binding, 'show-source !%$')).to match(/yellow fever/) end it 'should show source for a command with spaces in its name' do command_set.command('foo bar', :body_of_foo_bar) {} expect(pry_eval(binding, 'show-source foo bar')).to match(/:body_of_foo_bar/) end it 'should show source for a command by listing name' do command_set.command(/foo(.*)/, :body_of_foo_bar_regex, listing: "bar") {} expect(pry_eval(binding, 'show-source bar')).to match(/:body_of_foo_bar_regex/) end end describe "create_command commands" do it 'should show source for a command' do command_set.create_command "foo", "babble" do def process :body_of_foo end end expect(pry_eval(binding, 'show-source foo')).to match(/:body_of_foo/) end it 'should show source for a command defined inside pry' do pry_eval %{ pry_instance.commands.create_command "foo", "babble" do def process() :body_of_foo end end } expect(pry_eval(binding, 'show-source foo')).to match(/:body_of_foo/) end end describe "real class-based commands" do before do # rubocop:disable Style/ClassAndModuleChildren class ::TemporaryCommand < Pry::ClassCommand match 'temp-command' def process :body_of_temp end end # rubocop:enable Style/ClassAndModuleChildren Pry.config.commands.add_command(::TemporaryCommand) end after do Object.remove_const(:TemporaryCommand) end it 'should show source for a command' do expect(pry_eval('show-source temp-command')).to match(/:body_of_temp/) end it 'should show source for a command defined inside pry' do pry_eval %{ class ::TemporaryCommandInPry < Pry::ClassCommand match 'temp-command-in-pry' def process() :body_of_temp end end } Pry.config.commands.add_command(::TemporaryCommandInPry) expect(pry_eval('show-source temp-command-in-pry')).to match(/:body_of_temp/) Object.remove_const(:TemporaryCommandInPry) end end end describe "should set _file_ and _dir_" do let(:tempfile) { Tempfile.new(%w[pry .rb]) } before do define_persistent_class(tempfile, <<-CLASS) class TestClass def alpha; end end CLASS end after do Object.remove_const(:TestClass) tempfile.unlink end it 'should set _file_ and _dir_ to file containing method source' do t = pry_tester t.process_command "show-source TestClass#alpha" path = tempfile.path.split('/')[0..-2].join('/') expect(t.pry.last_dir).to match(path) expect(t.pry.last_file).to match(tempfile.path) end end describe "can't find class/module code" do describe "for classes" do before do module Jesus module Pig def lillybing :lillybing end end class Brian; end class Jingle def a :doink end end class Jangle < Jingle; include Pig; end class Bangle < Jangle; end end end after do Object.remove_const(:Jesus) end it 'shows superclass code' do t = pry_tester t.process_command "show-source Jesus::Jangle" expect(t.last_output).to match(/doink/) end it 'ignores included modules' do t = pry_tester t.process_command "show-source Jesus::Jangle" expect(t.last_output).not_to match(/lillybing/) end it 'errors when class has no superclass to show' do t = pry_tester expect { t.process_command "show-source Jesus::Brian" } .to raise_error(Pry::CommandError, /Couldn't locate/) end it 'shows warning when reverting to superclass code' do t = pry_tester t.process_command "show-source Jesus::Jangle" expect(t.last_output).to match( /Warning.*?Cannot find.*?Jesus::Jangle.*Showing.*Jesus::Jingle instead/ ) end it( 'shows nth level superclass code (when no intermediary ' \ 'superclasses have code either)' ) do t = pry_tester t.process_command "show-source Jesus::Bangle" expect(t.last_output).to match(/doink/) end it 'shows correct warning when reverting to nth level superclass' do t = pry_tester t.process_command "show-source Jesus::Bangle" expect(t.last_output).to match( /Warning.*?Cannot find.*?Jesus::Bangle.*Showing.*Jesus::Jingle instead/ ) end end describe "for modules" do before do module Jesus module Alpha def alpha :alpha end end module Zeta; end module Beta include Alpha end module Gamma include Beta end end end after do Object.remove_const(:Jesus) end it 'shows included module code' do t = pry_tester t.process_command "show-source Jesus::Beta" expect(t.last_output).to match(/alpha/) end it 'shows warning when reverting to included module code' do t = pry_tester t.process_command "show-source Jesus::Beta" expect(t.last_output).to match( /Warning.*?Cannot find.*?Jesus::Beta.*Showing.*Jesus::Alpha instead/ ) end it 'errors when module has no included module to show' do t = pry_tester expect { t.process_command "show-source Jesus::Zeta" } .to raise_error(Pry::CommandError, /Couldn't locate/) end it( 'shows nth level included module code (when no intermediary modules ' \ 'have code either)' ) do t = pry_tester t.process_command "show-source Jesus::Gamma" expect(t.last_output).to match(/alpha/) end it 'shows correct warning when reverting to nth level included module' do t = pry_tester t.process_command "show-source Jesus::Gamma" expect(t.last_output).to match( /Warning.*?Cannot find.*?Jesus::Gamma.*Showing.*Jesus::Alpha instead/ ) end end end describe "show-source --doc" do context "when given a class with a doc" do before do # Foo has docs. class Foo def bar; end end end after { Object.remove_const(:Foo) } it "shows documentation for the code object along with source code" do expect(pry_eval(binding, "show-source Foo -d")).to match( /Foo has docs\.\n\s+class Foo/ ) end end context "when given a module with a doc" do before do # TestMod has docs module TestMod def foo; end end end after { Object.remove_const(:TestMod) } it "shows documentation for the code object along with source code" do expect(pry_eval(binding, "show-source TestMod -d")).to match( /TestMod has docs\n\s+module TestMod/ ) end end context "when the Const = Class.new syntax is used" do before do # TestClass has docs TestClass = Class.new do def foo; end end end after { Object.remove_const(:TestClass) } it "shows documentation for the class" do expect(pry_eval(binding, "show-source TestClass -d")).to match( /TestClass has docs\n\s+TestClass = Class.new/ ) end end context "when the Const = Module.new syntax is used" do before do # TestMod has docs TestMod = Module.new do def foo; end end end after { Object.remove_const(:TestMod) } it "shows documentation for the module" do expect(pry_eval(binding, "show-source TestMod -d")).to match( /TestMod has docs\n\s+TestMod = Module.new/ ) end end context "when given a class defined in a REPL session" do after { Object.remove_const(:TobinaMyDog) } it "shows documentation for the class" do t = pry_tester t.eval <<-RUBY # hello tobina class TobinaMyDog def woof end end RUBY expect(t.eval('show-source -d TobinaMyDog')).to match(/hello tobina/) end end context "when the current context is a non-nested class" do before do # top-level beta class BetaClass def alpha; end end class AlphaClass # nested beta class BetaClass def beta; end end end end after do %i[BetaClass AlphaClass].each { |name| Object.remove_const(name) } end it "shows docs for the nested classes" do expect(pry_eval(AlphaClass, "show-source -d BetaClass")) .to match(/nested beta/) end end context "when given a nested class" do before do # top-level beta class BetaClass def alpha; end end class AlphaClass # nested beta class BetaClass def beta; end end end end after do %i[BetaClass AlphaClass].each { |name| Object.remove_const(name) } end it "shows docs for the nested classes" do expect(pry_eval(AlphaClass, "show-source -d AlphaClass::BetaClass")) .to match(/nested beta/) end end context "when given a method with a doc" do before do @obj = Object.new # test doc def @obj.test_method; end end it "finds the method's documentation" do expect(pry_eval(binding, "show-source -d @obj.test_method")) .to match(/test doc/) end end context "when #call is defined on Symbol" do before do class Symbol def call; end end @obj = Object.new # test doc def @obj.test_method; end end after { Symbol.class_eval { undef :call } } it "still finds documentation" do expect(pry_eval(binding, "show-source -d @obj.test_method")) .to match(/test doc/) end end context "when no docs can be found for the given class" do before do class TestClass def test_method; end end end after { Object.remove_const(:TestClass) } it "raises Pry::CommandError" do expect { pry_eval(binding, "show-source -d TestClass") } .to raise_error(Pry::CommandError) end end context "when no docs can be found for the given method" do before do @obj = Object.new def @obj.test_method; end end it "raises Pry::CommandError" do expect { pry_eval(binding, "show-source -d @obj.test_method") } .to raise_error(Pry::CommandError) end end context "when the --line-numbers switch is provided" do before do @obj = Object.new # test doc def @obj.test_method; end end it "outputs a method's docs with line numbers" do expect(pry_eval(binding, "show-source -d --line-numbers @obj.test_method")) .to match(/\d: test doc/) end end context "when the --base-one switch is provided" do before do @obj = Object.new # test doc def @obj.test_method; end end it "outputs a method's docs with line numbering starting at 1" do expect(pry_eval(binding, "show-source -d --base-one @obj.test_method")) .to match(/1: test doc/) end end context "when the current context is a method" do it "outputs the method without needing to use its name" do obj = Object.new # test method def obj.test_method pry_eval(binding, 'show-source -d') end expect(obj.test_method).to match(/test method/) end end context "when given a proc" do it "should show documentation for object" do # this is a documentation _the_proc = proc { puts 'hello world!' } expect(mock_pry(binding, "show-source -d _the_proc")) .to match(/this is a documentation/) end end context "when no class/module arg is given" do before do module TestHost # hello there froggy module M def d; end def e; end end end end after { Object.remove_const(:TestHost) } it "returns the doc for the current module" do expect(pry_eval(TestHost::M, 'show-source -d')) .to match(/hello there froggy/) end end context "when given a 'broken' module" do before do module TestHost # hello module M binding.eval("def a; end", "dummy.rb", 1) binding.eval("def b; end", "dummy.rb", 2) binding.eval("def c; end", "dummy.rb", 3) end # goodbye module M def d; end def e; end end end end after { Object.remove_const(:TestHost) } # FIXME: THis is nto a good spec anyway, because i dont think it # SHOULD skip! it "skips over the module" do output = pry_eval('show-source -d TestHost::M') expect(output).to match(/goodbye/) expect(output).not_to match(/hello/) end end describe "should set _file_ and _dir_" do let(:tempfile) { Tempfile.new(%w[pry .rb]) } before do define_persistent_class(tempfile, <<-CLASS) class TestClass # this is alpha def alpha; end end CLASS end after do Object.remove_const(:TestClass) tempfile.unlink end it "sets _file_ and _dir_ to file containing method source" do t = pry_tester t.process_command "show-source -d TestClass#alpha" path = tempfile.path.split('/')[0..-2].join('/') expect(t.pry.last_dir).to match(path) expect(t.pry.last_file).to match(tempfile.path) end end context "when provided a class without docs that has a superclass with docs" do before do # parent class class Parent def foo; end end class Child < Parent; end end after do %i[Child Parent].each { |name| Object.remove_const(name) } end it "shows the docs of the superclass" do expect(pry_eval(binding, 'show-source -d Child')).to match(/parent class/) end it "shows a warning about superclass reversion" do expect(pry_eval(binding, 'show-source -d Child')).to match( /Warning.*?Cannot find.*?Child.*Showing.*Parent instead/ ) end end context "when provided a class without docs that has nth superclass with docs" do before do # grandparent class class Grandparent def foo; end end class Parent < Grandparent; end class Child < Parent; end end after do %i[Grandparent Child Parent].each { |name| Object.remove_const(name) } end it "shows the docs of the superclass" do expect(pry_eval(binding, 'show-source -d Child')) .to match(/grandparent class/) end it "shows a warning about superclass reversion" do expect(pry_eval(binding, 'show-source -d Child')).to match( /Warning.*?Cannot find.*?Child.*Showing.*Grandparent instead/ ) end end context "when provided a class without docs that has a superclass without docs" do before do class Parent def foo; end end class Child < Parent; end end after do %i[Child Parent].each { |name| Object.remove_const(name) } end it "raises Pry::CommandError" do expect { pry_eval(binding, 'show-source -d Child') } .to raise_error(Pry::CommandError) end end context "when the module with docs was included in another module" do before do # mod module doc module Alpha def foo; end end module Beta include Alpha end end after do %i[Beta Alpha].each { |name| Object.remove_const(name) } end it "shows the included module's doc" do expect(pry_eval(binding, 'show-source -d Beta')) .to match(/mod module doc/) end it "shows a warning about the included module reversion" do expect(pry_eval(binding, 'show-source -d Beta')).to match( /Warning.*?Cannot find.*?Beta.*Showing.*Alpha instead/ ) end end context "when both the base mod and the included module have no docs" do before do module Alpha def foo; end end module Beta include Alpha end end after do %i[Beta Alpha].each { |name| Object.remove_const(name) } end it "raises Pry::CommandError" do expect { pry_eval(binding, 'show-source -d Beta') } .to raise_error(Pry::CommandError) end end context "when included module has docs and there are intermediary docless modules" do before do # alpha doc module Alpha def alpha; end end module Beta include Alpha end module Gamma include Beta end end after do %i[Gamma Beta Alpha].each { |name| Object.remove_const(name) } end it "shows nth level included module doc" do expect(pry_eval(binding, 'show-source -d Gamma')).to match(/alpha doc/) end it "shows a warning about module reversion" do expect(pry_eval(binding, 'show-source -d Gamma')).to match( /Warning.*?Cannot find.*?Gamma.*Showing.*Alpha instead/ ) end end context "when the --super switch is provided" do before do class Grandparent # grandparent init def initialize; end end class Parent < Grandparent # parent init def initialize; end end class Child < Parent # child init def initialize; end end @obj = Child.new # instance init def @obj.initialize; end end after do %i[Grandparent Parent Child].each { |name| Object.remove_const(name) } end context "and when it's passed once" do it "finds the super method docs" do expect(pry_eval(binding, 'show-source -d --super @obj.initialize')) .to match(/child init/) end end context "and when it's passed twice" do it "finds the parent method docs" do expect(pry_eval(binding, 'show-source -d -ss @obj.initialize')) .to match(/parent init/) end end context "and when it's passed thrice" do it "finds the grandparent method docs" do expect(pry_eval(binding, 'show-source -d -sss @obj.initialize')) .to match(/parent init/) end end context "and when the super method doesn't exist" do it "raises Pry::CommandError" do expect { pry_eval(binding, 'show-source -d -ssss @obj.initialize') } .to raise_error(Pry::CommandError) end end context "and when the explicit argument is not provided" do let(:son) { Child.new } it "finds super method docs without explicit method argument" do # son init def son.initialize pry_eval(binding, 'show-source -d --super') end expect(son.initialize).to match(/child init/) end it "finds super method docs with multiple `--super` switches" do son.extend( Module.new do def initialize; end end ) # son init def son.initialize pry_eval(binding, 'show-source -d --super --super') end expect(son.initialize).to match(/child init/) end end end describe "code highlighting" do context "when there's code in the docs" do let(:klass) do Class.new do # This can initialize your class: # # a = klass.new :foo # # @param foo def initialize(foo); end end end it "highlights the code" do expect(pry_eval(binding, 'show-source -d klass#initialize')) .to match(/klass.new :foo/) # We don't want the test to rely on which colour codes are there, so # we just assert that something is being colorized. expect( pry_eval( binding, 'pry_instance.color = true', "show-source -d klass#initialize" ) ).not_to match(/klass.new :foo/) end end context "when there's inline code in the docs" do let(:klass) do Class.new do # After initializing your class with `klass.new(:inline)`, go have # fun! # # @param foo def initialize(foo); end end end it "highlights the code" do expect(pry_eval(binding, 'show-source -d klass#initialize')) .to match(/klass.new\(:inline\)/) # We don't want the test to rely on which colour codes are there, so # we just assert that something is being colorized. expect( pry_eval( binding, 'pry_instance.color = true', "show-source -d klass#initialize" ) ).not_to match(/klass.new\(:inline\)/) end end context "when there's inline code with backticks the docs" do let(:klass) do Class.new do # Convert aligned output (from many shell commands) into nested arrays: # # a = decolumnize `ls -l $HOME` # # @param output def decolumnize(output); end end end it "doesn't highlight the backticks" do output = pry_eval( binding, 'pry_instance.color = true', "show-source -d klass#decolumnize" ) expect(output).to match(/ls -l \$HOME/) expect(output).not_to match(/`ls -l \$HOME`/) end end end describe "the --all switch behavior" do let(:tempfile) { Tempfile.new(%w[pry .rb]) } context "when there are monkeypatches in different files" do before do define_persistent_class(tempfile, <<-CLASS) # file monkeypatch class TestClass def alpha; end end CLASS # local monkeypatch class TestClass def beta; end end end after do Object.remove_const(:TestClass) tempfile.unlink end it "shows them" do result = pry_eval(binding, 'show-source -d TestClass -a') expect(result).to match(/file monkeypatch/) expect(result).to match(/local monkeypatch/) end end context "when --all is not used but there are multiple monkeypatches" do before do define_persistent_class(tempfile, <<-CLASS) # alpha class TestClass def alpha; end end CLASS class TestClass def beta; end end end after do Object.remove_const(:TestClass) tempfile.unlink end it "correctly displays the number of monkeypatches" do result = pry_eval(binding, 'show-source -d TestClass') expect(result).to match(/Number of monkeypatches: 2/) end it "displays the original definition first" do result = pry_eval(binding, 'show-source -d TestClass') expect(result).to match(/alpha/) end it "mentions available monkeypatches" do result = pry_eval(binding, 'show-source -d TestClass') expect(result).to match(/available monkeypatches/) end end context "when --all is not used and there's only 1 candidate for the class" do before do # alpha class TestClass def alpha; end end end after { Object.remove_const(:TestClass) } it "doesn't mention anything about monkeypatches" do result = pry_eval(binding, 'show-source -d TestClass') expect(result).not_to match(/available monkeypatches/) end end end context "when used against a command" do let(:default_commands) { Pry.config.commands } let(:command_set) do Pry::CommandSet.new { import Pry::Commands } end before { Pry.config.commands = command_set } after { Pry.config.commands = default_commands } it "displays help for a specific command" do expect(pry_eval(binding, 'show-source -d ls')).to match(/Usage: ls/) end it "displays help for a regex command with a \"listing\"" do command_set.command(/bar(.*)/, 'Test listing', listing: 'foo') {} expect(pry_eval(binding, 'show-source -d foo')).to match(/Test listing/) end it "displays help for a command with a spaces in its name" do command_set.command('command with spaces', 'command with spaces desc') {} expect(pry_eval(binding, 'show-source -d command with spaces')).to match( /command with spaces desc/ ) end describe "class commands" do before do # pretty pink pincers class LobsterLady < Pry::ClassCommand match 'lobster-lady' description 'nada.' def process 'lobster' end end command_set.add_command(LobsterLady) end after { Object.remove_const(:LobsterLady) } context "when looking up by command name" do it "displays help" do expect(pry_eval('show-source -d lobster-lady')).to match(/nada/) end end context "when class is used (rather than command name) is used for lookup" do it "displays actual preceding comment for a class command" do expect(pry_eval('show-source -d LobsterLady')).to match(/pretty pink pincers/) end end end end end end