rubocop-rails 2.30.2 → 2.33.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/config/default.yml +87 -49
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +2 -2
  5. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
  6. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +2 -2
  7. data/lib/rubocop/cop/mixin/enforce_superclass.rb +6 -1
  8. data/lib/rubocop/cop/mixin/index_method.rb +6 -1
  9. data/lib/rubocop/cop/rails/arel_star.rb +5 -5
  10. data/lib/rubocop/cop/rails/delegate.rb +7 -4
  11. data/lib/rubocop/cop/rails/duplicate_association.rb +1 -1
  12. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +1 -3
  13. data/lib/rubocop/cop/rails/env_local.rb +50 -26
  14. data/lib/rubocop/cop/rails/file_path.rb +2 -2
  15. data/lib/rubocop/cop/rails/find_by_or_assignment_memoization.rb +65 -0
  16. data/lib/rubocop/cop/rails/index_by.rb +9 -0
  17. data/lib/rubocop/cop/rails/index_with.rb +14 -0
  18. data/lib/rubocop/cop/rails/order_arguments.rb +79 -0
  19. data/lib/rubocop/cop/rails/output.rb +4 -2
  20. data/lib/rubocop/cop/rails/pluck.rb +13 -4
  21. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +3 -3
  22. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +7 -2
  23. data/lib/rubocop/cop/rails/reflection_class_name.rb +2 -2
  24. data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
  25. data/lib/rubocop/cop/rails/reversible_migration.rb +2 -1
  26. data/lib/rubocop/cop/rails/save_bang.rb +4 -4
  27. data/lib/rubocop/cop/rails/schema_comment.rb +1 -1
  28. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +1 -1
  29. data/lib/rubocop/cop/rails/time_zone.rb +3 -1
  30. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +5 -3
  31. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +1 -1
  32. data/lib/rubocop/cop/rails/where_exists.rb +2 -2
  33. data/lib/rubocop/cop/rails_cops.rb +2 -0
  34. data/lib/rubocop/rails/version.rb +1 -1
  35. data/lib/rubocop-rails.rb +0 -1
  36. metadata +10 -8
@@ -24,45 +24,69 @@ module RuboCop
24
24
 
25
25
  minimum_target_rails_version 7.1
26
26
 
