lifen-ruby-style 0.2.1 → 1.0.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/.overcommit.yml +55 -0
- data/.rubocop.yml +1 -1
- data/Gemfile +2 -0
- data/README.md +358 -31
- data/Rakefile +2 -0
- data/default_overcommit.yml +55 -0
- data/default_rubocop.yml +133 -0
- data/lib/lifen/ruby_style/version.rb +5 -1
- data/lib/lifen/ruby_style.rb +2 -0
- data/lifen-ruby-style.gemspec +23 -17
- data/rubocop +1 -0
- data/style_guide.md +673 -0
- metadata +82 -8
- data/default.yml +0 -170
data/style_guide.md
ADDED
@@ -0,0 +1,673 @@
|
|
1
|
+
# The Lifen Ruby Style Guide
|
2
|
+
|
3
|
+
In this guide, we present the Lifen guidelines, the most important rules and the specific ones (i.e. rules with custom config, different from [RuboCop default ones](https://github.com/rubocop-hq/ruby-style-guide)).
|
4
|
+
|
5
|
+
## General
|
6
|
+
|
7
|
+
- Make all lines of your methods operate on the same level of abstraction. ([Single Level of Abstraction Principle](https://medium.com/@yukas/single-level-of-abstraction-1e2bb6a645d7))
|
8
|
+
- Do not mutate arguments unless that is the purpose of the method.
|
9
|
+
- Do not mess around in / monkeypatch core classes when writing libraries.
|
10
|
+
- Keep the code simple.
|
11
|
+
- Avoid needless metaprogramming.
|
12
|
+
|
13
|
+
(based on [Shopify's ruby style guide](https://shopify.github.io/ruby-style-guide/))
|
14
|
+
|
15
|
+
## Not-RuboCop rules
|
16
|
+
|
17
|
+
- Prefer `public_send` over `send` so as not to circumvent private/protected visibility.
|
18
|
+
- Prefer using [ActiveRecord Bang (!) Methods](https://riptutorial.com/ruby-on-rails/example/9285/activerecord-bang-----methods), such as `#save!`, `#update!`, `.create!`. Careful, you must not use a Bang Method and then rescue an exception. You should prevent the app to get invalid inputs beforehand.
|
19
|
+
- Always use `destroy!` or `destroy`. Never use `delete`, unless you know exactly why. Basically `destroy` runs any callbacks on the model while `delete` doesn't. More [info](https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-delete).
|
20
|
+
- Prefer `Time` over `DateTime` since it supports proper time zones instead of UTC offsets. [More info](https://gist.github.com/pixeltrix/e2298822dd89d854444b).
|
21
|
+
|
22
|
+
## RuboCop rules
|
23
|
+
|
24
|
+
**All RuboCop rules are enforced by the linter: you don't have to memorize them all. This section is for documentation purposes only.**
|
25
|
+
|
26
|
+
### Custom rules
|
27
|
+
|
28
|
+
All the RuboCop rules overrided by Lifen are detailed below:
|
29
|
+
|
30
|
+
<details><summary>Layout</summary>
|
31
|
+
|
32
|
+
#### ArgumentAlignment
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# bad
|
36
|
+
foo :bar,
|
37
|
+
:baz
|
38
|
+
|
39
|
+
# good
|
40
|
+
foo :bar,
|
41
|
+
:baz
|
42
|
+
```
|
43
|
+
|
44
|
+
#### CaseIndentation
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
# bad
|
48
|
+
case n
|
49
|
+
when 0
|
50
|
+
x * 2
|
51
|
+
else
|
52
|
+
y / 3
|
53
|
+
end
|
54
|
+
|
55
|
+
# good
|
56
|
+
case n
|
57
|
+
when 0
|
58
|
+
x * 2
|
59
|
+
else
|
60
|
+
y / 3
|
61
|
+
end
|
62
|
+
|
63
|
+
# bad
|
64
|
+
a = case n
|
65
|
+
when 0
|
66
|
+
x * 2
|
67
|
+
else
|
68
|
+
y / 3
|
69
|
+
end
|
70
|
+
|
71
|
+
# good
|
72
|
+
a = case n
|
73
|
+
when 0
|
74
|
+
x * 2
|
75
|
+
else
|
76
|
+
y / 3
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
#### EmptyLinesAroundClassBody
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# bad
|
84
|
+
class Foo
|
85
|
+
def bar
|
86
|
+
# ...
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# good
|
91
|
+
class Foo
|
92
|
+
|
93
|
+
def bar
|
94
|
+
# ...
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
#### EmptyLinesAroundModuleBody
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# bad
|
104
|
+
module Foo
|
105
|
+
def bar
|
106
|
+
# ...
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# good
|
111
|
+
module Foo
|
112
|
+
|
113
|
+
def bar
|
114
|
+
# ...
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
#### EndAlignment
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
# bad
|
124
|
+
variable = if true
|
125
|
+
end
|
126
|
+
|
127
|
+
# good
|
128
|
+
variable = if true
|
129
|
+
end
|
130
|
+
|
131
|
+
variable =
|
132
|
+
if true
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
#### FirstArgumentIndentation
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
# bad
|
140
|
+
some_method(
|
141
|
+
first_param,
|
142
|
+
second_param)
|
143
|
+
# good
|
144
|
+
some_method(
|
145
|
+
first_param,
|
146
|
+
second_param
|
147
|
+
)
|
148
|
+
|
149
|
+
# bad
|
150
|
+
foo = some_method(
|
151
|
+
first_param,
|
152
|
+
second_param)
|
153
|
+
# good
|
154
|
+
foo = some_method(
|
155
|
+
first_param,
|
156
|
+
second_param
|
157
|
+
)
|
158
|
+
|
159
|
+
# bad
|
160
|
+
foo = some_method(nested_call(
|
161
|
+
nested_first_param),
|
162
|
+
second_param)
|
163
|
+
# good
|
164
|
+
foo = some_method(nested_call(
|
165
|
+
nested_first_param
|
166
|
+
),
|
167
|
+
second_param)
|
168
|
+
|
169
|
+
# bad
|
170
|
+
foo = some_method(
|
171
|
+
nested_call(
|
172
|
+
nested_first_param),
|
173
|
+
second_param)
|
174
|
+
# good
|
175
|
+
foo = some_method(
|
176
|
+
nested_call(
|
177
|
+
nested_first_param
|
178
|
+
),
|
179
|
+
second_param
|
180
|
+
)
|
181
|
+
|
182
|
+
# bad
|
183
|
+
some_method nested_call(
|
184
|
+
nested_first_param),
|
185
|
+
second_param
|
186
|
+
# good
|
187
|
+
some_method nested_call(
|
188
|
+
nested_first_param
|
189
|
+
),
|
190
|
+
second_param
|
191
|
+
```
|
192
|
+
|
193
|
+
#### FirstArrayElementIndentation
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
#bad
|
197
|
+
# consistent
|
198
|
+
array = [
|
199
|
+
:value
|
200
|
+
]
|
201
|
+
but_in_a_method_call([
|
202
|
+
:its_like_this
|
203
|
+
])
|
204
|
+
|
205
|
+
#good
|
206
|
+
array = [
|
207
|
+
:value
|
208
|
+
]
|
209
|
+
and_in_a_method_call([
|
210
|
+
:no_difference
|
211
|
+
])
|
212
|
+
```
|
213
|
+
|
214
|
+
#### FirstHashElementIndentation
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
# bad
|
218
|
+
hash = {
|
219
|
+
key: :value
|
220
|
+
}
|
221
|
+
but_in_a_method_call(
|
222
|
+
its_like: :this
|
223
|
+
)
|
224
|
+
|
225
|
+
# good
|
226
|
+
hash = {
|
227
|
+
key: :value
|
228
|
+
}
|
229
|
+
and_in_a_method_call(
|
230
|
+
no: :difference
|
231
|
+
)
|
232
|
+
```
|
233
|
+
|
234
|
+
#### IndentationConsistency
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
# bad
|
238
|
+
class A
|
239
|
+
|
240
|
+
def test
|
241
|
+
puts 'hello'
|
242
|
+
puts 'world'
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def bar
|
248
|
+
puts 'world'
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
# good
|
254
|
+
class A
|
255
|
+
|
256
|
+
def test
|
257
|
+
puts 'hello'
|
258
|
+
puts 'world'
|
259
|
+
end
|
260
|
+
|
261
|
+
private
|
262
|
+
|
263
|
+
def bar
|
264
|
+
puts 'hello'
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
#### LineLength
|
271
|
+
|
272
|
+
Maximum of 120 characters
|
273
|
+
|
274
|
+
#### MultilineMethodCallIndentation
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
# bad
|
278
|
+
while myvariable
|
279
|
+
.b
|
280
|
+
# do something
|
281
|
+
end
|
282
|
+
|
283
|
+
# good
|
284
|
+
while myvariables
|
285
|
+
.b
|
286
|
+
# do something
|
287
|
+
end
|
288
|
+
|
289
|
+
# bad
|
290
|
+
def my_method
|
291
|
+
my_variable
|
292
|
+
.method_1
|
293
|
+
.method_2
|
294
|
+
end
|
295
|
+
|
296
|
+
# good
|
297
|
+
def my_method
|
298
|
+
my_variable
|
299
|
+
.method_1
|
300
|
+
.method_2
|
301
|
+
end
|
302
|
+
```
|
303
|
+
|
304
|
+
#### ParameterAlignment
|
305
|
+
|
306
|
+
```ruby
|
307
|
+
# bad
|
308
|
+
def foo(bar,
|
309
|
+
baz)
|
310
|
+
puts bar + baz
|
311
|
+
end
|
312
|
+
|
313
|
+
# good
|
314
|
+
def foo(bar,
|
315
|
+
baz)
|
316
|
+
puts bar + baz
|
317
|
+
end
|
318
|
+
|
319
|
+
# bad
|
320
|
+
def foo(
|
321
|
+
bar,
|
322
|
+
baz)
|
323
|
+
puts bar + baz
|
324
|
+
end
|
325
|
+
|
326
|
+
# good
|
327
|
+
def foo(
|
328
|
+
bar,
|
329
|
+
baz
|
330
|
+
)
|
331
|
+
puts bar + baz
|
332
|
+
end
|
333
|
+
```
|
334
|
+
|
335
|
+
</details>
|
336
|
+
|
337
|
+
<details><summary>Linting</summary>
|
338
|
+
|
339
|
+
#### RescueException
|
340
|
+
|
341
|
+
```yaml
|
342
|
+
Enabled: false // does not check for rescue blocks targeting the Exception class.
|
343
|
+
```
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
# NOT bad
|
347
|
+
begin
|
348
|
+
do_something
|
349
|
+
rescue Exception
|
350
|
+
handle_exception
|
351
|
+
end
|
352
|
+
|
353
|
+
# good
|
354
|
+
begin
|
355
|
+
do_something
|
356
|
+
rescue ArgumentError
|
357
|
+
handle_exception
|
358
|
+
end
|
359
|
+
```
|
360
|
+
|
361
|
+
</details>
|
362
|
+
|
363
|
+
<details><summary>Metrics</summary>
|
364
|
+
|
365
|
+
#### AbcSize
|
366
|
+
|
367
|
+
```yaml
|
368
|
+
Enabled: false // does not check ABC size of methods
|
369
|
+
```
|
370
|
+
|
371
|
+
#### BlockLength
|
372
|
+
|
373
|
+
```yaml
|
374
|
+
Enabled: false // does not check if the length of a block exceeds some maximum value.
|
375
|
+
```
|
376
|
+
|
377
|
+
#### ClassLength
|
378
|
+
|
379
|
+
```yaml
|
380
|
+
Max: 250 // checks if the length of a class exceeds 250
|
381
|
+
```
|
382
|
+
|
383
|
+
#### CyclomaticComplexity
|
384
|
+
|
385
|
+
```yaml
|
386
|
+
Enabled: false // does not check the cyclomatic complexity
|
387
|
+
```
|
388
|
+
|
389
|
+
#### MethodLength
|
390
|
+
|
391
|
+
```yaml
|
392
|
+
Max: 40 // checks if the length of a method exceeds 40
|
393
|
+
```
|
394
|
+
|
395
|
+
#### ModuleLength
|
396
|
+
|
397
|
+
```yaml
|
398
|
+
Max: 250 // checks if the length of a module exceeds 250
|
399
|
+
```
|
400
|
+
|
401
|
+
#### PerceivedComplexity
|
402
|
+
|
403
|
+
```yaml
|
404
|
+
Enabled: false // does not check the perceived complexity
|
405
|
+
```
|
406
|
+
|
407
|
+
</details>
|
408
|
+
|
409
|
+
<details><summary>Naming</summary>
|
410
|
+
|
411
|
+
#### MemoizedInstanceVariableName
|
412
|
+
|
413
|
+
```ruby
|
414
|
+
# bad
|
415
|
+
def foo
|
416
|
+
@something ||= calculate_expensive_thing
|
417
|
+
end
|
418
|
+
|
419
|
+
# bad
|
420
|
+
def foo
|
421
|
+
@foo ||= calculate_expensive_thing
|
422
|
+
end
|
423
|
+
|
424
|
+
# good
|
425
|
+
def foo
|
426
|
+
@_foo ||= calculate_expensive_thing
|
427
|
+
end
|
428
|
+
```
|
429
|
+
|
430
|
+
</details>
|
431
|
+
|
432
|
+
<details><summary>Style</summary>
|
433
|
+
|
434
|
+
#### ClassAndModuleChildren
|
435
|
+
|
436
|
+
```yaml
|
437
|
+
Enabled: false // does not check the style of children definitions at classes and modules
|
438
|
+
```
|
439
|
+
|
440
|
+
```ruby
|
441
|
+
# good
|
442
|
+
class Foo
|
443
|
+
class Bar
|
444
|
+
end
|
445
|
+
end
|
446
|
+
# good
|
447
|
+
class Foo::Bar
|
448
|
+
end
|
449
|
+
```
|
450
|
+
|
451
|
+
#### Documentation
|
452
|
+
|
453
|
+
```yaml
|
454
|
+
Enabled: false // does not check for missing top-level documentation of classes and modules
|
455
|
+
```
|
456
|
+
|
457
|
+
```ruby
|
458
|
+
# good
|
459
|
+
class Person
|
460
|
+
# ...
|
461
|
+
end
|
462
|
+
|
463
|
+
# good
|
464
|
+
# Description/Explanation of Person class
|
465
|
+
class Person
|
466
|
+
# ...
|
467
|
+
end
|
468
|
+
```
|
469
|
+
|
470
|
+
#### NumericLiterals
|
471
|
+
|
472
|
+
```ruby
|
473
|
+
# bad
|
474
|
+
1000000
|
475
|
+
1_00_000
|
476
|
+
1_0000
|
477
|
+
10_000_00 # typical representation of $10,000 in cents
|
478
|
+
|
479
|
+
# good
|
480
|
+
1_000_000
|
481
|
+
1000
|
482
|
+
```
|
483
|
+
|
484
|
+
#### RegexpLiteral
|
485
|
+
|
486
|
+
```ruby
|
487
|
+
# bad
|
488
|
+
snake_case = %r{^[\dA-Z_]+$}
|
489
|
+
# good
|
490
|
+
snake_case = /^[\dA-Z_]+$/
|
491
|
+
|
492
|
+
# bad
|
493
|
+
regex = %r{
|
494
|
+
foo
|
495
|
+
(bar)
|
496
|
+
(baz)
|
497
|
+
}x
|
498
|
+
# good
|
499
|
+
regex = /
|
500
|
+
foo
|
501
|
+
(bar)
|
502
|
+
(baz)
|
503
|
+
/x
|
504
|
+
|
505
|
+
#bad
|
506
|
+
x =~ %r{home/}
|
507
|
+
# good
|
508
|
+
x =~ /home\//
|
509
|
+
```
|
510
|
+
|
511
|
+
#### StructInheritance
|
512
|
+
|
513
|
+
```yaml
|
514
|
+
Enabled: false // does not check for inheritance from Struct.new.
|
515
|
+
```
|
516
|
+
|
517
|
+
```ruby
|
518
|
+
# good
|
519
|
+
class Person < Struct.new(:first_name, :last_name)
|
520
|
+
|
521
|
+
def age
|
522
|
+
42
|
523
|
+
end
|
524
|
+
|
525
|
+
end
|
526
|
+
|
527
|
+
# good
|
528
|
+
Person = Struct.new(:first_name, :last_name) do
|
529
|
+
def age
|
530
|
+
42
|
531
|
+
end
|
532
|
+
end
|
533
|
+
```
|
534
|
+
|
535
|
+
#### SymbolArray
|
536
|
+
|
537
|
+
```ruby
|
538
|
+
# bad
|
539
|
+
%i[foo bar baz]
|
540
|
+
|
541
|
+
# good
|
542
|
+
[:foo, :bar, :baz]
|
543
|
+
```
|
544
|
+
|
545
|
+
#### WordArray
|
546
|
+
|
547
|
+
```ruby
|
548
|
+
# bad
|
549
|
+
%w[foo bar baz]
|
550
|
+
|
551
|
+
# good
|
552
|
+
['foo', 'bar', 'baz']
|
553
|
+
```
|
554
|
+
|
555
|
+
</details>
|
556
|
+
|
557
|
+
<details><summary>RSpec</summary>
|
558
|
+
|
559
|
+
#### AnyInstance
|
560
|
+
|
561
|
+
```yaml
|
562
|
+
Enabled: false // instances can be stubbed globally.
|
563
|
+
```
|
564
|
+
|
565
|
+
```ruby
|
566
|
+
# NOT bad
|
567
|
+
describe MyClass do
|
568
|
+
before(:each) { allow_any_instance_of(MyClass).to receive(:foo) }
|
569
|
+
end
|
570
|
+
|
571
|
+
# good
|
572
|
+
describe MyClass do
|
573
|
+
let(:my_instance) { instance_double(MyClass) }
|
574
|
+
|
575
|
+
before(:each) do
|
576
|
+
allow(MyClass).to receive(:new).and_return(my_instance)
|
577
|
+
allow(my_instance).to receive(:foo)
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
```
|
582
|
+
|
583
|
+
#### DescribedClass
|
584
|
+
|
585
|
+
```ruby
|
586
|
+
# bad
|
587
|
+
describe MyClass do
|
588
|
+
subject { described_class.do_something }
|
589
|
+
end
|
590
|
+
|
591
|
+
# good
|
592
|
+
describe MyClass do
|
593
|
+
subject { MyClass.do_something }
|
594
|
+
end
|
595
|
+
```
|
596
|
+
|
597
|
+
#### ExampleLength
|
598
|
+
|
599
|
+
```yaml
|
600
|
+
Enabled: false // does not check for long examples.
|
601
|
+
```
|
602
|
+
|
603
|
+
#### HookArgument
|
604
|
+
|
605
|
+
```ruby
|
606
|
+
# bad
|
607
|
+
before(:example) do
|
608
|
+
# ...
|
609
|
+
end
|
610
|
+
|
611
|
+
# good
|
612
|
+
before do
|
613
|
+
# ...
|
614
|
+
end
|
615
|
+
|
616
|
+
# good
|
617
|
+
before(:each) do
|
618
|
+
# ...
|
619
|
+
end
|
620
|
+
```
|
621
|
+
|
622
|
+
#### MultipleExpectations
|
623
|
+
|
624
|
+
```yaml
|
625
|
+
Enabled: false // does not check if examples contain too many expect calls.
|
626
|
+
```
|
627
|
+
|
628
|
+
#### NestedGroups
|
629
|
+
|
630
|
+
```yaml
|
631
|
+
Max: 4 // checks if nested groups does not exceed 4 levels
|
632
|
+
```
|
633
|
+
|
634
|
+
#### NotToNot
|
635
|
+
|
636
|
+
```ruby
|
637
|
+
# bad
|
638
|
+
it '...' do
|
639
|
+
expect(false).not_to be_true
|
640
|
+
end
|
641
|
+
|
642
|
+
# good
|
643
|
+
it '...' do
|
644
|
+
expect(false).to_not be_true
|
645
|
+
end
|
646
|
+
```
|
647
|
+
|
648
|
+
#### RepeatedDescription
|
649
|
+
|
650
|
+
```yaml
|
651
|
+
Enabled: false // example groups can have the same description string.
|
652
|
+
```
|
653
|
+
|
654
|
+
</details>
|
655
|
+
|
656
|
+
### Default rules
|
657
|
+
|
658
|
+
All other rules have the default configuration. They are detailed in the [RuboCop official documentation](https://docs.rubocop.org/en/stable/), in the [RSpec Extension official documentation](https://docs.rubocop.org/projects/rspec/en/stable/),in the [Performance Extension official documentation](https://docs.rubocop.org/projects/performance/en/stable/), and in the [Rails Extension official documentation](https://docs.rubocop.org/projects/rails/en/stable/).
|
659
|
+
|
660
|
+
Below are documented the default RuboCop rules considered as particularly important. **They are also enforced by RuboCop!**
|
661
|
+
|
662
|
+
#### Style/FrozenStringLiteralComment
|
663
|
+
|
664
|
+
Freezing Strings feature improves apps performance by freezing Strings. So, Matz - Ruby’s creator - decided to make all String literals frozen (immutable) by default in Ruby 3.0.
|
665
|
+
|
666
|
+
In order to have a transition path to this coming big change, we add a magic comment at the beginning of each file, which freezes strings by default:
|
667
|
+
|
668
|
+
```ruby
|
669
|
+
# frozen_string_literal: true
|
670
|
+
class YourClass
|
671
|
+
# ...
|
672
|
+
end
|
673
|
+
```
|