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
@@ -1,5 +1,4 @@
1
1
  require 'sexp'
2
- require File.join(File.dirname(File.expand_path(__FILE__)), 'block_context')
3
2
  require File.join(File.dirname(File.expand_path(__FILE__)), 'class_context')
4
3
  require File.join(File.dirname(File.expand_path(__FILE__)), 'method_context')
5
4
  require File.join(File.dirname(File.expand_path(__FILE__)), 'module_context')
@@ -61,21 +60,6 @@ module Reek
61
60
  @element.record_use_of_self
62
61
  end
63
62
 
64
- def process_lit(exp)
65
- val = exp[1]
66
- @element.record_depends_on_self if val == :self
67
- end
68
-
69
- def process_iter(exp)
70
- process(exp[1])
71
- scope = BlockContext.new(@element, exp)
72
- push(scope) do
73
- process_default(exp[2..-1])
74
- check_smells(exp[0])
75
- end
76
- scope
77
- end
78
-
79
63
  def process_block(exp)
80
64
  @element.count_statements(CodeParser.count_statements(exp))
81
65
  process_default(exp)
@@ -144,8 +128,7 @@ module Reek
144
128
  end
145
129
 
146
130
  def process_iasgn(exp)
147
- @element.record_instance_variable(exp[1])
148
- @element.record_depends_on_self
131
+ @element.record_use_of_self
149
132
  process_default(exp)
150
133
  end
151
134
 
@@ -17,6 +17,10 @@ module Reek
17
17
  @detectors << clone
18
18
  end
19
19
 
20
+ def adopt(config)
21
+ @detectors[-1].configure_with(config)
22
+ end
23
+
20
24
  def listen_to(hooks)
21
25
  @detectors.each { |det| det.listen_to(hooks) }
22
26
  end
@@ -12,6 +12,12 @@ module Reek
12
12
  @visible_items = SortedSet.new
13
13
  @masked_items = SortedSet.new
14
14
  end
15
+
16
+ def collect_from(sources, config)
17
+ sources.each { |src| Core::Sniffer.new(src, config).report_on(self) }
18
+ self
19
+ end
20
+
15
21
  def all_items
16
22
  all = SortedSet.new(@visible_items)
17
23
  all.merge(@masked_items)
@@ -1,31 +1,5 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), 'object_refs')
2
2
 
3
- #
4
- # Extensions to +Array+ needed by Reek.
5
- #
6
- class Array
7
- def power_set
8
- self.inject([[]]) { |cum, element| cum.cross(element) }
9
- end
10
-
11
- def bounded_power_set(lower_bound)
12
- power_set.select {|ps| ps.length > lower_bound}
13
- end
14
-
15
- def cross(element)
16
- result = []
17
- self.each do |set|
18
- result << set
19
- result << (set + [element])
20
- end
21
- result
22
- end
23
-
24
- def intersection
25
- self.inject { |res, elem| elem & res }
26
- end
27
- end
28
-
29
3
  module Reek
30
4
  module Core
31
5
 
@@ -77,10 +51,7 @@ module Reek
77
51
  @parameters = exp[exp[0] == :defn ? 2 : 3] # SMELL: SimulatedPolymorphism
78
52
  @parameters ||= []
79
53
  @parameters.extend(MethodParameters)
80
- @name = exp[1].to_s
81
- @scope_connector = '#'
82
54
  @num_statements = 0
83
- @depends_on_self = false
84
55
  @refs = ObjectRefs.new
85
56
  @outer.record_method(self) # SMELL: these could be found by tree walking
86
57
  end
@@ -89,44 +60,25 @@ module Reek
89
60
  @num_statements += num
90
61
  end
91
62
 
92
- def depends_on_instance?
93
- @depends_on_self
94
- end
95
-
96
63
  def record_call_to(exp)
97
- record_receiver(exp)
64
+ receiver, meth = exp[1..2]
65
+ receiver ||= [:self]
66
+ case receiver[0]
67
+ when :lvar
68
+ @refs.record_ref(receiver) unless meth == :new
69
+ when :self
70
+ record_use_of_self
71
+ end
98
72
  end
99
73
 
100
74
  def record_use_of_self
101
- record_depends_on_self
102
75
  @refs.record_reference_to_self
103
76
  end
104
77
 
105
- def record_instance_variable(sym)
106
- record_use_of_self
107
- end
108
-
109
- def record_depends_on_self
110
- @depends_on_self = true
111
- end
112
-
113
78
  def envious_receivers
114
79
  return [] if @refs.self_is_max?
115
80
  @refs.max_keys
116
81
  end
117
-
118
- private
119
-
120
- def record_receiver(exp)
121
- receiver, meth = exp[1..2]
122
- receiver ||= [:self]
123
- case receiver[0]
124
- when :lvar
125
- @refs.record_ref(receiver) unless meth == :new
126
- when :self
127
- record_use_of_self
128
- end
129
- end
130
82
  end
131
83
  end
132
84
  end
