rubocop-rails 2.13.1 → 2.14.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 624c6bfb8dc9c5db0f6dd6bec835bd71e5e5a7aadc298342c15faae187226079
4
- data.tar.gz: 4354d39a8367f0e4cb46b5a890cdd0836bfff6d838b5ae320b3df3a480f529af
3
+ metadata.gz: c8ac7c4db62e8518ff9316e0a3ac8919ac440eaf9ba84eacf883cf902fffca7a
4
+ data.tar.gz: 8a4b1e0c90e4ea2b8ba06352bc7525b56c4d95af49e618fb77a6bfd4bc5dafdb
5
5
  SHA512:
6
- metadata.gz: 19863e8b5b71b4403d66b46e73ed41ceb1953d2a68996fbcc800ab4c06fcf4dfebadf3907f62d2c575b002223a95fa67fce2412c01fa42b6234848c2791b37e9
7
- data.tar.gz: 1e3d14e3240f7fdb9182529865ee8257cf19368bac19c33abfba44b0a3b63c54e64457f73725e6c629a5846ea402a96e650f7d6ce0e1943386c857d6a8642d7b
6
+ metadata.gz: fb91cd3a6e867309efc10717843f0994a743bf1a4eb65b058863340d48904624377e88bb1b38cc0e091b9c2d5c676901987c1a31c3f6f50e5923863feb11f900
7
+ data.tar.gz: fe8208f3d1962a6c5400c74ab174b7b5b8c88641a564616f155cf90d91915ea85b6de031dc99a9737eb93fe3c39d878507f52d7f0aed85480142f630d0a082f5
data/config/default.yml CHANGED
@@ -7,7 +7,9 @@ inherit_mode:
7
7
  AllCops:
8
8
  Exclude:
9
9
  - bin/*
10
- - db/schema.rb
10
+ # Exclude db/schema.rb and db/[CONFIGURATION_NAMESPACE]_schema.rb by default.
11
+ # See: https://guides.rubyonrails.org/active_record_multiple_databases.html#setting-up-your-application
12
+ - db/*schema.rb
11
13
  # What version of Rails is the inspected code using? If a value is specified
12
14
  # for TargetRailsVersion then it is used. Acceptable values are specified
13
15
  # as a float (i.e. 5.1); the patch version of Rails should not be included.
@@ -38,6 +40,16 @@ Lint/NumberConversion:
38
40
  - fortnights
39
41
  - in_milliseconds
40
42
 
43
+ Rails/ActionControllerTestCase:
44
+ Description: 'Use `ActionDispatch::IntegrationTest` instead of `ActionController::TestCase`.'
45
+ StyleGuide: 'https://rails.rubystyle.guide/#integration-testing'
46
+ Reference: 'https://api.rubyonrails.org/classes/ActionController/TestCase.html'
47
+ Enabled: 'pending'
48
+ SafeAutocorrect: false
49
+ VersionAdded: '2.14'
50
+ Include:
51
+ - '**/test/**/*.rb'
52
+
41
53
  Rails/ActionFilter:
42
54
  Description: 'Enforces consistent use of action filter methods.'
43
55
  Enabled: true
@@ -195,9 +207,12 @@ Rails/ContentTag:
195
207
  Enabled: true
196
208
  VersionAdded: '2.6'
197
209
  VersionChanged: '2.12'
198
- # This `Exclude` config prevents false positives for `tag` calls to `has_one: tag`. No helpers are used in normal models.
210
+ # This `Exclude` config prevents false positives for `tag` calls to `has_one: tag` and Puma configuration:
211
+ # https://puma.io/puma/Puma/DSL.html#tag-instance_method
212
+ # No helpers are used in normal models and configs.
199
213
  Exclude:
200
214
  - app/models/**/*.rb
215
+ - config/**/*.rb
201
216
 
202
217
  Rails/CreateTableWithTimestamps:
203
218
  Description: >-
@@ -251,6 +266,22 @@ Rails/DelegateAllowBlank:
251
266
  Enabled: true
252
267
  VersionAdded: '0.44'
253
268
 
