reek 1.2.2 → 1.2.3

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 (59) hide show
  1. data/History.txt +4 -0
  2. data/Rakefile +0 -1
  3. data/config/defaults.reek +4 -6
  4. data/features/masking_smells.feature +9 -9
  5. data/features/options.feature +2 -2
  6. data/features/profile.feature +34 -0
  7. data/features/rake_task.feature +74 -0
  8. data/features/reports.feature +1 -1
  9. data/features/samples.feature +4 -4
  10. data/features/stdin.feature +1 -1
  11. data/features/step_definitions/reek_steps.rb +11 -7
  12. data/features/support/env.rb +26 -18
  13. data/lib/reek.rb +1 -1
  14. data/lib/reek/adapters/application.rb +9 -2
  15. data/lib/reek/adapters/command_line.rb +2 -2
  16. data/lib/reek/adapters/source.rb +4 -1
  17. data/lib/reek/adapters/spec.rb +1 -3
  18. data/lib/reek/block_context.rb +14 -8
  19. data/lib/reek/class_context.rb +6 -66
  20. data/lib/reek/code_context.rb +10 -0
  21. data/lib/reek/code_parser.rb +25 -53
  22. data/lib/reek/configuration.rb +12 -6
  23. data/lib/reek/if_context.rb +2 -3
  24. data/lib/reek/method_context.rb +3 -12
  25. data/lib/reek/module_context.rb +30 -22
  26. data/lib/reek/name.rb +2 -0
  27. data/lib/reek/object_refs.rb +0 -3
  28. data/lib/reek/sexp_formatter.rb +0 -2
  29. data/lib/reek/smells/class_variable.rb +17 -4
  30. data/lib/reek/smells/control_couple.rb +3 -10
  31. data/lib/reek/smells/data_clump.rb +10 -10
  32. data/lib/reek/smells/feature_envy.rb +1 -8
  33. data/lib/reek/smells/large_class.rb +3 -3
  34. data/lib/reek/smells/simulated_polymorphism.rb +17 -3
  35. data/lib/reek/smells/smell_detector.rb +11 -2
  36. data/lib/reek/smells/utility_function.rb +1 -1
  37. data/lib/reek/sniffer.rb +0 -8
  38. data/lib/reek/stop_context.rb +1 -1
  39. data/lib/reek/tree_dresser.rb +74 -0
  40. data/reek.gemspec +3 -3
  41. data/spec/reek/block_context_spec.rb +6 -6
  42. data/spec/reek/class_context_spec.rb +2 -23
  43. data/spec/reek/code_context_spec.rb +149 -67
  44. data/spec/reek/code_parser_spec.rb +0 -102
  45. data/spec/reek/method_context_spec.rb +4 -4
  46. data/spec/reek/singleton_method_context_spec.rb +1 -1
  47. data/spec/reek/smells/attribute_spec.rb +1 -1
  48. data/spec/reek/smells/class_variable_spec.rb +96 -7
  49. data/spec/reek/smells/control_couple_spec.rb +1 -1
  50. data/spec/reek/smells/data_clump_spec.rb +31 -13
  51. data/spec/reek/smells/feature_envy_spec.rb +1 -1
  52. data/spec/reek/smells/large_class_spec.rb +32 -18
  53. data/spec/reek/smells/simulated_polymorphism_spec.rb +66 -18
  54. data/spec/reek/sniffer_spec.rb +1 -0
  55. data/spec/samples/not_quite_masked/dirty.rb +2 -0
  56. data/spec/spec_helper.rb +1 -1
  57. data/tasks/reek.rake +1 -1
  58. metadata +5 -3
  59. data/lib/reek/exceptions.reek +0 -20
@@ -1,4 +1,6 @@
1
+ require 'ruby_parser'
1
2
  require 'reek/adapters/config_file'
3
+ require 'reek/tree_dresser'
2
4
 
3
5
  module Reek
4
6
 
@@ -17,7 +19,8 @@ module Reek
17
19
  def configure(sniffer) end
18
20
 
19
21
  def syntax_tree
20
- RubyParser.new.parse(@source, @desc) || s()
22
+ ast = RubyParser.new.parse(@source, @desc) || s()
23
+ TreeDresser.new.dress(ast)
21
24
  end
22
25
  end
23
26
 
@@ -88,11 +88,9 @@ module Reek
88
88
  end
89
89
 
90
90
  class ShouldReekOnlyOf < ShouldReekOf # :nodoc:
91
- include ReekMatcher
92
-
93
91
  def matches?(actual)
94
92
  @sniffer = actual.sniff
