reek 3.1 → 3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/{CHANGELOG → CHANGELOG.md} +150 -123
  4. data/README.md +61 -21
  5. data/Rakefile +2 -1
  6. data/bin/reek +1 -0
  7. data/config/defaults.reek +2 -2
  8. data/docs/Attribute.md +9 -13
  9. data/docs/Basic-Smell-Options.md +2 -2
  10. data/docs/Command-Line-Options.md +2 -2
  11. data/docs/Too-Many-Instance-Variables.md +1 -1
  12. data/features/samples.feature +22 -31
  13. data/features/step_definitions/sample_file_steps.rb +2 -2
  14. data/features/support/env.rb +1 -0
  15. data/lib/reek.rb +1 -0
  16. data/lib/reek/ast/ast_node_class_map.rb +5 -1
  17. data/lib/reek/ast/node.rb +4 -2
  18. data/lib/reek/ast/object_refs.rb +9 -5
  19. data/lib/reek/ast/reference_collector.rb +4 -2
  20. data/lib/reek/cli/application.rb +12 -9
  21. data/lib/reek/cli/command.rb +4 -0
  22. data/lib/reek/cli/input.rb +4 -4
  23. data/lib/reek/cli/option_interpreter.rb +11 -7
  24. data/lib/reek/cli/options.rb +42 -40
  25. data/lib/reek/cli/reek_command.rb +3 -3
  26. data/lib/reek/cli/warning_collector.rb +7 -3
  27. data/lib/reek/code_comment.rb +5 -1
  28. data/lib/reek/configuration/app_configuration.rb +4 -4
  29. data/lib/reek/context/code_context.rb +19 -17
  30. data/lib/reek/examiner.rb +8 -6
  31. data/lib/reek/rake/task.rb +13 -22
  32. data/lib/reek/report/formatter.rb +5 -1
  33. data/lib/reek/report/report.rb +46 -44
  34. data/lib/reek/smells/attribute.rb +42 -24
  35. data/lib/reek/smells/control_parameter.rb +21 -13
  36. data/lib/reek/smells/data_clump.rb +17 -9
  37. data/lib/reek/smells/duplicate_method_call.rb +12 -6
  38. data/lib/reek/smells/long_parameter_list.rb +2 -2
  39. data/lib/reek/smells/long_yield_list.rb +4 -4
  40. data/lib/reek/smells/nested_iterators.rb +4 -2
  41. data/lib/reek/smells/nil_check.rb +6 -2
  42. data/lib/reek/smells/repeated_conditional.rb +2 -2
  43. data/lib/reek/smells/smell_configuration.rb +15 -7
  44. data/lib/reek/smells/smell_detector.rb +23 -10
  45. data/lib/reek/smells/smell_repository.rb +9 -16
  46. data/lib/reek/smells/smell_warning.rb +6 -6
  47. data/lib/reek/smells/too_many_instance_variables.rb +4 -4
  48. data/lib/reek/smells/too_many_methods.rb +2 -2
  49. data/lib/reek/smells/too_many_statements.rb +4 -4
  50. data/lib/reek/smells/uncommunicative_method_name.rb +5 -5
  51. data/lib/reek/smells/uncommunicative_module_name.rb +5 -5
  52. data/lib/reek/smells/uncommunicative_parameter_name.rb +8 -4
  53. data/lib/reek/smells/uncommunicative_variable_name.rb +8 -4
  54. data/lib/reek/source/source_code.rb +6 -2
  55. data/lib/reek/source/source_locator.rb +4 -4
  56. data/lib/reek/spec/should_reek.rb +9 -4
  57. data/lib/reek/spec/should_reek_of.rb +8 -5
  58. data/lib/reek/spec/should_reek_only_of.rb +12 -8
  59. data/lib/reek/tree_dresser.rb +6 -2
  60. data/lib/reek/tree_walker.rb +28 -22
  61. data/lib/reek/version.rb +1 -1
  62. data/reek.gemspec +6 -5
  63. data/spec/gem/yard_spec.rb +6 -9
  64. data/spec/reek/code_comment_spec.rb +1 -1
  65. data/spec/reek/report/xml_report_spec.rb +11 -21
  66. data/spec/reek/smells/attribute_spec.rb +73 -57
  67. data/spec/reek/smells/too_many_instance_variables_spec.rb +26 -12
  68. data/spec/reek/source/source_locator_spec.rb +2 -2
  69. data/spec/samples/checkstyle.xml +12 -1
  70. data/spec/spec_helper.rb +1 -0
  71. metadata +20 -7
  72. data/spec/samples/unusual_syntax.rb +0 -21
