rubocop-rails 2.25.1 → 2.26.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b55703b258e4df9bae3a9a8c1b77a4fa143af6af3ed9b1b22118fd839d1ce06c
4
- data.tar.gz: 24568d7d8d22d69469ae9a4aaf426ea45bf848989604a8744ab6493bc0b222a3
3
+ metadata.gz: c18cefb961b3100a871e2411c7a029244eda6998d793e3ece62aef1adcd4c571
4
+ data.tar.gz: 24cde448ef91e689e05a2357c1aa0cbcc4d61630f1bb6e7fcaf8999ff7678105
5
5
  SHA512:
6
- metadata.gz: 47d5668b744967fc740b47552e9d05068fc5ce5c209a4a994a429262a0628b13e0f1e48d6a39018430fb7e874f88e4badbd9427219b9a77220e522d6706b1c3c
7
- data.tar.gz: 242030d6fe9063d51f18e98869e2a048a4b7b847f0f6a25937fc3d9781dbb7749de8b015b3fa0b4933511b83487ad060fbdbf9adb7942a20bfc035d5fc9e2f04
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.10'
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.compact.any? do |render_candidate|
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(node.block_node.body)
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.reject!(&:blank?) # Same behavior as `ActionController::Parameters#compact_blank!`
35
- # collection.reject! { |_k, v| v.blank? } # Same behavior as `ActionController::Parameters#compact_blank!`
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 reject!].freeze
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 :reject!})
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 :reject!}
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
- if (arguments, receiver_in_block = reject_with_block?(node.parent))
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 'strict'
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 'to_time_in_current_zone' are reported as warning.
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 :enum?, <<~PATTERN
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
- enum?(node) do |pairs|
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: format(MSG, enum: enum_name(key))) do |corrector|
41
- hash = array.children.each_with_index.map do |elem, index|
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 |pairs|
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
- items = args.values
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
@@ -52,7 +52,7 @@ module RuboCop
52
52
  (send
53
53
  nil?
54
54
  {#{FILTERS.join(' ')}}
55
- _
55
+ ...
56
56
  $_)
57
57
  PATTERN
58
58
 
@@ -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 different enforcement modes. When the `EnforcedStyle`
13
- # is `conservative` (the default) then only calls to `pluck` on a constant
14
- # (i.e. a model class) in the `where` is used as offenses.
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 the `EnforcedStyle` is `aggressive` then all calls to `pluck` in the
18
- # `where` is used as offenses. This may lead to false positives
19
- # as the cop cannot replace to `select` between calls to `pluck` on an
20
- # `ActiveRecord::Relation` instance vs a call to `pluck` on an `Array` 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
- SINGULAR_DURATION_METHODS = { second: :seconds,
21
- minute: :minutes,
22
- hour: :hours,
23
- day: :days,
24
- week: :weeks,
25
- fortnight: :fortnights,
26
- month: :months,
27
- year: :years }.freeze
28
-
29
- RESTRICT_ON_SEND = SINGULAR_DURATION_METHODS.keys + SINGULAR_DURATION_METHODS.values
30
-
31
- PLURAL_DURATION_METHODS = SINGULAR_DURATION_METHODS.invert.freeze
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
- SINGULAR_DURATION_METHODS.fetch(method_name.to_sym).to_s
103
+ SINGULAR_METHODS.fetch(method_name.to_sym).to_s
90
104
  end
91
105
 
92
106
  def singularize(method_name)
93
- PLURAL_DURATION_METHODS.fetch(method_name.to_sym).to_s
107
+ PLURAL_METHODS.fetch(method_name.to_sym).to_s
94
108
  end
95
109
 
96
110
  def duration_method?(method_name)
97
- SINGULAR_DURATION_METHODS.key?(method_name) || PLURAL_DURATION_METHODS.key?(method_name)
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?(node)
57
- (node && node.value.value == 'text/plain') ||
58
- (!node && !cop_config['ContentTypeCompatibility'])
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) :glob ...)
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, method)
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, method)
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}.#{method}(#{argument})"
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 `where(attribute: value)`.
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
- column_and_value = extract_column_and_value(template_node, value_node)
48
- return unless column_and_value
52
+ column, value = extract_column_and_value(template_node, value_node)
53
+ return unless value
49
54
 
50
- good_method = build_good_method(*column_and_value)
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.source
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
- "where(#{table}: { #{column}: #{value} })"
100
+ "#{method_name}(#{table}: { #{column}: #{value} })"
96
101
  else
97
- "where(#{column}: #{value})"
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
- column_and_value = extract_column_and_value(template_node, value_node)
47
- return unless column_and_value
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, *column_and_value)
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.source
75
+ value_node&.source
76
76
  when NOT_EQ_NAMED_RE, NOT_IN_NAMED_RE
77
- return unless value_node.hash_type?
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'
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Rails
5
5
  # This module holds the RuboCop Rails version information.
6
6
  module Version
7
- STRING = '2.25.1'
7
+ STRING = '2.26.0'
8
8
 
9
9
  def self.document_version
10
10
  STRING.match('\d+\.\d+').to_s
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.25.1
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-06-29 00:00:00.000000000 Z
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.33.0
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.33.0
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.25/
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: