-
-
Notifications
You must be signed in to change notification settings - Fork 10
Add analyzer for high scores exercise #113
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| two_fer | ||
| acronym | ||
| leap | ||
| high-scores | ||
| ].freeze | ||
|
|
||
| FILENAMES = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,39 @@ def results | |
| } | ||
| end | ||
|
|
||
| def approve_if_whitespace_is_sensible(msg = nil, params = {}) | ||
| 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 = {}) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, as mentioned, is the second place the |
||
| 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 | ||
|
|
||
|
|
||
| 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 |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
|
@@ -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 | ||
|
|
||
| # ### | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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. | ||
|
|
@@ -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 | ||
|
|
@@ -154,38 +153,9 @@ def check_for_names!; end | |
| def approve_if_implicit_return!(msg = nil, params = {}) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
kotp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| end | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
msgseems 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.