rubocop-rails 2.14.2 → 2.19.1

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 (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 +190 -12
  5. data/config/obsoletion.yml +10 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +3 -6
  7. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +1 -3
  8. data/lib/rubocop/cop/mixin/enforce_superclass.rb +1 -1
  9. data/lib/rubocop/cop/mixin/index_method.rb +7 -17
  10. data/lib/rubocop/cop/mixin/migrations_helper.rb +1 -1
  11. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +112 -0
  12. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  13. data/lib/rubocop/cop/rails/action_filter.rb +2 -2
  14. data/lib/rubocop/cop/rails/action_order.rb +116 -0
  15. data/lib/rubocop/cop/rails/active_record_aliases.rb +3 -4
  16. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +7 -4
  17. data/lib/rubocop/cop/rails/active_record_override.rb +2 -5
  18. data/lib/rubocop/cop/rails/active_support_aliases.rb +1 -1
  19. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  20. data/lib/rubocop/cop/rails/add_column_index.rb +3 -6
  21. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  22. data/lib/rubocop/cop/rails/application_controller.rb +2 -2
  23. data/lib/rubocop/cop/rails/application_job.rb +3 -3
  24. data/lib/rubocop/cop/rails/application_mailer.rb +2 -2
  25. data/lib/rubocop/cop/rails/application_record.rb +2 -2
  26. data/lib/rubocop/cop/rails/arel_star.rb +2 -2
  27. data/lib/rubocop/cop/rails/assert_not.rb +1 -1
  28. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +1 -1
  29. data/lib/rubocop/cop/rails/belongs_to.rb +2 -5
  30. data/lib/rubocop/cop/rails/blank.rb +10 -11
  31. data/lib/rubocop/cop/rails/bulk_change_table.rb +8 -25
  32. data/lib/rubocop/cop/rails/compact_blank.rb +6 -2
  33. data/lib/rubocop/cop/rails/content_tag.rb +6 -7
  34. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +16 -3
  35. data/lib/rubocop/cop/rails/date.rb +12 -17
  36. data/lib/rubocop/cop/rails/default_scope.rb +1 -1
  37. data/lib/rubocop/cop/rails/delegate.rb +24 -18
  38. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +2 -2
  39. data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +63 -3
  40. data/lib/rubocop/cop/rails/dot_separated_keys.rb +71 -0
  41. data/lib/rubocop/cop/rails/duplicate_association.rb +2 -2
  42. data/lib/rubocop/cop/rails/duplicate_scope.rb +1 -1
  43. data/lib/rubocop/cop/rails/duration_arithmetic.rb +4 -4
  44. data/lib/rubocop/cop/rails/dynamic_find_by.rb +26 -14
  45. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +6 -2
  46. data/lib/rubocop/cop/rails/enum_hash.rb +3 -4
  47. data/lib/rubocop/cop/rails/enum_uniqueness.rb +3 -6
  48. data/lib/rubocop/cop/rails/environment_comparison.rb +3 -4
  49. data/lib/rubocop/cop/rails/environment_variable_access.rb +1 -1
  50. data/lib/rubocop/cop/rails/exit.rb +1 -1
  51. data/lib/rubocop/cop/rails/expanded_date_range.rb +39 -23
  52. data/lib/rubocop/cop/rails/file_path.rb +41 -24
  53. data/lib/rubocop/cop/rails/find_by.rb +1 -1
  54. data/lib/rubocop/cop/rails/find_by_id.rb +3 -3
  55. data/lib/rubocop/cop/rails/find_each.rb +14 -4
  56. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  57. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +1 -1
  58. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +14 -8
  59. data/lib/rubocop/cop/rails/helper_instance_variable.rb +3 -3
  60. data/lib/rubocop/cop/rails/http_positional_arguments.rb +23 -12
  61. data/lib/rubocop/cop/rails/http_status.rb +6 -11
  62. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +3 -1
  63. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +1 -1
  64. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +2 -2
  65. data/lib/rubocop/cop/rails/ignored_columns_assignment.rb +50 -0
  66. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +5 -14
  67. data/lib/rubocop/cop/rails/index_by.rb +2 -2
  68. data/lib/rubocop/cop/rails/index_with.rb +2 -2
  69. data/lib/rubocop/cop/rails/inquiry.rb +1 -1
  70. data/lib/rubocop/cop/rails/inverse_of.rb +4 -10
  71. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +22 -16
  72. data/lib/rubocop/cop/rails/link_to_blank.rb +2 -5
  73. data/lib/rubocop/cop/rails/mailer_name.rb +5 -5
  74. data/lib/rubocop/cop/rails/match_route.rb +1 -1
  75. data/lib/rubocop/cop/rails/migration_class_name.rb +2 -2
  76. data/lib/rubocop/cop/rails/negate_include.rb +2 -2
  77. data/lib/rubocop/cop/rails/not_null_column.rb +10 -7
  78. data/lib/rubocop/cop/rails/order_by_id.rb +2 -3
  79. data/lib/rubocop/cop/rails/output.rb +7 -9
  80. data/lib/rubocop/cop/rails/output_safety.rb +6 -2
  81. data/lib/rubocop/cop/rails/pick.rb +1 -1
  82. data/lib/rubocop/cop/rails/pluck.rb +45 -13
  83. data/lib/rubocop/cop/rails/pluck_id.rb +2 -2
  84. data/lib/rubocop/cop/rails/pluck_in_where.rb +1 -1
  85. data/lib/rubocop/cop/rails/pluralization_grammar.rb +2 -3
  86. data/lib/rubocop/cop/rails/presence.rb +22 -13
  87. data/lib/rubocop/cop/rails/present.rb +10 -13
  88. data/lib/rubocop/cop/rails/rake_environment.rb +3 -3
  89. data/lib/rubocop/cop/rails/read_write_attribute.rb +2 -2
  90. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +5 -7
  91. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +3 -3
  92. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +3 -3
  93. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +31 -27
  94. data/lib/rubocop/cop/rails/redundant_travel_back.rb +1 -1
  95. data/lib/rubocop/cop/rails/reflection_class_name.rb +35 -2
  96. data/lib/rubocop/cop/rails/refute_methods.rb +1 -5
  97. data/lib/rubocop/cop/rails/relative_date_constant.rb +5 -8
  98. data/lib/rubocop/cop/rails/render_inline.rb +1 -1
  99. data/lib/rubocop/cop/rails/render_plain_text.rb +1 -1
  100. data/lib/rubocop/cop/rails/request_referer.rb +2 -3
  101. data/lib/rubocop/cop/rails/require_dependency.rb +2 -2
  102. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  103. data/lib/rubocop/cop/rails/reversible_migration.rb +15 -63
  104. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +5 -6
  105. data/lib/rubocop/cop/rails/root_join_chain.rb +1 -1
  106. data/lib/rubocop/cop/rails/root_pathname_methods.rb +238 -0
  107. data/lib/rubocop/cop/rails/root_public_path.rb +59 -0
  108. data/lib/rubocop/cop/rails/safe_navigation.rb +8 -13
  109. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +2 -4
  110. data/lib/rubocop/cop/rails/save_bang.rb +12 -24
  111. data/lib/rubocop/cop/rails/schema_comment.rb +1 -1
  112. data/lib/rubocop/cop/rails/scope_args.rb +1 -1
  113. data/lib/rubocop/cop/rails/short_i18n.rb +3 -6
  114. data/lib/rubocop/cop/rails/skips_model_validations.rb +3 -4
  115. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +10 -7
  116. data/lib/rubocop/cop/rails/strip_heredoc.rb +56 -0
  117. data/lib/rubocop/cop/rails/table_name_assignment.rb +1 -1
  118. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +73 -0
  119. data/lib/rubocop/cop/rails/time_zone.rb +34 -32
  120. data/lib/rubocop/cop/rails/time_zone_assignment.rb +4 -4
  121. data/lib/rubocop/cop/rails/to_formatted_s.rb +46 -0
  122. data/lib/rubocop/cop/rails/to_s_with_argument.rb +78 -0
  123. data/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb +49 -0
  124. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +17 -12
  125. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +3 -6
  126. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +14 -7
  127. data/lib/rubocop/cop/rails/unknown_env.rb +3 -5
  128. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +7 -2
  129. data/lib/rubocop/cop/rails/validation.rb +5 -13
  130. data/lib/rubocop/cop/rails/where_equals.rb +2 -2
  131. data/lib/rubocop/cop/rails/where_exists.rb +3 -3
  132. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  133. data/lib/rubocop/cop/rails/where_not.rb +2 -2
  134. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  135. data/lib/rubocop/cop/rails_cops.rb +16 -0
  136. data/lib/rubocop/rails/schema_loader/schema.rb +8 -5
  137. data/lib/rubocop/rails/version.rb +1 -1
  138. data/lib/rubocop/rails.rb +1 -1
  139. data/lib/rubocop-rails.rb +19 -0
  140. metadata +23 -9
  141. data/bin/console +0 -11
  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 alter queries are combinable.
6
+ # Checks whether alter queries are combinable.
7
7
  # If combinable queries are detected, it suggests to you
8
8
  # to use `change_table` with `bulk: true` instead.
9
9
  # This option causes the migration to generate a single
@@ -62,9 +62,6 @@ module RuboCop
62
62
  # t.string :nickname
63
63
  # end
64
64
  # end
65
- #
66
- # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_table
67
- # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html
68
65
  class BulkChangeTable < Base
69
66
  MSG_FOR_CHANGE_TABLE = <<~MSG.chomp
70
67
  You can combine alter queries using `bulk: true` options.
@@ -111,25 +108,13 @@ module RuboCop
111
108
  remove_timestamps
112
109
  ].freeze