269
+ Rails/DeprecatedActiveModelErrorsMethods:
270
+ Description: 'Avoid manipulating ActiveModel errors hash directly.'
271
+ Enabled: pending
272
+ Safe: false
273
+ VersionAdded: '2.14'
274
+
275
+ Rails/DuplicateAssociation:
276
+ Description: "Don't repeat associations in a model."
277
+ Enabled: pending
278
+ VersionAdded: '2.14'
279
+
280
+ Rails/DuplicateScope:
281
+ Description: 'Multiple scopes share this same where clause.'
282
+ Enabled: pending
283
+ VersionAdded: '2.14'
284
+
254
285
  Rails/DurationArithmetic:
255
286
  Description: 'Do not use duration as arithmetic operand with `Time.current`.'
256
287
  StyleGuide: 'https://rails.rubystyle.guide#duration-arithmetic'
@@ -415,6 +446,15 @@ Rails/HttpStatus:
415
446
  - numeric
416
447
  - symbolic
417
448
 
449
+ Rails/I18nLazyLookup:
450
+ Description: 'Checks for places where I18n "lazy" lookup can be used.'
451
+ StyleGuide: 'https://rails.rubystyle.guide/#lazy-lookup'
452
+ Reference: 'https://guides.rubyonrails.org/i18n.html#lazy-lookup'
453
+ Enabled: pending
454
+ VersionAdded: '2.14'
455
+ Include:
456
+ - 'controllers/**/*'
457
+
418
458
  Rails/I18nLocaleAssignment:
419
459
  Description: 'Prefer the usage of `I18n.with_locale` instead of manually updating `I18n.locale` value.'
420
460
  Enabled: 'pending'
@@ -423,6 +463,12 @@ Rails/I18nLocaleAssignment:
423
463
  - spec/**/*.rb
424
464
  - test/**/*.rb
425
465
 
466
+ Rails/I18nLocaleTexts:
467
+ Description: 'Enforces use of I18n and locale files instead of locale specific strings.'
468
+ StyleGuide: 'https://rails.rubystyle.guide/#locale-texts'
469
+ Enabled: pending
470
+ VersionAdded: '2.14'
471
+
426
472
  Rails/IgnoredSkipActionFilterOption:
427
473
  Description: 'Checks that `if` and `only` (or `except`) are not used together as options of `skip_*` action filter.'
428
474
  Reference: 'https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options'
@@ -453,6 +499,7 @@ Rails/InverseOf:
453
499
  Description: 'Checks for associations where the inverse cannot be determined automatically.'
454
500
  Enabled: true
455
501
  VersionAdded: '0.52'
502
+ IgnoreScopes: false
456
503
  Include:
457
504
  - app/models/**/*.rb
458
505
 
@@ -494,6 +541,13 @@ Rails/MatchRoute:
494
541
  - config/routes.rb
495
542
  - config/routes/**/*.rb
496
543
 
544
+ Rails/MigrationClassName:
545
+ Description: 'The class name of the migration should match its file name.'
546
+ Enabled: pending
547
+ VersionAdded: '2.14'
548
+ Include:
549
+ - db/migrate/*.rb
550
+
497
551
  Rails/NegateInclude:
498
552
  Description: 'Prefer `collection.exclude?(obj)` over `!collection.include?(obj)`.'
499
553
  StyleGuide: 'https://rails.rubystyle.guide#exclude'
@@ -627,6 +681,7 @@ Rails/RedundantForeignKey:
627
681
  Rails/RedundantPresenceValidationOnBelongsTo:
628
682
  Description: 'Checks for redundant presence validation on belongs_to association.'
629
683
  Enabled: pending
684
+ SafeAutoCorrect: false
630
685
  VersionAdded: '2.13'
631
686
 
632
687
  Rails/RedundantReceiverInWithOptions:
@@ -702,15 +757,17 @@ Rails/ReversibleMigration:
702
757
  Reference: 'https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html'
703
758
  Enabled: true
704
759
  VersionAdded: '0.47'
760
+ VersionChanged: '2.13'
705
761
  Include:
706
- - db/migrate/*.rb
762
+ - db/**/*.rb
707
763
 
708
764
  Rails/ReversibleMigrationMethodDefinition:
709
765
  Description: 'Checks whether the migration implements either a `change` method or both an `up` and a `down` method.'
710
766
  Enabled: false
