reek 3.2.1 → 3.3.0

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