27
- # @!method rails_env_local_or?(node)
28
- def_node_matcher :rails_env_local_or?, <<~PATTERN
29
- (or
30
- (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
31
- (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
32
- )
27
+ # @!method rails_env_local?(node)
28
+ def_node_matcher :rails_env_local?, <<~PATTERN
29
+ (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
33
30
  PATTERN
34
31
 
35
- # @!method rails_env_local_and?(node)
36
- def_node_matcher :rails_env_local_and?, <<~PATTERN
37
- (and
38
- (send
39
- (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
40
- :!)
41
- (send
42
- (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
43
- :!)
44
- )
32
+ # @!method not_rails_env_local?(node)
33
+ def_node_matcher :not_rails_env_local?, <<~PATTERN
34
+ (send #rails_env_local? :!)
45
35
  PATTERN
46
36
 
47
37
  def on_or(node)
48
- rails_env_local_or?(node) do |*environments|
49
- next unless environments.to_set == LOCAL_ENVIRONMENTS
38
+ lhs, rhs = *node.children
39
+ return unless rails_env_local?(rhs)
50
40
 
51
- add_offense(node) do |corrector|
52
- corrector.replace(node, 'Rails.env.local?')
53
- end
41
+ nodes = [rhs]
42
+
43
+ if rails_env_local?(lhs)
44
+ nodes << lhs
45
+ elsif lhs.or_type? && rails_env_local?(lhs.rhs)
46
+ nodes << lhs.rhs
47
+ end
48
+
49
+ return unless environments(nodes).to_set == LOCAL_ENVIRONMENTS
50
+
51
+ range = offense_range(nodes)
52
+ add_offense(range) do |corrector|
53
+ corrector.replace(range, 'Rails.env.local?')
54
54
  end
55
55
  end
56
56
 
57
57
  def on_and(node)
58
- rails_env_local_and?(node) do |*environments|
59
- next unless environments.to_set == LOCAL_ENVIRONMENTS
58
+ lhs, rhs = *node.children
59
+ return unless not_rails_env_local?(rhs)
60
+
61
+ nodes = [rhs]
62
+
63
+ if not_rails_env_local?(lhs)
64
+ nodes << lhs
65
+ elsif lhs.operator_keyword? && not_rails_env_local?(lhs.rhs)
66
+ nodes << lhs.rhs
67
+ end
60
68
 
61
- add_offense(node, message: MSG_NEGATED) do |corrector|
62
- corrector.replace(node, '!Rails.env.local?')
63
- end
69
+ return unless environments(nodes).to_set == LOCAL_ENVIRONMENTS
70
+
71
+ range = offense_range(nodes)
72
+ add_offense(range, message: MSG_NEGATED) do |corrector|
73
+ corrector.replace(range, '!Rails.env.local?')
64
74
  end
65
75
  end
76
+
77
+ private
78
+
79
+ def environments(nodes)
80
+ if nodes[0].method?(:!)
81
+ nodes.map { |node| node.receiver.method_name }
82
+ else
83
+ nodes.map(&:method_name)
84
+ end
85
+ end
86
+
87
+ def offense_range(nodes)
88
+ nodes[1].source_range.begin.join(nodes[0].source_range.end)
89
+ end
66
90
  end
67
91
  end
68
92
  end
@@ -190,7 +190,7 @@ module RuboCop
190
190
  else
191
191
  replace_with_rails_root_join(corrector, rails_root_node, argument_source)
192
192
  end
193
- node.children[rails_root_index + 1..].each { |child| corrector.remove(child) }
193
+ node.children[(rails_root_index + 1)..].each { |child| corrector.remove(child) }
194
194
  end
195
195
 
196
196
  def autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
@@ -281,7 +281,7 @@ module RuboCop
281
281
  end
282
282
 
283
283
  def extract_rails_root_join_argument_source(node, rails_root_index)
284
- node.children[rails_root_index + 1..].map(&:source).join.delete_prefix(File::SEPARATOR)
284
+ node.children[(rails_root_index + 1)..].map(&:source).join.delete_prefix(File::SEPARATOR)
285
285
  end
286
286
 
287
287
  def extension_node?(node)
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Avoid memoizing `find_by` results with `||=`.
7
+ #
8
+ # It is common to see code that attempts to memoize `find_by` result by `||=`,
9
+ # but `find_by` may return `nil`, in which case it is not memoized as intended.
10
+ #
11
+ # @safety
12
+ # This cop is unsafe because detected `find_by` may not be Active Record's method,
13
+ # or the code may have a different purpose than memoization.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # def current_user
18
+ # @current_user ||= User.find_by(id: session[:user_id])
19
+ # end
20
+ #
21
+ # # good
22
+ # def current_user
23
+ # if instance_variable_defined?(:@current_user)
24
+ # @current_user
25
+ # else
26
+ # @current_user = User.find_by(id: session[:user_id])
27
+ # end
28
+ # end
29
+ class FindByOrAssignmentMemoization < Base
30
+ extend AutoCorrector
31
+
32
+ MSG = 'Avoid memoizing `find_by` results with `||=`.'
33
+
34
+ RESTRICT_ON_SEND = %i[find_by].freeze
35
+
36
+ def_node_matcher :find_by_or_assignment_memoization, <<~PATTERN
37
+ (or_asgn
38
+ (ivasgn $_)
39
+ $(send _ :find_by ...)
40
+ )
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ assignment_node = node.parent
45
+ find_by_or_assignment_memoization(assignment_node) do |varible_name, find_by|
46
+ next if assignment_node.each_ancestor(:if).any?
47
+
48
+ add_offense(assignment_node) do |corrector|
49
+ corrector.replace(
50
+ assignment_node,
51
+ <<~RUBY.rstrip
52
+ if instance_variable_defined?(:#{varible_name})
53
+ #{varible_name}
54
+ else
55
+ #{varible_name} = #{find_by.source}
56
+ end
57
+ RUBY
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -37,6 +37,9 @@ module RuboCop
37
37
  (numblock
38
38
  (call _ :to_h) $1
39
39
  (array $_ (lvar :_1)))
40
+ (itblock
41
+ (call _ :to_h) $:it
42
+ (array $_ (lvar :it)))
40
43
  }
41
44
  PATTERN
42
45
 
@@ -50,6 +53,9 @@ module RuboCop
50
53
  (numblock
51
54
  (call _ {:map :collect}) $1
52
55
  (array $_ (lvar :_1)))
56
+ (itblock
57
+ (call _ {:map :collect}) $:it
58
+ (array $_ (lvar :it)))
53
59
  }
54
60
  :to_h)
55
61
  PATTERN
@@ -66,6 +72,9 @@ module RuboCop
66
72
  (numblock
67
73
  (call _ {:map :collect}) $1
68
74
  (array $_ (lvar :_1)))
75
+ (itblock
76
+ (call _ {:map :collect}) $:it
77
+ (array $_ (lvar :it)))
69
78
  }
70
79
  )
71
80
  PATTERN
@@ -8,6 +8,11 @@ module RuboCop
8
8
  # an enumerable into a hash where the keys are the original elements.
9
9
  # Rails provides the `index_with` method for this purpose.
10
10
  #
11
+ # @safety
12
+ # This cop is marked as unsafe autocorrection, because `nil.to_h` returns {}
13
+ # but `nil.with_index` throws `NoMethodError`. Therefore, autocorrection is not
14
+ # compatible if the receiver is nil.
15
+ #
11
16
  # @example
12
17
  # # bad
13
18
  # [1, 2, 3].each_with_object({}) { |el, h| h[el] = foo(el) }
@@ -40,6 +45,9 @@ module RuboCop
40
45
  (numblock
41
46
  (call _ :to_h) $1
42
47
  (array (lvar :_1) $_))
48
+ (itblock
49
+ (call _ :to_h) $:it
50
+ (array (lvar :it) $_))
43
51
  }
44
52
  PATTERN
45
53
 
@@ -53,6 +61,9 @@ module RuboCop
53
61
  (numblock
54
62
  (call _ {:map :collect}) $1
55
63
  (array (lvar :_1) $_))
64
+ (itblock
65
+ (call _ {:map :collect}) $:it
66
+ (array (lvar :it) $_))
56
67
  }
57
68
  :to_h)