711
767
  VersionAdded: '2.10'
768
+ VersionChanged: '2.13'
712
769
  Include:
713
- - db/migrate/*.rb
770
+ - db/**/*.rb
714
771
 
715
772
  Rails/RootJoinChain:
716
773
  Description: 'Use a single `#join` instead of chaining on `Rails.root` or `Rails.public_path`.'
@@ -813,6 +870,15 @@ Rails/SquishedSQLHeredocs:
813
870
  # to be preserved in order to work, thus auto-correction is not safe.
814
871
  SafeAutoCorrect: false
815
872
 
873
+ Rails/TableNameAssignment:
874
+ Description: >-
875
+ Do not use `self.table_name =`. Use Inflections or `table_name_prefix` instead.
876
+ StyleGuide: 'https://rails.rubystyle.guide/#keep-ar-defaults'
877
+ Enabled: false
878
+ VersionAdded: '2.14'
879
+ Include:
880
+ - app/models/**/*.rb
881
+
816
882
  Rails/TimeZone:
817
883
  Description: 'Checks the correct usage of time zone aware methods.'
818
884
  StyleGuide: 'https://rails.rubystyle.guide#time'
@@ -839,6 +905,11 @@ Rails/TimeZoneAssignment:
839
905
  - spec/**/*.rb
840
906
  - test/**/*.rb
841
907
 
908
+ Rails/TransactionExitStatement:
909
+ Description: 'Avoid the usage of `return`, `break` and `throw` in transaction blocks.'
910
+ Enabled: pending
911
+ VersionAdded: '2.14'
912
+
842
913
  Rails/UniqBeforePluck:
843
914
  Description: 'Prefer the use of uniq or distinct before pluck.'
844
915
  Enabled: true
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # A mixin to return all of the class send nodes.
6
+ module ClassSendNodeHelper
7
+ def class_send_nodes(class_node)
8
+ class_def = class_node.body
9
+
10
+ return [] unless class_def
11
+
12
+ if class_def.send_type?
13
+ [class_def]
14
+ else
15
+ class_def.each_child_node(:send)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for cops working with migrations
6
+ module MigrationsHelper
7
+ extend NodePattern::Macros
8
+
9
+ def_node_matcher :migration_class?, <<~PATTERN
10
+ (class
11
+ (const nil? _)
12
+ (send
13
+ (const (const {nil? cbase} :ActiveRecord) :Migration)
14
+ :[]
15
+ (float _))
16
+ _)
17
+ PATTERN
18
+
19
+ def in_migration?(node)
20
+ node.each_ancestor(:class).any? do |class_node|
21
+ migration_class?(class_node)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Using `ActionController::TestCase`` is discouraged and should be replaced by
7
+ # `ActionDispatch::IntegrationTest``. Controller tests are too close to the
8
+ # internals of a controller whereas integration tests mimic the browser/user.
9
+ #
10
+ # @safety
11
+ # This cop's autocorrection is unsafe because the API of each test case class is different.
12
+ # Make sure to update each test of your controller test cases after changing the superclass.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # class MyControllerTest < ActionController::TestCase
17
+ # end
18
+ #
19
+ # # good
20
+ # class MyControllerTest < ActionDispatch::IntegrationTest
21
+ # end
22
+ #
23
+ class ActionControllerTestCase < Base
24
+ extend AutoCorrector
25
+ extend TargetRailsVersion
26
+
27
+ MSG = 'Use `ActionDispatch::IntegrationTest` instead.'
28
+
29
+ minimum_target_rails_version 5.0
30
+
31
+ def_node_matcher :action_controller_test_case?, <<~PATTERN
32
+ (class
33
+ (const nil? _)
34
+ (const (const {nil? cbase} :ActionController) :TestCase) nil?)
35
+ PATTERN
36
+
37
+ def on_class(node)
38
+ return unless action_controller_test_case?(node)
39
+
40
+ add_offense(node.parent_class) do |corrector|
41
+ corrector.replace(node.parent_class, 'ActionDispatch::IntegrationTest')
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -32,6 +32,8 @@ module RuboCop
32
32
  # after_update_commit :log_update_action
33
33
  #
34
34
  class AfterCommitOverride < Base
