rubocop-rails 2.11.2 → 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +23 -2
  4. data/config/default.yml +316 -20
  5. data/config/obsoletion.yml +10 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +17 -8
  7. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +32 -0
  8. data/lib/rubocop/cop/mixin/class_send_node_helper.rb +20 -0
  9. data/lib/rubocop/cop/mixin/enforce_superclass.rb +1 -1
  10. data/lib/rubocop/cop/mixin/index_method.rb +7 -17
  11. data/lib/rubocop/cop/mixin/migrations_helper.rb +26 -0
  12. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +112 -0
  13. data/lib/rubocop/cop/rails/action_controller_test_case.rb +47 -0
  14. data/lib/rubocop/cop/rails/action_filter.rb +2 -2
  15. data/lib/rubocop/cop/rails/action_order.rb +116 -0
  16. data/lib/rubocop/cop/rails/active_record_aliases.rb +9 -6
  17. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +8 -13
  18. data/lib/rubocop/cop/rails/active_record_override.rb +2 -5
  19. data/lib/rubocop/cop/rails/active_support_aliases.rb +1 -1
  20. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  21. data/lib/rubocop/cop/rails/add_column_index.rb +3 -6
  22. data/lib/rubocop/cop/rails/after_commit_override.rb +3 -13
  23. data/lib/rubocop/cop/rails/application_controller.rb +6 -2
  24. data/lib/rubocop/cop/rails/application_job.rb +7 -3
  25. data/lib/rubocop/cop/rails/application_mailer.rb +6 -2
  26. data/lib/rubocop/cop/rails/application_record.rb +7 -2
  27. data/lib/rubocop/cop/rails/arel_star.rb +8 -2
  28. data/lib/rubocop/cop/rails/assert_not.rb +1 -1
  29. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +1 -1
  30. data/lib/rubocop/cop/rails/belongs_to.rb +2 -5
  31. data/lib/rubocop/cop/rails/blank.rb +13 -13
  32. data/lib/rubocop/cop/rails/bulk_change_table.rb +28 -31
  33. data/lib/rubocop/cop/rails/compact_blank.rb +111 -0
  34. data/lib/rubocop/cop/rails/content_tag.rb +42 -33
  35. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +16 -8
  36. data/lib/rubocop/cop/rails/date.rb +12 -17
  37. data/lib/rubocop/cop/rails/default_scope.rb +1 -1
  38. data/lib/rubocop/cop/rails/delegate.rb +24 -18
  39. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +2 -2
  40. data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +168 -0
  41. data/lib/rubocop/cop/rails/dot_separated_keys.rb +71 -0
  42. data/lib/rubocop/cop/rails/duplicate_association.rb +56 -0
  43. data/lib/rubocop/cop/rails/duplicate_scope.rb +46 -0
  44. data/lib/rubocop/cop/rails/duration_arithmetic.rb +98 -0
  45. data/lib/rubocop/cop/rails/dynamic_find_by.rb +31 -15
  46. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +6 -2
  47. data/lib/rubocop/cop/rails/enum_hash.rb +3 -4
  48. data/lib/rubocop/cop/rails/enum_uniqueness.rb +3 -6
  49. data/lib/rubocop/cop/rails/environment_comparison.rb +3 -4
  50. data/lib/rubocop/cop/rails/environment_variable_access.rb +1 -1
  51. data/lib/rubocop/cop/rails/exit.rb +1 -1
  52. data/lib/rubocop/cop/rails/expanded_date_range.rb +39 -23
  53. data/lib/rubocop/cop/rails/file_path.rb +41 -24
  54. data/lib/rubocop/cop/rails/find_by.rb +1 -1
  55. data/lib/rubocop/cop/rails/find_by_id.rb +3 -3
  56. data/lib/rubocop/cop/rails/find_each.rb +27 -4
  57. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  58. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +1 -1
  59. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +19 -9
  60. data/lib/rubocop/cop/rails/helper_instance_variable.rb +3 -3
  61. data/lib/rubocop/cop/rails/http_positional_arguments.rb +29 -11
  62. data/lib/rubocop/cop/rails/http_status.rb +6 -11
  63. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +96 -0
  64. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +1 -1
  65. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +110 -0
  66. data/lib/rubocop/cop/rails/ignored_columns_assignment.rb +50 -0
  67. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +5 -14
  68. data/lib/rubocop/cop/rails/index_by.rb +8 -8
  69. data/lib/rubocop/cop/rails/index_with.rb +8 -8
  70. data/lib/rubocop/cop/rails/inquiry.rb +1 -1
  71. data/lib/rubocop/cop/rails/inverse_of.rb +20 -10
  72. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +44 -16
  73. data/lib/rubocop/cop/rails/link_to_blank.rb +4 -7
  74. data/lib/rubocop/cop/rails/mailer_name.rb +9 -5
  75. data/lib/rubocop/cop/rails/match_route.rb +1 -1
  76. data/lib/rubocop/cop/rails/migration_class_name.rb +63 -0
  77. data/lib/rubocop/cop/rails/negate_include.rb +5 -4
  78. data/lib/rubocop/cop/rails/not_null_column.rb +10 -7
  79. data/lib/rubocop/cop/rails/order_by_id.rb +2 -3
  80. data/lib/rubocop/cop/rails/output.rb +26 -10
  81. data/lib/rubocop/cop/rails/output_safety.rb +6 -2
  82. data/lib/rubocop/cop/rails/pick.rb +8 -1
  83. data/lib/rubocop/cop/rails/pluck.rb +51 -11
  84. data/lib/rubocop/cop/rails/pluck_id.rb +5 -2
  85. data/lib/rubocop/cop/rails/pluck_in_where.rb +8 -7
  86. data/lib/rubocop/cop/rails/pluralization_grammar.rb +4 -5
  87. data/lib/rubocop/cop/rails/presence.rb +22 -13
  88. data/lib/rubocop/cop/rails/present.rb +10 -13
  89. data/lib/rubocop/cop/rails/rake_environment.rb +8 -3
  90. data/lib/rubocop/cop/rails/read_write_attribute.rb +52 -15
  91. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +7 -17
  92. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +3 -3
  93. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +257 -0
  94. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +31 -27
  95. data/lib/rubocop/cop/rails/redundant_travel_back.rb +57 -0
  96. data/lib/rubocop/cop/rails/reflection_class_name.rb +39 -4
  97. data/lib/rubocop/cop/rails/refute_methods.rb +1 -5
  98. data/lib/rubocop/cop/rails/relative_date_constant.rb +9 -9
  99. data/lib/rubocop/cop/rails/render_inline.rb +1 -1
  100. data/lib/rubocop/cop/rails/render_plain_text.rb +1 -1
  101. data/lib/rubocop/cop/rails/request_referer.rb +2 -3
  102. data/lib/rubocop/cop/rails/require_dependency.rb +2 -2
  103. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  104. data/lib/rubocop/cop/rails/reversible_migration.rb +29 -67
  105. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +6 -15
  106. data/lib/rubocop/cop/rails/root_join_chain.rb +72 -0
  107. data/lib/rubocop/cop/rails/root_pathname_methods.rb +238 -0
  108. data/lib/rubocop/cop/rails/root_public_path.rb +59 -0
  109. data/lib/rubocop/cop/rails/safe_navigation.rb +8 -13
  110. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +14 -7
  111. data/lib/rubocop/cop/rails/save_bang.rb +30 -23
  112. data/lib/rubocop/cop/rails/schema_comment.rb +104 -0
  113. data/lib/rubocop/cop/rails/scope_args.rb +6 -2
  114. data/lib/rubocop/cop/rails/short_i18n.rb +3 -6
  115. data/lib/rubocop/cop/rails/skips_model_validations.rb +3 -4
  116. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +13 -8
  117. data/lib/rubocop/cop/rails/strip_heredoc.rb +56 -0
  118. data/lib/rubocop/cop/rails/table_name_assignment.rb +44 -0
  119. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +73 -0
  120. data/lib/rubocop/cop/rails/time_zone.rb +37 -32
  121. data/lib/rubocop/cop/rails/time_zone_assignment.rb +4 -4
  122. data/lib/rubocop/cop/rails/to_formatted_s.rb +46 -0
  123. data/lib/rubocop/cop/rails/to_s_with_argument.rb +78 -0
  124. data/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb +49 -0
  125. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +99 -0
  126. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +32 -41
  127. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +16 -9
  128. data/lib/rubocop/cop/rails/unknown_env.rb +3 -5
  129. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +9 -2
  130. data/lib/rubocop/cop/rails/validation.rb +5 -13
  131. data/lib/rubocop/cop/rails/where_equals.rb +6 -2
  132. data/lib/rubocop/cop/rails/where_exists.rb +11 -10
  133. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  134. data/lib/rubocop/cop/rails/where_not.rb +2 -2
  135. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  136. data/lib/rubocop/cop/rails_cops.rb +34 -0
  137. data/lib/rubocop/rails/schema_loader/schema.rb +8 -5
  138. data/lib/rubocop/rails/version.rb +1 -1
  139. data/lib/rubocop/rails.rb +1 -1
  140. data/lib/rubocop-rails.rb +19 -0
  141. metadata +42 -8
  142. data/bin/setup +0 -7
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks whether the change method of the migration file is
6
+ # Checks whether the change method of the migration file is
7
7
  # reversible.
