reek 3.2.1 → 3.3.0

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/Rakefile +0 -1
  4. data/config/defaults.reek +1 -1
  5. data/features/samples.feature +17 -16
  6. data/lib/reek.rb +0 -1
  7. data/lib/reek/ast/ast_node_class_map.rb +5 -1
  8. data/lib/reek/ast/node.rb +10 -3
  9. data/lib/reek/ast/object_refs.rb +11 -5
  10. data/lib/reek/ast/reference_collector.rb +6 -2
  11. data/lib/reek/ast/sexp_extensions.rb +42 -1
  12. data/lib/reek/ast/sexp_formatter.rb +2 -1
  13. data/lib/reek/cli/application.rb +12 -9
  14. data/lib/reek/cli/command.rb +6 -0
  15. data/lib/reek/cli/input.rb +4 -4
  16. data/lib/reek/cli/option_interpreter.rb +11 -7
  17. data/lib/reek/cli/options.rb +42 -40
  18. data/lib/reek/cli/reek_command.rb +3 -3
  19. data/lib/reek/cli/silencer.rb +12 -3
  20. data/lib/reek/cli/warning_collector.rb +8 -3
  21. data/lib/reek/code_comment.rb +6 -1
  22. data/lib/reek/configuration/app_configuration.rb +65 -100
  23. data/lib/reek/configuration/configuration_file_finder.rb +4 -13
  24. data/lib/reek/configuration/configuration_validator.rb +35 -0
  25. data/lib/reek/configuration/default_directive.rb +12 -0
  26. data/lib/reek/configuration/directory_directives.rb +54 -0
  27. data/lib/reek/configuration/excluded_paths.rb +18 -0
  28. data/lib/reek/context/code_context.rb +19 -17
  29. data/lib/reek/examiner.rb +9 -7
  30. data/lib/reek/rake/task.rb +12 -22
  31. data/lib/reek/report/formatter.rb +6 -1
  32. data/lib/reek/report/report.rb +22 -13
  33. data/lib/reek/smells/attribute.rb +6 -53
  34. data/lib/reek/smells/control_parameter.rb +21 -13
  35. data/lib/reek/smells/data_clump.rb +17 -9
  36. data/lib/reek/smells/duplicate_method_call.rb +12 -6
  37. data/lib/reek/smells/long_parameter_list.rb +2 -2
  38. data/lib/reek/smells/long_yield_list.rb +4 -4
  39. data/lib/reek/smells/nested_iterators.rb +4 -2
  40. data/lib/reek/smells/nil_check.rb +6 -2
  41. data/lib/reek/smells/repeated_conditional.rb +3 -3
  42. data/lib/reek/smells/smell_configuration.rb +17 -7
  43. data/lib/reek/smells/smell_detector.rb +24 -11
  44. data/lib/reek/smells/smell_repository.rb +1 -1
  45. data/lib/reek/smells/smell_warning.rb +6 -6
  46. data/lib/reek/smells/too_many_instance_variables.rb +2 -2
  47. data/lib/reek/smells/too_many_methods.rb +4 -4
  48. data/lib/reek/smells/too_many_statements.rb +4 -4
  49. data/lib/reek/smells/uncommunicative_method_name.rb +5 -5
  50. data/lib/reek/smells/uncommunicative_module_name.rb +6 -6
  51. data/lib/reek/smells/uncommunicative_parameter_name.rb +8 -4
  52. data/lib/reek/smells/uncommunicative_variable_name.rb +9 -5
  53. data/lib/reek/smells/utility_function.rb +1 -1
  54. data/lib/reek/source/source_code.rb +5 -1
  55. data/lib/reek/source/source_locator.rb +3 -2
  56. data/lib/reek/spec.rb +3 -3
  57. data/lib/reek/spec/should_reek.rb +10 -5
  58. data/lib/reek/spec/should_reek_of.rb +9 -6
  59. data/lib/reek/spec/should_reek_only_of.rb +13 -8
  60. data/lib/reek/tree_dresser.rb +6 -2
  61. data/lib/reek/tree_walker.rb +40 -32
  62. data/lib/reek/version.rb +1 -1
  63. data/reek.gemspec +1 -1
  64. data/spec/reek/ast/node_spec.rb +1 -2
  65. data/spec/reek/ast/object_refs_spec.rb +40 -42
  66. data/spec/reek/ast/sexp_extensions_spec.rb +98 -104
  67. data/spec/reek/cli/warning_collector_spec.rb +8 -12
  68. data/spec/reek/code_comment_spec.rb +3 -5
  69. data/spec/reek/configuration/app_configuration_spec.rb +43 -57
  70. data/spec/reek/configuration/configuration_file_finder_spec.rb +5 -7
  71. data/spec/reek/configuration/default_directive_spec.rb +13 -0
  72. data/spec/reek/configuration/directory_directives_spec.rb +89 -0
  73. data/spec/reek/configuration/excluded_paths_spec.rb +30 -0
  74. data/spec/reek/context/code_context_spec.rb +63 -62
  75. data/spec/reek/context/method_context_spec.rb +8 -12
  76. data/spec/reek/context/module_context_spec.rb +1 -1
  77. data/spec/reek/context/root_context_spec.rb +3 -7
  78. data/spec/reek/examiner_spec.rb +14 -25
  79. data/spec/reek/smells/attribute_spec.rb +2 -4
  80. data/spec/reek/smells/boolean_parameter_spec.rb +5 -7
  81. data/spec/reek/smells/class_variable_spec.rb +29 -44
  82. data/spec/reek/smells/control_parameter_spec.rb +7 -9
  83. data/spec/reek/smells/data_clump_spec.rb +25 -32
  84. data/spec/reek/smells/duplicate_method_call_spec.rb +8 -7
  85. data/spec/reek/smells/feature_envy_spec.rb +16 -17
  86. data/spec/reek/smells/irresponsible_module_spec.rb +2 -4
  87. data/spec/reek/smells/long_parameter_list_spec.rb +6 -9
  88. data/spec/reek/smells/long_yield_list_spec.rb +6 -9
  89. data/spec/reek/smells/nested_iterators_spec.rb +14 -16
  90. data/spec/reek/smells/repeated_conditional_spec.rb +25 -25
  91. data/spec/reek/smells/smell_configuration_spec.rb +32 -27
  92. data/spec/reek/smells/smell_detector_shared.rb +12 -13
  93. data/spec/reek/smells/smell_warning_spec.rb +54 -58
  94. data/spec/reek/smells/too_many_instance_variables_spec.rb +9 -9
  95. data/spec/reek/smells/too_many_methods_spec.rb +13 -14
  96. data/spec/reek/smells/too_many_statements_spec.rb +8 -10
  97. data/spec/reek/smells/uncommunicative_method_name_spec.rb +8 -9
  98. data/spec/reek/smells/uncommunicative_module_name_spec.rb +12 -13
  99. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +7 -10
  100. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +16 -20
  101. data/spec/reek/smells/utility_function_spec.rb +11 -15
  102. data/spec/reek/source/source_code_spec.rb +6 -11
  103. data/spec/reek/spec/should_reek_of_spec.rb +19 -30
  104. data/spec/reek/spec/should_reek_only_of_spec.rb +28 -34
  105. data/spec/reek/tree_walker_spec.rb +14 -2
  106. data/spec/spec_helper.rb +2 -3
  107. data/tasks/test.rake +0 -5
  108. metadata +10 -6
  109. data/docs/Configuration-Files.md +0 -49
  110. data/spec/gem/updates_spec.rb +0 -25
  111. data/spec/gem/yard_spec.rb +0 -11
  112. data/spec/reek/smells/behaves_like_variable_detector.rb +0 -39