35
+ include ClassSendNodeHelper
36
+
35
37
  MSG = 'There can only be one `after_*_commit :%<name>s` hook defined for a model.'
36
38
 
37
39
  AFTER_COMMIT_CALLBACKS = %i[
@@ -63,18 +65,6 @@ module RuboCop
63
65
  end
64
66
  end
65
67
 
66
- def class_send_nodes(class_node)
67
- class_def = class_node.body
68
-
69
- return [] unless class_def
70
-
71
- if class_def.send_type?
72
- [class_def]
73
- else
74
- class_def.each_child_node(:send).to_a
75
- end
76
- end
77
-
78
68
  def after_commit_callback?(node)
79
69
  AFTER_COMMIT_CALLBACKS.include?(node.method_name)
80
70
  end
@@ -155,17 +155,31 @@ module RuboCop
155
155
  return if include_bulk_options?(node)
156
156
  return unless node.block_node
157
157
 
158
- send_nodes = node.block_node.body.each_child_node(:send).to_a
158
+ send_nodes = send_nodes_from_change_table_block(node.block_node.body)
159
159
 
160
- transformations = send_nodes.select do |send_node|
161
- combinable_transformations.include?(send_node.method_name)
162
- end
163
-
164
- add_offense_for_change_table(node) if transformations.size > 1
160
+ add_offense_for_change_table(node) if count_transformations(send_nodes) > 1
165
161
  end
166
162
 
167
163
  private
168
164
 
165
+ def send_nodes_from_change_table_block(body)
166
+ if body.send_type?
167
+ [body]
168
+ else
169
+ body.each_child_node(:send).to_a
170
+ end
171
+ end
172
+
173
+ def count_transformations(send_nodes)
174
+ send_nodes.sum do |node|
175
+ if node.method?(:remove)
176
+ node.arguments.count { |arg| !arg.hash_type? }
177
+ else
178
+ combinable_transformations.include?(node.method_name) ? 1 : 0
179
+ end
180
+ end
181
+ end
182
+
169
183
  # @param node [RuboCop::AST::SendNode] (send nil? :change_table ...)
170
184
  def include_bulk_options?(node)
171
185
  # arguments: [{(sym :table)(str "table")} (hash (pair (sym :bulk) _))]
@@ -10,25 +10,29 @@ module RuboCop
10
10
  # blank check of block arguments to the receiver object.
11
11
  #
12
12
  # For example, `[[1, 2], [3, nil]].reject { |first, second| second.blank? }` and
13
- # `[[1, 2], [3, nil]].compact_blank` are not compatible. The same is true for `empty?`.
13
+ # `[[1, 2], [3, nil]].compact_blank` are not compatible. The same is true for `blank?`.
14
14
  # This will work fine when the receiver is a hash object.
15
15
  #
16
+ # And `compact_blank!` has different implementations for `Array`, `Hash`, and
17
+ # `ActionController::Parameters`.
18
+ # `Array#compact_blank!`, `Hash#compact_blank!` are equivalent to `delete_if(&:blank?)`.
19
+ # `ActionController::Parameters#compact_blank!` is equivalent to `reject!(&:blank?)`.
20
+ # If the cop makes a mistake, auto-corrected code may get unexpected behavior.
21
+ #
16
22
  # @example
17
23
  #
18
24
  # # bad
19
25
  # collection.reject(&:blank?)
20
- # collection.reject(&:empty?)
21
26
  # collection.reject { |_k, v| v.blank? }
22
- # collection.reject { |_k, v| v.empty? }
23
27
  #
24
28
  # # good
25
29
  # collection.compact_blank
26
30
  #
27
31
  # # bad
28
- # collection.reject!(&:blank?)
29
- # collection.reject!(&:empty?)
30
- # collection.reject! { |_k, v| v.blank? }
31
- # collection.reject! { |_k, v| v.empty? }
32
+ # collection.delete_if(&:blank?) # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
33
+ # collection.delete_if { |_k, v| v.blank? } # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
34
+ # collection.reject!(&:blank?) # Same behavior as `ActionController::Parameters#compact_blank!`
35
+ # collection.reject! { |_k, v| v.blank? } # Same behavior as `ActionController::Parameters#compact_blank!`
32
36
  #
33
37
  # # good
34
38
  # collection.compact_blank!
@@ -39,22 +43,22 @@ module RuboCop
39
43
  extend TargetRailsVersion
40
44
 
41
45
  MSG = 'Use `%<preferred_method>s` instead.'
42
- RESTRICT_ON_SEND = %i[reject reject!].freeze
46
+ RESTRICT_ON_SEND = %i[reject delete_if reject!].freeze
43
47
 
44
48
  minimum_target_rails_version 6.1
45
49
 
46
50
  def_node_matcher :reject_with_block?, <<~PATTERN
47
51
  (block
48
- (send _ {:reject :reject!})
52
+ (send _ {:reject :delete_if :reject!})
49
53
  $(args ...)
50
54
  (send
51
- $(lvar _) {:blank? :empty?}))
55
+ $(lvar _) :blank?))
52
56
  PATTERN
