reek 1.2.7.1 → 1.2.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/History.txt +13 -1
  2. data/config/defaults.reek +4 -1
  3. data/features/masking_smells.feature +7 -7
  4. data/features/rake_task.feature +2 -2
  5. data/features/reports.feature +3 -3
  6. data/features/samples.feature +5 -2
  7. data/features/yaml.feature +0 -39
  8. data/lib/reek.rb +1 -1
  9. data/lib/reek/cli/command_line.rb +3 -3
  10. data/lib/reek/cli/reek_command.rb +5 -6
  11. data/lib/reek/cli/report.rb +9 -20
  12. data/lib/reek/cli/yaml_command.rb +1 -1
  13. data/lib/reek/core/class_context.rb +1 -2
  14. data/lib/reek/core/code_context.rb +10 -27
  15. data/lib/reek/core/code_parser.rb +1 -18
  16. data/lib/reek/core/detector_stack.rb +4 -0
  17. data/lib/reek/core/masking_collection.rb +6 -0
  18. data/lib/reek/core/method_context.rb +8 -56
  19. data/lib/reek/core/module_context.rb +6 -32
  20. data/lib/reek/core/object_refs.rb +36 -36
  21. data/lib/reek/core/singleton_method_context.rb +10 -21
  22. data/lib/reek/core/sniffer.rb +3 -2
  23. data/lib/reek/examiner.rb +39 -31
  24. data/lib/reek/smell_warning.rb +8 -0
  25. data/lib/reek/smells/attribute.rb +4 -2
  26. data/lib/reek/smells/class_variable.rb +3 -3
  27. data/lib/reek/smells/control_couple.rb +1 -2
  28. data/lib/reek/smells/data_clump.rb +86 -9
  29. data/lib/reek/smells/duplication.rb +2 -3
  30. data/lib/reek/smells/feature_envy.rb +9 -4
  31. data/lib/reek/smells/simulated_polymorphism.rb +1 -2
  32. data/lib/reek/smells/smell_detector.rb +0 -6
  33. data/lib/reek/smells/uncommunicative_method_name.rb +8 -2
  34. data/lib/reek/smells/uncommunicative_parameter_name.rb +1 -1
  35. data/lib/reek/smells/uncommunicative_variable_name.rb +1 -1
  36. data/lib/reek/smells/utility_function.rb +17 -5
  37. data/lib/reek/source/reference_collector.rb +21 -0
  38. data/lib/reek/source/sexp_formatter.rb +1 -0
  39. data/lib/reek/source/tree_dresser.rb +67 -9
  40. data/lib/reek/spec/should_reek.rb +1 -1
  41. data/lib/reek/spec/should_reek_of.rb +1 -1
  42. data/lib/reek/spec/should_reek_only_of.rb +1 -1
  43. data/reek.gemspec +3 -3
  44. data/spec/reek/cli/reek_command_spec.rb +3 -2
  45. data/spec/reek/cli/report_spec.rb +2 -2
  46. data/spec/reek/cli/yaml_command_spec.rb +2 -2
  47. data/spec/reek/core/code_context_spec.rb +39 -54
  48. data/spec/reek/core/method_context_spec.rb +7 -26
  49. data/spec/reek/core/module_context_spec.rb +0 -15
  50. data/spec/reek/core/singleton_method_context_spec.rb +0 -6
  51. data/spec/reek/examiner_spec.rb +6 -6
  52. data/spec/reek/smells/attribute_spec.rb +30 -32
  53. data/spec/reek/smells/class_variable_spec.rb +15 -18
  54. data/spec/reek/smells/data_clump_spec.rb +22 -6
  55. data/spec/reek/smells/duplication_spec.rb +33 -19
  56. data/spec/reek/smells/feature_envy_spec.rb +82 -88
  57. data/spec/reek/smells/large_class_spec.rb +1 -1
  58. data/spec/reek/smells/smell_detector_shared.rb +1 -1
  59. data/spec/reek/smells/uncommunicative_method_name_spec.rb +37 -35
  60. data/spec/reek/smells/utility_function_spec.rb +7 -0
  61. data/spec/reek/source/reference_collector_spec.rb +53 -0
  62. data/spec/reek/source/tree_dresser_spec.rb +10 -0
  63. data/spec/reek/spec/should_reek_only_of_spec.rb +1 -1
  64. data/spec/spec_helper.rb +7 -0
  65. metadata +4 -5
  66. data/features/profile.feature +0 -34
  67. data/lib/reek/core/block_context.rb +0 -18
  68. data/spec/reek/core/block_context_spec.rb +0 -26
