rubocop-rails 2.22.1 → 2.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +1 -1
  3. data/lib/rubocop/cop/mixin/active_record_helper.rb +9 -2
  4. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +1 -1
  5. data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -2
  6. data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
  7. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  8. data/lib/rubocop/cop/rails/bulk_change_table.rb +4 -4
  9. data/lib/rubocop/cop/rails/dangerous_column_names.rb +1 -1
  10. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +2 -2
  11. data/lib/rubocop/cop/rails/file_path.rb +5 -5
  12. data/lib/rubocop/cop/rails/find_by.rb +2 -2
  13. data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
  14. data/lib/rubocop/cop/rails/inquiry.rb +1 -0
  15. data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
  16. data/lib/rubocop/cop/rails/pick.rb +6 -5
  17. data/lib/rubocop/cop/rails/pluck.rb +1 -1
  18. data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
  19. data/lib/rubocop/cop/rails/pluck_in_where.rb +18 -5
  20. data/lib/rubocop/cop/rails/rake_environment.rb +2 -2
  21. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +42 -30
  22. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
  23. data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
  24. data/lib/rubocop/cop/rails/reversible_migration.rb +2 -2
  25. data/lib/rubocop/cop/rails/save_bang.rb +4 -4
  26. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +1 -1
  27. data/lib/rubocop/cop/rails/unknown_env.rb +3 -3
  28. data/lib/rubocop/cop/rails/validation.rb +2 -2
  29. data/lib/rubocop/cop/rails/where_equals.rb +3 -2
  30. data/lib/rubocop/cop/rails/where_exists.rb +9 -8
  31. data/lib/rubocop/cop/rails/where_missing.rb +1 -1
  32. data/lib/rubocop/cop/rails/where_not.rb +7 -6
  33. data/lib/rubocop/rails/schema_loader/schema.rb +2 -2
  34. data/lib/rubocop/rails/version.rb +1 -1
  35. metadata +24 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4d08f111893c16ea5c725b8da3b488a85d193366cdec6c876e780e794d66ba3
4
- data.tar.gz: 40bd526fbf58ea8b3b45ab82ff5b7f809dcf29c7377991023fb30157dc66cc0d
3
+ metadata.gz: fc3cc6694d88b3f46d969723b209936bd4d9626006adac20f9e27efc026e678a
4
+ data.tar.gz: 6cab7ac58d4cebba15ac715dd773934d1991e0dabd44cd379cf206d620ddd7a1
5
5
  SHA512:
6
- metadata.gz: 6dd91e7f4068bba386e8b66ce86ec18961bc6e346d000947220af3fccdaf076907a11d7367786702124370aa070aacadba96bde0c200303ad64eaf1b5de6f8d5
7
- data.tar.gz: 783d1a9013524550b3bbd2271f3d244b13565038b1f0e73b7726565a6ea79e6e409b19f748e043fd7d850c623ea3f56c831f81f7d5bafd2cbd5e08d4181b1172
6
+ metadata.gz: 57874991d5024e5d330122698d6572a05c0ef29840f1069e5008d455b45d401f03f70c1d349004e0bf7c8169a4c183f1a5de96217d135981930bb892a06740df
7
+ data.tar.gz: cd0ab83879c7a264fccffeb2a9b483d9af95977fe15e227ecbbc37f9dfbbf196ff4e7de511630c91f98915626121304e80e5960a3b2897bd62637481e3f3f2fc
data/config/default.yml CHANGED
@@ -902,7 +902,7 @@ Rails/RequireDependency:
902
902
  VersionAdded: '2.10'
903
903
 
904
904
  Rails/ResponseParsedBody:
905
- Description: Prefer `response.parsed_body` to `JSON.parse(response.body)`.
905
+ Description: Prefer `response.parsed_body` to custom parsing logic for `response.body`.
906
906
  Enabled: pending
907
907
  Safe: false
908
908
  VersionAdded: '2.18'
@@ -98,8 +98,15 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def in_where?(node)
101
- send_node = node.each_ancestor(:send).first
102
- send_node && WHERE_METHODS.include?(send_node.method_name)
101
+ send_node = node.each_ancestor(:send, :csend).first
102
+ return false unless send_node
103
+
104
+ return true if WHERE_METHODS.include?(send_node.method_name)
105
+
106
+ receiver = send_node.receiver
107
+ return false unless receiver&.send_type?
108
+
109
+ send_node.method?(:not) && WHERE_METHODS.include?(receiver.method_name)
103
110
  end
104
111
  end
105
112
  end
@@ -23,7 +23,7 @@ module RuboCop
23
23
  case database_adapter
24
24
  when 'mysql2', 'trilogy'
25
25
  MYSQL
26
- when 'postgresql'
26
+ when 'postgresql', 'postgis'
27
27
  POSTGRESQL
28
28
  end
29
29
  end
@@ -11,10 +11,10 @@ module RuboCop
11
11
  # `update` but the method name remained same in the method definition.
12
12
  #
13
13
  # @example
