rubocop-rails 2.19.1 → 2.30.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +70 -16
  4. data/config/default.yml +173 -28
  5. data/lib/rubocop/cop/mixin/active_record_helper.rb +16 -4
  6. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
  7. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +66 -0
  8. data/lib/rubocop/cop/mixin/index_method.rb +68 -61
  9. data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
  10. data/lib/rubocop/cop/mixin/target_rails_version.rb +27 -2
  11. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +3 -1
  12. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  13. data/lib/rubocop/cop/rails/action_filter.rb +3 -0
  14. data/lib/rubocop/cop/rails/action_order.rb +1 -5
  15. data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -2
  16. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
  17. data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
  18. data/lib/rubocop/cop/rails/active_support_on_load.rb +21 -1
  19. data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
  20. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  21. data/lib/rubocop/cop/rails/application_record.rb +4 -0
  22. data/lib/rubocop/cop/rails/assert_not.rb +0 -1
  23. data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
  24. data/lib/rubocop/cop/rails/blank.rb +1 -1
  25. data/lib/rubocop/cop/rails/bulk_change_table.rb +19 -45
  26. data/lib/rubocop/cop/rails/compact_blank.rb +29 -8
  27. data/lib/rubocop/cop/rails/content_tag.rb +2 -2
  28. data/lib/rubocop/cop/rails/dangerous_column_names.rb +448 -0
  29. data/lib/rubocop/cop/rails/date.rb +14 -5
  30. data/lib/rubocop/cop/rails/delegate.rb +53 -7
  31. data/lib/rubocop/cop/rails/duplicate_association.rb +71 -10
  32. data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
  33. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +2 -2
  34. data/lib/rubocop/cop/rails/enum_hash.rb +31 -8
  35. data/lib/rubocop/cop/rails/enum_syntax.rb +130 -0
  36. data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
  37. data/lib/rubocop/cop/rails/env_local.rb +69 -0
  38. data/lib/rubocop/cop/rails/expanded_date_range.rb +1 -1
  39. data/lib/rubocop/cop/rails/file_path.rb +186 -18
  40. data/lib/rubocop/cop/rails/find_by.rb +3 -3
  41. data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
  42. data/lib/rubocop/cop/rails/find_each.rb +1 -1
  43. data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
  44. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -1
  45. data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
  46. data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
  47. data/lib/rubocop/cop/rails/http_status.rb +16 -5
  48. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
  49. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +5 -1
  50. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +23 -3
  51. data/lib/rubocop/cop/rails/index_by.rb +28 -12
  52. data/lib/rubocop/cop/rails/index_with.rb +28 -12
  53. data/lib/rubocop/cop/rails/inquiry.rb +2 -1
  54. data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
  55. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +19 -10
  56. data/lib/rubocop/cop/rails/link_to_blank.rb +2 -2
  57. data/lib/rubocop/cop/rails/match_route.rb +1 -9
  58. data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
  59. data/lib/rubocop/cop/rails/not_null_column.rb +100 -6
  60. data/lib/rubocop/cop/rails/output.rb +3 -2
  61. data/lib/rubocop/cop/rails/pick.rb +10 -5
  62. data/lib/rubocop/cop/rails/pluck.rb +21 -1
  63. data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
  64. data/lib/rubocop/cop/rails/pluck_in_where.rb +35 -13
  65. data/lib/rubocop/cop/rails/pluralization_grammar.rb +30 -16
  66. data/lib/rubocop/cop/rails/presence.rb +1 -1
  67. data/lib/rubocop/cop/rails/present.rb +1 -3
  68. data/lib/rubocop/cop/rails/rake_environment.rb +22 -6
  69. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +190 -0
  70. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
  71. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +16 -0
  72. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +2 -2
  73. data/lib/rubocop/cop/rails/reflection_class_name.rb +2 -2
  74. data/lib/rubocop/cop/rails/refute_methods.rb +0 -1
  75. data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
  76. data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
  77. data/lib/rubocop/cop/rails/request_referer.rb +1 -1
  78. data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
  79. data/lib/rubocop/cop/rails/reversible_migration.rb +7 -5
  80. data/lib/rubocop/cop/rails/root_pathname_methods.rb +58 -15
  81. data/lib/rubocop/cop/rails/save_bang.rb +22 -14
  82. data/lib/rubocop/cop/rails/schema_comment.rb +17 -10
  83. data/lib/rubocop/cop/rails/select_map.rb +79 -0
  84. data/lib/rubocop/cop/rails/skips_model_validations.rb +9 -4
  85. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -2
  86. data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
  87. data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
  88. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +4 -5
  89. data/lib/rubocop/cop/rails/time_zone.rb +26 -11
  90. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +40 -9
  91. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +11 -26
  92. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +17 -21
  93. data/lib/rubocop/cop/rails/unknown_env.rb +5 -1
  94. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
  95. data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
  96. data/lib/rubocop/cop/rails/validation.rb +9 -4
  97. data/lib/rubocop/cop/rails/where_equals.rb +29 -12
  98. data/lib/rubocop/cop/rails/where_exists.rb +9 -9
  99. data/lib/rubocop/cop/rails/where_missing.rb +6 -2
  100. data/lib/rubocop/cop/rails/where_not.rb +18 -11
  101. data/lib/rubocop/cop/rails/where_range.rb +203 -0
  102. data/lib/rubocop/cop/rails_cops.rb +11 -0
  103. data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
  104. data/lib/rubocop/rails/plugin.rb +48 -0
  105. data/lib/rubocop/rails/schema_loader/schema.rb +8 -7
  106. data/lib/rubocop/rails/schema_loader.rb +5 -15
  107. data/lib/rubocop/rails/version.rb +1 -1
  108. data/lib/rubocop/rails.rb +1 -8
  109. data/lib/rubocop-rails.rb +12 -4
  110. metadata +55 -11
  111. data/lib/rubocop/rails/inject.rb +0 -18
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # Identifies usages of file path joining process to use `Rails.root.join` clause.
7
7
  # It is used to add uniformity when joining paths.
