@@ -4,9 +4,11 @@ module TwoFer
44 no_module : "ruby.general.no_target_module" ,
55 no_method : "ruby.general.no_target_method" ,
66 incorrect_indentation : "ruby.general.incorrect_indentation" ,
7+ explicit_return : "ruby.general.explicit_return" , #"The last line automatically gets returned"
78 splat_args : "ruby.two_fer.splat_args" , #Rather than using *%s, how about actually setting a parameter called 'name'?",
89 missing_default_param : "ruby.two_fer.missing_default_param" , #"There is no correct default param - the tests will fail",
910 incorrect_default_param : "ruby.two_fer.incorrect_default_param" , #You could set the default value to 'you' to avoid conditionals",
11+ reassigning_param : "ruby.two_fer.reassigning_param" , # You don't need to reassign - use the default param
1012 string_building : "ruby.two_fer.avoid_string_building" , # "Rather than using string building, use interpolation",
1113 kernel_format : "ruby.two_fer.avoid_kernel_format" , #"Rather than using the format method, use interpolation",
1214 string_format : "ruby.two_fer.avoid_string_format" , #"Rather than using string's format/percentage method, use interpolation"
@@ -16,6 +18,8 @@ class Analyze < ExerciseAnalyzer
1618 include Mandate
1719
1820 def analyze!
21+ #target_method.pry
22+
1923 # Note that all "check_...!" methods exit this method if the solution
2024 # is approved or disapproved, so each step is only called if the
2125 # previous one has not resolved what to do.
@@ -46,6 +50,10 @@ def analyze!
4650 # default parameter if we see one.
4751 check_for_conditional_on_default_argument!
4852
53+ # Sometimes, rather than setting a variable, people reassign the input param e.g.
54+ # use name ||= "you"
55+ check_for_reassigned_parameter!
56+
4957 # Sometimes people specify the names (if name == "Alice" ...). If we
5058 # do this, suggest using string interpolation to make us of the
5159 # parameter, rather than using a conditional on it.
@@ -90,13 +98,33 @@ def check_for_optimal_solution!
9098 # statement using interpolation. Other solutions might be approved
9199 # but this is the only one that we would approve without comment.
92100
101+
93102 return unless default_argument_is_optimal?
94103 return unless one_line_solution?
95- return unless using_string_interpolation?
96104
97- # If the interpolation has more than three components, then they've
105+ if target_method . body . return_type?
106+ loc = target_method . body . children . first
107+ explicit_return = true
108+ elsif target_method . body . dstr_type?
109+ loc = target_method . body
110+ explicit_return = false
111+ else
112+ # We're only expecting one of these two scenarios
113+ return
114+ end
115+
116+ # Return unless we have string interpolation
117+ return unless loc . dstr_type?
118+
119+ # If the interpolation does not follow this pattern then the student has
98120 # done something weird, so let's get a mentor to look at it!
99- refer_to_mentor! unless string_interpolation_has_three_components?
121+ refer_to_mentor! unless loc . children [ 0 ] == s ( :str , "One for " ) &&
122+ loc . children [ 1 ] == s ( :begin , s ( :lvar , first_parameter_name ) ) &&
123+ loc . children [ 2 ] == s ( :str , ", one for me." )
124+
125+ # If we're got a correct solution but they've given an explicit
126+ # return then let's warn them against that.
127+ disapprove! ( :explicit_return ) if explicit_return
100128
101129 approve_if_whitespace_is_sensible!
102130 end
@@ -109,6 +137,9 @@ def check_for_correct_solution_without_string_interpolaton!
109137
110138 loc = SA ::Helpers . extract_first_line_from_method ( target_method )
111139
140+ # Protect against explicit return
141+ #loc = loc.children.first if loc.return_type?
142+
112143 # In the case of:
113144 # "One for " + name + ", one for me."
114145 if loc . method_name == :+ &&
@@ -139,28 +170,65 @@ def check_for_correct_solution_without_string_interpolaton!
139170 end
140171
141172 def check_for_conditional_on_default_argument!
142- loc = SA ::Helpers . extract_first_line_from_method ( target_method )
173+ stmts = SA ::Helpers . extract_nodes ( :if , target_method )
143174
144- # If we don't have a conditional, then let's get out of here.
145- #
146- # TODO: We might want to refactor this to extract a conditional from the
147- # method rather than insist on it being the first line.
148- return unless loc . type == :if
175+ # If there are no statements then we can safely get out of here.
176+ return if stmts . empty?
177+
178+ # If there is more than one statement, then let's refer this to a mentor
179+ refer_to_mentor! if stmts . size > 1
149180
150- # Get the clause of the conditional (i.e. the bit after the "if" keyword)
151- conditional = SA ::Helpers . extract_conditional_clause ( loc )
181+ # Now we have the statement, let's also get the conditional
182+ # clause (i.e. the bit after the "if" keyword)
183+ if_statement = stmts . first
184+ conditional = SA ::Helpers . extract_conditional_clause ( if_statement )
185+
186+ if SA ::Helpers . lvar? ( conditional , first_parameter_name )
187+ # Let's warn about using a better default if they do `if name`
188+ disapprove! ( :incorrect_default_param )
189+
190+ elsif conditional . send_type? &&
191+ SA ::Helpers . lvar? ( conditional . receiver , first_parameter_name )
192+ conditional . first_argument == :nil?
193+ # Let's warn about using a better default if they do `if name.nil?`
194+ disapprove! ( :incorrect_default_param )
152195
153196 # Let's warn about using a better default if they `if name == nil`
154- if SA ::Helpers . lvar? ( conditional . receiver , :name ) &&
197+ elsif SA ::Helpers . lvar? ( conditional . receiver , first_parameter_name ) &&
155198 conditional . first_argument == default_argument
156199 disapprove! ( :incorrect_default_param )
157- end
158200
159201 # Same thing but if they do it the other way round, i.e. `if nil == name`
160- if conditional . receiver == default_argument &&
161- SA :: Helpers . lvar? ( conditional . first_argument , :name )
202+ elsif SA :: Helpers . lvar? ( conditional . first_argument , first_parameter_name ) &&
203+ conditional . receiver == default_argument
162204 disapprove! ( :incorrect_default_param )
163205 end
206+
207+ # If we have an if without that does not do an expected comparison,
208+ # let's refer this to a mentor and get out of here!
209+ refer_to_mentor!
210+ end
211+
212+ def check_for_reassigned_parameter!
213+ assignments = SA ::Helpers . extract_nodes ( :or_asgn , target_method )
214+
215+ # If there are no statements then we can safely get out of here.
216+ return if assignments . empty?
217+
218+ # If there is more than one statement, then let's refer this to a mentor
219+ refer_to_mentor! if assignments . size > 1
220+
221+ # Now we what is being reassigned is the param and it's being rassigned to "you"
222+ # let's warn the user
223+ assignment = assignments . first
224+ if assignment . children [ 0 ] == s ( :lvasgn , first_parameter_name ) &&
225+ assignment . children [ 1 ] == s ( :str , "you" )
226+ disapprove! ( :reassigning_param )
227+ end
228+
229+ # If we have a reassignment that doesn't conform to this
230+ # let's refer this to a mentor and get out of here!
231+ refer_to_mentor!
164232 end
165233
166234 # ###
@@ -174,15 +242,6 @@ def one_line_solution?
174242 target_method . body . line_count == 1
175243 end
176244
177- def using_string_interpolation?
178- target_method . body . dstr_type?
179- end
180-
181- def string_interpolation_has_three_components?
182- #target_method.body.pry
183- target_method . body . children . size == 3
184- end
185-
186245 # REFACTOR: This could be refactored to strip blank
187246 # lines and then use each_cons(2).
188247 def indentation_is_sensible?
0 commit comments