reek 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/History.txt +9 -1
  2. data/features/options.feature +1 -9
  3. data/features/samples.feature +7 -2
  4. data/features/step_definitions/reek_steps.rb +3 -3
  5. data/lib/reek.rb +1 -1
  6. data/lib/reek/adapters/application.rb +22 -27
  7. data/lib/reek/adapters/command_line.rb +41 -42
  8. data/lib/reek/adapters/report.rb +30 -23
  9. data/lib/reek/adapters/spec.rb +1 -1
  10. data/lib/reek/code_context.rb +6 -2
  11. data/lib/reek/code_parser.rb +3 -7
  12. data/lib/reek/detector_stack.rb +2 -4
  13. data/lib/reek/help_command.rb +14 -0
  14. data/lib/reek/masking_collection.rb +33 -0
  15. data/lib/reek/method_context.rb +18 -6
  16. data/lib/reek/module_context.rb +0 -13
  17. data/lib/reek/reek_command.rb +28 -0
  18. data/lib/reek/singleton_method_context.rb +1 -1
  19. data/lib/reek/smell_warning.rb +5 -3
  20. data/lib/reek/smells/attribute.rb +17 -1
  21. data/lib/reek/smells/class_variable.rb +1 -1
  22. data/lib/reek/smells/control_couple.rb +13 -10
  23. data/lib/reek/smells/large_class.rb +1 -1
  24. data/lib/reek/smells/long_method.rb +0 -2
  25. data/lib/reek/smells/simulated_polymorphism.rb +2 -2
  26. data/lib/reek/sniffer.rb +1 -3
  27. data/lib/reek/tree_dresser.rb +35 -23
  28. data/lib/reek/version_command.rb +14 -0
  29. data/reek.gemspec +3 -3
  30. data/spec/reek/adapters/report_spec.rb +8 -8
  31. data/spec/reek/adapters/should_reek_of_spec.rb +1 -1
  32. data/spec/reek/adapters/should_reek_only_of_spec.rb +2 -2
  33. data/spec/reek/adapters/should_reek_spec.rb +3 -3
  34. data/spec/reek/code_context_spec.rb +11 -11
  35. data/spec/reek/code_parser_spec.rb +0 -88
  36. data/spec/reek/help_command_spec.rb +24 -0
  37. data/spec/reek/masking_collection_spec.rb +236 -0
  38. data/spec/reek/method_context_spec.rb +43 -1
  39. data/spec/reek/reek_command_spec.rb +45 -0
  40. data/spec/reek/smell_warning_spec.rb +12 -4
  41. data/spec/reek/smells/attribute_spec.rb +79 -7
  42. data/spec/reek/smells/control_couple_spec.rb +40 -11
  43. data/spec/reek/smells/long_parameter_list_spec.rb +1 -1
  44. data/spec/reek/smells/smell_detector_spec.rb +0 -17
  45. data/spec/reek/tree_dresser_spec.rb +20 -0
  46. data/spec/reek/version_command_spec.rb +29 -0
  47. metadata +11 -2
@@ -91,7 +91,6 @@ module Reek
91
91
 
92
92
  def process_call(exp)
93
93
  @element.record_call_to(exp)
94
- @element.check_for_attribute_declaration(exp)
95
94
  process_default(exp)
96
95
  end
97
96
 
@@ -120,20 +119,17 @@ module Reek
120
119
 
121
120
  def process_until(exp)
122
121
  count_clause(exp[2])
123
- process_default(exp)
124
- @element.count_statements(-1)
122
+ process_case(exp)
125
123
  end
126
124
 
127
125
  def process_for(exp)
128
126
  count_clause(exp[3])
129
- process_default(exp)
130
- @element.count_statements(-1)
127
+ process_case(exp)
131
128
  end
132
129
 
133
130
  def process_rescue(exp)
134
131
  count_clause(exp[1])
135
- process_default(exp)
136
- @element.count_statements(-1)
132
+ process_case(exp)
137
133
  end
138
134
 
139
135
  def process_resbody(exp)
@@ -20,9 +20,7 @@ module Reek
20
20
  end
21
21
 
22
22
  def num_smells
23
- total = 0
24
- @detectors.each { |det| total += det.num_smells }
25
- total
23
+ @detectors.inject(0) { |total, detector| total += detector.num_smells }
26
24
  end
27
25
 
28
26
  def has_smell?(patterns)
@@ -31,7 +29,7 @@ module Reek
31
29
  end
32
30
 
33
31
  def smelly?
34
- # SMELL: Duplication: look at al those loops!
32
+ # SMELL: Duplication: look at all those loops!
35
33
  @detectors.each { |det| return true if det.smelly? }
