reek 3.9.1 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b3dd68d4b01b16583809e98f6d9511f3c794096c
4
- data.tar.gz: 6eab525a7bb2b6638a9afb8d6897e5a3ff7f755a
3
+ metadata.gz: cb34a3d020ec6d9c214ba0601080f6f3f1be9f62
4
+ data.tar.gz: 7f9ce71ca71ef44adad6fc79f99abeef88f72a02
5
5
  SHA512:
6
- metadata.gz: 9a69de2d73546ef583ee68addc7dfaa5ade459c1f12f7152e731627670e96b3ed1e9324aab30d4a8ad091d99b563a13c17c9a1357696d2d4d8bb14c569bdb26e
7
- data.tar.gz: 91e0aebacfd5f64b3d4fbb08d1dbb74dc87db3655c43874f1c388f23d70d4c533514ce44b3e76ca257ba33bb762095ef2c944acfb5ab69c74bca6243823eeeb2
6
+ metadata.gz: b1f7e66b52c231fe72f07e209ca06bd3abe9f65af64e9b886d1137b744f244cd93de634769da9598f6bd403d82bfce674d4ace97118b77d19c5d19da974f6496
7
+ data.tar.gz: d43adf2de2ffb4ec3a4351afed0ddd1be460b1d1dbfcd10afd3a04a15e747d54da7132c56f5cc324e44c39ad167546204195f96ef6b35e024af142d41852fd10
data/.codeclimate.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ engines:
3
+ reek:
4
+ enabled: true
5
+ ratings:
6
+ paths:
7
+ - "**.rb"
data/.dockerignore ADDED
@@ -0,0 +1,26 @@
1
+ # Folders
2
+
3
+ .git
4
+ .idea
5
+ docs
6
+ features
7
+ logo
8
+ pkg
9
+ spec
10
+ tasks
11
+ tmp
12
+
13
+ # Files
14
+
15
+ .dockerignore
16
+ .gitignore
17
+ .rubocop.yml
18
+ .travis.yml
19
+ .yardopts
20
+ ataru_setup.rb
21
+ CHANGELOG.md
22
+ CONTRIBUTING.md
23
+ defaults.reek
24
+ Dockerfile
25
+ Rakefile
26
+ README.md
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.10.0 (2016-01-27)
6
+
7
+ * Add CodeClimate Docker integration. This will allow users to deduct their own docker image
8
+ from the existing one and use it for their own CI set up in whatever ways they see fit.
9
+ Furthermore this will enable users to run `Reek` locally in combination with `codeclimate cli`.
10
+
5
11
  ## 3.9.1 (2016-01-24)
6
12
 
7
13
  * (troessner) Actually use the corresponding parser for Ruby 2.3