58
69
  PATTERN
@@ -69,6 +80,9 @@ module RuboCop
69
80
  (numblock
70
81
  (call _ {:map :collect}) $1
71
82
  (array (lvar :_1) $_))
83
+ (itblock
84
+ (call _ {:map :collect}) $:it
85
+ (array (lvar :it) $_))
72
86
  }
73
87
  )
74
88
  PATTERN
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Prefer symbol arguments over strings in `order` method.
7
+ #
8
+ # @safety
9
+ # Cop is unsafe because the receiver might not be an Active Record query.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # User.order('name')
14
+ # User.order('name DESC')
15
+ #
16
+ # # good
17
+ # User.order(:name)
18
+ # User.order(name: :desc)
19
+ #
20
+ class OrderArguments < Base
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Prefer `%<prefer>s` instead.'
24
+
25
+ RESTRICT_ON_SEND = %i[order].freeze
26
+
27
+ def_node_matcher :string_order, <<~PATTERN
28
+ (call _ :order (str $_value)+)
29
+ PATTERN
30
+
31
+ ORDER_EXPRESSION_REGEX = /\A(\w+) ?(asc|desc)?\z/i.freeze
32
+
33
+ def on_send(node)
34
+ return unless (current_expressions = string_order(node))
35
+ return unless (preferred_expressions = replacement(current_expressions))
36
+
37
+ offense_range = find_offense_range(node)
38
+ add_offense(offense_range, message: format(MSG, prefer: preferred_expressions)) do |corrector|
39
+ corrector.replace(offense_range, preferred_expressions)
40
+ end
41
+ end
42
+ alias on_csend on_send
43
+
44
+ private
45
+
46
+ def find_offense_range(node)
47
+ node.first_argument.source_range.join(node.last_argument.source_range)
48
+ end
49
+
50
+ def replacement(order_expressions)
51
+ order_arguments = order_expressions.flat_map { |expr| expr.split(',') }
52
+ order_arguments.map! { |arg| extract_column_and_direction(arg.strip) }
53
+
54
+ return if order_arguments.any?(&:nil?)
55
+
56
+ convert_to_preferred_arguments(order_arguments).join(', ')
57
+ end
58
+
59
+ def convert_to_preferred_arguments(order_expressions)
60
+ use_hash = false
61
+ order_expressions.map do |column, direction|
62
+ if direction == :asc && !use_hash
63
+ ":#{column}"
64
+ else
65
+ use_hash = true
66
+ "#{column}: :#{direction}"
67
+ end
68
+ end
69
+ end
70
+
71
+ def extract_column_and_direction(order_expression)
72
+ return unless (column, direction = ORDER_EXPRESSION_REGEX.match(order_expression)&.captures)
73
+
74
+ [column.downcase, direction&.downcase&.to_sym || :asc]
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -23,7 +23,6 @@ module RuboCop
23
23
 
