rubocop-rails 2.19.1 → 2.30.3

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +70 -16
  4. data/config/default.yml +173 -28
  5. data/lib/rubocop/cop/mixin/active_record_helper.rb +16 -4
  6. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
  7. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +66 -0
  8. data/lib/rubocop/cop/mixin/index_method.rb +68 -61
  9. data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
  10. data/lib/rubocop/cop/mixin/target_rails_version.rb +27 -2
  11. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +3 -1
  12. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  13. data/lib/rubocop/cop/rails/action_filter.rb +3 -0
  14. data/lib/rubocop/cop/rails/action_order.rb +1 -5
  15. data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -2
  16. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
  17. data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
  18. data/lib/rubocop/cop/rails/active_support_on_load.rb +21 -1
  19. data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
  20. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  21. data/lib/rubocop/cop/rails/application_record.rb +4 -0
  22. data/lib/rubocop/cop/rails/assert_not.rb +0 -1
  23. data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
  24. data/lib/rubocop/cop/rails/blank.rb +1 -1
  25. data/lib/rubocop/cop/rails/bulk_change_table.rb +19 -45
  26. data/lib/rubocop/cop/rails/compact_blank.rb +29 -8
  27. data/lib/rubocop/cop/rails/content_tag.rb +2 -2
  28. data/lib/rubocop/cop/rails/dangerous_column_names.rb +448 -0
  29. data/lib/rubocop/cop/rails/date.rb +14 -5
  30. data/lib/rubocop/cop/rails/delegate.rb +53 -7
  31. data/lib/rubocop/cop/rails/duplicate_association.rb +71 -10
  32. data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
  33. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +2 -2
  34. data/lib/rubocop/cop/rails/enum_hash.rb +31 -8
  35. data/lib/rubocop/cop/rails/enum_syntax.rb +130 -0
  36. data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
  37. data/lib/rubocop/cop/rails/env_local.rb +69 -0
  38. data/lib/rubocop/cop/rails/expanded_date_range.rb +1 -1
  39. data/lib/rubocop/cop/rails/file_path.rb +186 -18
  40. data/lib/rubocop/cop/rails/find_by.rb +3 -3
  41. data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
  42. data/lib/rubocop/cop/rails/find_each.rb +1 -1
  43. data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
  44. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -1
  45. data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
  46. data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
  47. data/lib/rubocop/cop/rails/http_status.rb +16 -5
  48. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
  49. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +5 -1
  50. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +23 -3
  51. data/lib/rubocop/cop/rails/index_by.rb +28 -12
  52. data/lib/rubocop/cop/rails/index_with.rb +28 -12
  53. data/lib/rubocop/cop/rails/inquiry.rb +2 -1
  54. data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
  55. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +19 -10
  56. data/lib/rubocop/cop/rails/link_to_blank.rb +2 -2
  57. data/lib/rubocop/cop/rails/match_route.rb +1 -9
  58. data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
  59. data/lib/rubocop/cop/rails/not_null_column.rb +100 -6
  60. data/lib/rubocop/cop/rails/output.rb +3 -2
  61. data/lib/rubocop/cop/rails/pick.rb +10 -5
  62. data/lib/rubocop/cop/rails/pluck.rb +21 -1
  63. data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
  64. data/lib/rubocop/cop/rails/pluck_in_where.rb +35 -13
  65. data/lib/rubocop/cop/rails/pluralization_grammar.rb +30 -16
  66. data/lib/rubocop/cop/rails/presence.rb +1 -1
  67. data/lib/rubocop/cop/rails/present.rb +1 -3
  68. data/lib/rubocop/cop/rails/rake_environment.rb +22 -6
  69. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +190 -0
  70. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
  71. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +16 -0
  72. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +2 -2
  73. data/lib/rubocop/cop/rails/reflection_class_name.rb +2 -2
  74. data/lib/rubocop/cop/rails/refute_methods.rb +0 -1
  75. data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
  76. data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
  77. data/lib/rubocop/cop/rails/request_referer.rb +1 -1
  78. data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
  79. data/lib/rubocop/cop/rails/reversible_migration.rb +7 -5
  80. data/lib/rubocop/cop/rails/root_pathname_methods.rb +58 -15
  81. data/lib/rubocop/cop/rails/save_bang.rb +22 -14
  82. data/lib/rubocop/cop/rails/schema_comment.rb +17 -10
  83. data/lib/rubocop/cop/rails/select_map.rb +79 -0
  84. data/lib/rubocop/cop/rails/skips_model_validations.rb +9 -4
  85. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -2
  86. data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
  87. data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
  88. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +4 -5
  89. data/lib/rubocop/cop/rails/time_zone.rb +26 -11
  90. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +40 -9
  91. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +11 -26
  92. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +17 -21
  93. data/lib/rubocop/cop/rails/unknown_env.rb +5 -1
  94. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
  95. data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
  96. data/lib/rubocop/cop/rails/validation.rb +9 -4
  97. data/lib/rubocop/cop/rails/where_equals.rb +29 -12
  98. data/lib/rubocop/cop/rails/where_exists.rb +9 -9
  99. data/lib/rubocop/cop/rails/where_missing.rb +6 -2
  100. data/lib/rubocop/cop/rails/where_not.rb +18 -11
  101. data/lib/rubocop/cop/rails/where_range.rb +203 -0
  102. data/lib/rubocop/cop/rails_cops.rb +11 -0
  103. data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
  104. data/lib/rubocop/rails/plugin.rb +48 -0
  105. data/lib/rubocop/rails/schema_loader/schema.rb +8 -7
  106. data/lib/rubocop/rails/schema_loader.rb +5 -15
  107. data/lib/rubocop/rails/version.rb +1 -1
  108. data/lib/rubocop/rails.rb +1 -8
  109. data/lib/rubocop-rails.rb +12 -4
  110. metadata +55 -11
  111. data/lib/rubocop/rails/inject.rb +0 -18
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # # good
18
18
  # def change