36
34
  false
37
35
  end
@@ -0,0 +1,14 @@
1
+
2
+ module Reek
3
+
4
+ class HelpCommand
5
+ def initialize(parser)
6
+ @parser = parser
7
+ end
8
+ def execute(view)
9
+ view.output(@parser.to_s)
10
+ view.report_success
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,33 @@
1
+ require 'set'
2
+
3
+ #
4
+ # A set of items that can be "masked" or "visible". If an item is added
5
+ # more than once, only the "visible" copy is retained.
6
+ #
7
+ class MaskingCollection
8
+ def initialize
9
+ @visible_items = SortedSet.new
10
+ @masked_items = SortedSet.new
11
+ end
12
+ def add(item)
13
+ @visible_items.add(item)
14
+ @masked_items.delete(item) if @masked_items.include?(item)
15
+ end
16
+ def add_masked(item)
17
+ @masked_items.add(item) unless @visible_items.include?(item)
18
+ end
19
+ def num_visible_items
20
+ @visible_items.length
21
+ end
22
+ def num_masked_items
23
+ @masked_items.length
24
+ end
25
+ def each_item(&blk)
26
+ all = SortedSet.new(@visible_items)
27
+ all.merge(@masked_items)
28
+ all.each(&blk)
29
+ end
30
+ def each_visible_item(&blk)
31
+ @visible_items.each(&blk)
32
+ end
33
+ end
@@ -28,14 +28,26 @@ end
28
28
  module Reek
29
29
 
30
30
  module MethodParameters
31
- def self.is_arg?(param)
32
- return false if (Array === param and param[0] == :block)
31
+ def default_assignments
32
+ assignments = self[-1]
33
+ result = {}
34
+ return result unless is_assignment_block?(assignments)
35
+ assignments[1..-1].each do |exp|
36
+ result[exp[1]] = exp[2] if exp[0] == :lasgn
37
+ end
38
+ result
39
+ end
40
+ def is_arg?(param)
41
+ return false if is_assignment_block?(param)
33
42
  return !(param.to_s =~ /^\&/)
34
43
  end
44
+ def is_assignment_block?(param)
45
+ Array === param and param[0] == :block
46
+ end
35
47
 
36
48
  def names
37
49
  return @names if @names
38
- @names = self[1..-1].select {|arg| MethodParameters.is_arg?(arg)}.map {|arg| Name.new(arg)}
50
+ @names = self[1..-1].select {|arg| is_arg?(arg)}.map {|arg| Name.new(arg)}
39
51
  end
40
52
 
41
53
  def length
@@ -57,7 +69,8 @@ module Reek
57
69
  attr_reader :refs
58
70
  attr_reader :num_statements
59
71
 
60
- def initialize(outer, exp, record = true)
72
+ def initialize(outer, exp)
73
+ # SMELL: Unused Parameter
61
74
  super(outer, exp)
62
75
  @parameters = exp[exp[0] == :defn ? 2 : 3] # SMELL: SimulatedPolymorphism
63
76
  @parameters ||= []
@@ -67,7 +80,7 @@ module Reek
67
80
  @calls = Hash.new(0)
68
81
  @depends_on_self = false
69
82
  @refs = ObjectRefs.new
70
- @outer.record_method(self)
83
+ @outer.record_method(self) # SMELL: these could be found by tree walking
71
84
  end
72
85
 
73
86
  def count_statements(num)
@@ -85,7 +98,6 @@ module Reek
85
98
  def record_call_to(exp)
86
99
  @calls[exp] += 1
87
100
  record_receiver(exp)
88
- check_for_attribute_declaration(exp)
89
101
  end
90
102
 
91
103
  def record_receiver(exp)
@@ -18,12 +18,9 @@ module Reek
18
18
  end
19
19
  end
20
20
 
21
- attr_reader :attributes
22
-
23
21
  def initialize(outer, name, exp)
24
22
  super(outer, exp)
25
23
  @name = name
26
- @attributes = Set.new
27
24
  @parsed_methods = []
28
25
  end
29
26
 
@@ -40,20 +37,10 @@ module Reek
40
37
  @parsed_methods.select {|meth| meth.parameters.length >= min_clump_size }
41
38
  end
42
39
 
43
- def record_attribute(attr)
44
- @attributes << Name.new(attr)
45
- end
46
-
47
40
  def record_method(meth)
48
41
  @parsed_methods << meth
49
42
  end
50
43
 
51
- def check_for_attribute_declaration(exp)
52
- if [:attr, :attr_reader, :attr_writer, :attr_accessor].include? exp[2]
53
- exp[3][1..-1].each {|arg| record_attribute(arg[1])}
54
- end
55
- end
56
-
57
44
  def outer_name
