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 that `if` and `only` (or `except`) are not used together
6
+ # Checks that `if` and `only` (or `except`) are not used together
7
7
  # as options of `skip_*` action filter.
8
8
  #
9
9
  # The `if` option will be ignored when `if` and `only` are used together.
@@ -35,19 +35,12 @@ module RuboCop
35
35
  # skip_before_action :login_required,
36
36
  # if: -> { trusted_origin? && action_name != "admin" }
37
37
  # end
38
- #
39
- # @see https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options
40
38
  class IgnoredSkipActionFilterOption < Base
41
39
  MSG = <<~MSG.chomp.freeze
42
40
  `%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
43
41
  MSG
44
42
 
45
- RESTRICT_ON_SEND = %i[
46
- skip_after_action
47
- skip_around_action
48
- skip_before_action
49
- skip_action_callback
50
- ].freeze
43
+ RESTRICT_ON_SEND = %i[skip_after_action skip_around_action skip_before_action skip_action_callback].freeze
51
44
 
52
45
  FILTERS = RESTRICT_ON_SEND.map { |method_name| ":#{method_name}" }
53
46
 
@@ -67,11 +60,9 @@ module RuboCop
67
60
  options = options_hash(options)
68
61
 
69
62
  if if_and_only?(options)
70
- add_offense(options[:if],
71
- message: format(MSG, prefer: :only, ignore: :if))
63
+ add_offense(options[:if], message: format(MSG, prefer: :only, ignore: :if))
72
64
  elsif if_and_except?(options)
73
- add_offense(options[:except],
74
- message: format(MSG, prefer: :if, ignore: :except))
65
+ add_offense(options[:except], message: format(MSG, prefer: :if, ignore: :except))
75
66
  end
76
67
  end
77
68
 
@@ -80,7 +71,7 @@ module RuboCop
80
71
  def options_hash(options)
81
72
  options.pairs
82
73
  .select { |pair| pair.key.sym_type? }
83
- .map { |pair| [pair.key.value, pair] }.to_h
74
+ .to_h { |pair| [pair.key.value, pair] }
84
75
  end
85
76
 
86
77
  def if_and_only?(options)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for uses of `each_with_object({}) { ... }`,
6
+ # Looks for uses of `each_with_object({}) { ... }`,
7
7
  # `map { ... }.to_h`, and `Hash[map { ... }]` that are transforming
8
8
  # an enumerable into a hash where the values are the original elements.
9
9
  # Rails provides the `index_by` method for this purpose.
@@ -23,22 +23,22 @@ module RuboCop
23
23
 
24
24
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
25
25
  (block
26
- ({send csend} _ :each_with_object (hash))
26
+ (call _ :each_with_object (hash))
27
27
  (args (arg $_el) (arg _memo))
28
- ({send csend} (lvar _memo) :[]= $!`_memo (lvar _el)))
28
+ (call (lvar _memo) :[]= $!`_memo (lvar _el)))
29
29
  PATTERN
30
30
 
31
31
  def_node_matcher :on_bad_to_h, <<~PATTERN
32
32
  (block
33
- ({send csend} _ :to_h)
33
+ (call _ :to_h)
34
34
  (args (arg $_el))
35
35
  (array $_ (lvar _el)))
36
36
  PATTERN
37
37
 
38
38
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
39
- ({send csend}
39
+ (call
40
40
  (block
41
- ({send csend} _ {:map :collect})
41
+ (call _ {:map :collect})
42
42
  (args (arg $_el))
43
43
  (array $_ (lvar _el)))
44
44
  :to_h)
@@ -46,10 +46,10 @@ module RuboCop
46
46
 
47
47
  def_node_matcher :on_bad_hash_brackets_map, <<~PATTERN
48
48
  (send
49
- (const _ :Hash)
49
+ (const {nil? cbase} :Hash)
50
50
  :[]
51
51
  (block
52
- ({send csend} _ {:map :collect})
52
+ (call _ {:map :collect})
53
53
  (args (arg $_el))
54
54
  (array $_ (lvar _el))))
55
55
  PATTERN
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for uses of `each_with_object({}) { ... }`,
6
+ # Looks for uses of `each_with_object({}) { ... }`,
7
7
  # `map { ... }.to_h`, and `Hash[map { ... }]` that are transforming
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.
@@ -26,22 +26,22 @@ module RuboCop
26
26
 
27
27
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
28
28
  (block
29
- ({send csend} _ :each_with_object (hash))
29
+ (call _ :each_with_object (hash))
30
30
  (args (arg $_el) (arg _memo))
31
- ({send csend} (lvar _memo) :[]= (lvar _el) $!`_memo))
31
+ (call (lvar _memo) :[]= (lvar _el) $!`_memo))
32
32
  PATTERN
33
33
 
34
34
  def_node_matcher :on_bad_to_h, <<~PATTERN
35
35
  (block
36
- ({send csend} _ :to_h)
36
+ (call _ :to_h)
37
37
  (args (arg $_el))
38
38
  (array (lvar _el) $_))
39
39
  PATTERN
40
40
 
41
41
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
42
- ({send csend}
42
+ (call
43
43
  (block
44
- ({send csend} _ {:map :collect})
44
+ (call _ {:map :collect})
45
45
  (args (arg $_el))
46
46
  (array (lvar _el) $_))
47
47
  :to_h)
@@ -49,10 +49,10 @@ module RuboCop
49
49
 
50
50
  def_node_matcher :on_bad_hash_brackets_map, <<~PATTERN
51
51
  (send
52
- (const _ :Hash)
52
+ (const {nil? cbase} :Hash)
53
53
  :[]
54
54
  (block
55
- ({send csend} _ {:map :collect})
55
+ (call _ {:map :collect})
56
56
  (args (arg $_el))
57
57
  (array (lvar _el) $_)))
58
58
  PATTERN
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that Active Support's `inquiry` method is not used.
6
+ # Checks that Active Support's `inquiry` method is not used.
7
7
  #
8
8
  # @example
9
9
  # # bad - String#inquiry
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for has_(one|many) and belongs_to associations where
6
+ # Looks for has_(one|many) and belongs_to associations where
7
7
  # Active Record can't automatically determine the inverse association
8
8
  # because of a scope or the options used. Using the blog with order scope
9
9
  # example below, traversing the a Blog's association in both directions
@@ -126,8 +126,17 @@ module RuboCop
126
126
  # has_many :physicians, through: :appointments
127
127
  # end
128
128
  #
129
- # @see https://guides.rubyonrails.org/association_basics.html#bi-directional-associations
130
- # @see https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
129
+ # @example IgnoreScopes: false (default)
130
+ # # bad
131
+ # class Blog < ApplicationRecord
132
+ # has_many :posts, -> { order(published_at: :desc) }
133
+ # end
134
+ #
135
+ # @example IgnoreScopes: true
136
+ # # good
137
+ # class Blog < ApplicationRecord
138
+ # has_many :posts, -> { order(published_at: :desc) }
139
+ # end
131
140
  class InverseOf < Base
132
141
  SPECIFY_MSG = 'Specify an `:inverse_of` option.'
133
142
  NIL_MSG = 'You specified `inverse_of: nil`, you probably meant to use `inverse_of: false`.'
@@ -180,8 +189,7 @@ module RuboCop
180
189
  end
181
190
  return if options_ignoring_inverse_of?(options)
182
191
 
183
- return unless scope?(arguments) ||
184
- options_requiring_inverse_of?(options)
192
+ return unless scope?(arguments) || options_requiring_inverse_of?(options)
185
193
 
186
194
  return if options_contain_inverse_of?(options)
187
195
 
@@ -189,13 +197,12 @@ module RuboCop
189
197
  end
190
198
 
191
199
  def scope?(arguments)
192
- arguments.any?(&:block_type?)
200
+ !ignore_scopes? && arguments.any?(&:block_type?)
193
201
  end
194
202
 
195
203
  def options_requiring_inverse_of?(options)
196
204
  required = options.any? do |opt|
197
- conditions_option?(opt) ||
198
- foreign_key_option?(opt)
205
+ conditions_option?(opt) || foreign_key_option?(opt)
199
206
  end
200
207
 
201
208
  return required if target_rails_version >= 5.2
@@ -215,8 +222,7 @@ module RuboCop
215
222
 
216
223
  def with_options_arguments(recv, node)
217
224
  blocks = node.each_ancestor(:block).select do |block|
218
- block.send_node.command?(:with_options) &&
219
- same_context_in_with_options?(block.arguments.first, recv)
225
+ block.send_node.command?(:with_options) && same_context_in_with_options?(block.arguments.first, recv)
220
226
  end
221
227
  blocks.flat_map { |n| n.send_node.arguments }
222
228
  end
@@ -236,6 +242,10 @@ module RuboCop
236
242
  SPECIFY_MSG
237
243
  end
238
244
  end
245
+
246
+ def ignore_scopes?
247
+ cop_config['IgnoreScopes'] == true
248
+ end
239
249
  end
240
250
  end
241
251
  end
@@ -3,16 +3,17 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that methods specified in the filter's `only` or
6
+ # Checks that methods specified in the filter's `only` or
7
7
  # `except` options are defined within the same class or module.
8
8
  #
9
- # You can technically specify methods of superclass or methods added by
10
- # mixins on the filter, but these can confuse developers. If you specify
11
- # methods that are defined in other classes or modules, you should
12
- # define the filter in that class or module.
9
+ # @safety
10
+ # You can technically specify methods of superclass or methods added by
11
+ # mixins on the filter, but these can confuse developers. If you specify
12
+ # methods that are defined in other classes or modules, you should
13
+ # define the filter in that class or module.
13
14
  #
14
- # If you rely on behaviour defined in the superclass actions, you must
15
- # remember to invoke `super` in the subclass actions.
15
+ # If you rely on behavior defined in the superclass actions, you must
16
+ # remember to invoke `super` in the subclass actions.
16
17
  #
17
18
  # @example
18
19
  # # bad
@@ -70,7 +71,7 @@ module RuboCop
70
71
  # class ArticlesController < ContentController
71
72
  # before_action :load_article, only: [:update]
72
73
  #
73
- # # the cop requires this method, but it relies on behaviour defined
74
+ # # the cop requires this method, but it relies on behavior defined
74
75
  # # in the superclass, so needs to invoke `super`
75
76
  # def update
76
77
  # super
@@ -124,9 +125,10 @@ module RuboCop
124
125
  block = parent.each_child_node(:begin).first
125
126
  return unless block
126
127
 
127
- defined_methods = block.each_child_node(:def).map(&:method_name)
128
+ defined_action_methods = defined_action_methods(block)
129
+
128
130
  methods = array_values(methods_node).reject do |method|
129
- defined_methods.include?(method)
131
+ defined_action_methods.include?(method)
130
132
  end
131
133
 
132
134
  message = message(methods, parent)
@@ -135,6 +137,36 @@ module RuboCop
135
137
 
136
138
  private
137
139
 
140
+ def defined_action_methods(block)
141
+ defined_methods = block.each_child_node(:def).map(&:method_name)
142
+
143
+ defined_methods + aliased_action_methods(block, defined_methods)
144
+ end
145
+
146
+ def aliased_action_methods(node, defined_methods)
147
+ alias_methods = alias_methods(node)
148
+ defined_methods.each_with_object([]) do |defined_method, aliased_method|
149
+ if (new_method_name = alias_methods[defined_method])
150
+ aliased_method << new_method_name
151
+ end
152
+ end
153
+ end
154
+
155
+ def alias_methods(node)
156
+ result = {}
157
+ node.each_child_node(:send, :alias) do |child_node|
158
+ case child_node.type
159
+ when :send
160
+ if child_node.method?(:alias_method)
161
+ result[child_node.last_argument.value] = child_node.first_argument.value
162
+ end
163
+ when :alias
164
+ result[child_node.old_identifier.value] = child_node.new_identifier.value
165
+ end
166
+ end
167
+ result
168
+ end
169
+
138
170
  # @param node [RuboCop::AST::Node]
139
171
  # @return [Array<Symbol>]
140
172
  def array_values(node) # rubocop:disable Metrics/MethodLength
@@ -162,13 +194,9 @@ module RuboCop
162
194
  # @return [String]
163
195
  def message(methods, parent)
164
196
  if methods.size == 1
165
- format(MSG,
166
- action: "`#{methods[0]}` is",
167
- type: parent.type)
197
+ format(MSG, action: "`#{methods[0]}` is", type: parent.type)
168
198
  else
