@@ -4,9 +4,11 @@ module TwoFer
4
4
no_module : "ruby.general.no_target_module" ,
5
5
no_method : "ruby.general.no_target_method" ,
6
6
incorrect_indentation : "ruby.general.incorrect_indentation" ,
7
+ explicit_return : "ruby.general.explicit_return" , #"The last line automatically gets returned"
7
8
splat_args : "ruby.two_fer.splat_args" , #Rather than using *%s, how about actually setting a parameter called 'name'?",
8
9
missing_default_param : "ruby.two_fer.missing_default_param" , #"There is no correct default param - the tests will fail",
9
10
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
10
12
string_building : "ruby.two_fer.avoid_string_building" , # "Rather than using string building, use interpolation",
11
13
kernel_format : "ruby.two_fer.avoid_kernel_format" , #"Rather than using the format method, use interpolation",
12
14
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
16
18
include Mandate
17
19
18
20
def analyze!
21
+ #target_method.pry
22
+
19
23
# Note that all "check_...!" methods exit this method if the solution
20
24
# is approved or disapproved, so each step is only called if the
21
25
# previous one has not resolved what to do.
@@ -46,6 +50,10 @@ def analyze!
46
50
# default parameter if we see one.
47
51
check_for_conditional_on_default_argument!
48
52
53
+ # Sometimes, rather than setting a variable, people reassign the input param e.g.
54
+ # use name ||= "you"
55
+ check_for_reassigned_parameter!
56
+
49
57
# Sometimes people specify the names (if name == "Alice" ...). If we
50
58
# do this, suggest using string interpolation to make us of the
51
59
# parameter, rather than using a conditional on it.
@@ -90,13 +98,33 @@ def check_for_optimal_solution!
90
98
# statement using interpolation. Other solutions might be approved
91
99
# but this is the only one that we would approve without comment.
92
100
101
+
93
102
return unless default_argument_is_optimal?
94
103
return unless one_line_solution?
95
- return unless using_string_interpolation?
96
104
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
98
120
# 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
100
128
101
129
approve_if_whitespace_is_sensible!
102
130
end
@@ -109,6 +137,9 @@ def check_for_correct_solution_without_string_interpolaton!
109
137
110
138
loc = SA ::Helpers . extract_first_line_from_method ( target_method )
111
139
140
+ # Protect against explicit return
141
+ #loc = loc.children.first if loc.return_type?
142
+
112
143
# In the case of:
113
144
# "One for " + name + ", one for me."
114
145
if loc . method_name == :+ &&
@@ -139,28 +170,65 @@ def check_for_correct_solution_without_string_interpolaton!
139
170
end
140
171
141
172
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 )
143
174
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
149
180
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 )
152
195
153
196
# 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 ) &&
155
198
conditional . first_argument == default_argument
156
199
disapprove! ( :incorrect_default_param )
157
- end
158
200
159
201
# 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
162
204
disapprove! ( :incorrect_default_param )
163
205
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!
164
232
end
165
233
166
234
# ###
@@ -174,15 +242,6 @@ def one_line_solution?
174
242
target_method . body . line_count == 1
175
243
end
176
244
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
-
186
245
# REFACTOR: This could be refactored to strip blank
187
246
# lines and then use each_cons(2).
188
247
def indentation_is_sensible?
0 commit comments