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
@@ -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