169
- format(MSG,
170
- action: "`#{methods.join('`, `')}` are",
171
- type: parent.type)
199
+ format(MSG, action: "`#{methods.join('`, `')}` are", type: parent.type)
172
200
  end
173
201
  end
174
202
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for calls to `link_to` that contain a
6
+ # Checks for calls to `link_to` that contain a
7
7
  # `target: '_blank'` but no `rel: 'noopener'`. This can be a security
8
8
  # risk as the loaded page will have control over the previous page
9
9
  # and could change its location for phishing purposes.
@@ -68,15 +68,12 @@ module RuboCop
68
68
 
69
69
  def append_to_rel(rel_node, corrector)
70
70
  existing_rel = rel_node.children.last.value
71
- str_range = rel_node.children.last.loc.expression.adjust(
72
- begin_pos: 1,
73
- end_pos: -1
74
- )
71
+ str_range = rel_node.children.last.source_range.adjust(begin_pos: 1, end_pos: -1)
75
72
  corrector.replace(str_range, "#{existing_rel} noopener")
76
73
  end
77
74
 
78
- def add_rel(send_node, offence_node, corrector)
79
- opening_quote = offence_node.children.last.source[0]
75
+ def add_rel(send_node, offense_node, corrector)
76
+ opening_quote = offense_node.children.last.source[0]
80
77
  closing_quote = opening_quote == ':' ? '' : opening_quote
