reek 3.7.1 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -1
  3. data/CHANGELOG.md +5 -0
  4. data/README.md +1 -1
  5. data/defaults.reek +3 -0
  6. data/docs/Code-Smells.md +1 -0
  7. data/docs/How-reek-works-internally.md +3 -2
  8. data/docs/Unused-Private-Method.md +47 -0
  9. data/features/samples.feature +22 -2
  10. data/features/step_definitions/sample_file_steps.rb +3 -0
  11. data/lib/reek/ast/node.rb +1 -2
  12. data/lib/reek/ast/object_refs.rb +30 -7
  13. data/lib/reek/code_comment.rb +25 -19
  14. data/lib/reek/context/class_context.rb +11 -0
  15. data/lib/reek/context/code_context.rb +58 -78
  16. data/lib/reek/context/module_context.rb +18 -0
  17. data/lib/reek/context/send_context.rb +17 -0
  18. data/lib/reek/context/singleton_method_context.rb +0 -3
  19. data/lib/reek/context/statement_counter.rb +32 -0
  20. data/lib/reek/context/visibility_tracker.rb +54 -0
  21. data/lib/reek/context_builder.rb +473 -0
  22. data/lib/reek/examiner.rb +14 -14
  23. data/lib/reek/smells/feature_envy.rb +3 -3
  24. data/lib/reek/smells/smell_detector.rb +1 -0
  25. data/lib/reek/smells/smell_repository.rb +11 -0
  26. data/lib/reek/smells/too_many_statements.rb +1 -1
  27. data/lib/reek/smells/unused_private_method.rb +82 -0
  28. data/lib/reek/smells/utility_function.rb +1 -1
  29. data/lib/reek/smells.rb +1 -0
  30. data/lib/reek/version.rb +1 -1
  31. data/spec/reek/ast/object_refs_spec.rb +20 -20
  32. data/spec/reek/cli/input_spec.rb +55 -0
  33. data/spec/reek/code_comment_spec.rb +10 -0
  34. data/spec/reek/context/code_context_spec.rb +8 -0
  35. data/spec/reek/context/module_context_spec.rb +10 -8
  36. data/spec/reek/context_builder_spec.rb +221 -0
  37. data/spec/reek/examiner_spec.rb +13 -0
  38. data/spec/reek/smells/boolean_parameter_spec.rb +2 -0
  39. data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
  40. data/spec/reek/smells/feature_envy_spec.rb +1 -1
  41. data/spec/reek/smells/too_many_statements_spec.rb +3 -3
  42. data/spec/reek/smells/unused_private_method_spec.rb +110 -0
  43. data/spec/spec_helper.rb +7 -0
  44. data/tasks/console.rake +5 -0
  45. metadata +13 -5
  46. data/lib/reek/tree_walker.rb +0 -237
  47. data/spec/reek/context/singleton_method_context_spec.rb +0 -16
  48. data/spec/reek/tree_walker_spec.rb +0 -237