95
- @sniffer.smells_only_of?(@klass, @patterns)
93
+ @sniffer.num_smells == 1 and @sniffer.has_smell?(@klass, @patterns)
96
94
  end
97
95
  def failure_message_for_should
98
96
  "Expected #{@sniffer.desc} to reek only of #{@klass}, but got:\n#{report}"
@@ -21,15 +21,25 @@ module Reek
21
21
  end
22
22
  end
23
23
 
24
- class BlockContext < CodeContext
24
+ class VariableContainer < CodeContext
25
+
26
+ def initialize(outer, exp)
27
+ super
28
+ @local_variables = Set.new
29
+ end
30
+
31
+ def record_local_variable(sym)
32
+ @local_variables << Name.new(sym)
33
+ end
34
+ end
35
+
36
+ class BlockContext < VariableContainer
25
37
 
26
38
  def initialize(outer, exp)
27
39
  super
28
40
  @name = Name.new('block')
29
- @parameters = exp[0] if exp
30
- @parameters ||= []
41
+ @parameters = exp[2] || []
31
42
  @parameters.extend(ParameterSet)
32
- @local_variables = Set.new
33
43
  end
34
44
 
35
45
  def inside_a_block?
@@ -43,10 +53,6 @@ module Reek
43
53
  def nested_block?
44
54
  @outer.inside_a_block?
45
55
  end
46
-
47
- def record_local_variable(sym)
48
- @local_variables << Name.new(sym)
49
- end
50
56
 
51
57
  def outer_name
52
58
  "#{@outer.outer_name}#{@name}/"
@@ -1,5 +1,5 @@
1
1
  require 'set'
2
- require 'reek/code_context'
2
+ require 'reek/module_context'
3
3
 
4
4
  class Class
5
5
  def is_overriding_method?(name)
@@ -11,40 +11,14 @@ class Class
11
11
  end
12
12
 
13
13
  module Reek
14
- class ClassContext < CodeContext
14
+ class ClassContext < ModuleContext
15
15
 
16
- def ClassContext.create(outer, exp)
17
- res = Name.resolve(exp[1], outer)
18
- ClassContext.new(res[0], res[1], exp[2])
19
- end
20
-
21
- def ClassContext.from_s(src)
22
- source = src.to_reek_source
23
- sniffer = Sniffer.new(source)
24
- CodeParser.new(sniffer).process_class(source.syntax_tree)
25
- end
16
+ attr_reader :parsed_methods
26
17
 
27
- attr_reader :conditionals, :parsed_methods, :class_variables, :attributes
28
-
29
- # SMELL: inconsistent with other contexts (not linked to the sexp)
30
- def initialize(outer, name, superclass = nil)
31
- super(outer, nil)
32
- @name = name
33
- @superclass = superclass
34
- @parsed_methods = []
18
+ def initialize(outer, name, exp)
19
+ super
20
+ @superclass = exp[2]
35
21
  @instance_variables = Set.new
36
- @conditionals = []
37
- @attributes = Set.new
38
- @class_variables = Set.new
39
- end
40
-
41
- def myself
42
- @myself ||= @outer.find_module(@name)
43
- end
44
-
45
- def find_module(modname)
46
- return nil unless myself
47
- @myself.const_or_nil(modname.to_s)
48
22
  end
49
23
 
50
24
  def is_overriding_method?(name)
@@ -56,50 +30,16 @@ module Reek
56
30
  @superclass == [:const, :Struct]
57
31
  end
58
32
 
59
- def num_methods
60
- @parsed_methods.length
61
- end
62
-
63
- def check_for_attribute_declaration(exp)
64
- if [:attr, :attr_reader, :attr_writer, :attr_accessor].include? exp[2]
65
- exp[3][1..-1].each {|arg| record_attribute(arg[1])}
66
- end
67
- end
68
-
69
- def record_attribute(attr)
70
- @attributes << Name.new(attr)
71
- end
72
-
73
- def record_class_variable(cvar)
74
- @class_variables << Name.new(cvar)
75
- end
76
-
77
33
  def record_instance_variable(sym)
78
34
  @instance_variables << Name.new(sym)
79
35
  end
80
36
 
81
- def record_method(meth)
82
- @parsed_methods << meth
83
- end
84
-
85
37
  def outer_name
86
38
  "#{@outer.outer_name}#{@name}#"
87
39
  end
88
40
 
89
- def to_s
90
- "#{@outer.outer_name}#{@name}"
91
- end
92
-
93
41
  def variable_names
94
42
  @instance_variables
95
43
  end
96
-
97
- def record_conditional(exp)
98
- @conditionals << exp
99
- end
100
-
101
- def parameterized_methods(min_clump_size)
102
- parsed_methods.select {|meth| meth.parameters.length >= min_clump_size }
103
- end
104
44
  end
