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.
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
+ ```