8
8
  #
9
9
  # @example
@@ -16,23 +16,15 @@ module RuboCop
16
16
  #
17
17
  # # good
18
18
  # def change
19
- # create_table :users do |t|
20
- # t.string :name
19
+ # change_table :users do |t|
20
+ # t.remove :name, :string
21
21
  # end
22
22
  # end
23
23
  #
24
24
  # # good
25
25
  # def change
26
- # reversible do |dir|
27
- # change_table :users do |t|
28
- # dir.up do
29
- # t.column :name, :string
30
- # end
31
- #
32
- # dir.down do
33
- # t.remove :name
34
- # end
35
- # end
26
+ # create_table :users do |t|
27
+ # t.string :name
36
28
  # end
37
29
  # end
38
30
  #
@@ -114,21 +106,6 @@ module RuboCop
114
106
  # end
115
107
  # end
116
108
  #
117
- # # good
118
- # def change
119
- # reversible do |dir|
120
- # change_table :users do |t|
121
- # dir.up do
122
- # t.change :price, :string
123
- # end
124
- #
125
- # dir.down do
126
- # t.change :price, :integer
127
- # end
128
- # end
129
- # end
130
- # end
131
- #
132
109
  # @example
133
110
  # # remove_columns
134
111
  #
@@ -173,13 +150,13 @@ module RuboCop
173
150
  # def change