@@ -44,6 +44,10 @@ module Reek
44
44
  #
45
45
  attr_reader :smell
46
46
 
47
+ def smell_class() @smell[CLASS_KEY] end
48
+ def subclass() @smell[SUBCLASS_KEY] end
49
+ def message() @smell[MESSAGE_KEY] end
50
+
47
51
  #
48
52
  # Details of the smell's location, including its context ({CONTEXT_KEY}),
49
53
  # the line numbers on which it occurs ({LINES_KEY}) and the source
@@ -53,6 +57,10 @@ module Reek
53
57
  #
54
58
  attr_reader :location
55
59
 
60
+ def context() @location[CONTEXT_KEY] end
61
+ def lines() @location[LINES_KEY] end
62
+ def source() @location[SOURCE_KEY] end
63
+
56
64
  #
57
65
  # Details of the smell's status, including whether it is active ({ACTIVE_KEY})
58
66
  # (as opposed to being masked by a config file)
@@ -20,7 +20,8 @@ module Reek
20
20
  #
21
21
  class Attribute < SmellDetector
22
22
 
23
- ATTRIBUTE_METHODS = [:attr, :attr_reader, :attr_writer, :attr_accessor]
23
+ SMELL_CLASS = self.name.split(/::/)[-1]
24
+ ATTRIBUTE_KEY = 'attribute'
24
25
 
25
26
  def self.contexts # :nodoc:
26
27
  [:class, :module]
@@ -51,8 +52,9 @@ module Reek
51
52
  #
52
53
  def attributes_in(module_ctx)
53
54
  result = Set.new
55
+ attr_defn_methods = [:attr, :attr_reader, :attr_writer, :attr_accessor]
54
56
  module_ctx.local_nodes(:call) do |call_node|
55
- if ATTRIBUTE_METHODS.include?(call_node.method_name)
57
+ if attr_defn_methods.include?(call_node.method_name)
56
58
  call_node.arg_names.each {|arg| result << [arg, call_node.line] }
57
59
  end
58
60
  end
@@ -24,7 +24,7 @@ module Reek
24
24
  # Remembers any smells found.
25
25
  #
26
26
  def examine_context(ctx)
27
- class_variables_in(ctx).each do |cvar_name|
27
+ class_variables_in(ctx.exp).each do |cvar_name|
28
28
  found(ctx, "declares the class variable #{cvar_name}", '', {'variable' => cvar_name.to_s})
29
29
  end
30
30
  end
@@ -33,11 +33,11 @@ module Reek
33
33
  # Collects the names of the class variables declared and/or used
34
34
  # in the given module.
35
35
  #
36
- def class_variables_in(ctx)
36
+ def class_variables_in(ast)
37
37
  result = Set.new
38
38
  collector = proc { |cvar_node| result << cvar_node.name }
39
39
  [:cvar, :cvasgn, :cvdecl].each do |stmt_type|
40
- ctx.local_nodes(stmt_type, &collector)
40
+ ast.each_node(stmt_type, [:class, :module], &collector)
41
41
  end
42
42
  result
43
43
  end
@@ -1,6 +1,5 @@
1
1
  require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
2
  require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -51,7 +50,7 @@ module Reek
51
50
  #
52
51
  def examine_context(method_ctx)
53
52
  control_parameters(method_ctx).each do |cond, occurs|
54
- param = Source::SexpFormatter.format(cond)
53
+ param = cond.format
55
54
  lines = occurs.map {|exp| exp.line}
56
55
  found(method_ctx, "is controlled by argument #{param}",
57
56
  'ControlParameter', {'parameter' => param}, lines)
@@ -2,6 +2,32 @@ require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
2
  require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
3
  require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
4
4
 
5
+ #
6
+ # Extensions to +Array+ needed by Reek.
7
+ #
8
+ class Array
9
+ def power_set
10
+ self.inject([[]]) { |cum, element| cum.cross(element) }
11
+ end
12
+
13
+ def bounded_power_set(lower_bound)
14
+ power_set.select {|ps| ps.length > lower_bound}
15
+ end
16
+
17
+ def cross(element)
18
+ result = []
19
+ self.each do |set|
20
+ result << set
21
+ result << (set + [element])
22
+ end
23
+ result
24
+ end
25
+
26
+ def intersection
27
+ self.inject { |res, elem| elem & res }
28
+ end
29
+ end
30
+
5
31
  module Reek