@@ -0,0 +1,110 @@
1
+ require_relative '../../spec_helper'
2
+ require_lib 'reek/smells/unused_private_method'
3
+ require_lib 'reek/examiner'
4
+ require_relative 'smell_detector_shared'
5
+
6
+ RSpec.describe Reek::Smells::UnusedPrivateMethod do
7
+ let(:detector) { build(:smell_detector, smell_type: :UnusedPrivateMethod) }
8
+
9
+ it_should_behave_like 'SmellDetector'
10
+
11
+ context 'unused private methods' do
12
+ it 'reports instance methods' do
13
+ source = <<-EOF
14
+ class Car
15
+ private
16
+ def start; end
17
+ def drive; end
18
+ end
19
+ EOF
20
+
21
+ expect(source).to reek_of(:UnusedPrivateMethod, name: :start)
22
+ expect(source).to reek_of(:UnusedPrivateMethod, name: :drive)
23
+ end
24
+
25
+ it 'creates warnings correctly' do
26
+ source = <<-EOF
27
+ class Car
28
+ private
29
+ def start; end
30
+ def drive; end
31
+ end
32
+ EOF
33
+
34
+ examiner = Reek::Examiner.new(source, 'UnusedPrivateMethod')
35
+
36
+ first_warning = examiner.smells.first
37
+ expect(first_warning.smell_category).to eq(Reek::Smells::UnusedPrivateMethod.smell_category)
38
+ expect(first_warning.smell_type).to eq(Reek::Smells::UnusedPrivateMethod.smell_type)
39
+ expect(first_warning.parameters[:name]).to eq(:drive)
40
+ expect(first_warning.lines).to eq([4])
41
+
42
+ second_warning = examiner.smells.last
43
+ expect(second_warning.smell_category).to eq(Reek::Smells::UnusedPrivateMethod.smell_category)
44
+ expect(second_warning.smell_type).to eq(Reek::Smells::UnusedPrivateMethod.smell_type)
45
+ expect(second_warning.parameters[:name]).to eq(:start)
46
+ expect(second_warning.lines).to eq([3])
47
+ end
48
+ end
49
+
50
+ describe 'configuring the detector via source code comment' do
51
+ it 'does not report methods we excluded' do
52
+ source = <<-EOF
53
+ # :reek:UnusedPrivateMethod: { exclude: [ start ] }
54
+ class Car
55
+ private
56
+ def start; end
57
+ def drive; end
58
+ end
59
+ EOF
60
+
61
+ examiner = Reek::Examiner.new(source, 'UnusedPrivateMethod')
62
+
63
+ expect(examiner.smells.size).to eq(1)
64
+ warning_for_drive = examiner.smells.first
65
+ expect(warning_for_drive.parameters[:name]).to eq(:drive)
66
+ end
67
+ end
68
+
69
+ context 'used private methods' do
70
+ it 'are not reported' do
71
+ source = <<-EOF
72
+ class Car
73
+ def drive
74
+ start
75
+ end
76
+
77
+ private
78
+ def start; end
79
+ end
80
+ EOF
81
+
82
+ expect(source).not_to reek_of(:UnusedPrivateMethod)
83
+ end
84
+ end
85
+
86
+ context 'unused protected methods' do
87
+ it 'are not reported' do
88
+ source = <<-EOF
89
+ class Car
90
+ protected
91
+ def start; end
92
+ end
93
+ EOF
94
+
95
+ expect(source).not_to reek_of(:UnusedPrivateMethod)
96
+ end
97
+ end
98
+
99
+ context 'unused public methods' do
100
+ it 'are not reported' do
101
+ source = <<-EOF
102
+ class Car
103
+ def start; end
104
+ end
105
+ EOF
106
+
107
+ expect(source).not_to reek_of(:UnusedPrivateMethod)
108
+ end
109
+ end
110
+ end
data/spec/spec_helper.rb CHANGED
@@ -33,6 +33,13 @@ module Helpers
33
33
  configuration
34
34
  end
35
35
 
36
+ # @param code [String] The given code.
37
+ #
38
+ # @return syntax_tree [Reek::AST::Node]
39
+ def syntax_tree(code)
40
+ Reek::Source::SourceCode.from(code).syntax_tree
41
+ end
42
+
36
43
  # :reek:UncommunicativeMethodName
37
44
  def sexp(type, *children)
38
45
  @klass_map ||= Reek::AST::ASTNodeClassMap.new
@@ -0,0 +1,5 @@
1
+ desc 'Starts the interactive console'
2
+ task :console do
3
+ require 'pry'
4
+ Pry.start
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reek
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.1
4
+ version: 3.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Rutherford
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-11-29 00:00:00.000000000 Z
14
+ date: 2015-12-27 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: codeclimate-engine-rb
@@ -276,6 +276,7 @@ files:
276
276
  - docs/Uncommunicative-Parameter-Name.md
277
277
  - docs/Uncommunicative-Variable-Name.md
278
278
  - docs/Unused-Parameters.md