19
19
  # change_table :users do |t|
20
- # t.remove :name, :string
20
+ # t.remove :name, type: :string
21
21
  # end
22
22
  # end
23
23
  #
@@ -215,8 +215,10 @@ module RuboCop
215
215
  end
216
216
 
217
217
  def check_drop_table_node(node)
218
+ return unless (last_argument = node.last_argument)
219
+
218
220
  drop_table_call(node) do
219
- unless node.parent.block_type? || node.last_argument.block_pass_type?
221
+ unless node.parent.any_block_type? || last_argument.block_pass_type?
220
222
  add_offense(node, message: format(MSG, action: 'drop_table(without block)'))
221
223
  end
222
224
  end
@@ -290,10 +292,10 @@ module RuboCop
290
292
  when :change
291
293
  false
292
294
  when :remove
293
- target_rails_version >= 6.1 && all_hash_key?(node.arguments.last, :type)
295
+ target_rails_version >= 6.1 && all_hash_key?(node.last_argument, :type)
294
296
  when :change_default, :change_column_default, :change_table_comment,
295
297
  :change_column_comment
296
- all_hash_key?(node.arguments.last, :from, :to)
298
+ all_hash_key?(node.last_argument, :from, :to)
297
299
  else
298
300
  true
299
301
  end
@@ -307,7 +309,7 @@ module RuboCop
307
309
 
308
310
  def within_reversible_or_up_only_block?(node)
309
311
  node.each_ancestor(:block).any? do |ancestor|
310
- (ancestor.block_type? && ancestor.send_node.method?(:reversible)) || ancestor.send_node.method?(:up_only)
312
+ (ancestor.block_type? && ancestor.method?(:reversible)) || ancestor.method?(:up_only)
311
313
  end
312
314
  end
313
315
 
@@ -12,7 +12,7 @@ module RuboCop
12
12
  # `Style/FileRead`, `Style/FileWrite` and `Rails/RootJoinChain`.
13
13
  #
14
14
  # @safety
15
- # This cop is unsafe for autocorrection because `Dir`'s `children`, `each_child`, `entries`, and `glob`
15
+ # This cop is unsafe for autocorrection because ``Dir``'s `children`, `each_child`, `entries`, and `glob`
16
16
  # methods return string element, but these methods of `Pathname` return `Pathname` element.
17
17
  #
18
18
  # @example
@@ -23,6 +23,8 @@ module RuboCop
23
23
  # File.binread(Rails.root.join('db', 'schema.rb'))
24
24
  # File.write(Rails.root.join('db', 'schema.rb'), content)
25
25
  # File.binwrite(Rails.root.join('db', 'schema.rb'), content)
26
+ # Dir.glob(Rails.root.join('db', 'schema.rb'))
27
+ # Dir[Rails.root.join('db', 'schema.rb')]
26
28
  #