6
32
  module Smells
7
33
 
@@ -19,6 +45,12 @@ module Reek
19
45
  #
20
46
  class DataClump < SmellDetector
21
47
 
48
+ SMELL_CLASS = self.name.split(/::/)[-1]
49
+
50
+ METHODS_KEY = 'methods'
51
+ OCCURRENCES_KEY = 'occurrences'
52
+ PARAMETERS_KEY = 'parameters'
53
+
22
54
  def self.contexts # :nodoc:
23
55
  [:class, :module]
24
56
  end
@@ -50,11 +82,18 @@ module Reek
50
82
  def examine_context(ctx)
51
83
  max_copies = value(MAX_COPIES_KEY, ctx, DEFAULT_MAX_COPIES)
52
84
  min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
53
- MethodGroup.new(ctx, min_clump_size, max_copies).clumps.each do |clump, occurs|
54
- found(ctx, "takes parameters #{DataClump.print_clump(clump)} to #{occurs} methods",
55
- 'DataClump', {'parameters' => clump.map {|name| name.to_s}, 'occurrences' => occurs})
85
+ MethodGroup.new(ctx, min_clump_size, max_copies).clumps.each do |clump, methods|
86
+ smell = SmellWarning.new('DataClump', ctx.full_name,
87
+ methods.map {|meth| meth.line},
88
+ "takes parameters #{DataClump.print_clump(clump)} to #{methods.length} methods", @masked,
89
+ @source, 'DataClump', {
90
+ PARAMETERS_KEY => clump.map {|name| name.to_s},
91
+ OCCURRENCES_KEY => methods.length,
92
+ METHODS_KEY => methods.map {|meth| meth.name}
93
+ })
94
+ @smells_found << smell
95
+ #SMELL: serious duplication
56
96
  # SMELL: name.to_s is becoming a nuisance
57
- # TODO: record the methods in [lines] and in the hash
58
97
  end
59
98
  end
60
99
 
@@ -69,24 +108,62 @@ module Reek
69
108
  class MethodGroup # :nodoc:
70
109
 
71
110
  def self.intersection_of_parameters_of(methods)
72
- methods.map {|meth| meth.parameters.names.sort}.intersection
111
+ methods.map {|meth| meth.arg_names.sort {|a,b| a.to_s <=> b.to_s}}.intersection
73
112
  end
74
113
 
75
114
  def initialize(ctx, min_clump_size, max_copies)
76
- @ctx = ctx
77
115
  @min_clump_size = min_clump_size
78
116
  @max_copies = max_copies
117
+ @candidate_methods = ctx.local_nodes(:defn).select do |meth|
118
+ meth.arg_names.length >= @min_clump_size
119
+ end.map {|defn_node| CandidateMethod.new(defn_node)}
120
+ prune_candidates
79
121
  end
80
122
 
81
123
  def clumps
82
- results = Hash.new(0)
83
- @ctx.parameterized_methods(@min_clump_size).bounded_power_set(@max_copies).each do |methods|
124
+ results = Hash.new([])
125
+ @candidate_methods.bounded_power_set(@max_copies).each do |methods|
84
126
  clump = MethodGroup.intersection_of_parameters_of(methods)
85
127
  if clump.length >= @min_clump_size
86
- results[clump] = [methods.length, results[clump]].max
128
+ results[clump] = methods if methods.length > results[clump].length
87
129
  end
88
130
  end
89
131
  results
90
132
  end
133
+
134
+ def prune_candidates
135
+ @candidate_methods.each do |meth|
136
+ meth.arg_names.each do |param|
137
+ count = @candidate_methods.select {|cm| cm.arg_names.include?(param)}.length
138
+ meth.delete(param) if count <= @max_copies
139
+ end
140
+ end
141
+ @candidate_methods = @candidate_methods.select do |meth|
142
+ meth.arg_names.length >= @min_clump_size
143
+ end
144
+ end
145
+ end
146
+
147
+ class CandidateMethod
148
+ def initialize(defn_node)
149
+ @defn = defn_node
150
+ @params = defn_node.arg_names.clone
151
+ end
152
+
153
+ def arg_names
154
+ @params
155
+ end
156
+
157
+ def delete(param)
158
+ @params.delete(param)
159
+ end
160
+
161
+ def line
162
+ @defn.line
163
+ end
164
+
165
+ def name
166
+ @defn.name.to_s # BUG: should report the symbols!
167
+ end
91
168
  end
