Skip to content
Merged
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
1 change: 1 addition & 0 deletions lib/analyzer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
two_fer
acronym
leap
high-scores
].freeze

FILENAMES = {
Expand Down
33 changes: 33 additions & 0 deletions lib/analyzers/exercise_analyzer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,39 @@ def results
}
end

def approve_if_whitespace_is_sensible(msg = nil, params = {})
Copy link
Member

Choose a reason for hiding this comment

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

The msg seems to be the comment, when I am testing this. Later we use {comment: msg, params:} and if the names are the same this can be reduced to {comment:, params:}.

This happens in two places in this code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I feel that this is outside of the scope of this PR since this is not technically new code. I was just moving it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would like to do another PR to clean up some things up, so I could make this change then if you like.

Copy link
Member

Choose a reason for hiding this comment

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

In some ways this is an "Add analyzer" so making it exemplar now provides a good template for new things coming in. But of course, it does not matter in the grand scheme of things.

if solution.indentation_is_sensible?
self.comments << if params.any?
{comment: msg, params:}
else
msg
end
self.comments.compact!
self.status = :approve

raise FinishedFlowControlException
else
disapprove("ruby.general.incorrect_indentation")
end
end

def disapprove(msg, params = {})
Copy link
Member

Choose a reason for hiding this comment

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

Here, as mentioned, is the second place the msg happens when it is likely comment.

self.status = :disapprove
self.comments << if params.any?
{comment: msg, params:}
else
msg
end

raise FinishedFlowControlException
end

def refer_to_mentor
self.status = :refer_to_mentor

raise FinishedFlowControlException
end

private
attr_reader :solution

Expand Down
15 changes: 15 additions & 0 deletions lib/analyzers/high-scores/analyze.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module HighScores
class Analyze < ExerciseAnalyzer
include Mandate

def analyze!
if solution.uses_accepted_solution?
approve_if_whitespace_is_sensible
elsif solution.needs_attr_reader?
approve_if_whitespace_is_sensible("ruby.high-scores.attr_reader")
else
refer_to_mentor
end
end
end
end
59 changes: 59 additions & 0 deletions lib/analyzers/high-scores/representation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module HighScores
class Representation < SolutionRepresentation
APPROVED_METHODS = {
personal_best: ["scores.max"],
latest: ["scores.last"],
personal_top_three: ["scores.max(3)", "scores.max 3"],
latest_is_personal_best?: ["latest == personal_best"]
}.freeze

def uses_accepted_solution?
has_correct_number_of_methods? && implements_approved_methods? &&
implements_attr_reader? && implements_initialize?
end

def needs_attr_reader?
has_correct_number_of_methods? && implements_approved_methods? && implements_initialize? && implements_scores?
end

private
memoize
def target_class_body
SA::Helpers.extract_module_or_class(root_node, "HighScores").children.compact.find { |node| node.type == :begin }
end

memoize
def method_definitions
target_class_body.children.select { |node| node.type == :def }
end

def has_correct_number_of_methods?
target_class_body.children.count == 6
end

memoize
def implements_approved_methods?
APPROVED_METHODS.all? do |method_name, method_bodies|
method_node = method_definitions.find { |node| node.method_name == method_name }
method_node && method_bodies.include?(method_node.body.source)
end
end

memoize
def implements_initialize?
initialize_node = method_definitions.find { |node| node.method_name == :initialize }
source = ["def initialize(scores)", "@scores = scores"]
initialize_node && initialize_node.source.lines[0..1].map(&:strip) == source
end

def implements_attr_reader?
attr_reader_node = target_class_body.children.find { |node| node.type == :send }
attr_reader_node && attr_reader_node.source == "attr_reader :scores"
end