279
+ - docs/Unused-Private-Method.md
279
280
  - docs/Utility-Function.md
280
281
  - docs/Versioning-Policy.md
281
282
  - docs/YAML-Reports.md
@@ -342,11 +343,16 @@ files:
342
343
  - lib/reek/configuration/directory_directives.rb
343
344
  - lib/reek/configuration/excluded_paths.rb
344
345
  - lib/reek/context/attribute_context.rb
346
+ - lib/reek/context/class_context.rb
345
347
  - lib/reek/context/code_context.rb
346
348
  - lib/reek/context/method_context.rb
347
349
  - lib/reek/context/module_context.rb
348
350
  - lib/reek/context/root_context.rb
351
+ - lib/reek/context/send_context.rb
349
352
  - lib/reek/context/singleton_method_context.rb
353
+ - lib/reek/context/statement_counter.rb
354
+ - lib/reek/context/visibility_tracker.rb
355
+ - lib/reek/context_builder.rb
350
356
  - lib/reek/examiner.rb
351
357
  - lib/reek/rake/task.rb
352
358
  - lib/reek/report.rb
@@ -384,6 +390,7 @@ files:
384
390
  - lib/reek/smells/uncommunicative_parameter_name.rb
385
391
  - lib/reek/smells/uncommunicative_variable_name.rb
386
392
  - lib/reek/smells/unused_parameters.rb
393
+ - lib/reek/smells/unused_private_method.rb
387
394
  - lib/reek/smells/utility_function.rb
388
395
  - lib/reek/source/source_code.rb
389
396
  - lib/reek/source/source_locator.rb
@@ -393,7 +400,6 @@ files:
393
400
  - lib/reek/spec/should_reek_only_of.rb
394
401
  - lib/reek/spec/smell_matcher.rb
395
402
  - lib/reek/tree_dresser.rb
396
- - lib/reek/tree_walker.rb
397
403
  - lib/reek/version.rb
398
404
  - logo/reek.bw.png
399
405
  - logo/reek.bw.svg
@@ -409,6 +415,7 @@ files:
409
415
  - spec/reek/ast/reference_collector_spec.rb
410
416
  - spec/reek/ast/sexp_extensions_spec.rb
411
417
  - spec/reek/ast/sexp_formatter_spec.rb
418
+ - spec/reek/cli/input_spec.rb
412
419
  - spec/reek/cli/option_interpreter_spec.rb
413
420
  - spec/reek/cli/options_spec.rb
414
421
  - spec/reek/cli/warning_collector_spec.rb
@@ -422,7 +429,7 @@ files:
422
429
  - spec/reek/context/method_context_spec.rb
423
430
  - spec/reek/context/module_context_spec.rb
424
431
  - spec/reek/context/root_context_spec.rb
425
- - spec/reek/context/singleton_method_context_spec.rb
432
+ - spec/reek/context_builder_spec.rb
426
433
  - spec/reek/examiner_spec.rb
427
434
  - spec/reek/report/code_climate_formatter_spec.rb
428
435
  - spec/reek/report/code_climate_report_spec.rb
@@ -459,6 +466,7 @@ files:
459
466
  - spec/reek/smells/uncommunicative_parameter_name_spec.rb
460
467
  - spec/reek/smells/uncommunicative_variable_name_spec.rb
461
468
  - spec/reek/smells/unused_parameters_spec.rb
469
+ - spec/reek/smells/unused_private_method_spec.rb
462
470
  - spec/reek/smells/utility_function_spec.rb
463
471
  - spec/reek/source/source_code_spec.rb
464
472
  - spec/reek/source/source_locator_spec.rb
@@ -467,7 +475,6 @@ files:
467
475
  - spec/reek/spec/should_reek_spec.rb
468
476
  - spec/reek/spec/smell_matcher_spec.rb
469
477
  - spec/reek/tree_dresser_spec.rb
470
- - spec/reek/tree_walker_spec.rb
471
478
  - spec/samples/all_but_one_masked/clean_one.rb