174
151
  # remove_index :users, column: :email
175
152
  # end
176
- #
177
- # @see https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html
178
153
  class ReversibleMigration < Base
154
+ include MigrationsHelper
155
+
179
156
  MSG = '%<action>s is not reversible.'
180
157
 
181
158
  def_node_matcher :irreversible_schema_statement_call, <<~PATTERN
182
- (send nil? ${:execute :remove_belongs_to} ...)
159
+ (send nil? ${:change_column :execute} ...)
183
160
  PATTERN
184
161
 
185
162
  def_node_matcher :drop_table_call, <<~PATTERN
@@ -207,7 +184,7 @@ module RuboCop
207
184
  PATTERN
208
185
 
209
186
  def on_send(node)
210
- return unless within_change_method?(node)
187
+ return unless in_migration?(node) && within_change_method?(node)
211
188
  return if within_reversible_or_up_only_block?(node)
212
189
 
213
190
  check_irreversible_schema_statement_node(node)
@@ -220,13 +197,15 @@ module RuboCop
220
197
  end
221
198
 
222
199
  def on_block(node)
223
- return unless within_change_method?(node)
200
+ return unless in_migration?(node) && within_change_method?(node)
224
201
  return if within_reversible_or_up_only_block?(node)
225
202
  return if node.body.nil?
226
203
 
227
204
  check_change_table_node(node.send_node, node.body)
228
205
  end
229
206
 
207
+ alias on_numblock on_block
208
+
230
209
  private