105
45
  end
@@ -23,6 +23,16 @@ module Reek
23
23
  @myself = nil
24
24
  end
25
25
 
26
+ def each(type, ignoring, &blk)
27
+ if block_given?
28
+ @exp.look_for(type, ignoring, &blk)
29
+ else
30
+ result = []
31
+ @exp.look_for(type, ignoring) {|exp| result << exp}
32
+ result
33
+ end
34
+ end
35
+
26
36
  # SMELL: Temporary Field -- @name isn't always initialized
27
37
  def matches?(strings)
28
38
  me = @name.to_s
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'sexp'
3
2
  require 'reek/block_context'
4
3
  require 'reek/class_context'
@@ -9,31 +8,12 @@ require 'reek/method_context'
9
8
  require 'reek/singleton_method_context'
10
9
  require 'reek/yield_call_context'
11
10
 
12
- #
13
- # Extensions to +Sexp+ to allow +CodeParser+ to navigate the abstract
14
- # syntax tree more easily.
15
- #
16
- class Sexp
17
- def children
18
- find_all { |item| Sexp === item }
19
- end
20
-
21
- def is_language_node?
22
- first.class == Symbol
23
- end
24
-
25
- def has_type?(type)
26
- is_language_node? and first == type
27
- end
28
- end
29
-
30
11
  module Reek
31
-
12
+ #
13
+ # Traverses a Sexp abstract syntax tree and fires events whenever
14
+ # it encounters specific node types.
15
+ #
32
16
  class CodeParser
33
-
34
- #
35
- # Creates a new Ruby code checker.
36
- #
37
17
  def initialize(sniffer, ctx = StopContext.new)
38
18
  @sniffer = sniffer
39
19
  @element = ctx
@@ -50,42 +30,29 @@ module Reek
50
30
  exp[0..-1].each { |sub| process(sub) if Array === sub }
51
31
  end
52
32
 
53
- def process_module(exp)
54
- scope = ModuleContext.create(@element, exp)
55
- push(scope) do
56
- process_default(exp)
57
- check_smells(:module)
58
- end
59
- scope
60
- end
61
-
62
- def process_class(exp)
63
- scope = ClassContext.create(@element, exp)
33
+ def do_module_or_class(exp, context_class)
34
+ scope = context_class.create(@element, exp)
64
35
  push(scope) do
65
36
  process_default(exp) unless @element.is_struct?
66
- check_smells(:class)
37
+ check_smells(exp[0])
67
38
  end
68
39
  scope
69
40
  end
70
41
 
71
- def process_cvar(exp)
72
- @element.record_class_variable(exp[1])
73
- end
74
-
75
- def process_cvasgn(exp)
76
- process_cvar(exp)
42
+ def process_module(exp)
43
+ do_module_or_class(exp, ModuleContext)
77
44
  end
78
45
 
79
- def process_cvdecl(exp)
80
- process_cvar(exp)
46
+ def process_class(exp)
47
+ do_module_or_class(exp, ClassContext)
81
48
  end
82
49
 
83
50
  def process_defn(exp)
84
- handle_context(MethodContext, :defn, exp)
51
+ handle_context(MethodContext, exp[0], exp)
85
52
  end
86
53
 
87
54
  def process_defs(exp)
88
- handle_context(SingletonMethodContext, :defs, exp)
55
+ handle_context(SingletonMethodContext, exp[0], exp)
89
56
  end
90
57
 
91
58
  def process_args(exp) end
@@ -105,7 +72,12 @@ module Reek
105
72
 
106
73
  def process_iter(exp)
107
74
  process(exp[1])
108
- handle_context(BlockContext, :iter, exp[2..-1])
75
+ scope = BlockContext.new(@element, exp)
76
+ push(scope) do
77
+ process_default(exp[2..-1])
78
+ check_smells(exp[0])
79
+ end
80
+ scope
109
81
  end
110
82
 
111
83
  def process_block(exp)
@@ -114,7 +86,7 @@ module Reek
114
86
  end
115
87
 
116
88
  def process_yield(exp)
117
- handle_context(YieldCallContext, :yield, exp)
89
+ handle_context(YieldCallContext, exp[0], exp)
118
90
  end
119
91
 
120
92
  def process_call(exp)
@@ -136,10 +108,9 @@ module Reek
136
108
  end
137
109
 
138
110
  def process_if(exp)
139
- @element.record_conditional(exp[1])
140
111
  count_clause(exp[2])
141
112
  count_clause(exp[3])