14
- # #bad
14
+ # # bad
15
15
  # book.update_attributes!(author: 'Alice')
16
16
  #
17
- # #good
17
+ # # good
18
18
  # book.update!(author: 'Alice')
19
19
  class ActiveRecordAliases < Base
20
20
  extend AutoCorrector
@@ -27,13 +27,13 @@ module RuboCop
27
27
 
28
28
  ALIASES = {
29
29
  starts_with?: {
30
- original: :start_with?, matcher: '(send str :starts_with? _)'
30
+ original: :start_with?, matcher: '(call str :starts_with? _)'
31
31
  },
32
32
  ends_with?: {
33
- original: :end_with?, matcher: '(send str :ends_with? _)'
33
+ original: :end_with?, matcher: '(call str :ends_with? _)'
34
34
  },
35
- append: { original: :<<, matcher: '(send array :append _)' },
36
- prepend: { original: :unshift, matcher: '(send array :prepend _)' }
35
+ append: { original: :<<, matcher: '(call array :append _)' },
36
+ prepend: { original: :unshift, matcher: '(call array :prepend _)' }
37
37
  }.freeze
38
38
 
39
39
  ALIASES.each do |aliased_method, options|
@@ -47,13 +47,14 @@ module RuboCop
47
47
  preferred_method = ALIASES[aliased_method][:original]
48
48
  message = format(MSG, prefer: preferred_method, current: aliased_method)
49
49
 
50
- add_offense(node, message: message) do |corrector|
50
+ add_offense(node.loc.selector.join(node.source_range.end), message: message) do |corrector|
51
51
  next if append(node)
52
52
 
53
53
  corrector.replace(node.loc.selector, preferred_method)
54
54
  end
55
55
  end
56
56
  end
57
+ alias on_csend on_send
57
58
  end
58
59
  end
59
60
  end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  seen_callback_names = {}
49
49
 
50
50
  each_after_commit_callback(class_node) do |node|
51
- callback_name = node.arguments[0].value
51
+ callback_name = node.first_argument.value
52
52
  if seen_callback_names.key?(callback_name)
53
53
  add_offense(node, message: format(MSG, name: callback_name))
54
54
  else
@@ -14,7 +14,7 @@ module RuboCop
14
14
  # automatically detect an adapter from `development` environment
15
15
  # in `config/database.yml` or the environment variable `DATABASE_URL`
16
16
  # when the `Database` option is not set.
17
- # If the adapter is not `mysql2`, `trilogy`, or `postgresql`,
17
+ # If the adapter is not `mysql2`, `trilogy`, `postgresql`, or `postgis`,
18
18
  # this Cop ignores offenses.
19
19
  #
20
20
  # @example
@@ -212,7 +212,7 @@ module RuboCop
212
212
  # @param node [RuboCop::AST::SendNode]
213
213
  def add_offense_for_alter_methods(node)
214
214
  # arguments: [{(sym :table)(str "table")} ...]
215
- table_node = node.arguments[0]
215
+ table_node = node.first_argument
216
216
  return unless table_node.is_a? RuboCop::AST::BasicLiteralNode
217
217
 
218
218
  message = format(MSG_FOR_ALTER_METHODS, table: table_node.value)
@@ -234,10 +234,10 @@ module RuboCop
234
234
  # @param new_node [RuboCop::AST::SendNode]
235
235
  def process(new_node)
236
236
  # arguments: [{(sym :table)(str "table")} ...]
237
- table_node = new_node.arguments[0]
237
+ table_node = new_node.first_argument
238
238
  if table_node.is_a? RuboCop::AST::BasicLiteralNode
239
239
  flush unless @nodes.all? do |node|
240
- node.arguments[0].value.to_s == table_node.value.to_s
240
+ node.first_argument.value.to_s == table_node.value.to_s
241
241
  end
242
242
  @nodes << new_node
243
243
  else
@@ -428,7 +428,7 @@ module RuboCop
428
428
  when :rename_column
429
429
  node.arguments[2]
430
430
  when *COLUMN_TYPE_METHOD_NAMES
431
- node.arguments[0]
431
+ node.first_argument
432
432
  end
433
433
  end
434
434
 
@@ -14,10 +14,10 @@ module RuboCop
14
14
  # when no output would be produced anyway.
15
15
  #
16
16
  # @example
17
- # #bad
17
+ # # bad
18
18
  # Rails.logger.debug "The time is #{Time.zone.now}."
19
19
  #
20
- # #good
20
+ # # good
21
21
  # Rails.logger.debug { "The time is #{Time.zone.now}." }
22
22
  #
23
23
  class EagerEvaluationLogMessage < Base
@@ -163,9 +163,9 @@ module RuboCop
163
163
 
164
164
  def autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
165
165
  rails_root_node = node.children[rails_root_index].children.first
166
- return unless rails_root_node.arguments.last.str_type?
166
+ return unless rails_root_node.last_argument.str_type?
167
167
 
