diff options
author | Reginald Tan <redge.tan@gmail.com> | 2012-05-02 22:29:17 -0400 |
---|---|---|
committer | Reginald Tan <redge.tan@gmail.com> | 2012-05-23 20:42:16 -0400 |
commit | 21553e3dda02ec44d859f402ee72ca00deed0f26 (patch) | |
tree | 755d95544b1a24afb3628a85e41ca2a7a1d99b60 | |
parent | 7bd1f9b748df78dbc2e7021fa337c1e712068491 (diff) | |
download | method_source-21553e3dda02ec44d859f402ee72ca00deed0f26.tar.gz |
If displaying method source fails for *_evaled methods, retry again.
This time, assume inside eval string and simulate interpolation of #{}
by replacing with a placeholder when doing syntax validation
Only works if *_eval contains the arguments filename and lineno + 1. Without them,
UnboundMethod#source_location would not return the proper filename and lineno needed
to display the source.
-rw-r--r-- | lib/method_source.rb | 46 | ||||
-rw-r--r-- | test/test.rb | 15 | ||||
-rw-r--r-- | test/test_helper.rb | 48 |
3 files changed, 98 insertions, 11 deletions
diff --git a/lib/method_source.rb b/lib/method_source.rb index 9a3c325..75bbb8e 100644 --- a/lib/method_source.rb +++ b/lib/method_source.rb @@ -31,22 +31,48 @@ module MethodSource # Helper method responsible for extracting method body. # Defined here to avoid polluting `Method` class. # @param [Array] source_location The array returned by Method#source_location - # @return [File] The opened source file + # @return [String] The method body def self.source_helper(source_location) return nil if !source_location.is_a?(Array) - file_name, line = source_location - File.open(file_name) do |file| - (line - 1).times { file.readline } + # 1st try: simple eval + code = extract_code(source_location) + + unless code + # 2nd try: attempt to re-scan method body, this time, assume we're inside an eval string simulate interpolation of #{} expressions by replacing it with placeholder + # + # A temporary work around for cases where method body is defined inside a + # string (i.e. class_evaled methods), and the resulting valid_expression + # doesn't return true due to string not being interpolated. + # (see https://github.com/banister/method_source/issues/13) + # + code = extract_code(source_location) { |code| code.gsub(/\#{.*?}/,"temp") } + end - code = "" - loop do - val = file.readline - code << val + code + rescue Errno::ENOENT + # source_location[0] of evaled methods would return (eval) if __FILE__ and __LINE__ is not given. File.readlines "(eval)" would raise ENOENT + nil + end - return code if valid_expression?(code) - end + # @param [Array] source_location The array containing file_name [String], line [Fixnum] + # @param [Block] An optional block that can be passed that will be used to modify + # the code buffer before its syntax is evaluated + # @return [String] The method body + def self.extract_code(source_location) + file_name, line = source_location + code = "" + lines_for_file(file_name)[(line - 1)..-1].each do |line| + code << line + expression = block_given? ? yield(code) : code + return code if valid_expression?(expression) end + nil + end + + def self.lines_for_file(file_name) + @lines_for_file ||= {} + @lines_for_file[file_name] ||= File.readlines(file_name) end # Helper method responsible for opening source file and buffering up diff --git a/test/test.rb b/test/test.rb index 425e56a..7e598ef 100644 --- a/test/test.rb +++ b/test/test.rb @@ -33,6 +33,10 @@ describe MethodSource do @lambda_comment = "# This is a comment for MyLambda\n" @lambda_source = "MyLambda = lambda { :lambda }\n" @proc_source = "MyProc = Proc.new { :proc }\n" + @hello_instance_evaled_source = " def hello_\#{name}(*args)\n send_mesg(:\#{name}, *args)\n end\n" + @hello_instance_evaled_source_2 = " def \#{name}_two()\n if 44\n 45\n end\n end\n" + @hello_class_evaled_source = " def hello_\#{name}(*args)\n send_mesg(:\#{name}, *args)\n end\n" + @hi_module_evaled_source = " def hi_\#{name}\n @var = \#{name}\n end\n" end it 'should define methods on Method and UnboundMethod and Proc' do @@ -58,11 +62,20 @@ describe MethodSource do $o.method(:hello).source.should == @hello_singleton_source end - it 'should return a comment for method' do method(:hello).comment.should == @hello_comment end + it 'should return source for an *_evaled method' do + M.method(:hello_name).source.should == @hello_instance_evaled_source + M.method(:name_two).source.should == @hello_instance_evaled_source_2 + M.instance_method(:hello_name).source.should == @hello_class_evaled_source + M.instance_method(:hi_name).source.should == @hi_module_evaled_source + end + + it "should raise error for evaled methods that do not pass __FILE__ and __LINE__ + 1 as its arguments" do + lambda { M.instance_method(:name_three).source }.should.raise RuntimeError + end if !is_rbx? it 'should raise for C methods' do diff --git a/test/test_helper.rb b/test/test_helper.rb index 53da4e5..3aabdf1 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -48,3 +48,51 @@ def comment_test5; end MyLambda = lambda { :lambda } MyProc = Proc.new { :proc } + +name = "name" + +M.instance_eval <<-METHOD, __FILE__, __LINE__ + 1 + def hello_#{name}(*args) + send_mesg(:#{name}, *args) + end +METHOD + +M.class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def hello_#{name}(*args) + send_mesg(:#{name}, *args) + end +METHOD + +# module_eval to DRY code up +# +M.module_eval <<-METHOD, __FILE__, __LINE__ + 1 + + # module_eval is used here + # + def hi_#{name} + @var = #{name} + end +METHOD + +# case where 2 methods are defined inside an _eval block +# +M.instance_eval <<EOF, __FILE__, __LINE__ + 1 + + def #{name}_one() + if 43 + 44 + end + end + + + def #{name}_two() + if 44 + 45 + end + end +EOF + +# class_eval without filename and lineno + 1 parameter + +M.class_eval "def #{name}_three; @tempfile.#{name}; end" + |