rubocop-rails 2.0.1 → 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +52 -5
  4. data/config/default.yml +726 -32
  5. data/config/obsoletion.yml +17 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +106 -0
  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 +40 -0
  10. data/lib/rubocop/cop/mixin/index_method.rb +165 -0
  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 +11 -21
  15. data/lib/rubocop/cop/rails/action_order.rb +116 -0
  16. data/lib/rubocop/cop/rails/active_record_aliases.rb +23 -24
  17. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +143 -0
  18. data/lib/rubocop/cop/rails/active_record_override.rb +3 -6
  19. data/lib/rubocop/cop/rails/active_support_aliases.rb +13 -22
  20. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  21. data/lib/rubocop/cop/rails/add_column_index.rb +61 -0
  22. data/lib/rubocop/cop/rails/after_commit_override.rb +81 -0
  23. data/lib/rubocop/cop/rails/application_controller.rb +36 -0
  24. data/lib/rubocop/cop/rails/application_job.rb +9 -4
  25. data/lib/rubocop/cop/rails/application_mailer.rb +39 -0
  26. data/lib/rubocop/cop/rails/application_record.rb +9 -9
  27. data/lib/rubocop/cop/rails/arel_star.rb +47 -0
  28. data/lib/rubocop/cop/rails/assert_not.rb +8 -10
  29. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
  30. data/lib/rubocop/cop/rails/belongs_to.rb +12 -24
  31. data/lib/rubocop/cop/rails/blank.rb +40 -36
  32. data/lib/rubocop/cop/rails/bulk_change_table.rb +40 -35
  33. data/lib/rubocop/cop/rails/compact_blank.rb +111 -0
  34. data/lib/rubocop/cop/rails/content_tag.rb +93 -0
  35. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +22 -15
  36. data/lib/rubocop/cop/rails/date.rb +41 -36
  37. data/lib/rubocop/cop/rails/default_scope.rb +61 -0
  38. data/lib/rubocop/cop/rails/delegate.rb +33 -29
  39. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +9 -10
  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 +76 -31
  46. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +82 -0
  47. data/lib/rubocop/cop/rails/enum_hash.rb +75 -0
  48. data/lib/rubocop/cop/rails/enum_uniqueness.rb +30 -12
  49. data/lib/rubocop/cop/rails/environment_comparison.rb +70 -22
  50. data/lib/rubocop/cop/rails/environment_variable_access.rb +67 -0
  51. data/lib/rubocop/cop/rails/exit.rb +7 -13
  52. data/lib/rubocop/cop/rails/expanded_date_range.rb +102 -0
  53. data/lib/rubocop/cop/rails/file_path.rb +48 -31
  54. data/lib/rubocop/cop/rails/find_by.rb +43 -24
  55. data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
  56. data/lib/rubocop/cop/rails/find_each.rb +42 -18
  57. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  58. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +4 -3
  59. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +62 -25
  60. data/lib/rubocop/cop/rails/helper_instance_variable.rb +32 -4
  61. data/lib/rubocop/cop/rails/http_positional_arguments.rb +61 -32
  62. data/lib/rubocop/cop/rails/http_status.rb +27 -23
  63. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +96 -0
  64. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +37 -0
  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 +9 -16
  68. data/lib/rubocop/cop/rails/index_by.rb +65 -0
  69. data/lib/rubocop/cop/rails/index_with.rb +68 -0
  70. data/lib/rubocop/cop/rails/inquiry.rb +39 -0
  71. data/lib/rubocop/cop/rails/inverse_of.rb +33 -27
  72. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +62 -32
  73. data/lib/rubocop/cop/rails/link_to_blank.rb +31 -32
  74. data/lib/rubocop/cop/rails/mailer_name.rb +90 -0
  75. data/lib/rubocop/cop/rails/match_route.rb +120 -0
  76. data/lib/rubocop/cop/rails/migration_class_name.rb +63 -0
  77. data/lib/rubocop/cop/rails/negate_include.rb +42 -0
  78. data/lib/rubocop/cop/rails/not_null_column.rb +16 -12
  79. data/lib/rubocop/cop/rails/order_by_id.rb +51 -0
  80. data/lib/rubocop/cop/rails/output.rb +29 -10
  81. data/lib/rubocop/cop/rails/output_safety.rb +9 -4
  82. data/lib/rubocop/cop/rails/pick.rb +64 -0
  83. data/lib/rubocop/cop/rails/pluck.rb +96 -0
  84. data/lib/rubocop/cop/rails/pluck_id.rb +59 -0
  85. data/lib/rubocop/cop/rails/pluck_in_where.rb +71 -0
  86. data/lib/rubocop/cop/rails/pluralization_grammar.rb +14 -19
  87. data/lib/rubocop/cop/rails/presence.rb +54 -26
  88. data/lib/rubocop/cop/rails/present.rb +40 -37
  89. data/lib/rubocop/cop/rails/rake_environment.rb +112 -0
  90. data/lib/rubocop/cop/rails/read_write_attribute.rb +56 -18
  91. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +33 -45
  92. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +77 -0
  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 +34 -32
  95. data/lib/rubocop/cop/rails/redundant_travel_back.rb +57 -0
  96. data/lib/rubocop/cop/rails/reflection_class_name.rb +56 -7
  97. data/lib/rubocop/cop/rails/refute_methods.rb +56 -35
  98. data/lib/rubocop/cop/rails/relative_date_constant.rb +52 -33
  99. data/lib/rubocop/cop/rails/render_inline.rb +41 -0
  100. data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
  101. data/lib/rubocop/cop/rails/request_referer.rb +10 -11
  102. data/lib/rubocop/cop/rails/require_dependency.rb +38 -0
  103. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  104. data/lib/rubocop/cop/rails/reversible_migration.rb +122 -82
  105. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +66 -0
  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 +55 -43
  110. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +50 -0
  111. data/lib/rubocop/cop/rails/save_bang.rb +89 -63
  112. data/lib/rubocop/cop/rails/schema_comment.rb +104 -0
  113. data/lib/rubocop/cop/rails/scope_args.rb +8 -3
  114. data/lib/rubocop/cop/rails/short_i18n.rb +71 -0
  115. data/lib/rubocop/cop/rails/skips_model_validations.rb +53 -16
  116. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +87 -0
  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 +83 -67
  121. data/lib/rubocop/cop/rails/time_zone_assignment.rb +37 -0
  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 +40 -49
  127. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +172 -0
  128. data/lib/rubocop/cop/rails/unknown_env.rb +52 -21
  129. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +76 -0
  130. data/lib/rubocop/cop/rails/validation.rb +54 -23
  131. data/lib/rubocop/cop/rails/where_equals.rb +102 -0
  132. data/lib/rubocop/cop/rails/where_exists.rb +138 -0
  133. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  134. data/lib/rubocop/cop/rails/where_not.rb +101 -0
  135. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  136. data/lib/rubocop/cop/rails_cops.rb +78 -8
  137. data/lib/rubocop/rails/inject.rb +1 -1
  138. data/lib/rubocop/rails/schema_loader/schema.rb +191 -0
  139. data/lib/rubocop/rails/schema_loader.rb +61 -0
  140. data/lib/rubocop/rails/version.rb +5 -1
  141. data/lib/rubocop/rails.rb +3 -1
  142. data/lib/rubocop-rails.rb +22 -0
  143. metadata +120 -19
  144. data/bin/setup +0 -7