27
29
  # # good
28
30
  # Rails.root.join('db', 'schema.rb').open
@@ -31,14 +33,30 @@ module RuboCop
31
33
  # Rails.root.join('db', 'schema.rb').binread
32
34
  # Rails.root.join('db', 'schema.rb').write(content)
33
35
  # Rails.root.join('db', 'schema.rb').binwrite(content)
36
+ # Rails.root.glob("db/schema.rb")
34
37
  #
35
- class RootPathnameMethods < Base
38
+ class RootPathnameMethods < Base # rubocop:disable Metrics/ClassLength
36
39
  extend AutoCorrector
37
40
  include RangeHelp
38
41
 
39
- MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
42
+ MSG = '`%<rails_root>s` is a `Pathname`, so you can use `%<replacement>s`.'
40
43
 
41
- DIR_METHODS = %i[children delete each_child empty? entries exist? glob mkdir open rmdir unlink].to_set.freeze
44
+ DIR_GLOB_METHODS = %i[[] glob].to_set.freeze
45
+
46
+ DIR_NON_GLOB_METHODS = %i[
47
+ children
48
+ delete
49
+ each_child
50
+ empty?
51
+ entries
52
+ exist?
53
+ mkdir
54
+ open
55
+ rmdir
56
+ unlink
57
+ ].to_set.freeze
58
+
59
+ DIR_METHODS = (DIR_GLOB_METHODS + DIR_NON_GLOB_METHODS).freeze
42
60
 
43
61
  FILE_METHODS = %i[
44
62
  atime
@@ -134,7 +152,8 @@ module RuboCop
134
152
 
135
153
  RESTRICT_ON_SEND = (DIR_METHODS + FILE_METHODS + FILE_TEST_METHODS + FILE_UTILS_METHODS).to_set.freeze
136
154
 
137
- def_node_matcher :pathname_method, <<~PATTERN
155
+ # @!method pathname_method_for_ruby_2_5_or_higher(node)
156
+ def_node_matcher :pathname_method_for_ruby_2_5_or_higher, <<~PATTERN
138
157
  {
139
158
  (send (const {nil? cbase} :Dir) $DIR_METHODS $_ $...)
140
159
  (send (const {nil? cbase} {:IO :File}) $FILE_METHODS $_ $...)
@@ -143,9 +162,19 @@ module RuboCop
143
162
  }
144
163
  PATTERN
145
164
 
165
+ # @!method pathname_method_for_ruby_2_4_or_lower(node)
166
+ def_node_matcher :pathname_method_for_ruby_2_4_or_lower, <<~PATTERN
167
+ {
168
+ (send (const {nil? cbase} :Dir) $DIR_NON_GLOB_METHODS $_ $...)
169
+ (send (const {nil? cbase} {:IO :File}) $FILE_METHODS $_ $...)
170
+ (send (const {nil? cbase} :FileTest) $FILE_TEST_METHODS $_ $...)
171
+ (send (const {nil? cbase} :FileUtils) $FILE_UTILS_METHODS $_ $...)
172
+ }
173
+ PATTERN
174
+
146
175
  def_node_matcher :dir_glob?, <<~PATTERN
147
176
  (send
148
- (const {cbase nil?} :Dir) :glob ...)
177
+ (const {cbase nil?} :Dir) DIR_GLOB_METHODS ...)
149
178
  PATTERN
150
179
 
151
180
  def_node_matcher :rails_root_pathname?, <<~PATTERN
@@ -162,13 +191,14 @@ module RuboCop
162
191
 
163
192
  def on_send(node)
164
193
  evidence(node) do |method, path, args, rails_root|
165
- add_offense(node, message: format(MSG, method: method, rails_root: rails_root.source)) do |corrector|
166
- replacement = if dir_glob?(node)
167
- build_path_glob_replacement(path, method)
168
- else
169
- build_path_replacement(path, method, args)
170
- end
194
+ replacement = if dir_glob?(node)
195
+ build_path_glob_replacement(path)
196
+ else
197
+ build_path_replacement(path, method, args)
198
+ end
171
199
 
200
+ message = format(MSG, rails_root: rails_root.source, replacement: replacement)
201
+ add_offense(node, message: message) do |corrector|
172
202
  corrector.replace(node, replacement)
173
203
  end
174
204
  end
