rubocop-rails 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
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