@@ -17,15 +17,12 @@ module Reek
17
17
  #
18
18
  # TODO: Catch attributes declared "by hand"
19
19
  class Attribute < SmellDetector
20
- ATTR_DEFN_METHODS = [:attr_writer, :attr_accessor]
21
- VISIBILITY_MODIFIERS = [:private, :public, :protected]
22
-
23
20
  def initialize(*args)
24
21
  super
25
22
  end
26
23
 
27
24
  def self.contexts # :nodoc:
28
- [:class, :module]
25
+ [:sym]
29
26
  end
30
27
 
31
28
  #
@@ -34,13 +31,11 @@ module Reek
34
31
  # @return [Array<SmellWarning>]
35
32
  #
36
33
  def examine_context(ctx)
37
- @visiblity_tracker = {}
38
- @visiblity_mode = :public
39
34
  attributes_in(ctx).map do |attribute, line|
40
35
  SmellWarning.new self,
41
36
  context: ctx.full_name,
42
37
  lines: [line],
43
- message: "declares the writable attribute #{attribute}",
38
+ message: 'is a writable attribute',
44
39
  parameters: { name: attribute.to_s }
45
40
  end
46
41
  end
@@ -48,55 +43,13 @@ module Reek
48
43
  private
49
44
 
50
45
  def attributes_in(module_ctx)
