rubocop-rails 2.0.0

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +73 -0
  4. data/bin/setup +7 -0
  5. data/config/default.yml +466 -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 +117 -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_job.rb +40 -0
  13. data/lib/rubocop/cop/rails/application_record.rb +40 -0
  14. data/lib/rubocop/cop/rails/assert_not.rb +44 -0
  15. data/lib/rubocop/cop/rails/belongs_to.rb +102 -0
  16. data/lib/rubocop/cop/rails/blank.rb +164 -0
  17. data/lib/rubocop/cop/rails/bulk_change_table.rb +289 -0
  18. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +91 -0
  19. data/lib/rubocop/cop/rails/date.rb +161 -0
  20. data/lib/rubocop/cop/rails/delegate.rb +132 -0
  21. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +37 -0
  22. data/lib/rubocop/cop/rails/dynamic_find_by.rb +91 -0
  23. data/lib/rubocop/cop/rails/enum_uniqueness.rb +45 -0
  24. data/lib/rubocop/cop/rails/environment_comparison.rb +68 -0
  25. data/lib/rubocop/cop/rails/exit.rb +67 -0
  26. data/lib/rubocop/cop/rails/file_path.rb +108 -0
  27. data/lib/rubocop/cop/rails/find_by.rb +55 -0
  28. data/lib/rubocop/cop/rails/find_each.rb +51 -0
  29. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +25 -0
  30. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +106 -0
  31. data/lib/rubocop/cop/rails/helper_instance_variable.rb +39 -0
  32. data/lib/rubocop/cop/rails/http_positional_arguments.rb +117 -0
  33. data/lib/rubocop/cop/rails/http_status.rb +160 -0
  34. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +94 -0
  35. data/lib/rubocop/cop/rails/inverse_of.rb +246 -0
  36. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +175 -0
  37. data/lib/rubocop/cop/rails/link_to_blank.rb +98 -0
  38. data/lib/rubocop/cop/rails/not_null_column.rb +67 -0
  39. data/lib/rubocop/cop/rails/output.rb +49 -0
  40. data/lib/rubocop/cop/rails/output_safety.rb +99 -0
  41. data/lib/rubocop/cop/rails/pluralization_grammar.rb +107 -0
  42. data/lib/rubocop/cop/rails/presence.rb +124 -0
  43. data/lib/rubocop/cop/rails/present.rb +153 -0
  44. data/lib/rubocop/cop/rails/read_write_attribute.rb +74 -0
  45. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +111 -0
  46. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +136 -0
  47. data/lib/rubocop/cop/rails/reflection_class_name.rb +37 -0
  48. data/lib/rubocop/cop/rails/refute_methods.rb +76 -0
  49. data/lib/rubocop/cop/rails/relative_date_constant.rb +93 -0
  50. data/lib/rubocop/cop/rails/request_referer.rb +56 -0
  51. data/lib/rubocop/cop/rails/reversible_migration.rb +286 -0
  52. data/lib/rubocop/cop/rails/safe_navigation.rb +87 -0
  53. data/lib/rubocop/cop/rails/save_bang.rb +316 -0
  54. data/lib/rubocop/cop/rails/scope_args.rb +29 -0
  55. data/lib/rubocop/cop/rails/skips_model_validations.rb +87 -0
  56. data/lib/rubocop/cop/rails/time_zone.rb +238 -0
  57. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +105 -0
  58. data/lib/rubocop/cop/rails/unknown_env.rb +63 -0
  59. data/lib/rubocop/cop/rails/validation.rb +109 -0
  60. data/lib/rubocop/cop/rails_cops.rb +64 -0
  61. data/lib/rubocop/rails.rb +12 -0
  62. data/lib/rubocop/rails/inject.rb +18 -0
  63. data/lib/rubocop/rails/version.rb +10 -0
  64. metadata +143 -0
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for consistent uses of `request.referer` or
7
+ # `request.referrer`, depending on the cop's configuration.
8
+ #
9
+ # @example EnforcedStyle: referer (default)
10
+ # # bad
11
+ # request.referrer
12
+ #
13
+ # # good
14
+ # request.referer
15
+ #
16
+ # @example EnforcedStyle: referrer
17
+ # # bad
18
+ # request.referer
19
+ #
20
+ # # good
21
+ # request.referrer
22
+ class RequestReferer < Cop
23
+ include ConfigurableEnforcedStyle
24
+
25
+ MSG = 'Use `request.%<prefer>s` instead of ' \
26
+ '`request.%<current>s`.'
27
+
28
+ def_node_matcher :referer?, <<-PATTERN
29
+ (send (send nil? :request) {:referer :referrer})
30
+ PATTERN
31
+
32
+ def on_send(node)
33
+ referer?(node) do
34
+ return unless node.method?(wrong_method_name)
35
+
36
+ add_offense(node.source_range, location: node.source_range)
37
+ end
38
+ end
39
+
40
+ def autocorrect(node)
41
+ ->(corrector) { corrector.replace(node, "request.#{style}") }
42
+ end
43
+
44
+ private
45
+
46
+ def message(_node)
47
+ format(MSG, prefer: style, current: wrong_method_name)
48
+ end
49
+
50
+ def wrong_method_name
51
+ style == :referer ? :referrer : :referer
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,286 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks whether the change method of the migration file is
7
+ # reversible.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # def change
12
+ # change_table :users do |t|
13
+ # t.remove :name
14
+ # end
15
+ # end
16
+ #
17
+ # # good
18
+ # def change
19
+ # create_table :users do |t|
20
+ # t.string :name
21
+ # end
22
+ # end
23
+ #
24
+ # # good
25
+ # def change
26
+ # reversible do |dir|
27
+ # change_table :users do |t|
28
+ # dir.up do
29
+ # t.column :name, :string
30
+ # end
31
+ #
32
+ # dir.down do
33
+ # t.remove :name
34
+ # end
35
+ # end
36
+ # end
37
+ # end
38
+ #
39
+ # @example
40
+ # # drop_table
41
+ #
42
+ # # bad
43
+ # def change
44
+ # drop_table :users
45
+ # end
46
+ #
47
+ # # good
48
+ # def change
49
+ # drop_table :users do |t|
50
+ # t.string :name
51
+ # end
52
+ # end
53
+ #
54
+ # @example
55
+ # # change_column_default
56
+ #
57
+ # # bad
58
+ # def change
59
+ # change_column_default(:suppliers, :qualification, 'new')
60
+ # end
61
+ #
62
+ # # good
63
+ # def change
64
+ # change_column_default(:posts, :state, from: nil, to: "draft")
65
+ # end
66
+ #
67
+ # @example
68
+ # # remove_column
69
+ #
70
+ # # bad
71
+ # def change
72
+ # remove_column(:suppliers, :qualification)
73
+ # end
74
+ #
75
+ # # good
76
+ # def change
77
+ # remove_column(:suppliers, :qualification, :string)
78
+ # end
79
+ #
80
+ # @example
81
+ # # remove_foreign_key
82
+ #
83
+ # # bad
84
+ # def change
85
+ # remove_foreign_key :accounts, column: :owner_id
86
+ # end
87
+ #
88
+ # # good
89
+ # def change
90
+ # remove_foreign_key :accounts, :branches
91
+ # end
92
+ #
93
+ # @example
94
+ # # change_table
95
+ #
96
+ # # bad
97
+ # def change
98
+ # change_table :users do |t|
99
+ # t.remove :name
100
+ # t.change_default :authorized, 1
101
+ # t.change :price, :string
102
+ # end
103
+ # end
104
+ #
105
+ # # good
106
+ # def change
107
+ # change_table :users do |t|
108
+ # t.string :name
109
+ # end
110
+ # end
111
+ #
112
+ # # good
113
+ # def change
114
+ # reversible do |dir|
115
+ # change_table :users do |t|
116
+ # dir.up do
117
+ # t.change :price, :string
118
+ # end
119
+ #
120
+ # dir.down do
121
+ # t.change :price, :integer
122
+ # end
123
+ # end
124
+ # end
125
+ # end
126
+ #
127
+ # @see https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html
128
+ class ReversibleMigration < Cop
129
+ MSG = '%<action>s is not reversible.'
130
+ IRREVERSIBLE_CHANGE_TABLE_CALLS = %i[
131
+ change change_default remove
132
+ ].freeze
133
+
134
+ def_node_matcher :irreversible_schema_statement_call, <<-PATTERN
135
+ (send nil? ${:change_table_comment :execute :remove_belongs_to} ...)
136
+ PATTERN
137
+
138
+ def_node_matcher :drop_table_call, <<-PATTERN
139
+ (send nil? :drop_table ...)
140
+ PATTERN
141
+
142
+ def_node_matcher :change_column_default_call, <<-PATTERN
143
+ (send nil? :change_column_default {[(sym _) (sym _)] (splat _)} $...)
144
+ PATTERN
145
+
146
+ def_node_matcher :remove_column_call, <<-PATTERN
147
+ (send nil? :remove_column $...)
148
+ PATTERN
149
+
150
+ def_node_matcher :remove_foreign_key_call, <<-PATTERN
151
+ (send nil? :remove_foreign_key _ $_)
152
+ PATTERN
153
+
154
+ def_node_matcher :change_table_call, <<-PATTERN
155
+ (send nil? :change_table $_ ...)
156
+ PATTERN
157
+
158
+ def on_send(node)
159
+ return unless within_change_method?(node)
160
+ return if within_reversible_or_up_only_block?(node)
161
+
162
+ check_irreversible_schema_statement_node(node)
163
+ check_drop_table_node(node)
164
+ check_change_column_default_node(node)
165
+ check_remove_column_node(node)
166
+ check_remove_foreign_key_node(node)
167
+ end
168
+
169
+ def on_block(node)
170
+ return unless within_change_method?(node)
171
+ return if within_reversible_or_up_only_block?(node)
172
+ return if node.body.nil?
173
+
174
+ check_change_table_node(node.send_node, node.body)
175
+ end
176
+
177
+ private
178
+
179
+ def check_irreversible_schema_statement_node(node)
180
+ irreversible_schema_statement_call(node) do |method_name|
181
+ add_offense(node, message: format(MSG, action: method_name))
182
+ end
183
+ end
184
+
185
+ def check_drop_table_node(node)
186
+ drop_table_call(node) do
187
+ unless node.parent.block_type?
188
+ add_offense(
189
+ node,
190
+ message: format(MSG, action: 'drop_table(without block)')
191
+ )
192
+ end
193
+ end
194
+ end
195
+
196
+ def check_change_column_default_node(node)
197
+ change_column_default_call(node) do |args|
198
+ unless all_hash_key?(args.last, :from, :to)
199
+ add_offense(
200
+ node,
201
+ message: format(
202
+ MSG, action: 'change_column_default(without :from and :to)'
203
+ )
204
+ )
205
+ end
206
+ end
207
+ end
208
+
209
+ def check_remove_column_node(node)
210
+ remove_column_call(node) do |args|
211
+ if args.to_a.size < 3
212
+ add_offense(
213
+ node,
214
+ message: format(MSG, action: 'remove_column(without type)')
215
+ )
216
+ end
217
+ end
218
+ end
219
+
220
+ def check_remove_foreign_key_node(node)
221
+ remove_foreign_key_call(node) do |arg|
222
+ if arg.hash_type?
223
+ add_offense(
224
+ node,
225
+ message: format(MSG,
226
+ action: 'remove_foreign_key(without table)')
227
+ )
228
+ end
229
+ end
230
+ end
231
+
232
+ def check_change_table_node(node, block)
233
+ change_table_call(node) do |arg|
234
+ if target_rails_version < 4.0
235
+ add_offense(
236
+ node,
237
+ message: format(MSG, action: 'change_table')
238
+ )
239
+ elsif block.send_type?
240
+ check_change_table_offense(arg, block)
241
+ else
242
+ block.each_child_node(:send) do |child_node|
243
+ check_change_table_offense(arg, child_node)
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ def check_change_table_offense(receiver, node)
250
+ method_name = node.method_name
251
+ return if receiver != node.receiver &&
252
+ !IRREVERSIBLE_CHANGE_TABLE_CALLS.include?(method_name)
253
+
254
+ add_offense(
255
+ node,
256
+ message: format(MSG, action: "change_table(with #{method_name})")
257
+ )
258
+ end
259
+
260
+ def within_change_method?(node)
261
+ node.each_ancestor(:def).any? do |ancestor|
262
+ ancestor.method?(:change)
263
+ end
264
+ end
265
+
266
+ def within_reversible_or_up_only_block?(node)
267
+ node.each_ancestor(:block).any? do |ancestor|
268
+ ancestor.block_type? &&
269
+ ancestor.send_node.method?(:reversible) ||
270
+ ancestor.send_node.method?(:up_only)
271
+ end
272
+ end
273
+
274
+ def all_hash_key?(args, *keys)
275
+ return false unless args&.hash_type?
276
+
277
+ hash_keys = args.keys.map do |key|
278
+ key.children.first.to_sym
279
+ end
280
+
281
+ hash_keys & keys == keys
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,87 @@
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
10
+ # # ConvertTry: false
11
+ # # bad
12
+ # foo.try!(:bar)
13
+ # foo.try!(:bar, baz)
14
+ # foo.try!(:bar) { |e| e.baz }
15
+ #
16
+ # foo.try!(:[], 0)
17
+ #
18
+ # # good
19
+ # foo.try(:bar)
20
+ # foo.try(:bar, baz)
21
+ # foo.try(:bar) { |e| e.baz }
22
+ #
23
+ # foo&.bar
24
+ # foo&.bar(baz)
25
+ # foo&.bar { |e| e.baz }
26
+ #
27
+ #
28
+ # # ConvertTry: true
29
+ # # bad
30
+ # foo.try!(:bar)
31
+ # foo.try!(:bar, baz)
32
+ # foo.try!(:bar) { |e| e.baz }
33
+ # foo.try(:bar)
34
+ # foo.try(:bar, baz)
35
+ # foo.try(:bar) { |e| e.baz }
36
+ #
37
+ # # good
38
+ # foo&.bar
39
+ # foo&.bar(baz)
40
+ # foo&.bar { |e| e.baz }
41
+ class SafeNavigation < Cop
42
+ include RangeHelp
43
+
44
+ MSG = 'Use safe navigation (`&.`) instead of `%<try>s`.'
45
+
46
+ def_node_matcher :try_call, <<-PATTERN
47
+ (send !nil? ${:try :try!} $_ ...)
48
+ PATTERN
49
+
50
+ def on_send(node)
51
+ try_call(node) do |try_method, dispatch|
52
+ return if try_method == :try && !cop_config['ConvertTry']
53
+ return unless dispatch.sym_type? && dispatch.value =~ /\w+[=!?]?/
54
+
55
+ add_offense(node, message: format(MSG, try: try_method))
56
+ end
57
+ end
58
+
59
+ def autocorrect(node)
60
+ method_node, *params = *node.arguments
61
+ method = method_node.source[1..-1]
62
+
63
+ range = range_between(node.loc.dot.begin_pos,
64
+ node.loc.expression.end_pos)
65
+
66
+ lambda do |corrector|
67
+ corrector.replace(range, replacement(method, params))
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def replacement(method, params)
74
+ new_params = params.map(&:source).join(', ')
75
+
76
+ if method.end_with?('=')
77
+ "&.#{method[0...-1]} = #{new_params}"
78
+ elsif params.empty?
79
+ "&.#{method}"
80
+ else
81
+ "&.#{method}(#{new_params})"
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,316 @@
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
+ # - update or save calls, assigned to a variable,
12
+ # or used as a condition in an if/unless/case statement.
13
+ # - create calls, assigned to a variable that then has a
14
+ # call to `persisted?`.
15
+ # - calls if the result is explicitly returned from methods and blocks,
16
+ # or provided as arguments.
17
+ # - calls whose signature doesn't look like an ActiveRecord
18
+ # persistence method.
19
+ #
20
+ # By default it will also allow implicit returns from methods and blocks.
21
+ # that behavior can be turned off with `AllowImplicitReturn: false`.
22
+ #
23
+ # You can permit receivers that are giving false positives with
24
+ # `AllowedReceivers: []`
25
+ #
26
+ # @example
27
+ #
28
+ # # bad
29
+ # user.save
30
+ # user.update(name: 'Joe')
31
+ # user.find_or_create_by(name: 'Joe')
32
+ # user.destroy
33
+ #
34
+ # # good
35
+ # unless user.save
36
+ # # ...
37
+ # end
38
+ # user.save!
39
+ # user.update!(name: 'Joe')
40
+ # user.find_or_create_by!(name: 'Joe')
41
+ # user.destroy!
42
+ #
43
+ # user = User.find_or_create_by(name: 'Joe')
44
+ # unless user.persisted?
45
+ # # ...
46
+ # end
47
+ #
48
+ # def save_user
49
+ # return user.save
50
+ # end
51
+ #
52
+ # @example AllowImplicitReturn: true (default)
53
+ #
54
+ # # good
55
+ # users.each { |u| u.save }
56
+ #
57
+ # def save_user
58
+ # user.save
59
+ # end
60
+ #
61
+ # @example AllowImplicitReturn: false
62
+ #
63
+ # # bad
64
+ # users.each { |u| u.save }
65
+ # def save_user
66
+ # user.save
67
+ # end
68
+ #
69
+ # # good
70
+ # users.each { |u| u.save! }
71
+ #
72
+ # def save_user
73
+ # user.save!
74
+ # end
75
+ #
76
+ # def save_user
77
+ # return user.save
78
+ # end
79
+ #
80
+ # @example AllowedReceivers: ['merchant.customers', 'Service::Mailer']
81
+ #
82
+ # # bad
83
+ # merchant.create
84
+ # customers.builder.save
85
+ # Mailer.create
86
+ #
87
+ # module Service::Mailer
88
+ # self.create
89
+ # end
90
+ #
91
+ # # good
92
+ # merchant.customers.create
93
+ # MerchantService.merchant.customers.destroy
94
+ # Service::Mailer.update(message: 'Message')
95
+ # ::Service::Mailer.update
96
+ # Services::Service::Mailer.update(message: 'Message')
97
+ # Service::Mailer::update
98
+ #
99
+ class SaveBang < Cop
100
+ include NegativeConditional
101
+
102
+ MSG = 'Use `%<prefer>s` instead of `%<current>s` if the return ' \
103
+ 'value is not checked.'
104
+ CREATE_MSG = (MSG +
105
+ ' Or check `persisted?` on model returned from ' \
106
+ '`%<current>s`.').freeze
107
+ CREATE_CONDITIONAL_MSG = '`%<current>s` returns a model which is ' \
108
+ 'always truthy.'
109
+
110
+ CREATE_PERSIST_METHODS = %i[create
111
+ first_or_create find_or_create_by].freeze
112
+ MODIFY_PERSIST_METHODS = %i[save
113
+ update update_attributes destroy].freeze
114
+ PERSIST_METHODS = (CREATE_PERSIST_METHODS +
115
+ MODIFY_PERSIST_METHODS).freeze
116
+
117
+ def join_force?(force_class)
118
+ force_class == VariableForce
119
+ end
120
+
121
+ def after_leaving_scope(scope, _variable_table)
122
+ scope.variables.each_value do |variable|
123
+ variable.assignments.each do |assignment|
124
+ check_assignment(assignment)
125
+ end
126
+ end
127
+ end
128
+
129
+ def check_assignment(assignment)
130
+ node = right_assignment_node(assignment)
131
+
132
+ return unless node&.send_type?
133
+ return unless persist_method?(node, CREATE_PERSIST_METHODS)
134
+ return if persisted_referenced?(assignment)
135
+
136
+ add_offense_for_node(node, CREATE_MSG)
137
+ end
138
+
139
+ def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity
140
+ return unless persist_method?(node)
141
+ return if return_value_assigned?(node)
142
+ return if check_used_in_conditional(node)
143
+ return if argument?(node)
144
+ return if implicit_return?(node)
145
+ return if explicit_return?(node)
146
+
147
+ add_offense_for_node(node)
148
+ end
149
+ alias on_csend on_send
150
+
151
+ def autocorrect(node)
152
+ save_loc = node.loc.selector
153
+ new_method = "#{node.method_name}!"
154
+
155
+ ->(corrector) { corrector.replace(save_loc, new_method) }
156
+ end
157
+
158
+ private
159
+
160
+ def add_offense_for_node(node, msg = MSG)
161
+ name = node.method_name
162
+ full_message = format(msg, prefer: "#{name}!", current: name.to_s)
163
+
164
+ add_offense(node, location: :selector, message: full_message)
165
+ end
166
+
167
+ def right_assignment_node(assignment)
168
+ node = assignment.node.child_nodes.first
169
+
170
+ return node unless node&.block_type?
171
+
172
+ node.send_node
173
+ end
174
+
175
+ def persisted_referenced?(assignment)
176
+ return unless assignment.referenced?
177
+
178
+ assignment.variable.references.any? do |reference|
179
+ call_to_persisted?(reference.node.parent)
180
+ end
181
+ end
182
+
183
+ def call_to_persisted?(node)
184
+ node.send_type? && node.method?(:persisted?)
185
+ end
186
+
187
+ def assignable_node(node)
188
+ assignable = node.block_node || node
189
+ while node
190
+ node = hash_parent(node) || array_parent(node)
191
+ assignable = node if node
192
+ end
193
+ assignable
194
+ end
195
+
196
+ def hash_parent(node)
197
+ pair = node.parent
198
+ return unless pair&.pair_type?
199
+
200
+ hash = pair.parent
201
+ return unless hash&.hash_type?
202
+
203
+ hash
204
+ end
205
+
206
+ def array_parent(node)
207
+ array = node.parent
208
+ return unless array&.array_type?
209
+
210
+ array
211
+ end
212
+
213
+ def check_used_in_conditional(node)
214
+ return false unless conditional?(node)
215
+
216
+ unless MODIFY_PERSIST_METHODS.include?(node.method_name)
217
+ add_offense_for_node(node, CREATE_CONDITIONAL_MSG)
218
+ end
219
+
220
+ true
221
+ end
222
+
223
+ def conditional?(node) # rubocop:disable Metrics/CyclomaticComplexity
224
+ node = node.block_node || node
225
+
226
+ condition = node.parent
227
+ return false unless condition
228
+
229
+ condition.if_type? || condition.case_type? ||
230
+ condition.or_type? || condition.and_type? ||
231
+ single_negative?(condition)
232
+ end
233
+
234
+ def allowed_receiver?(node)
235
+ return false unless node.receiver
236
+ return false unless cop_config['AllowedReceivers']
237
+
238
+ cop_config['AllowedReceivers'].any? do |allowed_receiver|
239
+ receiver_chain_matches?(node, allowed_receiver)
240
+ end
241
+ end
242
+
243
+ def receiver_chain_matches?(node, allowed_receiver)
244
+ allowed_receiver.split('.').reverse.all? do |receiver_part|
245
+ node = node.receiver
246
+ return false unless node
247
+
248
+ if node.variable?
249
+ node.node_parts.first == receiver_part.to_sym
250
+ elsif node.send_type?
251
+ node.method_name == receiver_part.to_sym
252
+ elsif node.const_type?
253
+ const_matches?(node.const_name, receiver_part)
254
+ end
255
+ end
256
+ end
257
+
258
+ # Const == Const
259
+ # ::Const == ::Const
260
+ # ::Const == Const
261
+ # Const == ::Const
262
+ # NameSpace::Const == Const
263
+ # NameSpace::Const == NameSpace::Const
264
+ # NameSpace::Const != ::Const
265
+ # Const != NameSpace::Const
266
+ def const_matches?(const, allowed_const)
267
+ parts = allowed_const.split('::').reverse.zip(
268
+ const.split('::').reverse
269
+ )
270
+ parts.all? do |(allowed_part, const_part)|
271
+ allowed_part == const_part.to_s
272
+ end
273
+ end
274
+
275
+ def implicit_return?(node)
276
+ return false unless cop_config['AllowImplicitReturn']
277
+
278
+ node = assignable_node(node)
279
+ method = node.parent
280
+ return unless method && (method.def_type? || method.block_type?)
281
+
282
+ method.children.size == node.sibling_index + 1
283
+ end
284
+
285
+ def argument?(node)
286
+ assignable_node(node).argument?
287
+ end
288
+
289
+ def explicit_return?(node)
290
+ ret = assignable_node(node).parent
291
+ ret && (ret.return_type? || ret.next_type?)
292
+ end
293
+
294
+ def return_value_assigned?(node)
295
+ assignment = assignable_node(node).parent
296
+ assignment&.lvasgn_type?
297
+ end
298
+
299
+ def persist_method?(node, methods = PERSIST_METHODS)
300
+ methods.include?(node.method_name) &&
301
+ expected_signature?(node) &&
302
+ !allowed_receiver?(node)
303
+ end
304
+
305
+ # Check argument signature as no arguments or one hash
306
+ def expected_signature?(node)
307
+ !node.arguments? ||
308
+ (node.arguments.one? &&
309
+ node.method_name != :destroy &&
310
+ (node.first_argument.hash_type? ||
311
+ !node.first_argument.literal?))
312
+ end
313
+ end
314
+ end
315
+ end
316
+ end