rubocop-rails 2.24.1 → 2.26.2
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/README.md +16 -2
- data/config/default.yml +29 -6
- data/lib/rubocop/cop/mixin/target_rails_version.rb +29 -2
- data/lib/rubocop/cop/rails/action_order.rb +1 -5
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
- data/lib/rubocop/cop/rails/application_record.rb +4 -0
- data/lib/rubocop/cop/rails/bulk_change_table.rb +10 -4
- data/lib/rubocop/cop/rails/compact_blank.rb +29 -8
- data/lib/rubocop/cop/rails/date.rb +2 -2
- data/lib/rubocop/cop/rails/enum_hash.rb +31 -8
- data/lib/rubocop/cop/rails/enum_syntax.rb +128 -0
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
- data/lib/rubocop/cop/rails/file_path.rb +1 -1
- data/lib/rubocop/cop/rails/http_status.rb +12 -2
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +1 -1
- data/lib/rubocop/cop/rails/link_to_blank.rb +2 -2
- data/lib/rubocop/cop/rails/not_null_column.rb +93 -13
- data/lib/rubocop/cop/rails/pick.rb +4 -0
- data/lib/rubocop/cop/rails/pluck_in_where.rb +17 -8
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +29 -15
- data/lib/rubocop/cop/rails/present.rb +0 -2
- data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +0 -29
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
- data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +9 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
- data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
- data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
- data/lib/rubocop/cop/rails/request_referer.rb +1 -1
- data/lib/rubocop/cop/rails/root_pathname_methods.rb +15 -11
- data/lib/rubocop/cop/rails/skips_model_validations.rb +8 -3
- data/lib/rubocop/cop/rails/unknown_env.rb +1 -1
- data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
- data/lib/rubocop/cop/rails/validation.rb +8 -3
- data/lib/rubocop/cop/rails/where_equals.rb +28 -12
- data/lib/rubocop/cop/rails/where_not.rb +11 -6
- data/lib/rubocop/cop/rails/where_range.rb +203 -0
- data/lib/rubocop/cop/rails_cops.rb +2 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +1 -1
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6d480f2ef30bba709b183d1a3b71dee6c0838ea2815975b5fd25ab5933277ad
|
4
|
+
data.tar.gz: 1d36f2779e44e1fa03437154f388bf2e865c3af810e0238f5ddc6580f024fd04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cc2521c185293872667eb2a23aaaadc4ddee8289b1f16c9fff1e6c87b9498708226a0147697acb9bcc3be69642c3f3ecc063b749056c0df1cdb065a224e4a3d
|
7
|
+
data.tar.gz: ea268a009b987a6f61008602d67a8e89659611b7469fe6c1b9c81825b5f9227391035866b2ec593e6109e91a470d607f6f4bb48fd11155b9177d59daf2301111
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# RuboCop Rails
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/rubocop-rails)
|
4
|
-
[](https://github.com/rubocop/rubocop-rails/actions/workflows/test.yml)
|
5
5
|
|
6
6
|
A [RuboCop](https://github.com/rubocop/rubocop) extension focused on enforcing Rails best practices and coding conventions.
|
7
7
|
|
@@ -65,7 +65,7 @@ end
|
|
65
65
|
|
66
66
|
## Rails configuration tip
|
67
67
|
|
68
|
-
|
68
|
+
In Rails 6.1+, add the following `config.generators.after_generate` setting to
|
69
69
|
your `config/environments/development.rb` to apply RuboCop autocorrection to code generated by `bin/rails g`.
|
70
70
|
|
71
71
|
```ruby
|
@@ -84,6 +84,20 @@ It uses `rubocop -A` to apply `Style/FrozenStringLiteralComment` and other unsaf
|
|
84
84
|
`rubocop -A` is unsafe autocorrection, but code generated by default is simple and less likely to
|
85
85
|
be incompatible with `rubocop -A`. If you have problems you can replace it with `rubocop -a` instead.
|
86
86
|
|
87
|
+
In Rails 7.2+, it is recommended to use `config.generators.apply_rubocop_autocorrect_after_generate!` instead of the above setting:
|
88
|
+
|
89
|
+
```diff
|
90
|
+
# config/environments/development.rb
|
91
|
+
Rails.application.configure do
|
92
|
+
(snip)
|
93
|
+
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
|
94
|
+
- # config.generators.apply_rubocop_autocorrect_after_generate!
|
95
|
+
+ config.generators.apply_rubocop_autocorrect_after_generate!
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
You only need to uncomment.
|
100
|
+
|
87
101
|
## The Cops
|
88
102
|
|
89
103
|
All cops are located under
|
data/config/default.yml
CHANGED
@@ -212,7 +212,9 @@ Rails/ApplicationRecord:
|
|
212
212
|
Enabled: true
|
213
213
|
SafeAutoCorrect: false
|
214
214
|
VersionAdded: '0.49'
|
215
|
-
VersionChanged: '2.
|
215
|
+
VersionChanged: '2.26'
|
216
|
+
Exclude:
|
217
|
+
- db/**/*.rb
|
216
218
|
|
217
219
|
Rails/ArelStar:
|
218
220
|
Description: 'Enforces `Arel.star` instead of `"*"` for expanded columns.'
|
@@ -424,6 +426,14 @@ Rails/EnumHash:
|
|
424
426
|
Include:
|
425
427
|
- app/models/**/*.rb
|
426
428
|
|
429
|
+
Rails/EnumSyntax:
|
430
|
+
Description: 'Use positional arguments over keyword arguments when defining enums.'
|
431
|
+
Enabled: pending
|
432
|
+
Severity: warning
|
433
|
+
VersionAdded: '2.26'
|
434
|
+
Include:
|
435
|
+
- app/models/**/*.rb
|
436
|
+
|
427
437
|
Rails/EnumUniqueness:
|
428
438
|
Description: 'Avoid duplicate integers in hash-syntax `enum` declaration.'
|
429
439
|
Enabled: true
|
@@ -693,7 +703,7 @@ Rails/NegateInclude:
|
|
693
703
|
VersionChanged: '2.9'
|
694
704
|
|
695
705
|
Rails/NotNullColumn:
|
696
|
-
Description: 'Do not add a NOT NULL column without a default value.'
|
706
|
+
Description: 'Do not add a NOT NULL column without a default value to existing tables.'
|
697
707
|
Enabled: true
|
698
708
|
VersionAdded: '0.43'
|
699
709
|
VersionChanged: '2.20'
|
@@ -1018,8 +1028,9 @@ Rails/SkipsModelValidations:
|
|
1018
1028
|
See reference for more information.
|
1019
1029
|
Reference: 'https://guides.rubyonrails.org/active_record_validations.html#skipping-validations'
|
1020
1030
|
Enabled: true
|
1031
|
+
Safe: false
|
1021
1032
|
VersionAdded: '0.47'
|
1022
|
-
VersionChanged: '2.
|
1033
|
+
VersionChanged: '2.25'
|
1023
1034
|
ForbiddenMethods:
|
1024
1035
|
- decrement!
|
1025
1036
|
- decrement_counter
|
@@ -1163,8 +1174,9 @@ Rails/UnknownEnv:
|
|
1163
1174
|
|
1164
1175
|
Rails/UnusedIgnoredColumns:
|
1165
1176
|
Description: 'Remove a column that does not exist from `ignored_columns`.'
|
1166
|
-
Enabled:
|
1177
|
+
Enabled: false
|
1167
1178
|
VersionAdded: '2.11'
|
1179
|
+
VersionChanged: '2.25'
|
1168
1180
|
Include:
|
1169
1181
|
- app/models/**/*.rb
|
1170
1182
|
|
@@ -1183,12 +1195,12 @@ Rails/Validation:
|
|
1183
1195
|
- app/models/**/*.rb
|
1184
1196
|
|
1185
1197
|
Rails/WhereEquals:
|
1186
|
-
Description: 'Pass conditions to `where` as a hash instead of manually constructing SQL.'
|
1198
|
+
Description: 'Pass conditions to `where` and `where.not` as a hash instead of manually constructing SQL.'
|
1187
1199
|
StyleGuide: 'https://rails.rubystyle.guide/#hash-conditions'
|
1188
1200
|
Enabled: 'pending'
|
1189
1201
|
SafeAutoCorrect: false
|
1190
1202
|
VersionAdded: '2.9'
|
1191
|
-
VersionChanged: '2.
|
1203
|
+
VersionChanged: '2.26'
|
1192
1204
|
|
1193
1205
|
Rails/WhereExists:
|
1194
1206
|
Description: 'Prefer `exists?(...)` over `where(...).exists?`.'
|
@@ -1221,10 +1233,21 @@ Rails/WhereNotWithMultipleConditions:
|
|
1221
1233
|
VersionAdded: '2.17'
|
1222
1234
|
VersionChanged: '2.18'
|
1223
1235
|
|
1236
|
+
Rails/WhereRange:
|
1237
|
+
Description: 'Use ranges in `where` instead of manually constructing SQL.'
|
1238
|
+
StyleGuide: 'https://rails.rubystyle.guide/#where-ranges'
|
1239
|
+
Enabled: pending
|
1240
|
+
SafeAutoCorrect: false
|
1241
|
+
VersionAdded: '2.25'
|
1242
|
+
|
1224
1243
|
# Accept `redirect_to(...) and return` and similar cases.
|
1225
1244
|
Style/AndOr:
|
1226
1245
|
EnforcedStyle: conditionals
|
1227
1246
|
|
1247
|
+
Style/CollectionCompact:
|
1248
|
+
AllowedReceivers:
|
1249
|
+
- params
|
1250
|
+
|
1228
1251
|
Style/FormatStringToken:
|
1229
1252
|
AllowedMethods:
|
1230
1253
|
- redirect
|
@@ -4,13 +4,40 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
# Common functionality for checking target rails version.
|
6
6
|
module TargetRailsVersion
|
7
|
+
# Informs the base RuboCop gem that it the Rails version is checked via `requires_gem` API,
|
8
|
+
# without needing to call this `#support_target_rails_version` method.
|
9
|
+
USES_REQUIRES_GEM_API = true
|
10
|
+
|
7
11
|
def minimum_target_rails_version(version)
|
8
|
-
|
12
|
+
if respond_to?(:requires_gem)
|
13
|
+
case version
|
14
|
+
when Integer, Float then requires_gem(TARGET_GEM_NAME, ">= #{version}")
|
15
|
+
when String then requires_gem(TARGET_GEM_NAME, version)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
# Fallback path for previous versions of RuboCop which don't support the `requires_gem` API yet.
|
19
|
+
@minimum_target_rails_version = version
|
20
|
+
end
|
9
21
|
end
|
10
22
|
|
11
23
|
def support_target_rails_version?(version)
|
12
|
-
|
24
|
+
if respond_to?(:requires_gem)
|
25
|
+
return false unless gem_requirements
|
26
|
+
|
27
|
+
gem_requirement = gem_requirements[TARGET_GEM_NAME]
|
28
|
+
return true unless gem_requirement # If we have no requirement, then we support all versions
|
29
|
+
|
30
|
+
gem_requirement.satisfied_by?(Gem::Version.new(version))
|
31
|
+
else
|
32
|
+
# Fallback path for previous versions of RuboCop which don't support the `requires_gem` API yet.
|
33
|
+
@minimum_target_rails_version <= version
|
34
|
+
end
|
13
35
|
end
|
36
|
+
|
37
|
+
# Look for `railties` instead of `rails`, to support apps that only use a subset of `rails`
|
38
|
+
# See https://github.com/rubocop/rubocop/pull/11289
|
39
|
+
TARGET_GEM_NAME = 'railties'
|
40
|
+
private_constant :TARGET_GEM_NAME
|
14
41
|
end
|
15
42
|
end
|
16
43
|
end
|
@@ -92,11 +92,7 @@ module RuboCop
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def range_with_comments(node)
|
95
|
-
|
96
|
-
# Using `RuboCop::Ext::Comment#source_range` requires RuboCop > 1.46,
|
97
|
-
# which introduces https://github.com/rubocop/rubocop/pull/11630.
|
98
|
-
ranges = [node, *processed_source.ast_with_comments[node]].map { |comment| comment.loc.expression }
|
99
|
-
# rubocop:enable InternalAffairs/LocationExpression
|
95
|
+
ranges = [node, *processed_source.ast_with_comments[node]].map(&:source_range)
|
100
96
|
ranges.reduce do |result, range|
|
101
97
|
add_range(result, range)
|
102
98
|
end
|
@@ -123,11 +123,7 @@ module RuboCop
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def inline_comment?(comment)
|
126
|
-
|
127
|
-
# Using `RuboCop::Ext::Comment#source_range` requires RuboCop > 1.46,
|
128
|
-
# which introduces https://github.com/rubocop/rubocop/pull/11630.
|
129
|
-
!comment_line?(comment.loc.expression.source_line)
|
130
|
-
# rubocop:enable InternalAffairs/LocationExpression
|
126
|
+
!comment_line?(comment.source_range.source_line)
|
131
127
|
end
|
132
128
|
|
133
129
|
def start_line_position(node)
|
@@ -5,6 +5,10 @@ module RuboCop
|
|
5
5
|
module Rails
|
6
6
|
# Checks that models subclass `ApplicationRecord` with Rails 5.0.
|
7
7
|
#
|
8
|
+
# It is a common practice to define models inside migrations in order to retain forward
|
9
|
+
# compatibility by avoiding loading any application code. And so migration files are excluded
|
10
|
+
# by default for this cop.
|
11
|
+
#
|
8
12
|
# @safety
|
9
13
|
# This cop's autocorrection is unsafe because it may let the logic from `ApplicationRecord`
|
10
14
|
# sneak into an Active Record model that is not purposed to inherit logic common among other
|
@@ -113,8 +113,10 @@ module RuboCop
|
|
113
113
|
MYSQL_COMBINABLE_ALTER_METHODS = %i[rename_column add_index remove_index].freeze
|
114
114
|
|
115
115
|
POSTGRESQL_COMBINABLE_TRANSFORMATIONS = %i[change_default].freeze
|
116
|
+
POSTGRESQL_COMBINABLE_TRANSFORMATIONS_SINCE_6_1 = %i[change_null].freeze
|
116
117
|
|
117
118
|
POSTGRESQL_COMBINABLE_ALTER_METHODS = %i[change_column_default].freeze
|
119
|
+
POSTGRESQL_COMBINABLE_ALTER_METHODS_SINCE_6_1 = %i[change_column_null].freeze
|
118
120
|
|
119
121
|
def on_def(node)
|
120
122
|
return unless support_bulk_alter?
|
@@ -138,9 +140,9 @@ module RuboCop
|
|
138
140
|
return unless support_bulk_alter?
|
139
141
|
return unless node.command?(:change_table)
|
140
142
|
return if include_bulk_options?(node)
|
141
|
-
return unless node.block_node
|
143
|
+
return unless (body = node.block_node&.body)
|
142
144
|
|
143
|
-
send_nodes = send_nodes_from_change_table_block(
|
145
|
+
send_nodes = send_nodes_from_change_table_block(body)
|
144
146
|
|
145
147
|
add_offense_for_change_table(node) if count_transformations(send_nodes) > 1
|
146
148
|
end
|
@@ -196,7 +198,9 @@ module RuboCop
|
|
196
198
|
when MYSQL
|
197
199
|
COMBINABLE_ALTER_METHODS + MYSQL_COMBINABLE_ALTER_METHODS
|
198
200
|
when POSTGRESQL
|
199
|
-
COMBINABLE_ALTER_METHODS + POSTGRESQL_COMBINABLE_ALTER_METHODS
|
201
|
+
result = COMBINABLE_ALTER_METHODS + POSTGRESQL_COMBINABLE_ALTER_METHODS
|
202
|
+
result += POSTGRESQL_COMBINABLE_ALTER_METHODS_SINCE_6_1 if target_rails_version >= 6.1
|
203
|
+
result
|
200
204
|
end
|
201
205
|
end
|
202
206
|
|
@@ -205,7 +209,9 @@ module RuboCop
|
|
205
209
|
when MYSQL
|
206
210
|
COMBINABLE_TRANSFORMATIONS + MYSQL_COMBINABLE_TRANSFORMATIONS
|
207
211
|
when POSTGRESQL
|
208
|
-
COMBINABLE_TRANSFORMATIONS + POSTGRESQL_COMBINABLE_TRANSFORMATIONS
|
212
|
+
result = COMBINABLE_TRANSFORMATIONS + POSTGRESQL_COMBINABLE_TRANSFORMATIONS
|
213
|
+
result += POSTGRESQL_COMBINABLE_TRANSFORMATIONS_SINCE_6_1 if target_rails_version >= 6.1
|
214
|
+
result
|
209
215
|
end
|
210
216
|
end
|
211
217
|
|
@@ -16,7 +16,6 @@ module RuboCop
|
|
16
16
|
# And `compact_blank!` has different implementations for `Array`, `Hash`, and
|
17
17
|
# `ActionController::Parameters`.
|
18
18
|
# `Array#compact_blank!`, `Hash#compact_blank!` are equivalent to `delete_if(&:blank?)`.
|
19
|
-
# `ActionController::Parameters#compact_blank!` is equivalent to `reject!(&:blank?)`.
|
20
19
|
# If the cop makes a mistake, autocorrected code may get unexpected behavior.
|
21
20
|
#
|
22
21
|
# @example
|
@@ -24,6 +23,10 @@ module RuboCop
|
|
24
23
|
# # bad
|
25
24
|
# collection.reject(&:blank?)
|
26
25
|
# collection.reject { |_k, v| v.blank? }
|
26
|
+
# collection.select(&:present?)
|
27
|
+
# collection.select { |_k, v| v.present? }
|
28
|
+
# collection.filter(&:present?)
|
29
|
+
# collection.filter { |_k, v| v.present? }
|
27
30
|
#
|
28
31
|
# # good
|
29
32
|
# collection.compact_blank
|
@@ -31,8 +34,8 @@ module RuboCop
|
|
31
34
|
# # bad
|
32
35
|
# collection.delete_if(&:blank?) # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
|
33
36
|
# collection.delete_if { |_k, v| v.blank? } # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
|
34
|
-
# collection.
|
35
|
-
# collection.
|
37
|
+
# collection.keep_if(&:present?) # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
|
38
|
+
# collection.keep_if { |_k, v| v.present? } # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
|
36
39
|
#
|
37
40
|
# # good
|
38
41
|
# collection.compact_blank!
|
@@ -43,25 +46,41 @@ module RuboCop
|
|
43
46
|
extend TargetRailsVersion
|
44
47
|
|
45
48
|
MSG = 'Use `%<preferred_method>s` instead.'
|
46
|
-
RESTRICT_ON_SEND = %i[reject delete_if
|
49
|
+
RESTRICT_ON_SEND = %i[reject delete_if select filter keep_if].freeze
|
50
|
+
DESTRUCTIVE_METHODS = %i[delete_if keep_if].freeze
|
47
51
|
|
48
52
|
minimum_target_rails_version 6.1
|
49
53
|
|
50
54
|
def_node_matcher :reject_with_block?, <<~PATTERN
|
51
55
|
(block
|
52
|
-
(send _ {:reject :delete_if
|
56
|
+
(send _ {:reject :delete_if})
|
53
57
|
$(args ...)
|
54
58
|
(send
|
55
59
|
$(lvar _) :blank?))
|
56
60
|
PATTERN
|
57
61
|
|
58
62
|
def_node_matcher :reject_with_block_pass?, <<~PATTERN
|
59
|
-
(send _ {:reject :delete_if
|
63
|
+
(send _ {:reject :delete_if}
|
60
64
|
(block_pass
|
61
65
|
(sym :blank?)))
|
62
66
|
PATTERN
|
63
67
|
|
68
|
+
def_node_matcher :select_with_block?, <<~PATTERN
|
69
|
+
(block
|
70
|
+
(send _ {:select :filter :keep_if})
|
71
|
+
$(args ...)
|
72
|
+
(send
|
73
|
+
$(lvar _) :present?))
|
74
|
+
PATTERN
|
75
|
+
|
76
|
+
def_node_matcher :select_with_block_pass?, <<~PATTERN
|
77
|
+
(send _ {:select :filter :keep_if}
|
78
|
+
(block-pass
|
79
|
+
(sym :present?)))
|
80
|
+
PATTERN
|
81
|
+
|
64
82
|
def on_send(node)
|
83
|
+
return if target_ruby_version < 2.6 && node.method?(:filter)
|
65
84
|
return unless bad_method?(node)
|
66
85
|
|
67
86
|
range = offense_range(node)
|
@@ -75,8 +94,10 @@ module RuboCop
|
|
75
94
|
|
76
95
|
def bad_method?(node)
|
77
96
|
return true if reject_with_block_pass?(node)
|
97
|
+
return true if select_with_block_pass?(node)
|
78
98
|
|
79
|
-
|
99
|
+
arguments, receiver_in_block = reject_with_block?(node.parent) || select_with_block?(node.parent)
|
100
|
+
if arguments
|
80
101
|
return use_single_value_block_argument?(arguments, receiver_in_block) ||
|
81
102
|
use_hash_value_block_argument?(arguments, receiver_in_block)
|
82
103
|
end
|
@@ -103,7 +124,7 @@ module RuboCop
|
|
103
124
|
end
|
104
125
|
|
105
126
|
def preferred_method(node)
|
106
|
-
|
127
|
+
DESTRUCTIVE_METHODS.include?(node.method_name) ? 'compact_blank!' : 'compact_blank'
|
107
128
|
end
|
108
129
|
end
|
109
130
|
end
|
@@ -12,10 +12,10 @@ module RuboCop
|
|
12
12
|
# The cop also reports warnings when you are using `to_time` method,
|
13
13
|
# because it doesn't know about Rails time zone either.
|
14
14
|
#
|
15
|
-
# Two styles are supported for this cop. When `EnforcedStyle` is
|
15
|
+
# Two styles are supported for this cop. When `EnforcedStyle` is `strict`
|
16
16
|
# then the Date methods `today`, `current`, `yesterday`, and `tomorrow`
|
17
17
|
# are prohibited and the usage of both `to_time`
|
18
|
-
# and
|
18
|
+
# and `to_time_in_current_zone` are reported as warning.
|
19
19
|
#
|
20
20
|
# When `EnforcedStyle` is `flexible` then only `Date.today` is prohibited.
|
21
21
|
#
|
@@ -12,6 +12,12 @@ module RuboCop
|
|
12
12
|
#
|
13
13
|
# @example
|
14
14
|
# # bad
|
15
|
+
# enum :status, [:active, :archived]
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# enum :status, { active: 0, archived: 1 }
|
19
|
+
#
|
20
|
+
# # bad
|
15
21
|
# enum status: [:active, :archived]
|
16
22
|
#
|
17
23
|
# # good
|
@@ -23,7 +29,11 @@ module RuboCop
|
|
23
29
|
MSG = 'Enum defined as an array found in `%<enum>s` enum declaration. Use hash syntax instead.'
|
24
30
|
RESTRICT_ON_SEND = %i[enum].freeze
|
25
31
|
|
26
|
-
def_node_matcher :
|
32
|
+
def_node_matcher :enum_with_array?, <<~PATTERN
|
33
|
+
(send nil? :enum $_ ${array} ...)
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def_node_matcher :enum_with_old_syntax?, <<~PATTERN
|
27
37
|
(send nil? :enum (hash $...))
|
28
38
|
PATTERN
|
29
39
|
|
@@ -32,17 +42,19 @@ module RuboCop
|
|
32
42
|
PATTERN
|
33
43
|
|
34
44
|
def on_send(node)
|
35
|
-
|
45
|
+
target_rails_version >= 7.0 && enum_with_array?(node) do |key, array|
|
46
|
+
add_offense(array, message: message(key)) do |corrector|
|
47
|
+
corrector.replace(array, build_hash(array))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
enum_with_old_syntax?(node) do |pairs|
|
36
52
|
pairs.each do |pair|
|
37
53
|
key, array = array_pair?(pair)
|
38
54
|
next unless key
|
39
55
|
|
40
|
-
add_offense(array, message:
|
41
|
-
|
42
|
-
"#{source(elem)} => #{index}"
|
43
|
-
end.join(', ')
|
44
|
-
|
45
|
-
corrector.replace(array, "{#{hash}}")
|
56
|
+
add_offense(array, message: message(key)) do |corrector|
|
57
|
+
corrector.replace(array, build_hash(array))
|
46
58
|
end
|
47
59
|
end
|
48
60
|
end
|
@@ -50,6 +62,10 @@ module RuboCop
|
|
50
62
|
|
51
63
|
private
|
52
64
|
|
65
|
+
def message(key)
|
66
|
+
format(MSG, enum: enum_name(key))
|
67
|
+
end
|
68
|
+
|
53
69
|
def enum_name(key)
|
54
70
|
case key.type
|
55
71
|
when :sym, :str
|
@@ -69,6 +85,13 @@ module RuboCop
|
|
69
85
|
elem.source
|
70
86
|
end
|
71
87
|
end
|
88
|
+
|
89
|
+
def build_hash(array)
|
90
|
+
hash = array.children.each_with_index.map do |elem, index|
|
91
|
+
"#{source(elem)} => #{index}"
|
92
|
+
end.join(', ')
|
93
|
+
"{#{hash}}"
|
94
|
+
end
|
72
95
|
end
|
73
96
|
end
|
74
97
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Looks for enums written with keyword arguments syntax.
|
7
|
+
#
|
8
|
+
# Defining enums with keyword arguments syntax is deprecated and will be removed in Rails 8.0.
|
9
|
+
# Positional arguments should be used instead:
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# enum status: { active: 0, archived: 1 }, _prefix: true
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# enum :status, { active: 0, archived: 1 }, prefix: true
|
17
|
+
#
|
18
|
+
class EnumSyntax < Base
|
19
|
+
extend AutoCorrector
|
20
|
+
extend TargetRubyVersion
|
21
|
+
extend TargetRailsVersion
|
22
|
+
|
23
|
+
minimum_target_ruby_version 3.0
|
24
|
+
minimum_target_rails_version 7.0
|
25
|
+
|
26
|
+
MSG = 'Enum defined with keyword arguments in `%<enum>s` enum declaration. Use positional arguments instead.'
|
27
|
+
MSG_OPTIONS = 'Enum defined with deprecated options in `%<enum>s` enum declaration. Remove the `_` prefix.'
|
28
|
+
RESTRICT_ON_SEND = %i[enum].freeze
|
29
|
+
|
30
|
+
# From https://github.com/rails/rails/blob/v7.2.1/activerecord/lib/active_record/enum.rb#L231
|
31
|
+
OPTION_NAMES = %w[prefix suffix scopes default instance_methods].freeze
|
32
|
+
UNDERSCORED_OPTION_NAMES = OPTION_NAMES.map { |option| "_#{option}" }.freeze
|
33
|
+
|
34
|
+
def_node_matcher :enum?, <<~PATTERN
|
35
|
+
(send nil? :enum (hash $...))
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def_node_matcher :enum_with_options?, <<~PATTERN
|
39
|
+
(send nil? :enum $_ ${array hash} $_)
|
40
|
+
PATTERN
|
41
|
+
|
42
|
+
def on_send(node)
|
43
|
+
check_and_correct_keyword_args(node)
|
44
|
+
check_enum_options(node)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def check_and_correct_keyword_args(node)
|
50
|
+
enum?(node) do |pairs|
|
51
|
+
pairs.each do |pair|
|
52
|
+
next if option_key?(pair)
|
53
|
+
|
54
|
+
correct_keyword_args(node, pair.key, pair.value, pairs[1..])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_enum_options(node)
|
60
|
+
enum_with_options?(node) do |key, _, options|
|
61
|
+
options.children.each do |option|
|
62
|
+
next unless option_key?(option)
|
63
|
+
|
64
|
+
add_offense(option.key, message: format(MSG_OPTIONS, enum: enum_name_value(key))) do |corrector|
|
65
|
+
corrector.replace(option.key, option.key.source.delete_prefix('_'))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def correct_keyword_args(node, key, values, options)
|
72
|
+
add_offense(values, message: format(MSG, enum: enum_name_value(key))) do |corrector|
|
73
|
+
# TODO: Multi-line autocorrect could be implemented in the future.
|
74
|
+
next if multiple_enum_definitions?(node)
|
75
|
+
|
76
|
+
preferred_syntax = "enum #{enum_name(key)}, #{values.source}#{correct_options(options)}"
|
77
|
+
|
78
|
+
corrector.replace(node, preferred_syntax)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def multiple_enum_definitions?(node)
|
83
|
+
keys = node.first_argument.keys.map { |key| key.source.delete_prefix('_') }
|
84
|
+
filterred_keys = keys.filter { |key| !OPTION_NAMES.include?(key) }
|
85
|
+
filterred_keys.size >= 2
|
86
|
+
end
|
87
|
+
|
88
|
+
def enum_name_value(key)
|
89
|
+
case key.type
|
90
|
+
when :sym, :str
|
91
|
+
key.value
|
92
|
+
else
|
93
|
+
key.source
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def enum_name(elem)
|
98
|
+
case elem.type
|
99
|
+
when :str
|
100
|
+
elem.value.dump
|
101
|
+
when :sym
|
102
|
+
elem.value.inspect
|
103
|
+
else
|
104
|
+
elem.source
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def option_key?(pair)
|
109
|
+
UNDERSCORED_OPTION_NAMES.include?(pair.key.source)
|
110
|
+
end
|
111
|
+
|
112
|
+
def correct_options(options)
|
113
|
+
corrected_options = options.map do |pair|
|
114
|
+
name = if pair.key.source[0] == '_'
|
115
|
+
pair.key.source[1..]
|
116
|
+
else
|
117
|
+
pair.key.source
|
118
|
+
end
|
119
|
+
|
120
|
+
"#{name}: #{pair.value.source}"
|
121
|
+
end.join(', ')
|
122
|
+
|
123
|
+
", #{corrected_options}" unless corrected_options.empty?
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -7,6 +7,18 @@ module RuboCop
|
|
7
7
|
#
|
8
8
|
# @example
|
9
9
|
# # bad
|
10
|
+
# enum :status, { active: 0, archived: 0 }
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# enum :status, { active: 0, archived: 1 }
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# enum :status, [:active, :archived, :active]
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# enum :status, [:active, :archived]
|
20
|
+
#
|
21
|
+
# # bad
|
10
22
|
# enum status: { active: 0, archived: 0 }
|
11
23
|
#
|
12
24
|
# # good
|
@@ -24,6 +36,10 @@ module RuboCop
|
|
24
36
|
RESTRICT_ON_SEND = %i[enum].freeze
|
25
37
|
|
26
38
|
def_node_matcher :enum?, <<~PATTERN
|
39
|
+
(send nil? :enum $_ ${array hash} ...)
|
40
|
+
PATTERN
|
41
|
+
|
42
|
+
def_node_matcher :enum_with_old_syntax?, <<~PATTERN
|
27
43
|
(send nil? :enum (hash $...))
|
28
44
|
PATTERN
|
29
45
|
|
@@ -32,15 +48,17 @@ module RuboCop
|
|
32
48
|
PATTERN
|
33
49
|
|
34
50
|
def on_send(node)
|
35
|
-
enum?(node) do |
|
51
|
+
enum?(node) do |key, args|
|
52
|
+
consecutive_duplicates(args.values).each do |item|
|
53
|
+
add_offense(item, message: message(key, item))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
enum_with_old_syntax?(node) do |pairs|
|
36
58
|
pairs.each do |pair|
|
37
59
|
enum_values(pair) do |key, args|
|
38
|
-
|
39
|
-
|
40
|
-
next unless duplicates?(items)
|
41
|
-
|
42
|
-
consecutive_duplicates(items).each do |item|
|
43
|
-
add_offense(item, message: format(MSG, value: item.source, enum: enum_name(key)))
|
60
|
+
consecutive_duplicates(args.values).each do |item|
|
61
|
+
add_offense(item, message: message(key, item))
|
44
62
|
end
|
45
63
|
end
|
46
64
|
end
|
@@ -57,6 +75,10 @@ module RuboCop
|
|
57
75
|
key.source
|
58
76
|
end
|
59
77
|
end
|
78
|
+
|
79
|
+
def message(key, item)
|
80
|
+
format(MSG, value: item.source, enum: enum_name(key))
|
81
|
+
end
|
60
82
|
end
|
61
83
|
end
|
62
84
|
end
|
@@ -97,7 +97,7 @@ module RuboCop
|
|
97
97
|
return unless node.arguments.any? { |e| rails_root_nodes?(e) }
|
98
98
|
|
99
99
|
register_offense(node, require_to_s: true) do |corrector|
|
100
|
-
autocorrect_file_join(corrector, node)
|
100
|
+
autocorrect_file_join(corrector, node) unless node.first_argument.array_type?
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|