data/Dockerfile ADDED
@@ -0,0 +1,28 @@
1
+ # CodeClimate specification: https://github.com/codeclimate/spec/blob/master/SPEC.md
2
+ #
3
+ # Build and run via:
4
+ # docker build -t codeclimate/codeclimate-reek . && docker run codeclimate/codeclimate-reek
5
+
6
+ FROM codeclimate/alpine-ruby:b38
7
+
8
+ MAINTAINER The Reek core team
9
+
10
+ ENV code_dir /code
11
+ ENV app_dir /usr/src/app
12
+ ENV user app
13
+
14
+ RUN apk --update add git
15
+
16
+ ADD . ${app_dir}
17
+
18
+ WORKDIR ${app_dir}
19
+
20
+ RUN bundle install --without debugging development
21
+ RUN adduser -u 9000 -D ${user}
22
+ RUN chown -R ${user}:${user} ${app_dir}
23
+
24
+ VOLUME ${code_dir}
25
+ WORKDIR ${code_dir}
26
+ USER ${user}
27
+
28
+ CMD [ "/usr/src/app/bin/code_climate_reek" ]
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Wrapper for the CodeClimate integration.
4
+
5
+ require_relative '../lib/reek'
6
+ require_relative '../lib/reek/cli/application'
7
+
8
+ # Map input coming from CodeClimate to Reek.
9
+ class CodeClimateToReek
10
+ # Following the spec (https://github.com/codeclimate/spec/blob/master/SPEC.md)
11
+ # we have to exit with a zero for both failure and success.
12
+ ENGINE_CONFIGURATION = [
13
+ '-f', 'code_climate',
14
+ '--failure-exit-code', '0',
15
+ '--success-exit-code', '0'
16
+ ]
17
+
18
+ attr_reader :configuration_file_path, :include_paths_key, :include_paths_default
19
+
20
+ def initialize(configuration_file_path: '/config.json',
21
+ include_paths_key: 'include_paths',
22
+ include_paths_default: [])
23
+ @configuration_file_path = configuration_file_path
24
+ @include_paths_key = include_paths_key
25
+ @include_paths_default = include_paths_default
26
+ end
27
+
28
+ def cli_arguments
29
+ include_paths + ENGINE_CONFIGURATION
30
+ end
31
+
32
+ private
33
+
34
+ def configuration_file_exists?
35
+ Pathname.new(configuration_file_path).exist?
36
+ end
37
+
38
+ # The config.json file we try to read below might look like this:
39
+ # {
40
+ # "include_paths":[
41
+ # "lib",
42
+ # "spec"
43
+ # ]
44
+ # }
45
+ def include_paths
46
+ if configuration_file_exists?
47
+ config = JSON.parse File.read(configuration_file_path)
48
+ config.fetch include_paths_key, include_paths_default
49
+ else
50
+ include_paths_default
51
+ end
52
+ end
53
+ end
54
+
55
+ application = Reek::CLI::Application.new(CodeClimateToReek.new.cli_arguments)
56
+
57
+ exit application.execute
data/engine.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "Reek",
3
+ "description": "Reek is a tool that examines Ruby classes, modules and methods and reports any code smells it finds.",
4
+ "maintainer": {
5
+ "name": "Matijs van Zuijlen, Piotr Szotkowski, Timo Rößner",
6
+ "email": "timo.roessner@googlemail.com"
7
+ },
8
+ "languages" : ["Ruby"],
9
+ "version": "0.1",
10
+ "spec_version": "0.0.1"
11
+ }
@@ -0,0 +1,664 @@
1
+ ---
2
+ Attribute:
3
+ remediation_points: 250_000
4
+ content: |
5
+ A class that publishes a setter for an instance variable invites client classes to become too intimate with its inner workings, and in particular with its representation of state.
6
+
7
+ The same holds to a lesser extent for getters, but Reek doesn't flag those.
8
+
9
+ ## Example
10
+
11
+ Given:
12
+
13
+ ```Ruby
14
+ class Klass
15
+ attr_accessor :dummy
16
+ end
17
+ ```
18
+
19
+ Reek would emit the following warning:
20
+
21
+ ```
22
+ reek test.rb
23
+
24
+ test.rb -- 1 warning:
25
+ [2]:Klass declares the writable attribute dummy (Attribute)
26
+ ```
27
+ BooleanParameter:
28
+ remediation_points: 500_000
29
+ content: |
30
+ `Boolean Parameter` is a special case of `Control Couple`, where a method parameter is defaulted to true or false. A _Boolean Parameter_ effectively permits a method's caller to decide which execution path to take. This is a case of bad cohesion. You're creating a dependency between methods that is not really necessary, thus increasing coupling.
31
+
32
+ ## Example
33
+
34
+ Given
35
+
36
+ ```Ruby
37
+ class Dummy
38
+ def hit_the_switch(switch = true)
39
+ if switch
40
+ puts 'Hitting the switch'
41
+ # do other things...
42
+ else
43
+ puts 'Not hitting the switch'
44
+ # do other things...
45
+ end
46
+ end
47
+ end
48
+ ```
49
+
50
+ Reek would emit the following warning:
51
+
52
+ ```
53
+ test.rb -- 3 warnings:
54
+ [1]:Dummy#hit_the_switch has boolean parameter 'switch' (BooleanParameter)
55
+ [2]:Dummy#hit_the_switch is controlled by argument switch (ControlParameter)
56
+ ```
57
+
58
+ Note that both smells are reported, `Boolean Parameter` and `Control Parameter`.
59
+
60
+ ## Getting rid of the smell
61
+
62
+ This is highly dependant on your exact architecture, but looking at the example above what you could do is:
63
+
64
+ * Move everything in the `if` branch into a separate method
65
+ * Move everything in the `else` branch into a separate method
66
+ * Get rid of the `hit_the_switch` method alltogether
67
+ * Make the decision what method to call in the initial caller of `hit_the_switch`
68
+ ClassVariable:
69
+ remediation_points: 350_000
70
+ content: |
71
+ Class variables form part of the global runtime state, and as such make it easy for one part of the system to accidentally or inadvertently depend on another part of the system. So the system becomes more prone to problems where changing something over here breaks something over there. In particular, class variables can make it hard to set up tests (because the context of the test includes all global state).
72
+
73
+ For a detailed explanation, check out [this article](http://4thmouse.com/index.php/2011/03/20/why-class-variables-in-ruby-are-a-bad-idea/)
74
+
75
+ ## Example
76
+
77
+ Given
78
+
79
+ ```Ruby
80
+ class Dummy
81
+ @@class_variable = :whatever
82
+ end
83
+ ```
84
+
85
+ Reek would emit the following warning:
86
+
87
+ ```
88
+ reek test.rb
89
+
90
+ test.rb -- 1 warning:
91
+ [2]:Dummy declares the class variable @@class_variable (ClassVariable)
92
+ ```
93
+
94
+ ## Getting rid of the smell
95
+
96
+ You can use class-instance variable to mitigate the problem (as also suggested in the linked article above):
97
+
98
+ ```Ruby
99
+ class Dummy
100
+ @class_variable = :whatever
101
+ end
102
+ ```
103
+ ControlParameter:
104
+ remediation_points: 500_000
105
+ content: |
106
+ `Control Parameter` is a special case of `Control Couple`
107
+
108
+ ## Example
109
+
110
+ A simple example would be the "quoted" parameter in the following method:
111
+
112
+ ```Ruby
113
+ def write(quoted)
114
+ if quoted
115
+ write_quoted @value
116
+ else
117
+ write_unquoted @value
118
+ end
119
+ end
120
+ ```
121
+
122
+ Fixing those problems is out of the scope of this document but an easy solution could be to remove the "write" method alltogether and to move the calls to "write_quoted" / "write_unquoted" in the initial caller of "write".
123
+ DataClump:
124
+ remediation_points: 250_000
125
+ content: |
126
+ In general, a `Data Clump` occurs when the same two or three items frequently appear together in classes and parameter lists, or when a group of instance variable names start or end with similar substrings.
127
+
128
+ The recurrence of the items often means there is duplicate code spread around to handle them. There may be an abstraction missing from the code, making the system harder to understand.
129
+
130
+ ## Example
131
+
132
+ Given
133
+
134
+ ```Ruby
135
+ class Dummy
136
+ def x(y1,y2); end
137
+ def y(y1,y2); end
138
+ def z(y1,y2); end
139
+ end
140
+ ```
141
+
142
+ Reek would emit the following warning:
143
+
144
+ ```
145
+ test.rb -- 1 warning:
146
+ [2, 3, 4]:Dummy takes parameters [y1, y2] to 3 methods (DataClump)
147
+ ```
148
+
149
+ A possible way to fix this problem (quoting from [Martin Fowler](http://martinfowler.com/bliki/DataClump.html)):
150
+
151
+ > The first step is to replace data clumps with objects and use the objects whenever you see them. An immediate benefit is that you'll shrink some parameter lists. The interesting stuff happens as you begin to look for behavior to move into the new objects.
152
+ DuplicateMethodCall:
153
+ remediation_points: 350_000
154
+ content: |
155
+ Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.
156
+
157
+ Reek implements a check for _Duplicate Method Call_.
158
+
159
+ ## Example
160
+
161
+ Here's a very much simplified and contrived example. The following method will report a warning:
162
+
163
+ ```Ruby
164
+ def double_thing()
165
+ @other.thing + @other.thing
166
+ end
167
+ ```
168
+
169
+ One quick approach to silence Reek would be to refactor the code thus:
170
+
171
+ ```Ruby
172
+ def double_thing()
173
+ thing = @other.thing
174
+ thing + thing
175
+ end
176
+ ```
177
+
178
+ A slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:
179
+
180
+ ```Ruby
181
+ class Other
182
+ def double_thing()
183
+ thing + thing
184
+ end
185
+ end
186
+ ```
187
+
188
+ The approach you take will depend on balancing other factors in your code.
189
+ FeatureEnvy:
190
+ remediation_points: 500_000
191
+ content: |
192
+ _Feature Envy_ occurs when a code fragment references another object more often than it references itself, or when several clients do the same series of manipulations on a particular type of object.
193
+
194
+ _Feature Envy_ reduces the code's ability to communicate intent: code that "belongs" on one class but which is located in another can be hard to find, and may upset the "System of Names" in the host class.
195
+
196
+ _Feature Envy_ also affects the design's flexibility: A code fragment that is in the wrong class creates couplings that may not be natural within the application's domain, and creates a loss of cohesion in the unwilling host class.
197
+
198
+ _Feature Envy_ often arises because it must manipulate other objects (usually its arguments) to get them into a useful form, and one force preventing them (the arguments) doing this themselves is that the common knowledge lives outside the arguments, or the arguments are of too basic a type to justify extending that type. Therefore there must be something which 'knows' about the contents or purposes of the arguments. That thing would have to be more than just a basic type, because the basic types are either containers which don't know about their contents, or they are single objects which can't capture their relationship with their fellows of the same type. So, this thing with the extra knowledge should be reified into a class, and the utility method will most likely belong there.
199
+
200
+ ## Example
201
+
202
+ Running Reek on:
203
+
204
+ ```Ruby
205
+ class Warehouse
206
+ def sale_price(item)
207
+ (item.price - item.rebate) * @vat
208
+ end
209
+ end
210
+ ```
211
+
212
+ would report:
213
+
214
+ ```Bash
215
+ Warehouse#total_price refers to item more than self (FeatureEnvy)
216
+ ```
217
+
218
+ since this:
219
+
220
+ ```Ruby
221
+ (item.price - item.rebate)
222
+ ```
223
+
224
+ belongs to the Item class, not the Warehouse.
225
+ IrresponsibleModule:
226
+ remediation_points: 350_000
227
+ content: |
228
+ Classes and modules are the units of reuse and release. It is therefore considered good practice to annotate every class and module with a brief comment outlining its responsibilities.
229
+
230
+ ## Example
231
+
232
+ Given
233
+
234
+ ```Ruby
235
+ class Dummy
236
+ # Do things...
237
+ end
238
+ ```
239
+
240
+ Reek would emit the following warning:
241
+
242
+ ```
243
+ test.rb -- 1 warning:
244
+ [1]:Dummy has no descriptive comment (IrresponsibleModule)
245
+ ```
246
+
247
+ Fixing this is simple - just an explaining comment:
248
+
249
+ ```Ruby
250
+ # The Dummy class is responsible for ...
251
+ class Dummy
252
+ # Do things...
253
+ end
254
+ ```
255
+ LongParameterList:
256
+ remediation_points: 500_000
257
+ content: |
258
+ A `Long Parameter List` occurs when a method has a lot of parameters.
259
+
260
+ ## Example
261
+
262
+ Given
263
+
264
+ ```Ruby
265
+ class Dummy
266
+ def long_list(foo,bar,baz,fling,flung)
267
+ puts foo,bar,baz,fling,flung
268
+ end
269
+ end
270
+ ```
271
+
272
+ Reek would report the following warning:
273
+
274
+ ```
275
+ test.rb -- 1 warning:
276
+ [2]:Dummy#long_list has 5 parameters (LongParameterList)
277
+ ```
278
+
279
+ A common solution to this problem would be the introduction of parameter objects.
280
+ LongYieldList:
281
+ remediation_points: 500_000
282
+ content: |
283
+ A _Long Yield List_ occurs when a method yields a lot of arguments to the block it gets passed.
284
+
285
+ ## Example
286
+
287
+ ```Ruby
288
+ class Dummy
289
+ def yields_a_lot(foo,bar,baz,fling,flung)
290
+ yield foo,bar,baz,fling,flung
291
+ end
292
+ end
293
+ ```
294
+
295
+ Reek would report the following warning:
296
+
297
+ ```
298
+ test.rb -- 1 warning:
299
+ [4]:Dummy#yields_a_lot yields 5 parameters (LongYieldList)
300
+ ```
301
+
302
+ A common solution to this problem would be the introduction of parameter objects.
303
+ ModuleInitialize:
304
+ remediation_points: 350_000
305
+ content: |
306
+ A module is usually a mixin, so when an `#initialize` method is present it is
307
+ hard to tell initialization order and parameters so having `#initialize`
308
+ in a module is usually a bad idea.
309
+
310
+ ## Example
311
+
312
+ The `Foo` module below contains a method `initialize`. Although class `B` inherits from `A`, the inclusion of `Foo` stops `A#initialize` from being called.
313
+
314
+ ```Ruby
315
+ class A
316
+ def initialize(a)
317
+ @a = a
318
+ end
319
+ end
320
+
321
+ module Foo
322
+ def initialize(foo)
323
+ @foo = foo
324
+ end
325
+ end
326
+
327
+ class B < A
328
+ include Foo
329
+
330
+ def initialize(b)
331
+ super('bar')
332
+ @b = b
333
+ end
334
+ end
335
+ ```
336
+
337
+ A simple solution is to rename `Foo#initialize` and call that method by name:
338
+
339
+ ```Ruby
340
+ module Foo
341
+ def setup_foo_module(foo)
342
+ @foo = foo
343
+ end
344
+ end
345
+
346
+ class B < A
347
+ include Foo
348
+
349
+ def initialize(b)
350
+ super 'bar'
351
+ setup_foo_module('foo')
352
+ @b = b
353
+ end
354
+ end
355
+ ```
356
+ NestedIterators:
357
+ remediation_points: 500_000
358
+ content: |
359
+ A `Nested Iterator` occurs when a block contains another block.
360
+
361
+ ## Example
362
+
363
+ Given
364
+
365
+ ```Ruby
366
+ class Duck
367
+ class << self
368
+ def duck_names
369
+ %i!tick trick track!.each do |surname|
370
+ %i!duck!.each do |last_name|
371
+ puts "full name is #{surname} #{last_name}"
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end
377
+ ```
378
+
379
+ Reek would report the following warning:
380
+
381
+ ```
382
+ test.rb -- 1 warning:
383
+ [5]:Duck#duck_names contains iterators nested 2 deep (NestedIterators)
384
+ ```
385
+ NilCheck:
386
+ remediation_points: 250_000
387
+ content: |
388
+ A `NilCheck` is a type check. Failures of `NilCheck` violate the ["tell, don't ask"](http://robots.thoughtbot.com/tell-dont-ask) principle.
389
+
390
+ Additionally, type checks often mask bigger problems in your source code like not using OOP and / or polymorphism when you should.
391
+
392
+ ## Example
393
+
394
+ Given
395
+
396
+ ```Ruby
397
+ class Klass
398
+ def nil_checker(argument)
399
+ if argument.nil?
400
+ puts "argument isn't nil!"
401
+ end
402
+ end
403
+ end
404
+ ```
405
+
406
+ Reek would emit the following warning:
407
+
408
+ ```
409
+ test.rb -- 1 warning:
410
+ [3]:Klass#nil_checker performs a nil-check. (NilCheck)
411
+ ```
412
+ PrimaDonnaMethod:
413
+ remediation_points: 250_000
414
+ content: |
415
+ A candidate method for the `Prima Donna Method` smell are methods whose names end with an exclamation mark.
416
+
417
+ An exclamation mark in method names means (the explanation below is taken from [here](http://dablog.rubypal.com/2007/8/15/bang-methods-or-danger-will-rubyist) ):
418
+
419
+ >>
420
+ The ! in method names that end with ! means, “This method is dangerous”—or, more precisely, this method is the “dangerous” version of an otherwise equivalent method, with the same name minus the !. “Danger” is relative; the ! doesn’t mean anything at all unless the method name it’s in corresponds to a similar but bang-less method name.
421
+ So, for example, gsub! is the dangerous version of gsub. exit! is the dangerous version of exit. flatten! is the dangerous version of flatten. And so forth.
422
+
423
+ Such a method is called `Prima Donna Method` if and only if her non-bang version does not exist and this method is reported as a smell.
424
+
425
+ ## Example
426
+
427
+ Given
428
+
429
+ ```Ruby
430
+ class C
431
+ def foo; end
432
+ def foo!; end
433
+ def bar!; end
434
+ end
435
+ ```
436
+
437
+ Reek would report `bar!` as `prima donna method` smell but not `foo!`.
438
+
439
+ Reek reports this smell only in a class context, not in a module context in order to allow perfectly legit code like this:
440
+
441
+
442
+ ```Ruby
443
+ class Parent
444
+ def foo; end
445
+ end
446
+
447
+ module Dangerous
448
+ def foo!; end
449
+ end
450
+
451
+ class Son < Parent
452
+ include Dangerous
453
+ end
454
+
455
+ class Daughter < Parent
456
+ end
457
+ ```
458
+
459
+ In this example, Reek would not report the `prima donna method` smell for the method `foo` of the `Dangerous` module.
460
+ RepeatedConditional:
461
+ remediation_points: 400_000
462
+ content: |
463
+ `Repeated Conditional` is a special case of `Simulated Polymorphism`. Basically it means you are checking the same value throughout a single class and take decisions based on this.
464
+
465
+ ## Example
466
+
467
+ Given
468
+
469
+ ```Ruby
470
+ class RepeatedConditionals
471
+ attr_accessor :switch
472
+
473
+ def repeat_1
474
+ puts "Repeat 1!" if switch
475
+ end
476
+
477
+ def repeat_2
478
+ puts "Repeat 2!" if switch
479
+ end
480
+
481
+ def repeat_3
482
+ puts "Repeat 3!" if switch
483
+ end
484
+ end
485
+ ```
486
+
487
+ Reek would emit the following warning:
488
+
489
+ ```
490
+ test.rb -- 4 warnings:
491
+ [5, 9, 13]:RepeatedConditionals tests switch at least 3 times (RepeatedConditional)
492
+ ```
493
+
494
+ If you get this warning then you are probably not using the right abstraction or even more probable, missing an additional abstraction.
495
+ TooManyInstanceVariables:
496
+ remediation_points: 500_000
497
+ content: |
498
+ `Too Many Instance Variables` is a special case of `LargeClass`.
499
+
500
+ ## Example
501
+
502
+ Given this configuration
503
+
504
+ ```yaml
505
+ TooManyInstanceVariables:
506
+ max_instance_variables: 3
507
+ ```
508
+
509
+ and this code:
510
+
511
+ ```Ruby
512
+ class TooManyInstanceVariables
513
+ def initialize
514
+ @arg_1 = :dummy
515
+ @arg_2 = :dummy
516
+ @arg_3 = :dummy
517
+ @arg_4 = :dummy
518
+ end
519
+ end
520
+ ```
521
+
522
+ Reek would emit the following warning:
523
+
524
+ ```
525
+ test.rb -- 5 warnings:
526
+ [1]:TooManyInstanceVariables has at least 4 instance variables (TooManyInstanceVariables)
527
+ ```
528
+ TooManyMethods:
529
+ remediation_points: 500_000
530
+ content: |
531
+ `Too Many Methods` is a special case of `LargeClass`.
532
+
533
+ ## Example
534
+
535
+ Given this configuration
536
+
537
+ ```yaml
538
+ TooManyMethods:
539
+ max_methods: 3
540
+ ```
541
+
542
+ and this code:
543
+
544
+ ```Ruby
545
+ class TooManyMethods
546
+ def one; end
547
+ def two; end
548
+ def three; end
549
+ def four; end
550
+ end
551
+ ```
552
+
553
+ Reek would emit the following warning:
554
+
555
+ ```
556
+ test.rb -- 1 warning:
557
+ [1]:TooManyMethods has at least 4 methods (TooManyMethods)
558
+ ```
559
+ TooManyStatements:
560
+ remediation_points: 500_000
561
+ content: |
562
+ A method with `Too Many Statements` is any method that has a large number of lines.
563
+
564
+ `Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.
565
+
566
+ So the following method would score +6 in Reek's statement-counting algorithm:
567
+
568
+ ```Ruby
569
+ def parse(arg, argv, &error)
570
+ if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
571
+ return nil, block, nil # +1
572
+ end
573
+ opt = (val = parse_arg(val, &error))[1] # +2
574
+ val = conv_arg(*val) # +3
575
+ if opt and !arg
576
+ argv.shift # +4
577
+ else
578
+ val[0] = nil # +5
579
+ end
580
+ val # +6
581
+ end
582
+ ```
583
+
584
+ (You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)
585
+ UncommunicativeMethodName:
586
+ remediation_points: 150_000
587
+ content: |
588
+ An `Uncommunicative Method Name` is a method name that doesn't communicate its intent well enough.
589
+
590
+ Poor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.
591
+ UncommunicativeModuleName:
592
+ remediation_points: 150_000
593
+ content: |
594
+ An `Uncommunicative Module Name` is a module name that doesn't communicate its intent well enough.
595
+
596
+ Poor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.
597
+ UncommunicativeParameterName:
598
+ remediation_points: 150_000
599
+ content: |
600
+ An `Uncommunicative Parameter Name` is a parameter name that doesn't communicate its intent well enough.
601
+
602
+ Poor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.
603
+ UncommunicativeVariableName:
604
+ remediation_points: 150_000
605
+ content: |
606
+ An `Uncommunicative Variable Name` is a variable name that doesn't communicate its intent well enough.
607
+
608
+ Poor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.
609
+ UnusedParameters:
610
+ remediation_points: 200_000
611
+ content: |
612
+ `Unused Parameter` refers to methods with parameters that are unused in scope of the method.
613
+
614
+ Having unused parameters in a method is code smell because leaving dead code in a method can never improve the method and it makes the code confusing to read.
615
+
616
+ ## Example
617
+
618
+ Given:
619
+
620
+ ```Ruby
621
+ class Klass
622
+ def unused_parameters(x,y,z)
623
+ puts x,y # but not z
624
+ end
625
+ end
626
+ ```
627
+
628
+ Reek would emit the following warning:
629
+
630
+ ```
631
+ [2]:Klass#unused_parameters has unused parameter 'z' (UnusedParameters)
632
+ ```
633
+ UnusedPrivateMethod:
634
+ remediation_points: 200_000
635
+ content: |
636
+ Classes should use their private methods. Otherwise this is dead
637
+ code which is confusing and bad for maintenance.
638
+
639
+ The `Unused Private Method` detector reports unused private instance
640
+ methods and instance methods only - class methods are ignored.
641
+
642
+ ## Example
643
+
644
+ Given:
645
+
646
+ ```Ruby
647
+ class Car
648
+ private
649
+ def drive; end
650
+ def start; end
651
+ end
652
+ ```
653
+
654
+ `Reek` would emit the following warning:
655
+
656
+ ```
657
+ 2 warnings:
658
+ [3]:Car has the unused private instance method `drive` (UnusedPrivateMethod)
659
+ [4]:Car has the unused private instance method `start` (UnusedPrivateMethod)
660
+ ```
661
+ UtilityFunction:
662
+ remediation_points: 250_000
663
+ content: |
664
+ A _Utility Function_ is any instance method that has no dependency on the state of the instance.
@@ -11,12 +11,14 @@ module Reek
11
11
  @warning = warning
12
12
  end
13
13
 
14
- def to_hash
14
+ def render
15
15
  CCEngine::Issue.new(check_name: check_name,
16
16
  description: description,
17
17
  categories: categories,
18
- location: location
19
- ).to_hash
18
+ location: location,
19
+ remediation_points: remediation_points,
20
+ content: content
21
+ ).render
20
22
  end
21
23
 
22
24
  private
@@ -41,6 +43,21 @@ module Reek
41
43
  line_range: warning_lines.first..warning_lines.last
42
44
  )
43
45
  end
46
+
47
+ def remediation_points
48
+ configuration[warning.smell_type].fetch('remediation_points')
49
+ end
50
+
51
+ def content
52
+ configuration[warning.smell_type].fetch('content')
53
+ end
54
+
55
+ def configuration
56
+ @configuration ||= begin
57
+ config_file = File.expand_path('../code_climate_configuration.yml', __FILE__)
58
+ YAML.load_file config_file
59
+ end
60
+ end
44
61
  end
45
62
  end
46
63
  end
@@ -1,6 +1,6 @@
1
1
  require 'private_attr/everywhere'
2
2
  require_relative 'location_formatter'
3
- require_relative '../report/code_climate_formatter'
3
+ require_relative 'code_climate/code_climate_formatter'
4
4
 
5
5
  module Reek
6
6
  module Report
@@ -45,7 +45,7 @@ module Reek
45
45
 
46
46
  # :reek:UtilityFunction
47
47
  def format_code_climate_hash(warning)
48
- CodeClimateFormatter.new(warning).to_hash
48
+ CodeClimateFormatter.new(warning).render
49
49
  end
50
50
 
51
51
  private_attr_reader :location_formatter
@@ -151,7 +151,9 @@ module Reek
151
151
  class CodeClimateReport < Base
152
152
  # @public
153
153
  def show(out = $stdout)
154
- out.print ::JSON.generate smells.map { |smell| warning_formatter.format_code_climate_hash(smell) }
154
+ smells.map do |smell|
155
+ out.print warning_formatter.format_code_climate_hash(smell)
156
+ end
155
157
  end
156
158
  end
157
159
 
data/lib/reek/version.rb CHANGED
@@ -6,6 +6,6 @@ module Reek
6
6
  # @public
7
7
  module Version
8
8
  # @public
9
- STRING = '3.9.1'
9
+ STRING = '3.10.0'
10
10
  end
11
11
  end
data/reek.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
22
22
  s.required_ruby_version = '>= 2.0.0'
23
23
  s.summary = 'Code smell detector for Ruby'
24
24
 
25
- s.add_runtime_dependency 'codeclimate-engine-rb', '~> 0.1.0'
25
+ s.add_runtime_dependency 'codeclimate-engine-rb', '~> 0.3.1'
26
26
  s.add_runtime_dependency 'parser', '~> 2.3'
27
27
  s.add_runtime_dependency 'private_attr', '~> 1.1'
28
28
  s.add_runtime_dependency 'rainbow', '~> 2.0'
@@ -1,63 +1,44 @@
1
1
  require_relative '../../spec_helper'
2
- require_lib 'reek/report/code_climate_formatter'
2
+ require_lib 'reek/report/code_climate/code_climate_formatter'
3
+
4
+ RSpec.describe Reek::Report::CodeClimateFormatter, '#render' do
5
+ let(:warning) do
6
+ FactoryGirl.build(:smell_warning,
7
+ smell_detector: Reek::Smells::UtilityFunction.new,
8
+ context: 'context foo',
9
+ message: 'message bar',
10
+ lines: [1, 2],
11
+ source: 'a/ruby/source/file.rb')
12
+ end
13
+ let(:rendered) { Reek::Report::CodeClimateFormatter.new(warning).render }
3
14
 
4
- RSpec.describe Reek::Report::CodeClimateFormatter, '#to_hash' do
5
15
  it "sets the type as 'issue'" do
6
- warning = FactoryGirl.build(:smell_warning)
7
- issue = Reek::Report::CodeClimateFormatter.new(warning)
8
-
9
- result = issue.to_hash
10
-
11
- expect(result).to include(type: 'issue')
16
+ expect(rendered).to match(/\"type\":\"issue\"/)
12
17
  end
13
18
 
14
19
  it 'sets the category' do
15
- warning = FactoryGirl.build(:smell_warning)
16
- issue = Reek::Report::CodeClimateFormatter.new(warning)
17
-
18
- result = issue.to_hash
19
-
20
- expect(result).to include(categories: ['Complexity'])
20
+ expect(rendered).to match(/\"categories\":\[\"Complexity\"\]/)
21
21
  end
22
22
 
23
23
  it 'constructs a description based on the context and message' do
24
- warning = FactoryGirl.build(:smell_warning,
25
- context: 'context foo',
26
- message: 'message bar')
27
- issue = Reek::Report::CodeClimateFormatter.new(warning)
28
-
29
- result = issue.to_hash
30
-
31
- expect(result).to include(
32
- description: 'context foo message bar')
24
+ expect(rendered).to match(/\"description\":\"context foo message bar\"/)
33
25
  end
34
26
 
35
27
  it 'sets a check name based on the smell detector' do
36
- warning = FactoryGirl.build(:smell_warning,
37
- smell_detector: Reek::Smells::UtilityFunction.new)
38
- issue = Reek::Report::CodeClimateFormatter.new(warning)
39
-
40
- result = issue.to_hash
41
-
42
- expect(result).to include(check_name: 'LowCohesion/UtilityFunction')
28
+ expect(rendered).to match(%r{\"check_name\":\"LowCohesion\/UtilityFunction\"})
43
29
  end
44
30
 
45
31
  it 'sets the location' do
46
- warning = FactoryGirl.build(:smell_warning,
47
- lines: [1, 2],
48
- source: 'a/ruby/source/file.rb')
49
- issue = Reek::Report::CodeClimateFormatter.new(warning)
50
-
51
- result = issue.to_hash
52
-
53
- expect(result).to include(
54
- location: {
55
- path: 'a/ruby/source/file.rb',
56
- lines: {
57
- begin: 1,
58
- end: 2
59
- }
60
- }
32
+ expect(rendered).to match(%r{\"location\":{\"path\":\"a/ruby/source/file.rb\",\"lines\":{\"begin\":1,\"end\":2}}})
33
+ end
34
+
35
+ it 'sets a content based on the smell detector' do
36
+ expect(rendered).to match(
37
+ /\"body\":\"A _Utility Function_ is any instance method that has no dependency on the state of the instance.\\n\"/
61
38
  )
62
39
  end
40
+
41
+ it 'sets remediation points based on the smell detector' do
42
+ expect(rendered).to match(/\"remediation_points\":250000/)
43
+ end
63
44
  end
@@ -18,8 +18,8 @@ RSpec.describe Reek::Report::CodeClimateReport do
18
18
  context 'with empty source' do
19
19
  let(:source) { '' }
20
20
 
21
- it 'prints empty json' do
22
- expect { instance.show }.to output(/^\[\]$/).to_stdout
21
+ it 'prints an empty string' do
22
+ expect { instance.show }.to output('').to_stdout
23
23
  end
24
24
  end
25
25
 
@@ -27,42 +27,28 @@ RSpec.describe Reek::Report::CodeClimateReport do
27
27
  let(:source) { 'def simple(a) a[3] end' }
28
28
 
29
29
  it 'prints smells as json' do
30
- out = StringIO.new
31
- instance.show(out)
32
- out.rewind
33
- result = JSON.parse(out.read)
34
- expected = JSON.parse <<-EOS
35
- [
36
- {
37
- "type": "issue",
38
- "check_name": "UncommunicativeName/UncommunicativeParameterName",
39
- "description": "simple has the parameter name 'a'",
40
- "categories": ["Complexity"],
41
- "location": {
42
- "path": "string",
43
- "lines": {
44
- "begin": 1,
45
- "end": 1
46
- }
47
- }
48
- },
49
- {
50
- "type": "issue",
51
- "check_name": "LowCohesion/UtilityFunction",
52
- "description": "simple doesn't depend on instance state (maybe move it to another class?)",
53
- "categories": ["Complexity"],
54
- "location": {
55
- "path": "string",
56
- "lines": {
57
- "begin": 1,
58
- "end": 1
59
- }
60
- }
61
- }
62
- ]
30
+ expected = <<-EOS.delete("\n")
31
+ {\"type\":\"issue\",
32
+ \"check_name\":\"UncommunicativeName/UncommunicativeParameterName\",
33
+ \"description\":\"simple has the parameter name 'a'\",
34
+ \"categories\":[\"Complexity\"],
35
+ \"location\":{\"path\":\"string\",\"lines\":{\"begin\":1,\"end\":1}},
36
+ \"remediation_points\":150000,
37
+ \"content\":{\"body\":\"An `Uncommunicative Parameter Name` is a parameter name that
38
+ doesn't communicate its intent well enough.\\n\\nPoor names make it hard for the reader
39
+ to build a mental picture of what's going on in the code. They can also be
40
+ mis-interpreted; and they hurt the flow of reading, because the reader must slow down
41
+ to interpret the names.\\n\"}}\u0000
42
+ {\"type\":\"issue\",
43
+ \"check_name\":\"LowCohesion/UtilityFunction\",
44
+ \"description\":\"simple doesn't depend on instance state (maybe move it to another class?)\",
45
+ \"categories\":[\"Complexity\"],
46
+ \"location\":{\"path\":\"string\",\"lines\":{\"begin\":1,\"end\":1}},
47
+ \"remediation_points\":250000,
48
+ \"content\":{\"body\":\"A _Utility Function_ is any instance method that has no
49
+ dependency on the state of the instance.\\n\"}}\u0000
63
50
  EOS
64
-
65
- expect(result).to eq expected
51
+ expect { instance.show }.to output(expected).to_stdout
66
52
  end
67
53
  end
68
54
  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.9.1
4
+ version: 3.10.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: 2016-01-24 00:00:00.000000000 Z
14
+ date: 2016-01-27 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: codeclimate-engine-rb
@@ -19,14 +19,14 @@ dependencies:
19
19
  requirements:
20
20
  - - "~>"
21
21
  - !ruby/object:Gem::Version
22
- version: 0.1.0
22
+ version: 0.3.1
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: 0.1.0
29
+ version: 0.3.1
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: parser
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -75,23 +75,28 @@ description: |2
75
75
  email:
76
76
  - timo.roessner@googlemail.com
77
77
  executables:
78
+ - code_climate_reek
78
79
  - reek
79
80
  extensions: []
80
81
  extra_rdoc_files:
81
82
  - CHANGELOG.md
82
83
  - License.txt
83
84
  files:
85
+ - ".codeclimate.yml"
86
+ - ".dockerignore"
84
87
  - ".gitignore"
85
88
  - ".rubocop.yml"
86
89
  - ".travis.yml"
87
90
  - ".yardopts"
88
91
  - CHANGELOG.md
89
92
  - CONTRIBUTING.md
93
+ - Dockerfile
90
94
  - Gemfile
91
95
  - License.txt
92
96
  - README.md
93
97
  - Rakefile
94
98
  - ataru_setup.rb
99
+ - bin/code_climate_reek
95
100
  - bin/reek
96
101
  - defaults.reek
97
102
  - docs/API.md
@@ -139,6 +144,7 @@ files:
139
144
  - docs/templates/default/docstring/setup.rb
140
145
  - docs/templates/default/fulldoc/html/css/common.css
141
146
  - docs/yard_plugin.rb
147
+ - engine.json
142
148
  - features/command_line_interface/basic_usage.feature
143
149
  - features/command_line_interface/options.feature
144
150
  - features/command_line_interface/smell_selection.feature
@@ -212,7 +218,8 @@ files:
212
218
  - lib/reek/examiner.rb
213
219
  - lib/reek/rake/task.rb
214
220
  - lib/reek/report.rb
215
- - lib/reek/report/code_climate_formatter.rb
221
+ - lib/reek/report/code_climate/code_climate_configuration.yml
222
+ - lib/reek/report/code_climate/code_climate_formatter.rb
216
223
  - lib/reek/report/formatter.rb
217
224
  - lib/reek/report/heading_formatter.rb
218
225
  - lib/reek/report/html_report.html.erb
@@ -394,7 +401,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
394
401
  version: '0'
395
402
  requirements: []
396
403
  rubyforge_project:
397
- rubygems_version: 2.4.5.1
404
+ rubygems_version: 2.5.1
398
405
  signing_key:
399
406
  specification_version: 4
400
407
  summary: Code smell detector for Ruby