81
78
  new_rel_exp = ", rel: #{opening_quote}noopener#{closing_quote}"
82
79
  range = if (last_argument = send_node.last_argument).hash_type?
@@ -3,11 +3,15 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces that mailer names end with `Mailer` suffix.
6
+ # Enforces that mailer names end with `Mailer` suffix.
7
7
  #
8
8
  # Without the `Mailer` suffix it isn't immediately apparent what's a mailer
9
9
  # and which views are related to the mailer.
10
10
  #
11
+ # @safety
12
+ # This cop's autocorrection is unsafe because renaming a constant is
13
+ # always an unsafe operation.
14
+ #
11
15
  # @example
12
16
  # # bad
13
17
  # class User < ActionMailer::Base
@@ -30,8 +34,8 @@ module RuboCop
30
34
 
31
35
  def_node_matcher :mailer_base_class?, <<~PATTERN
32
36
  {
33
- (const (const nil? :ActionMailer) :Base)
34
- (const nil? :ApplicationMailer)
37
+ (const (const {nil? cbase} :ActionMailer) :Base)
38
+ (const {nil? cbase} :ApplicationMailer)
35
39
  }
36
40
  PATTERN
37
41
 
@@ -40,7 +44,7 @@ module RuboCop
40
44
  PATTERN
