rubocop-rails 2.0.0

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