reek 1.2.2 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
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