8
8
  #
9
+ # NOTE: This cop ignores leading slashes in string literal arguments for `Rails.root.join`
10
+ # and multiple slashes in string literal arguments for `Rails.root.join` and `File.join`.
11
+ #
9
12
  # @example EnforcedStyle: slashes (default)
10
13
  # # bad
11
14
  # Rails.root.join('app', 'models', 'goober')
@@ -35,6 +38,8 @@ module RuboCop
35
38
  # Rails.root.join('app', 'models', 'goober').to_s
36
39
  #
37
40
  class FilePath < Base
41
+ extend AutoCorrector
42
+
38
43
  include ConfigurableEnforcedStyle
39
44
  include RangeHelp
40
45
 
@@ -56,61 +61,112 @@ module RuboCop
56
61
 
57
62
  def on_dstr(node)
58
63
  return unless rails_root_nodes?(node)
59
- return unless node.children.last.str_type?
64
+ return if dstr_separated_by_colon?(node)
60
65
 
61
- last_child_source = node.children.last.source
62
- return unless last_child_source.start_with?('.') || last_child_source.include?(File::SEPARATOR)
63
- return if last_child_source.start_with?(':')
64
-
65
- register_offense(node, require_to_s: true)
66
+ check_for_slash_after_rails_root_in_dstr(node)
67
+ check_for_extension_after_rails_root_join_in_dstr(node)
66
68
  end
67
69
 
68
70
  def on_send(node)
69
71
  check_for_file_join_with_rails_root(node)
72
+ return unless node.receiver
73
+
70
74
  check_for_rails_root_join_with_slash_separated_path(node)
71
75
  check_for_rails_root_join_with_string_arguments(node)
72
76
  end
73
77
 
74
78
  private
75
79
 