@@ -52,9 +52,9 @@ module Reek
52
52
  # @return [Array<SmellWarning>]
53
53
  #
54
54
  def examine_context(ctx)
55
- @max_copies = value(MAX_COPIES_KEY, ctx, DEFAULT_MAX_COPIES)
56
- @min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
57
- MethodGroup.new(ctx, @min_clump_size, @max_copies).clumps.map do |clump, methods|
55
+ max_copies = value(MAX_COPIES_KEY, ctx, DEFAULT_MAX_COPIES)
56
+ min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
57
+ MethodGroup.new(ctx, min_clump_size, max_copies).clumps.map do |clump, methods|
58
58
  print_clump = DataClump.print_clump(clump)
59
59
  SmellWarning.new self,
60
60
  context: ctx.full_name,
@@ -88,10 +88,10 @@ module Reek
88
88
  end
89
89
 
90
90
  def candidate_clumps
91
- @candidate_methods.each_cons(@max_copies + 1).map do |methods|
91
+ candidate_methods.each_cons(max_copies + 1).map do |methods|
92
92
  common_argument_names_for(methods)
93
93
  end.select do |clump|
94
- clump.length >= @min_clump_size
94
+ clump.length >= min_clump_size
95
95
  end.uniq
96
96
  end
97
97
 
@@ -100,7 +100,7 @@ module Reek
100
100
  end
101
101
 
102
102
  def methods_containing_clump(clump)
103
- @candidate_methods.select { |method| clump & method.arg_names == clump }
103
+ candidate_methods.select { |method| clump & method.arg_names == clump }
104
104
  end
105
105
 
106
106
  def clumps
@@ -108,6 +108,10 @@ module Reek
108
108
  [clump, methods_containing_clump(clump)]
109
109
  end
110
110
  end
111
+
112
+ private
113
+
114
+ private_attr_reader :candidate_methods, :max_copies, :min_clump_size
111
115
  end
112
116
 
113
117
  # A method definition and a copy of its parameters
@@ -118,15 +122,19 @@ module Reek
118
122
  end
119
123
 
120
124
  def arg_names
121
- @arg_names ||= @defn.arg_names.compact.sort
125
+ @arg_names ||= defn.arg_names.compact.sort
122
126
  end
123
127
 
124
128
  def line
125
- @defn.line
129
+ defn.line
126
130
  end
127
131
 
128
132
  def name
129
- @defn.name.to_s # BUG: should report the symbols!
133
+ defn.name.to_s # BUG: should report the symbols!
130
134
  end
135
+
136
+ private
137
+
138
+ private_attr_reader :defn
131
139
  end
132
140
  end
@@ -68,20 +68,24 @@ module Reek
68
68
  end
69
69
 
70
70
  def record(occurence)
71
- @occurences.push occurence
71
+ occurences.push occurence
72
72
  end
73
73
 
74
74
  def call
75
- @call ||= @call_node.format_to_ruby
75
+ @call ||= call_node.format_to_ruby
76
76
  end
77
77
 
78
78
  def occurs
79
- @occurences.length
79
+ occurences.length
80
80
  end
81
81
 
82
82
  def lines
83
- @occurences.map(&:line)
83
+ occurences.map(&:line)
84
84
  end
85
+
86
+ private
87
+
88
+ private_attr_reader :call_node, :occurences
85
89
  end
86
90
 
87
91
  # Collects all calls in a given context
@@ -106,6 +110,8 @@ module Reek
106
110
 
107
111
  private
108
112
 
113
+ private_attr_reader :allow_calls, :max_allowed_calls
114
+
109
115
  def collect_calls(result)
110
116
  context.each_node(:send, [:mlhs]) do |call_node|
111
117
  next if call_node.object_creation_call?
@@ -118,7 +124,7 @@ module Reek
118
124
  end
119
125
 
120
126
  def smelly_call?(found_call)
121
- found_call.occurs > @max_allowed_calls && !allow_calls?(found_call.call)
127
+ found_call.occurs > max_allowed_calls && !allow_calls?(found_call.call)
122
128
  end
123
129
 
124
130
  def simple_method_call?(call_node)
@@ -126,7 +132,7 @@ module Reek
126
132
  end
127
133
 
128
134
  def allow_calls?(method)
