ducalis 0.2.0 → 0.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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +6 -0
- data/.rubocop.yml +3 -0
- data/DOCUMENTATION.md +319 -52
- data/Gemfile +6 -0
- data/Gemfile.lock +1 -2
- data/README.md +43 -1
- data/config/.ducalis.yml +8 -0
- data/ducalis.gemspec +0 -6
- data/lib/ducalis.rb +2 -0
- data/lib/ducalis/commentators/github.rb +7 -3
- data/lib/ducalis/cops/case_mapping.rb +92 -0
- data/lib/ducalis/cops/possible_tap.rb +59 -0
- data/lib/ducalis/cops/protected_scope_cop.rb +2 -1
- data/lib/ducalis/documentation.rb +10 -2
- data/lib/ducalis/version.rb +1 -1
- metadata +5 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6fa5fd04439b5d750ef52ab27f19c4fe233dc6c
|
4
|
+
data.tar.gz: a700248bc70906a88c759217a76c60873df249c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e297975d9b71e10ea881e99eebe64af49a756dbf1e5d6167da25431893408697794ee6125f61b60da2981d4dbe0e91a58377b92750a7433b08872e1f00ec64f1
|
7
|
+
data.tar.gz: 5c29c471688d345b4433cf60fe7b488a39ce5499d847a3474edccadab255f4a115dd3bee52fcabd76a50b32f93f8227e7d74312edca74fcdacf148d4bacee33d
|
data/.codeclimate.yml
ADDED
data/.rubocop.yml
CHANGED
data/DOCUMENTATION.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
## Ducalis::CallbacksActiverecord
|
2
2
|
|
3
3
|
Please, avoid using of callbacks for models. It's better to keep models small ("dumb") and instead use "builder" classes/services: to construct new objects. You can read more [here](https://medium.com/planet-arkency/a61fd75ab2d3).
|
4
|
-
|
4
|
+
|
5
|
+
 raises on ActiveRecord classes which contains callbacks
|
5
6
|
```ruby
|
6
7
|
|
7
8
|
class A < ActiveRecord::Base
|
@@ -9,18 +10,116 @@ class A < ActiveRecord::Base
|
|
9
10
|
end
|
10
11
|
|
11
12
|
```
|
12
|
-
|
13
|
+
|
14
|
+
 ignores non-ActiveRecord classes which contains callbacks
|
13
15
|
```ruby
|
14
16
|
|
15
17
|
class A < SomeBasicClass
|
16
18
|
before_create :generate_code
|
17
19
|
end
|
18
20
|
|
21
|
+
```
|
22
|
+
## Ducalis::CaseMapping
|
23
|
+
|
24
|
+
Try to avoid `case when` statements. You can replace it with a sequence of
|
25
|
+
`if... elsif... elsif... else`. For cases where you need to choose from a
|
26
|
+
large number of possibilities, you can create a dictionary mapping case values
|
27
|
+
to functions to call by `call`. It's nice to have prefix for the method
|
28
|
+
names, i.e.: `visit_`.
|
29
|
+
|
30
|
+
<details>
|
31
|
+
Usually `case when` statements are using for the next reasons:
|
32
|
+
|
33
|
+
I. Mapping between different values.
|
34
|
+
("A" => 1, "B" => 2, ...)
|
35
|
+
|
36
|
+
This case is all about data representing. If you do not need to execute any code
|
37
|
+
it's better to use data structure which represents it. This way you are
|
38
|
+
separating concepts: code returns corresponding value and you have config-like
|
39
|
+
data structure which describes your data.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
%w[A B ...].index("A") + 1
|
43
|
+
# or
|
44
|
+
{ "A" => 1, "B" => 2 }.fetch("A")
|
45
|
+
```
|
46
|
+
|
47
|
+
II. Code execution depending of parameter or type:
|
48
|
+
|
49
|
+
- a. (:attack => attack, :defend => defend)
|
50
|
+
- b. (Feet => value * 0.348, Meters => `value`)
|
51
|
+
|
52
|
+
In this case code violates OOP and S[O]LID principle. Code shouldn't know about
|
53
|
+
object type and classes should be open for extension, but closed for
|
54
|
+
modification (but you can't do it with case-statements).
|
55
|
+
This is a signal that you have some problems with architecture.
|
56
|
+
|
57
|
+
a.
|
58
|
+
```ruby
|
59
|
+
attack: -> { execute_attack }, defend: -> { execute_defend }
|
60
|
+
# or
|
61
|
+
call(:"execute_#{action}")
|
62
|
+
```
|
63
|
+
|
64
|
+
b.
|
65
|
+
```ruby
|
66
|
+
class Meters; def to_metters; value; end
|
67
|
+
class Feet; def to_metters; value * 0.348; end
|
68
|
+
```
|
69
|
+
|
70
|
+
III. Code execution depending on some statement.
|
71
|
+
(`a > 0` => 1, `a == 0` => 0, `a < 0` => -1)
|
72
|
+
|
73
|
+
This case is combination of I and II -- high code complexity and unit-tests
|
74
|
+
complexity. There are variants how to solve it:
|
75
|
+
|
76
|
+
a. Rewrite to simple if statement
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
return 0 if a == 0
|
80
|
+
a > 0 ? 1 : -1
|
81
|
+
```
|
82
|
+
|
83
|
+
b. Move statements to lambdas:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
->(a) { a > 0 } => 1,
|
87
|
+
->(a) { a == 0 } => 0,
|
88
|
+
->(a) { a < 0 } => -1
|
89
|
+
```
|
90
|
+
|
91
|
+
This way decreases code complexity by delegating it to lambdas and makes it easy
|
92
|
+
to unit-testing because it's easy to test pure lambdas.
|
93
|
+
|
94
|
+
Such approach is named
|
95
|
+
[table-driven design](<https://www.d.umn.edu/~gshute/softeng/table-driven.html>)
|
96
|
+
. Table-driven methods are schemes that allow you to look up information in a
|
97
|
+
table rather than using logic statements (i.e. case, if). In simple cases,
|
98
|
+
it's quicker and easier to use logic statements, but as the logic chain becomes
|
99
|
+
more complex, table-driven code is simpler than complicated logic, easier to
|
100
|
+
modify and more efficient.
|
101
|
+
</details>
|
102
|
+
|
103
|
+
 raises on case statements
|
104
|
+
```ruby
|
105
|
+
|
106
|
+
case grade
|
107
|
+
when "A"
|
108
|
+
puts "Well done!"
|
109
|
+
when "B"
|
110
|
+
puts "Try harder!"
|
111
|
+
when "C"
|
112
|
+
puts "You need help!!!"
|
113
|
+
else
|
114
|
+
puts "You just making it up!"
|
115
|
+
end
|
116
|
+
|
19
117
|
```
|
20
118
|
## Ducalis::ControllersExcept
|
21
119
|
|
22
120
|
Prefer to use `:only` over `:except` in controllers because it's more explicit and will be easier to maintain for new developers.
|
23
|
-
|
121
|
+
|
122
|
+
 raises for `before_filters` with `except` method as array
|
24
123
|
```ruby
|
25
124
|
|
26
125
|
class MyController < ApplicationController
|
@@ -32,7 +131,8 @@ class MyController < ApplicationController
|
|
32
131
|
end
|
33
132
|
|
34
133
|
```
|
35
|
-
|
134
|
+
|
135
|
+
 raises for filters with many actions and only one `except` method
|
36
136
|
```ruby
|
37
137
|
|
38
138
|
class MyController < ApplicationController
|
@@ -45,7 +145,8 @@ class MyController < ApplicationController
|
|
45
145
|
end
|
46
146
|
|
47
147
|
```
|
48
|
-
|
148
|
+
|
149
|
+
 ignores `before_filters` without arguments
|
49
150
|
```ruby
|
50
151
|
|
51
152
|
class MyController < ApplicationController
|
@@ -59,30 +160,36 @@ end
|
|
59
160
|
## Ducalis::KeywordDefaults
|
60
161
|
|
61
162
|
Prefer to use keyword arguments for defaults. It increases readability and reduces ambiguities.
|
62
|
-
|
163
|
+
|
164
|
+
 raises if method definition contains default values