def implements_scores?
scores_node = method_definitions.find { |node| node.method_name == :scores }
scores_node && scores_node.body.source == "@scores"
end
end
end
62 changes: 16 additions & 46 deletions lib/analyzers/two_fer/analyze.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module TwoFer
MESSAGES = {
no_module: "ruby.general.no_target_module",
no_method: "ruby.general.no_target_method",
incorrect_indentation: "ruby.general.incorrect_indentation",
explicit_return: "ruby.general.explicit_return", # "The last line automatically gets returned"
splat_args: "ruby.two-fer.splat_args", # "Rather than using *%s, how about actually setting a parameter called 'name'?"
missing_default_param: "ruby.two-fer.missing_default_param", # "There is no correct default param - the tests will fail"
Expand Down Expand Up @@ -32,7 +31,7 @@ def analyze!

# We don't have any idea about this solution, so let's refer it to a
# mentor and get exit our analysis.
refer_to_mentor!
refer_to_mentor
end

# ###
Expand All @@ -43,16 +42,16 @@ def analyze!
# solution is correct and that there is nothing structural
# stopping it from passing the tests
def check_structure!
disapprove!(:no_module) unless solution.has_target_module?
disapprove!(:no_method) unless solution.has_target_method?
disapprove(MESSAGES[:no_module]) unless solution.has_target_module?
disapprove(MESSAGES[:no_method]) unless solution.has_target_method?
end

# Then we we want to ensure that the method signature
# is sane and that it has valid arguments
def check_method_signature!
disapprove!(:missing_default_param) unless solution.has_one_parameter?
disapprove!(:splat_args, {name_variable: solution.first_parameter_name}) if solution.uses_splat_args?
disapprove!(:missing_default_param) unless solution.first_paramater_has_default_value?
disapprove(MESSAGES[:missing_default_param]) unless solution.has_one_parameter?
disapprove(MESSAGES[:splat_args], {name_variable: solution.first_parameter_name}) if solution.uses_splat_args?
disapprove(MESSAGES[:missing_default_param]) unless solution.first_paramater_has_default_value?
end

# There is one optimal solution for two-fer which needs
Expand All @@ -79,7 +78,7 @@ def check_for_single_line_solution!
if solution.string_interpolation_is_correct?
approve_if_implicit_return!(:string_interpolation, {name_variable: solution.first_parameter_name})
else
refer_to_mentor!
refer_to_mentor
end
end

Expand All @@ -100,7 +99,7 @@ def check_for_single_line_solution!

# If we have a one-line method that passes the tests, then it's not
# something we've planned for, so let's refer it to a mentor
refer_to_mentor!
refer_to_mentor
end

# The most common error in twofer is people using conditionals
Expand All @@ -111,15 +110,15 @@ def check_for_conditional_on_default_argument!
return unless solution.has_any_if_statements?

# If there is more than one statement, then let's refer this to a mentor
refer_to_mentor! unless solution.has_single_if_statement?
refer_to_mentor unless solution.has_single_if_statement?

# If the person checks the default paramter, then we can always tell them
# just to set this to a more sensible value (ie "you")
disapprove!(:incorrect_default_param) if solution.uses_default_param_in_if_statement?
disapprove(MESSAGES[:incorrect_default_param]) if solution.uses_default_param_in_if_statement?

# If we have an if without that does not do an expected comparison,
# let's refer this to a mentor and get out of here!
refer_to_mentor!
refer_to_mentor
end

# Sometimes, rather than setting a variable, people reassign the input param e.g.
Expand All @@ -128,15 +127,15 @@ def check_for_reassigned_parameter!
return unless solution.reassigns_parameter?

# If there is more than one statement, then let's refer this to a mentor
refer_to_mentor! if solution.reassigns_parameter_multiple_times?
refer_to_mentor if solution.reassigns_parameter_multiple_times?

# If the solution reassigns the input paramater to "you" then we can warn
# about reassigning the parameter and get out of here
disapprove!(:reassigning_param) if solution.reassigns_parameter_to_you?
disapprove(MESSAGES[:reassigning_param]) if solution.reassigns_parameter_to_you?

# If we have a reassignment that doesn't conform to this
# let's refer this to a mentor and get out of here!
refer_to_mentor!
refer_to_mentor
end

# Sometimes people specify the names (if name == "Alice" ...). If we
Expand All @@ -154,38 +153,9 @@ def check_for_names!; end
def approve_if_implicit_return!(msg = nil, params = {})
Copy link
Member

Choose a reason for hiding this comment

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

msg can be comment.

Copy link
Member

Choose a reason for hiding this comment

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

If the code is moved, then it is ripe for change as well. Same argument (would rather do it here than have a PR that might not come in). If you are good with that.

# If we're got a correct solution but they've given an explicit
# return then let's warn them against that.
disapprove!(:explicit_return) if solution.has_explicit_return?
disapprove(MESSAGES[:explicit_return]) if solution.has_explicit_return?

approve_if_whitespace_is_sensible!(msg, params)
end

def approve_if_whitespace_is_sensible!(msg = nil, params = {})
if solution.indentation_is_sensible?
self.comments << {comment: MESSAGES[msg], params:} if msg
self.status = :approve

raise FinishedFlowControlException

else
disapprove!(:incorrect_indentation)
end
end

def refer_to_mentor!
self.status = :refer_to_mentor

raise FinishedFlowControlException
end

def disapprove!(msg, params = {})
self.status = :disapprove
self.comments << if params.length > 0
{comment: MESSAGES[msg], params:}
else
MESSAGES[msg]
end

raise FinishedFlowControlException
approve_if_whitespace_is_sensible(MESSAGES[msg], params)
end
end
end
Loading