129
- @allow_calls.any? { |allow| /#{allow}/ =~ method }
135
+ allow_calls.any? { |allow| /#{allow}/ =~ method }
130
136
  end
131
137
  end
132
138
  end
@@ -35,9 +35,9 @@ module Reek
35
35
  # @return [Array<SmellWarning>]
36
36
  #
37
37
  def examine_context(ctx)
38
- @max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, ctx, DEFAULT_MAX_ALLOWED_PARAMS)
38
+ max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, ctx, DEFAULT_MAX_ALLOWED_PARAMS)
39
39
  count = ctx.exp.arg_names.length
40
- return [] if count <= @max_allowed_params
40
+ return [] if count <= max_allowed_params
41
41
  [SmellWarning.new(self,
42
42
  context: ctx.full_name,
43
43
  lines: [ctx.exp.line],
@@ -29,11 +29,11 @@ module Reek
29
29
  # @return [Array<SmellWarning>]
30
30
  #
31
31
  def examine_context(method_ctx)
32
- @max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY,
33
- method_ctx,
34
- DEFAULT_MAX_ALLOWED_PARAMS)
32
+ max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY,
33
+ method_ctx,
34
+ DEFAULT_MAX_ALLOWED_PARAMS)
35
35
  method_ctx.local_nodes(:yield).select do |yield_node|
36
- yield_node.args.length > @max_allowed_params
36
+ yield_node.args.length > max_allowed_params
37
37
  end.map do |yield_node|
38
38
  count = yield_node.args.length
39
39
  SmellWarning.new self,
@@ -50,8 +50,10 @@ module Reek
50
50
 
51
51
  private
52
52
 
53
+ private_attr_accessor :ignore_iterators
54
+
53
55
  def find_deepest_iterator(ctx)
54
- @ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
56
+ self.ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
55
57
 
56
58
  find_iters(ctx.exp, 1).sort_by { |item| item[1] }.last
57
59
  end
@@ -73,7 +75,7 @@ module Reek
73
75
 
74
76
  def ignored_iterator?(exp)
75
77
  name = exp.call.method_name.to_s