|
63
165
|
```ruby
|
64
166
|
def some_method(a, b, c = 3); end
|
65
167
|
```
|
66
|
-
|
168
|
+
|
169
|
+
 raises if class method definition contains default values
|
67
170
|
```ruby
|
68
171
|
def self.some_method(a, b, c = 3); end
|
69
172
|
```
|
70
|
-
|
173
|
+
|
174
|
+
 ignores if method definition contains default values through keywords
|
71
175
|
```ruby
|
72
176
|
def some_method(a, b, c: 3); end
|
73
177
|
```
|
74
|
-
|
178
|
+
|
179
|
+
 ignores for methods without arguments
|
75
180
|
```ruby
|
76
181
|
def some_method; end
|
77
182
|
```
|
78
|
-
|
183
|
+
|
184
|
+
 ignores for class methods without arguments
|
79
185
|
```ruby
|
80
186
|
def self.some_method; end
|
81
187
|
```
|
82
188
|
## Ducalis::ModuleLikeClass
|
83
189
|
|
84
190
|
Seems like it will be better to define initialize and pass %<args>s there instead of each method.
|
85
|
-
|
191
|
+
|
192
|
+
 raises if class doesn't contain constructor but accept the same args
|
86
193
|
```ruby
|
87
194
|
|
88
195
|
class MyClass
|
@@ -107,7 +214,8 @@ class MyClass
|
|
107
214
|
end
|
108
215
|
|
109
216
|
```
|
110
|
-
|
217
|
+
|
218
|
+
 raises for class with only one public method with args
|
111
219
|
```ruby
|
112
220
|
|
113
221
|
class MyClass
|
@@ -123,7 +231,8 @@ class MyClass
|
|
123
231
|
end
|
124
232
|
|
125
233
|
```
|
126
|
-
|
234
|
+
|
235
|
+
 ignores classes with custom includes
|
127
236
|
```ruby
|
128
237
|
|
129
238
|
class MyClass
|
@@ -135,7 +244,8 @@ class MyClass
|
|
135
244
|
end
|
136
245
|
|
137
246
|
```
|
138
|
-
|
247
|
+
|
248
|
+
 ignores classes with inheritance
|
139
249
|
```ruby
|
140
250
|
|
141
251
|
class MyClass < AnotherClass
|
@@ -151,7 +261,8 @@ class MyClass < AnotherClass
|
|
151
261
|
end
|
152
262
|
|
153
263
|
```
|
154
|
-
|
264
|
+
|
265
|
+
 ignores classes with one method and initializer