53
57
 
54
58
  def_node_matcher :reject_with_block_pass?, <<~PATTERN
55
- (send _ {:reject :reject!}
59
+ (send _ {:reject :delete_if :reject!}
56
60
  (block_pass
57
- (sym {:blank? :empty?})))
61
+ (sym :blank?)))
58
62
  PATTERN
59
63
 
60
64
  def on_send(node)
@@ -73,12 +77,17 @@ module RuboCop
73
77
  return true if reject_with_block_pass?(node)
74
78
 
75
79
  if (arguments, receiver_in_block = reject_with_block?(node.parent))
76
- return arguments.length == 1 || use_hash_value_block_argument?(arguments, receiver_in_block)
80
+ return use_single_value_block_argument?(arguments, receiver_in_block) ||
81
+ use_hash_value_block_argument?(arguments, receiver_in_block)
77
82
  end
78
83
 
79
84
  false
80
85
  end
81
86
 
87
+ def use_single_value_block_argument?(arguments, receiver_in_block)
88
+ arguments.length == 1 && arguments[0].source == receiver_in_block.source
89
+ end
90
+
82
91
  def use_hash_value_block_argument?(arguments, receiver_in_block)
83
92
  arguments.length == 2 && arguments[1].source == receiver_in_block.source
84
93
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks direct manipulation of ActiveModel#errors as hash.
7
+ # These operations are deprecated in Rails 6.1 and will not work in Rails 7.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe because it can report `errors` manipulation on non-ActiveModel,
11
+ # which is obviously valid.
12
+ # The cop has no way of knowing whether a variable is an ActiveModel or not.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # user.errors[:name] << 'msg'
17
+ # user.errors.messages[:name] << 'msg'
18
+ #
19
+ # # good
20
+ # user.errors.add(:name, 'msg')
21
+ #
22
+ # # bad
23
+ # user.errors[:name].clear
24
+ # user.errors.messages[:name].clear
25
+ #
26
+ # # good
27
+ # user.errors.delete(:name)
28
+ #
29
+ class DeprecatedActiveModelErrorsMethods < Base
30
+ MSG = 'Avoid manipulating ActiveModel errors as hash directly.'
31
+
32
+ MANIPULATIVE_METHODS = Set[
33
+ *%i[
34
+ << append clear collect! compact! concat
35
+ delete delete_at delete_if drop drop_while fill filter! keep_if
36
+ flatten! insert map! pop prepend push reject! replace reverse!
37
+ rotate! select! shift shuffle! slice! sort! sort_by! uniq! unshift
38
+ ]
39
+ ].freeze
40
+
41
+ def_node_matcher :receiver_matcher_outside_model, '{send ivar lvar}'
42
+ def_node_matcher :receiver_matcher_inside_model, '{nil? send ivar lvar}'
43
+
44
+ def_node_matcher :any_manipulation?, <<~PATTERN
45
+ {
46
+ #root_manipulation?
47
+ #root_assignment?
48
+ #messages_details_manipulation?
49
+ #messages_details_assignment?
50
+ }
51
+ PATTERN
52
+
53
+ def_node_matcher :root_manipulation?, <<~PATTERN
54
+ (send
55
+ (send
56
+ (send #receiver_matcher :errors) :[] ...)
57
+ MANIPULATIVE_METHODS
58
+ ...
59
+ )
60
+ PATTERN
61
+
62
+ def_node_matcher :root_assignment?, <<~PATTERN
63
+ (send
64
+ (send #receiver_matcher :errors)
65
+ :[]=
66
+ ...)
67
+ PATTERN
68
+
69
+ def_node_matcher :messages_details_manipulation?, <<~PATTERN
70
+ (send
71
+ (send
72
+ (send
73
+ (send #receiver_matcher :errors)
74
+ {:messages :details})
75
+ :[]
76
+ ...)
77
+ MANIPULATIVE_METHODS
78
+ ...)
79
+ PATTERN
80
+
81
+ def_node_matcher :messages_details_assignment?, <<~PATTERN
82
+ (send
83
+ (send
84
+ (send #receiver_matcher :errors)
85
+ {:messages :details})
86
+ :[]=
87
+ ...)
88
+ PATTERN
89
+
90
+ def on_send(node)
91
+ any_manipulation?(node) do
92
+ add_offense(node)
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def receiver_matcher(node)
99
+ model_file? ? receiver_matcher_inside_model(node) : receiver_matcher_outside_model(node)
100
+ end
101
+
102
+ def model_file?
103
+ processed_source.buffer.name.include?('/models/')
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for associations that have been defined multiple times in the same file.
7
+ #
8
+ # When an association is defined multiple times on a model, Active Record overrides the
9
+ # previously defined association with the new one. Because of this, this cop's autocorrection
10
+ # simply keeps the last of any duplicates and discards the rest.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # belongs_to :foo
16
+ # belongs_to :bar
17
+ # has_one :foo
18
+ #
19
+ # # good
20
+ # belongs_to :bar
21
+ # has_one :foo
22
+ #
23
+ class DuplicateAssociation < Base
24
+ include RangeHelp
25
+ extend AutoCorrector
26
+ include ClassSendNodeHelper
27
+
28
+ MSG = "Association `%<name>s` is defined multiple times. Don't repeat associations."
29
+
30
+ def_node_matcher :association, <<~PATTERN
31
+ (send nil? {:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_) ...)
32
+ PATTERN
33
+
34
+ def on_class(class_node)
35
+ offenses(class_node).each do |name, nodes|
36
+ nodes.each do |node|
37
+ add_offense(node, message: format(MSG, name: name)) do |corrector|
38
+ next if nodes.last == node
39
+
40
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def offenses(class_node)
49
+ class_send_nodes(class_node).select { |node| association(node) }
50
+ .group_by { |node| association(node).to_sym }
51
+ .select { |_, nodes| nodes.length > 1 }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for multiple scopes in a model that have the same `where` clause. This
7
+ # often means you copy/pasted a scope, updated the name, and forgot to change the condition.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # scope :visible, -> { where(visible: true) }
13
+ # scope :hidden, -> { where(visible: true) }
14
+ #
15
+ # # good
16
+ # scope :visible, -> { where(visible: true) }
17
+ # scope :hidden, -> { where(visible: false) }
18
+ #
19
+ class DuplicateScope < Base
20
+ include ClassSendNodeHelper
21
+
22
+ MSG = 'Multiple scopes share this same where clause.'
23
+
24
+ def_node_matcher :scope, <<~PATTERN
25
+ (send nil? :scope _ $...)
26
+ PATTERN
27
+
28
+ def on_class(class_node)
29
+ offenses(class_node).each do |node|
30
+ add_offense(node)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def offenses(class_node)
37
+ class_send_nodes(class_node).select { |node| scope(node) }
38
+ .group_by { |node| scope(node) }
39
+ .select { |_, nodes| nodes.length > 1 }
40
+ .values
41
+ .flatten
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -26,7 +26,8 @@ module RuboCop
26
26
  RESTRICT_ON_SEND = %i[+ -].freeze
27
27
 
28
28
  DURATIONS = Set[:second, :seconds, :minute, :minutes, :hour, :hours,
29
- :day, :days, :week, :weeks, :fortnight, :fortnights]
29
+ :day, :days, :week, :weeks, :fortnight, :fortnights,
30
+ :month, :months, :year, :years]
30
31
 
31
32
  # @!method duration_arithmetic_argument?(node)
32
33
  # Match duration subtraction or addition with current time.