58
45
  "#{@outer.outer_name}#{@name}::"
59
46
  end
@@ -0,0 +1,28 @@
1
+ require 'reek/sniffer'
2
+
3
+ module Reek
4
+
5
+ class ReekCommand
6
+
7
+ def initialize(sources, report_class, show_all)
8
+ @sniffer = sources.length > 0 ? sources.sniff : sniff_stdin
9
+ @report_class = report_class
10
+ @show_all = show_all
11
+ end
12
+
13
+ def sniff_stdin
14
+ Reek::Sniffer.new($stdin.to_reek_source('$stdin'))
15
+ end
16
+
17
+ def execute(view)
18
+ rpt = @report_class.new(@sniffer.sniffers, @show_all)
19
+ view.output(rpt.report)
20
+ if @sniffer.smelly?
21
+ view.report_smells
22
+ else
23
+ view.report_success
24
+ end
25
+ end
26
+ end
27
+
28
+ end
@@ -6,7 +6,7 @@ module Reek
6
6
  class SingletonMethodContext < MethodContext
7
7
 
8
8
  def initialize(outer, exp)
9
- super(outer, exp, false)
9
+ super(outer, exp)
10
10
  @name = Name.new(exp[2])
11
11
  @receiver = SexpFormatter.format(exp[1])
12
12
  record_depends_on_self
@@ -21,7 +21,9 @@ module Reek
21
21
  sort_key <=> other.sort_key
22
22
  end
23
23
 
24
- alias eql? <=> # :nodoc:
24
+ def eql?(other)
25
+ (self <=> other) == 0
26
+ end
25
27
 
26
28
  #
27
29
  # Returns +true+ only if this is a warning about an instance of
@@ -49,9 +51,9 @@ module Reek
49
51
 
50
52
  def report_on(report)
51
53
  if @is_masked
52
- report.record_masked_smell(self)
54
+ report.found_masked_smell(self)
53
55
  else
54
- report << self
56
+ report.found_smell(self)
55
57
  end
56
58
  end
57
59
  end
@@ -18,6 +18,8 @@ module Reek
18
18
  #
19
19
  class Attribute < SmellDetector
20
20
 
21
+ ATTRIBUTE_METHODS = [:attr, :attr_reader, :attr_writer, :attr_accessor]
22
+
21
23
  def self.contexts # :nodoc:
22
24
  [:class, :module]
23
25
  end
@@ -39,10 +41,24 @@ module Reek
39
41
  # MethodContext, ClassContext and ModuleContext all know which
40
42
  # calls constitute attribute declarations. Need a method on
41
43
  # ModuleContext: each_public_call.select [names] {...}
42
- mod.attributes.each do |attr|
44
+ attributes_in(mod).each do |attr|
43
45
  found(mod, "declares the attribute #{attr}")
44
46
  end
45
47
  end
48
+
49
+ #
50
+ # Collects the names of the class variables declared and/or used
51
+ # in the given module.
52
+ #
53
+ def attributes_in(mod)
54
+ result = Set.new
55
+ mod.local_nodes(:call) do |call_node|
56
+ if ATTRIBUTE_METHODS.include?(call_node.method_name)
57
+ call_node.arg_names.each {|arg| result << arg }
58
+ end
59
+ end
60
+ result
61
+ end
46
62
  end
47
63
  end
48
64
  end
@@ -35,7 +35,7 @@ module Reek
35
35
  result = Set.new
36
36
  collector = proc { |cvar_node| result << cvar_node.name }
37
37
  [:cvar, :cvasgn, :cvdecl].each do |stmt_type|
38
- mod.each(stmt_type, [:class, :module], &collector)
38
+ mod.local_nodes(stmt_type, &collector)
39
39
  end
40
40
  result
41
41
  end
@@ -34,24 +34,27 @@ module Reek
34
34
  # because it includes at least two different code paths.
35
35
  #
36
36
  class ControlCouple < SmellDetector
37
- include ExcludeInitialize
38
37
 
39
38
  def self.contexts # :nodoc:
40
- [:if]
39
+ [:if, :defn, :defs]
41
40
  end
42
41
 
43
42
  #
44
43
  # Checks whether the given conditional statement relies on a control couple.
45
44
  # Remembers any smells found.
46
45
  #