|
155
266
|
```ruby
|
156
267
|
|
157
268
|
class MyClass
|
@@ -169,7 +280,8 @@ end
|
|
169
280
|
|
170
281
|
It's better to pass already preprocessed params hash to services. Or you can use
|
171
282
|
`arcane` gem
|
172
|
-
|
283
|
+
|
284
|
+
 raises if user pass `params` as argument from controller
|
173
285
|
```ruby
|
174
286
|
|
175
287
|
class MyController < ApplicationController
|
@@ -179,7 +291,8 @@ class MyController < ApplicationController
|
|
179
291
|
end
|
180
292
|
|
181
293
|
```
|
182
|
-
|
294
|
+
|
295
|
+
 raises if user pass `params` as any argument from controller
|
183
296
|
```ruby
|
184
297
|
|
185
298
|
class MyController < ApplicationController
|
@@ -189,7 +302,8 @@ class MyController < ApplicationController
|
|
189
302
|
end
|
190
303
|
|
191
304
|
```
|
192
|
-
|
305
|
+
|
306
|
+
 raises if user pass `params` as keyword argument from controller
|
193
307
|
```ruby
|
194
308
|
|
195
309
|
class MyController < ApplicationController
|
@@ -199,7 +313,8 @@ class MyController < ApplicationController
|
|
199
313
|
end
|
200
314
|
|
201
315
|
```
|
202
|
-
|
316
|
+
|
317
|
+
 ignores passing only one `params` field
|
203
318
|
```ruby
|
204
319
|
|
205
320
|
class MyController < ApplicationController
|
@@ -209,7 +324,8 @@ class MyController < ApplicationController
|
|
209
324
|
end
|
210
325
|
|
211
326
|
```
|
212
|
-
|
327
|
+
|
328
|
+
 ignores passing processed `params`
|
213
329
|
```ruby
|
214
330
|
|
215
331
|
class MyController < ApplicationController
|
@@ -219,7 +335,8 @@ class MyController < ApplicationController
|
|
219
335
|
end
|
220
336
|
|
221
337
|
```
|
222
|
-
|
338
|
+
|
339
|
+
 ignores passing `params` from `arcane` gem
|
223
340
|
```ruby
|
224
341
|
|
225
342
|
class MyController < ApplicationController
|
@@ -228,11 +345,117 @@ class MyController < ApplicationController
|
|
228
345
|
end
|
229
346
|
end
|
230
347
|
|
348
|
+
```
|
349
|
+
## Ducalis::PossibleTap
|
350
|
+
|
351
|
+
Consider of using `.tap`, default ruby [method](<https://apidock.com/ruby/Object/tap>) which allows to replace intermediate variables with block, by this you are limiting scope pollution and make scope more clear. [Related article](<http://seejohncode.com/2012/01/02/ruby-tap-that/>).
|
352
|
+
|
353
|
+
 raises for methods with scope variable return
|
354
|
+
```ruby
|
355
|
+
|
356
|
+
def load_group
|
357
|
+
group = channel.groups.find(params[:group_id])
|
358
|
+
authorize group, :edit?
|
359
|
+
group
|
360
|
+
end
|
361
|
+
|
362
|
+
```
|
363
|
+
|
364
|
+
 raises for methods with instance variable changes and return
|
365
|
+
```ruby
|
366
|
+
|
367
|
+
def load_group
|
368
|
+
@group = Group.find(params[:id])
|
369
|
+
authorize @group
|
370
|
+
@group
|
371
|
+
end
|
372
|
+
|
373
|
+
```
|
374
|
+
|
375
|
+
 raises for methods with instance variable `||=` assign and return
|
376
|
+
```ruby
|
377
|
+
|
378
|
+
def define_roles
|
379
|
+
return [] unless employee
|
380
|
+
|
381
|
+
@roles ||= []
|
382
|
+
@roles << "primary" if employee.primary?
|
383
|
+
@roles << "contract" if employee.contract?
|
384
|
+
@roles
|
385
|
+
end
|
386
|
+
|
387
|
+
```
|
388
|
+
|
389
|
+
 raises for methods which return call on scope variable
|
390
|
+
```ruby
|
391
|
+
|
392
|
+
def load_group
|
393
|
+
elections = @elections.group_by(&:code)
|
394
|
+
result = elections.map do |code, elections|
|
395
|
+
{ code => statistic }
|
396
|
+
end
|
397
|
+
result << total_spend(@elections)
|
398
|
+
result.inject(:merge)
|
399
|
+
end
|
400
|
+
|
401
|
+
```
|
402
|
+
|
403
|
+
 raises for methods which return instance variable but have scope vars
|
404
|
+
```ruby
|
405
|
+
|
406
|
+
def generate_file(file_name)
|
407
|
+
@file = Tempfile.new([file_name, ".pdf"])
|
408
|
+
signed_pdf = some_new_stuff
|
409
|
+
@file.write(signed_pdf.to_pdf)
|
410
|
+
@file.close
|
411
|
+
@file
|
412
|
+
end
|
413
|
+
|
414
|
+
```
|
415
|
+
|
416
|
+
 ignores empty methods
|
417
|
+
```ruby
|
418
|
+
|
419
|
+
def edit
|
420
|
+
end
|
421
|
+
|
422
|
+
```
|
423
|
+
|
424
|
+
 ignores methods which body is just call
|
425
|
+
```ruby
|
426
|
+
|
427
|
+
def total_cost(cost_field)
|
428
|
+
Service.cost_sum(cost_field)
|
429
|
+
end
|
430
|
+
|
431
|
+
```
|
432
|
+
|
433
|
+
 ignores methods which return some statement
|
434
|
+
```ruby
|
435
|
+
|
436
|
+
def stop_terminated_employee
|
437
|
+
if current_user && current_user.terminated?
|
438
|
+
sign_out current_user
|
439
|
+
redirect_to new_user_session_path
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
|
444
|
+
```
|
445
|
+
|
446
|
+
 ignores methods which simply returns instance var without changes
|
447
|
+
```ruby
|
448
|
+
|
449
|
+
def employee
|
450
|
+
@employee
|
451
|
+
end
|
452
|
+
|
231
453
|
```
|
232
454
|
## Ducalis::PrivateInstanceAssign
|
233
455
|
|
234
456
|
Please, don't assign instance variables in controller's private methods. It's make hard to understand what variables are available in views.
|
235
|
-
|
457
|
+
|
458
|
+
 raises for assigning instance variables in controllers private methods
|
236
459
|
```ruby
|
237
460
|
|
238
461
|
class MyController < ApplicationController
|
@@ -244,7 +467,8 @@ class MyController < ApplicationController
|
|
244
467
|
end
|
245
468
|
|
246
469
|
```
|
247
|
-
|
470
|
+
|
471
|
+
 raises for memoization variables in controllers private methods
|
248
472
|
```ruby
|
249
473
|
|
250
474
|
class MyController < ApplicationController
|
@@ -256,7 +480,8 @@ class MyController < ApplicationController
|
|
256
480
|
end
|
257
481
|
|
258
482
|
```
|
259
|
-
|
483
|
+
|
484
|
+
 ignores memoization variables in controllers private methods with _
|
260
485
|
```ruby
|
261
486
|
|
262
487
|
class MyController < ApplicationController
|
@@ -268,7 +493,8 @@ class MyController < ApplicationController
|
|
268
493
|
end
|
269
494
|
|
270
495
|
```
|
271
|
-
|
496
|
+
|
497
|
+
 ignores assigning instance variables in controllers public methods
|
272
498
|
```ruby
|
273
499
|
|
274
500
|
class MyController < ApplicationController
|
@@ -295,30 +521,50 @@ current_group.employees.find(params[:id])
|
|
295
521
|
# better then
|
296
522
|
Employee.find(params[:id])
|
297
523
|
```
|
298
|
-
|
524
|
+
|
525
|
+
 raises if somewhere AR search was called on not protected scope
|
299
526
|
```ruby
|
300
527
|
Group.find(8)
|
301
528
|
```
|
302
|
-
|
529
|
+
|
530
|
+
 raises if AR search was called even for chain of calls
|
303
531
|
```ruby
|
304
532
|
Group.includes(:some_relation).find(8)
|
305
533
|
```
|
306
|
-
|
534
|
+
|
535
|
+
 ignores where statements and still raises error
|
307
536
|
```ruby
|
308
537
|
Group.includes(:some_relation).where(name: "John").find(8)
|
538
|
+
```
|
539
|
+
|
540
|
+
 ignores find method with passed block
|
541
|
+
```ruby
|
542
|
+
MAPPING.find { |x| x == 42 }
|
543
|
+
```
|
544
|
+
|
545
|
+
 ignores find method with passed multiline block
|
546
|
+
```ruby
|
547
|
+
|
548
|
+
MAPPING.find do |x|
|
549
|
+
x == 42
|
550
|
+
end
|
551
|
+
|
309
552
|
```
|
310
553
|
## Ducalis::RaiseWithourErrorClass
|
311
554
|
|
312
555
|
It's better to add exception class as raise argument. It will make easier to catch and process it later.
|
313
|
-
|
556
|
+
|
557
|
+
 raises when `raise` called without exception class
|
314
558
|
```ruby
|
315
559
|
raise "Something went wrong"
|
316
560
|
```
|
317
|
-
|
561
|
+
|
562
|
+
 ignores when `raise` called with exception class
|
318
563
|
```ruby
|
319
564
|
raise StandardError, "Something went wrong"
|
320
565
|
```
|
321
|
-
|
566
|
+
|
567
|
+
 ignores when `raise` called with exception instance
|
322
568
|
```ruby
|
323
569
|
raise StandardError.new("Something went wrong")
|
324
570
|
```
|
@@ -331,14 +577,16 @@ It will allow you to reuse this regex and provide instructions for others.
|
|
331
577
|
CONST_NAME = %<constant>s # "%<example>s"
|
332
578
|
%<fixed_string>s
|
333
579
|
```
|
334
|
-
|
580
|
+
|
581
|
+
 raises if somewhere in code used regex which is not moved to const
