rubocop-rails 2.4.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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +92 -0
  4. data/bin/setup +7 -0
  5. data/config/default.yml +510 -0
  6. data/lib/rubocop-rails.rb +12 -0
  7. data/lib/rubocop/cop/mixin/target_rails_version.rb +16 -0
  8. data/lib/rubocop/cop/rails/action_filter.rb +111 -0
  9. data/lib/rubocop/cop/rails/active_record_aliases.rb +48 -0
  10. data/lib/rubocop/cop/rails/active_record_override.rb +82 -0
  11. data/lib/rubocop/cop/rails/active_support_aliases.rb +69 -0
  12. data/lib/rubocop/cop/rails/application_controller.rb +36 -0
  13. data/lib/rubocop/cop/rails/application_job.rb +40 -0
  14. data/lib/rubocop/cop/rails/application_mailer.rb +40 -0
  15. data/lib/rubocop/cop/rails/application_record.rb +40 -0
  16. data/lib/rubocop/cop/rails/assert_not.rb +44 -0
  17. data/lib/rubocop/cop/rails/belongs_to.rb +102 -0
  18. data/lib/rubocop/cop/rails/blank.rb +164 -0
  19. data/lib/rubocop/cop/rails/bulk_change_table.rb +293 -0
  20. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +91 -0
  21. data/lib/rubocop/cop/rails/date.rb +161 -0
  22. data/lib/rubocop/cop/rails/delegate.rb +132 -0
  23. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +37 -0
  24. data/lib/rubocop/cop/rails/dynamic_find_by.rb +91 -0
  25. data/lib/rubocop/cop/rails/enum_hash.rb +75 -0
  26. data/lib/rubocop/cop/rails/enum_uniqueness.rb +65 -0
  27. data/lib/rubocop/cop/rails/environment_comparison.rb +68 -0
  28. data/lib/rubocop/cop/rails/exit.rb +67 -0
  29. data/lib/rubocop/cop/rails/file_path.rb +108 -0
  30. data/lib/rubocop/cop/rails/find_by.rb +55 -0
  31. data/lib/rubocop/cop/rails/find_each.rb +51 -0
  32. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +25 -0
  33. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +106 -0
  34. data/lib/rubocop/cop/rails/helper_instance_variable.rb +39 -0
  35. data/lib/rubocop/cop/rails/http_positional_arguments.rb +117 -0
  36. data/lib/rubocop/cop/rails/http_status.rb +160 -0
  37. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +94 -0
  38. data/lib/rubocop/cop/rails/inverse_of.rb +246 -0
  39. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +175 -0
  40. data/lib/rubocop/cop/rails/link_to_blank.rb +98 -0
  41. data/lib/rubocop/cop/rails/not_null_column.rb +67 -0
  42. data/lib/rubocop/cop/rails/output.rb +49 -0
  43. data/lib/rubocop/cop/rails/output_safety.rb +99 -0
  44. data/lib/rubocop/cop/rails/pluralization_grammar.rb +107 -0
  45. data/lib/rubocop/cop/rails/presence.rb +148 -0
  46. data/lib/rubocop/cop/rails/present.rb +153 -0
  47. data/lib/rubocop/cop/rails/rake_environment.rb +91 -0
  48. data/lib/rubocop/cop/rails/read_write_attribute.rb +74 -0
  49. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +111 -0
  50. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +136 -0
  51. data/lib/rubocop/cop/rails/reflection_class_name.rb +37 -0
  52. data/lib/rubocop/cop/rails/refute_methods.rb +76 -0
  53. data/lib/rubocop/cop/rails/relative_date_constant.rb +102 -0
  54. data/lib/rubocop/cop/rails/request_referer.rb +56 -0
  55. data/lib/rubocop/cop/rails/reversible_migration.rb +284 -0
  56. data/lib/rubocop/cop/rails/safe_navigation.rb +85 -0
  57. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +48 -0
  58. data/lib/rubocop/cop/rails/save_bang.rb +331 -0
  59. data/lib/rubocop/cop/rails/scope_args.rb +29 -0
  60. data/lib/rubocop/cop/rails/skips_model_validations.rb +87 -0
  61. data/lib/rubocop/cop/rails/time_zone.rb +249 -0
  62. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +105 -0
  63. data/lib/rubocop/cop/rails/unknown_env.rb +84 -0
  64. data/lib/rubocop/cop/rails/validation.rb +147 -0
  65. data/lib/rubocop/cop/rails_cops.rb +61 -0
  66. data/lib/rubocop/rails.rb +12 -0
  67. data/lib/rubocop/rails/inject.rb +18 -0
  68. data/lib/rubocop/rails/version.rb +10 -0
  69. metadata +148 -0
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop converts usages of `try!` to `&.`. It can also be configured
7
+ # to convert `try`. It will convert code to use safe navigation.
8
+ #
9
+ # @example ConvertTry: false (default)
10
+ # # bad
11
+ # foo.try!(:bar)
12
+ # foo.try!(:bar, baz)
13
+ # foo.try!(:bar) { |e| e.baz }
14
+ #
15
+ # foo.try!(:[], 0)
16
+ #
17
+ # # good
18
+ # foo.try(:bar)
19
+ # foo.try(:bar, baz)
20
+ # foo.try(:bar) { |e| e.baz }
21
+ #
22
+ # foo&.bar
23
+ # foo&.bar(baz)
24
+ # foo&.bar { |e| e.baz }
25
+ #
26
+ # @example ConvertTry: true
27
+ # # bad
28
+ # foo.try!(:bar)
29
+ # foo.try!(:bar, baz)
30
+ # foo.try!(:bar) { |e| e.baz }
31
+ # foo.try(:bar)
32
+ # foo.try(:bar, baz)
33
+ # foo.try(:bar) { |e| e.baz }
34
+ #
35
+ # # good
36
+ # foo&.bar
37
+ # foo&.bar(baz)
38
+ # foo&.bar { |e| e.baz }
39
+ class SafeNavigation < Cop
40
+ include RangeHelp
41
+
42
+ MSG = 'Use safe navigation (`&.`) instead of `%<try>s`.'
43
+
44
+ def_node_matcher :try_call, <<~PATTERN
45
+ (send !nil? ${:try :try!} $_ ...)
46
+ PATTERN
47
+
48
+ def on_send(node)
49
+ try_call(node) do |try_method, dispatch|
50
+ return if try_method == :try && !cop_config['ConvertTry']
51
+ return unless dispatch.sym_type? && dispatch.value =~ /\w+[=!?]?/
52
+
53
+ add_offense(node, message: format(MSG, try: try_method))
54
+ end
55
+ end
56
+
57
+ def autocorrect(node)
58
+ method_node, *params = *node.arguments
59
+ method = method_node.source[1..-1]
60
+
61
+ range = range_between(node.loc.dot.begin_pos,
62
+ node.loc.expression.end_pos)
63
+
64
+ lambda do |corrector|
65
+ corrector.replace(range, replacement(method, params))
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def replacement(method, params)
72
+ new_params = params.map(&:source).join(', ')
73
+
74
+ if method.end_with?('=')
75
+ "&.#{method[0...-1]} = #{new_params}"
76
+ elsif params.empty?
77
+ "&.#{method}"
78
+ else
79
+ "&.#{method}(#{new_params})"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks to make sure safe navigation isn't used with `blank?` in
7
+ # a conditional.
8
+ #
9
+ # While the safe navigation operator is generally a good idea, when
10
+ # checking `foo&.blank?` in a conditional, `foo` being `nil` will actually
11
+ # do the opposite of what the author intends.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # do_something if foo&.blank?
16
+ # do_something unless foo&.blank?
17
+ #
18
+ # # good
19
+ # do_something if foo.blank?
20
+ # do_something unless foo.blank?
21
+ #
22
+ class SafeNavigationWithBlank < Cop
23
+ MSG =
24
+ 'Avoid calling `blank?` with the safe navigation operator ' \
25
+ 'in conditionals.'
26
+
27
+ def_node_matcher :safe_navigation_blank_in_conditional?, <<~PATTERN
28
+ (if $(csend ... :blank?) ...)
29
+ PATTERN
30
+
31
+ def on_if(node)
32
+ return unless safe_navigation_blank_in_conditional?(node)
33
+
34
+ add_offense(node)
35
+ end
36
+
37
+ def autocorrect(node)
38
+ lambda do |corrector|
39
+ corrector.replace(
40
+ safe_navigation_blank_in_conditional?(node).location.dot,
41
+ '.'
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,331 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop identifies possible cases where Active Record save! or related
7
+ # should be used instead of save because the model might have failed to
8
+ # save and an exception is better than unhandled failure.
9
+ #
10
+ # This will allow:
11
+ #
12
+ # - update or save calls, assigned to a variable,
13
+ # or used as a condition in an if/unless/case statement.
14
+ # - create calls, assigned to a variable that then has a
15
+ # call to `persisted?`.
16
+ # - calls if the result is explicitly returned from methods and blocks,
17
+ # or provided as arguments.
18
+ # - calls whose signature doesn't look like an ActiveRecord
19
+ # persistence method.
20
+ #
21
+ # By default it will also allow implicit returns from methods and blocks.
22
+ # that behavior can be turned off with `AllowImplicitReturn: false`.
23
+ #
24
+ # You can permit receivers that are giving false positives with
25
+ # `AllowedReceivers: []`
26
+ #
27
+ # @example
28
+ #
29
+ # # bad
30
+ # user.save
31
+ # user.update(name: 'Joe')
32
+ # user.find_or_create_by(name: 'Joe')
33
+ # user.destroy
34
+ #
35
+ # # good
36
+ # unless user.save
37
+ # # ...
38
+ # end
39
+ # user.save!
40
+ # user.update!(name: 'Joe')
41
+ # user.find_or_create_by!(name: 'Joe')
42
+ # user.destroy!
43
+ #
44
+ # user = User.find_or_create_by(name: 'Joe')
45
+ # unless user.persisted?
46
+ # # ...
47
+ # end
48
+ #
49
+ # def save_user
50
+ # return user.save
51
+ # end
52
+ #
53
+ # @example AllowImplicitReturn: true (default)
54
+ #
55
+ # # good
56
+ # users.each { |u| u.save }
57
+ #
58
+ # def save_user
59
+ # user.save
60
+ # end
61
+ #
62
+ # @example AllowImplicitReturn: false
63
+ #
64
+ # # bad
65
+ # users.each { |u| u.save }
66
+ # def save_user
67
+ # user.save
68
+ # end
69
+ #
70
+ # # good
71
+ # users.each { |u| u.save! }
72
+ #
73
+ # def save_user
74
+ # user.save!
75
+ # end
76
+ #
77
+ # def save_user
78
+ # return user.save
79
+ # end
80
+ #
81
+ # @example AllowedReceivers: ['merchant.customers', 'Service::Mailer']
82
+ #
83
+ # # bad
84
+ # merchant.create
85
+ # customers.builder.save
86
+ # Mailer.create
87
+ #
88
+ # module Service::Mailer
89
+ # self.create
90
+ # end
91
+ #
92
+ # # good
93
+ # merchant.customers.create
94
+ # MerchantService.merchant.customers.destroy
95
+ # Service::Mailer.update(message: 'Message')
96
+ # ::Service::Mailer.update
97
+ # Services::Service::Mailer.update(message: 'Message')
98
+ # Service::Mailer::update
99
+ #
100
+ class SaveBang < Cop
101
+ include NegativeConditional
102
+
103
+ MSG = 'Use `%<prefer>s` instead of `%<current>s` if the return ' \
104
+ 'value is not checked.'
105
+ CREATE_MSG = (MSG +
106
+ ' Or check `persisted?` on model returned from ' \
107
+ '`%<current>s`.').freeze
108
+ CREATE_CONDITIONAL_MSG = '`%<current>s` returns a model which is ' \
109
+ 'always truthy.'
110
+
111
+ CREATE_PERSIST_METHODS = %i[create create_or_find_by
112
+ first_or_create find_or_create_by].freeze
113
+ MODIFY_PERSIST_METHODS = %i[save
114
+ update update_attributes destroy].freeze
115
+ PERSIST_METHODS = (CREATE_PERSIST_METHODS +
116
+ MODIFY_PERSIST_METHODS).freeze
117
+
118
+ def join_force?(force_class)
119
+ force_class == VariableForce
120
+ end
121
+
122
+ def after_leaving_scope(scope, _variable_table)
123
+ scope.variables.each_value do |variable|
124
+ variable.assignments.each do |assignment|
125
+ check_assignment(assignment)
126
+ end
127
+ end
128
+ end
129
+
130
+ def check_assignment(assignment)
131
+ node = right_assignment_node(assignment)
132
+
133
+ return unless node&.send_type?
134
+ return unless persist_method?(node, CREATE_PERSIST_METHODS)
135
+ return if persisted_referenced?(assignment)
136
+
137
+ add_offense_for_node(node, CREATE_MSG)
138
+ end
139
+
140
+ def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity
141
+ return unless persist_method?(node)
142
+ return if return_value_assigned?(node)
143
+ return if implicit_return?(node)
144
+ return if check_used_in_condition_or_compound_boolean(node)
145
+ return if argument?(node)
146
+ return if explicit_return?(node)
147
+
148
+ add_offense_for_node(node)
149
+ end
150
+ alias on_csend on_send
151
+
152
+ def autocorrect(node)
153
+ save_loc = node.loc.selector
154
+ new_method = "#{node.method_name}!"
155
+
156
+ ->(corrector) { corrector.replace(save_loc, new_method) }
157
+ end
158
+
159
+ private
160
+
161
+ def add_offense_for_node(node, msg = MSG)
162
+ name = node.method_name
163
+ full_message = format(msg, prefer: "#{name}!", current: name.to_s)
164
+
165
+ add_offense(node, location: :selector, message: full_message)
166
+ end
167
+
168
+ def right_assignment_node(assignment)
169
+ node = assignment.node.child_nodes.first
170
+
171
+ return node unless node&.block_type?
172
+
173
+ node.send_node
174
+ end
175
+
176
+ def persisted_referenced?(assignment)
177
+ return unless assignment.referenced?
178
+
179
+ assignment.variable.references.any? do |reference|
180
+ call_to_persisted?(reference.node.parent)
181
+ end
182
+ end
183
+
184
+ def call_to_persisted?(node)
185
+ node.send_type? && node.method?(:persisted?)
186
+ end
187
+
188
+ def assignable_node(node)
189
+ assignable = node.block_node || node
190
+ while node
191
+ node = hash_parent(node) || array_parent(node)
192
+ assignable = node if node
193
+ end
194
+ assignable
195
+ end
196
+
197
+ def hash_parent(node)
198
+ pair = node.parent
199
+ return unless pair&.pair_type?
200
+
201
+ hash = pair.parent
202
+ return unless hash&.hash_type?
203
+
204
+ hash
205
+ end
206
+
207
+ def array_parent(node)
208
+ array = node.parent
209
+ return unless array&.array_type?
210
+
211
+ array
212
+ end
213
+
214
+ def check_used_in_condition_or_compound_boolean(node)
215
+ return false unless in_condition_or_compound_boolean?(node)
216
+
217
+ unless MODIFY_PERSIST_METHODS.include?(node.method_name)
218
+ add_offense_for_node(node, CREATE_CONDITIONAL_MSG)
219
+ end
220
+
221
+ true
222
+ end
223
+
224
+ def in_condition_or_compound_boolean?(node)
225
+ node = node.block_node || node
226
+ parent = node.parent
227
+ return false unless parent
228
+
229
+ operator_or_single_negative?(parent) ||
230
+ (conditional?(parent) && node == parent.condition)
231
+ end
232
+
233
+ def operator_or_single_negative?(node)
234
+ node.or_type? || node.and_type? || single_negative?(node)
235
+ end
236
+
237
+ def conditional?(parent)
238
+ parent.if_type? || parent.case_type?
239
+ end
240
+
241
+ def allowed_receiver?(node)
242
+ return false unless node.receiver
243
+ return false unless cop_config['AllowedReceivers']
244
+
245
+ cop_config['AllowedReceivers'].any? do |allowed_receiver|
246
+ receiver_chain_matches?(node, allowed_receiver)
247
+ end
248
+ end
249
+
250
+ def receiver_chain_matches?(node, allowed_receiver)
251
+ allowed_receiver.split('.').reverse.all? do |receiver_part|
252
+ node = node.receiver
253
+ return false unless node
254
+
255
+ if node.variable?
256
+ node.node_parts.first == receiver_part.to_sym
257
+ elsif node.send_type?
258
+ node.method?(receiver_part.to_sym)
259
+ elsif node.const_type?
260
+ const_matches?(node.const_name, receiver_part)
261
+ end
262
+ end
263
+ end
264
+
265
+ # Const == Const
266
+ # ::Const == ::Const
267
+ # ::Const == Const
268
+ # Const == ::Const
269
+ # NameSpace::Const == Const
270
+ # NameSpace::Const == NameSpace::Const
271
+ # NameSpace::Const != ::Const
272
+ # Const != NameSpace::Const
273
+ def const_matches?(const, allowed_const)
274
+ parts = allowed_const.split('::').reverse.zip(
275
+ const.split('::').reverse
276
+ )
277
+ parts.all? do |(allowed_part, const_part)|
278
+ allowed_part == const_part.to_s
279
+ end
280
+ end
281
+
282
+ def implicit_return?(node)
283
+ return false unless cop_config['AllowImplicitReturn']
284
+
285
+ node = assignable_node(node)
286
+ method, sibling_index = find_method_with_sibling_index(node.parent)
287
+ return unless method && (method.def_type? || method.block_type?)
288
+
289
+ method.children.size == node.sibling_index + sibling_index
290
+ end
291
+
292
+ def find_method_with_sibling_index(node, sibling_index = 1)
293
+ return node, sibling_index unless node&.or_type?
294
+
295
+ sibling_index += 1
296
+
297
+ find_method_with_sibling_index(node.parent, sibling_index)
298
+ end
299
+
300
+ def argument?(node)
301
+ assignable_node(node).argument?
302
+ end
303
+
304
+ def explicit_return?(node)
305
+ ret = assignable_node(node).parent
306
+ ret && (ret.return_type? || ret.next_type?)
307
+ end
308
+
309
+ def return_value_assigned?(node)
310
+ assignment = assignable_node(node).parent
311
+ assignment&.lvasgn_type?
312
+ end
313
+
314
+ def persist_method?(node, methods = PERSIST_METHODS)
315
+ methods.include?(node.method_name) &&
316
+ expected_signature?(node) &&
317
+ !allowed_receiver?(node)
318
+ end
319
+
320
+ # Check argument signature as no arguments or one hash
321
+ def expected_signature?(node)
322
+ !node.arguments? ||
323
+ (node.arguments.one? &&
324
+ node.method_name != :destroy &&
325
+ (node.first_argument.hash_type? ||
326
+ !node.first_argument.literal?))
327
+ end
328
+ end
329
+ end
330
+ end
331
+ end