142
- handle_context(IfContext, :if, exp)
113
+ handle_context(IfContext, exp[0], exp)
143
114
  @element.count_statements(-1)
144
115
  end
145
116
 
@@ -155,12 +126,14 @@ module Reek
155
126
 
156
127
  def process_for(exp)
157
128
  count_clause(exp[3])
158
- process_case(exp)
129
+ process_default(exp)
130
+ @element.count_statements(-1)
159
131
  end
160
132
 
161
133
  def process_rescue(exp)
162
134
  count_clause(exp[1])
163
- process_case(exp)
135
+ process_default(exp)
136
+ @element.count_statements(-1)
164
137
  end
165
138
 
166
139
  def process_resbody(exp)
@@ -168,7 +141,6 @@ module Reek
168
141
  end
169
142
 
170
143
  def process_case(exp)
171
- @element.record_conditional(exp[1])
172
144
  process_default(exp)
173
145
  @element.count_statements(-1)
174
146
  end
@@ -13,19 +13,25 @@ module Reek
13
13
  # for other values in the current smell detector's configuration.
14
14
  OVERRIDES_KEY = 'overrides'
15
15
 
16
- attr_reader :hash
17
-
18
16
  def initialize(hash)
19
- @hash = hash
17
+ @options = hash
18
+ end
19
+
20
+ def adopt!(options)
21
+ @options.adopt!(options)
22
+ end
23
+
24
+ def deep_copy
25
+ @options.deep_copy # SMELL: Open Secret -- returns a Hash
20
26
  end
21
27
 
22
28
  # SMELL: Getter
23
29
  def enabled?
24
- @hash[ENABLED_KEY]
30
+ @options[ENABLED_KEY]
25
31
  end
26
32
 
27
33
  def overrides_for(context)
28
- Overrides.new(@hash.fetch(OVERRIDES_KEY, {})).for_context(context)
34
+ Overrides.new(@options.fetch(OVERRIDES_KEY, {})).for_context(context)
29
35
  end
30
36
 
31
37
  # Retrieves the value, if any, for the given +key+.
@@ -34,7 +40,7 @@ module Reek
34
40
  #
35
41
  def value(key, context, fall_back)
36
42
  overrides_for(context).each { |conf| return conf[key] if conf.has_key?(key) }
37
- return @hash.fetch(key, fall_back)
43
+ return @options.fetch(key, fall_back)
38
44
  end
39
45
  end
40
46
 
@@ -5,8 +5,7 @@ module Reek
5
5
  attr_reader :if_expr
6
6
 
7
7
  def initialize(outer, exp)
8
- @outer = outer
9
- @exp = exp
8
+ super
10
9
  @if_expr = exp[1]
11
10
  end
12
11
 
@@ -18,7 +17,7 @@ module Reek
18
17
  @outer.outer_name
19
18
  end
20
19
 
21
- def to_s
20
+ def to_s # SMELL: should be unnecessary :(
22
21
  @outer.to_s
23
22
  end
24
23
  end
@@ -1,5 +1,5 @@
1
1
  require 'reek/name'
2
- require 'reek/code_context'
2
+ require 'reek/block_context'
3
3
  require 'reek/object_refs'
4
4
 
5
5
  class Array
@@ -51,7 +51,7 @@ module Reek
51
51
  end
52
52
  end
53
53
 
54
- class MethodContext < CodeContext
54
+ class MethodContext < VariableContainer
55
55
  attr_reader :parameters
56
56
  attr_reader :calls
57
57
  attr_reader :refs
@@ -62,7 +62,6 @@ module Reek
62
62
  @parameters = exp[exp[0] == :defn ? 2 : 3] # SMELL: SimulatedPolymorphism
63
63
  @parameters ||= []
64
64
  @parameters.extend(MethodParameters)
65
- @local_variables = []
66
65
  @name = Name.new(exp[1])
67
66
  @num_statements = 0
68
67
  @calls = Hash.new(0)
@@ -114,25 +113,17 @@ module Reek
114
113
  @depends_on_self = true
115
114
  end
116
115
 
117
- def record_local_variable(sym)
118
- @local_variables << Name.new(sym)
119
- end
120
-
121
116
  def outer_name
122
117
  "#{@outer.outer_name}#{@name}/"
123
118
  end
124
119
 
125
- def to_s
126
- "#{@outer.outer_name}#{@name}"
127
- end
128
-
129
120
  def envious_receivers
130
121
  return [] if @refs.self_is_max?
131
122
  @refs.max_keys
132
123
  end
133
124
 
134
125
  def variable_names
135
- @parameters.names + @local_variables
126
+ @parameters.names + @local_variables.to_a
136
127
  end
137
128
  end
138
129
  end