113
110
 
114
- MYSQL_COMBINABLE_TRANSFORMATIONS = %i[
115
- rename
116
- index
117
- remove_index
118
- ].freeze
111
+ MYSQL_COMBINABLE_TRANSFORMATIONS = %i[rename index remove_index].freeze
119
112
 
120
- MYSQL_COMBINABLE_ALTER_METHODS = %i[
121
- rename_column
122
- add_index
123
- remove_index
124
- ].freeze
113
+ MYSQL_COMBINABLE_ALTER_METHODS = %i[rename_column add_index remove_index].freeze
125
114
 
126
- POSTGRESQL_COMBINABLE_TRANSFORMATIONS = %i[
127
- change_default
128
- ].freeze
115
+ POSTGRESQL_COMBINABLE_TRANSFORMATIONS = %i[change_default].freeze
129
116
 
130
- POSTGRESQL_COMBINABLE_ALTER_METHODS = %i[
131
- change_column_default
132
- ].freeze
117
+ POSTGRESQL_COMBINABLE_ALTER_METHODS = %i[change_column_default].freeze
133
118
 
134
119
  def on_def(node)
135
120
  return unless support_bulk_alter?
@@ -186,8 +171,7 @@ module RuboCop
186
171
  options = node.arguments[1]
187
172
  return false unless options