@@ -3,104 +3,104 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of Time methods without zone.
6
+ # Checks for the use of Time methods without zone.
7
7
  #
8
- # Built on top of Ruby on Rails style guide (https://github.com/rubocop-hq/rails-style-guide#time)
8
+ # Built on top of Ruby on Rails style guide (https://rails.rubystyle.guide#time)
9
9
  # and the article http://danilenko.org/2012/7/6/rails_timezones/
10
10
  #
11
- # Two styles are supported for this cop. When EnforcedStyle is 'strict'
12
- # then only use of Time.zone is allowed.
11
+ # Two styles are supported for this cop. When `EnforcedStyle` is 'strict'
12
+ # then only use of `Time.zone` is allowed.
13
13
  #
14
14
  # When EnforcedStyle is 'flexible' then it's also allowed
15
- # to use Time.in_time_zone.
15
+ # to use `Time#in_time_zone`.
16
16
  #
17
- # @example EnforcedStyle: strict
18
- # # `strict` means that `Time` should be used with `zone`.
17
+ # @safety
18
+ # This cop's autocorrection is unsafe because it may change handling time.
19
19
  #
20
+ # @example
20
21
  # # bad
21
22
  # Time.now
22
- # Time.parse('2015-03-02 19:05:37')
23
- #
24
- # # bad
25
- # Time.current
26
- # Time.at(timestamp).in_time_zone
23
+ # Time.parse('2015-03-02T19:05:37')
27
24
  #
