Class: MethodSequenceChecker

Inherits:
Object
  • Object
show all
Defined in:
lib/pippi/checks/method_sequence_checker.rb

Constant Summary collapse

ARITY_TYPE_BLOCK_ARG =
1
ARITY_TYPE_NONE =
2

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(check, clazz_to_decorate, method1, method2, first_method_arity_type, second_method_arity_type, should_check_subsequent_calls) ⇒ MethodSequenceChecker

Returns a new instance of MethodSequenceChecker.

[View source]

8
9
10
11
12
13
14
15
16
# File 'lib/pippi/checks/method_sequence_checker.rb', line 8

def initialize(check, clazz_to_decorate, method1, method2, first_method_arity_type, second_method_arity_type, should_check_subsequent_calls)
  @check = check
  @clazz_to_decorate = clazz_to_decorate
  @method1 = method1
  @method2 = method2
  @first_method_arity_type = first_method_arity_type
  @second_method_arity_type = second_method_arity_type
  @should_check_subsequent_calls = should_check_subsequent_calls
end

Instance Attribute Details

#checkObject (readonly)

Returns the value of attribute check.


6
7
8
# File 'lib/pippi/checks/method_sequence_checker.rb', line 6

def check
  @check
end

#clazz_to_decorateObject (readonly)

Returns the value of attribute clazz_to_decorate.


6
7
8
# File 'lib/pippi/checks/method_sequence_checker.rb', line 6

def clazz_to_decorate
  @clazz_to_decorate
end

#first_method_arity_typeObject (readonly)

Returns the value of attribute first_method_arity_type.


6
7
8
# File 'lib/pippi/checks/method_sequence_checker.rb', line 6

def first_method_arity_type
  @first_method_arity_type
end

#method1Object (readonly)

Returns the value of attribute method1.


6
7
8
# File 'lib/pippi/checks/method_sequence_checker.rb', line 6

def method1
  @method1
end

#method2Object (readonly)

Returns the value of attribute method2.


6
7
8
# File 'lib/pippi/checks/method_sequence_checker.rb', line 6

def method2
  @method2
end

#second_method_arity_typeObject (readonly)

Returns the value of attribute second_method_arity_type.


6
7
8
# File 'lib/pippi/checks/method_sequence_checker.rb', line 6

def second_method_arity_type
  @second_method_arity_type
end

#should_check_subsequent_callsObject (readonly)

Returns the value of attribute should_check_subsequent_calls.


6
7
8
# File 'lib/pippi/checks/method_sequence_checker.rb', line 6

def should_check_subsequent_calls
  @should_check_subsequent_calls
end

Instance Method Details

#array_mutator_methodsObject

[View source]

72
73
74
# File 'lib/pippi/checks/method_sequence_checker.rb', line 72

def array_mutator_methods
  [:collect!, :compact!, :flatten!, :map!, :reject!, :reverse!, :rotate!, :select!, :shuffle!, :slice!, :sort!, :sort_by!, :uniq!]
end

#decorateObject

[View source]

18
19
20
21
22
23
24
25
26
27
28
29
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/pippi/checks/method_sequence_checker.rb', line 18

def decorate
  clazz_to_decorate.class_exec(check, self) do |my_check, method_sequence_check_instance|
    name = "@_pippi_check_#{my_check.class.name.split('::').last.downcase}"
    self.instance_variable_set(name, my_check)
    self.class.send(:define_method, name[1..-1]) do
      instance_variable_get(name)
    end

    # e.g., "size" in "select followed by size"
    second_method_decorator = if method_sequence_check_instance.second_method_arity_type.kind_of?(Module)
      method_sequence_check_instance.second_method_arity_type
    else
      Module.new do
        define_method(method_sequence_check_instance.method2) do |*args, &blk|
          # Using "self.class" implies that the first method invocation returns the same type as the receiver
          # e.g., Array#select returns an Array.  Would need to further parameterize this to get
          # different behavior.
          self.class.instance_variable_get(name).add_problem
          if method_sequence_check_instance.should_check_subsequent_calls && method_sequence_check_instance.clazz_to_decorate == Array
            problem_location = caller_locations.find { |c| c.to_s !~ /byebug|lib\/pippi\/checks/ }
            self.class.instance_variable_get(name).method_names_that_indicate_this_is_being_used_as_a_collection.each do |this_means_its_ok_sym|
              define_singleton_method(this_means_its_ok_sym, self.class.instance_variable_get(name).clear_fault_proc(self.class.instance_variable_get(name), problem_location))
            end
          end
          if method_sequence_check_instance.second_method_arity_type == ARITY_TYPE_BLOCK_ARG
            super(&blk)
          elsif method_sequence_check_instance.second_method_arity_type == ARITY_TYPE_NONE
            super()
          end
        end
      end
    end

    # e.g., "select" in "select followed by size"
   first_method_decorator = Module.new do
      define_method(method_sequence_check_instance.method1) do |*args, &blk|
        result = if method_sequence_check_instance.first_method_arity_type == ARITY_TYPE_BLOCK_ARG
          super(&blk)
        elsif method_sequence_check_instance.first_method_arity_type == ARITY_TYPE_NONE
          super()
        end
        if self.class.instance_variable_get(name)
          result.extend second_method_decorator
          self.class.instance_variable_get(name).array_mutator_methods.each do |this_means_its_ok_sym|
            result.define_singleton_method(this_means_its_ok_sym, self.class.instance_variable_get(name).its_ok_watcher_proc(second_method_decorator, method_sequence_check_instance.method2))
          end
        end
        result
      end
    end
    prepend first_method_decorator
  end
end

#its_ok_watcher_proc(clazz, method_name) ⇒ Object

[View source]

76
77
78
79
80
81
82
83
84
85
86
# File 'lib/pippi/checks/method_sequence_checker.rb', line 76

def its_ok_watcher_proc(clazz, method_name)
  proc do |*args, &blk|
    begin
      singleton_class.ancestors.find { |x| x == clazz }.instance_eval { remove_method method_name }
    rescue NameError
      return super(*args, &blk)
    else
      return super(*args, &blk)
    end
  end
end