188
173
 
189
- options.hash_type? &&
190
- options.keys.any? { |key| key.sym_type? && key.value == :bulk }
174
+ options.hash_type? && options.keys.any? { |key| key.sym_type? && key.value == :bulk }
191
175
  end
192
176
 
193
177
  def database
@@ -229,7 +213,7 @@ module RuboCop
229
213
  true
230
214
  when POSTGRESQL
231
215
  # Add bulk alter support for PostgreSQL in 5.2.0
232
- # @see https://github.com/rails/rails/pull/31331
216
+ # See: https://github.com/rails/rails/pull/31331
233
217
  target_rails_version >= 5.2
234
218
  else
235
219
  false
@@ -237,8 +221,7 @@ module RuboCop
237
221
  end
238
222
 
239
223
  def call_to_combinable_alter_method?(child_node)
240
- child_node.send_type? &&
241
- combinable_alter_methods.include?(child_node.method_name)
224
+ child_node.send_type? && combinable_alter_methods.include?(child_node.method_name)
242
225
  end
243
226
 
244
227
  def combinable_alter_methods
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # `ActionController::Parameters`.
18
18
  # `Array#compact_blank!`, `Hash#compact_blank!` are equivalent to `delete_if(&:blank?)`.
19
19
  # `ActionController::Parameters#compact_blank!` is equivalent to `reject!(&:blank?)`.