168
- corrector.insert_before(rails_root_node.arguments.last.location.end, extension_node.source)
168
+ corrector.insert_before(rails_root_node.last_argument.location.end, extension_node.source)
169
169
  corrector.remove(extension_node)
170
170
  end
171
171
 
@@ -174,7 +174,7 @@ module RuboCop
174
174
  corrector.remove(
175
175
  range_with_surrounding_space(
176
176
  range_with_surrounding_comma(
177
- node.arguments.first.source_range,
177
+ node.first_argument.source_range,
178
178
  :right
179
179
  ),
180
180
  side: :right
@@ -187,7 +187,7 @@ module RuboCop
187
187
  end
188
188
 
189
189
  def autocorrect_rails_root_join_with_string_arguments(corrector, node)
190
- corrector.replace(node.arguments.first, %("#{node.arguments.map(&:value).join('/')}"))
190
+ corrector.replace(node.first_argument, %("#{node.arguments.map(&:value).join('/')}"))
191
191
  node.arguments[1..].each do |argument|
192
192
  corrector.remove(
193
193
  range_with_surrounding_comma(
@@ -221,7 +221,7 @@ module RuboCop
221
221
  end
222
222
 
223
223
  def append_argument(corrector, node, argument_source)
224
- corrector.insert_after(node.arguments.last, %(, "#{argument_source}"))
224
+ corrector.insert_after(node.last_argument, %(, "#{argument_source}"))
225
225
  end
226
226
 
227
227
  def replace_with_rails_root_join(corrector, node, argument_source)
@@ -28,7 +28,7 @@ module RuboCop
28
28
  include RangeHelp
29
29
  extend AutoCorrector
30
30
 
31
- MSG = 'Use `find_by` instead of `where.%<method>s`.'
31
+ MSG = 'Use `find_by` instead of `where%<dot>s%<method>s`.'
32
32
  RESTRICT_ON_SEND = %i[first take].freeze
33
33
 
34
34
  def on_send(node)
@@ -37,7 +37,7 @@ module RuboCop
37
37
 
38
38
  range = offense_range(node)
39
39
 
40
- add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
40
+ add_offense(range, message: format(MSG, dot: node.loc.dot.source, method: node.method_name)) do |corrector|
41
41
  autocorrect(corrector, node)
42
42
  end
43
43
  end
@@ -24,40 +24,39 @@ module RuboCop
24
24
  RESTRICT_ON_SEND = %i[take! find_by_id! find_by!].freeze
25
25
 
26
26
  def_node_matcher :where_take?, <<~PATTERN
27
- (send
28
- $(send _ :where
27
+ (call
28
+ $(call _ :where
29
29
  (hash
30
30
  (pair (sym :id) $_))) :take!)
31
31
  PATTERN
32
32
 
33
33
  def_node_matcher :find_by?, <<~PATTERN
34
34
  {
35
- (send _ :find_by_id! $_)
36
- (send _ :find_by! (hash (pair (sym :id) $_)))
35
+ (call _ :find_by_id! $_)
36
+ (call _ :find_by! (hash (pair (sym :id) $_)))
37
37
  }
38
38
  PATTERN
39
39
 
40
40
  def on_send(node)
41
41
  where_take?(node) do |where, id_value|
42
42
  range = where_take_offense_range(node, where)
43
- bad_method = build_where_take_bad_method(id_value)
44
43
 
45
- register_offense(range, id_value, bad_method)
44
+ register_offense(range, id_value)
46
45
  end
47
46
 
48
47
  find_by?(node) do |id_value|
49
48
  range = find_by_offense_range(node)
50
- bad_method = build_find_by_bad_method(node, id_value)
51
49
 
52
- register_offense(range, id_value, bad_method)
50
+ register_offense(range, id_value)
53
51
  end
54
52
  end
53
+ alias on_csend on_send
55
54
 
56
55
  private
57
56
 
58
- def register_offense(range, id_value, bad_method)
57
+ def register_offense(range, id_value)
59
58
  good_method = build_good_method(id_value)
60
- message = format(MSG, good_method: good_method, bad_method: bad_method)
59
+ message = format(MSG, good_method: good_method, bad_method: range.source)
61
60
 
62
61
  add_offense(range, message: message) do |corrector|
63
62
  corrector.replace(range, good_method)
@@ -75,19 +74,6 @@ module RuboCop
75
74
  def build_good_method(id_value)
76
75
  "find(#{id_value.source})"
77
76
  end
78
-
79
- def build_where_take_bad_method(id_value)
80
- "where(id: #{id_value.source}).take!"
81
- end
82
-
83
- def build_find_by_bad_method(node, id_value)
84
- case node.method_name
85
- when :find_by_id!
86
- "find_by_id!(#{id_value.source})"
87
- when :find_by!
88
- "find_by!(id: #{id_value.source})"
89
- end
90
- end
91
77
  end
92
78
  end
93
79
  end
@@ -33,6 +33,7 @@ module RuboCop
33
33
 
34
34
  add_offense(node.loc.selector)
35
35
  end
36
+ alias on_csend on_send
36
37
  end
37
38
  end
38
39
  end
@@ -222,7 +222,7 @@ module RuboCop
222
222
 
223
223
  def with_options_arguments(recv, node)
224
224
  blocks = node.each_ancestor(:block).select do |block|
225
- block.send_node.command?(:with_options) && same_context_in_with_options?(block.arguments.first, recv)
225
+ block.send_node.command?(:with_options) && same_context_in_with_options?(block.first_argument, recv)
226
226
  end
227
227
  blocks.flat_map { |n| n.send_node.arguments }
228
228
  end
@@ -28,13 +28,13 @@ module RuboCop
28
28
  extend AutoCorrector
29
29
  extend TargetRailsVersion
30
30
 
31
- MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
31
+ MSG = 'Prefer `pick(%<args>s)` over `%<current>s`.'
32
32
  RESTRICT_ON_SEND = %i[first].freeze
33
33
 
34
34
  minimum_target_rails_version 6.0
35
35
 
36
36
  def_node_matcher :pick_candidate?, <<~PATTERN
37
- (send (send _ :pluck ...) :first)
37
+ (call (call _ :pluck ...) :first)
38
38
  PATTERN
39
39
 
40
40
  def on_send(node)
@@ -44,7 +44,7 @@ module RuboCop
44
44
  node_selector = node.loc.selector
45
45
  range = receiver_selector.join(node_selector)
46
46
 
47
- add_offense(range, message: message(receiver)) do |corrector|
47
+ add_offense(range, message: message(receiver, range)) do |corrector|
48
48
  first_range = receiver.source_range.end.join(node_selector)
49
49
 
50
50
  corrector.remove(first_range)
@@ -52,11 +52,12 @@ module RuboCop
52
52
  end
53
53
  end
54
54
  end
55
+ alias on_csend on_send
55
56
 
56
57
  private
57
58
 
58
- def message(receiver)
59
- format(MSG, args: receiver.arguments.map(&:source).join(', '))
59
+ def message(receiver, current)
60
+ format(MSG, args: receiver.arguments.map(&:source).join(', '), current: current.source)
60
61
  end
61
62
  end
62
63
  end
@@ -38,7 +38,7 @@ module RuboCop
38
38
  minimum_target_rails_version 5.0
39
39
 
40
40
  def_node_matcher :pluck_candidate?, <<~PATTERN
41
- ({block numblock} (send _ {:map :collect}) $_argument (send lvar :[] $_key))
41
+ ({block numblock} (call _ {:map :collect}) $_argument (send lvar :[] $_key))
42
42
  PATTERN
43
43
 
44
44
  def on_block(node)
@@ -34,7 +34,7 @@ module RuboCop
34
34
  RESTRICT_ON_SEND = %i[pluck].freeze
35
35
 
36
36
  def_node_matcher :pluck_id_call?, <<~PATTERN
37
- (send _ :pluck {(sym :id) (send nil? :primary_key)})
37
+ (call _ :pluck {(sym :id) (send nil? :primary_key)})
38
38
  PATTERN
39
39
 
40
40
  def on_send(node)
@@ -47,6 +47,7 @@ module RuboCop
47
47
  corrector.replace(offense_range(node), 'ids')
48
48
  end
49
49
  end
50
+ alias on_csend on_send
50
51
 
51
52
  private
52
53
 
@@ -22,10 +22,13 @@ module RuboCop
22
22
  # @example
23
23
  # # bad
24
24
  # Post.where(user_id: User.active.pluck(:id))
25
+ # Post.where(user_id: User.active.ids)
26
+ # Post.where.not(user_id: User.active.pluck(:id))
25
27
  #
26
28
  # # good
27
29
  # Post.where(user_id: User.active.select(:id))
28
30
  # Post.where(user_id: active_users.select(:id))
31
+ # Post.where.not(user_id: active_users.select(:id))
29
32
  #
30
33
  # @example EnforcedStyle: conservative (default)
31
34
  # # good
@@ -40,8 +43,9 @@ module RuboCop
40
43
  include ConfigurableEnforcedStyle
41
44
  extend AutoCorrector
42
45
 
43
- MSG = 'Use `select` instead of `pluck` within `where` query method.'
44
- RESTRICT_ON_SEND = %i[pluck].freeze
46
+ MSG_SELECT = 'Use `select` instead of `pluck` within `where` query method.'
47
+ MSG_IDS = 'Use `select(:id)` instead of `ids` within `where` query method.'
48
+ RESTRICT_ON_SEND = %i[pluck ids].freeze
45
49
 
46
50
  def on_send(node)
47
51
  return unless in_where?(node)
@@ -49,17 +53,26 @@ module RuboCop
49
53
 
50
54
  range = node.loc.selector
51
55
 
52
- add_offense(range) do |corrector|
53
- corrector.replace(range, 'select')
56
+ if node.method?(:ids)
57
+ replacement = 'select(:id)'
58
+ message = MSG_IDS
59
+ else
60
+ replacement = 'select'
61
+ message = MSG_SELECT
62
+ end
63
+
64
+ add_offense(range, message: message) do |corrector|
65
+ corrector.replace(range, replacement)
54
66
  end
55
67
  end
68
+ alias on_csend on_send
56
69
 
57
70
  private
58
71
 
59
72
  def root_receiver(node)
60
73
  receiver = node.receiver
61
74
 
62
- if receiver&.send_type?
75
+ if receiver&.call_type?
63
76
  root_receiver(receiver)
64
77
  else
65
78
  receiver
@@ -72,7 +72,7 @@ module RuboCop
72
72
  end
73
73
 
74
74
  def task_name(node)
75
- first_arg = node.arguments[0]
75
+ first_arg = node.first_argument
76
76
  case first_arg&.type
77
77
  when :sym, :str
78
78
  first_arg.value.to_sym
@@ -97,7 +97,7 @@ module RuboCop
97
97
  end
98
98
 
99
99
  def with_dependencies?(node)
100
- first_arg = node.arguments[0]
100
+ first_arg = node.first_argument
101
101
  return false unless first_arg
102
102
 
103
103
  if first_arg.hash_type?
@@ -3,8 +3,43 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
+ # TODO: In the future, please support only RuboCop 1.52+ and use `RuboCop::Cop::AllowedReceivers`:
7
+ # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/cop/mixin/allowed_receivers.rb
8
+ # At that time, this duplicated module implementation can be removed.
9
+ module AllowedReceivers
10
+ def allowed_receiver?(receiver)
11
+ receiver_name = receiver_name(receiver)
12
+
13
+ allowed_receivers.include?(receiver_name)
14
+ end
15
+
16
+ def receiver_name(receiver)
17
+ return receiver_name(receiver.receiver) if receiver.receiver && !receiver.receiver.const_type?
18
+
19
+ if receiver.send_type?
20
+ if receiver.receiver
21
+ "#{receiver_name(receiver.receiver)}.#{receiver.method_name}"
22
+ else
23
+ receiver.method_name.to_s
24
+ end
25
+ else
26
+ receiver.source
27
+ end
28
+ end
29
+
30
+ def allowed_receivers
31
+ cop_config.fetch('AllowedReceivers', [])
32
+ end
33
+ end
34
+
6
35
  # Detect redundant `all` used as a receiver for Active Record query methods.
7
36
  #
37
+ # For the methods `delete_all` and `destroy_all`, this cop will only check cases where the receiver is a model.
38
+ # It will ignore cases where the receiver is an association (e.g., `user.articles.all.delete_all`).
39
+ # This is because omitting `all` from an association changes the methods
40
+ # from `ActiveRecord::Relation` to `ActiveRecord::Associations::CollectionProxy`,
41
+ # which can affect their behavior.
42
+ #
8
43
  # @safety
9
44
  # This cop is unsafe for autocorrection if the receiver for `all` is not an Active Record object.
10
45
  #
@@ -144,13 +179,15 @@ module RuboCop
144
179
  ].to_set.freeze
145
180
 
146
181
  POSSIBLE_ENUMERABLE_BLOCK_METHODS = %i[any? count find none? one? select sum].freeze
182
+ SENSITIVE_METHODS_ON_ASSOCIATION = %i[delete_all destroy_all].freeze
147
183
 
148
184
  def_node_matcher :followed_by_query_method?, <<~PATTERN
149
185
  (send (send _ :all) QUERYING_METHODS ...)
150
186
  PATTERN
151
187
 
152
188
  def on_send(node)
153
- return if !followed_by_query_method?(node.parent) || possible_enumerable_block_method?(node)
189
+ return unless followed_by_query_method?(node.parent)
190
+ return if possible_enumerable_block_method?(node) || sensitive_association_method?(node)
154
191
  return if node.receiver ? allowed_receiver?(node.receiver) : !inherit_active_record_base?(node)
155
192
 
156
193
  range_of_all_method = offense_range(node)
@@ -169,37 +206,12 @@ module RuboCop
169
206
  parent.parent&.block_type? || parent.parent&.numblock_type? || parent.first_argument&.block_pass_type?
170
207
  end
171
208
 
172
- def offense_range(node)
173
- range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
209
+ def sensitive_association_method?(node)
210
+ !node.receiver&.const_type? && SENSITIVE_METHODS_ON_ASSOCIATION.include?(node.parent.method_name)
174
211
  end
175
212
 
176
- # TODO: In the future, please support only RuboCop 1.52+ and use `RuboCop::Cop::AllowedReceivers`:
177
- # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/cop/mixin/allowed_receivers.rb
178
- # At that time, this duplicated module implementation can be removed.
179
- module AllowedReceivers
180
- def allowed_receiver?(receiver)
181
- receiver_name = receiver_name(receiver)
182
-
183
- allowed_receivers.include?(receiver_name)
184
- end
185
-
186
- def receiver_name(receiver)
187
- return receiver_name(receiver.receiver) if receiver.receiver && !receiver.receiver.const_type?
188
-
189
- if receiver.send_type?
190
- if receiver.receiver
191
- "#{receiver_name(receiver.receiver)}.#{receiver.method_name}"
192
- else
193
- receiver.method_name.to_s
194
- end
195
- else
196
- receiver.source
197
- end
198
- end
199
-
200
- def allowed_receivers
201
- cop_config.fetch('AllowedReceivers', [])
202
- end
213
+ def offense_range(node)
214
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
203
215
  end
204
216
  end
205
217
  end
@@ -100,7 +100,7 @@ module RuboCop
100
100
  else
101
101
  return false if node.arguments.empty?
102
102
 
103
- arg = node.arguments.first
103
+ arg = node.first_argument
104
104
  ->(n) { same_value?(arg, n.receiver) }
105
105
  end
106
106
 
@@ -3,25 +3,30 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Prefer `response.parsed_body` to `JSON.parse(response.body)`.
6
+ # Prefer `response.parsed_body` to custom parsing logic for `response.body`.
7
7
  #
8
8
  # @safety
9
- # This cop is unsafe because Content-Type may not be `application/json`. For example, the proprietary
10
- # Content-Type provided by corporate entities such as `application/vnd.github+json` is not supported at
11
- # `response.parsed_body` by default, so you still have to use `JSON.parse(response.body)` there.
9
+ # This cop is unsafe because Content-Type may not be `application/json` or `text/html`.
10
+ # For example, the proprietary Content-Type provided by corporate entities such as
11
+ # `application/vnd.github+json` is not supported at `response.parsed_body` by default,
12
+ # so you still have to use `JSON.parse(response.body)` there.
12
13
  #
13
14
  # @example
14
15
  # # bad
15
16
  # JSON.parse(response.body)
16
17
  #
18
+ # # bad
19
+ # Nokogiri::HTML.parse(response.body)
20
+ #
21
+ # # bad
22
+ # Nokogiri::HTML5.parse(response.body)
23
+ #
17
24
  # # good
18
25
  # response.parsed_body
19
26
  class ResponseParsedBody < Base
20
27
  extend AutoCorrector
21
28
  extend TargetRailsVersion
22
29
 
23
- MSG = 'Prefer `response.parsed_body` to `JSON.parse(response.body)`.'
24
-
25
30
  RESTRICT_ON_SEND = %i[parse].freeze
26
31
 
27
32
  minimum_target_rails_version 5.0
@@ -38,12 +43,27 @@ module RuboCop
38
43
  )
39
44
  PATTERN
40
45
 
46
+ # @!method nokogiri_html_parse_response_body(node)
47
+ def_node_matcher :nokogiri_html_parse_response_body, <<~PATTERN
48
+ (send
49
+ (const
50
+ (const {nil? cbase} :Nokogiri)
51
+ ${:HTML :HTML5}
52
+ )
53
+ :parse
54
+ (send
55
+ (send nil? :response)
56
+ :body
57
+ )
58
+ )
59
+ PATTERN
60
+
41
61
  def on_send(node)
42
- return unless json_parse_response_body?(node)
62
+ check_json_parse_response_body(node)
43
63
 
44
- add_offense(node) do |corrector|
45
- autocorrect(corrector, node)
46
- end
64
+ return unless target_rails_version >= 7.1
65
+
66
+ check_nokogiri_html_parse_response_body(node)
47
67
  end
48
68
 
49
69
  private
@@ -51,6 +71,28 @@ module RuboCop
51
71
  def autocorrect(corrector, node)
52
72
  corrector.replace(node, 'response.parsed_body')
53
73
  end
74
+
75
+ def check_json_parse_response_body(node)
76
+ return unless json_parse_response_body?(node)
77
+
78
+ add_offense(
79
+ node,
80
+ message: 'Prefer `response.parsed_body` to `JSON.parse(response.body)`.'
81
+ ) do |corrector|
82
+ autocorrect(corrector, node)
83
+ end
84
+ end
85
+
86
+ def check_nokogiri_html_parse_response_body(node)
87
+ return unless (const = nokogiri_html_parse_response_body(node))
88
+
89
+ add_offense(
90
+ node,
91
+ message: "Prefer `response.parsed_body` to `Nokogiri::#{const}.parse(response.body)`."
92
+ ) do |corrector|
93
+ autocorrect(corrector, node)
94
+ end
95
+ end
54
96
  end
55
97
  end
56
98
  end
@@ -290,10 +290,10 @@ module RuboCop
290
290
  when :change
291
291
  false
292
292
  when :remove
293
- target_rails_version >= 6.1 && all_hash_key?(node.arguments.last, :type)
293
+ target_rails_version >= 6.1 && all_hash_key?(node.last_argument, :type)
294
294
  when :change_default, :change_column_default, :change_table_comment,
295
295
  :change_column_comment
296
- all_hash_key?(node.arguments.last, :from, :to)
296
+ all_hash_key?(node.last_argument, :from, :to)
297
297
  else
298
298
  true
299
299
  end
@@ -336,10 +336,10 @@ module RuboCop
336
336
 
337
337
  # Check argument signature as no arguments or one hash
338
338
  def expected_signature?(node)
339
- !node.arguments? ||
340
- (node.arguments.one? &&
341
- node.method_name != :destroy &&
342
- (node.first_argument.hash_type? || !node.first_argument.literal?))
339
+ return true unless node.arguments?
340
+ return false if !node.arguments.one? || node.method?(:destroy)
341
+
342
+ node.first_argument.hash_type? || !node.first_argument.literal?
343
343
  end
344
344
  end
345
345
  end
@@ -124,7 +124,7 @@ module RuboCop
124
124
  end
125
125
 
126
126
  def uniqueness_part(node)
127
- pairs = node.arguments.last
127
+ pairs = node.last_argument
128
128
  return unless pairs&.hash_type?
129
129
 
130
130
  pairs.each_pair.find do |pair|
@@ -87,9 +87,9 @@ module RuboCop
87
87
 
88
88
  def environments
89
89
  @environments ||= begin
90
- e = cop_config['Environments']
91
- e += ['local'] if target_rails_version >= 7.1
92
- e
90
+ environments = cop_config['Environments'] || []
91
+ environments << 'local' if target_rails_version >= 7.1
92
+ environments
93
93
  end
94
94
  end
95
95
  end
@@ -51,7 +51,7 @@ module RuboCop
51
51
  uniqueness
52
52
  ].freeze
53
53
 
54
- RESTRICT_ON_SEND = TYPES.map { |p| "validates_#{p}_of".to_sym }.freeze
54
+ RESTRICT_ON_SEND = TYPES.map { |p| :"validates_#{p}_of" }.freeze
55
55
  ALLOWLIST = TYPES.map { |p| "validates :column, #{p}: value" }.freeze
56
56
 
57
57
  def on_send(node)
@@ -60,7 +60,7 @@ module RuboCop
60
60
  range = node.loc.selector
61
61
 
62
62
  add_offense(range, message: message(node)) do |corrector|
63
- last_argument = node.arguments.last
63
+ last_argument = node.last_argument
64
64
  return if !last_argument.literal? && !last_argument.splat_type? && !frozen_array_argument?(last_argument)
65
65
 
66
66
  corrector.replace(range, 'validates')
@@ -33,8 +33,8 @@ module RuboCop
33
33
 
34
34
  def_node_matcher :where_method_call?, <<~PATTERN
35
35
  {
36
- (send _ :where (array $str_type? $_ ?))
37
- (send _ :where $str_type? $_ ?)
36
+ (call _ :where (array $str_type? $_ ?))
37
+ (call _ :where $str_type? $_ ?)
38
38
  }
39
39
  PATTERN
40
40
 
@@ -55,6 +55,7 @@ module RuboCop
55
55
  end
56
56
  end
57
57
  end
58
+ alias on_csend on_send
58
59
 
59
60
  EQ_ANONYMOUS_RE = /\A([\w.]+)\s+=\s+\?\z/.freeze # column = ?
60
61
  IN_ANONYMOUS_RE = /\A([\w.]+)\s+IN\s+\(\?\)\z/i.freeze # column IN (?)
@@ -55,11 +55,11 @@ module RuboCop
55
55
  RESTRICT_ON_SEND = %i[exists?].freeze
56
56
 
57
57
  def_node_matcher :where_exists_call?, <<~PATTERN
58
- (send (send _ :where $...) :exists?)
58
+ (call (call _ :where $...) :exists?)
59
59
  PATTERN
60
60
 
61
61
  def_node_matcher :exists_with_args?, <<~PATTERN
62
- (send _ :exists? $...)
62
+ (call _ :exists? $...)
63
63
  PATTERN
64
64
 
65
65
  def on_send(node)
@@ -67,7 +67,7 @@ module RuboCop
67
67
  return unless convertable_args?(args)
68
68
 
69
69
  range = correction_range(node)
70
- good_method = build_good_method(args)
70
+ good_method = build_good_method(args, dot_source: node.loc.dot.source)
71
71
  message = format(MSG, good_method: good_method, bad_method: range.source)
72
72
 
73
73
  add_offense(range, message: message) do |corrector|
@@ -75,6 +75,7 @@ module RuboCop
75
75
  end
76
76
  end
77
77
  end
78
+ alias on_csend on_send
78
79
 
79
80
  private
80
81
 
@@ -108,11 +109,11 @@ module RuboCop
108
109
  end
109
110
  end
110
111
 
111
- def build_good_method(args)
112
+ def build_good_method(args, dot_source: '.')
112
113
  if exists_style?
113
114
  build_good_method_exists(args)
114
115
  elsif where_style?
115
- build_good_method_where(args)
116
+ build_good_method_where(args, dot_source)
116
117
  end
117
118
  end
118
119
 
@@ -124,11 +125,11 @@ module RuboCop
124
125
  end
125
126
  end
126
127
 
127
- def build_good_method_where(args)
128
+ def build_good_method_where(args, dot_source)
128
129
  if args.size > 1
129
- "where(#{args.map(&:source).join(', ')}).exists?"
130
+ "where(#{args.map(&:source).join(', ')})#{dot_source}exists?"
130
131
  else
131
- "where(#{args[0].source}).exists?"
132
+ "where(#{args[0].source})#{dot_source}exists?"
132
133
  end
133
134
  end
134
135
  end
@@ -36,7 +36,7 @@ module RuboCop
36
36
  PATTERN
37
37
 
38
38
  def on_send(node)
39
- return unless node.first_argument.sym_type?
39
+ return unless node.first_argument&.sym_type?
40
40
 
41
41
  root_receiver = root_receiver(node)
42
42
  where_node_and_argument(root_receiver) do |where_node, where_argument|
@@ -32,8 +32,8 @@ module RuboCop
32
32
 
33
33
  def_node_matcher :where_method_call?, <<~PATTERN
34
34
  {
35
- (send _ :where (array $str_type? $_ ?))
36
- (send _ :where $str_type? $_ ?)
35
+ (call _ :where (array $str_type? $_ ?))
36
+ (call _ :where $str_type? $_ ?)
37
37
  }
38
38
  PATTERN
39
39
 
@@ -46,7 +46,7 @@ module RuboCop
46
46
  column_and_value = extract_column_and_value(template_node, value_node)
47
47
  return unless column_and_value
48
48
 
49
- good_method = build_good_method(*column_and_value)
49
+ good_method = build_good_method(node.loc.dot.source, *column_and_value)
50
50
  message = format(MSG, good_method: good_method)
51
51
 
52
52
  add_offense(range, message: message) do |corrector|
@@ -54,6 +54,7 @@ module RuboCop
54
54
  end
55
55
  end
56
56
  end
57
+ alias on_csend on_send
57
58
 
58
59
  NOT_EQ_ANONYMOUS_RE = /\A([\w.]+)\s+(?:!=|<>)\s+\?\z/.freeze # column != ?, column <> ?
59
60
  NOT_IN_ANONYMOUS_RE = /\A([\w.]+)\s+NOT\s+IN\s+\(\?\)\z/i.freeze # column NOT IN (?)
@@ -86,13 +87,13 @@ module RuboCop
86
87
  [Regexp.last_match(1), value]
87
88
  end
88
89
 
89
- def build_good_method(column, value)
90
+ def build_good_method(dot, column, value)
90
91
  if column.include?('.')
91
92
  table, column = column.split('.')
92
93
 
93
- "where.not(#{table}: { #{column}: #{value} })"
94
+ "where#{dot}not(#{table}: { #{column}: #{value} })"
94
95
  else
95
- "where.not(#{column}: #{value})"
96
+ "where#{dot}not(#{column}: #{value})"
96
97
  end
97
98
  end
98
99
  end
@@ -127,7 +127,7 @@ module RuboCop
127
127
  private
128
128
 
129
129
  def analyze_keywords!(node)
130
- pairs = node.arguments.last
130
+ pairs = node.last_argument
131
131
  return unless pairs.hash_type?
132
132
 
133
133
  pairs.each_pair do |k, v|
@@ -158,7 +158,7 @@ module RuboCop
158
158
  end
159
159
 
160
160
  def analyze_keywords!(node)
161
- pairs = node.arguments.last
161
+ pairs = node.last_argument
162
162
  return unless pairs.hash_type?
163
163
 
164
164
  pairs.each_pair do |k, v|
@@ -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.22.1'
7
+ STRING = '2.23.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.22.1
4
+ version: 2.23.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: 2023-10-28 00:00:00.000000000 Z
13
+ date: 2023-12-16 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -60,6 +60,26 @@ dependencies:
60
60
  - - "<"
61
61
  - !ruby/object:Gem::Version
62
62
  version: '2.0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: rubocop-ast
65
+ requirement: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 1.30.0
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '2.0'
73
+ type: :runtime
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 1.30.0
80
+ - - "<"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
63
83
  description: |
64
84
  Automatic Rails code style checking tool.
65
85
  A RuboCop extension focused on enforcing Rails best practices and coding conventions.
@@ -225,7 +245,7 @@ metadata:
225
245
  homepage_uri: https://docs.rubocop.org/rubocop-rails/
226
246
  changelog_uri: https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md
227
247
  source_code_uri: https://github.com/rubocop/rubocop-rails/
228
- documentation_uri: https://docs.rubocop.org/rubocop-rails/2.22/
248
+ documentation_uri: https://docs.rubocop.org/rubocop-rails/2.23/
229
249
  bug_tracker_uri: https://github.com/rubocop/rubocop-rails/issues
230
250
  rubygems_mfa_required: 'true'
231
251
  post_install_message:
@@ -243,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
243
263
  - !ruby/object:Gem::Version
244
264
  version: '0'
245
265
  requirements: []
246
- rubygems_version: 3.5.0.dev
266
+ rubygems_version: 3.4.19
247
267
  signing_key:
248
268
  specification_version: 4
249
269
  summary: Automatic Rails code style checking tool.