ducalis 0.5.10 → 0.5.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/DOCUMENTATION.md +165 -99
- data/Gemfile.lock +1 -1
- data/README.md +11 -0
- data/config/.ducalis.yml +10 -1
- data/lib/ducalis/cops/black_list_suffix.rb +4 -6
- data/lib/ducalis/cops/callbacks_activerecord.rb +3 -5
- data/lib/ducalis/cops/case_mapping.rb +61 -79
- data/lib/ducalis/cops/controllers_except.rb +2 -3
- data/lib/ducalis/cops/keyword_defaults.rb +2 -3
- data/lib/ducalis/cops/module_like_class.rb +2 -3
- data/lib/ducalis/cops/options_argument.rb +48 -0
- data/lib/ducalis/cops/params_passing.rb +2 -3
- data/lib/ducalis/cops/possible_tap.rb +3 -6
- data/lib/ducalis/cops/preferable_methods.rb +15 -4
- data/lib/ducalis/cops/private_instance_assign.rb +5 -11
- data/lib/ducalis/cops/protected_scope_cop.rb +4 -5
- data/lib/ducalis/cops/raise_without_error_class.rb +2 -3
- data/lib/ducalis/cops/regex_cop.rb +7 -9
- data/lib/ducalis/cops/rest_only_cop.rb +2 -2
- data/lib/ducalis/cops/rubocop_disable.rb +2 -3
- data/lib/ducalis/cops/standard_methods.rb +20 -0
- data/lib/ducalis/cops/strings_in_activerecords.rb +2 -4
- data/lib/ducalis/cops/too_long_workers.rb +3 -5
- data/lib/ducalis/cops/uncommented_gem.rb +3 -4
- data/lib/ducalis/cops/unlocked_gem.rb +22 -0
- data/lib/ducalis/cops/useless_only.rb +5 -6
- data/lib/ducalis/documentation.rb +1 -1
- data/lib/ducalis/passed_args.rb +3 -1
- data/lib/ducalis/patched_rubocop/rubo_cop.rb +8 -0
- data/lib/ducalis/version.rb +1 -1
- data/lib/ducalis.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e9545be92ee4417db0ef82c0becd1ccb5c7a8cd
|
4
|
+
data.tar.gz: a11c6683e3364627615fcaec02444e21100aadb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 21529fb2e38466c9c011bf2310811db2bc35b197d178e8c87ab2067a67a35ef2987a1920dcc6f54bc41945c774c84d90fdbf4ca1561defc1d3f55dc1fe089356
|
7
|
+
data.tar.gz: 6d8d1be1aeda6b342d594f87bb9a68037712aea33b13802b7637d8c5055c608a9686e5d44647e41f3595ece8f9d6e2a2387307b21f9975c175657cd2bd7dbf84
|
data/DOCUMENTATION.md
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
## Ducalis::BlackListSuffix
|
2
2
|
|
3
|
-
Please, avoid using of class suffixes like `Meneger`, `Client`
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
behavior to it.
|
8
|
-
Related [article](<http://www.carlopescio.com/2011/04/your-coding-conventions-are-hurting-you.html>)
|
3
|
+
Please, avoid using of class suffixes like `Meneger`, `Client` and so on. If it has no parts, change the name of the class to what each object is managing.
|
4
|
+
|
5
|
+
It's ok to use Manager as subclass of Person, which is there to refine a type of personal that has management behavior to it.
|
6
|
+
Related [article](<http://www.carlopescio.com/2011/04/your-coding-conventions-are-hurting-you.html>)
|
9
7
|
|
10
8
|
![](https://placehold.it/10/f03c15/000000?text=+) raises on classes with suffixes from black list
|
11
9
|
```ruby
|
@@ -32,10 +30,8 @@ end
|
|
32
30
|
```
|
33
31
|
## Ducalis::CallbacksActiverecord
|
34
32
|
|
35
|
-
Please, avoid using of callbacks for models. It's better to
|
36
|
-
|
37
|
-
/ services: to construct new objects. You can read more
|
38
|
-
[here](https://medium.com/planet-arkency/a61fd75ab2d3).
|
33
|
+
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.
|
34
|
+
You can read more [here](https://medium.com/planet-arkency/a61fd75ab2d3).
|
39
35
|
|
40
36
|
![](https://placehold.it/10/f03c15/000000?text=+) raises on ActiveRecord classes which contains callbacks
|
41
37
|
```ruby
|
@@ -56,20 +52,14 @@ end
|
|
56
52
|
```
|
57
53
|
## Ducalis::CaseMapping
|
58
54
|
|
59
|
-
Try to avoid `case when` statements. You can replace it with a sequence
|
60
|
-
|
61
|
-
from a large number of possibilities, you can create a dictionary
|
62
|
-
mapping case values to functions to call by `call`. It's nice to have
|
63
|
-
prefix for the method names, i.e.: `visit_`.
|
55
|
+
Try to avoid `case when` statements. You can replace it with a sequence of `if... elsif... elsif... else`.
|
56
|
+
For cases where you need to choose from a large number of possibilities, you can create a dictionary mapping case values to functions to call by `call`. It's nice to have prefix for the method names, i.e.: `visit_`.
|
64
57
|
Usually `case when` statements are using for the next reasons:
|
65
58
|
|
66
59
|
I. Mapping between different values.
|
67
60
|
("A" => 1, "B" => 2, ...)
|
68
61
|
|
69
|
-
This case is all about data representing. If you do not need to execute any code
|
70
|
-
it's better to use data structure which represents it. This way you are
|
71
|
-
separating concepts: code returns corresponding value and you have config-like
|
72
|
-
data structure which describes your data.
|
62
|
+
This case is all about data representing. If you do not need to execute any code it's better to use data structure which represents it. This way you are separating concepts: code returns corresponding value and you have config-like data structure which describes your data.
|
73
63
|
|
74
64
|
```ruby
|
75
65
|
%w[A B ...].index("A") + 1
|
@@ -82,19 +72,16 @@ II. Code execution depending of parameter or type:
|
|
82
72
|
- a. (:attack => attack, :defend => defend)
|
83
73
|
- b. (Feet => value * 0.348, Meters => `value`)
|
84
74
|
|
85
|
-
In this case code violates OOP and S[O]LID principle. Code shouldn't know about
|
86
|
-
object type and classes should be open for extension, but closed for
|
87
|
-
modification (but you can't do it with case-statements).
|
88
|
-
This is a signal that you have some problems with architecture.
|
75
|
+
In this case code violates OOP and S[O]LID principle. Code shouldn't know about object type and classes should be open for extension, but closed for modification (but you can't do it with case-statements). This is a signal that you have some problems with architecture.
|
89
76
|
|
90
|
-
|
77
|
+
a.
|
91
78
|
```ruby
|
92
79
|
attack: -> { execute_attack }, defend: -> { execute_defend }
|
93
80
|
# or
|
94
81
|
call(:"execute_#{action}")
|
95
82
|
```
|
96
83
|
|
97
|
-
|
84
|
+
b.
|
98
85
|
```ruby
|
99
86
|
class Meters; def to_metters; value; end
|
100
87
|
class Feet; def to_metters; value * 0.348; end
|
@@ -103,17 +90,16 @@ class Feet; def to_metters; value * 0.348; end
|
|
103
90
|
III. Code execution depending on some statement.
|
104
91
|
(`a > 0` => 1, `a == 0` => 0, `a < 0` => -1)
|
105
92
|
|
106
|
-
This case is combination of I and II -- high code complexity and unit-tests
|
107
|
-
complexity. There are variants how to solve it:
|
93
|
+
This case is combination of I and II -- high code complexity and unit-tests complexity. There are variants how to solve it:
|
108
94
|
|
109
|
-
|
95
|
+
a. Rewrite to simple if statement
|
110
96
|
|
111
97
|
```ruby
|
112
98
|
return 0 if a == 0
|
113
99
|
a > 0 ? 1 : -1
|
114
100
|
```
|
115
101
|
|
116
|
-
|
102
|
+
b. Move statements to lambdas:
|
117
103
|
|
118
104
|
```ruby
|
119
105
|
->(a) { a > 0 } => 1,
|
@@ -121,17 +107,9 @@ a > 0 ? 1 : -1
|
|
121
107
|
->(a) { a < 0 } => -1
|
122
108
|
```
|
123
109
|
|
124
|
-
This way decreases code complexity by delegating it to lambdas and makes it easy
|
125
|
-
to unit-testing because it's easy to test pure lambdas.
|
110
|
+
This way decreases code complexity by delegating it to lambdas and makes it easy to unit-testing because it's easy to test pure lambdas.
|
126
111
|
|
127
|
-
Such approach is named
|
128
|
-
[table-driven design](<https://www.d.umn.edu/~gshute/softeng/table-driven.html>)
|
129
|
-
. Table-driven methods are schemes that allow you to look up information in a
|
130
|
-
table rather than using logic statements (i.e. case, if). In simple cases,
|
131
|
-
it's quicker and easier to use logic statements, but as the logic chain becomes
|
132
|
-
more complex, table-driven code is simpler than complicated logic, easier to
|
133
|
-
modify and more efficient.
|
134
|
-
</details>
|
112
|
+
Such approach is named [table-driven design](<https://www.d.umn.edu/~gshute/softeng/table-driven.html>). Table-driven methods are schemes that allow you to look up information in a table rather than using logic statements (i.e. case, if). In simple cases, it's quicker and easier to use logic statements, but as the logic chain becomes more complex, table-driven code is simpler than complicated logic, easier to modify and more efficient.
|
135
113
|
|
136
114
|
![](https://placehold.it/10/f03c15/000000?text=+) raises on case statements
|
137
115
|
```ruby
|
@@ -150,8 +128,7 @@ end
|
|
150
128
|
```
|
151
129
|
## Ducalis::ControllersExcept
|
152
130
|
|
153
|
-
Prefer to use `:only` over `:except` in controllers because it's more
|
154
|
-
explicit and will be easier to maintain for new developers.
|
131
|
+
Prefer to use `:only` over `:except` in controllers because it's more explicit and will be easier to maintain for new developers.
|
155
132
|
|
156
133
|
![](https://placehold.it/10/f03c15/000000?text=+) raises for `before_filters` with `except` method as array
|
157
134
|
```ruby
|
@@ -202,8 +179,7 @@ end
|
|
202
179
|
```
|
203
180
|
## Ducalis::KeywordDefaults
|
204
181
|
|
205
|
-
Prefer to use keyword arguments for defaults. It increases readability
|
206
|
-
and reduces ambiguities.
|
182
|
+
Prefer to use keyword arguments for defaults. It increases readability and reduces ambiguities.
|
207
183
|
|
208
184
|
![](https://placehold.it/10/f03c15/000000?text=+) raises if method definition contains default values
|
209
185
|
```ruby
|
@@ -231,8 +207,7 @@ def self.calculate_amount; end
|
|
231
207
|
```
|
232
208
|
## Ducalis::ModuleLikeClass
|
233
209
|
|
234
|
-
Seems like it will be better to define initialize and pass %<args>s
|
235
|
-
there instead of each method.
|
210
|
+
Seems like it will be better to define initialize and pass %<args>s there instead of each method.
|
236
211
|
|
237
212
|
![](https://placehold.it/10/f03c15/000000?text=+) raises if class doesn't contain constructor but accept the same args
|
238
213
|
```ruby
|
@@ -319,11 +294,62 @@ class TaskJournal
|
|
319
294
|
end
|
320
295
|
end
|
321
296
|
|
297
|
+
```
|
298
|
+
## Ducalis::OptionsArgument
|
299
|
+
|
300
|
+
Default options argument isn't good idea. It's better to explicitly pass which keys are you interested in as keyword arguments. You can use split operator to support hash arguments.
|
301
|
+
|
302
|
+
Compare:
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
def generate_1(document, options = {})
|
306
|
+
format = options.delete(:format)
|
307
|
+
limit = options.delete(:limit) || 20
|
308
|
+
# ...
|
309
|
+
[format, limit, options]
|
310
|
+
end
|
311
|
+
generate_1(1, format: 'csv', limit: 5, useless_arg: :value)
|
312
|
+
|
313
|
+
# vs
|
314
|
+
|
315
|
+
def generate_2(document, format:, limit: 20, **options)
|
316
|
+
# ...
|
317
|
+
[format, limit, options]
|
318
|
+
end
|
319
|
+
generate_2(1, format: 'csv', limit: 5, useless_arg: :value)
|
320
|
+
```
|
321
|
+
|
322
|
+
![](https://placehold.it/10/f03c15/000000?text=+) raises if method accepts default options argument
|
323
|
+
```ruby
|
324
|
+
|
325
|
+
def generate(document, options = {})
|
326
|
+
format = options.delete(:format)
|
327
|
+
limit = options.delete(:limit) || 20
|
328
|
+
[format, limit, options]
|
329
|
+
end
|
330
|
+
|
331
|
+
```
|
332
|
+
|
333
|
+
![](https://placehold.it/10/f03c15/000000?text=+) raises if method accepts options argument
|
334
|
+
```ruby
|
335
|
+
|
336
|
+
def log(record, options)
|
337
|
+
# ...
|
338
|
+
end
|
339
|
+
|
340
|
+
```
|
341
|
+
|
342
|
+
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores passing options with split operator
|
343
|
+
```ruby
|
344
|
+
|
345
|
+
def generate(document, format:, limit: 20, **options)
|
346
|
+
[format, limit, options]
|
347
|
+
end
|
348
|
+
|
322
349
|
```
|
323
350
|
## Ducalis::ParamsPassing
|
324
351
|
|
325
|
-
It's better to pass already preprocessed params hash to services. Or
|
326
|
-
you can use `arcane` gem.
|
352
|
+
It's better to pass already preprocessed params hash to services. Or you can use `arcane` gem.
|
327
353
|
|
328
354
|
![](https://placehold.it/10/f03c15/000000?text=+) raises if user pass `params` as argument from controller
|
329
355
|
```ruby
|
@@ -392,12 +418,9 @@ end
|
|
392
418
|
```
|
393
419
|
## Ducalis::PossibleTap
|
394
420
|
|
395
|
-
Consider of using `.tap`, default ruby
|
396
|
-
|
397
|
-
|
398
|
-
are limiting scope pollution and make method scope more clear. If it isn't
|
399
|
-
possible, consider of moving it to method or even inline it.
|
400
|
-
[Related article](<http://seejohncode.com/2012/01/02/ruby-tap-that/>).
|
421
|
+
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 method scope more clear.
|
422
|
+
If it isn't possible, consider of moving it to method or even inline it.
|
423
|
+
[Related article](<http://seejohncode.com/2012/01/02/ruby-tap-that/>).
|
401
424
|
|
402
425
|
![](https://placehold.it/10/f03c15/000000?text=+) raises for methods with scope variable return
|
403
426
|
```ruby
|
@@ -493,16 +516,35 @@ end
|
|
493
516
|
```
|
494
517
|
## Ducalis::PreferableMethods
|
495
518
|
|
496
|
-
Prefer to use %<alternative>s method instead of %<original>s because of
|
497
|
-
%<reason>s.
|
519
|
+
Prefer to use %<alternative>s method instead of %<original>s because of %<reason>s.
|
498
520
|
Dangerous methods are:
|
499
|
-
`delete_all`, `
|
521
|
+
`toggle!`, `save`, `delete`, `delete_all`, `update_attribute`, `update_column`, `update_columns`.
|
500
522
|
|
501
523
|
![](https://placehold.it/10/f03c15/000000?text=+) raises for `delete` method calling
|
502
524
|
```ruby
|
503
525
|
User.where(id: 7).delete
|
504
526
|
```
|
505
527
|
|
528
|
+
![](https://placehold.it/10/f03c15/000000?text=+) raises `save` method calling with validate: false
|
529
|
+
```ruby
|
530
|
+
User.where(id: 7).save(validate: false)
|
531
|
+
```
|
532
|
+
|
533
|
+
![](https://placehold.it/10/f03c15/000000?text=+) raises `toggle!` method calling
|
534
|
+
```ruby
|
535
|
+
User.where(id: 7).toggle!
|
536
|
+
```
|
537
|
+
|
538
|
+
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores `save` method calling without validate: false
|
539
|
+
```ruby
|
540
|
+
User.where(id: 7).save
|
541
|
+
```
|
542
|
+
|
543
|
+
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores `save` method calling without validate: false
|
544
|
+
```ruby
|
545
|
+
User.where(id: 7).save(some_arg: true)
|
546
|
+
```
|
547
|
+
|
506
548
|
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores calling `delete` with symbol
|
507
549
|
```ruby
|
508
550
|
params.delete(:code)
|
@@ -524,12 +566,7 @@ tempfile.delete
|
|
524
566
|
```
|
525
567
|
## Ducalis::PrivateInstanceAssign
|
526
568
|
|
527
|
-
Don't use controller's filter methods for setting instance variables, use
|
528
|
-
them only for changing application flow, such as redirecting if a user
|
529
|
-
is not authenticated. Controller instance variables are forming contract
|
530
|
-
between controller and view. Keeping instance variables defined in one
|
531
|
-
place makes it easier to: reason, refactor and remove old views, test
|
532
|
-
controllers and views, extract actions to new controllers, etc.
|
569
|
+
Don't use controller's filter methods for setting instance variables, use them only for changing application flow, such as redirecting if a user is not authenticated. Controller instance variables are forming contract between controller and view. Keeping instance variables defined in one place makes it easier to: reason, refactor and remove old views, test controllers and views, extract actions to new controllers, etc.
|
533
570
|
If you want to memoize variable, please, add underscore to the variable name start: `@_name`.
|
534
571
|
|
535
572
|
![](https://placehold.it/10/f03c15/000000?text=+) raises for assigning instance variables in controllers private methods
|
@@ -589,15 +626,14 @@ end
|
|
589
626
|
```
|
590
627
|
## Ducalis::ProtectedScopeCop
|
591
628
|
|
592
|
-
Seems like you are using `find` on non-protected scope. Potentially it
|
593
|
-
|
594
|
-
authorized resources scopes. Example:
|
629
|
+
Seems like you are using `find` on non-protected scope. Potentially it could lead to unauthorized access. It's better to call `find` on authorized resources scopes.
|
630
|
+
Example:
|
595
631
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
632
|
+
```ruby
|
633
|
+
current_group.employees.find(params[:id])
|
634
|
+
# better then
|
635
|
+
Employee.find(params[:id])
|
636
|
+
```
|
601
637
|
|
602
638
|
![](https://placehold.it/10/f03c15/000000?text=+) raises if somewhere AR search was called on not protected scope
|
603
639
|
```ruby
|
@@ -629,8 +665,7 @@ end
|
|
629
665
|
```
|
630
666
|
## Ducalis::RaiseWithoutErrorClass
|
631
667
|
|
632
|
-
It's better to add exception class as raise argument. It will make
|
633
|
-
easier to catch and process it later.
|
668
|
+
It's better to add exception class as raise argument. It will make easier to catch and process it later.
|
634
669
|
|
635
670
|
![](https://placehold.it/10/f03c15/000000?text=+) raises when `raise` called without exception class
|
636
671
|
```ruby
|
@@ -653,9 +688,7 @@ raise StandardError.new("Something went wrong")
|
|
653
688
|
```
|
654
689
|
## Ducalis::RegexCop
|
655
690
|
|
656
|
-
It's better to move regex to constants with example instead of direct
|
657
|
-
using it. It will allow you to reuse this regex and provide instructions
|
658
|
-
for others.
|
691
|
+
It's better to move regex to constants with example instead of direct using it. It will allow you to reuse this regex and provide instructions for others.
|
659
692
|
|
660
693
|
```ruby
|
661
694
|
CONST_NAME = %<constant>s # "%<example>s"
|
@@ -699,7 +732,7 @@ puts "hi" if name =~ /.{#{name.length}}/
|
|
699
732
|
## Ducalis::RestOnlyCop
|
700
733
|
|
701
734
|
It's better for controllers to stay adherent to REST:
|
702
|
-
|
735
|
+
http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/.
|
703
736
|
[About RESTful architecture](<https://confreaks.tv/videos/railsconf2017-in-relentless-pursuit-of-rest>)
|
704
737
|
|
705
738
|
![](https://placehold.it/10/f03c15/000000?text=+) raises for controllers with non-REST methods
|
@@ -751,8 +784,7 @@ end
|
|
751
784
|
```
|
752
785
|
## Ducalis::RubocopDisable
|
753
786
|
|
754
|
-
Please, do not suppress RuboCop metrics, may be you can introduce some
|
755
|
-
refactoring or another concept.
|
787
|
+
Please, do not suppress RuboCop metrics, may be you can introduce some refactoring or another concept.
|
756
788
|
|
757
789
|
![](https://placehold.it/10/f03c15/000000?text=+) raises on RuboCop disable comments
|
758
790
|
```ruby
|
@@ -768,12 +800,32 @@ def calculate(five, args, at, one, list); end
|
|
768
800
|
# some meaningful comment
|
769
801
|
def calculate(five, args, at, one, list); end
|
770
802
|
|
803
|
+
```
|
804
|
+
## Ducalis::StandardMethods
|
805
|
+
|
806
|
+
Please, be sure that you really want to redefine standard ruby methods.
|
807
|
+
You should know what are you doing and all consequences.
|
808
|
+
|
809
|
+
![](https://placehold.it/10/f03c15/000000?text=+) raises if use redefines default ruby methods
|
810
|
+
```ruby
|
811
|
+
|
812
|
+
def to_s
|
813
|
+
"my version"
|
814
|
+
end
|
815
|
+
|
816
|
+
```
|
817
|
+
|
818
|
+
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores if use defines simple ruby methods
|
819
|
+
```ruby
|
820
|
+
|
821
|
+
def present
|
822
|
+
"my version"
|
823
|
+
end
|
824
|
+
|
771
825
|
```
|
772
826
|
## Ducalis::StringsInActiverecords
|
773
827
|
|
774
|
-
Please, do not use strings as arguments for %<method_name>s argument.
|
775
|
-
It's hard to test, grep sources, code highlighting and so on.
|
776
|
-
Consider using of symbols or lambdas for complex expressions.
|
828
|
+
Please, do not use strings as arguments for %<method_name>s argument. It's hard to test, grep sources, code highlighting and so on. Consider using of symbols or lambdas for complex expressions.
|
777
829
|
|
778
830
|
![](https://placehold.it/10/f03c15/000000?text=+) raises for string if argument
|
779
831
|
```ruby
|
@@ -789,11 +841,9 @@ validates :file, if: -> { remote_url.blank? }
|
|
789
841
|
```
|
790
842
|
## Ducalis::TooLongWorkers
|
791
843
|
|
792
|
-
Seems like your worker is doing too much work, consider of moving business
|
793
|
-
|
794
|
-
|
795
|
-
it's nescessary to cast them into actual objects.
|
796
|
-
- __Errors handling__: Rescue errors and figure out what to do with them.
|
844
|
+
Seems like your worker is doing too much work, consider of moving business logic to service object. As rule, workers should have only two responsibilities:
|
845
|
+
- __Model materialization__: As async jobs working with serialized attributes it's nescessary to cast them into actual objects.
|
846
|
+
- __Errors handling__: Rescue errors and figure out what to do with them.
|
797
847
|
|
798
848
|
![](https://placehold.it/10/f03c15/000000?text=+) raises for a class with more than 5 lines
|
799
849
|
```ruby
|
@@ -859,9 +909,8 @@ end
|
|
859
909
|
```
|
860
910
|
## Ducalis::UncommentedGem
|
861
911
|
|
862
|
-
Please, add comment why are you including non-realized gem version for
|
863
|
-
|
864
|
-
[bus-factor](<https://en.wikipedia.org/wiki/Bus_factor>).
|
912
|
+
Please, add comment why are you including non-realized gem version for %<gem>s.
|
913
|
+
It will increase [bus-factor](<https://en.wikipedia.org/wiki/Bus_factor>).
|
865
914
|
|
866
915
|
![](https://placehold.it/10/f03c15/000000?text=+) raises for gem from github without comment
|
867
916
|
```ruby
|
@@ -879,22 +928,39 @@ gem 'pry', '~> 0.10', '>= 0.10.0'
|
|
879
928
|
gem 'rake', '~> 12.1'
|
880
929
|
gem 'rspec', github: 'rspec/rspec' # new non released API
|
881
930
|
|
931
|
+
```
|
932
|
+
## Ducalis::UnlockedGem
|
933
|
+
|
934
|
+
It's better to lock gem versions explicitly with pessimistic operator (~>).
|
935
|
+
|
936
|
+
![](https://placehold.it/10/f03c15/000000?text=+) raises for gem without version
|
937
|
+
```ruby
|
938
|
+
gem 'pry'
|
939
|
+
```
|
940
|
+
|
941
|
+
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores gems with locked versions
|
942
|
+
```ruby
|
943
|
+
|
944
|
+
gem 'pry', '~> 0.10', '>= 0.10.0'
|
945
|
+
gem 'rake', '~> 12.1'
|
946
|
+
gem 'thor', '= 0.20.0'
|
947
|
+
gem 'rspec', github: 'rspec/rspec'
|
948
|
+
|
882
949
|
```
|
883
950
|
## Ducalis::UselessOnly
|
884
951
|
|
885
|
-
Seems like there is no any reason to keep before filter only for one
|
886
|
-
action. Maybe it will be better to inline it?
|
952
|
+
Seems like there is no any reason to keep before filter only for one action. Maybe it will be better to inline it?
|
887
953
|
|
888
|
-
|
889
|
-
|
890
|
-
|
954
|
+
```ruby
|
955
|
+
before_filter :do_something, only: %i[index]
|
956
|
+
def index; end
|
891
957
|
|
892
|
-
|
958
|
+
# to
|
893
959
|
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
960
|
+
def index
|
961
|
+
do_something
|
962
|
+
end
|
963
|
+
```
|
898
964
|
|
899
965
|
![](https://placehold.it/10/f03c15/000000?text=+) raises for `before_filters` with only one method as array
|
900
966
|
```ruby
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -59,6 +59,17 @@ In CLI modes you can provide yours `.ducalis.yml` file based on
|
|
59
59
|
[default](https://github.com/ignat-z/ducalis/blob/master/config/.ducalis.yml) by
|
60
60
|
`-c` flag or simply putting it in your project directory.
|
61
61
|
|
62
|
+
## Configuration
|
63
|
+
|
64
|
+
One or more individual cops can be disabled locally in a section of a file by adding a comment such as
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
# ducalis:disable Ducalis/PreferableMethods Use `delete_all` because of performance reasons
|
68
|
+
def remove_audits
|
69
|
+
AuditLog.where(user_id: user_id).delete_all
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
62
73
|
## Contribution
|
63
74
|
|
64
75
|
To pass your code through the all checks you simply need to run:
|
data/config/.ducalis.yml
CHANGED
@@ -30,7 +30,7 @@ Ducalis/CallbacksActiverecord:
|
|
30
30
|
Enabled: true
|
31
31
|
|
32
32
|
Ducalis/PossibleTap:
|
33
|
-
Enabled:
|
33
|
+
Enabled: false
|
34
34
|
|
35
35
|
Ducalis/ProtectedScopeCop:
|
36
36
|
Enabled: true
|
@@ -60,6 +60,9 @@ Ducalis/RestOnlyCop:
|
|
60
60
|
Ducalis/KeywordDefaults:
|
61
61
|
Enabled: true
|
62
62
|
|
63
|
+
Ducalis/OptionsArgument:
|
64
|
+
Enabled: true
|
65
|
+
|
63
66
|
Ducalis/RubocopDisable:
|
64
67
|
Enabled: true
|
65
68
|
|
@@ -80,9 +83,15 @@ Ducalis/ControllersExcept:
|
|
80
83
|
Ducalis/PrivateInstanceAssign:
|
81
84
|
Enabled: true
|
82
85
|
|
86
|
+
Ducalis/StandardMethods:
|
87
|
+
Enabled: true
|
88
|
+
|
83
89
|
Ducalis/RaiseWithoutErrorClass:
|
84
90
|
Enabled: true
|
85
91
|
|
92
|
+
Ducalis/UnlockedGem:
|
93
|
+
Enabled: true
|
94
|
+
|
86
95
|
Ducalis/ModuleLikeClass:
|
87
96
|
Enabled: true
|
88
97
|
AllowedIncludes:
|
@@ -4,12 +4,10 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class BlackListSuffix < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Please, avoid using of class suffixes like `Meneger`, `Client`
|
9
|
-
|
10
|
-
|
|
11
|
-
| which is there to refine a type of personal that has management
|
12
|
-
| behavior to it.
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| Please, avoid using of class suffixes like `Meneger`, `Client` and so on. If it has no parts, change the name of the class to what each object is managing.
|
9
|
+
|
10
|
+
| It's ok to use Manager as subclass of Person, which is there to refine a type of personal that has management behavior to it.
|
13
11
|
| Related [article](<http://www.carlopescio.com/2011/04/your-coding-conventions-are-hurting-you.html>)
|
14
12
|
MESSAGE
|
15
13
|
|
@@ -4,11 +4,9 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class CallbacksActiverecord < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Please, avoid using of callbacks for models. It's better to
|
9
|
-
|
|
10
|
-
| / services: to construct new objects. You can read more
|
11
|
-
| [here](https://medium.com/planet-arkency/a61fd75ab2d3).
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| 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.
|
9
|
+
| You can read more [here](https://medium.com/planet-arkency/a61fd75ab2d3).
|
12
10
|
MESSAGE
|
13
11
|
|
14
12
|
MODELS_CLASS_NAMES = [
|
@@ -4,87 +4,69 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class CaseMapping < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Try to avoid `case when` statements. You can replace it with a sequence
|
9
|
-
|
|
10
|
-
| from a large number of possibilities, you can create a dictionary
|
11
|
-
| mapping case values to functions to call by `call`. It's nice to have
|
12
|
-
| prefix for the method names, i.e.: `visit_`.
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| Try to avoid `case when` statements. You can replace it with a sequence of `if... elsif... elsif... else`.
|
9
|
+
| For cases where you need to choose from a large number of possibilities, you can create a dictionary mapping case values to functions to call by `call`. It's nice to have prefix for the method names, i.e.: `visit_`.
|
13
10
|
MESSAGE
|
14
11
|
|
15
|
-
DETAILS =
|
16
|
-
Usually `case when` statements are using for the next reasons:
|
17
|
-
|
18
|
-
I. Mapping between different values.
|
19
|
-
("A" => 1, "B" => 2, ...)
|
20
|
-
|
21
|
-
This case is all about data representing. If you do not need to execute any code
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
```
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
```ruby
|
64
|
-
|
65
|
-
a
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
->(a) { a < 0 } => -1
|
74
|
-
```
|
75
|
-
|
76
|
-
This way decreases code complexity by delegating it to lambdas and makes it easy
|
77
|
-
to unit-testing because it's easy to test pure lambdas.
|
78
|
-
|
79
|
-
Such approach is named
|
80
|
-
[table-driven design](<https://www.d.umn.edu/~gshute/softeng/table-driven.html>)
|
81
|
-
. Table-driven methods are schemes that allow you to look up information in a
|
82
|
-
table rather than using logic statements (i.e. case, if). In simple cases,
|
83
|
-
it's quicker and easier to use logic statements, but as the logic chain becomes
|
84
|
-
more complex, table-driven code is simpler than complicated logic, easier to
|
85
|
-
modify and more efficient.
|
86
|
-
</details>
|
87
|
-
).strip
|
12
|
+
DETAILS = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
13
|
+
| Usually `case when` statements are using for the next reasons:
|
14
|
+
|
15
|
+
| I. Mapping between different values.
|
16
|
+
| ("A" => 1, "B" => 2, ...)
|
17
|
+
|
18
|
+
| This case is all about data representing. If you do not need to execute any code it's better to use data structure which represents it. This way you are separating concepts: code returns corresponding value and you have config-like data structure which describes your data.
|
19
|
+
|
20
|
+
| ```ruby
|
21
|
+
| %w[A B ...].index("A") + 1
|
22
|
+
| # or
|
23
|
+
| { "A" => 1, "B" => 2 }.fetch("A")
|
24
|
+
| ```
|
25
|
+
|
26
|
+
| II. Code execution depending of parameter or type:
|
27
|
+
|
28
|
+
| - a. (:attack => attack, :defend => defend)
|
29
|
+
| - b. (Feet => value * 0.348, Meters => `value`)
|
30
|
+
|
31
|
+
| In this case code violates OOP and S[O]LID principle. Code shouldn't know about object type and classes should be open for extension, but closed for modification (but you can't do it with case-statements). This is a signal that you have some problems with architecture.
|
32
|
+
|
33
|
+
| a.
|
34
|
+
| ```ruby
|
35
|
+
| attack: -> { execute_attack }, defend: -> { execute_defend }
|
36
|
+
| #{(action = '#{' + 'action' + '}') && '# or'}
|
37
|
+
| call(:"execute_#{action}")
|
38
|
+
| ```
|
39
|
+
|
40
|
+
| b.
|
41
|
+
| ```ruby
|
42
|
+
| class Meters; def to_metters; value; end
|
43
|
+
| class Feet; def to_metters; value * 0.348; end
|
44
|
+
| ```
|
45
|
+
|
46
|
+
| III. Code execution depending on some statement.
|
47
|
+
| (`a > 0` => 1, `a == 0` => 0, `a < 0` => -1)
|
48
|
+
|
49
|
+
| This case is combination of I and II -- high code complexity and unit-tests complexity. There are variants how to solve it:
|
50
|
+
|
51
|
+
| a. Rewrite to simple if statement
|
52
|
+
|
53
|
+
| ```ruby
|
54
|
+
| return 0 if a == 0
|
55
|
+
| a > 0 ? 1 : -1
|
56
|
+
| ```
|
57
|
+
|
58
|
+
| b. Move statements to lambdas:
|
59
|
+
|
60
|
+
| ```ruby
|
61
|
+
| ->(a) { a > 0 } => 1,
|
62
|
+
| ->(a) { a == 0 } => 0,
|
63
|
+
| ->(a) { a < 0 } => -1
|
64
|
+
| ```
|
65
|
+
|
66
|
+
| This way decreases code complexity by delegating it to lambdas and makes it easy to unit-testing because it's easy to test pure lambdas.
|
67
|
+
|
68
|
+
| Such approach is named [table-driven design](<https://www.d.umn.edu/~gshute/softeng/table-driven.html>). Table-driven methods are schemes that allow you to look up information in a table rather than using logic statements (i.e. case, if). In simple cases, it's quicker and easier to use logic statements, but as the logic chain becomes more complex, table-driven code is simpler than complicated logic, easier to modify and more efficient.
|
69
|
+
MESSAGE
|
88
70
|
|
89
71
|
def on_case(node)
|
90
72
|
add_offense(node, :expression, OFFENSE)
|
@@ -4,9 +4,8 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class ControllersExcept < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Prefer to use `:only` over `:except` in controllers because it's more
|
9
|
-
| explicit and will be easier to maintain for new developers.
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| Prefer to use `:only` over `:except` in controllers because it's more explicit and will be easier to maintain for new developers.
|
10
9
|
MESSAGE
|
11
10
|
|
12
11
|
FILTERS = %i(before_filter after_filter around_filter
|
@@ -4,9 +4,8 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class KeywordDefaults < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Prefer to use keyword arguments for defaults. It increases readability
|
9
|
-
| and reduces ambiguities.
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| Prefer to use keyword arguments for defaults. It increases readability and reduces ambiguities.
|
10
9
|
MESSAGE
|
11
10
|
|
12
11
|
def on_def(node)
|
@@ -5,9 +5,8 @@ require 'rubocop'
|
|
5
5
|
module Ducalis
|
6
6
|
class ModuleLikeClass < RuboCop::Cop::Cop
|
7
7
|
include RuboCop::Cop::DefNode
|
8
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
9
|
-
| Seems like it will be better to define initialize and pass %<args>s
|
10
|
-
| there instead of each method.
|
8
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
9
|
+
| Seems like it will be better to define initialize and pass %<args>s there instead of each method.
|
11
10
|
MESSAGE
|
12
11
|
|
13
12
|
def on_class(node)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
|
5
|
+
module Ducalis
|
6
|
+
class OptionsArgument < RuboCop::Cop::Cop
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| Default options argument isn't good idea. It's better to explicitly pass which keys are you interested in as keyword arguments. You can use split operator to support hash arguments.
|
9
|
+
|
10
|
+
| Compare:
|
11
|
+
|
12
|
+
| ```ruby
|
13
|
+
| def generate_1(document, options = {})
|
14
|
+
| format = options.delete(:format)
|
15
|
+
| limit = options.delete(:limit) || 20
|
16
|
+
| # ...
|
17
|
+
| [format, limit, options]
|
18
|
+
| end
|
19
|
+
| generate_1(1, format: 'csv', limit: 5, useless_arg: :value)
|
20
|
+
|
21
|
+
| # vs
|
22
|
+
|
23
|
+
| def generate_2(document, format:, limit: 20, **options)
|
24
|
+
| # ...
|
25
|
+
| [format, limit, options]
|
26
|
+
| end
|
27
|
+
| generate_2(1, format: 'csv', limit: 5, useless_arg: :value)
|
28
|
+
| ```
|
29
|
+
MESSAGE
|
30
|
+
|
31
|
+
def on_def(node)
|
32
|
+
_name, args, _body = *node
|
33
|
+
return unless default_options?(args)
|
34
|
+
add_offense(node, :expression, OFFENSE)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def_node_search :options_arg?, '(arg :options)'
|
40
|
+
def_node_search :options_arg_with_default?, '(optarg :options ...)'
|
41
|
+
|
42
|
+
def default_options?(args)
|
43
|
+
args.children.any? do |node|
|
44
|
+
options_arg?(node) || options_arg_with_default?(node)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -4,9 +4,8 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class ParamsPassing < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| It's better to pass already preprocessed params hash to services. Or
|
9
|
-
| you can use `arcane` gem.
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| It's better to pass already preprocessed params hash to services. Or you can use `arcane` gem.
|
10
9
|
MESSAGE
|
11
10
|
|
12
11
|
PARAMS_CALL = s(:send, nil, :params)
|
@@ -4,12 +4,9 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class PossibleTap < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Consider of using `.tap`, default ruby
|
9
|
-
|
|
10
|
-
| which allows to replace intermediate variables with block, by this you
|
11
|
-
| are limiting scope pollution and make method scope more clear. If it isn't
|
12
|
-
| possible, consider of moving it to method or even inline it.
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| 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 method scope more clear.
|
9
|
+
| If it isn't possible, consider of moving it to method or even inline it.
|
13
10
|
| [Related article](<http://seejohncode.com/2012/01/02/ruby-tap-that/>).
|
14
11
|
MESSAGE
|
15
12
|
|
@@ -3,9 +3,8 @@ require 'rubocop'
|
|
3
3
|
|
4
4
|
module Ducalis
|
5
5
|
class PreferableMethods < RuboCop::Cop::Cop
|
6
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
7
|
-
| Prefer to use %<alternative>s method instead of %<original>s because of
|
8
|
-
| %<reason>s.
|
6
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
7
|
+
| Prefer to use %<alternative>s method instead of %<original>s because of %<reason>s.
|
9
8
|
MESSAGE
|
10
9
|
|
11
10
|
ALWAYS_TRUE = ->(_who, _what, _args) { true }
|
@@ -15,10 +14,22 @@ module Ducalis
|
|
15
14
|
args.count <= 1 && who.to_s !~ /file/
|
16
15
|
end
|
17
16
|
|
17
|
+
VALIDATE_CHECK = lambda do |_who, _what, args|
|
18
|
+
(args.first && args.first.source) =~ /validate/
|
19
|
+
end
|
20
|
+
|
18
21
|
DESCRIPTION = {
|
19
22
|
# Method => [Alternative, Reason, Callable condition]
|
23
|
+
toggle!: ['toggle.save', 'it is not invoking validations', ALWAYS_TRUE],
|
24
|
+
save: [:save, 'it is not invoking validations', VALIDATE_CHECK],
|
25
|
+
delete: [:destroy, 'it is not invoking callbacks', DELETE_CHECK],
|
20
26
|
delete_all: [:destroy_all, 'it is not invoking callbacks', ALWAYS_TRUE],
|
21
|
-
|
27
|
+
update_attribute: [:update, 'it is not invoking validation', ALWAYS_TRUE],
|
28
|
+
update_column: [:update, 'it is not invoking callbacks', ALWAYS_TRUE],
|
29
|
+
update_columns: [
|
30
|
+
:update, 'it is not invoking validations, callbacks and updated_at',
|
31
|
+
ALWAYS_TRUE
|
32
|
+
]
|
22
33
|
}.freeze
|
23
34
|
|
24
35
|
DETAILS = "Dangerous methods are:
|
@@ -5,19 +5,13 @@ require 'rubocop'
|
|
5
5
|
module Ducalis
|
6
6
|
class PrivateInstanceAssign < RuboCop::Cop::Cop
|
7
7
|
include RuboCop::Cop::DefNode
|
8
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
9
|
-
| Don't use controller's filter methods for setting instance variables, use
|
10
|
-
| them only for changing application flow, such as redirecting if a user
|
11
|
-
| is not authenticated. Controller instance variables are forming contract
|
12
|
-
| between controller and view. Keeping instance variables defined in one
|
13
|
-
| place makes it easier to: reason, refactor and remove old views, test
|
14
|
-
| controllers and views, extract actions to new controllers, etc.
|
8
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
9
|
+
| Don't use controller's filter methods for setting instance variables, use them only for changing application flow, such as redirecting if a user is not authenticated. Controller instance variables are forming contract between controller and view. Keeping instance variables defined in one place makes it easier to: reason, refactor and remove old views, test controllers and views, extract actions to new controllers, etc.
|
15
10
|
MESSAGE
|
16
11
|
|
17
|
-
ADD_OFFENSE =
|
18
|
-
If you want to memoize variable, please, add underscore to the variable name
|
19
|
-
|
20
|
-
).strip
|
12
|
+
ADD_OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
13
|
+
If you want to memoize variable, please, add underscore to the variable name start: `@_name`.
|
14
|
+
MESSAGE
|
21
15
|
|
22
16
|
DETAILS = ADD_OFFENSE
|
23
17
|
|
@@ -4,11 +4,10 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class ProtectedScopeCop < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Seems like you are using `find` on non-protected scope. Potentially it
|
9
|
-
|
|
10
|
-
|
11
|
-
|
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| Seems like you are using `find` on non-protected scope. Potentially it could lead to unauthorized access. It's better to call `find` on authorized resources scopes.
|
9
|
+
| Example:
|
10
|
+
|
12
11
|
| ```ruby
|
13
12
|
| current_group.employees.find(params[:id])
|
14
13
|
| # better then
|
@@ -4,9 +4,8 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class RaiseWithoutErrorClass < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| It's better to add exception class as raise argument. It will make
|
9
|
-
| easier to catch and process it later.
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| It's better to add exception class as raise argument. It will make easier to catch and process it later.
|
10
9
|
MESSAGE
|
11
10
|
|
12
11
|
def on_send(node)
|
@@ -5,15 +5,13 @@ require 'regexp-examples'
|
|
5
5
|
|
6
6
|
module Ducalis
|
7
7
|
class RegexCop < RuboCop::Cop::Cop
|
8
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
9
|
-
| It's better to move regex to constants with example instead of direct
|
10
|
-
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
14
|
-
|
|
15
|
-
|%<fixed_string>s
|
16
|
-
|```
|
8
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
9
|
+
| It's better to move regex to constants with example instead of direct using it. It will allow you to reuse this regex and provide instructions for others.
|
10
|
+
|
11
|
+
| ```ruby
|
12
|
+
| CONST_NAME = %<constant>s # "%<example>s"
|
13
|
+
| %<fixed_string>s
|
14
|
+
| ```
|
17
15
|
MESSAGE
|
18
16
|
|
19
17
|
SELF_DESCRIPTIVE = %w(
|
@@ -5,12 +5,12 @@ require 'rubocop'
|
|
5
5
|
module Ducalis
|
6
6
|
class RestOnlyCop < RuboCop::Cop::Cop
|
7
7
|
include RuboCop::Cop::DefNode
|
8
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
9
9
|
| It's better for controllers to stay adherent to REST:
|
10
10
|
| http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/.
|
11
11
|
MESSAGE
|
12
12
|
|
13
|
-
DETAILS = <<-MESSAGE.gsub(/^
|
13
|
+
DETAILS = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
14
14
|
| [About RESTful architecture](<https://confreaks.tv/videos/railsconf2017-in-relentless-pursuit-of-rest>)
|
15
15
|
MESSAGE
|
16
16
|
|
@@ -4,9 +4,8 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class RubocopDisable < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Please, do not suppress RuboCop metrics, may be you can introduce some
|
9
|
-
| refactoring or another concept.
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| Please, do not suppress RuboCop metrics, may be you can introduce some refactoring or another concept.
|
10
9
|
MESSAGE
|
11
10
|
|
12
11
|
def investigate(processed_source)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
|
5
|
+
module Ducalis
|
6
|
+
class StandardMethods < RuboCop::Cop::Cop
|
7
|
+
BLACK_LIST = [Object].flat_map { |klass| klass.new.methods }
|
8
|
+
|
9
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
10
|
+
| Please, be sure that you really want to redefine standard ruby methods.
|
11
|
+
| You should know what are you doing and all consequences.
|
12
|
+
MESSAGE
|
13
|
+
|
14
|
+
def on_def(node)
|
15
|
+
name, _args, _body = *node
|
16
|
+
return unless BLACK_LIST.include?(name)
|
17
|
+
add_offense(node, :expression, OFFENSE)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -5,10 +5,8 @@ require_relative './callbacks_activerecord'
|
|
5
5
|
|
6
6
|
module Ducalis
|
7
7
|
class StringsInActiverecords < RuboCop::Cop::Cop
|
8
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
9
|
-
| Please, do not use strings as arguments for %<method_name>s argument.
|
10
|
-
| It's hard to test, grep sources, code highlighting and so on.
|
11
|
-
| Consider using of symbols or lambdas for complex expressions.
|
8
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
9
|
+
| Please, do not use strings as arguments for %<method_name>s argument. It's hard to test, grep sources, code highlighting and so on. Consider using of symbols or lambdas for complex expressions.
|
12
10
|
MESSAGE
|
13
11
|
|
14
12
|
VALIDATEBLE_METHODS =
|
@@ -6,11 +6,9 @@ module Ducalis
|
|
6
6
|
class TooLongWorkers < RuboCop::Cop::Cop
|
7
7
|
include RuboCop::Cop::ClassishLength
|
8
8
|
|
9
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
10
|
-
| Seems like your worker is doing too much work, consider of moving business
|
11
|
-
|
|
12
|
-
| - __Model materialization__: As async jobs working with serialized attributes
|
13
|
-
| it's nescessary to cast them into actual objects.
|
9
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
10
|
+
| Seems like your worker is doing too much work, consider of moving business logic to service object. As rule, workers should have only two responsibilities:
|
11
|
+
| - __Model materialization__: As async jobs working with serialized attributes it's nescessary to cast them into actual objects.
|
14
12
|
| - __Errors handling__: Rescue errors and figure out what to do with them.
|
15
13
|
MESSAGE
|
16
14
|
|
@@ -4,10 +4,9 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class UncommentedGem < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Please, add comment why are you including non-realized gem version for
|
9
|
-
|
|
10
|
-
| [bus-factor](<https://en.wikipedia.org/wiki/Bus_factor>).
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| Please, add comment why are you including non-realized gem version for %<gem>s.
|
9
|
+
| It will increase [bus-factor](<https://en.wikipedia.org/wiki/Bus_factor>).
|
11
10
|
MESSAGE
|
12
11
|
|
13
12
|
def investigate(processed_source)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
|
5
|
+
module Ducalis
|
6
|
+
class UnlockedGem < RuboCop::Cop::Cop
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| It's better to lock gem versions explicitly with pessimistic operator (~>).
|
9
|
+
MESSAGE
|
10
|
+
|
11
|
+
def investigate(processed_source)
|
12
|
+
return unless processed_source.ast
|
13
|
+
gem_declarations(processed_source.ast).select do |node|
|
14
|
+
_, _, gemname, _args = *node
|
15
|
+
add_offense(node, :selector,
|
16
|
+
format(OFFENSE, gem: gemname.loc.expression.source))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def_node_search :gem_declarations, '(send nil :gem (str _))'
|
21
|
+
end
|
22
|
+
end
|
@@ -4,16 +4,15 @@ require 'rubocop'
|
|
4
4
|
|
5
5
|
module Ducalis
|
6
6
|
class UselessOnly < RuboCop::Cop::Cop
|
7
|
-
OFFENSE = <<-MESSAGE.gsub(/^
|
8
|
-
| Seems like there is no any reason to keep before filter only for one
|
9
|
-
|
10
|
-
|
|
7
|
+
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
|
+
| Seems like there is no any reason to keep before filter only for one action. Maybe it will be better to inline it?
|
9
|
+
|
11
10
|
| ```ruby
|
12
11
|
| before_filter :do_something, only: %i[index]
|
13
12
|
| def index; end
|
14
|
-
|
13
|
+
|
15
14
|
| # to
|
16
|
-
|
15
|
+
|
17
16
|
| def index
|
18
17
|
| do_something
|
19
18
|
| end
|
data/lib/ducalis/passed_args.rb
CHANGED
@@ -4,8 +4,10 @@ module Ducalis
|
|
4
4
|
module PassedArgs
|
5
5
|
module_function
|
6
6
|
|
7
|
+
RUBOCOP_FLAGS = %w(-D).freeze
|
8
|
+
|
7
9
|
def help_command?
|
8
|
-
ARGV.any? { |arg| Thor::HELP_MAPPINGS.include?(arg) }
|
10
|
+
ARGV.any? { |arg| (Thor::HELP_MAPPINGS - RUBOCOP_FLAGS).include?(arg) }
|
9
11
|
end
|
10
12
|
|
11
13
|
def ci_mode?
|
@@ -8,6 +8,14 @@ module RuboCop
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
class CommentConfig
|
12
|
+
::Ducalis::Utils.silence_warnings do
|
13
|
+
COMMENT_DIRECTIVE_REGEXP = Regexp.new(
|
14
|
+
('# ducalis : ((?:dis|en)able)\b ' + COPS_PATTERN).gsub(' ', '\s*')
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
11
19
|
class TargetFinder
|
12
20
|
prepend PatchedRubocop::GitTurgetFinder
|
13
21
|
end
|
data/lib/ducalis/version.rb
CHANGED
data/lib/ducalis.rb
CHANGED
@@ -37,6 +37,7 @@ require 'ducalis/cops/case_mapping'
|
|
37
37
|
require 'ducalis/cops/controllers_except'
|
38
38
|
require 'ducalis/cops/keyword_defaults'
|
39
39
|
require 'ducalis/cops/module_like_class'
|
40
|
+
require 'ducalis/cops/options_argument'
|
40
41
|
require 'ducalis/cops/params_passing'
|
41
42
|
require 'ducalis/cops/possible_tap'
|
42
43
|
require 'ducalis/cops/private_instance_assign'
|
@@ -46,7 +47,9 @@ require 'ducalis/cops/raise_without_error_class'
|
|
46
47
|
require 'ducalis/cops/regex_cop'
|
47
48
|
require 'ducalis/cops/rest_only_cop'
|
48
49
|
require 'ducalis/cops/rubocop_disable'
|
50
|
+
require 'ducalis/cops/standard_methods'
|
49
51
|
require 'ducalis/cops/strings_in_activerecords'
|
50
52
|
require 'ducalis/cops/too_long_workers'
|
51
53
|
require 'ducalis/cops/uncommented_gem'
|
54
|
+
require 'ducalis/cops/unlocked_gem'
|
52
55
|
require 'ducalis/cops/useless_only'
|
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.5.
|
4
|
+
version: 0.5.11
|
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-
|
11
|
+
date: 2017-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: git
|
@@ -136,6 +136,7 @@ files:
|
|
136
136
|
- lib/ducalis/cops/controllers_except.rb
|
137
137
|
- lib/ducalis/cops/keyword_defaults.rb
|
138
138
|
- lib/ducalis/cops/module_like_class.rb
|
139
|
+
- lib/ducalis/cops/options_argument.rb
|
139
140
|
- lib/ducalis/cops/params_passing.rb
|
140
141
|
- lib/ducalis/cops/possible_tap.rb
|
141
142
|
- lib/ducalis/cops/preferable_methods.rb
|
@@ -145,9 +146,11 @@ files:
|
|
145
146
|
- lib/ducalis/cops/regex_cop.rb
|
146
147
|
- lib/ducalis/cops/rest_only_cop.rb
|
147
148
|
- lib/ducalis/cops/rubocop_disable.rb
|
149
|
+
- lib/ducalis/cops/standard_methods.rb
|
148
150
|
- lib/ducalis/cops/strings_in_activerecords.rb
|
149
151
|
- lib/ducalis/cops/too_long_workers.rb
|
150
152
|
- lib/ducalis/cops/uncommented_gem.rb
|
153
|
+
- lib/ducalis/cops/unlocked_gem.rb
|
151
154
|
- lib/ducalis/cops/useless_only.rb
|
152
155
|
- lib/ducalis/documentation.rb
|
153
156
|
- lib/ducalis/passed_args.rb
|