rubocop-rails 2.13.0 → 2.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/config/default.yml +75 -4
- data/lib/rubocop/cop/mixin/class_send_node_helper.rb +20 -0
- data/lib/rubocop/cop/mixin/migrations_helper.rb +26 -0
- data/lib/rubocop/cop/rails/action_controller_test_case.rb +47 -0
- data/lib/rubocop/cop/rails/after_commit_override.rb +2 -12
- data/lib/rubocop/cop/rails/bulk_change_table.rb +20 -6
- data/lib/rubocop/cop/rails/compact_blank.rb +22 -13
- data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +108 -0
- data/lib/rubocop/cop/rails/duplicate_association.rb +56 -0
- data/lib/rubocop/cop/rails/duplicate_scope.rb +46 -0
- data/lib/rubocop/cop/rails/duration_arithmetic.rb +2 -1
- data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +94 -0
- data/lib/rubocop/cop/rails/i18n_locale_texts.rb +110 -0
- data/lib/rubocop/cop/rails/index_by.rb +6 -6
- data/lib/rubocop/cop/rails/index_with.rb +6 -6
- data/lib/rubocop/cop/rails/inverse_of.rb +17 -1
- data/lib/rubocop/cop/rails/migration_class_name.rb +61 -0
- data/lib/rubocop/cop/rails/pluck.rb +15 -7
- data/lib/rubocop/cop/rails/read_write_attribute.rb +51 -14
- data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +93 -28
- data/lib/rubocop/cop/rails/reversible_migration.rb +5 -3
- data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +2 -10
- data/lib/rubocop/cop/rails/table_name_assignment.rb +44 -0
- data/lib/rubocop/cop/rails/transaction_exit_statement.rb +77 -0
- data/lib/rubocop/cop/rails/unused_ignored_columns.rb +2 -0
- data/lib/rubocop/cop/rails_cops.rb +11 -0
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +15 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf7825df8f38360ce42c29846bd7b2e276447e6d87a5585acc35da05b829992f
|
4
|
+
data.tar.gz: 6f219da0c0e0b92f9b5027bd1e50381435a4ac660484e5164a2718e79d52e6c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3ebdfbd8f0d547d3c5becc45b95f2faa4c36651397e2082ec5c454c0388cfed7b09edc8d3bfbbdd1d70ea09b8a171e08efedf6f0eb660e39b24df2888d92547
|
7
|
+
data.tar.gz: '06831b53608a82160e586d295aa04fe14670791545602386440fd9b40792391f2ddb8ac7d49b2dd677722251d7618c0f11388866bfd5dc1975b02ce99fb9246e'
|
data/LICENSE.txt
CHANGED
data/config/default.yml
CHANGED
@@ -7,7 +7,9 @@ inherit_mode:
|
|
7
7
|
AllCops:
|
8
8
|
Exclude:
|
9
9
|
- bin/*
|
10
|
-
|
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
|
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
|
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
|
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
|
158
|
+
send_nodes = send_nodes_from_change_table_block(node.block_node.body)
|
159
159
|
|
160
|
-
|
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 `
|
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.
|
29
|
-
# collection.
|
30
|
-
# collection.reject!
|
31
|
-
# collection.reject! { |_k, v| v.
|
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 _)
|
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
|
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
|
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.
|