|
335
582
|
```ruby
|
336
583
|
|
337
584
|
name = "john"
|
338
585
|
puts "hi" if name =~ /john/
|
339
586
|
|
340
587
|
```
|
341
|
-
|
588
|
+
|
589
|
+
 ignores matching constants
|
342
590
|
```ruby
|
343
591
|
|
344
592
|
REGEX = /john/
|
@@ -346,14 +594,16 @@ name = "john"
|
|
346
594
|
puts "hi" if name =~ REGEX
|
347
595
|
|
348
596
|
```
|
349
|
-
|
597
|
+
|
598
|
+
 ignores named ruby constants
|
350
599
|
```ruby
|
351
600
|
|
352
601
|
name = "john"
|
353
602
|
puts "hi" if name =~ /[[:alpha:]]/
|
354
603
|
|
355
604
|
```
|
356
|
-
|
605
|
+
|
606
|
+
 ignores dynamic regexs
|
357
607
|
```ruby
|
358
608
|
|
359
609
|
name = "john"
|
@@ -364,7 +614,8 @@ puts "hi" if name =~ /.{#{name.length}}/
|
|
364
614
|
|
365
615
|
It's better for controllers to stay adherent to REST:
|
366
616
|
http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/
|
367
|
-
|
617
|
+
|
618
|
+
 raises for controllers with non-REST methods
|
368
619
|
```ruby
|
369
620
|
|
370
621
|
class MyController < ApplicationController
|
@@ -373,7 +624,8 @@ class MyController < ApplicationController
|
|
373
624
|
end
|
374
625
|
|
375
626
|
```
|
376
|
-
|
627
|
+
|
628
|
+
 ignores controllers with private non-REST methods
|
377
629
|
```ruby
|
378
630
|
|
379
631
|
class MyController < ApplicationController
|
@@ -383,7 +635,8 @@ class MyController < ApplicationController
|
|
383
635
|
end
|
384
636
|
|
385
637
|
```
|
386
|
-
|
638
|
+
|
639
|
+
 ignores controllers with only REST methods
|
387
640
|
```ruby
|
388
641
|
|
389
642
|
class MyController < ApplicationController
|
@@ -397,7 +650,8 @@ class MyController < ApplicationController
|
|
397
650
|
end
|
398
651
|
|
399
652
|
```
|
400
|
-
|
653
|
+
|
654
|
+
 ignores non-controllers with non-REST methods
|
401
655
|
```ruby
|
402
656
|
|
403
657
|
class MyClass
|
@@ -411,14 +665,16 @@ end
|
|
411
665
|
|
412
666
|
Please, do not suppress RuboCop metrics, may be you can introduce some refactoring or another concept.
|
413
667
|
|
414
|
-
|
668
|
+
|
669
|
+
 raises on RuboCop disable comments