92
169
  end
@@ -1,6 +1,5 @@
1
1
  require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
2
  require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -38,7 +37,7 @@ module Reek
38
37
  calls(method_ctx).each do |call_exp, copies|
39
38
  occurs = copies.length
40
39
  next if occurs <= value(MAX_ALLOWED_CALLS_KEY, method_ctx, DEFAULT_MAX_CALLS)
41
- call = Source::SexpFormatter.format(call_exp)
40
+ call = call_exp.format
42
41
  multiple = occurs == 2 ? 'twice' : "#{occurs} times"
43
42
  found(method_ctx, "calls #{call} #{multiple}",
44
43
  'DuplicateMethodCall', {'call' => call, 'occurrences' => occurs},
@@ -53,7 +52,7 @@ module Reek
53
52
  result[call_node].push(call_node)
54
53
  end
55
54
  method_ctx.local_nodes(:attrasgn) do |asgn_node|
56
- result[asgn_node].push(asgn_node)
55
+ result[asgn_node].push(asgn_node) unless asgn_node.args.length < 2
57
56
  end
58
57
  result
59
58
  end
@@ -1,6 +1,5 @@
1
1
  require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
2
  require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -35,6 +34,12 @@ module Reek
35
34
  class FeatureEnvy < SmellDetector
36
35
  include ExcludeInitialize
37
36
 
37
+ SMELL_CLASS = 'LowCohesion'
38
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
39
+
40
+ RECEIVER_KEY = 'receiver'
41
+ REFERENCES_KEY = 'references'
42
+
38
43
  #
39
44
  # Checks whether the given +context+ includes any code fragment that
40
45
  # might "belong" on another class.
@@ -42,10 +47,10 @@ module Reek
42
47
  #
43
48
  def examine_context(method_ctx)
44
49
  method_ctx.envious_receivers.each do |ref, occurs|
45
- target = Source::SexpFormatter.format(ref)
46
- smell = SmellWarning.new('LowCohesion', method_ctx.full_name, [method_ctx.exp.line],
50
+ target = ref.format
51
+ smell = SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
47
52
  "refers to #{target} more than self", @masked,
48
- @source, 'FeatureEnvy', {'receiver' => target, 'references' => occurs})
53
+ @source, SMELL_SUBCLASS, {RECEIVER_KEY => target, REFERENCES_KEY => occurs})
49
54
  @smells_found << smell
50
55
  #SMELL: serious duplication
51
56
  end
@@ -1,6 +1,5 @@
1
1
  require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
2
  require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -50,7 +49,7 @@ module Reek
50
49
  conditional_counts(klass).each do |key, lines|
51
50
  occurs = lines.length
52
51
  next unless occurs > value(MAX_IDENTICAL_IFS_KEY, klass, DEFAULT_MAX_IFS)
53
- expr = Source::SexpFormatter.format(key)
52
+ expr = key.format
54
53
  found(klass, "tests #{expr} at least #{occurs} times",
55
54
  'RepeatedConditional', {'expression' => expr, 'occurrences' => occurs}, lines)
56
55
  end
@@ -59,12 +59,6 @@ module Reek
59
59
  @config.enabled?
60
60
  end
61
61
 
62
- def configure(config)
63
- my_part = config[self.class.name.split(/::/)[-1]]
64
- return unless my_part
65
- configure_with(my_part)
66
- end
67
-
68
62
  def configure_with(config)
69
63
  @config.adopt!(config)
70
64
  end
@@ -19,11 +19,15 @@ module Reek
19
19
  #
20
20
  class UncommunicativeMethodName < SmellDetector
21
21
 
22
+ SMELL_CLASS = 'UncommunicativeName'
23
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
24
+ METHOD_NAME_KEY = 'method_name'
25
+
22
26
  # The name of the config field that lists the regexps of
23
27
  # smelly names to be reported.
24
28
  REJECT_KEY = 'reject'
25
29
 
26
- DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/]
30
+ DEFAULT_REJECT_SET = [/^[a-z]$/, /[0-9]$/, /[A-Z]/]
27
31
 
28
32
  # The name of the config field that lists the specific names that are
29
33
  # to be treated as exceptions; these names will not be reported as