@@ -183,12 +213,20 @@ module RuboCop
183
213
  yield(method, path, args, rails_root)
184
214
  end
185
215
 
186
- def build_path_glob_replacement(path, method)
216
+ def pathname_method(node)
217
+ if target_ruby_version >= 2.5
218
+ pathname_method_for_ruby_2_5_or_higher(node)
219
+ else
220
+ pathname_method_for_ruby_2_4_or_lower(node)
221
+ end
222
+ end
223
+
224
+ def build_path_glob_replacement(path)
187
225
  receiver = range_between(path.source_range.begin_pos, path.children.first.loc.selector.end_pos).source
188
226
 
189
227
  argument = path.arguments.one? ? path.first_argument.source : join_arguments(path.arguments)
190
228
 
191
- "#{receiver}.#{method}(#{argument})"
229
+ "#{receiver}.glob(#{argument})"
192
230
  end
193
231
 
194
232
  def build_path_replacement(path, method, args)
@@ -199,7 +237,12 @@ module RuboCop
199
237
  end
200
238
 
201
239
  replacement = "#{path_replacement}.#{method}"
202
- replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
240
+
241
+ if args.any?
242
+ formatted_args = args.map { |arg| arg.array_type? ? "*#{arg.source}" : arg.source }
243
+ replacement += "(#{formatted_args.join(', ')})"
244
+ end
245
+
203
246
  replacement
204
247
  end
205
248
 
@@ -182,13 +182,13 @@ 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
189
189
 
190
190
  def persisted_referenced?(assignment)
191
- return unless assignment.referenced?
191
+ return false unless assignment.referenced?
192
192
 
193
193
  assignment.variable.references.any? do |reference|
194
194
  call_to_persisted?(reference.node.parent)
@@ -196,6 +196,8 @@ module RuboCop
196
196
  end
197
197
 
198
198
  def call_to_persisted?(node)
199
+ node = node.parent.condition if node.parenthesized_call? && node.parent.if_type?
200
+
199
201
  node.send_type? && node.method?(:persisted?)
200
202
  end
201
203
 
@@ -235,18 +237,23 @@ module RuboCop
235
237
 
236
238
  def in_condition_or_compound_boolean?(node)
237
239
  node = node.block_node || node
238
- parent = node.parent
240
+ parent = node.each_ancestor.find { |ancestor| !ancestor.begin_type? }
239
241
  return false unless parent
240
242
 
241
- operator_or_single_negative?(parent) || (conditional?(parent) && node == parent.condition)
243
+ operator_or_single_negative?(parent) || (conditional?(parent) && node == deparenthesize(parent.condition))
242
244
  end
243
245
 
244
246
  def operator_or_single_negative?(node)
245
- node.or_type? || node.and_type? || single_negative?(node)
247
+ node.operator_keyword? || single_negative?(node)
246
248
  end
247
249
 
248
250
  def conditional?(parent)
249
- parent.if_type? || parent.case_type?
251
+ parent.type?(:if, :case)
252
+ end
253
+
254
+ def deparenthesize(node)
255
+ node = node.children.last while node.begin_type?
256
+ node
250
257
  end
251
258
 
252
259
  def checked_immediately?(node)
@@ -298,7 +305,7 @@ module RuboCop
298
305
 
299
306
  node = assignable_node(node)
300
307
  method, sibling_index = find_method_with_sibling_index(node.parent)
301
- return unless method && (method.def_type? || method.block_type?)
308
+ return false unless method&.type?(:def, :any_block)
302
309
 
303
310
  method.children.size == node.sibling_index + sibling_index
304
311
  end
@@ -317,12 +324,13 @@ module RuboCop
317
324
 
318
325
  def explicit_return?(node)
319
326
  ret = assignable_node(node).parent
320
- ret && (ret.return_type? || ret.next_type?)
327
+ ret&.type?(:return, :next)
321
328
  end
322
329
 
323
330
  def return_value_assigned?(node)
324
- assignment = assignable_node(node).parent
325
- assignment&.lvasgn_type?
331
+ return false unless (assignment = assignable_node(node).parent)
332
+
333
+ assignment.assignment?
326
334
  end
327
335
 
328
336
  def persist_method?(node, methods = RESTRICT_ON_SEND)
@@ -331,10 +339,10 @@ module RuboCop
331
339
 
332
340
  # Check argument signature as no arguments or one hash
333
341
  def expected_signature?(node)