80
+ def check_for_slash_after_rails_root_in_dstr(node)
81
+ rails_root_index = find_rails_root_index(node)
82
+ slash_node = node.children[rails_root_index + 1]
83
+ return unless slash_node&.str_type? && slash_node.source.start_with?(File::SEPARATOR)
84
+ return unless node.children[rails_root_index].children.first.send_type?
85
+
86
+ register_offense(node, require_to_s: false) do |corrector|
87
+ autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
88
+ end
89
+ end
90
+
91
+ def check_for_extension_after_rails_root_join_in_dstr(node)
92
+ rails_root_index = find_rails_root_index(node)
93
+ extension_node = node.children[rails_root_index + 1]
94
+ return unless extension_node?(extension_node)
95
+
96
+ register_offense(node, require_to_s: false) do |corrector|
97
+ autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
98
+ end
99
+ end
100
+
76
101
  def check_for_file_join_with_rails_root(node)
77
102
  return unless file_join_nodes?(node)
78
- return unless node.arguments.any? { |e| rails_root_nodes?(e) }
103
+ return unless valid_arguments_for_file_join_with_rails_root?(node.arguments)
79
104
 
80
- register_offense(node, require_to_s: true)
105
+ register_offense(node, require_to_s: true) do |corrector|
106
+ autocorrect_file_join(corrector, node) unless node.first_argument.array_type?
107
+ end
81
108
  end
82
109
 
83
110
  def check_for_rails_root_join_with_string_arguments(node)
84
111
  return unless style == :slashes
85
112
  return unless rails_root_nodes?(node)
86
113
  return unless rails_root_join_nodes?(node)
87
- return unless node.arguments.size > 1
88
- return unless node.arguments.all?(&:str_type?)
114
+ return unless valid_string_arguments_for_rails_root_join?(node.arguments)
89
115
 
90
- register_offense(node, require_to_s: false)
116
+ register_offense(node, require_to_s: false) do |corrector|
117
+ autocorrect_rails_root_join_with_string_arguments(corrector, node)
118
+ end
91
119
  end
92
120
 
93
121
  def check_for_rails_root_join_with_slash_separated_path(node)
94
122
  return unless style == :arguments
95
123
  return unless rails_root_nodes?(node)
96
124
  return unless rails_root_join_nodes?(node)
97
- return unless node.arguments.any? { |arg| string_with_slash?(arg) }
125
+ return unless valid_slash_separated_path_for_rails_root_join?(node.arguments)
98
126
 
99
- register_offense(node, require_to_s: false)
127
+ register_offense(node, require_to_s: false) do |corrector|
128
+ autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
129
+ end
100
130
  end
101
131
 
102
- def string_with_slash?(node)
103
- node.str_type? && node.source.include?('/')
132
+ def valid_arguments_for_file_join_with_rails_root?(arguments)
133
+ return false unless arguments.any? { |arg| rails_root_nodes?(arg) }
134
+
135
+ arguments.none? { |arg| arg.variable? || arg.const_type? || string_contains_multiple_slashes?(arg) }
104
136
  end
105
137
 
106
- def register_offense(node, require_to_s:)
138
+ def valid_string_arguments_for_rails_root_join?(arguments)
139
+ return false unless arguments.size > 1
140
+ return false unless arguments.all?(&:str_type?)
141
+
142
+ arguments.none? { |arg| string_with_leading_slash?(arg) || string_contains_multiple_slashes?(arg) }
143
+ end
144
+
145
+ def valid_slash_separated_path_for_rails_root_join?(arguments)
146
+ return false unless arguments.any? { |arg| string_contains_slash?(arg) }
147
+
148
+ arguments.none? { |arg| string_with_leading_slash?(arg) || string_contains_multiple_slashes?(arg) }
149
+ end
150
+
151
+ def string_contains_slash?(node)
152
+ node.str_type? && node.value.include?(File::SEPARATOR)
153
+ end
154
+
155
+ def string_contains_multiple_slashes?(node)
156
+ node.str_type? && node.value.include?('//')
157
+ end
158
+
159
+ def string_with_leading_slash?(node)
160
+ node.str_type? && node.value.start_with?(File::SEPARATOR)
161
+ end
162
+
163
+ def register_offense(node, require_to_s:, &block)
107
164
  line_range = node.loc.column...node.loc.last_column
