Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] add more YARD docs #36

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/pippi/checks/check.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
module Pippi::Checks
# A Check is responsible for decorating the source code and detecting problems for reporting
# Duck typed on #decorate
class Check
attr_accessor :ctx

# @param [Pippi::Context] ctx the context object for this Pippi run
def initialize(ctx)
@ctx = ctx
end
Expand Down
9 changes: 8 additions & 1 deletion lib/pippi/checks/check_descriptor.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
# Data class used by {Pippi::Checks::Check}
class CheckDescriptor

attr_accessor :check, :clazz_to_decorate, :method_sequence, :should_check_subsequent_calls

# @param [Pippi::Checks::Check] check_object the check associated with this
# CheckDescriptor
# @param [Class] clazz_to_decorate the class who owns the method sequence being
# watched
# @param [Pippi::Checks::MethodSequence] method_sequence the method sequence to
# watch for
def initialize(check_object, clazz_to_decorate, method_sequence)
@clazz_to_decorate = clazz_to_decorate
@method_sequence = method_sequence
@check = check_object
@should_check_subsequent_calls = true
end

end
end
1 change: 1 addition & 0 deletions lib/pippi/checks/debug_check.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Pippi::Checks
# Used for debugging events and return values
class DebugCheck < Check
%w(line class end call return c_call c_return b_call b_return raise thread_begin thread_end).each do |event_name|
define_method "#{event_name}_event" do |tp|
Expand Down
2 changes: 2 additions & 0 deletions lib/pippi/checks/method_sequence.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Struct for describing a sequence of method calls which a
# {Pippi::Checks::MethodSequenceChecker} can watch for
class MethodSequence

attr_accessor :method1, :method2, :decorator
Expand Down
13 changes: 13 additions & 0 deletions lib/pippi/checks/method_sequence_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def initialize(check_descriptor)
end

def decorate
# Decorate watched Class with an accessor to the check instance
check_descriptor.clazz_to_decorate.class_exec(check_descriptor.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)
Expand All @@ -18,14 +19,21 @@ def decorate
second_method_decorator = if method_sequence_check_instance.check_descriptor.method_sequence.decorator
method_sequence_check_instance.check_descriptor.method_sequence.decorator
else
# Create a new module which will be used to intercept method calls via Object#extend
Module.new do
descriptor = method_sequence_check_instance.check_descriptor.method_sequence.method2
# Define a new method which will be used to intercept & decorate the original method call
# Delegate to super after calling decorator
define_method(descriptor) 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.
#
# TODO jbodah - We could use a closure here instead of an instance
# variable which might solve the issue noted above
the_check = self.class.instance_variable_get(name)
the_check.add_problem
# TODO - @tcopeland could you explain this block?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tcopeland would you mind explaining what's going on here?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey sorry for the delay in replying @jbodah.

The problem this is trying to solve is illustrated in this sequence of method calls:

x = foo.select {|y| y > 2 }
z = x.first
x.each {|q| puts q }

Normally pippi would flag that code since select is called and then first is called on the result. But in this case, the result is also used as a collection later - that is, we call each on it. So the code can't be simplified down into a single call to detect.

Pippi's logic is more or less to add a Problem, and then remove it if needed. There might be a nicer way to do that... like, adding it as a provisional problem and not actually adding it to the report until we confirm it.

if method_sequence_check_instance.check_descriptor.should_check_subsequent_calls && method_sequence_check_instance.check_descriptor.clazz_to_decorate == self.class
problem_location = caller_locations.find { |c| c.to_s !~ /byebug|lib\/pippi\/checks/ }
the_check.method_names_that_indicate_this_is_being_used_as_a_collection.each do |this_means_its_ok_sym|
Expand All @@ -38,12 +46,17 @@ def decorate
end

# e.g., "select" in "select followed by size"
#
# Create a new module which will be used to intercept method calls via Module#prepend
first_method_decorator = Module.new do
descriptor = method_sequence_check_instance.check_descriptor.method_sequence.method1
# Intercept and decorate the original method call
# Dynamically add the second decorator to the return value via Object#extend
define_method(descriptor) do |*args, &blk|
result = super(*args, &blk)
if self.class.instance_variable_get(name)
result.extend second_method_decorator
# TODO - @tcopeland could you explain this block?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tcopeland would you mind explaining what's going on here?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbodah this one is to handle this case:

x = foo.select {|y| y > 2 }
x.sort!
z = x.first

That is, in this case there's a mutator method that's called between select and first, and thus that method sequence can't be replaced with detect.

self.class.instance_variable_get(name).mutator_methods(result.class).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.check_descriptor.method_sequence.method2))
end
Expand Down