@@ -57,11 +61,13 @@ module Reek
57
61
  return false unless is_bad_name?(name, method_ctx)
58
62
  smell = SmellWarning.new('UncommunicativeName', method_ctx.full_name, [method_ctx.exp.line],
59
63
  "has the name '#{name}'", @masked,
60
- @source, 'UncommunicativeMethodName', {'method_name' => name.to_s})
64
+ @source, 'UncommunicativeMethodName', {METHOD_NAME_KEY => name.to_s})
61
65
  @smells_found << smell
62
66
  #SMELL: serious duplication
63
67
  end
64
68
 
69
+ private
70
+
65
71
  def accept?(context)
66
72
  value(ACCEPT_KEY, context, DEFAULT_ACCEPT_SET).include?(context.full_name)
67
73
  end
@@ -23,7 +23,7 @@ module Reek
23
23
  # smelly names to be reported.
24
24
  REJECT_KEY = 'reject'
25
25
 
26
- DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/]
26
+ DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
27
27
 
28
28
  # The name of the config field that lists the specific names that are
29
29
  # to be treated as exceptions; these names will not be reported as
@@ -23,7 +23,7 @@ module Reek
23
23
  # smelly names to be reported.
24
24
  REJECT_KEY = 'reject'
25
25
 
26
- DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/]
26
+ DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
27
27
 
28
28
  # The name of the config field that lists the specific names that are
29
29
  # to be treated as exceptions; these names will not be reported as
@@ -1,5 +1,6 @@
1
1
  require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
2
  require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source', 'reference_collector')
3
4
 
4
5
  module Reek
5
6
  module Smells
@@ -42,8 +43,13 @@ module Reek
42
43
 
43
44
  DEFAULT_HELPER_CALLS_LIMIT = 1
44
45
 
45
- def self.default_config
46
- super.adopt(HELPER_CALLS_LIMIT_KEY => DEFAULT_HELPER_CALLS_LIMIT)
46
+ class << self
47
+ def contexts # :nodoc:
48
+ [:defn]
49
+ end
50
+ def default_config
51
+ super.adopt(HELPER_CALLS_LIMIT_KEY => DEFAULT_HELPER_CALLS_LIMIT)
52
+ end
47
53
  end
48
54
 
49
55
  def initialize(source, config = UtilityFunction.default_config)
@@ -55,9 +61,9 @@ module Reek
55
61
  # Remembers any smells found.
56
62
  #
57
63
  def examine_context(method_ctx)
58
- return false if method_ctx.num_statements == 0 or
59
- method_ctx.depends_on_instance? or
60
- num_helper_methods(method_ctx) <= value(HELPER_CALLS_LIMIT_KEY, method_ctx, DEFAULT_HELPER_CALLS_LIMIT)
64
+ return false if method_ctx.num_statements == 0
65
+ return false if depends_on_instance?(method_ctx.exp.body)
66
+ return false if num_helper_methods(method_ctx) <= value(HELPER_CALLS_LIMIT_KEY, method_ctx, DEFAULT_HELPER_CALLS_LIMIT)
61
67
  # SMELL: loads of calls to value{} with the above pattern