108
165
  source_range = source_range(processed_source.buffer, node.first_line, line_range)
109
- require_to_s = false if node.dstr_type?
110
166
 
111
167
  message = build_message(require_to_s)
112
168
 
113
- add_offense(source_range, message: message)
169
+ add_offense(source_range, message: message, &block)
114
170
  end
115
171
 
116
172
  def build_message(require_to_s)
@@ -119,6 +175,118 @@ module RuboCop
119
175
 
120
176
  format(message_template, to_s: to_s)
121
177
  end
178
+
179
+ def dstr_separated_by_colon?(node)
180
+ node.children[1..].any? do |child|
181
+ child.str_type? && child.source.start_with?(':')
182
+ end
183
+ end
184
+
185
+ def autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
186
+ rails_root_node = node.children[rails_root_index].children.first
187
+ argument_source = extract_rails_root_join_argument_source(node, rails_root_index)
188
+ if rails_root_node.method?(:join)
189
+ append_argument(corrector, rails_root_node, argument_source)
190
+ else
191
+ replace_with_rails_root_join(corrector, rails_root_node, argument_source)
192
+ end
193
+ node.children[rails_root_index + 1..].each { |child| corrector.remove(child) }
194
+ end
195
+
196
+ def autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
197
+ rails_root_node = node.children[rails_root_index].children.first
198
+ return unless rails_root_node.last_argument.str_type?
199
+
200
+ corrector.insert_before(rails_root_node.last_argument.location.end, extension_node.source)
201
+ corrector.remove(extension_node)
202
+ end
203
+
204
+ def autocorrect_file_join(corrector, node)
205
+ replace_receiver_with_rails_root(corrector, node)
206
+ remove_first_argument_with_comma(corrector, node)
207
+ process_arguments(corrector, node.arguments)
208
+ append_to_string_conversion(corrector, node)
209
+ end
210
+
211
+ def replace_receiver_with_rails_root(corrector, node)
212
+ corrector.replace(node.receiver, 'Rails.root')
213
+ end
214
+
215
+ def remove_first_argument_with_comma(corrector, node)
216
+ corrector.remove(
217
+ range_with_surrounding_space(
218
+ range_with_surrounding_comma(
219
+ node.first_argument.source_range,
220
+ :right
221
+ ),
222
+ side: :right
223
+ )
224
+ )
225
+ end
226
+
227
+ def process_arguments(corrector, arguments)
228
+ arguments.each do |argument|
229
+ if argument.str_type?
230
+ corrector.replace(argument, argument.value.delete_prefix('/').inspect)
231
+ elsif argument.array_type?
232
+ corrector.replace(argument, "*#{argument.source}")
233
+ end
234
+ end
235
+ end
236
+
237
+ def append_to_string_conversion(corrector, node)
238
+ corrector.insert_after(node, '.to_s')
239
+ end
240
+
241
+ def autocorrect_rails_root_join_with_string_arguments(corrector, node)
242
+ corrector.replace(node.first_argument, %("#{node.arguments.map(&:value).join('/')}"))
243
+ node.arguments[1..].each do |argument|
244
+ corrector.remove(
245
+ range_with_surrounding_comma(
246
+ range_with_surrounding_space(
247
+ argument.source_range,
248
+ side: :left
249
+ ),
250
+ :left
251
+ )
252
+ )
253
+ end
254
+ end
255
+
256
+ def autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
257
+ node.arguments.each do |argument|
258
+ next unless string_contains_slash?(argument)
259
+
260
+ index = argument.source.index(File::SEPARATOR)
261
+ rest = inner_range_of(argument).adjust(begin_pos: index - 1)
262
+ corrector.remove(rest)
263
+ corrector.insert_after(argument, %(, "#{rest.source.delete_prefix(File::SEPARATOR)}"))
264
+ end
265
+ end
266
+
267
+ def inner_range_of(node)
268
+ node.location.end.with(begin_pos: node.location.begin.end_pos).adjust(end_pos: -1)
269
+ end
270
+
271
+ def find_rails_root_index(node)
272
+ node.children.index { |child| rails_root_nodes?(child) }
273
+ end
274
+
275
+ def append_argument(corrector, node, argument_source)
276
+ corrector.insert_after(node.last_argument, %(, "#{argument_source}"))
277
+ end
278
+
279
+ def replace_with_rails_root_join(corrector, node, argument_source)
280
+ corrector.replace(node, %<Rails.root.join("#{argument_source}")>)
281
+ end
282
+
283
+ def extract_rails_root_join_argument_source(node, rails_root_index)
284
+ node.children[rails_root_index + 1..].map(&:source).join.delete_prefix(File::SEPARATOR)
285
+ end
286
+
287
+ def extension_node?(node)
288
+ node&.str_type? && node.source.match?(/\A\.[A-Za-z]+/)
289
+ end
122
290
  end