334
- !node.arguments? ||
335
- (node.arguments.one? &&
336
- node.method_name != :destroy &&
337
- (node.first_argument.hash_type? || !node.first_argument.literal?))
342
+ return true unless node.arguments?
343
+ return false if !node.arguments.one? || node.method?(:destroy)
344
+
345
+ node.first_argument.hash_type? || !node.first_argument.literal?
338
346
  end
339
347
  end
340
348
  end
@@ -23,6 +23,7 @@ module RuboCop
23
23
  #
24
24
  class SchemaComment < Base
25
25
  include ActiveRecordMigrationsHelper
26
+ include MigrationsHelper
26
27
 
27
28
  COLUMN_MSG = 'New database column without `comment`.'
28
29
  TABLE_MSG = 'New database table without `comment`.'
@@ -74,17 +75,25 @@ module RuboCop
74
75
  def on_send(node)
75
76
  if add_column_without_comment?(node)
76
77
  add_offense(node, message: COLUMN_MSG)
77
- elsif create_table?(node)
78
- if create_table_without_comment?(node)
79
- add_offense(node, message: TABLE_MSG)
80
- elsif create_table_column_call_without_comment?(node)
81
- add_offense(node.parent.body, message: COLUMN_MSG)
82
- end
78
+ elsif create_table_without_comment?(node)
79
+ add_offense(node, message: TABLE_MSG)
80
+ elsif create_table_with_block?(node.parent)
81
+ check_column_within_create_table_block(node.parent.body)
83
82
  end
84
83
  end
85
84
 
86
85
  private
87
86
 
87
+ def check_column_within_create_table_block(node)
88
+ if node.begin_type?
89
+ node.child_nodes.each do |child_node|
90
+ add_offense(child_node, message: COLUMN_MSG) if t_column_without_comment?(child_node)
91
+ end
92
+ elsif t_column_without_comment?(node)
93
+ add_offense(node, message: COLUMN_MSG)
94
+ end
95
+ end
96
+
88
97
  def add_column_without_comment?(node)
89
98
  add_column?(node) && !add_column_with_comment?(node)
90
99
  end
@@ -93,10 +102,8 @@ module RuboCop
93
102
  create_table?(node) && !create_table_with_comment?(node)
94
103
  end
95
104
 
96
- def create_table_column_call_without_comment?(node)
97
- create_table_with_block?(node.parent) &&
98
- t_column?(node.parent.body) &&
99
- !t_column_with_comment?(node.parent.body)
105
+ def t_column_without_comment?(node)
106
+ t_column?(node) && !t_column_with_comment?(node)
100
107
  end
101
108
  end
102
109
  end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for uses of `select(:column_name)` with `map(&:column_name)`.
7
+ # These can be replaced with `pluck(:column_name)`.
8
+ #
9
+ # There also should be some performance improvement since it skips instantiating the model class for matches.
10
+ #
11
+ # @safety
12
+ # This cop is unsafe because the model might override the attribute getter.
13
+ # Additionally, the model's `after_initialize` hooks are skipped when using `pluck`.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # Model.select(:column_name).map(&:column_name)
18
+ #
19
+ # # good
20
+ # Model.pluck(:column_name)
21
+ #
22
+ class SelectMap < Base
23
+ extend AutoCorrector
24
+
25
+ MSG = 'Use `%<preferred_method>s` instead of `select` with `%<map_method>s`.'
26
+
27
+ RESTRICT_ON_SEND = %i[map collect].freeze
28
+
29
+ def on_send(node)
30
+ return unless node.first_argument
31
+
32
+ column_name = node.first_argument.source.delete_prefix('&:')
33
+ return unless (select_node = find_select_node(node, column_name))
34
+
35
+ offense_range = select_node.loc.selector.begin.join(node.source_range.end)
36
+ preferred_method = "pluck(:#{column_name})"
37
+ message = format(MSG, preferred_method: preferred_method, map_method: node.method_name)
38
+
39
+ add_offense(offense_range, message: message) do |corrector|
40
+ autocorrect(corrector, select_node, node, preferred_method)
41
+ end
42
+ end
43
+ alias on_csend on_send
44
+
45
+ private
46
+
47
+ def find_select_node(node, column_name)
48
+ node.descendants.detect do |select_candidate|
49
+ next if !select_candidate.call_type? || !select_candidate.method?(:select)
50
+
51
+ match_column_name?(select_candidate, column_name)
52
+ end
53
+ end
54
+
55
+ # rubocop:disable Metrics/AbcSize
56
+ def autocorrect(corrector, select_node, node, preferred_method)
57
+ corrector.remove(select_node.parent.loc.dot)
58
+ corrector.remove(select_node.loc.selector.begin.join(select_node.source_range.end))
59
+ corrector.replace(node.loc.selector.begin.join(node.source_range.end), preferred_method)
60
+ end
61
+ # rubocop:enable Metrics/AbcSize
62
+
63
+ def match_column_name?(select_candidate, column_name)
64
+ return false unless select_candidate.arguments.one?
65
+ return false unless (first_argument = select_candidate.first_argument)
66
+
67
+ argument = case select_candidate.first_argument.type
68
+ when :sym
69
+ first_argument.source.delete_prefix(':')
70
+ when :str
71
+ first_argument.value if first_argument.respond_to?(:value)
72
+ end
73
+
74
+ argument == column_name
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -9,6 +9,9 @@ module RuboCop
9
9
  #