47
- def examine_context(cond)
48
- return unless cond.tests_a_parameter?
49
- # SMELL: Duplication
50
- # This smell is reported once for each conditional that tests the
51
- # same parameter. Which means that the same smell can recur within
52
- # a single detector. Which in turn means that SmellDetector must
53
- # use a Set to hold smells found.
54
- found(cond, "is controlled by argument #{SexpFormatter.format(cond.if_expr)}")
46
+ def examine_context(ctx)
47
+ case ctx
48
+ when IfContext
49
+ return unless ctx.tests_a_parameter?
50
+ found(ctx, "is controlled by argument #{SexpFormatter.format(ctx.if_expr)}")
51
+ when MethodContext
52
+ ctx.parameters.default_assignments.each do |param, value|
53
+ next unless [:true, :false].include?(value[0])
54
+ found(ctx, "is controlled by argument #{param.to_s}")
55
+ end
56
+ else
57
+ end
55
58
  end
56
59
  end
57
60
  end
@@ -45,7 +45,7 @@ module Reek
45
45
  end
46
46
 
47
47
  def check_num_methods(klass) # :nodoc:
48
- actual = klass.each(:defn, [:class, :module]).length
48
+ actual = klass.local_nodes(:defn).length
49
49
  return if actual <= value(MAX_ALLOWED_METHODS_KEY, klass, DEFAULT_MAX_METHODS)
50
50
  found(klass, "has at least #{actual} methods")
51
51
  end
@@ -25,8 +25,6 @@ module Reek
25
25
  )
26
26
  end
27
27
 
28
- attr_reader :max_statements
29
-
30
28
  def initialize(config = LongMethod.default_config)
31
29
  super(config)
32
30
  end
@@ -63,8 +63,8 @@ module Reek
63
63
  condition = node.condition
64
64
  result[condition] += 1 unless condition == s(:call, nil, :block_given?, s(:arglist))
65
65
  }
66
- klass.each(:if, [:class, :module], &collector)
67
- klass.each(:case, [:class, :module], &collector)
66
+ klass.local_nodes(:if, &collector)
67
+ klass.local_nodes(:case, &collector)
68
68
  result
69
69
  end
70
70
  end
data/lib/reek/sniffer.rb CHANGED
@@ -166,9 +166,7 @@ private
166
166
  end
167
167
 
168
168
  def num_smells
169
- total = 0
170
- @sniffers.each {|sniffer| total += sniffer.num_smells}
171
- total
169
+ @sniffers.inject(0) {|total, sniffer| total += sniffer.num_smells}
172
170
  end
173
171
 
174
172
  def sniff
@@ -32,43 +32,55 @@ module Reek
32
32
  end
33
33
  end
34
34
 
35
- module CvarNode
36
- def name
37
- self[1]
35
+ module SexpExtensions
36
+ module CaseNode
37
+ def condition
38
+ self[1]
39
+ end
40
+ end
41
+
42
+ module CallNode
43
+ def receiver() self[1] end
44
+ def method_name() self[2] end
45
+ def args() self[3] end
46
+ def arg_names
47
+ args[1..-1].map {|arg| arg[1]}
48
+ end
38
49
  end
39
- end
40
50
 
41
- module IfNode
42
- def condition
43
- self[1]
51
+ module CvarNode
52
+ def name() self[1] end
44
53
  end
45
- end
46
54
 
47
- module CaseNode
48
- def condition
49
- self[1]
55
+ CvasgnNode = CvarNode
56
+ CvdeclNode = CvarNode
57
+
58
+ module DefnNode
59
+ def method_name() self[1] end
60
+ def parameters() self[2] end
61
+ end
62
+
63
+ module IfNode
64
+ def condition
65
+ self[1]
66
+ end
50
67
  end
51
68
  end
52
69
 
53
70
  class TreeDresser
54
- # SMELL: Duplication
55
- # Put these into a new module and build the mapping automagically
56
- # based on the node type
57
- EXTENSIONS = {
58
- :cvar => CvarNode,
59
- :cvasgn => CvarNode,
60
- :cvdecl => CvarNode,
61
- :if => IfNode,
62
- :case => CaseNode,
63
- }
64
71
 
65
72
  def dress(sexp)
66
73
  sexp.extend(SexpNode)
67
- if EXTENSIONS.has_key?(sexp[0])
68
- sexp.extend(EXTENSIONS[sexp[0]])
74
+ module_name = extensions_for(sexp.sexp_type)
75
+ if Reek::SexpExtensions.const_defined?(module_name)
76
+ sexp.extend(Reek::SexpExtensions.const_get(module_name))
69
77
  end
70
78
  sexp[0..-1].each { |sub| dress(sub) if Array === sub }
71
79
  sexp
72
80
  end
81
+
82
+ def extensions_for(node_type)
83
+ "#{node_type.to_s.capitalize}Node"
84
+ end
73
85
  end
74
86
  end