24
24
  MSG = "Do not write to stdout. Use Rails's logger if you want to log."
25
25
  RESTRICT_ON_SEND = %i[ap p pp pretty_print print puts binwrite syswrite write write_nonblock].freeze
26
- ALLOWED_TYPES = %i[send csend block numblock].freeze
27
26
 
28
27
  def_node_matcher :output?, <<~PATTERN
29
28
  (send nil? {:ap :p :pp :pretty_print :print :puts} ...)
@@ -39,9 +38,11 @@ module RuboCop
39
38
  ...)
40
39
  PATTERN
41
40
 
41
+ # rubocop:disable Metrics/CyclomaticComplexity
42
42
  def on_send(node)
43
- return if ALLOWED_TYPES.include?(node.parent&.type)
43
+ return if node.parent&.call_type? || node.block_node
44
44
  return if !output?(node) && !io_output?(node)
45
+ return if node.arguments.any? { |arg| arg.type?(:hash, :block_pass) }
45
46
 
46
47
  range = offense_range(node)
47
48
 
@@ -49,6 +50,7 @@ module RuboCop
49
50
  corrector.replace(range, 'Rails.logger.debug')
50
51
  end
51
52
  end
53
+ # rubocop:enable Metrics/CyclomaticComplexity
52
54
 
53
55
  private
54
56
 
@@ -27,6 +27,9 @@ module RuboCop
27
27
  # end
28
28
  # ----
29
29
  #
30
+ # If a method call has no receiver, like `do_something { users.map { |user| user[:foo] }`,
31
+ # it is not considered part of an iteration and will be detected.
32
+ #
30
33
  # @safety
31
34
  # This cop is unsafe because model can use column aliases.
32
35
  #
@@ -59,8 +62,9 @@ module RuboCop
59
62
  (any_block (call _ {:map :collect}) $_argument (send lvar :[] $_key))
60
63
  PATTERN
61
64
 
65
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
62
66
  def on_block(node)
63
- return if node.each_ancestor(:block, :numblock).any?
67
+ return if node.each_ancestor(:any_block).first&.receiver
64
68
 
65
69
  pluck_candidate?(node) do |argument, key|
66
70
  next if key.regexp_type? || !use_one_block_argument?(argument)
@@ -68,20 +72,25 @@ module RuboCop
68
72
  match = if node.block_type?
69
73
  block_argument = argument.children.first.source
70
74
  use_block_argument_in_key?(block_argument, key)