20
- # If the cop makes a mistake, auto-corrected code may get unexpected behavior.
20
+ # If the cop makes a mistake, autocorrected code may get unexpected behavior.
21
21
  #
22
22
  # @example
23
23
  #
@@ -93,7 +93,11 @@ module RuboCop
93
93
  end
94
94
 
95
95
  def offense_range(node)
96
- end_pos = node.parent&.block_type? ? node.parent.loc.expression.end_pos : node.loc.expression.end_pos
96
+ end_pos = if node.parent&.block_type? && node.parent&.send_node == node
97
+ node.parent.source_range.end_pos
98
+ else
99
+ node.source_range.end_pos
100
+ end
97
101
 
98
102
  range_between(node.loc.selector.begin_pos, end_pos)
99
103
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks legacy syntax usage of `tag`
6
+ # Checks legacy syntax usage of `tag`
7
7
  #
8
8
  # NOTE: Allow `tag` when the first argument is a variable because
9
9
  # `tag(name)` is simpler rather than `tag.public_send(name)`.
@@ -37,9 +37,7 @@ module RuboCop
37
37
  return if node.arguments.count >= 3
38
38
 
39
39
  first_argument = node.first_argument
40
- return if !first_argument ||
41
- allowed_argument?(first_argument) ||
42
- corrected_ancestor?(node)
40
+ return if !first_argument || allowed_argument?(first_argument) || corrected_ancestor?(node)
43
41
 
44
42
  preferred_method = node.first_argument.value.to_s.underscore
45
43
  message = format(MSG, preferred_method: preferred_method, current_argument: first_argument.source)
@@ -58,7 +56,8 @@ module RuboCop
58
56
  argument.send_type? ||
59
57
  argument.const_type? ||
60
58
  argument.splat_type? ||
61
- allowed_name?(argument)
59
+ allowed_name?(argument) ||
60
+ !argument.respond_to?(:value)
62
61
  end
63
62
 
64
63
  def register_offense(node, message, preferred_method)
@@ -82,11 +81,11 @@ module RuboCop
82
81
  def allowed_name?(argument)
83
82
  return false unless argument.str_type? || argument.sym_type?
84
83
 
85
- !/^[a-zA-Z\-][a-zA-Z\-0-9]*$/.match?(argument.value)
84
+ !/^[a-zA-Z-][a-zA-Z\-0-9]*$/.match?(argument.value)
86
85
  end
87
86
 
88
87
  def correction_range(node)
89
- range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
88
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
90
89
  end
91
90
  end
92
91
  end
@@ -3,10 +3,12 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks the migration for which timestamps are not included
7
- # when creating a new table.
6
+ # Checks the migration for which timestamps are not included when creating a new table.
8
7
  # In many cases, timestamps are useful information and should be added.
9
8
  #
9
+ # NOTE: Allow `timestamps` not written when `id: false` because this emphasizes respecting
10
+ # user's editing intentions.
11
+ #
10
12
  # @example
11
13
  # # bad
12
14
  # create_table :users
@@ -40,12 +42,23 @@ module RuboCop
40
42
  #
41
43
  # t.datetime :updated_at, default: -> { 'CURRENT_TIMESTAMP' }
42
44
  # end
45
+ #
46
+ # # good
47
+ # create_table :users, articles, id: false do |t|
48
+ # t.integer :user_id
49
+ # t.integer :article_id
50
+ # end
51
+ #
43
52
  class CreateTableWithTimestamps < Base
44
53
  include ActiveRecordMigrationsHelper
45
54
 
46
55
  MSG = 'Add timestamps when creating a new table.'
47
56
  RESTRICT_ON_SEND = %i[create_table].freeze
48
57
 