51
- attributes = Set.new
52
- module_ctx.local_nodes(:send) do |call_node|
53
- attributes += track_attributes(call_node)
54
- end
55
- attributes.select { |name, _line| recorded_public_methods.include?(name) }
56
- end
57
-
58
- def track_attributes(call_node)
59
- if attribute_writer? call_node
60
- return track_arguments call_node.args, call_node.line
61
- end
62
- track_visibility call_node if visibility_modifier? call_node
63
- []
64
- end
65
-
66
- def attribute_writer?(call_node)
67
- ATTR_DEFN_METHODS.include?(call_node.method_name) ||
68
- attr_with_writable_flag?(call_node)
69
- end
70
-
71
- def attr_with_writable_flag?(call_node)
72
- call_node.method_name == :attr && call_node.args.last.type == :true
73
- end
74
-
75
- def track_arguments(args, line)
76
- args.select { |arg| arg.type == :sym }.map { |arg| track_argument(arg, line) }
77
- end
78
-
79
- def track_argument(arg, line)
80
- arg_name = arg.children.first
81
- @visiblity_tracker[arg_name] = @visiblity_mode
82
- [arg_name, line]
83
- end
84
-
85
- def visibility_modifier?(call_node)
86
- VISIBILITY_MODIFIERS.include?(call_node.method_name)
87
- end
88
-
89
- def track_visibility(call_node)
90
- if call_node.arg_names.any?
91
- call_node.arg_names.each { |arg| @visiblity_tracker[arg] = call_node.method_name }
46
+ if module_ctx.visibility == :public
47
+ call_node = module_ctx.exp
48
+ [[call_node.name, call_node.line]]
92
49
  else
93
- @visiblity_mode = call_node.method_name
50
+ []
94
51
  end
95
52
  end
96
-
97
- def recorded_public_methods
98
- @visiblity_tracker.select { |_, visbility| visbility == :public }
99
- end
100
53
  end
101
54
  end
102
55
  end
@@ -73,16 +73,20 @@ module Reek
73
73
  end
74
74
 
75
75
  def smells?
76
- @occurences.any?
76
+ occurences.any?
77
77
  end
78
78
 
79
79
  def lines
80
- @occurences.map(&:line)
80
+ occurences.map(&:line)
81
81
  end
82
82
 
83
83
  def name
84
- @param.to_s
84
+ param.to_s
85
85
  end
86
+
87
+ private
88
+
89
+ private_attr_reader :occurences, :param
86
90
  end
87
91
 
88
92
  # Finds cases of ControlParameter in a particular node for a particular parameter
@@ -108,13 +112,15 @@ module Reek
108
112
 
109
113
  private
110
114
 
115
+ private_attr_reader :node, :param
116
+
111
117
  def conditional_nodes
112
- @node.body_nodes(CONDITIONAL_NODE_TYPES)
118
+ node.body_nodes(CONDITIONAL_NODE_TYPES)
113
119
  end
114
120
 
115
121
  def nested_finders
116
122
  @nested_finders ||= conditional_nodes.flat_map do |node|
117
- self.class.new(node, @param)
123
+ self.class.new(node, param)
118
124
  end
119
125
  end
120
126
 
@@ -129,12 +135,12 @@ module Reek
129
135
 
130
136
  def uses_of_param_in_condition
131
137
  return [] unless condition