62
68
  smell = SmellWarning.new('LowCohesion', method_ctx.full_name, [method_ctx.exp.line],
63
69
  "doesn't depend on instance state", @masked,
@@ -66,6 +72,12 @@ module Reek
66
72
  #SMELL: serious duplication
67
73
  end
68
74
 
75
+ private
76
+
77
+ def depends_on_instance?(exp)
78
+ Reek::Source::ReferenceCollector.new(exp).num_refs_to_self > 0
79
+ end
80
+
69
81
  def num_helper_methods(method_ctx)
70
82
  method_ctx.local_nodes(:call).length
71
83
  end
@@ -0,0 +1,21 @@
1
+
2
+ module Reek
3
+ module Source
4
+ class ReferenceCollector
5
+ def initialize(ast)
6
+ @ast = ast
7
+ end
8
+
9
+ def num_refs_to_self
10
+ result = 0
11
+ [:self, :zsuper, :ivar, :iasgn].each do |node_type|
12
+ @ast.look_for(node_type, [:class, :module, :defn, :defs]) { result += 1}
13
+ end
14
+ @ast.look_for(:call, [:class, :module, :defn, :defs]) do |call|
15
+ result += 1 unless call.receiver
16
+ end
17
+ result
18
+ end
19
+ end
20
+ end
21
+ end
@@ -8,6 +8,7 @@ module Reek
8
8
  #
9
9
  class SexpFormatter
10
10
  def self.format(sexp)
11
+ return sexp.to_s unless Array === sexp
11
12
  sexp = YAML::load(YAML::dump(sexp))
12
13
  Ruby2Ruby.new.process(sexp)
13
14
  end
@@ -14,6 +14,16 @@ module Reek
14
14
  is_language_node? and first == type
15
15
  end
16
16
 
17
+ def each_node(type, ignoring, &blk)
18
+ if block_given?
19
+ look_for(type, ignoring, &blk)
20
+ else
21
+ result = []
22
+ look_for(type, ignoring) {|exp| result << exp}
23
+ result
24
+ end
25
+ end
26
+
17
27
  #
18
28
  # Carries out a depth-first traversal of this syntax tree, yielding
19
29
  # every Sexp of type +target_type+. The traversal ignores any node
@@ -27,13 +37,22 @@ module Reek
27
37
  end
28
38
  blk.call(self) if first == target_type
29
39
  end
40
+ def format
41
+ return self[0].to_s unless Array === self
42
+ Ruby2Ruby.new.process(deep_copy)
43
+ end
44
+ def deep_copy
45
+ YAML::load(YAML::dump(self))
46
+ end
30
47
  end
31
48
 
32
49
  module SexpExtensions
50
+ module AttrasgnNode
51
+ def args() self[3] end
52
+ end
53
+
33
54
  module CaseNode
34
- def condition
35
- self[1]
36
- end
55
+ def condition() self[1] end
37
56
  end
38
57
 
39
58
  module CallNode
@@ -45,6 +64,15 @@ module Reek
45
64
  end
46
65
  end
47
66
 
67
+ module ClassNode
68
+ def name() self[1] end
69
+ def superclass() self[2] end
70
+ def full_name(outer)
71
+ prefix = outer == '' ? '' : "#{outer}::"
72
+ "#{prefix}#{name}"
73
+ end
74
+ end
75
+
48
76
  module CvarNode
49
77
  def name() self[1] end
50
78
  end
@@ -53,29 +81,47 @@ module Reek
53
81
  CvdeclNode = CvarNode
54
82
 
55
83
  module DefnNode
56
- def method_name() self[1] end
84
+ def name() self[1] end
85
+ def arg_names
86
+ unless @args
87
+ @args = self[2][1..-1].reject {|param| Sexp === param or param.to_s =~ /^&/}
88
+ end
89
+ @args
90
+ end
57
91
  def parameters()
58
- self[2].reject {|param| Sexp === param}
92
+ unless @params
93
+ @params = self[2].reject {|param| Sexp === param}
94
+ end
95
+ @params
59
96
  end
60
97
  def parameter_names
61
98
  parameters[1..-1]
62
99
  end
100
+ def body() self[3] end
101
+ def full_name(outer)
102
+ prefix = outer == '' ? '' : "#{outer}#"
103
+ "#{prefix}#{name}"
104
+ end
63
105
  end
64
106
 
65
107
  module DefsNode
66
- def method_name() self[2] end
108
+ def receiver() self[1] end
109
+ def name() self[2] end
67
110
  def parameters
68
111
  self[3].reject {|param| Sexp === param}
69
112
  end
70
113
  def parameter_names
71
114
  parameters[1..-1]
72
115
  end
116
+ def body() self[4] end
117
+ def full_name(outer)
118
+ prefix = outer == '' ? '' : "#{outer}#"
119
+ "#{prefix}#{receiver.format}.#{name}"
120
+ end
73
121
  end
74
122
 
75
123
  module IfNode
76
- def condition
77
- self[1]
78
- end
124
+ def condition() self[1] end
79
125
  end
80
126
 
81
127
  module IterNode
@@ -96,6 +142,18 @@ module Reek
96
142
  end
97
143
  end
98
144
 
145
+ module LitNode
146
+ def value() self[1] end
147
+ end
148
+
149
+ module ModuleNode
150
+ def name() self[1] end
151
+ def full_name(outer)
152
+ prefix = outer == '' ? '' : "#{outer}::"
153
+ "#{prefix}#{name}"
154
+ end
155
+ end
156
+
99
157
  module YieldNode
100
158
  def args() self[1..-1] end
101
159
  def arg_names