28
25
  # # good
26
+ # Time.current
29
27
  # Time.zone.now
30
- # Time.zone.parse('2015-03-02 19:05:37')
28
+ # Time.zone.parse('2015-03-02T19:05:37')
29
+ # Time.zone.parse('2015-03-02T19:05:37Z') # Respect ISO 8601 format with timezone specifier.
31
30
  #
32
31
  # @example EnforcedStyle: flexible (default)
33
32
  # # `flexible` allows usage of `in_time_zone` instead of `zone`.
34
33
  #
35
- # # bad
36
- # Time.now
37
- # Time.parse('2015-03-02 19:05:37')
38
- #
39
34
  # # good
40
- # Time.zone.now
41
- # Time.zone.parse('2015-03-02 19:05:37')
35
+ # Time.at(timestamp).in_time_zone
42
36
  #
43
- # # good
44
- # Time.current
37
+ # @example EnforcedStyle: strict
38
+ # # `strict` means that `Time` should be used with `zone`.
39
+ #
40
+ # # bad
45
41
  # Time.at(timestamp).in_time_zone
46
- class TimeZone < Cop
42
+ class TimeZone < Base
47
43
  include ConfigurableEnforcedStyle
44
+ extend AutoCorrector
48
45
 
49
- MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` ' \
50
- 'instead.'
46
+ MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` instead.'
51
47
 
52
- MSG_ACCEPTABLE = 'Do not use `%<current>s` without zone. ' \
53
- 'Use one of %<prefer>s instead.'
48
+ MSG_ACCEPTABLE = 'Do not use `%<current>s` without zone. Use one of %<prefer>s instead.'
54
49
 
55
- MSG_LOCALTIME = 'Do not use `Time.localtime` without ' \
56
- 'offset or zone.'
57
-
58
- TIMECLASSES = %i[Time DateTime].freeze
50
+ MSG_LOCALTIME = 'Do not use `Time.localtime` without offset or zone.'
59
51
 
60
52
  GOOD_METHODS = %i[zone zone_default find_zone find_zone!].freeze
61
53
 
62
- DANGEROUS_METHODS = %i[now local new parse at current].freeze
54
+ DANGEROUS_METHODS = %i[now local new parse at].freeze
55
+
56
+ ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601 jisx0301 rfc3339 httpdate to_i to_f].freeze
63
57
 
64
- ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601
65
- jisx0301 rfc3339 httpdate to_i to_f].freeze
58
+ TIMEZONE_SPECIFIER = /([A-z]|[+-]\d{2}:?\d{2})\z/.freeze
66
59
 
67
60
  def on_const(node)
68
61
  mod, klass = *node
69
62
  # we should only check core classes