472
479
  - spec/samples/all_but_one_masked/dirty.rb
473
480
  - spec/samples/all_but_one_masked/masked.reek
@@ -503,6 +510,7 @@ files:
503
510
  - spec/samples/two_smelly_files/dirty_two.rb
504
511
  - spec/spec_helper.rb
505
512
  - tasks/configuration.rake
513
+ - tasks/console.rake
506
514
  - tasks/reek.rake
507
515
  - tasks/rubocop.rake
508
516
  - tasks/test.rake
@@ -1,237 +0,0 @@
1
- require_relative 'context/method_context'
2
- require_relative 'context/module_context'
3
- require_relative 'context/root_context'
4
- require_relative 'context/singleton_method_context'
5
- require_relative 'context/attribute_context'
6
- require_relative 'ast/node'
7
-
8
- module Reek
9
- #
10
- # Traverses an abstract syntax tree and fires events whenever it encounters
11
- # specific node types.
12
- #
13
- # SMELL: This class is responsible for counting statements and for feeding
14
- # each context to the smell repository.
15
- #
16
- # TODO: Make TreeWalker responsible only for creating Context objects, and
17
- # loop over the created set of contexts elsewhere.
18
- #
19
- # :reek:TooManyMethods: { max_methods: 29 }
20
- class TreeWalker
21
- def initialize(smell_repository, exp)
22
- @smell_repository = smell_repository
23
- @exp = exp
24
- @element = Context::RootContext.new(exp)
25
- end
26
-
27
- def walk
28
- context_tree.each do |element|
29
- smell_repository.examine(element)
30
- end
31
- end
32
-
33
- private
34
-
35
- private_attr_accessor :element
36
- private_attr_reader :exp, :smell_repository
37
-
38
- # Processes the given AST, memoizes it and returns a tree of nested
39
- # contexts.
40
- #
41
- # For example this ruby code:
42
- # class Car; def drive; end; end
43
- # would get compiled into this AST:
44
- # (class
45
- # (const nil :Car) nil
46
- # (def :drive
47
- # (args) nil))
48
- # Processing this AST would result in a context tree where each node
49
- # contains the outer context, the AST and the child contexts. The top
50
- # node is always Reek::Context::RootContext. Using the example above,
51
- # the tree would look like this:
52
- #
53
- # RootContext -> children: 1 ModuleContext -> children: 1 MethodContext
54
- #
55
- # @return [Reek::Context::RootContext] tree of nested contexts
56
- def context_tree
57
- @context_tree ||= process(exp)
58
- end
59
-
60
- def process(exp)
61
- context_processor = "process_#{exp.type}"
62
- if context_processor_exists?(context_processor)
63
- send(context_processor, exp)
64
- else
65
- process_default exp
66
- end
67
- element
68
- end
69
-
70
- def process_module(exp)
71
- inside_new_context(Context::ModuleContext, exp) do
72
- process_default(exp)
73
- end
74
- end
75
-
76
- alias_method :process_class, :process_module
77
-
78
- def process_casgn(exp)
79
- if exp.defines_module?
80
- process_module(exp)
81
- else
82
- process_default(exp)
83
- end
84
- end
85
-
86
- def process_def(exp)
87
- inside_new_context(Context::MethodContext, exp) do
88
- count_clause(exp.body)
89
- process_default(exp)
90
- end
91
- end
92
-
93
- def process_defs(exp)
94
- inside_new_context(Context::SingletonMethodContext, exp) do
95
- count_clause(exp.body)
96
- process_default(exp)
97
- end
98
- end
99
-
100
- def process_default(exp)
101
- exp.children.each do |child|
102
- process(child) if child.is_a? AST::Node
103
- end
104
- end
105
-
106
- def process_args(_) end
107
-
108
- # :reek:TooManyStatements: { max_statements: 6 }
109
- # :reek:FeatureEnvy
110
- def process_send(exp)
111
- if exp.visibility_modifier?
112
- element.track_visibility(exp.method_name, exp.arg_names)
113
- end
114
- if exp.attribute_writer?
115
- exp.args.each do |arg|
116
- next unless arg.type == :sym
117
- new_context(Context::AttributeContext, arg, exp)
118
- end
119
- end
120
- element.record_call_to(exp)
121
- process_default(exp)
122
- end
123
-
124
- def process_attrasgn(exp)
125
- element.record_call_to(exp)
126
- process_default(exp)
127
- end
128
-
129
- alias_method :process_op_asgn, :process_attrasgn
130
-
131
- def process_ivar(exp)
132
- element.record_use_of_self
133
- process_default(exp)
134
- end
135
-
136
- alias_method :process_ivasgn, :process_ivar
137
-
138
- def process_self(_)
139
- element.record_use_of_self
140
- end
141
-
142
- alias_method :process_zsuper, :process_self
143
-
144
- #
145
- # Statement counting
146
- #
147
-
148
- def process_block(exp)
149
- count_clause(exp.block)
150
- process_default(exp)
151
- end
152
-
153
- def process_begin(exp)
154
- count_statement_list(exp.children)
155
- element.count_statements(-1)
156
- process_default(exp)
157
- end
158
-
159
- alias_method :process_kwbegin, :process_begin
160
-
161
- def process_if(exp)
162
- children = exp.children
163
- count_clause(children[1])
164
- count_clause(children[2])
165
- element.count_statements(-1)
166
- process_default(exp)
167
- end
168
-
169
- def process_while(exp)
170
- count_clause(exp.children[1])
171
- element.count_statements(-1)
172
- process_default(exp)
173
- end
174
-
175
- alias_method :process_until, :process_while
176
-
177
- def process_for(exp)
178
- count_clause(exp.children[2])
179
- element.count_statements(-1)
180
- process_default(exp)
181
- end
182
-
183
- def process_rescue(exp)
184
- count_clause(exp.children.first)
185
- element.count_statements(-1)
186
- process_default(exp)
187
- end
188
-
189
- def process_resbody(exp)
190
- count_statement_list(exp.children[1..-1].compact)
191
- process_default(exp)
192
- end
193
-
194
- def process_case(exp)
195
- count_clause(exp.else_body)
196
- element.count_statements(-1)
197
- process_default(exp)
198
- end
199
-
200
- def process_when(exp)
201
- count_clause(exp.body)
202
- process_default(exp)
203
- end
204
-
205
- def context_processor_exists?(name)
206
- self.class.private_method_defined?(name)
207
- end
208
-
209
- # :reek:ControlParameter
210
- def count_clause(sexp)
211
- element.count_statements(1) if sexp
212
- end
213
-
214
- def count_statement_list(statement_list)
215
- element.count_statements statement_list.length
216
- end
217
-
218
- def inside_new_context(klass, exp)
219
- push(new_context(klass, exp)) do
220
- yield
221
- end
222
- end
223
-
224
- def new_context(klass, *args)
225
- klass.new(element, *args).tap do |scope|
226
- element.append_child_context(scope)
227
- end
228
- end
229
-
230
- def push(scope)
231
- orig = element
232
- self.element = scope
233
- yield
234
- self.element = orig
235
- end
236
- end
237
- end
@@ -1,16 +0,0 @@
1
- require_relative '../../spec_helper'
2
- require_lib 'reek/context/singleton_method_context'
3
-
4
- RSpec.describe Reek::Context::SingletonMethodContext do
5
- let(:smc) do
6
- sexp = sexp(:def, :foo, sexp(:args, sexp(:arg, :bar)), nil)
7
- Reek::Context::SingletonMethodContext.new(nil, sexp)
8
- end
9
-
10
- describe '#envious_receivers' do
11
- it 'should not record envious calls' do
12
- smc.record_call_to sexp(:send, sexp(:lvar, :bar), :baz)
13
- expect(smc.envious_receivers).to be_empty
14
- end
15
- end
16
- end