58
+ def_node_search :use_id_false_option?, <<~PATTERN
59
+ (pair (sym :id) (false))
60
+ PATTERN
61
+
49
62
  def_node_matcher :create_table_with_timestamps_proc?, <<~PATTERN
50
63
  (send nil? :create_table (sym _) ... (block-pass (sym :timestamps)))
51
64
  PATTERN
@@ -61,7 +74,7 @@ module RuboCop
61
74
  PATTERN
62
75
 
63
76
  def on_send(node)
64
- return unless node.command?(:create_table)
77
+ return if !node.command?(:create_table) || use_id_false_option?(node)
65
78
 
66
79
  parent = node.parent
67
80
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the correct use of Date methods,
6
+ # Checks for the correct use of Date methods,
7
7
  # such as Date.today, Date.current etc.
8
8
  #
9
9
  # Using `Date.today` is dangerous, because it doesn't know anything about
@@ -22,26 +22,26 @@ module RuboCop
22
22
  # And you can set a warning for `to_time` with `AllowToTime: false`.
23
23
  # `AllowToTime` is `true` by default to prevent false positive on `DateTime` object.
24
24
  #
25
- # @example EnforcedStyle: strict
25
+ # @example EnforcedStyle: flexible (default)
26
26
  # # bad
27
- # Date.current
28
- # Date.yesterday
29
27
  # Date.today
30
28
  #
31
29
  # # good
32
30
  # Time.zone.today
33
31
  # Time.zone.today - 1.day
32
+ # Date.current
33
+ # Date.yesterday
34
+ # date.in_time_zone
34
35
  #
35
- # @example EnforcedStyle: flexible (default)
36
+ # @example EnforcedStyle: strict
36
37
  # # bad
38
+ # Date.current
39
+ # Date.yesterday
37
40
  # Date.today
38
41
  #
39
42
  # # good
40
43
  # Time.zone.today
41
44
  # Time.zone.today - 1.day
42
- # Date.current
43
- # Date.yesterday
44
- # date.in_time_zone
45
45
  #
46
46
  # @example AllowToTime: true (default)
47
47
  # # good
@@ -53,22 +53,17 @@ module RuboCop
53
53
  class Date < Base
54
54
  include ConfigurableEnforcedStyle
55
55
 
56
- MSG = 'Do not use `Date.%<method_called>s` without zone. Use ' \
57
- '`Time.zone.%<day>s` instead.'
56
+ MSG = 'Do not use `Date.%<method_called>s` without zone. Use `Time.zone.%<day>s` instead.'
58
57
 
59
- MSG_SEND = 'Do not use `%<method>s` on Date objects, because they ' \
60
- 'know nothing about the time zone in use.'
58
+ MSG_SEND = 'Do not use `%<method>s` on Date objects, because they know nothing about the time zone in use.'
61
59
 
62
60
  RESTRICT_ON_SEND = %i[to_time to_time_in_current_zone].freeze
63
61
 
64
62
  BAD_DAYS = %i[today current yesterday tomorrow].freeze
65
63
 
66
- DEPRECATED_METHODS = [
67
- { deprecated: 'to_time_in_current_zone', relevant: 'in_time_zone' }
68
- ].freeze
64
+ DEPRECATED_METHODS = [{ deprecated: 'to_time_in_current_zone', relevant: 'in_time_zone' }].freeze
69
65
 
70
- DEPRECATED_MSG = '`%<deprecated>s` is deprecated. ' \
71
- 'Use `%<relevant>s` instead.'
66
+ DEPRECATED_MSG = '`%<deprecated>s` is deprecated. Use `%<relevant>s` instead.'
72
67
 
73
68
  def on_const(node)
74
69
  mod, klass = *node.children
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for uses of `default_scope`.
6
+ # Looks for uses of `default_scope`.
7
7
  #
8
8
  # @example
9
9
  # # bad
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for delegations that could have been created
6
+ # Looks for delegations that could have been created
7
7
  # automatically with the `delegate` method.
8
8
  #
9
9
  # Safe navigation `&.` is ignored because Rails' `allow_nil`
@@ -24,6 +24,14 @@ module RuboCop
24
24
  # # good