70
- # (`DateTime`, `Time`, `::DateTime` or `::Time`)
63
+ # (`Time` or `::Time`)
71
64
  return unless (mod.nil? || mod.cbase_type?) && method_send?(node)
72
65
 
73
- check_time_node(klass, node.parent) if TIMECLASSES.include?(klass)
66
+ check_time_node(klass, node.parent) if klass == :Time
74
67
  end
75
68
 
76
- def autocorrect(node)
77
- lambda do |corrector|
78
- # add `.zone`: `Time.at` => `Time.zone.at`
79
- corrector.insert_after(node.children[0].source_range, '.zone')
69
+ private
70
+
71
+ def autocorrect(corrector, node)
72
+ # add `.zone`: `Time.at` => `Time.zone.at`
73
+ corrector.insert_after(node.children[0], '.zone')
74
+
75
+ case node.method_name
76
+ when :current
80
77
  # replace `Time.zone.current` => `Time.zone.now`
81
- if node.method_name == :current
82
- corrector.replace(node.loc.selector, 'now')
83
- end
84
- # prefer `Time` over `DateTime` class
85
- if strict?
86
- corrector.replace(node.children.first.source_range, 'Time')
87
- end
88
- remove_redundant_in_time_zone(corrector, node)
78
+ corrector.replace(node.loc.selector, 'now')
79
+ when :new
80
+ autocorrect_time_new(node, corrector)
89
81
  end
82
+
83
+ # prefer `Time` over `DateTime` class
84
+ corrector.replace(node.children.first, 'Time') if strict?
85
+ remove_redundant_in_time_zone(corrector, node)
90
86
  end
91
87
 
92
- private
88
+ def autocorrect_time_new(node, corrector)
89
+ if node.arguments?
90
+ corrector.replace(node.loc.selector, 'local')
91
+ else
92
+ corrector.replace(node.loc.selector, 'now')
93
+ end
94
+ end
93
95
 
94
96
  # remove redundant `.in_time_zone` from `Time.zone.now.in_time_zone`
95
97
  def remove_redundant_in_time_zone(corrector, node)
96
98
  time_methods_called = extract_method_chain(node)
97
- return unless time_methods_called.include?(:in_time_zone) ||
98
- time_methods_called.include?(:zone)
99
+ return unless time_methods_called.include?(:in_time_zone) || time_methods_called.include?(:zone)
99
100
 
100
101
  while node&.send_type?
101
102
  if node.children.last == :in_time_zone
102
- in_time_zone_with_dot =
103
- node.loc.selector.adjust(begin_pos: -1)
103
+ in_time_zone_with_dot = node.loc.selector.adjust(begin_pos: -1)
104
104
  corrector.remove(in_time_zone_with_dot)
105
105
  end
106
106
  node = node.parent
@@ -108,9 +108,10 @@ module RuboCop
108
108
  end
109
109
 
110
110
  def check_time_node(klass, node)
111
+ return if attach_timezone_specifier?(node.first_argument)
112
+
111
113
  chain = extract_method_chain(node)
112
114
  return if not_danger_chain?(chain)
113
-
114
115
  return check_localtime(node) if need_check_localtime?(chain)
115
116
 
116
117
  method_name = (chain & DANGEROUS_METHODS).join('.')
@@ -119,7 +120,13 @@ module RuboCop
119
120
 
120
121
  message = build_message(klass, method_name, node)
121
122
 
122
- add_offense(node, location: :selector, message: message)
123
+ add_offense(node.loc.selector, message: message) do |corrector|
124
+ autocorrect(corrector, node)
125
+ end
126
+ end
127
+
128
+ def attach_timezone_specifier?(date)
129
+ date.respond_to?(:value) && TIMEZONE_SPECIFIER.match?(date.value.to_s)
123
130
  end
124
131
 
125
132
  def build_message(klass, method_name, node)
@@ -131,9 +138,7 @@ module RuboCop
131
138
  )
132
139
  else