|
415
670
|
```ruby
|
416
671
|
|
417
672
|
# rubocop:disable Metrics/ParameterLists
|
418
673
|
def some_method(a, b, c, d, e, f); end
|
419
674
|
|
420
675
|
```
|
421
|
-
|
676
|
+
|
677
|
+
 ignores comment without RuboCop disabling
|
422
678
|
```ruby
|
423
679
|
|
424
680
|
# some meaningful comment
|
@@ -430,14 +686,16 @@ def some_method(a, b, c, d, e, f); end
|
|
430
686
|
Please, do not use strings as arguments for %<method_name>s argument.
|
431
687
|
It's hard to test, grep sources, code highlighting and so on.
|
432
688
|
Consider using of symbols or lambdas for complex expressions.
|
433
|
-
|
689
|
+
|
690
|
+
 raises for string if argument
|
434
691
|
```ruby
|
435
692
|
|
436
693
|
before_save :set_full_name,
|
437
694
|
if: 'name_changed? || postfix_name_changed?'
|
438
695
|
|
439
696
|
```
|
440
|
-
|
697
|
+
|
698
|
+
 ignores lambda if argument
|
441
699
|
```ruby
|
442
700
|
validates :file, if: -> { remote_url.blank? }
|
443
701
|
```
|
@@ -445,7 +703,8 @@ validates :file, if: -> { remote_url.blank? }
|
|
445
703
|
|
446
704
|
Please, add comment why are you including non-realized gem version for %<gem>s.
|
447
705
|
It will increase [bus-factor](<https://en.wikipedia.org/wiki/Bus_factor>).
|
448
|
-
|
706
|
+
|
707
|
+
 raises for gem from github without comment
|
449
708
|
```ruby
|
450
709
|
|
451
710
|
gem 'a'
|
@@ -453,7 +712,8 @@ gem 'b', '~> 1.3.1'
|
|
453
712
|
gem 'c', git: 'https://github.com/c/c'
|
454
713
|
|
455
714
|
```
|
456
|
-
|
715
|
+
|
716
|
+
 ignores for gem from github with comment
|
457
717
|
```ruby
|
458
718
|
|
459
719
|
gem 'a'
|
@@ -475,7 +735,8 @@ def index
|
|
475
735
|
do_something
|
476
736
|
end
|
477
737
|
```
|
478
|
-
|
738
|
+
|
739
|
+
 raises for `before_filters` with only one method as array
|
479
740
|
```ruby
|
480
741
|
|
481
742
|
class MyController < ApplicationController
|
@@ -486,7 +747,8 @@ class MyController < ApplicationController
|
|
486
747
|
end
|
487
748
|
|
488
749
|
```
|
489
|
-
|
750
|
+
|
751
|
+
 raises for `before_filters` with only one method as keyword array
|
490
752
|
```ruby
|
491
753
|
|
492
754
|
class MyController < ApplicationController
|
@@ -497,7 +759,8 @@ class MyController < ApplicationController
|
|
497
759
|
end
|
498
760
|
|
499
761
|
```
|
500
|
-
|
762
|
+
|
763
|
+
 raises for `before_filters` with many actions and only one method
|
501
764
|
```ruby
|
502
765
|
|
503
766
|
class MyController < ApplicationController
|
@@ -509,7 +772,8 @@ class MyController < ApplicationController
|
|
509
772
|
end
|
510
773
|
|
511
774
|
```
|
512
|
-
|
775
|
+
|
776
|
+
 raises for `before_filters` with only one method as argument
|
513
777
|
```ruby
|
514
778
|
|
515
779
|
class MyController < ApplicationController
|
@@ -520,7 +784,8 @@ class MyController < ApplicationController
|
|
520
784
|
end
|
521
785
|
|
522
786
|
```
|
523
|
-
|
787
|
+
|
788
|
+
 ignores `before_filters` without arguments
|
524
789
|
```ruby
|
525
790
|
|
526
791
|
class MyController < ApplicationController
|
@@ -531,7 +796,8 @@ class MyController < ApplicationController
|
|
531
796
|
end
|
532
797
|
|
533
798
|
```
|
534
|
-
|
799
|
+
|
800
|
+
 ignores `before_filters` with `only` and many arguments
|
535
801
|
```ruby
|
536
802
|
|
537
803
|
class MyController < ApplicationController
|
@@ -543,7 +809,8 @@ class MyController < ApplicationController
|
|
543
809
|
end
|
544
810
|
|
545
811
|
```
|
546
|
-
|
812
|
+
|
813
|
+
 ignores `before_filters` with `except` and one argument
|
547
814
|
```ruby
|
548
815
|
|
549
816
|
class MyController < ApplicationController
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,8 +2,12 @@
|
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/ducalis)
|
4
4
|
[](https://travis-ci.org/ignat-z/ducalis)
|
5
|
+
[](https://codeclimate.com/github/ignat-z/ducalis)
|
5
6
|
|
6
|
-
__Ducalis__ is RuboCop
|
7
|
+
__Ducalis__ is RuboCop-based static code analyzer for enterprise Rails applications.
|
8
|
+
As __Ducalis__ isn't style checker and could sometimes be false-positive it's not
|
9
|
+
necessary to follow all it rules, the main purpose of __Ducalis__ is help to find
|
10
|
+
possible weak code parts.
|
7
11
|
|
8
12
|
## Installation
|
9
13
|
|
@@ -16,3 +20,41 @@ gem 'ducalis'
|
|
16
20
|
## License
|
17
21
|
|
18
22
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
There are a lot of variants how you can use __Ducalis__:
|
27
|
+
|
28
|
+
1. As CLI application. In this mode __Ducalis__ will notify you about any
|
29
|
+
possible violations in CLI.
|
30
|
+
```
|
31
|
+
ducalis
|
32
|
+
ducalis app/controllers/
|
33
|
+
```
|
34
|
+
As __Ducalis__ allows to pass build even with violations it's make sense to run
|
35
|
+
__Ducalis__ across current branch or index:
|
36
|
+
```
|
37
|
+
ducalis --branch
|
38
|
+
ducalis --index
|
39
|
+
```
|
40
|
+
|
41
|
+
2. As CLI application in CI mode: In this mode __Ducalis__ will notify you about
|
42
|
+
any violations in your PR.
|
43
|
+
```
|
44
|
+
ducalis --ci --repo="author/repo" --id=3575 --dry
|
45
|
+
ducalis --ci --repo="author/repo" --id=3575
|
46
|
+
ducalis --ci --adapter=circle # mode for running on CircleCI
|
47
|
+
```
|
48
|
+
`--dry` option declares that output will be printed in console, if you will run
|
49
|
+
without this option __Ducalis__ will notify about violations in your PR.
|
50
|
+
_N.B._ You should provide GITHUB_TOKEN Env to allow __Ducalis__ download your PR
|
51
|
+
code and write review comments.
|
52
|
+
|
53
|
+
3. As stand-alone server mode: In this mode __Ducalis__ will work as server,
|
54
|
+
listen webhooks from GitHub, and notify about any violations in PR. There is a
|
55
|
+
`Dockerfile` which you could use to run server for this or run it manually like
|
56
|
+
rack application. All related files are located in the `client/` directory.
|
57
|
+
|
58
|
+
In CLI modes you can provide yours `.ducalis.yml` file based on
|
59
|
+
[default](https://github.com/ignat-z/ducalis/blob/master/config/.ducalis.yml) by
|
60
|
+
`-c` flag or simply putting it in your project directory.
|
data/config/.ducalis.yml
CHANGED
@@ -5,9 +5,15 @@ AllCops:
|
|
5
5
|
- 'node_modules/**/*'
|
6
6
|
- 'vendor/bundle/**/*'
|
7
7
|
|
8
|
+
Ducalis/CaseMapping:
|
9
|
+
Enabled: true
|
10
|
+
|
8
11
|
Ducalis/CallbacksActiverecord:
|
9
12
|
Enabled: true
|
10
13
|
|
14
|
+
Ducalis/PossibleTap:
|
15
|
+
Enabled: true
|
16
|
+
|
11
17
|
Ducalis/ProtectedScopeCop:
|
12
18
|
Enabled: true
|
13
19
|
|
@@ -39,6 +45,8 @@ Ducalis/UncommentedGem:
|
|
39
45
|
|
40
46
|
Ducalis/ParamsPassing:
|
41
47
|
Enabled: true
|
48
|
+
Exclude:
|
49
|
+
- 'spec/**/*.rb'
|
42
50
|
|
43
51
|
Ducalis/ControllersExcept:
|
44
52
|
Enabled: true
|
data/ducalis.gemspec
CHANGED
@@ -29,13 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
30
30
|
|
31
31
|
spec.add_dependency 'policial', '0.0.4'
|
32
|
-
spec.add_dependency 'rubocop', '~> 0.50.0'
|
33
32
|
spec.add_dependency 'regexp-examples', '~> 1.3', '>= 1.3.2'
|
34
33
|
spec.add_dependency 'thor', '~> 0.20.0'
|
35
34
|
spec.add_dependency 'git', '~> 1.3', '>= 1.3.0'
|
36
|
-
|
37
|
-
spec.add_development_dependency 'bundler', '~> 1.16.a'
|
38
|
-
spec.add_development_dependency 'rake', '~> 12.1'
|
39
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
40
|
-
spec.add_development_dependency 'pry', '~> 0.10', '>= 0.10.0'
|
41
35
|
end
|
data/lib/ducalis.rb
CHANGED
@@ -32,10 +32,12 @@ require 'ducalis/patched_rubocop/git_turget_finder'
|
|
32
32
|
require 'ducalis/patched_rubocop/rubo_cop'
|
33
33
|
|
34
34
|
require 'ducalis/cops/callbacks_activerecord'
|
35
|
+
require 'ducalis/cops/case_mapping'
|
35
36
|
require 'ducalis/cops/controllers_except'
|
36
37
|
require 'ducalis/cops/keyword_defaults'
|
37
38
|
require 'ducalis/cops/module_like_class'
|
38
39
|
require 'ducalis/cops/params_passing'
|
40
|
+
require 'ducalis/cops/possible_tap'
|
39
41
|
require 'ducalis/cops/private_instance_assign'
|
40
42
|
require 'ducalis/cops/protected_scope_cop'
|
41
43
|
require 'ducalis/cops/raise_withour_error_class'
|
@@ -4,6 +4,7 @@ module Ducalis
|
|
4
4
|
module Commentators
|
5
5
|
class Github
|
6
6
|
STATUS = 'COMMENT'
|
7
|
+
SIMILARITY_THRESHOLD = 0.8
|
7
8
|
|
8
9
|
def initialize(config)
|
9
10
|
@config = config
|
@@ -26,13 +27,16 @@ module Ducalis
|
|
26
27
|
[
|
27
28
|
violation.filename == commented_violation[:path],
|
28
29
|
violation.line.patch_position == commented_violation[:position],
|
29
|
-
|
30
|
-
violation.message, commented_violation[:body]
|
31
|
-
) > 0.9
|
30
|
+
similar_messages?(violation.message, commented_violation[:body])
|
32
31
|
].all?
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
35
|
+
def similar_messages?(message, body)
|
36
|
+
body.include?(message) ||
|
37
|
+
Utils.similarity(message, body) > SIMILARITY_THRESHOLD
|
38
|
+
end
|
39
|
+
|
36
40
|
def commented_violations
|
37
41
|
@commented_violations ||=
|
38
42
|
Utils.octokit.pull_request_comments(@config.repo, @config.id)
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
|
5
|
+
module Ducalis
|
6
|
+
class CaseMapping < RuboCop::Cop::Cop
|
7
|
+
OFFENSE = %(
|
8
|
+
Try to avoid `case when` statements. You can replace it with a sequence of
|
9
|
+
`if... elsif... elsif... else`. For cases where you need to choose from a
|
10
|
+
large number of possibilities, you can create a dictionary mapping case values
|
11
|
+
to functions to call by `call`. It's nice to have prefix for the method
|
12
|
+
names, i.e.: `visit_`.
|
13
|
+
|
14
|
+
<details>
|
15
|
+
Usually `case when` statements are using for the next reasons:
|
16
|
+
|
17
|
+
I. Mapping between different values.
|
18
|
+
("A" => 1, "B" => 2, ...)
|
19
|
+
|
20
|
+
This case is all about data representing. If you do not need to execute any code
|
21
|
+
it's better to use data structure which represents it. This way you are
|
22
|
+
separating concepts: code returns corresponding value and you have config-like
|
23
|
+
data structure which describes your data.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
%w[A B ...].index("A") + 1
|
27
|
+
# or
|
28
|
+
{ "A" => 1, "B" => 2 }.fetch("A")
|
29
|
+
```
|
30
|
+
|
31
|
+
II. Code execution depending of parameter or type:
|
32
|
+
|
33
|
+
- a. (:attack => attack, :defend => defend)
|
34
|
+
- b. (Feet => value * 0.348, Meters => `value`)
|
35
|
+
|
36
|
+
In this case code violates OOP and S[O]LID principle. Code shouldn't know about
|
37
|
+
object type and classes should be open for extension, but closed for
|
38
|
+
modification (but you can't do it with case-statements).
|
39
|
+
This is a signal that you have some problems with architecture.
|
40
|
+
|
41
|
+
a.
|
42
|
+
```ruby
|
43
|
+
attack: -> { execute_attack }, defend: -> { execute_defend }
|
44
|
+
#{(action = '#{' + 'action' + '}') && '# or'}
|
45
|
+
call(:"execute_#{action}")
|
46
|
+
```
|
47
|
+
|
48
|
+
b.
|
49
|
+
```ruby
|
50
|
+
class Meters; def to_metters; value; end
|
51
|
+
class Feet; def to_metters; value * 0.348; end
|
52
|
+
```
|
53
|
+
|
54
|
+
III. Code execution depending on some statement.
|
55
|
+
(`a > 0` => 1, `a == 0` => 0, `a < 0` => -1)
|
56
|
+
|
57
|
+
This case is combination of I and II -- high code complexity and unit-tests
|
58
|
+
complexity. There are variants how to solve it:
|
59
|
+
|
60
|
+
a. Rewrite to simple if statement
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
return 0 if a == 0
|
64
|
+
a > 0 ? 1 : -1
|
65
|
+
```
|
66
|
+
|
67
|
+
b. Move statements to lambdas:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
->(a) { a > 0 } => 1,
|
71
|
+
->(a) { a == 0 } => 0,
|
72
|
+
->(a) { a < 0 } => -1
|
73
|
+
```
|
74
|
+
|
75
|
+
This way decreases code complexity by delegating it to lambdas and makes it easy
|
76
|
+
to unit-testing because it's easy to test pure lambdas.
|
77
|
+
|
78
|
+
Such approach is named
|
79
|
+
[table-driven design](<https://www.d.umn.edu/~gshute/softeng/table-driven.html>)
|
80
|
+
. Table-driven methods are schemes that allow you to look up information in a
|
81
|
+
table rather than using logic statements (i.e. case, if). In simple cases,
|
82
|
+
it's quicker and easier to use logic statements, but as the logic chain becomes
|
83
|
+
more complex, table-driven code is simpler than complicated logic, easier to
|
84
|
+
modify and more efficient.
|
85
|
+
</details>
|
86
|
+
).strip
|
87
|
+
|
88
|
+
def on_case(node)
|
89
|
+
add_offense(node, :expression, OFFENSE)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
|
5
|
+
module Ducalis
|
6
|
+
class PossibleTap < RuboCop::Cop::Cop
|
7
|
+
include RuboCop::Cop::DefNode
|
8
|
+
|
9
|
+
OFFENSE = %(
|
10
|
+
Consider of using `.tap`, default ruby \
|
11
|
+
[method](<https://apidock.com/ruby/Object/tap>) which allows to replace \
|
12
|
+
intermediate variables with block, by this you are limiting scope pollution \
|
13
|
+
and make scope more clear. \
|
14
|
+
[Related article](<http://seejohncode.com/2012/01/02/ruby-tap-that/>).
|
15
|
+
).strip
|
16
|
+
|
17
|
+
PAIRS = {
|
18
|
+
lvar: :lvasgn,
|
19
|
+
ivar: :ivasgn
|
20
|
+
}.freeze
|
21
|
+
ASSIGNS = PAIRS.keys
|
22
|
+
|
23
|
+
def on_def(node)
|
24
|
+
_name, _args, body = *node
|
25
|
+
return if body.nil?
|
26
|
+
return unless (possibe_var = return_var?(body) || return_var_call?(body))
|
27
|
+
return unless (assign_node = find_assign(body, possibe_var))
|
28
|
+
add_offense(assign_node, :expression, OFFENSE)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def unwrap_asign(node)
|
34
|
+
node.type == :or_asgn ? node.children.first : node
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_assign(body, var_node)
|
38
|
+
subnodes(body).find do |subnode|
|
39
|
+
unwrap_asign(subnode).type == PAIRS[var_node.type] &&
|
40
|
+
unwrap_asign(subnode).to_a.first == var_node.to_a.first
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def return_var?(body)
|
45
|
+
return unless body.children.last.respond_to?(:type)
|
46
|
+
return unless ASSIGNS.include?(body.children.last.type)
|
47
|
+
body.children.last
|
48
|
+
end
|
49
|
+
|
50
|
+
def return_var_call?(body)
|
51
|
+
return unless body.children.last.respond_to?(:children)
|
52
|
+
subnodes(body.children.last).find { |node| ASSIGNS.include?(node.type) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def subnodes(node)
|
56
|
+
node.children.select { |child| child.respond_to?(:type) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -17,8 +17,9 @@ Employee.find(params[:id])
|
|
17
17
|
}.strip
|
18
18
|
|
19
19
|
def on_send(node)
|
20
|
-
_, method_name, = *node
|
20
|
+
_, method_name, *args = *node
|
21
21
|
return unless method_name == :find
|
22
|
+
return if args.empty?
|
22
23
|
return unless children(node).any? { |subnode| subnode.type == :const }
|
23
24
|
add_offense(node, :expression, OFFENSE)
|
24
25
|
end
|
@@ -69,12 +69,20 @@ class Documentation
|
|
69
69
|
] +
|
70
70
|
specs.map do |(it, code)|
|
71
71
|
[
|
72
|
-
"
|
73
|
-
"```ruby\n#{code.join("\n")}\n```"
|
72
|
+
"\n#{color(it)} #{it}", # case description
|
73
|
+
"```ruby\n#{code.join("\n")}\n```" # code example
|
74
74
|
]
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
+
def color(it)
|
79
|
+
if it.include?('raises')
|
80
|
+
''
|
81
|
+
else
|
82
|
+
''
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
78
86
|
def spec_cases_for(f)
|
79
87
|
source_code = File.read(
|
80
88
|
f.sub('/lib/ducalis/', '/spec/')
|
data/lib/ducalis/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ducalis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ignat Zakrevsky
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-10-
|
11
|
+
date: 2017-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: policial
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.0.4
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rubocop
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 0.50.0
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 0.50.0
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: regexp-examples
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -92,68 +78,6 @@ dependencies:
|
|
92
78
|
- - ">="
|
93
79
|
- !ruby/object:Gem::Version
|
94
80
|
version: 1.3.0
|
95
|
-
- !ruby/object:Gem::Dependency
|
96
|
-
name: bundler
|
97
|
-
requirement: !ruby/object:Gem::Requirement
|
98
|
-
requirements:
|
99
|
-
- - "~>"
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
version: 1.16.a
|
102
|
-
type: :development
|
103
|
-
prerelease: false
|
104
|
-
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
requirements:
|
106
|
-
- - "~>"
|
107
|
-
- !ruby/object:Gem::Version
|
108
|
-
version: 1.16.a
|
109
|
-
- !ruby/object:Gem::Dependency
|
110
|
-
name: rake
|
111
|
-
requirement: !ruby/object:Gem::Requirement
|
112
|
-
requirements:
|
113
|
-
- - "~>"
|
114
|
-
- !ruby/object:Gem::Version
|
115
|
-
version: '12.1'
|
116
|
-
type: :development
|
117
|
-
prerelease: false
|
118
|
-
version_requirements: !ruby/object:Gem::Requirement
|
119
|
-
requirements:
|
120
|
-
- - "~>"
|
121
|
-
- !ruby/object:Gem::Version
|
122
|
-
version: '12.1'
|
123
|
-
- !ruby/object:Gem::Dependency
|
124
|
-
name: rspec
|
125
|
-
requirement: !ruby/object:Gem::Requirement
|
126
|
-
requirements:
|
127
|
-
- - "~>"
|
128
|
-
- !ruby/object:Gem::Version
|
129
|
-
version: '3.0'
|
130
|
-
type: :development
|
131
|
-
prerelease: false
|
132
|
-
version_requirements: !ruby/object:Gem::Requirement
|
133
|
-
requirements:
|
134
|
-
- - "~>"
|
135
|
-
- !ruby/object:Gem::Version
|
136
|
-
version: '3.0'
|
137
|
-
- !ruby/object:Gem::Dependency
|
138
|
-
name: pry
|
139
|
-
requirement: !ruby/object:Gem::Requirement
|
140
|
-
requirements:
|
141
|
-
- - "~>"
|
142
|
-
- !ruby/object:Gem::Version
|
143
|
-
version: '0.10'
|
144
|
-
- - ">="
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
version: 0.10.0
|
147
|
-
type: :development
|
148
|
-
prerelease: false
|
149
|
-
version_requirements: !ruby/object:Gem::Requirement
|
150
|
-
requirements:
|
151
|
-
- - "~>"
|
152
|
-
- !ruby/object:Gem::Version
|
153
|
-
version: '0.10'
|
154
|
-
- - ">="
|
155
|
-
- !ruby/object:Gem::Version
|
156
|
-
version: 0.10.0
|
157
81
|
description: " Ducalis is RuboCop based static code analyzer for enterprise Rails
|
158
82
|
\ applications.\n"
|
159
83
|
email:
|
@@ -163,6 +87,7 @@ executables:
|
|
163
87
|
extensions: []
|
164
88
|
extra_rdoc_files: []
|
165
89
|
files:
|
90
|
+
- ".codeclimate.yml"
|
166
91
|
- ".gitignore"
|
167
92
|
- ".rspec"
|
168
93
|
- ".rubocop.yml"
|
@@ -186,10 +111,12 @@ files:
|
|
186
111
|
- lib/ducalis/commentators/console.rb
|
187
112
|
- lib/ducalis/commentators/github.rb
|
188
113
|
- lib/ducalis/cops/callbacks_activerecord.rb
|
114
|
+
- lib/ducalis/cops/case_mapping.rb
|
189
115
|
- lib/ducalis/cops/controllers_except.rb
|
190
116
|
- lib/ducalis/cops/keyword_defaults.rb
|
191
117
|
- lib/ducalis/cops/module_like_class.rb
|
192
118
|
- lib/ducalis/cops/params_passing.rb
|
119
|
+
- lib/ducalis/cops/possible_tap.rb
|
193
120
|
- lib/ducalis/cops/private_instance_assign.rb
|
194
121
|
- lib/ducalis/cops/protected_scope_cop.rb
|
195
122
|
- lib/ducalis/cops/raise_withour_error_class.rb
|