25
25
  # delegate :bar, to: :foo
26
26
  #
27
+ # # bad
28
+ # def bar
29
+ # self.bar
30
+ # end
31
+ #
32
+ # # good
33
+ # delegate :bar, to: :self
34
+ #
27
35
  # # good
28
36
  # def bar
29
37
  # foo&.bar
@@ -54,12 +62,13 @@ module RuboCop
54
62
  # delegate :bar, to: :foo, prefix: true
55
63
  class Delegate < Base
56
64
  extend AutoCorrector
65
+ include VisibilityHelp
57
66
 
58
67
  MSG = 'Use `delegate` to define delegations.'
59
68
 
60
69
  def_node_matcher :delegate?, <<~PATTERN
61
70
  (def _method_name _args
62
- (send (send nil? _) _ ...))
71
+ (send {(send nil? _) (self)} _ ...))
63
72
  PATTERN
64
73
 
65
74
  def on_def(node)
@@ -73,10 +82,14 @@ module RuboCop
73
82
 
74
83
  def register_offense(node)
75
84
  add_offense(node.loc.keyword) do |corrector|
76
- delegation = ["delegate :#{node.body.method_name}", "to: :#{node.body.receiver.method_name}"]
85
+ body = node.body
86
+
87
+ receiver = body.receiver.self_type? ? 'self' : ":#{body.receiver.method_name}"
88
+
89
+ delegation = ["delegate :#{body.method_name}", "to: #{receiver}"]
77
90
  delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
78
91
 
79
- corrector.replace(node.source_range, delegation.join(', '))
92
+ corrector.replace(node, delegation.join(', '))
80
93
  end
81
94
  end
82
95
 
@@ -92,15 +105,12 @@ module RuboCop
92
105
  return false if arg_array.size != argument_array.size
93
106
 
94
107
  arg_array.zip(argument_array).all? do |arg, argument|
95
- arg.arg_type? &&
96
- argument.lvar_type? &&
97
- arg.children == argument.children
108
+ arg.arg_type? && argument.lvar_type? && arg.children == argument.children
98
109
  end
99
110
  end
100
111
 
101
112
  def method_name_matches?(method_name, body)
102
- method_name == body.method_name ||
103
- (include_prefix_case? && method_name == prefixed_method_name(body))
113
+ method_name == body.method_name || (include_prefix_case? && method_name == prefixed_method_name(body))
104
114
  end
105
115
 
106
116
  def include_prefix_case?
@@ -108,21 +118,17 @@ module RuboCop
108
118
  end
109
119
 
110
120
  def prefixed_method_name(body)
121
+ return '' if body.receiver.self_type?
122
+
111
123
  [body.receiver.method_name, body.method_name].join('_').to_sym
112
124
  end
113
125
 
114
126
  def private_or_protected_delegation(node)
115
- line = node.first_line
116
- private_or_protected_before(line) ||
117
- private_or_protected_inline(line)
118
- end
119
-
120
- def private_or_protected_before(line)
121
- (processed_source[0..line].map(&:strip) & %w[private protected]).any?
127
+ private_or_protected_inline(node) || node_visibility(node) != :public
122
128
  end
123
129
 
124
- def private_or_protected_inline(line)
125
- processed_source[line - 1].strip.match?(/\A(private )|(protected )/)
130
+ def private_or_protected_inline(node)
131
+ processed_source[node.first_line - 1].strip.match?(/\A(private )|(protected )/)
126
132
  end
127
133
  end
128
134
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for delegations that pass :allow_blank as an option
6
+ # Looks for delegations that pass :allow_blank as an option
7
7
  # instead of :allow_nil. :allow_blank is not a valid option to pass
8
8
  # to ActiveSupport#delegate.
9
9
  #
@@ -27,7 +27,7 @@ module RuboCop
27
27
  return unless (offending_node = allow_blank_option(node))
28
28
 
29
29
  add_offense(offending_node) do |corrector|
30
- corrector.replace(offending_node.key.source_range, 'allow_nil')
30
+ corrector.replace(offending_node.key, 'allow_nil')
31
31
  end
