Skip to content

Commit eef24b1

Browse files
authored
Merge pull request #6 from exercism/helpers
Extract sa methods into helpers
2 parents 2150d32 + 35f9612 commit eef24b1

12 files changed

+306
-182
lines changed

Gemfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ gem 'activesupport'
1313
gem 'parser'
1414
gem 'rubocop'
1515

16-
1716
group :test do
1817
gem 'minitest', '~> 5.10', '!= 5.10.2'
1918
gem 'minitest-stub-const'

lib/analyzer.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
require 'parser/current'
1313
require 'active_support/inflector'
1414

15+
require_relative "generic/helpers"
1516
require_relative "generic/extract_class_method"
1617
require_relative "generic/extract_instance_method"
1718
require_relative "generic/extract_module_or_class"

lib/analyzers/two_fer/analyze.rb

Lines changed: 25 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ def analyze!
6161
def check_structure!
6262
# First we check that there is a two-fer class or module
6363
# and that it contains a method called two-fer
64-
disapprove!(:no_module) unless two_fer_module
65-
disapprove!(:no_method) unless main_method
64+
disapprove!(:no_module) unless target_module
65+
disapprove!(:no_method) unless target_method
6666
end
6767

6868
def check_method_signature!
@@ -71,11 +71,11 @@ def check_method_signature!
7171
disapprove!(:missing_default_param) if parameters.size != 1
7272

7373
# If they provide a splat, the tests can pass but we
74-
# should suggest they use a real paramater
75-
disapprove!(:splat_args, first_paramater_name) if first_paramater.restarg_type?
74+
# should suggest they use a real parameter
75+
disapprove!(:splat_args, first_parameter_name) if first_parameter.restarg_type?
7676

7777
# If they don't provide an optional argument the tests will fail
78-
disapprove!(:missing_default_param) unless first_paramater.optarg_type?
78+
disapprove!(:missing_default_param) unless first_parameter.optarg_type?
7979
end
8080

8181
def check_for_optimal_solution!
@@ -106,7 +106,7 @@ def check_for_correct_solution_without_string_interpolaton!
106106
return unless default_argument_is_optimal?
107107
return unless one_line_solution?
108108

109-
loc = first_line_in_method(main_method)
109+
loc = SA::Helpers.extract_first_line_from_method(target_method)
110110

111111
# In the case of:
112112
# "One for " + name + ", one for me."
@@ -138,7 +138,7 @@ def check_for_correct_solution_without_string_interpolaton!
138138
end
139139

140140
def check_for_conditional_on_default_argument!
141-
loc = first_line_in_method(main_method)
141+
loc = SA::Helpers.extract_first_line_from_method(target_method)
142142

143143
# If we don't have a conditional, then let's get out of here.
144144
#
@@ -147,17 +147,17 @@ def check_for_conditional_on_default_argument!
147147
return unless loc.type == :if
148148

149149
# Get the clause of the conditional (i.e. the bit after the "if" keyword)
150-
conditional = extract_conditional_clause(loc)
150+
conditional = SA::Helpers.extract_conditional_clause(loc)
151151

152152
# Let's warn about using a better default if they `if name == nil`
153-
if is_lvar?(conditional.receiver, :name) &&
153+
if SA::Helpers.lvar?(conditional.receiver, :name) &&
154154
conditional.first_argument == default_argument
155155
disapprove!(:incorrect_default_param)
156156
end
157157

158158
# Same thing but if they do it the other way round, i.e. `if nil == name`
159159
if conditional.receiver == default_argument &&
160-
is_lvar?(conditional.first_argument, :name)
160+
SA::Helpers.lvar?(conditional.first_argument, :name)
161161
disapprove!(:incorrect_default_param)
162162
end
163163
end
@@ -170,70 +170,44 @@ def default_argument_is_optimal?
170170
end
171171

172172
def one_line_solution?
173-
main_method.body.line_count == 1
173+
target_method.body.line_count == 1
174174
end
175175

176176
def using_string_interpolation?
177-
main_method.body.dstr_type?
177+
target_method.body.dstr_type?
178178
end
179179

180180
def string_interpolation_has_three_components?
181-
#main_method.body.pry
182-
main_method.body.children.size == 3
183-
end
184-
185-
# ###
186-
# Static analysis helpers
187-
# ###
188-
def num_lines_in_method(method)
189-
method.body.child_nodes.size
190-
end
191-
192-
def first_line_in_method(method)
193-
# A begin block signifies multiple lines
194-
# so we return the first line.
195-
method.body.children.first if main_method.body.type == :begin
196-
197-
# Without a begin block we just have one line,
198-
# so we return the method body, which *is* the first line
199-
method.body
200-
end
201-
202-
# Is this an lvar (local variable) with a given name?
203-
def is_lvar?(node, name)
204-
node.lvar_type? && node.children[0] == name
205-
end
206-
207-
def extract_conditional_clause(loc)
208-
loc.children[0]
181+
#target_method.body.pry
182+
target_method.body.children.size == 3
209183
end
210184

211185
memoize
212-
def two_fer_module
213-
ExtractModuleOrClass.(root_node, "TwoFer")
186+
def target_module
187+
SA::Helpers.extract_module_or_class(root_node, "TwoFer")
214188
end
215189

216190
memoize
217-
def main_method
218-
ExtractClassMethod.(two_fer_module, "two_fer")
191+
def target_method
192+
SA::Helpers.extract_class_method(target_module, "two_fer")
219193
end
220194