133
140
  safe_method_name = safe_method(method_name, node)
134
- format(MSG,
135
- current: "#{klass}.#{method_name}",
136
- prefer: "Time.zone.#{safe_method_name}")
141
+ format(MSG, current: "#{klass}.#{method_name}", prefer: "Time.zone.#{safe_method_name}")
137
142
  end
138
143
  end
139
144
 
@@ -153,7 +158,7 @@ module RuboCop
153
158
  if (receiver.is_a? RuboCop::AST::Node) && !receiver.cbase_type?
154
159
  method_from_time_class?(receiver)
155
160
  else
156
- TIMECLASSES.include?(method_name)
161
+ method_name == :Time
157
162
  end
158
163
  end
159
164
 
@@ -177,15 +182,16 @@ module RuboCop
177
182
  selector_node = node
178
183
 
179
184
  while node&.send_type?
180
- break if node.method_name == :localtime
185
+ break if node.method?(:localtime)
181
186
 
182
187
  node = node.parent
183
188
  end
184
189
 
185
190
  return if node.arguments?
186
191
 
187
- add_offense(selector_node,
188
- location: :selector, message: MSG_LOCALTIME)
192
+ add_offense(selector_node.loc.selector, message: MSG_LOCALTIME) do |corrector|
193
+ autocorrect(corrector, selector_node)
194
+ end
189
195
  end
190
196
 
191
197
  def not_danger_chain?(chain)
@@ -213,10 +219,7 @@ module RuboCop
213
219
  end
214
220
 
215
221
  def acceptable_methods(klass, method_name, node)
216
- acceptable = [
217
- "`Time.zone.#{safe_method(method_name, node)}`",
218
- "`#{klass}.current`"
219
- ]
222
+ acceptable = ["`Time.zone.#{safe_method(method_name, node)}`", "`#{klass}.current`"]
220
223
 
221
224
  ACCEPTED_METHODS.each do |am|
222
225
  acceptable << "`#{klass}.#{method_name}.#{am}`"
@@ -225,12 +228,25 @@ module RuboCop
225
228
  acceptable
226
229
  end
227
230
 
228
- # Time.new can be called with a time zone offset
231
+ # Time.new, Time.at, and Time.now can be called with a time zone offset
229
232
  # When it is, that should be considered safe
230
233
  # Example:
231
234
  # Time.new(1988, 3, 15, 3, 0, 0, "-05:00")
232
235
  def offset_provided?(node)
233
- node.arguments.size >= 7
236
+ case node.method_name
237
+ when :new
238
+ node.arguments.size == 7 || offset_option_provided?(node)
239
+ when :at, :now
240
+ offset_option_provided?(node)
241
+ end
242
+ end
243
+
244
+ def offset_option_provided?(node)
245
+ options = node.last_argument
246
+ options&.hash_type? &&
247
+ options.each_pair.any? do |pair|
248
+ pair.key.sym_type? && pair.key.value == :in && !pair.value.nil_type?
249
+ end
234
250
  end
235
251
  end