231
210
 
232
211
  def check_irreversible_schema_statement_node(node)
@@ -238,10 +217,7 @@ module RuboCop
238
217
  def check_drop_table_node(node)
239
218
  drop_table_call(node) do
240
219
  unless node.parent.block_type? || node.last_argument.block_pass_type?
241
- add_offense(
242
- node,
243
- message: format(MSG, action: 'drop_table(without block)')
244
- )
220
+ add_offense(node, message: format(MSG, action: 'drop_table(without block)'))
245
221
  end
246
222
  end
247
223
  end
@@ -249,22 +225,12 @@ module RuboCop
249
225
  def check_reversible_hash_node(node)
250
226
  return if reversible_change_table_call?(node)
251
227
 
252
- add_offense(
253
- node,
254
- message: format(
255
- MSG, action: "#{node.method_name}(without :from and :to)"
256
- )
257
- )
228
+ add_offense(node, message: format(MSG, action: "#{node.method_name}(without :from and :to)"))
258
229
  end
259
230
 
260
231
  def check_remove_column_node(node)
261
232
  remove_column_call(node) do |args|
262
- if args.to_a.size < 3
263
- add_offense(
264
- node,
265
- message: format(MSG, action: 'remove_column(without type)')
266
- )
267
- end
233
+ add_offense(node, message: format(MSG, action: 'remove_column(without type)')) if args.to_a.size < 3
268
234
  end
269
235
  end
270
236
 
@@ -293,10 +259,7 @@ module RuboCop
293
259
  unless all_hash_key?(args, :type) && target_rails_version >= 6.1
294
260
  action = target_rails_version >= 6.1 ? 'remove_columns(without type)' : 'remove_columns'
295
261
 
296
- add_offense(
297
- node,
298
- message: format(MSG, action: action)
299
- )
262
+ add_offense(node, message: format(MSG, action: action))
300
263
  end
301
264
  end
302
265
  end
@@ -304,29 +267,30 @@ module RuboCop
304
267
  def check_remove_index_node(node)
305
268
  remove_index_call(node) do |args|
306
269
  if args.hash_type? && !all_hash_key?(args, :column)
307
- add_offense(
308
- node,
309
- message: format(MSG, action: 'remove_index(without column)')
310
- )
270
+ add_offense(node, message: format(MSG, action: 'remove_index(without column)'))
311
271
  end
312
272
  end
313
273
  end
314
274
 
315
275
  def check_change_table_offense(receiver, node)
316
276
  method_name = node.method_name
317
- return if receiver != node.receiver &&
318
- reversible_change_table_call?(node)
277
+ return if receiver != node.receiver && reversible_change_table_call?(node)
278
+
279
+ action = if method_name == :remove
280
+ target_rails_version >= 6.1 ? 't.remove (without type)' : 't.remove'
281
+ else
282
+ "change_table(with #{method_name})"
283
+ end
319
284
 
320
- add_offense(
321
- node,
322
- message: format(MSG, action: "change_table(with #{method_name})")
323
- )
285
+ add_offense(node, message: format(MSG, action: action))
324
286
  end
325
287
 
326
288
  def reversible_change_table_call?(node)
327
289
  case node.method_name
328
- when :change, :remove
290
+ when :change
329
291
  false
292
+ when :remove
293
+ target_rails_version >= 6.1 && all_hash_key?(node.arguments.last, :type)
330
294
  when :change_default, :change_column_default, :change_table_comment,
331
295
  :change_column_comment
332
296
  all_hash_key?(node.arguments.last, :from, :to)
@@ -343,9 +307,7 @@ module RuboCop
343
307
 
344
308
  def within_reversible_or_up_only_block?(node)
345
309
  node.each_ancestor(:block).any? do |ancestor|
346
- ancestor.block_type? &&
347
- ancestor.send_node.method?(:reversible) ||
348
- ancestor.send_node.method?(:up_only)
310
+ (ancestor.block_type? && ancestor.send_node.method?(:reversible)) || ancestor.send_node.method?(:up_only)
349
311
  end
350
312
  end
