reek 3.1 → 3.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 (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],