221195
memoize
222-
def paramaters
223-
main_method.arguments
196+
def parameters
197+
target_method.arguments
224198
end
225199

226200
memoize
227-
def first_paramater
228-
paramaters.first
201+
def first_parameter
202+
parameters.first
229203
end
230204

231-
def first_paramater_name
232-
first_paramater.children[0]
205+
def first_parameter_name
206+
first_parameter.children[0]
233207
end
234208

235209
def default_argument
236-
first_paramater.children[1]
210+
first_parameter.children[1]
237211
end
238212

239213
def default_argument_value

lib/generic/extract_class_method.rb

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1-
class ExtractClassMethod < Parser::AST::Processor
2-
include Mandate
1+
module SA
2+
class ExtractClassMethod < Parser::AST::Processor
3+
include Mandate
34

4-
def initialize(node_to_search, name)
5-
@node_to_search = node_to_search
6-
@name = name.to_sym
7-
@found_method = nil
8-
end
5+
def initialize(node_to_search, name)
6+
@node_to_search = node_to_search
7+
@name = name.to_sym
8+
@found_method = nil
9+
end
910

10-
def call
11-
process(node_to_search)
12-
found_method
13-
end
11+
def call
12+
process(node_to_search)
13+
found_method
14+
end
1415

15-
def on_sclass(node)
16-
# If we're in a class << self context
17-
if node.children[0].self_type?
18-
@found_method = ExtractInstanceMethod.(node.children[1], name)
16+
def on_sclass(node)
17+
# If we're in a class << self context
18+
if node.children[0].self_type?
19+
@found_method = ExtractInstanceMethod.(node.children[1], name)
20+
end
1921
end
20-
end
2122

22-
def on_defs(node)
23-
if node.method_name == name
24-
@found_method = node
23+
def on_defs(node)
24+
if node.method_name == name
25+
@found_method = node
26+
end
2527
end
26-
end
2728

28-
private
29-
attr_reader :node_to_search, :name, :found_method
29+
private
30+
attr_reader :node_to_search, :name, :found_method
31+
end
3032
end
Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1-
class ExtractInstanceMethod < Parser::AST::Processor
2-
include Mandate
1+
module SA
2+
class ExtractInstanceMethod < Parser::AST::Processor
3+
include Mandate
34

4-
def initialize(node_to_search, name)
5-
@node_to_search = node_to_search
6-
@name = name.to_sym
7-
@found_method = nil
8-
end
5+
def initialize(node_to_search, name)
6+
@node_to_search = node_to_search
7+
@name = name.to_sym
8+
@found_method = nil
9+
end
910

10-
def call
11-
process(node_to_search)
12-
found_method
13-
end
11+
def call
12+
process(node_to_search)
13+
found_method
14+
end
1415

15-
def on_def(node)
16-
if node.method_name == name
17-
@found_method = node
16+
def on_def(node)
17+
if node.method_name == name
18+
@found_method = node
19+
end
1820
end
19-
end
2021

21-
private
22-
attr_reader :node_to_search, :name, :found_method
22+
private
23+
attr_reader :node_to_search, :name, :found_method
24+
end
2325
end

lib/generic/extract_module_or_class.rb

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
1-
class ExtractModuleOrClass < Parser::AST::Processor
2-
include Mandate
1+
module SA
2+
class ExtractModuleOrClass < Parser::AST::Processor
3+
include Mandate
34

4-
def initialize(node_to_search, name)
5-
@node_to_search = node_to_search
6-
@name = name
7-
@found_constant = nil
8-
end
5+
def initialize(node_to_search, name)
6+
@node_to_search = node_to_search
7+
@name = name
8+
@found_constant = nil
9+
end
910

10-
def call
11-
process(node_to_search)
12-
found_constant
13-
end
11+
def call
12+
process(node_to_search)
13+
found_constant
14+
end
1415

15-
def on_module(node)
16-
inspect_node(node)
17-
end
16+
def on_module(node)
17+
inspect_node(node)
18+
end
1819

19-
def on_class(node)
20-
inspect_node(node)
21-
end
20+
def on_class(node)
21+
inspect_node(node)
22+
end
2223

23-
private
24-
attr_reader :node_to_search, :name, :found_constant
24+
private
25+
attr_reader :node_to_search, :name, :found_constant
2526

26-
def inspect_node(node)
27-
if node.children.first.const_name == name
28-
@found_constant = node
27+
def inspect_node(node)
28+
if node.children.first.const_name == name
29+
@found_constant = node
30+
end
2931
end
3032
end
3133
end
32-

lib/generic/helpers.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module SA
2+
module Helpers
3+
def self.extract_module_or_class(*args)
4+
ExtractModuleOrClass.(*args)
5+
end
6+
7+
def self.extract_class_method(*args)
8+
ExtractClassMethod.(*args)
9+
end
10+
11+
def self.extract_first_line_from_method(method)
12+
# A begin block signifies multiple lines
13+
# so we return the first line.
14+
return method.body.children.first if method.body.type == :begin
15+
16+
# Without a begin block we just have one line,
17+
# so we return the method body, which *is* the first line
18+
return method.body
19+
end
20+
21+
def self.num_lines_in_method(method)
22+
method.body.child_nodes.size
23+
end
24+
25+
# Is this an lvar (local variable) with a given name?
26+
def self.lvar?(node, name)
27+
node.lvar_type? && node.children[0] == name
28+
end
29+
30+
def self.extract_conditional_clause(loc)
31+
loc.children[0]
32+
end
33+
end
34+
end

0 commit comments

Comments
 (0)