Class: LetItGo::MethodCall

Inherits:
Object
  • Object
show all
Defined in:
lib/let_it_go/method_call.rb

Overview

Wraps logic that require knowledge of the method call can parse original method call’s source and determine if a string literal was passed into the method.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(klass:, method_name:, kaller:, positions:) ⇒ MethodCall

Returns a new instance of MethodCall.



10
11
12
13
14
15
16
17
18
# File 'lib/let_it_go/method_call.rb', line 10

def initialize(klass: , method_name: , kaller:, positions: )
  @klass        = klass
  @method_name  = method_name.to_s
  # Subclasses report method definition as caller.first via TracePoint
  @key          = "Method: #{klass}##{method_name} [#{kaller.first(2).inspect}]"
  @caller_lines = kaller.first(2).map {|kaller_line| CallerLine.new(kaller_line) }
  @positions    = positions
  @call_count   = 0
end

Instance Attribute Details

#call_countObject

Returns the value of attribute call_count.



8
9
10
# File 'lib/let_it_go/method_call.rb', line 8

def call_count
  @call_count
end

#file_nameObject

Returns the value of attribute file_name.



8
9
10
# File 'lib/let_it_go/method_call.rb', line 8

def file_name
  @file_name
end

#klassObject

Returns the value of attribute klass.



8
9
10
# File 'lib/let_it_go/method_call.rb', line 8

def klass
  @klass
end

#line_numberObject

Returns the value of attribute line_number.



8
9
10
# File 'lib/let_it_go/method_call.rb', line 8

def line_number
  @line_number
end

#method_nameObject

Returns the value of attribute method_name.



8
9
10
# File 'lib/let_it_go/method_call.rb', line 8

def method_name
  @method_name
end

#positionsObject

Returns the value of attribute positions.



8
9
10
# File 'lib/let_it_go/method_call.rb', line 8

def positions
  @positions
end

Instance Method Details

#called_with_string_literal?Boolean

Parses original method call location Determines if a string literal was used or not

Returns:

  • (Boolean)


72
73
74
75
76
77
78
# File 'lib/let_it_go/method_call.rb', line 72

def called_with_string_literal?
  @string_allocation_count = 0
  method_array.each do |m|
    positions.each {|position| @string_allocation_count += 1 if m.arg_types[position] == :string_literal }
  end
  !@string_allocation_count.zero?
end

#countObject



20
21
22
# File 'lib/let_it_go/method_call.rb', line 20

def count
  call_count * string_allocation_count
end

#keyObject

Needs to be very low cost, cannot incur disk read



81
82
83
# File 'lib/let_it_go/method_call.rb', line 81

def key
  @key
end

#line_to_sObject



58
59
60
# File 'lib/let_it_go/method_call.rb', line 58

def line_to_s
  @line_to_s ||= contents_from_file_line(file_name, line_number)
end

#method_arrayObject

Loop through each line in the caller and see if the method we’re watching is being called This is needed due to the way TracePoint deals with inheritance



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/let_it_go/method_call.rb', line 30

def method_array
  @parser = nil
  @caller_lines.each do |kaller|
    code   = Ripper.sexp(kaller.contents)
    code  ||= Ripper.sexp(kaller.contents.sub(/^\W*(if|unless)/, ''.freeze)) # if and unless "block" statements aren't valid one line ruby code
    code  ||= Ripper.sexp(kaller.contents.sub(/do \|.*\|$/, ''.freeze)) # remove trailing do |thing| to make valid code
    code  ||= Ripper.sexp(kaller.contents.sub(/(and|or)\W*$/, ''.freeze))# trailing and || or
    code  ||= Ripper.sexp(kaller.contents.sub(/:\W*$/, ''.freeze)) # multi line ternary statements
    code  ||= Ripper.sexp(kaller.contents.sub(/(^\W*)|({ \|?.*\|?)}/, ''.freeze)) # multi line blocks using {}

    puts "LetItGoFailed parse (#{kaller.file_name}:#{kaller.line_number}: \n  \033[0;31m"+ kaller.contents.strip + "\e[0m".freeze if ENV['LET_IT_GO_RECORD_FAILED_CODE'] && code.nil? && kaller.contents.match(/"|'/)

    parser = ::LetItGo::WTFParser.new(code, contents: kaller.contents)

    if parser.each_method.any? { |m| m.method_name == method_name }
      @line_number = kaller.line_number
      @file_name   = kaller.file_name

      @parser = parser
      parser.each_method.each(&:arg_types)
      break
    else
      next
    end
  end
  @parser || []
end

#optimizable?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/let_it_go/method_call.rb', line 62

def optimizable?
  @optimizable ||= called_with_string_literal?
end

#string_allocation_countObject



66
67
68
# File 'lib/let_it_go/method_call.rb', line 66

def string_allocation_count
  @string_allocation_count
end

#zero?Boolean

Returns:

  • (Boolean)


24
25
26
# File 'lib/let_it_go/method_call.rb', line 24

def zero?
  count.zero?
end