rubocop-rails 2.13.0 → 2.14.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/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.
|