@@ -1,6 +1,7 @@
1
- require File.join( File.dirname( File.expand_path(__FILE__)), 'code_context')
2
- require File.join( File.dirname( File.expand_path(__FILE__)), 'code_parser')
3
- require File.join( File.dirname( File.expand_path(__FILE__)), 'sniffer')
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'code_context')
2
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'code_parser')
3
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'sniffer')
4
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source', 'sexp_formatter')
4
5
 
5
6
  module Reek
6
7
  module Core
@@ -12,8 +13,8 @@ module Reek
12
13
 
13
14
  class << self
14
15
  def create(outer, exp)
15
- res = resolve(exp[1], outer)
16
- new(res[0], res[1], exp)
16
+ res = Source::SexpFormatter.format(exp[1])
17
+ new(outer, res, exp)
17
18
  end
18
19
 
19
20
  def from_s(src)
@@ -21,41 +22,14 @@ module Reek
21
22
  sniffer = Sniffer.new(source)
22
23
  CodeParser.new(sniffer).do_module_or_class(source.syntax_tree, self)
23
24
  end
24
-
25
- def resolve(exp, context)
26
- unless Array === exp
27
- return resolve_string(exp.to_s, context)
28
- end
29
- name = exp[1]
30
- case exp[0]
31
- when :colon2
32
- return [resolve(name, context)[0], exp[2].to_s]
33
- when :const
34
- return [ModuleContext.create(context, exp), name.to_s]
35
- when :colon3
36
- return [StopContext.new, name.to_s]
37
- else
38
- return [context, name.to_s]
39
- end
40
- end
41
-
42
- def resolve_string(str, context)
43
- return [context, str.to_s] unless str =~ /::/
44
- resolve(RubyParser.new.parse(str), context)
45
- end
46
25
  end
47
26
 
48
27
  def initialize(outer, name, exp)
49
28
  super(outer, exp)
50
29
  @name = name
51
- @scope_connector = '::'
52
30
  @parsed_methods = []
53
31
  end
54
32
 
55
- def parameterized_methods(min_clump_size)
56
- @parsed_methods.select {|meth| meth.parameters.length >= min_clump_size }
57
- end
58
-
59
33
  def record_method(meth)
60
34
  @parsed_methods << meth
61
35
  end
@@ -1,51 +1,51 @@
1
1
  module Reek
2
2
  module Core
3
3
 
4
- #
5
- # Manages and counts the references out of a method to other objects.
6
- #
7
- class ObjectRefs # :nodoc:
8
- def initialize
9
- @refs = Hash.new(0)
10
- end
4
+ #
5
+ # Manages and counts the references out of a method to other objects.
6
+ #
7
+ class ObjectRefs # :nodoc:
8
+ def initialize
9
+ @refs = Hash.new(0)
10
+ end
11
11
 
12
- def record_reference_to_self
13
- record_ref(SELF_REF)
14
- end
12
+ def record_reference_to_self
13
+ record_ref(SELF_REF)
14
+ end
15
15
 
16
- def record_ref(exp)
17
- type = exp[0]
18
- case type
19
- when :gvar
20
- return
21
- when :self
22
- record_reference_to_self
23
- else
24
- @refs[exp] += 1
16
+ def record_ref(exp)
17
+ type = exp[0]
18
+ case type
19
+ when :gvar
20
+ return
21
+ when :self
22
+ record_reference_to_self
23
+ else
24
+ @refs[exp] += 1
25
+ end
25
26
  end
26
- end
27
27
 
28
- def refs_to_self
29
- @refs[SELF_REF]
30
- end
28
+ def refs_to_self
29
+ @refs[SELF_REF]
30
+ end
31
31
 
32
- def max_refs
33
- @refs.values.max or 0
34
- end
32
+ def max_refs
33
+ @refs.values.max or 0
34
+ end
35
35
 
36
- def max_keys
37
- max = max_refs
38
- @refs.reject {|key,val| val != max}
39
- end
36
+ def max_keys
37
+ max = max_refs
38
+ @refs.reject {|key,val| val != max}
39
+ end
40
40
 
41
- def self_is_max?
42
- max_keys.length == 0 || @refs[SELF_REF] == max_refs
43
- end
41
+ def self_is_max?
42
+ max_keys.length == 0 || @refs[SELF_REF] == max_refs
43
+ end
44
44
 
45
- private
45
+ private
46
46
 
47
- SELF_REF = Sexp.from_array([:lit, :self])
47
+ SELF_REF = Sexp.from_array([:lit, :self])
48
48
 
49
- end
49
+ end
50
50
  end
51
51
  end
@@ -1,31 +1,20 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), 'method_context')
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
3
2
 
4
3
  module Reek
5
4
  module Core
6
5
 
7
- #
8
- # A context wrapper for any singleton method definition found in a syntax tree.
9
- #
10
- class SingletonMethodContext < MethodContext
6
+ #
7
+ # A context wrapper for any singleton method definition found in a syntax tree.
8
+ #
9
+ class SingletonMethodContext < MethodContext
11
10
 
