lifen-ruby-style 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ ```