diff --git a/lib/method_source.rb b/lib/method_source.rb index ffd79ad..509ace8 100644 --- a/lib/method_source.rb +++ b/lib/method_source.rb @@ -20,15 +20,19 @@ class SourceNotFoundError < StandardError; end # @param [Array] source_location The array returned by Method#source_location # @param [String] method_name # @return [String] The method body - def self.source_helper(source_location, name=nil) + def self.source_helper(source_location, name=nil, options={}) raise SourceNotFoundError, "Could not locate source for #{name}!" unless source_location file, line = *source_location - expression_at(lines_for(file), line) + expression_at(lines_for(file), line, options) rescue SyntaxError => e raise SourceNotFoundError, "Could not parse source for #{name}: #{e.message}" end + def self.expression_options + {} + end + # Helper method responsible for opening source file and buffering up # the comments for a specified method. Defined here to avoid polluting # `Method` class. @@ -158,6 +162,12 @@ def class_comment end alias module_comment class_comment end + + module ProcExtensions + def source + MethodSource.source_helper(source_location, defined?(name) ? name : inspect, block: true) + end + end end class Method @@ -173,5 +183,6 @@ class UnboundMethod class Proc include MethodSource::SourceLocation::ProcExtensions include MethodSource::MethodExtensions + include MethodSource::ProcExtensions end diff --git a/lib/method_source/code_helpers.rb b/lib/method_source/code_helpers.rb index ccf054a..6b1998d 100644 --- a/lib/method_source/code_helpers.rb +++ b/lib/method_source/code_helpers.rb @@ -25,18 +25,28 @@ def expression_at(file, line_number, options={}) lines = file.is_a?(Array) ? file : file.each_line.to_a - relevant_lines = lines[(line_number - 1)..-1] || [] - - extract_first_expression(relevant_lines, options[:consume]) - rescue SyntaxError => e - raise if options[:strict] - begin - extract_first_expression(relevant_lines) do |code| - code.gsub(/\#\{.*?\}/, "temp") + relevant_lines = lines[(line_number - 1)..-1] || [] + + extract_first_expression(relevant_lines, options[:consume]) + rescue SyntaxError => e + raise if options[:strict] + + # blocks may be part of a multiline method call, + # and aren't valid if you cut off previous lines + if options[:block] && line_number > 1 + # search backwards until it's valid + line_number -= 1 + retry + end + + begin + extract_first_expression(relevant_lines) do |code| + code.gsub(/\#\{.*?\}/, "temp") + end + rescue SyntaxError + raise e end - rescue SyntaxError - raise e end end diff --git a/spec/method_source_spec.rb b/spec/method_source_spec.rb index 1927670..9bac234 100644 --- a/spec/method_source_spec.rb +++ b/spec/method_source_spec.rb @@ -38,6 +38,10 @@ @hello_instance_evaled_source_2 = " def \#{name}_two()\n if 40 + 4\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" + @multiline_method_block_source = <<~RUBY + MultilineMethodBlock = save_block( + ) {} + RUBY end it 'should define methods on Method and UnboundMethod and Proc' do @@ -123,6 +127,10 @@ it 'should return comment for class instance' do expect(C.new.method(:hello).class_comment).to eq(@class_comment) end + + it 'should return source for a block from a multiline method' do + expect(MultilineMethodBlock.source).to eq(@multiline_method_block_source) + end end # end describe "Comment tests" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0cf9739..b2efe4c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -105,3 +105,10 @@ def #{name}_two() M.class_eval "def #{name}_three; @tempfile.#{name}; end" +# multiline method call +def save_block(&block) + block +end + +MultilineMethodBlock = save_block( + ) {}