10
10
  # Methods may be ignored from this rule by configuring a `AllowedMethods`.
11
11
  #
12
+ # @safety
13
+ # This cop is unsafe if the receiver object is not an Active Record object.
14
+ #
12
15
  # @example
13
16
  # # bad
14
17
  # Article.first.decrement!(:view_count)
@@ -58,12 +61,12 @@ module RuboCop
58
61
  def_node_matcher :good_touch?, <<~PATTERN
59
62
  {
60
63
  (send (const {nil? cbase} :FileUtils) :touch ...)
61
- (send _ :touch {true false})
64
+ (send _ :touch boolean)
62
65
  }
63
66
  PATTERN
64
67
 
65
68
  def_node_matcher :good_insert?, <<~PATTERN
66
- (send _ {:insert :insert!} _ {
69
+ (call _ {:insert :insert!} _ {
67
70
  !(hash ...)
68
71
  (hash <(pair (sym !{:returning :unique_by}) _) ...>)
69
72
  } ...)
@@ -97,7 +100,8 @@ module RuboCop
97
100
  end
98
101
 
99
102
  def forbidden_methods
100
- obsolete_result = cop_config['Blacklist']
103
+ # TODO: Remove when RuboCop Rails 3 releases.
104
+ obsolete_result = cop_config['Blacklist'] # rubocop:disable InternalAffairs/UndefinedConfig
101
105
  if obsolete_result
102
106
  warn '`Blacklist` has been renamed to `ForbiddenMethods`.' unless @displayed_forbidden_warning
103
107
  @displayed_forbidden_warning = true
@@ -108,7 +112,8 @@ module RuboCop
108
112
  end
109
113
 
110
114
  def allowed_methods
111
- obsolete_result = cop_config['Whitelist']
115
+ # TODO: Remove when RuboCop Rails 3 releases.
116
+ obsolete_result = cop_config['Whitelist'] # rubocop:disable InternalAffairs/UndefinedConfig
112
117
  if obsolete_result
113
118
  warn '`Whitelist` has been renamed to `AllowedMethods`.' unless @displayed_allowed_warning
114
119
  @displayed_allowed_warning = true
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- #
7
6
  # Checks SQL heredocs to use `.squish`.
8
7
  #
9
8
  # @safety
@@ -69,7 +68,7 @@ module RuboCop
69
68
  end
70
69
 
71
70
  def using_squish?(node)
72
- node.parent&.send_type? && node.parent&.method?(:squish)
71
+ node.parent&.send_type? && node.parent.method?(:squish)
73
72
  end
74
73
 
75
74
  def singleline_comments_present?(node)
@@ -33,7 +33,7 @@ module RuboCop
33
33
 
34
34
  def on_send(node)
35
35
  return unless (receiver = node.receiver)
36
- return unless receiver.str_type? || receiver.dstr_type?
36
+ return unless receiver.type?(:str, :dstr)
37
37
  return unless receiver.respond_to?(:heredoc?) && receiver.heredoc?
38
38
 
39
39
  register_offense(node, receiver)
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Enforces the use of `ActionController::Parameters#expect` as a method for strong parameter handling.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is considered unsafe because there are cases where the HTTP status may change
10
+ # from 500 to 400 when handling invalid parameters. This change, however, reflects an intentional
11
+ # incompatibility introduced for valid reasons by the `expect` method, which aligns better with
12
+ # strong parameter conventions.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # params.require(:user).permit(:name, :age)
18
+ # params.permit(user: [:name, :age]).require(:user)
19
+ #
20
+ # # good
21
+ # params.expect(user: [:name, :age])
22
+ #
23
+ class StrongParametersExpect < Base
24
+ extend AutoCorrector
25
+ extend TargetRailsVersion
26
+
27
+ MSG = 'Use `%<prefer>s` instead.'
28
+ RESTRICT_ON_SEND = %i[require permit].freeze
29
+
30
+ minimum_target_rails_version 8.0
31
+
32
+ def_node_matcher :params_require_permit, <<~PATTERN
33
+ $(call
34
+ $(call
35
+ (send nil? :params) :require _) :permit _+)
36
+ PATTERN
37
+
38
+ def_node_matcher :params_permit_require, <<~PATTERN
39
+ $(call
40
+ $(call
41
+ (send nil? :params) :permit (hash (pair _require_param_name _ )))
42
+ :require _require_param_name)
43
+ PATTERN
44
+
45
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
46
+ def on_send(node)
47
+ return if part_of_ignored_node?(node)
48
+
49
+ if (permit_method, require_method = params_require_permit(node))
50
+ range = offense_range(require_method, node)
51
+ prefer = expect_method(require_method, permit_method)
52
+ replace_argument = true
53
+ elsif (require_method, permit_method = params_permit_require(node))
54
+ range = offense_range(permit_method, node)
55
+ prefer = "expect(#{permit_method.arguments.map(&:source).join(', ')})"
56
+ replace_argument = false
57
+ else
58
+ return
59
+ end
60
+
61
+ add_offense(range, message: format(MSG, prefer: prefer)) do |corrector|
62
+ corrector.remove(require_method.receiver.source_range.end.join(require_method.source_range.end))
63
+ corrector.replace(permit_method.loc.selector, 'expect')
64
+ if replace_argument
65
+ corrector.insert_before(permit_method.first_argument, "#{require_key(require_method)}[")
66
+ corrector.insert_after(permit_method.last_argument, ']')
67
+ end
68
+ end
69
+
70
+ ignore_node(node)
71
+ end
72
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
73
+ alias on_csend on_send
74
+
75
+ private
76
+
77
+ def offense_range(method_node, node)
78
+ method_node.loc.selector.join(node.source_range.end)
79
+ end
80
+
81
+ def expect_method(require_method, permit_method)
82
+ require_key = require_key(require_method)
83
+ permit_args = permit_method.arguments.map(&:source).join(', ')
84
+
85
+ arguments = "#{require_key}[#{permit_args}]"
86
+
87
+ "expect(#{arguments})"
88
+ end
89
+
90
+ def require_key(require_method)
91
+ if (first_argument = require_method.first_argument).respond_to?(:value)
92
+ require_arg = first_argument.value
93
+ separator = ': '
94
+ else
95
+ require_arg = first_argument.source
96
+ separator = ' => '
97
+ end
98
+
99
+ "#{require_arg}#{separator}"
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -18,8 +18,9 @@ module RuboCop
18
18
  # t.boolean :active, default: true, null: false
19
19
  #
20
20
  class ThreeStateBooleanColumn < Base
21
- MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
21
+ include MigrationsHelper
22
22
 
23
+ MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
23
24
  RESTRICT_ON_SEND = %i[add_column column boolean].freeze
24
25
 
25
26
  def_node_matcher :three_state_boolean?, <<~PATTERN
@@ -35,7 +36,7 @@ module RuboCop
35
36
  PATTERN
36
37
 
37
38
  def_node_search :change_column_null?, <<~PATTERN
38
- (send nil? :change_column_null {(sym %1) (str %1)} {(sym %2) (str %2)} false)
39
+ (send nil? :change_column_null %1 %2 false)
39
40
  PATTERN
40
41
 
41
42
  def on_send(node)
@@ -46,9 +47,7 @@ module RuboCop
46
47
 
47
48
  def_node = node.each_ancestor(:def, :defs).first
48
49
  table_node = table_node(node)
49
- if def_node && (table_node.nil? || change_column_null?(def_node, table_node.value, column_node.value))
50
- return
51
- end
50
+ return if def_node && (table_node.nil? || change_column_null?(def_node, table_node, column_node))
52
51
 
53
52
  add_offense(node)
54
53
  end