351
313
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks whether the migration implements
6
+ # Checks whether the migration implements
7
7
  # either a `change` method or both an `up` and a `down`
8
8
  # method.
9
9
  #
@@ -43,29 +43,20 @@ module RuboCop
43
43
  # end
44
44
  # end
45
45
  class ReversibleMigrationMethodDefinition < Base
46
- MSG = 'Migrations must contain either a `change` method, or ' \
47
- 'both an `up` and a `down` method.'
46
+ include MigrationsHelper
48
47
 
49
- def_node_matcher :migration_class?, <<~PATTERN
50
- (class
51
- (const nil? _)
52
- (send
53
- (const (const {nil? cbase} :ActiveRecord) :Migration)
54
- :[]
55
- (float _))
56
- _)
57
- PATTERN
48
+ MSG = 'Migrations must contain either a `change` method, or both an `up` and a `down` method.'
58
49
 
59
50
  def_node_matcher :change_method?, <<~PATTERN
60
- [ #migration_class? `(def :change (args) _) ]
51
+ `(def :change (args) _)
61
52
  PATTERN
62
53
 
63
54
  def_node_matcher :up_and_down_methods?, <<~PATTERN
64
- [ #migration_class? `(def :up (args) _) `(def :down (args) _) ]
55
+ [`(def :up (args) _) `(def :down (args) _)]
65
56
  PATTERN
66
57
 
67
58
  def on_class(node)
68
- return if change_method?(node) || up_and_down_methods?(node)
59
+ return if !migration_class?(node) || change_method?(node) || up_and_down_methods?(node)
69
60
 
70
61
  add_offense(node)
71
62
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Use a single `#join` instead of chaining on `Rails.root` or `Rails.public_path`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # Rails.root.join('db').join('schema.rb')
11
+ # Rails.root.join('db').join(migrate).join('migration.rb')
12
+ # Rails.public_path.join('path').join('file.pdf')
13
+ # Rails.public_path.join('path').join(to).join('file.pdf')
14
+ #
15
+ # # good
16
+ # Rails.root.join('db', 'schema.rb')
17
+ # Rails.root.join('db', migrate, 'migration.rb')
18
+ # Rails.public_path.join('path', 'file.pdf')
19
+ # Rails.public_path.join('path', to, 'file.pdf')
20
+ #
21
+ class RootJoinChain < Base
22
+ extend AutoCorrector
23
+ include RangeHelp
24
+
25
+ MSG = 'Use `%<root>s.join(...)` instead of chaining `#join` calls.'
26
+
27
+ RESTRICT_ON_SEND = %i[join].to_set.freeze
28
+
29
+ # @!method rails_root?(node)
30
+ def_node_matcher :rails_root?, <<~PATTERN
31
+ (send (const {nil? cbase} :Rails) {:root :public_path})
32
+ PATTERN
33
+
34
+ # @!method join?(node)
35
+ def_node_matcher :join?, <<~PATTERN
36
+ (send _ :join $...)
37
+ PATTERN
38
+
39
+ def on_send(node)
40
+ evidence(node) do |rails_node, args|
41
+ add_offense(node, message: format(MSG, root: rails_node.source)) do |corrector|
42
+ range = range_between(rails_node.loc.selector.end_pos, node.source_range.end_pos)
43
+ replacement = ".join(#{args.map(&:source).join(', ')})"
44
+
45
+ corrector.replace(range, replacement)
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def evidence(node)
53
+ # Are we at the *end* of the join chain?
54
+ return if join?(node.parent)
55
+ # Is there only one join?
56
+ return if rails_root?(node.receiver)
57
+
58
+ all_args = []
59
+
60
+ while (args = join?(node))
61
+ all_args = args + all_args
62
+ node = node.receiver
63
+ end
64
+
65
+ rails_root?(node) do
66
+ yield(node, all_args)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Use `Rails.root` IO methods instead of passing it to `File`.
7
+ #
8
+ # `Rails.root` is an instance of `Pathname`
9
+ # so we can apply many IO methods directly.
10
+ #
11
+ # This cop works best when used together with
12
+ # `Style/FileRead`, `Style/FileWrite` and `Rails/RootJoinChain`.
13
+ #
14
+ # @safety
15
+ # This cop is unsafe for autocorrection because `Dir`'s `children`, `each_child`, `entries`, and `glob`
16
+ # methods return string element, but these methods of `Pathname` return `Pathname` element.
17
+ #
18
+ # @example
19
+ # # bad
20
+ # File.open(Rails.root.join('db', 'schema.rb'))
21
+ # File.open(Rails.root.join('db', 'schema.rb'), 'w')
22
+ # File.read(Rails.root.join('db', 'schema.rb'))
23
+ # File.binread(Rails.root.join('db', 'schema.rb'))
24
+ # File.write(Rails.root.join('db', 'schema.rb'), content)
25
+ # File.binwrite(Rails.root.join('db', 'schema.rb'), content)
26
+ #
27
+ # # good
28
+ # Rails.root.join('db', 'schema.rb').open
29
+ # Rails.root.join('db', 'schema.rb').open('w')
30
+ # Rails.root.join('db', 'schema.rb').read
31
+ # Rails.root.join('db', 'schema.rb').binread
32
+ # Rails.root.join('db', 'schema.rb').write(content)
33
+ # Rails.root.join('db', 'schema.rb').binwrite(content)
34
+ #
35
+ class RootPathnameMethods < Base
36
+ extend AutoCorrector
37
+ include RangeHelp
38
+
39
+ MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
40
+
41
+ DIR_METHODS = %i[children delete each_child empty? entries exist? glob mkdir open rmdir unlink].to_set.freeze
42
+
43
+ FILE_METHODS = %i[
44
+ atime
45
+ basename
46
+ binread
47
+ binwrite
48
+ birthtime
49
+ blockdev?
50
+ chardev?
51
+ chmod
52
+ chown
53
+ ctime
54
+ delete
55
+ directory?
56
+ dirname
57
+ empty?
58
+ executable?
59
+ executable_real?
60
+ exist?
61
+ expand_path
62
+ extname
63
+ file?
64
+ fnmatch
65
+ fnmatch?
66
+ ftype
67
+ grpowned?
68
+ join
69
+ lchmod
70
+ lchown
71
+ lstat
72
+ mtime
73
+ open
74
+ owned?
75
+ pipe?
76
+ read
77
+ readable?
78
+ readable_real?
79
+ readlines
80
+ readlink
81
+ realdirpath
82
+ realpath
83
+ rename
84
+ setgid?
85
+ setuid?
86
+ size
87
+ size?
88
+ socket?
89
+ split
90
+ stat
91
+ sticky?
92
+ symlink?
93
+ sysopen
94
+ truncate
95
+ unlink
96
+ utime
97
+ world_readable?
98
+ world_writable?
99
+ writable?
100
+ writable_real?
101
+ write
102
+ zero?
103
+ ].to_set.freeze
104
+
105
+ FILE_TEST_METHODS = %i[
106
+ blockdev?
107
+ chardev?
108
+ directory?
109
+ empty?
110
+ executable?
111
+ executable_real?
112
+ exist?
113
+ file?
114
+ grpowned?
115
+ owned?
116
+ pipe?
117
+ readable?
118
+ readable_real?
119
+ setgid?
120
+ setuid?
121
+ size
122
+ size?
123
+ socket?
124
+ sticky?
125
+ symlink?
126
+ world_readable?
127
+ world_writable?
128
+ writable?
129
+ writable_real?
130
+ zero?
131
+ ].to_set.freeze
132
+
133
+ FILE_UTILS_METHODS = %i[chmod chown mkdir mkpath rmdir rmtree].to_set.freeze
134
+
135
+ RESTRICT_ON_SEND = (DIR_METHODS + FILE_METHODS + FILE_TEST_METHODS + FILE_UTILS_METHODS).to_set.freeze
136
+
137
+ def_node_matcher :pathname_method, <<~PATTERN
138
+ {
139
+ (send (const {nil? cbase} :Dir) $DIR_METHODS $_ $...)
140
+ (send (const {nil? cbase} {:IO :File}) $FILE_METHODS $_ $...)
141
+ (send (const {nil? cbase} :FileTest) $FILE_TEST_METHODS $_ $...)
142
+ (send (const {nil? cbase} :FileUtils) $FILE_UTILS_METHODS $_ $...)
143
+ }
144
+ PATTERN
145
+
146
+ def_node_matcher :dir_glob?, <<~PATTERN
147
+ (send
148
+ (const {cbase nil?} :Dir) :glob ...)
149
+ PATTERN
150
+
151
+ def_node_matcher :rails_root_pathname?, <<~PATTERN
152
+ {
153
+ $#rails_root?
154
+ (send $#rails_root? :join ...)
155
+ }
156
+ PATTERN
157
+
158
+ # @!method rails_root?(node)
159
+ def_node_matcher :rails_root?, <<~PATTERN
160
+ (send (const {nil? cbase} :Rails) {:root :public_path})
161
+ PATTERN
162
+
163
+ def on_send(node)
164
+ 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
171
+
172
+ corrector.replace(node, replacement)
173
+ end
174
+ end
175
+ end
176
+
177
+ private
178
+
179
+ def evidence(node)
180
+ return if node.method?(:open) && node.parent&.send_type?
181
+ return unless (method, path, args = pathname_method(node)) && (rails_root = rails_root_pathname?(path))
182
+
183
+ yield(method, path, args, rails_root)
184
+ end
185
+
186
+ def build_path_glob_replacement(path, method)
187
+ receiver = range_between(path.source_range.begin_pos, path.children.first.loc.selector.end_pos).source
188
+
189
+ argument = path.arguments.one? ? path.first_argument.source : join_arguments(path.arguments)
190
+
191
+ "#{receiver}.#{method}(#{argument})"
192
+ end
193
+
194
+ def build_path_replacement(path, method, args)
195
+ path_replacement = path.source
196
+ if path.arguments? && !path.parenthesized_call?
197
+ path_replacement[' '] = '('
198
+ path_replacement << ')'
199
+ end
200
+
201
+ replacement = "#{path_replacement}.#{method}"
202
+ replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
203
+ replacement
204
+ end
205
+
206
+ def include_interpolation?(arguments)
207
+ arguments.any? do |argument|
208
+ argument.children.any? { |child| child.respond_to?(:begin_type?) && child.begin_type? }
209
+ end
210
+ end
211
+
212
+ def join_arguments(arguments)
213
+ use_interpolation = false
214
+
215
+ joined_arguments = arguments.map do |arg|
216
+ if arg.respond_to?(:value)
217
+ arg.value
218
+ else
219
+ use_interpolation = true
220
+ "\#{#{arg.source}}"
221
+ end
222
+ end.join('/')
223
+ quote = enforce_double_quotes? || include_interpolation?(arguments) || use_interpolation ? '"' : "'"
224
+
225
+ "#{quote}#{joined_arguments}#{quote}"
226
+ end
227
+
228
+ def enforce_double_quotes?
229
+ string_literals_config['EnforcedStyle'] == 'double_quotes'
230
+ end
231
+
232
+ def string_literals_config
233
+ config.for_cop('Style/StringLiterals')
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Favor `Rails.public_path` over `Rails.root` with `'public'`
7
+ #
8
+ # @example
9
+ # # bad
10
+ # Rails.root.join('public')
11
+ # Rails.root.join('public/file.pdf')
12
+ # Rails.root.join('public', 'file.pdf')
13
+ #
14
+ # # good
15
+ # Rails.public_path
16
+ # Rails.public_path.join('file.pdf')
17
+ # Rails.public_path.join('file.pdf')
18
+ #
19
+ class RootPublicPath < Base
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Use `Rails.public_path`.'
23
+
24
+ RESTRICT_ON_SEND = %i[join].to_set.freeze
25
+
26
+ PATTERN = %r{\Apublic(/|\z)}.freeze
27
+
28
+ def_node_matcher :rails_root_public, <<~PATTERN
29
+ (send
30
+ (send
31
+ $(const {nil? cbase} :Rails) :root) :join
32
+ (str $#public_path?) $...)
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ return unless (rails, maybe_public_path, other_args = rails_root_public(node))
37
+
38
+ add_offense(node) do |corrector|
39
+ first_args = maybe_public_path.gsub(PATTERN, '')
40
+
41
+ args = other_args.map(&:source)
42
+ args.unshift("'#{first_args}'") unless first_args.empty?
43
+
44
+ replacement = "#{rails.source}.public_path"
45
+ replacement += ".join(#{args.join(', ')})" unless args.empty?
46
+
47
+ corrector.replace(node, replacement)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def public_path?(string)
54
+ PATTERN.match?(string)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -3,8 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop converts usages of `try!` to `&.`. It can also be configured
7
- # to convert `try`. It will convert code to use safe navigation.
6
+ # Converts usages of `try!` to `&.`. It can also be configured
7
+ # to convert `try`. It will convert code to use safe navigation
8
+ # if the target Ruby version is set to 2.3+
8
9
  #
9
10
  # @example ConvertTry: false (default)
10
11
  # # bad
@@ -39,6 +40,9 @@ module RuboCop
39
40
  class SafeNavigation < Base
40
41
  include RangeHelp
41
42
  extend AutoCorrector
43
+ extend TargetRubyVersion
44
+
45
+ minimum_target_ruby_version 2.3
42
46
 
43
47
  MSG = 'Use safe navigation (`&.`) instead of `%<try>s`.'
44
48
  RESTRICT_ON_SEND = %i[try try!].freeze
@@ -47,15 +51,6 @@ module RuboCop
47
51
  (send _ ${:try :try!} $_ ...)
48
52
  PATTERN
49
53
 
50
- # Monkey patching for `Style/RedundantSelf` of RuboCop core.
51
- # rubocop:disable Style/ClassAndModuleChildren
52
- class Style::RedundantSelf
53
- def self.autocorrect_incompatible_with
54
- [Rails::SafeNavigation]
55
- end
56
- end
57
- # rubocop:enable Style/ClassAndModuleChildren
58
-
59
54
  def self.autocorrect_incompatible_with
60
55
  [Style::RedundantSelf]
61
56
  end
@@ -75,10 +70,10 @@ module RuboCop
75
70
 
76
71
  def autocorrect(corrector, node)
77
72
  method_node, *params = *node.arguments
78
- method = method_node.source[1..-1]
73
+ method = method_node.source[1..]
79
74
 
80
75
  range = if node.receiver
81
- range_between(node.loc.dot.begin_pos, node.loc.expression.end_pos)
76
+ range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
82
77
  else
83
78
  corrector.insert_before(node, 'self')
84
79
  node
@@ -3,12 +3,21 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks to make sure safe navigation isn't used with `blank?` in
6
+ # Checks to make sure safe navigation isn't used with `blank?` in
7
7
  # a conditional.
8
8
  #
9
- # While the safe navigation operator is generally a good idea, when
10
- # checking `foo&.blank?` in a conditional, `foo` being `nil` will actually
11
- # do the opposite of what the author intends.
9
+ # @safety
10
+ # While the safe navigation operator is generally a good idea, when
11
+ # checking `foo&.blank?` in a conditional, `foo` being `nil` will actually
12
+ # do the opposite of what the author intends.
13
+ #
14
+ # For example:
15
+ #
16
+ # [source,ruby]
17
+ # ----
18
+ # foo&.blank? #=> nil
19
+ # foo.blank? #=> true
20
+ # ----
12
21
  #
13
22
  # @example
14
23
  # # bad
@@ -22,9 +31,7 @@ module RuboCop
22
31
  class SafeNavigationWithBlank < Base
23
32
  extend AutoCorrector
24
33
 
25
- MSG =
26
- 'Avoid calling `blank?` with the safe navigation operator ' \
27
- 'in conditionals.'
34
+ MSG = 'Avoid calling `blank?` with the safe navigation operator in conditionals.'
28
35
 
29
36
  def_node_matcher :safe_navigation_blank_in_conditional?, <<~PATTERN
30
37
  (if $(csend ... :blank?) ...)