41
45
 
42
46
  def_node_matcher :class_new_definition?, <<~PATTERN
43
- (send (const nil? :Class) :new #mailer_base_class?)
47
+ (send (const {nil? cbase} :Class) :new #mailer_base_class?)
44
48
  PATTERN
45
49
 
46
50
  def on_class(node)
@@ -73,7 +77,7 @@ module RuboCop
73
77
  corrector.replace(node.loc.name, "#{name}Mailer")
74
78
  else
75
79
  name = node.children.last
76
- corrector.replace(node.source_range, "#{name}Mailer")
80
+ corrector.replace(node, "#{name}Mailer")
77
81
  end
78
82
  end
79
83
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop identifies places where defining routes with `match`
6
+ # Identifies places where defining routes with `match`
7
7
  # can be replaced with a specific HTTP method.
8
8
  #
9
9
  # Don't use `match` to define any routes unless there is a need to map multiple request types
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Makes sure that each migration file defines a migration class
7
+ # whose name matches the file name.
8
+ # (e.g. `20220224111111_create_users.rb` should define `CreateUsers` class.)
9
+ #
10
+ # @example
11
+ # # db/migrate/20220224111111_create_users.rb
12
+ #
13
+ # # bad
14
+ # class SellBooks < ActiveRecord::Migration[7.0]
15
+ # end
16
+ #
17
+ # # good
18
+ # class CreateUsers < ActiveRecord::Migration[7.0]
19
+ # end
20
+ #
21
+ class MigrationClassName < Base
22
+ extend AutoCorrector
23
+ include MigrationsHelper
24
+
25
+ MSG = 'Replace with `%<camelized_basename>s` that matches the file name.'
26
+
27
+ def on_class(node)
28
+ return unless migration_class?(node)
29
+
30
+ basename = basename_without_timestamp_and_suffix(processed_source.file_path)
31
+
32
+ class_identifier = node.identifier.location.name
33
+ camelized_basename = camelize(basename)
34
+ return if class_identifier.source.casecmp(camelized_basename).zero?
35
+
36
+ message = format(MSG, camelized_basename: camelized_basename)
37
+
38
+ add_offense(class_identifier, message: message) do |corrector|
39
+ corrector.replace(class_identifier, camelized_basename)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def basename_without_timestamp_and_suffix(filepath)
46
+ basename = File.basename(filepath, '.rb')
47
+ basename = remove_gem_suffix(basename)
48
+
49
+ basename.sub(/\A\d+_/, '')
50
+ end
51
+
52
+ # e.g.: from `add_blobs.active_storage` to `add_blobs`.
53
+ def remove_gem_suffix(file_name)
54
+ file_name.sub(/\..+\z/, '')
55
+ end
56
+
57
+ def camelize(word)
58
+ word.split('_').map(&:capitalize).join
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -3,11 +3,12 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces the use of `collection.exclude?(obj)`
6
+ # Enforces the use of `collection.exclude?(obj)`
7
7
  # over `!collection.include?(obj)`.
8
8
  #
9
- # It is marked as unsafe by default because false positive will occur for
10
- # a receiver object that do not have `exclude?` method. (e.g. `IPAddr`)
9
+ # @safety
10
+ # This cop is unsafe because false positive will occur for
11
+ # receiver objects that do not have an `exclude?` method. (e.g. `IPAddr`)
11
12
  #
12
13
  # @example
13
14
  # # bad
@@ -25,7 +26,7 @@ module RuboCop
25
26
  RESTRICT_ON_SEND = %i[!].freeze
26
27
 
27
28
  def_node_matcher :negate_include_call?, <<~PATTERN
28
- (send (send $_ :include? $_) :!)
29
+ (send (send $!nil? :include? $_) :!)
29
30
  PATTERN
30
31
 
31
32
  def on_send(node)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for add_column call with NOT NULL constraint
6
+ # Checks for add_column call with NOT NULL constraint
7
7
  # in migration file.
8
8
  #
9
9
  # @example
@@ -21,7 +21,7 @@ module RuboCop
21
21
  RESTRICT_ON_SEND = %i[add_column add_reference].freeze
22
22
 
23
23
  def_node_matcher :add_not_null_column?, <<~PATTERN
24
- (send nil? :add_column _ _ _ (hash $...))
24
+ (send nil? :add_column _ _ $_ (hash $...))
25
25
  PATTERN
26
26
 
27
27
  def_node_matcher :add_not_null_reference?, <<~PATTERN
@@ -44,17 +44,20 @@ module RuboCop
44
44
  private
45
45
 
46
46
  def check_add_column(node)
47
- pairs = add_not_null_column?(node)
48
- check_pairs(pairs)
47
+ add_not_null_column?(node) do |type, pairs|
48
+ return if type.value == :virtual || type.value == 'virtual'
49
+
50
+ check_pairs(pairs)
51
+ end
49
52
  end
50
53
 
51
54
  def check_add_reference(node)
52
- pairs = add_not_null_reference?(node)
53
- check_pairs(pairs)
55
+ add_not_null_reference?(node) do |pairs|
56
+ check_pairs(pairs)
57
+ end
54
58
  end
55
59
 
56
60
  def check_pairs(pairs)
57
- return unless pairs
58
61
  return if pairs.any? { |pair| default_option?(pair) }
59
62
 
60
63
  null_false = pairs.find { |pair| null_false?(pair) }
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for places where ordering by `id` column is used.
6
+ # Checks for places where ordering by `id` column is used.
7
7
  #
8
8
  # Don't use the `id` column for ordering. The sequence of ids is not guaranteed
9
9
  # to be in any particular order, despite often (incidentally) being chronological.
@@ -23,8 +23,7 @@ module RuboCop
23
23
  class OrderById < Base
24
24
  include RangeHelp
25
25
 
26
- MSG = 'Do not use the `id` column for ordering. '\
27
- 'Use a timestamp column to order chronologically.'
26
+ MSG = 'Do not use the `id` column for ordering. Use a timestamp column to order chronologically.'
28
27
  RESTRICT_ON_SEND = %i[order].freeze
29
28
 
30
29
  def_node_matcher :order_by_id?, <<~PATTERN
@@ -3,7 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of output calls like puts and print
6
+ # Checks for the use of output calls like puts and print
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because depending on the Rails log level configuration,
10
+ # changing from `puts` to `Rails.logger.debug` could result in no output being shown.
7
11
  #
8
12
  # @example
9
13
  # # bad
@@ -14,11 +18,11 @@ module RuboCop
14
18
  # # good
15
19
  # Rails.logger.debug 'A debug message'
16
20
  class Output < Base
17
- MSG = 'Do not write to stdout. ' \
18
- "Use Rails's logger if you want to log."
19
- RESTRICT_ON_SEND = %i[
20
- ap p pp pretty_print print puts binwrite syswrite write write_nonblock
21
- ].freeze
21
+ include RangeHelp
22
+ extend AutoCorrector
23
+
24
+ MSG = "Do not write to stdout. Use Rails's logger if you want to log."
25
+ RESTRICT_ON_SEND = %i[ap p pp pretty_print print puts binwrite syswrite write write_nonblock].freeze
22
26
 
23
27
  def_node_matcher :output?, <<~PATTERN
24
28
  (send nil? {:ap :p :pp :pretty_print :print :puts} ...)
@@ -28,17 +32,21 @@ module RuboCop
28
32
  (send
29
33
  {
30
34
  (gvar #match_gvar?)
31
- {(const nil? :STDOUT) (const nil? :STDERR)}
35
+ (const {nil? cbase} {:STDOUT :STDERR})
32
36
  }
33
37
  {:binwrite :syswrite :write :write_nonblock}
34
38
  ...)
35
39
  PATTERN
36
40
 
37
41
  def on_send(node)
38
- return unless (output?(node) || io_output?(node)) &&
39
- node.arguments?
42
+ return if node.parent&.call_type?
43
+ return unless output?(node) || io_output?(node)
44
+
45
+ range = offense_range(node)
40
46
 
41
- add_offense(node.loc.selector)
47
+ add_offense(range) do |corrector|
48
+ corrector.replace(range, 'Rails.logger.debug')
49
+ end
42
50
  end
43
51
 
44
52
  private
@@ -46,6 +54,14 @@ module RuboCop
46
54
  def match_gvar?(sym)
47
55
  %i[$stdout $stderr].include?(sym)
48
56
  end
57
+
58
+ def offense_range(node)
59
+ if node.receiver
60
+ range_between(node.source_range.begin_pos, node.loc.selector.end_pos)
61
+ else
62
+ node.loc.selector
63
+ end
64
+ end
49
65
  end
50
66
  end
51
67
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of output safety calls like `html_safe`,
6
+ # Checks for the use of output safety calls like `html_safe`,
7
7
  # `raw`, and `safe_concat`. These methods do not escape content. They
8
8
  # simply return a SafeBuffer containing the content as is. Instead,
9
9
  # use `safe_join` to join content and escape it and concat to
@@ -66,8 +66,12 @@ module RuboCop
66
66
  MSG = 'Tagging a string as html safe may be a security risk.'
67
67
  RESTRICT_ON_SEND = %i[html_safe raw safe_concat].freeze
68
68
 
69
+ def_node_search :i18n_method?, <<~PATTERN
70
+ (send {nil? (const {nil? cbase} :I18n)} {:t :translate :l :localize} ...)
71
+ PATTERN
72
+
69
73
  def on_send(node)
70
- return if non_interpolated_string?(node)
74
+ return if non_interpolated_string?(node) || i18n_method?(node)
71
75
 
72
76
  return unless looks_like_rails_html_safe?(node) ||
73
77
  looks_like_rails_raw?(node) ||