rubocop-rails 2.25.1 → 2.26.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/config/default.yml +14 -2
- data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +5 -5
- data/lib/rubocop/cop/rails/bulk_change_table.rb +2 -2
- data/lib/rubocop/cop/rails/compact_blank.rb +25 -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 +126 -0
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +1 -1
- 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/redundant_presence_validation_on_belongs_to.rb +9 -0
- data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
- data/lib/rubocop/cop/rails/root_pathname_methods.rb +5 -5
- data/lib/rubocop/cop/rails/validation.rb +1 -1
- data/lib/rubocop/cop/rails/where_equals.rb +22 -11
- data/lib/rubocop/cop/rails/where_not.rb +5 -5
- data/lib/rubocop/cop/rails_cops.rb +1 -0
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c18cefb961b3100a871e2411c7a029244eda6998d793e3ece62aef1adcd4c571
|
4
|
+
data.tar.gz: 24cde448ef91e689e05a2357c1aa0cbcc4d61630f1bb6e7fcaf8999ff7678105
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7be18be7c11552f4a8d460e4833b57881ec3244d3abc7509ef3f9aa78533e3ff77e0ac60cd1737c608c8a724aed4b37fd1faed06ccb689aabf1e0a21970beef6
|
7
|
+
data.tar.gz: fb09716c5cb563cff4eaa33e68f0657424dce2650a9c7da8e37e6876ee8ee12a942ef2b096cb5604ee5a457a7f951dbd2198d290d0f5e0486c04b36e5a2679bc
|
data/config/default.yml
CHANGED
@@ -424,6 +424,14 @@ Rails/EnumHash:
|
|
424
424
|
Include:
|
425
425
|
- app/models/**/*.rb
|
426
426
|
|
427
|
+
Rails/EnumSyntax:
|
428
|
+
Description: 'Use positional arguments over keyword arguments when defining enums.'
|
429
|
+
Enabled: pending
|
430
|
+
Severity: warning
|
431
|
+
VersionAdded: '2.26'
|
432
|
+
Include:
|
433
|
+
- app/models/**/*.rb
|
434
|
+
|
427
435
|
Rails/EnumUniqueness:
|
428
436
|
Description: 'Avoid duplicate integers in hash-syntax `enum` declaration.'
|
429
437
|
Enabled: true
|
@@ -1185,12 +1193,12 @@ Rails/Validation:
|
|
1185
1193
|
- app/models/**/*.rb
|
1186
1194
|
|
1187
1195
|
Rails/WhereEquals:
|
1188
|
-
Description: 'Pass conditions to `where` as a hash instead of manually constructing SQL.'
|
1196
|
+
Description: 'Pass conditions to `where` and `where.not` as a hash instead of manually constructing SQL.'
|
1189
1197
|
StyleGuide: 'https://rails.rubystyle.guide/#hash-conditions'
|
1190
1198
|
Enabled: 'pending'
|
1191
1199
|
SafeAutoCorrect: false
|
1192
1200
|
VersionAdded: '2.9'
|
1193
|
-
VersionChanged: '2.
|
1201
|
+
VersionChanged: '2.26'
|
1194
1202
|
|
1195
1203
|
Rails/WhereExists:
|
1196
1204
|
Description: 'Prefer `exists?(...)` over `where(...).exists?`.'
|
@@ -1234,6 +1242,10 @@ Rails/WhereRange:
|
|
1234
1242
|
Style/AndOr:
|
1235
1243
|
EnforcedStyle: conditionals
|
1236
1244
|
|
1245
|
+
Style/CollectionCompact:
|
1246
|
+
AllowedReceivers:
|
1247
|
+
- params
|
1248
|
+
|
1237
1249
|
Style/FormatStringToken:
|
1238
1250
|
AllowedMethods:
|
1239
1251
|
- redirect
|
@@ -72,13 +72,13 @@ module RuboCop
|
|
72
72
|
if (node = context.each_ancestor(:if, :rescue).first)
|
73
73
|
return false if use_redirect_to?(context)
|
74
74
|
|
75
|
-
context = node
|
76
|
-
elsif context.right_siblings.empty?
|
77
|
-
return true
|
75
|
+
context = node.rescue_type? ? node.parent : node
|
78
76
|
end
|
79
|
-
context = context.right_siblings
|
80
77
|
|
81
|
-
context.
|
78
|
+
siblings = context.right_siblings
|
79
|
+
return true if siblings.empty?
|
80
|
+
|
81
|
+
siblings.compact.any? do |render_candidate|
|
82
82
|
render?(render_candidate)
|
83
83
|
end
|
84
84
|
end
|
@@ -140,9 +140,9 @@ module RuboCop
|
|
140
140
|
return unless support_bulk_alter?
|
141
141
|
return unless node.command?(:change_table)
|
142
142
|
return if include_bulk_options?(node)
|
143
|
-
return unless node.block_node
|
143
|
+
return unless (body = node.block_node&.body)
|
144
144
|
|
145
|
-
send_nodes = send_nodes_from_change_table_block(
|
145
|
+
send_nodes = send_nodes_from_change_table_block(body)
|
146
146
|
|
147
147
|
add_offense_for_change_table(node) if count_transformations(send_nodes) > 1
|
148
148
|
end
|
@@ -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,8 @@ 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? }
|
27
28
|
#
|
28
29
|
# # good
|
29
30
|
# collection.compact_blank
|
@@ -31,8 +32,8 @@ module RuboCop
|
|
31
32
|
# # bad
|
32
33
|
# collection.delete_if(&:blank?) # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
|
33
34
|
# collection.delete_if { |_k, v| v.blank? } # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
|
34
|
-
# collection.
|
35
|
-
# collection.
|
35
|
+
# collection.keep_if(&:present?) # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
|
36
|
+
# collection.keep_if { |_k, v| v.present? } # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
|
36
37
|
#
|
37
38
|
# # good
|
38
39
|
# collection.compact_blank!
|
@@ -43,24 +44,38 @@ module RuboCop
|
|
43
44
|
extend TargetRailsVersion
|
44
45
|
|
45
46
|
MSG = 'Use `%<preferred_method>s` instead.'
|
46
|
-
RESTRICT_ON_SEND = %i[reject delete_if
|
47
|
+
RESTRICT_ON_SEND = %i[reject delete_if select keep_if].freeze
|
47
48
|
|
48
49
|
minimum_target_rails_version 6.1
|
49
50
|
|
50
51
|
def_node_matcher :reject_with_block?, <<~PATTERN
|
51
52
|
(block
|
52
|
-
(send _ {:reject :delete_if
|
53
|
+
(send _ {:reject :delete_if})
|
53
54
|
$(args ...)
|
54
55
|
(send
|
55
56
|
$(lvar _) :blank?))
|
56
57
|
PATTERN
|
57
58
|
|
58
59
|
def_node_matcher :reject_with_block_pass?, <<~PATTERN
|
59
|
-
(send _ {:reject :delete_if
|
60
|
+
(send _ {:reject :delete_if}
|
60
61
|
(block_pass
|
61
62
|
(sym :blank?)))
|
62
63
|
PATTERN
|
63
64
|
|
65
|
+
def_node_matcher :select_with_block?, <<~PATTERN
|
66
|
+
(block
|
67
|
+
(send _ {:select :keep_if})
|
68
|
+
$(args ...)
|
69
|
+
(send
|
70
|
+
$(lvar _) :present?))
|
71
|
+
PATTERN
|
72
|
+
|
73
|
+
def_node_matcher :select_with_block_pass?, <<~PATTERN
|
74
|
+
(send _ {:select :keep_if}
|
75
|
+
(block-pass
|
76
|
+
(sym :present?)))
|
77
|
+
PATTERN
|
78
|
+
|
64
79
|
def on_send(node)
|
65
80
|
return unless bad_method?(node)
|
66
81
|
|
@@ -75,8 +90,10 @@ module RuboCop
|
|
75
90
|
|
76
91
|
def bad_method?(node)
|
77
92
|
return true if reject_with_block_pass?(node)
|
93
|
+
return true if select_with_block_pass?(node)
|
78
94
|
|
79
|
-
|
95
|
+
arguments, receiver_in_block = reject_with_block?(node.parent) || select_with_block?(node.parent)
|
96
|
+
if arguments
|
80
97
|
return use_single_value_block_argument?(arguments, receiver_in_block) ||
|
81
98
|
use_hash_value_block_argument?(arguments, receiver_in_block)
|
82
99
|
end
|
@@ -103,7 +120,7 @@ module RuboCop
|
|
103
120
|
end
|
104
121
|
|
105
122
|
def preferred_method(node)
|
106
|
-
node.method?(:reject) ? 'compact_blank' : 'compact_blank!'
|
123
|
+
node.method?(:reject) || node.method?(:select) ? 'compact_blank' : 'compact_blank!'
|
107
124
|
end
|
108
125
|
end
|
109
126
|
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,126 @@
|
|
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 TargetRailsVersion
|
21
|
+
|
22
|
+
minimum_target_rails_version 7.0
|
23
|
+
|
24
|
+
MSG = 'Enum defined with keyword arguments in `%<enum>s` enum declaration. Use positional arguments instead.'
|
25
|
+
MSG_OPTIONS = 'Enum defined with deprecated options in `%<enum>s` enum declaration. Remove the `_` prefix.'
|
26
|
+
RESTRICT_ON_SEND = %i[enum].freeze
|
27
|
+
OPTION_NAMES = %w[prefix suffix scopes default].freeze
|
28
|
+
|
29
|
+
def_node_matcher :enum?, <<~PATTERN
|
30
|
+
(send nil? :enum (hash $...))
|
31
|
+
PATTERN
|
32
|
+
|
33
|
+
def_node_matcher :enum_with_options?, <<~PATTERN
|
34
|
+
(send nil? :enum $_ ${array hash} $_)
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
def_node_matcher :enum_values, <<~PATTERN
|
38
|
+
(pair $_ ${array hash})
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def_node_matcher :enum_options, <<~PATTERN
|
42
|
+
(pair $_ $_)
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
def on_send(node)
|
46
|
+
check_and_correct_keyword_args(node)
|
47
|
+
check_enum_options(node)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def check_and_correct_keyword_args(node)
|
53
|
+
enum?(node) do |pairs|
|
54
|
+
pairs.each do |pair|
|
55
|
+
key, values = enum_values(pair)
|
56
|
+
next unless key
|
57
|
+
|
58
|
+
correct_keyword_args(node, key, values, pairs[1..])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def check_enum_options(node)
|
64
|
+
enum_with_options?(node) do |key, _, options|
|
65
|
+
options.children.each do |option|
|
66
|
+
name, = enum_options(option)
|
67
|
+
|
68
|
+
add_offense(name, message: format(MSG_OPTIONS, enum: enum_name_value(key))) if name.source[0] == '_'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def correct_keyword_args(node, key, values, options)
|
74
|
+
add_offense(values, message: format(MSG, enum: enum_name_value(key))) do |corrector|
|
75
|
+
# TODO: Multi-line autocorrect could be implemented in the future.
|
76
|
+
next if multiple_enum_definitions?(node)
|
77
|
+
|
78
|
+
preferred_syntax = "enum #{enum_name(key)}, #{values.source}#{correct_options(options)}"
|
79
|
+
|
80
|
+
corrector.replace(node, preferred_syntax)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def multiple_enum_definitions?(node)
|
85
|
+
keys = node.first_argument.keys.map { |key| key.source.delete_prefix('_') }
|
86
|
+
filterred_keys = keys.filter { |key| !OPTION_NAMES.include?(key) }
|
87
|
+
filterred_keys.size >= 2
|
88
|
+
end
|
89
|
+
|
90
|
+
def enum_name_value(key)
|
91
|
+
case key.type
|
92
|
+
when :sym, :str
|
93
|
+
key.value
|
94
|
+
else
|
95
|
+
key.source
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def enum_name(elem)
|
100
|
+
case elem.type
|
101
|
+
when :str
|
102
|
+
elem.value.dump
|
103
|
+
when :sym
|
104
|
+
elem.value.inspect
|
105
|
+
else
|
106
|
+
elem.source
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def correct_options(options)
|
111
|
+
corrected_options = options.map do |pair|
|
112
|
+
name = if pair.key.source[0] == '_'
|
113
|
+
pair.key.source[1..]
|
114
|
+
else
|
115
|
+
pair.key.source
|
116
|
+
end
|
117
|
+
|
118
|
+
"#{name}: #{pair.value.source}"
|
119
|
+
end.join(', ')
|
120
|
+
|
121
|
+
", #{corrected_options}" unless corrected_options.empty?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
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
|
@@ -7,17 +7,26 @@ module RuboCop
|
|
7
7
|
# and can be replaced with `select`.
|
8
8
|
#
|
9
9
|
# Since `pluck` is an eager method and hits the database immediately,
|
10
|
-
# using `select` helps to avoid additional database queries
|
10
|
+
# using `select` helps to avoid additional database queries by running as
|
11
|
+
# a subquery.
|
11
12
|
#
|
12
|
-
# This cop has two
|
13
|
-
#
|
14
|
-
# (
|
13
|
+
# This cop has two modes of enforcement. When the `EnforcedStyle` is set
|
14
|
+
# to `conservative` (the default), only calls to `pluck` on a constant
|
15
|
+
# (e.g. a model class) within `where` are considered offenses.
|
15
16
|
#
|
16
17
|
# @safety
|
17
|
-
# When
|
18
|
-
# `where`
|
19
|
-
#
|
20
|
-
# `ActiveRecord::Relation` instance
|
18
|
+
# When `EnforcedStyle` is set to `aggressive`, all calls to `pluck`
|
19
|
+
# within `where` are considered offenses. This might lead to false
|
20
|
+
# positives because the check cannot distinguish between calls to
|
21
|
+
# `pluck` on an `ActiveRecord::Relation` instance and calls to `pluck`
|
22
|
+
# on an `Array` instance.
|
23
|
+
#
|
24
|
+
# Additionally, when using a subquery with the SQL `IN` operator,
|
25
|
+
# databases like PostgreSQL and MySQL can't optimize complex queries as
|
26
|
+
# well. They need to scan all records of the outer table against the
|
27
|
+
# subquery result sequentially, rather than using an index. This can
|
28
|
+
# cause significant performance issues compared to writing the query
|
29
|
+
# differently or using `pluck`.
|
21
30
|
#
|
22
31
|
# @example
|
23
32
|
# # bad
|
@@ -10,25 +10,39 @@ module RuboCop
|
|
10
10
|
# # bad
|
11
11
|
# 3.day.ago
|
12
12
|
# 1.months.ago
|
13
|
+
# 5.megabyte
|
14
|
+
# 1.gigabyte
|
13
15
|
#
|
14
16
|
# # good
|
15
17
|
# 3.days.ago
|
16
18
|
# 1.month.ago
|
19
|
+
# 5.megabytes
|
20
|
+
# 1.gigabyte
|
17
21
|
class PluralizationGrammar < Base
|
18
22
|
extend AutoCorrector
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
SINGULAR_METHODS = {
|
25
|
+
second: :seconds,
|
26
|
+
minute: :minutes,
|
27
|
+
hour: :hours,
|
28
|
+
day: :days,
|
29
|
+
week: :weeks,
|
30
|
+
fortnight: :fortnights,
|
31
|
+
month: :months,
|
32
|
+
year: :years,
|
33
|
+
byte: :bytes,
|
34
|
+
kilobyte: :kilobytes,
|
35
|
+
megabyte: :megabytes,
|
36
|
+
gigabyte: :gigabytes,
|
37
|
+
terabyte: :terabytes,
|
38
|
+
petabyte: :petabytes,
|
39
|
+
exabyte: :exabytes,
|
40
|
+
zettabyte: :zettabytes
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
RESTRICT_ON_SEND = SINGULAR_METHODS.keys + SINGULAR_METHODS.values
|
44
|
+
|
45
|
+
PLURAL_METHODS = SINGULAR_METHODS.invert.freeze
|
32
46
|
|
33
47
|
MSG = 'Prefer `%<number>s.%<correct>s`.'
|
34
48
|
|
@@ -86,15 +100,15 @@ module RuboCop
|
|
86
100
|
end
|
87
101
|
|
88
102
|
def pluralize(method_name)
|
89
|
-
|
103
|
+
SINGULAR_METHODS.fetch(method_name.to_sym).to_s
|
90
104
|
end
|
91
105
|
|
92
106
|
def singularize(method_name)
|
93
|
-
|
107
|
+
PLURAL_METHODS.fetch(method_name.to_sym).to_s
|
94
108
|
end
|
95
109
|
|
96
110
|
def duration_method?(method_name)
|
97
|
-
|
111
|
+
SINGULAR_METHODS.key?(method_name) || PLURAL_METHODS.key?(method_name)
|
98
112
|
end
|
99
113
|
end
|
100
114
|
end
|
@@ -39,6 +39,9 @@ module RuboCop
|
|
39
39
|
MSG = 'Remove explicit presence validation for %<association>s.'
|
40
40
|
RESTRICT_ON_SEND = %i[validates].freeze
|
41
41
|
|
42
|
+
# From https://github.com/rails/rails/blob/7a0bf93b9dd291c7f61121a41b3a813ac8857e6a/activemodel/lib/active_model/validations/validates.rb#L157-L159
|
43
|
+
NON_VALIDATION_OPTIONS = %i[if unless on allow_blank allow_nil strict].freeze
|
44
|
+
|
42
45
|
minimum_target_rails_version 5.0
|
43
46
|
|
44
47
|
# @!method presence_validation?(node)
|
@@ -170,6 +173,12 @@ module RuboCop
|
|
170
173
|
|
171
174
|
def on_send(node)
|
172
175
|
presence_validation?(node) do |all_keys, options, presence|
|
176
|
+
# If presence is the only validation option and other non-validation options
|
177
|
+
# are present, removing it will cause rails to error.
|
178
|
+
used_option_keys = options.keys.select(&:sym_type?).map(&:value)
|
179
|
+
remaining_validations = used_option_keys - NON_VALIDATION_OPTIONS - [:presence]
|
180
|
+
return if remaining_validations.none? && options.keys.length > 1
|
181
|
+
|
173
182
|
keys = non_optional_belongs_to(node.parent, all_keys)
|
174
183
|
return if keys.none?
|
175
184
|
|
@@ -53,9 +53,12 @@ module RuboCop
|
|
53
53
|
node.pairs.find { |p| p.key.value.to_sym == :content_type }
|
54
54
|
end
|
55
55
|
|
56
|
-
def compatible_content_type?(
|
57
|
-
|
58
|
-
|
56
|
+
def compatible_content_type?(pair_node)
|
57
|
+
if pair_node.nil?
|
58
|
+
!cop_config['ContentTypeCompatibility']
|
59
|
+
elsif pair_node.value.respond_to?(:value)
|
60
|
+
pair_node.value.value == 'text/plain'
|
61
|
+
end
|
59
62
|
end
|
60
63
|
|
61
64
|
def replacement(rest_options, option_value)
|
@@ -38,7 +38,7 @@ module RuboCop
|
|
38
38
|
|
39
39
|
MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
|
40
40
|
|
41
|
-
DIR_GLOB_METHODS = %i[glob].to_set.freeze
|
41
|
+
DIR_GLOB_METHODS = %i[[] glob].to_set.freeze
|
42
42
|
|
43
43
|
DIR_NON_GLOB_METHODS = %i[
|
44
44
|
children
|
@@ -171,7 +171,7 @@ module RuboCop
|
|
171
171
|
|
172
172
|
def_node_matcher :dir_glob?, <<~PATTERN
|
173
173
|
(send
|
174
|
-
(const {cbase nil?} :Dir)
|
174
|
+
(const {cbase nil?} :Dir) DIR_GLOB_METHODS ...)
|
175
175
|
PATTERN
|
176
176
|
|
177
177
|
def_node_matcher :rails_root_pathname?, <<~PATTERN
|
@@ -190,7 +190,7 @@ module RuboCop
|
|
190
190
|
evidence(node) do |method, path, args, rails_root|
|
191
191
|
add_offense(node, message: format(MSG, method: method, rails_root: rails_root.source)) do |corrector|
|
192
192
|
replacement = if dir_glob?(node)
|
193
|
-
build_path_glob_replacement(path
|
193
|
+
build_path_glob_replacement(path)
|
194
194
|
else
|
195
195
|
build_path_replacement(path, method, args)
|
196
196
|
end
|
@@ -217,12 +217,12 @@ module RuboCop
|
|
217
217
|
end
|
218
218
|
end
|
219
219
|
|
220
|
-
def build_path_glob_replacement(path
|
220
|
+
def build_path_glob_replacement(path)
|
221
221
|
receiver = range_between(path.source_range.begin_pos, path.children.first.loc.selector.end_pos).source
|
222
222
|
|
223
223
|
argument = path.arguments.one? ? path.first_argument.source : join_arguments(path.arguments)
|
224
224
|
|
225
|
-
"#{receiver}
|
225
|
+
"#{receiver}.glob(#{argument})"
|
226
226
|
end
|
227
227
|
|
228
228
|
def build_path_replacement(path, method, args)
|
@@ -59,11 +59,11 @@ module RuboCop
|
|
59
59
|
|
60
60
|
def on_send(node)
|
61
61
|
return if node.receiver
|
62
|
+
return unless (last_argument = node.last_argument)
|
62
63
|
|
63
64
|
range = node.loc.selector
|
64
65
|
|
65
66
|
add_offense(range, message: message(node)) do |corrector|
|
66
|
-
last_argument = node.last_argument
|
67
67
|
return if !last_argument.literal? && !last_argument.splat_type? && !frozen_array_argument?(last_argument)
|
68
68
|
|
69
69
|
corrector.replace(range, 'validates')
|
@@ -4,7 +4,8 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
6
|
# Identifies places where manually constructed SQL
|
7
|
-
# in `where` can be replaced with
|
7
|
+
# in `where` and `where.not` can be replaced with
|
8
|
+
# `where(attribute: value)` and `where.not(attribute: value)`.
|
8
9
|
#
|
9
10
|
# @safety
|
10
11
|
# This cop's autocorrection is unsafe because is may change SQL.
|
@@ -13,6 +14,7 @@ module RuboCop
|
|
13
14
|
# @example
|
14
15
|
# # bad
|
15
16
|
# User.where('name = ?', 'Gabe')
|
17
|
+
# User.where.not('name = ?', 'Gabe')
|
16
18
|
# User.where('name = :name', name: 'Gabe')
|
17
19
|
# User.where('name IS NULL')
|
18
20
|
# User.where('name IN (?)', ['john', 'jane'])
|
@@ -21,6 +23,7 @@ module RuboCop
|
|
21
23
|
#
|
22
24
|
# # good
|
23
25
|
# User.where(name: 'Gabe')
|
26
|
+
# User.where.not(name: 'Gabe')
|
24
27
|
# User.where(name: nil)
|
25
28
|
# User.where(name: ['john', 'jane'])
|
26
29
|
# User.where(users: { name: 'Gabe' })
|
@@ -29,25 +32,27 @@ module RuboCop
|
|
29
32
|
extend AutoCorrector
|
30
33
|
|
31
34
|
MSG = 'Use `%<good_method>s` instead of manually constructing SQL.'
|
32
|
-
RESTRICT_ON_SEND = %i[where].freeze
|
35
|
+
RESTRICT_ON_SEND = %i[where not].freeze
|
33
36
|
|
34
37
|
def_node_matcher :where_method_call?, <<~PATTERN
|
35
38
|
{
|
36
|
-
(call _ :where (array $str_type? $_ ?))
|
37
|
-
(call _ :where $str_type? $_ ?)
|
39
|
+
(call _ {:where :not} (array $str_type? $_ ?))
|
40
|
+
(call _ {:where :not} $str_type? $_ ?)
|
38
41
|
}
|
39
42
|
PATTERN
|
40
43
|
|
41
44
|
def on_send(node)
|
45
|
+
return if node.method?(:not) && !where_not?(node)
|
46
|
+
|
42
47
|
where_method_call?(node) do |template_node, value_node|
|
43
48
|
value_node = value_node.first
|
44
49
|
|
45
50
|
range = offense_range(node)
|
46
51
|
|
47
|
-
|
48
|
-
return unless
|
52
|
+
column, value = extract_column_and_value(template_node, value_node)
|
53
|
+
return unless value
|
49
54
|
|
50
|
-
good_method = build_good_method(
|
55
|
+
good_method = build_good_method(node.method_name, column, value)
|
51
56
|
message = format(MSG, good_method: good_method)
|
52
57
|
|
53
58
|
add_offense(range, message: message) do |corrector|
|
@@ -73,7 +78,7 @@ module RuboCop
|
|
73
78
|
value =
|
74
79
|
case template_node.value
|
75
80
|
when EQ_ANONYMOUS_RE, IN_ANONYMOUS_RE
|
76
|
-
value_node
|
81
|
+
value_node&.source
|
77
82
|
when EQ_NAMED_RE, IN_NAMED_RE
|
78
83
|
return unless value_node&.hash_type?
|
79
84
|
|
@@ -88,15 +93,21 @@ module RuboCop
|
|
88
93
|
[Regexp.last_match(1), value]
|
89
94
|
end
|
90
95
|
|
91
|
-
def build_good_method(column, value)
|
96
|
+
def build_good_method(method_name, column, value)
|
92
97
|
if column.include?('.')
|
93
98
|
table, column = column.split('.')
|
94
99
|
|
95
|
-
"
|
100
|
+
"#{method_name}(#{table}: { #{column}: #{value} })"
|
96
101
|
else
|
97
|
-
"
|
102
|
+
"#{method_name}(#{column}: #{value})"
|
98
103
|
end
|
99
104
|
end
|
105
|
+
|
106
|
+
def where_not?(node)
|
107
|
+
return false unless (receiver = node.receiver)
|
108
|
+
|
109
|
+
receiver.send_type? && receiver.method?(:where)
|
110
|
+
end
|
100
111
|
end
|
101
112
|
end
|
102
113
|
end
|
@@ -43,10 +43,10 @@ module RuboCop
|
|
43
43
|
|
44
44
|
range = offense_range(node)
|
45
45
|
|
46
|
-
|
47
|
-
return unless
|
46
|
+
column, value = extract_column_and_value(template_node, value_node)
|
47
|
+
return unless value
|
48
48
|
|
49
|
-
good_method = build_good_method(node.loc.dot&.source,
|
49
|
+
good_method = build_good_method(node.loc.dot&.source, column, value)
|
50
50
|
message = format(MSG, good_method: good_method)
|
51
51
|
|
52
52
|
add_offense(range, message: message) do |corrector|
|
@@ -72,9 +72,9 @@ module RuboCop
|
|
72
72
|
value =
|
73
73
|
case template_node.value
|
74
74
|
when NOT_EQ_ANONYMOUS_RE, NOT_IN_ANONYMOUS_RE
|
75
|
-
value_node
|
75
|
+
value_node&.source
|
76
76
|
when NOT_EQ_NAMED_RE, NOT_IN_NAMED_RE
|
77
|
-
return unless value_node
|
77
|
+
return unless value_node&.hash_type?
|
78
78
|
|
79
79
|
pair = value_node.pairs.find { |p| p.key.value.to_sym == Regexp.last_match(2).to_sym }
|
80
80
|
pair.value.source
|
@@ -46,6 +46,7 @@ require_relative 'rails/duration_arithmetic'
|
|
46
46
|
require_relative 'rails/dynamic_find_by'
|
47
47
|
require_relative 'rails/eager_evaluation_log_message'
|
48
48
|
require_relative 'rails/enum_hash'
|
49
|
+
require_relative 'rails/enum_syntax'
|
49
50
|
require_relative 'rails/enum_uniqueness'
|
50
51
|
require_relative 'rails/env_local'
|
51
52
|
require_relative 'rails/environment_comparison'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.26.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bozhidar Batsov
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-08-24 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -46,7 +46,7 @@ dependencies:
|
|
46
46
|
requirements:
|
47
47
|
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: 1.
|
49
|
+
version: 1.52.0
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: '2.0'
|
@@ -56,7 +56,7 @@ dependencies:
|
|
56
56
|
requirements:
|
57
57
|
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: 1.
|
59
|
+
version: 1.52.0
|
60
60
|
- - "<"
|
61
61
|
- !ruby/object:Gem::Version
|
62
62
|
version: '2.0'
|
@@ -140,6 +140,7 @@ files:
|
|
140
140
|
- lib/rubocop/cop/rails/dynamic_find_by.rb
|
141
141
|
- lib/rubocop/cop/rails/eager_evaluation_log_message.rb
|
142
142
|
- lib/rubocop/cop/rails/enum_hash.rb
|
143
|
+
- lib/rubocop/cop/rails/enum_syntax.rb
|
143
144
|
- lib/rubocop/cop/rails/enum_uniqueness.rb
|
144
145
|
- lib/rubocop/cop/rails/env_local.rb
|
145
146
|
- lib/rubocop/cop/rails/environment_comparison.rb
|
@@ -246,7 +247,7 @@ metadata:
|
|
246
247
|
homepage_uri: https://docs.rubocop.org/rubocop-rails/
|
247
248
|
changelog_uri: https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md
|
248
249
|
source_code_uri: https://github.com/rubocop/rubocop-rails/
|
249
|
-
documentation_uri: https://docs.rubocop.org/rubocop-rails/2.
|
250
|
+
documentation_uri: https://docs.rubocop.org/rubocop-rails/2.26/
|
250
251
|
bug_tracker_uri: https://github.com/rubocop/rubocop-rails/issues
|
251
252
|
rubygems_mfa_required: 'true'
|
252
253
|
post_install_message:
|