reek 1.2.7.1 → 1.2.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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