71
- else # numblock
72
- argument == 1 && use_block_argument_in_key?('_1', key)
75
+ elsif node.numblock_type?
76
+ use_block_argument_in_key?('_1', key)
77
+ else # itblock
78
+ use_block_argument_in_key?('it', key)
73
79
  end
74
80
  next unless match
75
81
 
76
82
  register_offense(node, key)
77
83
  end
78
84
  end
85
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
79
86
  alias on_numblock on_block
87
+ alias on_itblock on_block
80
88
 
81
89
  private
82
90
 
83
91
  def use_one_block_argument?(argument)
84
- return true if argument == 1 # Checks for numbered argument `_1`.
92
+ # Checks for numbered argument `_1` or `it block parameter.
93
+ return true if [1, :it].include?(argument)
85
94
 
86
95
  argument.respond_to?(:one?) && argument.one?
87
96
  end
@@ -74,7 +74,7 @@ module RuboCop
74
74
  $[
75
75
  (hash <$(pair (sym :presence) true) ...>) # presence: true
76
76
  !(hash <$(pair (sym :strict) {true const}) ...>) # strict: true
77
- !(hash <$(pair (sym {:if :unless}) _) ...>) # if: some_condition or unless: some_condition
77
+ !(hash <$(pair (sym {:if}) _) ...>) # if: some_condition or unless: some_condition
78
78
  ]
79
79
  )
80
80
  PATTERN
@@ -211,12 +211,12 @@ module RuboCop
211
211
 
212
212
  def non_optional_belongs_to(node, keys)
213
213
  keys.select do |key|
214
- belongs_to = belongs_to_for(node, key)
214
+ belongs_to = belongs_to_for?(node, key)
215
215
  belongs_to && !optional?(belongs_to)
216
216
  end
217
217
  end
218
218
 
219
- def belongs_to_for(model_class_node, key)
219
+ def belongs_to_for?(model_class_node, key)
220
220
  if key.to_s.end_with?('_id')
221
221
  normalized_key = key.to_s.delete_suffix('_id').to_sym
222
222
  belongs_to?(model_class_node, key: normalized_key, fk: key)
@@ -71,7 +71,7 @@ module RuboCop
71
71
  def on_block(node)
72
72
  return unless node.method?(:with_options)
73
73
  return unless (body = node.body)
74
- return unless all_block_nodes_in(body).count.zero?
74
+ return unless all_block_nodes_in(body).none?
75
75
 
76
76
  send_nodes = all_send_nodes_in(body)
77
77
  return unless redundant_receiver?(send_nodes, node)
@@ -85,18 +85,22 @@ module RuboCop
85
85
  end
86
86
 
87
87
  alias on_numblock on_block
88
+ alias on_itblock on_block
88
89
 
89
90
  private
90
91
 
91
92
  def autocorrect(corrector, send_node, node)
92
93
  corrector.remove(send_node.receiver)
93
94
  corrector.remove(send_node.loc.dot)
94
- corrector.remove(block_argument_range(send_node)) unless node.numblock_type?
95
+ corrector.remove(block_argument_range(send_node)) if node.block_type?
95
96
  end
96
97
 
98
+ # rubocop:disable Metrics/AbcSize
97
99
  def redundant_receiver?(send_nodes, node)
98
100
  proc = if node.numblock_type?
99
101
  ->(n) { n.receiver.lvar_type? && n.receiver.source == '_1' }
102
+ elsif node.itblock_type?
103
+ ->(n) { n.receiver.lvar_type? && n.receiver.source == 'it' }
100
104
  else
101
105
  return false if node.arguments.empty?
102
106
 
@@ -106,6 +110,7 @@ module RuboCop
106
110
 
107
111
  send_nodes.all?(&proc)
108
112
  end
113
+ # rubocop:enable Metrics/AbcSize
109
114
 
110
115
  def block_argument_range(node)
111
116
  block_node = node.each_ancestor(:block).first
@@ -40,7 +40,7 @@ module RuboCop
40
40
 
41
41
  def on_send(node)