123
291
  end
124
292
  end
@@ -28,7 +28,7 @@ module RuboCop
28
28
  include RangeHelp
29
29
  extend AutoCorrector
30
30
 
31
- MSG = 'Use `find_by` instead of `where.%<method>s`.'
31
+ MSG = 'Use `find_by` instead of `where%<dot>s%<method>s`.'
32
32
  RESTRICT_ON_SEND = %i[first take].freeze
33
33
 
34
34
  def on_send(node)
@@ -37,7 +37,7 @@ module RuboCop
37
37
 
38
38
  range = offense_range(node)
39
39
 
40
- add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
40
+ add_offense(range, message: format(MSG, dot: node.loc.dot.source, method: node.method_name)) do |corrector|
41
41
  autocorrect(corrector, node)
42
42
  end
43
43
  end
@@ -59,7 +59,7 @@ module RuboCop
59
59
  return if node.method?(:first)
60
60
 
61
61
  where_loc = node.receiver.loc.selector
62
- first_loc = range_between(node.loc.dot.begin_pos, node.loc.selector.end_pos)
62
+ first_loc = range_between(node.receiver.source_range.end_pos, node.loc.selector.end_pos)
63
63
 
64
64
  corrector.replace(where_loc, 'find_by')
65
65
  corrector.replace(first_loc, '')
@@ -24,40 +24,39 @@ module RuboCop
24
24
  RESTRICT_ON_SEND = %i[take! find_by_id! find_by!].freeze
25
25
 
26
26
  def_node_matcher :where_take?, <<~PATTERN