76
- @ignore_iterators.any? { |pattern| /#{pattern}/ =~ name }
78
+ ignore_iterators.any? { |pattern| /#{pattern}/ =~ name }
77
79
  end
78
80
  end
79
81
  end
@@ -36,10 +36,14 @@ module Reek
36
36
  end
37
37
 
38
38
  def smelly_nodes
39
- @nodes.select do |when_node|
40
- @detector.detect(when_node)
39
+ nodes.select do |when_node|
40
+ detector.detect(when_node)
41
41
  end
42
42
  end
43
+
44
+ private
45
+
46
+ private_attr_reader :detector, :nodes
43
47
  end
44
48
 
45
49
  # Detect 'call' nodes which perform a nil check.
@@ -51,9 +51,9 @@ module Reek
51
51
  # @return [Array<SmellWarning>]
52
52
  #
53
53
  def examine_context(ctx)
54
- @max_identical_ifs = value(MAX_IDENTICAL_IFS_KEY, ctx, DEFAULT_MAX_IFS)
54
+ max_identical_ifs = value(MAX_IDENTICAL_IFS_KEY, ctx, DEFAULT_MAX_IFS)
55
55
  conditional_counts(ctx).select do |_key, lines|
56
- lines.length > @max_identical_ifs
56
+ lines.length > max_identical_ifs
57
57
  end.map do |key, lines|
58
58
  occurs = lines.length
59
59
  expression = key.format_to_ruby
@@ -17,8 +17,8 @@ module Reek
17
17
  @options = hash
18
18
  end
19
19
 
20
- def merge!(options)
21
- @options.merge!(options)
20
+ def merge!(new_options)
21
+ options.merge!(new_options)
22
22
  end
23
23
 
24
24
  #
@@ -26,11 +26,11 @@ module Reek
26
26
  #--
27
27
  # SMELL: Getter
28
28
  def enabled?
29
- @options[ENABLED_KEY]
29
+ options[ENABLED_KEY]
30
30
  end
31
31
 
32
32
  def overrides_for(context)
33
- Overrides.new(@options.fetch(OVERRIDES_KEY, {})).for_context(context)
33
+ Overrides.new(options.fetch(OVERRIDES_KEY, {})).for_context(context)
34
34
  end
35
35
 
36
36
  # Retrieves the value, if any, for the given +key+.
@@ -39,8 +39,12 @@ module Reek
39
39
  #
40
40
  def value(key, context, fall_back)
41
41
  overrides_for(context).each { |conf| return conf[key] if conf.key?(key) }
42
- @options.fetch(key, fall_back)
42
+ options.fetch(key, fall_back)
43
43
  end
44
+
45
+ private
46
+
47
+ private_attr_reader :options
44
48
  end
45
49
 
46
50
  #
@@ -54,9 +58,13 @@ module Reek
54
58
 
55
59
  # Find any overrides that match the supplied context
56
60
  def for_context(context)
57
- contexts = @hash.keys.select { |ckey| context.matches?([ckey]) }
58
- contexts.map { |exc| @hash[exc] }
61
+ contexts = hash.keys.select { |ckey| context.matches?([ckey]) }
62
+ contexts.map { |exc| hash[exc] }
59
63
  end
64
+
65
+ private
66
+
67
+ private_attr_reader :hash
60
68
  end
61
69
  end
62
70
  end
@@ -38,12 +38,17 @@ module Reek
38
38
  end
39
39
 
40
40
  def inherited(subclass)
41
- @subclasses ||= []
42
- @subclasses << subclass
41
+ subclasses << subclass
43
42
  end
44
43
 
45
44
  def descendants
46
- @subclasses
45
+ subclasses
46
+ end
47
+
48
+ private
49
+
50
+ def subclasses
51
+ @subclasses ||= []
47
52
  end
48
53
  end
49
54
 
@@ -78,17 +83,17 @@ module Reek
78
83
  end
79
84
 
80
85
  def register(hooks)
81
- return unless @config.enabled?
86
+ return unless config.enabled?
82
87
  self.class.contexts.each { |ctx| hooks[ctx] << self }
83
88
  end
84
89
 
85
90
  # SMELL: Getter (only used in 1 test)
86
91
  def enabled?
87
- @config.enabled?
92
+ config.enabled?
88
93
  end
89
94
 
90
- def configure_with(config)
91
- @config.merge!(config)
95
+ def configure_with(new_config)
96
+ config.merge!(new_config)
92
97
  end
93
98
 
94
99
  def examine(context)
@@ -96,7 +101,7 @@ module Reek
96
101
  return if exception?(context)
97
102
 
98
103
  sm = examine_context(context)
99
- @smells_found += sm
104
+ self.smells_found += sm
100
105
  end
101
106
 
102
107
  def enabled_for?(context)
@@ -108,16 +113,24 @@ module Reek
108
113
  end
109
114
 
110
115
  def report_on(report)
111
- @smells_found.each { |smell| smell.report_on(report) }
116
+ smells_found.each { |smell| smell.report_on(report) }
112
117
  end
113
118
 
114
119
  def value(key, ctx, fall_back)
115
- config_for(ctx)[key] || @config.value(key, ctx, fall_back)
120
+ config_for(ctx)[key] || config.value(key, ctx, fall_back)
116
121
  end
117
122
 
118
123
  def config_for(ctx)
119
124
  ctx.config_for(self.class)
120
125
  end
126
+
127
+ protected
128
+
129
+ attr_writer :smells_found
130
+
131
+ private
132
+
133
+ private_attr_reader :config
121
134
  end
122
135
  end
123
136
  end
@@ -16,10 +16,9 @@ module Reek
16
16
  def initialize(source_description: nil,
17
17
  smell_types: self.class.smell_types,
18
18
  configuration: Configuration::AppConfiguration.new)
19
- self.source_via = source_description
20
- self.typed_detectors = nil
21
- self.configuration = configuration
22
- self.smell_types = smell_types
19
+ @source_via = source_description
20
+ @configuration = configuration
21
+ @smell_types = smell_types
23
22
 
24
23
  configuration.directive_for(source_via).each do |klass, config|
25
24
  configure klass, config
@@ -43,25 +42,19 @@ module Reek
43
42
  end
44
43
 
45
44
  def detectors
46
- @initialized_detectors ||= begin
47
- @detectors = {}
48
- smell_types.each do |klass|
49
- @detectors[klass] = klass.new(source_via)
50
- end
51
- @detectors
52
- end
45
+ @initialized_detectors ||= smell_types.map do |klass|
46
+ { klass => klass.new(source_via) }
47
+ end.reduce({}, :merge)
53
48
  end
54
49
 
55
50
  private
56
51
 
57
- attr_accessor :typed_detectors, :configuration, :source_via, :smell_types
52
+ private_attr_reader :configuration, :source_via, :smell_types
58
53
 
59
54
  def smell_listeners
60
- unless typed_detectors
61
- self.typed_detectors = Hash.new { |hash, key| hash[key] = [] }
62
- detectors.each_value { |detector| detector.register(typed_detectors) }
55
+ @smell_listeners ||= Hash.new { |hash, key| hash[key] = [] }.tap do |listeners|
56
+ detectors.each_value { |detector| detector.register(listeners) }
63
57
  end
64
- typed_detectors
65
58
  end
66
59
  end
67
60
  end
@@ -8,15 +8,15 @@ module Reek
8
8
  class SmellWarning
9
9
  include Comparable
10
10
  extend Forwardable
11
- attr_accessor :smell_detector, :context, :lines, :message, :parameters
11
+ attr_reader :context, :lines, :message, :parameters, :smell_detector
12
12
  def_delegators :smell_detector, :smell_category, :smell_type, :source
13
13
 
14
14
  def initialize(smell_detector, options = {})
15
- self.smell_detector = smell_detector
16
- self.context = options.fetch(:context, '').to_s
17
- self.lines = options.fetch(:lines)
18
- self.message = options.fetch(:message)
19
- self.parameters = options.fetch(:parameters, {})
15
+ @smell_detector = smell_detector
16
+ @context = options.fetch(:context, '').to_s
17
+ @lines = options.fetch(:lines)
18
+ @message = options.fetch(:message)
19
+ @parameters = options.fetch(:parameters, {})
20
20
  end
21
21
 
22
22
  def hash
@@ -16,13 +16,13 @@ module Reek
16
16
  # The name of the config field that sets the maximum number of instance
17
17
  # variables permitted in a class.
18
18
  MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
19
- DEFAULT_MAX_IVARS = 9
19
+ DEFAULT_MAX_IVARS = 4
20
20
 
21
21
  def self.smell_category
22
22
  'LargeClass'
23
23
  end
24
24
 
25
- def self.contexts # :nodoc:
25
+ def self.contexts
26
26
  [:class]
27
27
  end
28
28
 
@@ -39,9 +39,9 @@ module Reek
39
39
  # @return [Array<SmellWarning>]
40
40
  #
41
41
  def examine_context(ctx)
42
- @max_allowed_ivars = value(MAX_ALLOWED_IVARS_KEY, ctx, DEFAULT_MAX_IVARS)
42
+ max_allowed_ivars = value(MAX_ALLOWED_IVARS_KEY, ctx, DEFAULT_MAX_IVARS)
43
43
  count = ctx.local_nodes(:ivasgn).map { |ivasgn| ivasgn[1] }.uniq.length
44
- return [] if count <= @max_allowed_ivars
44
+ return [] if count <= max_allowed_ivars
45
45
  [SmellWarning.new(self,
46
46
  context: ctx.full_name,
47
47
  lines: [ctx.exp.line],
@@ -41,9 +41,9 @@ module Reek
41
41
  # @return [Array<SmellWarning>]
42
42
  #
43
43
  def examine_context(ctx)
44
- @max_allowed_methods = value(MAX_ALLOWED_METHODS_KEY, ctx, DEFAULT_MAX_METHODS)
44
+ max_allowed_methods = value(MAX_ALLOWED_METHODS_KEY, ctx, DEFAULT_MAX_METHODS)
45
45
  actual = ctx.node_instance_methods.length
46
- return [] if actual <= @max_allowed_methods
46
+ return [] if actual <= max_allowed_methods
47
47
  [SmellWarning.new(self,
48
48
  context: ctx.full_name,
49
49
  lines: [ctx.exp.line],
@@ -33,11 +33,11 @@ module Reek
33
33
  # @return [Array<SmellWarning>]
34
34
  #
35
35
  def examine_context(ctx)
36
- @max_allowed_statements = value(MAX_ALLOWED_STATEMENTS_KEY,
37
- ctx,
38
- DEFAULT_MAX_STATEMENTS)
36
+ max_allowed_statements = value(MAX_ALLOWED_STATEMENTS_KEY,
37
+ ctx,
38
+ DEFAULT_MAX_STATEMENTS)
39
39
  count = ctx.num_statements
40
- return [] if count <= @max_allowed_statements
40
+ return [] if count <= max_allowed_statements
41
41
  [SmellWarning.new(self,
42
42
  context: ctx.full_name,
43
43
  lines: [ctx.exp.line],