42
42
  association_with_reflection(node) do |reflection_class_name|
43
- return if reflection_class_name.value.send_type? && reflection_class_name.value.receiver.nil?
43
+ return if reflection_class_name.value.send_type? && !reflection_class_name.value.receiver&.const_type?
44
44
  return if reflection_class_name.value.lvar_type? && str_assigned?(reflection_class_name)
45
45
 
46
46
  add_offense(reflection_class_name) do |corrector|
@@ -76,7 +76,7 @@ module RuboCop
76
76
  def autocorrect(corrector, class_config)
77
77
  class_value = class_config.value
78
78
  replacement = const_or_string(class_value)
79
- return unless replacement.present?
79
+ return unless replacement
80
80
 
81
81
  corrector.replace(class_value, replacement.source.inspect)
82
82
  end
@@ -90,7 +90,7 @@ module RuboCop
90
90
  end
91
91
 
92
92
  def nested_relative_date(node, &callback)
93
- return if node.nil? || node.block_type?
93
+ return if node.nil? || node.any_block_type?
94
94
 
95
95
  node.each_child_node do |child|
96
96
  nested_relative_date(child, &callback)
@@ -205,6 +205,7 @@ module RuboCop
205
205
  end
206
206
 
207
207
  alias on_numblock on_block
208
+ alias on_itblock on_block
208
209
 
209
210
  private
210
211
 
@@ -218,7 +219,7 @@ module RuboCop
218
219
  return unless (last_argument = node.last_argument)
219
220
 
220
221
  drop_table_call(node) do
221
- unless node.parent.block_type? || last_argument.block_pass_type?
222
+ unless node.parent.any_block_type? || last_argument.block_pass_type?
222
223
  add_offense(node, message: format(MSG, action: 'drop_table(without block)'))
223
224
  end
224
225
  end
@@ -156,7 +156,7 @@ module RuboCop
156
156
  return unless persist_method?(node)
157
157
  return if return_value_assigned?(node)
158
158
  return if implicit_return?(node)
159
- return if check_used_in_condition_or_compound_boolean(node)
159
+ return if check_used_in_condition_or_compound_boolean?(node)
160
160
  return if argument?(node)
161
161
  return if explicit_return?(node)
162
162
  return if checked_immediately?(node)
@@ -182,7 +182,7 @@ module RuboCop
182
182
  def right_assignment_node(assignment)
183
183
  node = assignment.node.child_nodes.first
184
184
 
185
- return node unless node&.block_type?
185
+ return node unless node&.any_block_type?
186
186
 
187
187
  node.send_node
188
188
  end
@@ -227,7 +227,7 @@ module RuboCop
227
227
  array
228
228
  end
229
229
 
230
- def check_used_in_condition_or_compound_boolean(node)
230
+ def check_used_in_condition_or_compound_boolean?(node)
231
231
  return false unless in_condition_or_compound_boolean?(node)
232
232
 