132
- condition.each_node(:lvar).select { |inner| inner.var_name == @param }
138
+ condition.each_node(:lvar).select { |inner| inner.var_name == param }
133
139
  end
134
140
 
135
141
  def condition
136
- return nil unless CONDITIONAL_NODE_TYPES.include? @node.type
137
- @node.condition
142
+ return nil unless CONDITIONAL_NODE_TYPES.include? node.type
143
+ node.condition
138
144
  end
139
145
 
140
146
  def regular_call_involving_param?(call_node)
@@ -150,12 +156,12 @@ module Reek
150
156
  end
151
157
 
152
158
  def call_involving_param?(call_node)
153
- call_node.each_node(:lvar).any? { |it| it.var_name == @param }
159
+ call_node.each_node(:lvar).any? { |it| it.var_name == param }
154
160
  end
155
161
 
156
162
  def uses_param_in_body?
157
- nodes = @node.body_nodes([:lvar], [:if, :case, :and, :or])
158
- nodes.any? { |lvar_node| lvar_node.var_name == @param }
163
+ nodes = node.body_nodes([:lvar], [:if, :case, :and, :or])
164
+ nodes.any? { |lvar_node| lvar_node.var_name == param }
159
165
  end
160
166
  end
161
167
 
@@ -175,12 +181,14 @@ module Reek
175
181
 
176
182
  private
177
183
 
184
+ private_attr_reader :context
185
+
178
186
  def potential_parameters
179
- @context.exp.parameter_names
187
+ context.exp.parameter_names
180
188
  end
181
189
 
182
190
  def find_matches(param)
183
- ControlParameterFinder.new(@context.exp, param).find_matches
191
+ ControlParameterFinder.new(context.exp, param).find_matches
184
192
  end
185
193
  end
186
194
  end
@@ -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.
@@ -37,7 +37,7 @@ module Reek
37
37
 
38
38
  BLOCK_GIVEN_CONDITION = ::Parser::AST::Node.new(:send, [nil, :block_given?])
39
39
 
40
- def self.contexts # :nodoc:
40
+ def self.contexts # :nodoc:
41
41
  [:class]
42
42
  end
43
43
 
@@ -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
@@ -1,3 +1,5 @@
1
+ require 'private_attr/everywhere'
2
+
1
3
  module Reek
2
4
  module Smells
3
5
  #
@@ -17,8 +19,8 @@ module Reek
17
19
  @options = hash
18
20
  end
19
21
 
20
- def merge!(options)
21
- @options.merge!(options)
22
+ def merge!(new_options)
23
+ options.merge!(new_options)
22
24
  end
23
25
 
24
26
  #
@@ -26,11 +28,11 @@ module Reek
26
28
  #--
27
29
  # SMELL: Getter
28
30
  def enabled?
29
- @options[ENABLED_KEY]
31
+ options[ENABLED_KEY]
30
32
  end
31
33
 
32
34
  def overrides_for(context)
33
- Overrides.new(@options.fetch(OVERRIDES_KEY, {})).for_context(context)
35
+ Overrides.new(options.fetch(OVERRIDES_KEY, {})).for_context(context)
34
36
  end
35
37
 
36
38
  # Retrieves the value, if any, for the given +key+.
@@ -39,8 +41,12 @@ module Reek
39
41
  #
40
42
  def value(key, context, fall_back)
41
43
  overrides_for(context).each { |conf| return conf[key] if conf.key?(key) }
42
- @options.fetch(key, fall_back)
44
+ options.fetch(key, fall_back)
43
45
  end
46
+
47
+ private
48
+
49
+ private_attr_reader :options
44
50
  end
45
51
 
46
52
  #
@@ -54,9 +60,13 @@ module Reek
54
60
 
55
61
  # Find any overrides that match the supplied context
56
62
  def for_context(context)
57
- contexts = @hash.keys.select { |ckey| context.matches?([ckey]) }
58
- contexts.map { |exc| @hash[exc] }
63
+ contexts = hash.keys.select { |ckey| context.matches?([ckey]) }
64
+ contexts.map { |exc| hash[exc] }
59
65
  end
66
+
67
+ private
68
+
69
+ private_attr_reader :hash
60
70
  end
61
71
  end
62
72
  end