32
32
  end
33
33
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks direct manipulation of ActiveModel#errors as hash.
6
+ # Checks direct manipulation of ActiveModel#errors as hash.
7
7
  # These operations are deprecated in Rails 6.1 and will not work in Rails 7.
8
8
  #
9
9
  # @safety
@@ -26,8 +26,19 @@ module RuboCop
26
26
  # # good
27
27
  # user.errors.delete(:name)
28
28
  #
29
+ # # bad
30
+ # user.errors.keys.include?(:attr)
31
+ #
32
+ # # good
33
+ # user.errors.attribute_names.include?(:attr)
34
+ #
29
35
  class DeprecatedActiveModelErrorsMethods < Base
36
+ include RangeHelp
37
+ extend AutoCorrector
38
+
30
39
  MSG = 'Avoid manipulating ActiveModel errors as hash directly.'
40
+ AUTOCORRECTABLE_METHODS = %i[<< clear keys].freeze
41
+ INCOMPATIBLE_METHODS = %i[keys values to_h to_xml].freeze
31
42
 
32
43
  MANIPULATIVE_METHODS = Set[
33
44
  *%i[
@@ -45,6 +56,7 @@ module RuboCop
45
56
  {
46
57
  #root_manipulation?
47
58
  #root_assignment?
59
+ #errors_deprecated?
48
60
  #messages_details_manipulation?
49
61
  #messages_details_assignment?
50
62
  }
@@ -66,6 +78,12 @@ module RuboCop
66
78
  ...)
67
79
  PATTERN
68
80
 
81
+ def_node_matcher :errors_deprecated?, <<~PATTERN
82
+ (send
83
+ (send #receiver_matcher :errors)
84
+ {:keys :values :to_h :to_xml})
85
+ PATTERN
86
+
69
87
  def_node_matcher :messages_details_manipulation?, <<~PATTERN
70
88
  (send
71
89
  (send
@@ -89,18 +107,60 @@ module RuboCop
89
107
 
90
108
  def on_send(node)
91
109
  any_manipulation?(node) do
92
- add_offense(node)
110
+ next if target_rails_version <= 6.0 && INCOMPATIBLE_METHODS.include?(node.method_name)
111
+
112
+ add_offense(node) do |corrector|
113
+ next if skip_autocorrect?(node)
114
+
115
+ autocorrect(corrector, node)
116
+ end
93
117
  end
94
118
  end
95
119
 
96
120
  private
97
121
 
122
+ def skip_autocorrect?(node)
123
+ return true unless AUTOCORRECTABLE_METHODS.include?(node.method_name)
124
+ return false unless (receiver = node.receiver.receiver)
125
+
126
+ receiver.send_type? && receiver.method?(:details) && node.method?(:<<)
127
+ end
128
+
129
+ def autocorrect(corrector, node)
130
+ receiver = node.receiver
131
+
132
+ range = offense_range(node, receiver)
133
+ replacement = replacement(node, receiver)
134
+
135
+ corrector.replace(range, replacement)
136
+ end
137
+
138
+ def offense_range(node, receiver)
139
+ receiver = receiver.receiver while receiver.send_type? && !receiver.method?(:errors) && receiver.receiver
140
+ range_between(receiver.source_range.end_pos, node.source_range.end_pos)
141
+ end
142
+
143
+ def replacement(node, receiver)
144
+ return '.attribute_names' if node.method?(:keys)
145
+
146
+ key = receiver.first_argument.source
147
+
148
+ case node.method_name
149
+ when :<<
150
+ value = node.first_argument.source
151
+
152
+ ".add(#{key}, #{value})"
153
+ when :clear
154
+ ".delete(#{key})"
155
+ end
156
+ end
157
+
98
158
  def receiver_matcher(node)
99
159
  model_file? ? receiver_matcher_inside_model(node) : receiver_matcher_outside_model(node)
100
160
  end
101
161
 
102
162
  def model_file?
103
- processed_source.buffer.name.include?('/models/')
163
+ processed_source.file_path.include?('/models/')
104
164
  end
105
165
  end
106
166
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Enforces the use of dot-separated locale keys instead of specifying the `:scope` option
7
+ # with an array or a single symbol in `I18n` translation methods.
8
+ # Dot-separated notation is easier to read and trace the hierarchy.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
13
+ # I18n.t :title, scope: :invitation
14
+ #
15
+ # # good
16
+ # I18n.t 'activerecord.errors.messages.record_invalid'
17
+ # I18n.t :record_invalid, scope: 'activerecord.errors.messages'
18
+ #
19
+ class DotSeparatedKeys < Base
20
+ include RangeHelp
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Use the dot-separated keys instead of specifying the `:scope` option.'
24
+ TRANSLATE_METHODS = %i[translate t].freeze
25
+
26
+ def_node_matcher :translate_with_scope?, <<~PATTERN
27
+ (send {nil? (const {nil? cbase} :I18n)} {:translate :t} ${sym_type? str_type?}
28
+ (hash <$(pair (sym :scope) ${array_type? sym_type?}) ...>)
29
+ )
30
+ PATTERN
31
+
32
+ def on_send(node)
33
+ return unless TRANSLATE_METHODS.include?(node.method_name)
34
+
35
+ translate_with_scope?(node) do |key_node, scope_node|
36
+ return unless should_convert_scope?(scope_node)
37
+
38
+ add_offense(scope_node) do |corrector|
39
+ # Eat the comma on the left.
40
+ range = range_with_surrounding_space(scope_node.source_range, side: :left)
41
+ range = range_with_surrounding_comma(range, :left)
42
+ corrector.remove(range)
43
+
44
+ corrector.replace(key_node, new_key(key_node, scope_node))
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def should_convert_scope?(scope_node)
52
+ scopes(scope_node).all?(&:basic_literal?)
53
+ end
54
+
55
+ def new_key(key_node, scope_node)
56
+ "'#{scopes(scope_node).map(&:value).join('.')}.#{key_node.value}'".squeeze('.')
57
+ end
58
+
59
+ def scopes(scope_node)
60
+ value = scope_node.value
61
+
62
+ if value.array_type?
63
+ value.values
64
+ else
65
+ [value]
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for associations that have been defined multiple times in the same file.
6
+ # Looks for associations that have been defined multiple times in the same file.
7
7
  #
8
8
  # When an association is defined multiple times on a model, Active Record overrides the
9
9
  # previously defined association with the new one. Because of this, this cop's autocorrection
@@ -35,7 +35,7 @@ module RuboCop
35
35
  offenses(class_node).each do |name, nodes|
36
36
  nodes.each do |node|
37
37
  add_offense(node, message: format(MSG, name: name)) do |corrector|
38
- next if nodes.last == node
38
+ next if same_line?(nodes.last, node)
39
39
 
40
40
  corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
41
41
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for multiple scopes in a model that have the same `where` clause. This
6
+ # Checks for multiple scopes in a model that have the same `where` clause. This
7
7
  # often means you copy/pasted a scope, updated the name, and forgot to change the condition.
8
8
  #
9
9
  # @example
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks if a duration is added to or subtracted from `Time.current`.
6
+ # Checks if a duration is added to or subtracted from `Time.current`.
7
7
  #
8
8
  # @example
9
9
  # # bad
@@ -70,15 +70,15 @@ module RuboCop
70
70
  # @return [Boolean] true if matches
71
71
  def_node_matcher :time_current?, <<~PATTERN
72
72
  {
73
- (send (const _ :Time) :current)
74
- (send (send (const _ :Time) :zone) :now)
73
+ (send (const {nil? cbase} :Time) :current)
74
+ (send (send (const {nil? cbase} :Time) :zone) :now)
75
75
  }
76
76
  PATTERN
77
77
 
78
78
  def on_send(node)
79
79
  duration_arithmetic_argument?(node) do |*operation|
80
80
  add_offense(node) do |corrector|
81
- corrector.replace(node.source_range, corrected_source(*operation))
81
+ corrector.replace(node, corrected_source(*operation))
82
82
  end
83
83
  end
84
84
  end