236
252
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for the use of `Time.zone=` method.
7
+ #
8
+ # The `zone` attribute persists for the rest of the Ruby runtime, potentially causing
9
+ # unexpected behavior at a later time.
10
+ # Using `Time.use_zone` ensures the code passed in the block is the only place Time.zone is affected.
11
+ # It eliminates the possibility of a `zone` sticking around longer than intended.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # Time.zone = 'EST'
16
+ #
17
+ # # good
18
+ # Time.use_zone('EST') do
19
+ # end
20
+ #
21
+ class TimeZoneAssignment < Base
22
+ MSG = 'Use `Time.use_zone` with block instead of `Time.zone=`.'
23
+ RESTRICT_ON_SEND = %i[zone=].freeze
24
+
25
+ def_node_matcher :time_zone_assignment?, <<~PATTERN
26
+ (send (const {nil? cbase} :Time) :zone= ...)
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ return unless time_zone_assignment?(node)
31
+
32
+ add_offense(node)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for consistent uses of `to_fs` or `to_formatted_s`,
7
+ # depending on the cop's configuration.
8
+ #
9
+ # @example EnforcedStyle: to_fs (default)
10
+ #
11
+ # # bad
12
+ # time.to_formatted_s(:db)
13
+ #
14
+ # # good
15
+ # time.to_fs(:db)
16
+ #
17
+ # @example EnforcedStyle: to_formatted_s
18
+ #
19
+ # # bad
20
+ # time.to_fs(:db)
21
+ #
22
+ # # good
23
+ # time.to_formatted_s(:db)
24
+ #
25
+ class ToFormattedS < Base
26
+ include ConfigurableEnforcedStyle
27
+ extend AutoCorrector
28
+ extend TargetRailsVersion
29
+
30
+ minimum_target_rails_version 7.0
31
+
32
+ MSG = 'Use `%<prefer>s` instead.'
33
+ RESTRICT_ON_SEND = %i[to_formatted_s to_fs].freeze
34
+
35
+ def on_send(node)
36
+ return if node.method?(style)
37
+
38
+ add_offense(node.loc.selector, message: format(MSG, prefer: style)) do |corrector|
39
+ corrector.replace(node.loc.selector, style)
40
+ end
41
+ end
42
+ alias on_csend on_send
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies passing any argument to `#to_s`.
7
+ #
8
+ # @safety
9
+ # This cop is marked as unsafe because it may detect `#to_s` calls
10
+ # that are not related to Active Support implementation.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # obj.to_s(:delimited)
16
+ #
17
+ # # good
18
+ # obj.to_formatted_s(:delimited)
19
+ #
20
+ class ToSWithArgument < Base
21
+ extend AutoCorrector
22
+ extend TargetRailsVersion
23
+
24
+ # These types are defined by the following files in ActiveSupport:
25
+ # lib/active_support/core_ext/array/conversions.rb
26
+ # lib/active_support/core_ext/date/conversions.rb
27
+ # lib/active_support/core_ext/date_time/conversions.rb
28
+ # lib/active_support/core_ext/numeric/conversions.rb
29
+ # lib/active_support/core_ext/range/conversions.rb
30
+ # lib/active_support/core_ext/time/conversions.rb
31
+ # lib/active_support/time_with_zone.rb
32
+ EXTENDED_FORMAT_TYPES = Set.new(
33
+ %i[
34
+ currency
35
+ db
36
+ delimited
37
+ human
38
+ human_size
39
+ inspect
40
+ iso8601
41
+ long
42
+ long_ordinal
43
+ nsec
44
+ number
45
+ percentage
46
+ phone
47
+ rfc822
48
+ rounded
49
+ short
50
+ time
51
+ usec
52
+ ]
53
+ )
54
+
55
+ MSG = 'Use `to_formatted_s` instead.'
56
+
57
+ RESTRICT_ON_SEND = %i[to_s].freeze
58
+
59
+ minimum_target_rails_version 7.0
60
+
61
+ def on_send(node)
62
+ return unless rails_extended_to_s?(node)
63
+
64
+ add_offense(node.loc.selector) do |corrector|
65
+ corrector.replace(node.loc.selector, 'to_formatted_s')
66
+ end
67
+ end
68
+ alias on_csend on_send
69
+
70
+ private
71
+
72
+ def rails_extended_to_s?(node)
73
+ node.first_argument&.sym_type? && EXTENDED_FORMAT_TYPES.include?(node.first_argument.value)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies top-level `HashWithIndifferentAccess`.
7
+ # This has been soft-deprecated since Rails 5.1.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # HashWithIndifferentAccess.new(foo: 'bar')
12
+ #
13
+ # # good
14
+ # ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
15
+ #
16
+ class TopLevelHashWithIndifferentAccess < Base
17
+ extend AutoCorrector
18
+ extend TargetRailsVersion
19
+
20
+ minimum_target_rails_version 5.1
21
+
22
+ MSG = 'Avoid top-level `HashWithIndifferentAccess`.'
23
+
24
+ # @!method top_level_hash_with_indifferent_access?(node)
25
+ # @param [RuboCop::AST::ConstNode] node
26
+ # @return [Boolean]
27
+ def_node_matcher :top_level_hash_with_indifferent_access?, <<~PATTERN
28
+ (const {nil? cbase} :HashWithIndifferentAccess)
29
+ PATTERN
30
+
31
+ # @param [RuboCop::AST::ConstNode] node
32
+ def on_const(node)
33
+ return unless top_level_hash_with_indifferent_access?(node)
34
+ return if node.parent&.class_type? && node.parent.ancestors.any?(&:module_type?)
35
+
36
+ add_offense(node) do |corrector|
37
+ autocorrect(corrector, node)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def autocorrect(corrector, node)
44
+ corrector.insert_before(node.location.name, 'ActiveSupport::')
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for the use of exit statements (namely `return`,
7
+ # `break` and `throw`) in transactions. This is due to the eventual
8
+ # unexpected behavior when using ActiveRecord >= 7, where transactions
9
+ # exited using these statements are being rollbacked rather than
10
+ # committed (pre ActiveRecord 7 behavior).
11
+ #
12
+ # As alternatives, it would be more intuitive to explicitly raise an
13
+ # error when rollback is desired, and to use `next` when commit is
14
+ # desired.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # ApplicationRecord.transaction do
19
+ # return if user.active?
20
+ # end
21
+ #
22
+ # # bad
23
+ # ApplicationRecord.transaction do
24
+ # break if user.active?
25
+ # end
26
+ #
27
+ # # bad
28
+ # ApplicationRecord.transaction do
29
+ # throw if user.active?
30
+ # end
31
+ #
32
+ # # bad, as `with_lock` implicitly opens a transaction too
33
+ # user.with_lock do
34
+ # throw if user.active?
35
+ # end
36
+ #
37
+ # # good
38
+ # ApplicationRecord.transaction do
39
+ # # Rollback
40
+ # raise "User is active" if user.active?
41
+ # end
42
+ #
43
+ # # good
44
+ # ApplicationRecord.transaction do
45
+ # # Commit
46
+ # next if user.active?
47
+ # end
48
+ class TransactionExitStatement < Base
49
+ MSG = <<~MSG.chomp
50
+ Exit statement `%<statement>s` is not allowed. Use `raise` (rollback) or `next` (commit).
51
+ MSG
52
+
53
+ RESTRICT_ON_SEND = %i[transaction with_lock].freeze
54
+
55
+ def_node_search :exit_statements, <<~PATTERN
56
+ ({return | break | send nil? :throw} ...)
57
+ PATTERN
58
+
59
+ def_node_matcher :rescue_body_return_node?, <<~PATTERN
60
+ (:resbody ...
61
+ ...
62
+ ({return | break | send nil? :throw} ...)
63
+ ...
64
+ )
65
+ PATTERN
66
+
67
+ def on_send(node)
68
+ return unless (parent = node.parent)
69
+ return unless parent.block_type? && parent.body
70
+
71
+ exit_statements(parent.body).each do |statement_node|
72
+ next if statement_node.break_type? && nested_block?(statement_node)
73
+
74
+ statement = statement(statement_node)
75
+ message = format(MSG, statement: statement)
76
+
77
+ add_offense(statement_node, message: message)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def statement(statement_node)
84
+ if statement_node.return_type?
85
+ 'return'
86
+ elsif statement_node.break_type?
87
+ 'break'
88
+ else
89
+ statement_node.method_name
90
+ end
91
+ end
92
+
93
+ def nested_block?(statement_node)
94
+ !statement_node.ancestors.find(&:block_type?).method?(:transaction)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end