Class: Peeky::MethodInfo

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/peeky/method_info.rb

Overview

Method info store a list of instance methods and attr_* for a ruby class.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(method, target_instance, implementation_type: :method, access_control: :public) ⇒ MethodInfo

Returns a new instance of MethodInfo.



32
33
34
35
36
37
38
39
40
41
# File 'lib/peeky/method_info.rb', line 32

def initialize(method, target_instance, implementation_type: :method, access_control: :public)
  @focal_method = method
  @target_instance = target_instance
  @access_control = access_control
  @implementation_type = implementation_type
  @parameters = ParameterInfo.from_method(method)

  infer_implementation_type
  infer_default_paramaters
end

Instance Attribute Details

#access_controlObject (readonly)

Returns the value of attribute access_control.



17
18
19
# File 'lib/peeky/method_info.rb', line 17

def access_control
  @access_control
end

#focal_methodObject (readonly)

MethodInfo delegates to the underlying ruby method object



15
16
17
# File 'lib/peeky/method_info.rb', line 15

def focal_method
  @focal_method
end

#implementation_typeObject (readonly)

Implementation type indicates the probable representation of this method in ruby, was it instance method ‘def method` instance method reader `attr_reader` instance method writer `attr_writer` class method `def self.method`



30
31
32
# File 'lib/peeky/method_info.rb', line 30

def implementation_type
  @implementation_type
end

#parametersObject

List of parameters for this method



12
13
14
# File 'lib/peeky/method_info.rb', line 12

def parameters
  @parameters
end

Instance Method Details

#clean_nameObject

Name of method minus writer annotations Example

:writable_attribute=
# becomes
:writable_attribute


51
52
53
54
55
56
# File 'lib/peeky/method_info.rb', line 51

def clean_name
  @clean_name ||= begin
    n = name.to_s
    n.end_with?('=') ? n.delete_suffix('=').to_sym : name
  end
end

#get_parameter(name) ⇒ Object

Get parameter by name

Parameters:

  • name (String)

    name (required)



69
70
71
72
# File 'lib/peeky/method_info.rb', line 69

def get_parameter(name)
  name = name.to_s
  parameters.find { |p| p.name == name }
end

#grab_source(limit = 10) ⇒ Object



170
171
172
173
# File 'lib/peeky/method_info.rb', line 170

def grab_source(limit = 10)
  file, line = @focal_method.source_location
  File.read(file).lines[line - 1, limit] if file && line
end

#infer_default_paramatersObject

Infer default paramater values

WARNING: Unit test coverage went from .1 seconds to 30-40 seconds when I first introduced this method.

I now only call TracePoint if I have optional parameters to be inferred.

The tests are now down to 5 seconds, but it highlights the cost of use TracePoint.



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/peeky/method_info.rb', line 90

def infer_default_paramaters
  class_name = @implementation_type == :class_method ? @target_instance.class.name : nil
  minimalist_method = Peeky::Renderer::MethodCallMinimumParamsRender.new(self, class_name: class_name).render

  return if minimalist_method.end_with?('=')
  return unless optional?

  # TODO: maybe I can use this technique instead and just read the source code
  #       file, line = @focal_method.source_location

  tracer.enable do
    # puts grab_source
    # TODO: minimalist method should be able to handle class methods
    @target_instance.instance_eval(minimalist_method)       if @implementation_type == :method
    @target_instance.class.instance_eval(minimalist_method) if @implementation_type == :class_method
  rescue StandardError, LoadError # => e
    # just print the error for now, we are only attempting to capture the
    # first call, any errors inside the call cannot be dealt with and should
    # not be re-raised
    # red full stop
    print "\e[31m.\e[0m"
    # puts minimalist_method
    # puts e.message
  end
end

#infer_implementation_typeObject

Infer implementation type [:class_method, :method, :attr_reader or :attr_writer]



59
60
61
62
63
64
# File 'lib/peeky/method_info.rb', line 59

def infer_implementation_type
  return unless @implementation_type == :method

  @implementation_type = :attr_reader if match(Peeky::Predicates::AttrReaderPredicate)
  @implementation_type = :attr_writer if match(Peeky::Predicates::AttrWriterPredicate)
end

#match(predicate) ⇒ Object

Match

Parameters:

  • predicate (String)

    use a predicate object with the signature match(instance, method_info)



135
136
137
# File 'lib/peeky/method_info.rb', line 135

def match(predicate)
  predicate.new.match(@target_instance, self)
end

#method?Boolean

Returns true when implementation type is method?.

Returns:

  • (Boolean)

    true when implementation type is method?



142
143
144
# File 'lib/peeky/method_info.rb', line 142

def method?
  @implementation_type == :method
end

#optional?Boolean

Returns true when any parameter is optional?.

Returns:

  • (Boolean)

    true when any parameter is optional?



77
78
79
# File 'lib/peeky/method_info.rb', line 77

def optional?
  parameters.any?(&:optional?)
end

#readable?Boolean

Readable?

Returns:

  • (Boolean)

    true when readable?



149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/peeky/method_info.rb', line 149

def readable?
  # Method naming issue: VSCode Ruby Language Server
  #
  # If this method is renamed to attr_readable, same for attr_writable.
  #
  # https://github.com/rubyide/vscode-ruby/issues/454
  # if I prefix these methods with attr_ then will get an issue
  # in the language server.
  #
  # Cannot read property 'namedChildren' of undefined

  @implementation_type == :attr_reader
end

#tracerObject



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/peeky/method_info.rb', line 116

def tracer
  TracePoint.trace(:call, :c_call) do |tp|
    next unless tp.self.is_a?(@target_instance.class)
    next unless tp.method_id == name

    tp.parameters.each do |_type, param_name|
      method_paramater = get_parameter(param_name)

      if method_paramater.optional?
        value = tp.binding.local_variable_get(param_name)
        method_paramater.default_value = value
      end
    end
  end
end

#writable?Boolean

Returns true when implementation_type writable?.

Returns:

  • (Boolean)

    true when implementation_type writable?



166
167
168
# File 'lib/peeky/method_info.rb', line 166

def writable?
  @implementation_type == :attr_writer
end