12
- def initialize(outer, exp)
13
- super(outer, exp)
14
- @name = exp[2].to_s
15
- @receiver = Source::SexpFormatter.format(exp[1])
16
- @scope_connector = ""
17
- record_depends_on_self
18
- end
19
-
20
- def envious_receivers
21
- []
22
- end
11
+ def initialize(outer, exp)
12
+ super(outer, exp)
13
+ end
23
14
 
24
- def full_name
25
- outer = @outer ? @outer.full_name : ''
26
- prefix = outer == '' ? '' : "#{outer}#"
27
- "#{prefix}#{@receiver}.#{@name}"
15
+ def envious_receivers
16
+ []
17
+ end
28
18
  end
29
19
  end
30
- end
31
20
  end
@@ -65,7 +65,8 @@ module Reek
65
65
  ]
66
66
  end
67
67
 
68
- def initialize(src)
68
+ def initialize(src, config_strategy = ActiveSmellsOnly.new) # SMELL: open secret -- need a Strategy
69
+ @config_strategy = config_strategy
69
70
  @already_checked_for_smells = false
70
71
  @typed_detectors = nil
71
72
  @detectors = Hash.new
@@ -83,7 +84,7 @@ module Reek
83
84
  end
84
85
 
85
86
  def configure(klass, config)
86
- @detectors[klass].push(config)
87
+ @config_strategy.configure(@detectors[klass], config)
87
88
  end
88
89
 
89
90
  def report_on(report)
data/lib/reek/examiner.rb CHANGED
@@ -4,8 +4,28 @@ require File.join(File.dirname(File.expand_path(__FILE__)), 'source')
4
4
 
5
5
  module Reek
6
6
 
7
+ class ActiveSmellsOnly
8
+ def configure(detectors, config)
9
+ detectors.adopt(config)
10
+ end
11
+
12
+ def smells_in(sources)
13
+ Core::MaskingCollection.new.collect_from(sources, self).all_active_items.to_a
14
+ end
15
+ end
16
+
17
+ class ActiveAndMaskedSmells
18
+ def configure(detectors, config)
19
+ detectors.push(config)
20
+ end
21
+
22
+ def smells_in(sources)
23
+ Core::MaskingCollection.new.collect_from(sources, self).all_items
24
+ end
25
+ end
26
+
7
27
  #
8
- # Finds the code smells in Ruby source code.
28
+ # Finds the active code smells in Ruby source code.
9
29
  #
10
30
  class Examiner
11
31
 
@@ -27,62 +47,50 @@ module Reek
27
47
  # and if it is an Array, it is assumed to be a list of file paths,
28
48
  # each of which is opened and parsed for source code.
29
49
  #
30
- def initialize(source)
31
- sniffers = case source
50
+ # @param [#smells_in]
51
+ # The +collector+ will be asked to examine the sources and report
52
+ # an array of SmellWarning objects. The default collector is an
53
+ # instance of ActiveSmellsOnly, which completely ignores all smells
54
+ # that have been masked by configuration options.
55
+ #
56
+ def initialize(source, collector = ActiveSmellsOnly.new)
57
+ sources = case source
32
58
  when Array
33
59
  @description = 'dir'
34
- Source::SourceLocator.new(source).all_sources.map {|src| Core::Sniffer.new(src)}
60
+ Source::SourceLocator.new(source).all_sources
35
61
  when Source::SourceCode
36
62
  @description = source.desc
37
- [Core::Sniffer.new(source)]
63
+ [source]
38
64
  else
39
65
  src = source.to_reek_source
40
66
  @description = src.desc
41
- [Core::Sniffer.new(src)]
67
+ [src]
42
68
  end
43
- @warnings = Core::MaskingCollection.new
44
- sniffers.each {|sniffer| sniffer.report_on(@warnings)}
69
+ @smells = collector.smells_in(sources)
45
70
  end
46
71
 
47
72
  #
48
- # Returns an Array of SmellWarning objects, one for each non-masked smell
73
+ # Returns an Array of SmellWarning objects, one for each active smell
49
74
  # in the source.
50
75
  #
51
76
  # @return [Array<SmellWarning>]
52
77
  #
53
- def all_active_smells
54
- @warnings.all_active_items.to_a
55
- end
56
-
57
- #
58
- # Returns an Array of SmellWarning objects, one for each smell
59
- # in the source; includes active smells and masked smells.
60
- #
61
- # @return [Array<SmellWarning>]
62
- #
63
- def all_smells
64
- @warnings.all_items
78
+ def smells
79
+ @smells
65
80
  end
66
81
 
67
82
  #
68
83
  # Returns the number of non-masked smells in the source.
69
84
  #
70
- def num_active_smells
71
- @warnings.num_visible_items
72
- end
73
-
74
- #
75
- # Returns the number of masked smells in the source.
76
- #
77
- def num_masked_smells
78
- @warnings.num_masked_items
85
+ def num_smells
86
+ @smells.length
79
87
  end
80
88
 
81
89
  #
82
90
  # True if and only if there are non-masked code smells in the given source.
83
91
  #
84
92
  def smelly?
85
- not all_active_smells.empty?
93
+ not @smells.empty?
86
94
  end
87
95
  end
88
96
  end