27
- (send
28
- $(send _ :where
27
+ (call
28
+ $(call _ :where
29
29
  (hash
30
30
  (pair (sym :id) $_))) :take!)
31
31
  PATTERN
32
32
 
33
33
  def_node_matcher :find_by?, <<~PATTERN
34
34
  {
35
- (send _ :find_by_id! $_)
36
- (send _ :find_by! (hash (pair (sym :id) $_)))
35
+ (call _ :find_by_id! $_)
36
+ (call _ :find_by! (hash (pair (sym :id) $_)))
37
37
  }
38
38
  PATTERN
39
39
 
40
40
  def on_send(node)
41
41
  where_take?(node) do |where, id_value|
42
42
  range = where_take_offense_range(node, where)
43
- bad_method = build_where_take_bad_method(id_value)
44
43
 
45
- register_offense(range, id_value, bad_method)
44
+ register_offense(range, id_value)
46
45
  end
47
46
 
48
47
  find_by?(node) do |id_value|
49
48
  range = find_by_offense_range(node)
50
- bad_method = build_find_by_bad_method(node, id_value)
51
49
 
52
- register_offense(range, id_value, bad_method)
50
+ register_offense(range, id_value)
53
51
  end
54
52
  end
53
+ alias on_csend on_send
55
54
 
56
55
  private
57
56
 
58
- def register_offense(range, id_value, bad_method)
57
+ def register_offense(range, id_value)
59
58
  good_method = build_good_method(id_value)
60
- message = format(MSG, good_method: good_method, bad_method: bad_method)
59
+ message = format(MSG, good_method: good_method, bad_method: range.source)
61
60
 
62
61
  add_offense(range, message: message) do |corrector|
63
62
  corrector.replace(range, good_method)
@@ -75,19 +74,6 @@ module RuboCop
75
74
  def build_good_method(id_value)
76
75
  "find(#{id_value.source})"
77
76
  end
78
-
79
- def build_where_take_bad_method(id_value)
80
- "where(id: #{id_value.source}).take!"
81
- end
82
-
83
- def build_find_by_bad_method(node, id_value)
84
- case node.method_name
85
- when :find_by_id!
86
- "find_by_id!(#{id_value.source})"
87
- when :find_by!
88
- "find_by!(id: #{id_value.source})"
89
- end
90
- end
91
77
  end
92
78
  end
93
79
  end
@@ -34,7 +34,7 @@ module RuboCop
34
34
  RESTRICT_ON_SEND = %i[each].freeze
35
35
 
36
36
  SCOPE_METHODS = %i[
37
- all eager_load includes joins left_joins left_outer_joins not preload
37
+ all eager_load includes joins left_joins left_outer_joins not or preload
38
38
  references unscoped where
39
39
  ].freeze
40
40
 
@@ -69,7 +69,7 @@ module RuboCop
69
69
  return false unless CONVERT_METHODS.include?(method_name)
70
70
 
71
71
  child_node, child_method_name, time_argument = *node.children
72
- return if time_argument
72
+ return false if time_argument
73
73
 
74
74
  current_time?(child_node, child_method_name)
75
75
  end
@@ -60,7 +60,7 @@ module RuboCop
60
60
  (block
61
61
  (send nil? :with_options
62
62
  (hash $...))
63
- (args) ...)
63
+ (args _?) ...)
64
64
  PATTERN
65
65
 
66
66
  def_node_matcher :association_extension_block?, <<~PATTERN
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # Checks for use of the helper methods which reference
7
7
  # instance variables.
8
8
  #
9
- # Relying on instance variables makes it difficult to re-use helper
9
+ # Relying on instance variables makes it difficult to reuse helper
10
10
  # methods.
11
11
  #
12
12
  # If it seems awkward to explicitly pass in each dependent
@@ -40,6 +40,10 @@ module RuboCop
40
40
  (hash (kwsplat _))
41
41
  PATTERN
42
42
 
43
+ def_node_matcher :forwarded_kwrestarg?, <<~PATTERN
44
+ (hash (forwarded-kwrestarg))
45
+ PATTERN
46
+
43
47
  def_node_matcher :include_rack_test_methods?, <<~PATTERN
44
48
  (send nil? :include
45
49
  (const
@@ -83,7 +87,9 @@ module RuboCop
83
87
  end
84
88
  end
85
89
 
90
+ # rubocop:disable Metrics/CyclomaticComplexity
86
91
  def needs_conversion?(data)
92
+ return false if data.forwarded_args_type? || forwarded_kwrestarg?(data)
87
93
  return true unless data.hash_type?
88
94
  return false if kwsplat_hash?(data)
89
95
 
@@ -91,6 +97,7 @@ module RuboCop
91
97
  special_keyword_arg?(pair.key) || (format_arg?(pair.key) && data.pairs.one?)
92
98
  end
93
99
  end
100
+ # rubocop:enable Metrics/CyclomaticComplexity
94
101
 
95
102
  def special_keyword_arg?(node)
96
103
  node.sym_type? && KEYWORD_ARGS.include?(node.value)
@@ -8,10 +8,13 @@ module RuboCop
8
8
  # @example EnforcedStyle: symbolic (default)
9
9
  # # bad
10
10
  # render :foo, status: 200
11
+ # render :foo, status: '200'
11
12
  # render json: { foo: 'bar' }, status: 200
12
13
  # render plain: 'foo/bar', status: 304
13
14
  # redirect_to root_url, status: 301
14
15
  # head 200
16
+ # assert_response 200
17
+ # assert_redirected_to '/some/path', status: 301
15
18
  #
16
19
  # # good
17
20
  # render :foo, status: :ok
@@ -19,6 +22,8 @@ module RuboCop
19
22
  # render plain: 'foo/bar', status: :not_modified
20
23
  # redirect_to root_url, status: :moved_permanently
21
24
  # head :ok
25
+ # assert_response :ok
26
+ # assert_redirected_to '/some/path', status: :moved_permanently
22
27
  #
23
28
  # @example EnforcedStyle: numeric
24
29
  # # bad
@@ -27,6 +32,8 @@ module RuboCop
27
32
  # render plain: 'foo/bar', status: :not_modified
28
33
  # redirect_to root_url, status: :moved_permanently
29
34
  # head :ok
35
+ # assert_response :ok
36
+ # assert_redirected_to '/some/path', status: :moved_permanently
30
37
  #
31
38
  # # good
32
39
  # render :foo, status: 200
@@ -34,23 +41,27 @@ module RuboCop
34
41
  # render plain: 'foo/bar', status: 304
35
42
  # redirect_to root_url, status: 301
36
43
  # head 200
44
+ # assert_response 200
45
+ # assert_redirected_to '/some/path', status: 301
37
46
  #
38
47
  class HttpStatus < Base
39
48
  include ConfigurableEnforcedStyle
40
49
  extend AutoCorrector
41
50
 
42
- RESTRICT_ON_SEND = %i[render redirect_to head].freeze
51
+ RESTRICT_ON_SEND = %i[render redirect_to head assert_response assert_redirected_to].freeze
43
52
 
44
53
  def_node_matcher :http_status, <<~PATTERN
45
54
  {
46
55
  (send nil? {:render :redirect_to} _ $hash)
47
56
  (send nil? {:render :redirect_to} $hash)
48
- (send nil? :head ${int sym} ...)
57
+ (send nil? {:head :assert_response} ${int sym} ...)
58
+ (send nil? :assert_redirected_to _ $hash ...)
59
+ (send nil? :assert_redirected_to $hash ...)
49
60
  }
50
61
  PATTERN
51
62
 
52
63
  def_node_matcher :status_code, <<~PATTERN
53
- (hash <(pair (sym :status) ${int sym}) ...>)
64
+ (hash <(pair (sym :status) ${int sym str}) ...>)
54
65
  PATTERN
55
66
 
56
67
  def on_send(node)
@@ -108,7 +119,7 @@ module RuboCop
108
119
  private
109
120
 
110
121
  def symbol
111
- ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number)
122
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number.to_i)
112
123
  end
113
124
 
114
125
  def number
@@ -133,7 +144,7 @@ module RuboCop
133
144
  end
134
145
 
135
146
  def offensive?
136
- !node.int_type? && !permitted_symbol?
147
+ !node.int_type? && !permitted_symbol? && number
137
148
  end
138
149
 
139
150
  def message
@@ -5,7 +5,13 @@ module RuboCop
5
5
  module Rails
6
6
  # Checks for places where I18n "lazy" lookup can be used.
7
7
  #
8
- # @example
8
+ # This cop has two different enforcement modes. When the EnforcedStyle
9
+ # is `lazy` (the default), explicit lookups are added as offenses.
10
+ #
11
+ # When the EnforcedStyle is `explicit` then lazy lookups are added as
12
+ # offenses.
13
+ #
14
+ # @example EnforcedStyle: lazy (default)
9
15
  # # en.yml
10
16
  # # en:
11
17
  # # books:
@@ -28,11 +34,29 @@ module RuboCop
28
34
  # end
29
35
  # end
30
36
  #
37
+ # @example EnforcedStyle: explicit
38
+ # # bad
39
+ # class BooksController < ApplicationController
40
+ # def create
41
+ # # ...
42
+ # redirect_to books_url, notice: t('.success')
43
+ # end
44
+ # end
45
+ #
46
+ # # good
47
+ # class BooksController < ApplicationController
48
+ # def create
49
+ # # ...
50
+ # redirect_to books_url, notice: t('books.create.success')
51
+ # end
52
+ # end
53
+ #
31
54
  class I18nLazyLookup < Base
55
+ include ConfigurableEnforcedStyle
32
56
  include VisibilityHelp
33
57
  extend AutoCorrector
34
58
 
35
- MSG = 'Use "lazy" lookup for the text used in controllers.'
59
+ MSG = 'Use %<style>s lookup for the text used in controllers.'
36
60
 
37
61
  RESTRICT_ON_SEND = %i[translate t].freeze
38
62
 
@@ -42,23 +66,45 @@ module RuboCop
42
66
 
43
67
  def on_send(node)
44
68
  translate_call?(node) do |key_node|
45
- key = key_node.value
46
- return if key.to_s.start_with?('.')
69
+ case style
70
+ when :lazy
71
+ handle_lazy_style(node, key_node)
72
+ when :explicit
73
+ handle_explicit_style(node, key_node)
74
+ end
75
+ end
76
+ end
77
+
78
+ private
47
79
 
48
- controller, action = controller_and_action(node)
49
- return unless controller && action
80
+ def handle_lazy_style(node, key_node)
81
+ key = key_node.value
82
+ return if key.to_s.start_with?('.')
50
83
 
51
- scoped_key = get_scoped_key(key_node, controller, action)
52
- return unless key == scoped_key
84
+ controller, action = controller_and_action(node)
85
+ return unless controller && action
53
86
 
54
- add_offense(key_node) do |corrector|
55
- unscoped_key = key_node.value.to_s.split('.').last
56
- corrector.replace(key_node, "'.#{unscoped_key}'")
57
- end
87
+ scoped_key = get_scoped_key(key_node, controller, action)
88
+ return unless key == scoped_key
89
+
90
+ add_offense(key_node) do |corrector|
91
+ unscoped_key = key_node.value.to_s.split('.').last
92
+ corrector.replace(key_node, "'.#{unscoped_key}'")
58
93
  end
59
94
  end
60
95
 
61
- private
96
+ def handle_explicit_style(node, key_node)
97
+ key = key_node.value
98
+ return unless key.to_s.start_with?('.')
99
+
100
+ controller, action = controller_and_action(node)
101
+ return unless controller && action
102
+
103
+ scoped_key = get_scoped_key(key_node, controller, action)
104
+ add_offense(key_node) do |corrector|
105
+ corrector.replace(key_node, "'#{scoped_key}'")
106
+ end
107
+ end
62
108
 
63
109
  def controller_and_action(node)
64
110
  action_node = node.each_ancestor(:def).first
@@ -90,6 +136,10 @@ module RuboCop
90
136
 
91
137
  path.delete_suffix('Controller').underscore
92
138
  end
139
+
140
+ def message(_range)
141
+ format(MSG, style: style)
142
+ end
93
143
  end
94
144
  end
95
145
  end
@@ -80,7 +80,11 @@ module RuboCop
80
80
  PATTERN
81
81
 
82
82
  def_node_matcher :flash_assignment?, <<~PATTERN
83
- (send (send nil? :flash) :[]= _ $str)
83
+ (send
84
+ {
85
+ (send nil? :flash)
86
+ (send (send nil? :flash) :now)
87
+ } :[]= _ $str)
84
88
  PATTERN
85
89
 
86
90
  def_node_search :mail_subject, <<~PATTERN