233
233
  register_offense(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
@@ -305,7 +305,7 @@ module RuboCop
305
305
 
306
306
  node = assignable_node(node)
307
307
  method, sibling_index = find_method_with_sibling_index(node.parent)
308
- return false unless method&.type?(:def, :block)
308
+ return false unless method&.type?(:def, :any_block)
309
309
 
310
310
  method.children.size == node.sibling_index + sibling_index
311
311
  end
@@ -39,7 +39,7 @@ module RuboCop
39
39
 
40
40
  # @!method comment_present?(node)
41
41
  def_node_matcher :comment_present?, <<~PATTERN
42
- (hash <(pair {(sym :comment) (str "comment")} (_ [present?])) ...>)
42
+ (hash <(pair {(sym :comment) (str "comment")} !{nil (str blank?)}) ...>)
43
43
  PATTERN
44
44
 
45
45
  # @!method add_column?(node)
@@ -45,7 +45,7 @@ module RuboCop
45
45
 
46
46
  return if required_options?(options_node)
47
47
 
48
- def_node = node.each_ancestor(:def, :defs).first
48
+ def_node = node.each_ancestor(:any_def).first
49
49
  table_node = table_node(node)
50
50
  return if def_node && (table_node.nil? || change_column_null?(def_node, table_node, column_node))
51
51
 
@@ -135,7 +135,9 @@ module RuboCop
135
135
  end
136
136
 
137
137
  def attach_timezone_specifier?(date)
138
- date.respond_to?(:value) && TIMEZONE_SPECIFIER.match?(date.value.to_s)
138
+ return false unless date.respond_to?(:value)
139
+
140
+ !date.value.to_s.valid_encoding? || TIMEZONE_SPECIFIER.match?(date.value.to_s)
139
141
  end
140
142
 
141
143
  def build_message(klass, method_name, node)
@@ -97,9 +97,11 @@ module RuboCop
97
97
 
98
98
  def in_transaction_block?(node)
99
99
  return false unless transaction_method_name?(node.method_name)
100
- return false unless (parent = node.parent)
100
+ return false unless node.parent&.body
101
101
 
102
- parent.block_type? && parent.body
102
+ node.right_siblings.none? do |sibling|
103
+ sibling.respond_to?(:loop_keyword?) && sibling.loop_keyword?
104
+ end
103
105
  end
104
106
 
105
107
  def statement(statement_node)
@@ -113,7 +115,7 @@ module RuboCop
113
115
  end
114
116
 
115
117
  def nested_block?(statement_node)
116
- name = statement_node.ancestors.find(&:block_type?).children.first.method_name
118
+ name = statement_node.ancestors.find(&:any_block_type?).children.first.method_name
117
119
  !transaction_method_name?(name)
118
120
  end
119
121
 
@@ -52,7 +52,7 @@ module RuboCop
52
52
  MSG = 'Use `distinct` before `pluck`.'
53
53
  RESTRICT_ON_SEND = %i[uniq].freeze
54
54
 
55
- def_node_matcher :uniq_before_pluck, '[!^block $(send $(send _ :pluck ...) :uniq ...)]'
55
+ def_node_matcher :uniq_before_pluck, '[!^any_block $(send $(send _ :pluck ...) :uniq ...)]'
56
56
 
57
57
  def on_send(node)
58
58
  uniq_before_pluck(node) do |uniq_node, pluck_node|
@@ -63,7 +63,7 @@ module RuboCop
63
63
  PATTERN
64
64
 
65
65
  def on_send(node)
66
- find_offenses(node) do |args|
66
+ find_offenses?(node) do |args|
67
67
  return unless convertable_args?(args)
68
68
 
69
69
  range = correction_range(node)
@@ -87,7 +87,7 @@ module RuboCop
87
87
  style == :exists
88
88
  end
89
89
 
90
- def find_offenses(node, &block)
90
+ def find_offenses?(node, &block)
91
91
  if exists_style?
92
92
  where_exists_call?(node, &block)
93
93
  elsif where_style?
@@ -58,6 +58,7 @@ require_relative 'rails/expanded_date_range'
58
58
  require_relative 'rails/file_path'
59
59
  require_relative 'rails/find_by'
60
60
  require_relative 'rails/find_by_id'
61
+ require_relative 'rails/find_by_or_assignment_memoization'
61
62
  require_relative 'rails/find_each'
62
63
  require_relative 'rails/freeze_time'
63
64
  require_relative 'rails/has_and_belongs_to_many'
@@ -82,6 +83,7 @@ require_relative 'rails/migration_class_name'
82
83
  require_relative 'rails/multiple_route_paths'
83
84
  require_relative 'rails/negate_include'
84
85
  require_relative 'rails/not_null_column'
86
+ require_relative 'rails/order_arguments'
85
87
  require_relative 'rails/order_by_id'
86
88
  require_relative 'rails/output'
87
89
  require_relative 'rails/output_safety'
@@ -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.30.2'
7
+ STRING = '2.33.0'
8
8
 
9
9
  def self.document_version
10
10
  STRING.match('\d+\.\d+').to_s