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.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +92 -0
- data/bin/setup +7 -0
- data/config/default.yml +510 -0
- data/lib/rubocop-rails.rb +12 -0
- data/lib/rubocop/cop/mixin/target_rails_version.rb +16 -0
- data/lib/rubocop/cop/rails/action_filter.rb +111 -0
- data/lib/rubocop/cop/rails/active_record_aliases.rb +48 -0
- data/lib/rubocop/cop/rails/active_record_override.rb +82 -0
- data/lib/rubocop/cop/rails/active_support_aliases.rb +69 -0
- data/lib/rubocop/cop/rails/application_controller.rb +36 -0
- data/lib/rubocop/cop/rails/application_job.rb +40 -0
- data/lib/rubocop/cop/rails/application_mailer.rb +40 -0
- data/lib/rubocop/cop/rails/application_record.rb +40 -0
- data/lib/rubocop/cop/rails/assert_not.rb +44 -0
- data/lib/rubocop/cop/rails/belongs_to.rb +102 -0
- data/lib/rubocop/cop/rails/blank.rb +164 -0
- data/lib/rubocop/cop/rails/bulk_change_table.rb +293 -0
- data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +91 -0
- data/lib/rubocop/cop/rails/date.rb +161 -0
- data/lib/rubocop/cop/rails/delegate.rb +132 -0
- data/lib/rubocop/cop/rails/delegate_allow_blank.rb +37 -0
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +91 -0
- data/lib/rubocop/cop/rails/enum_hash.rb +75 -0
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +65 -0
- data/lib/rubocop/cop/rails/environment_comparison.rb +68 -0
- data/lib/rubocop/cop/rails/exit.rb +67 -0
- data/lib/rubocop/cop/rails/file_path.rb +108 -0
- data/lib/rubocop/cop/rails/find_by.rb +55 -0
- data/lib/rubocop/cop/rails/find_each.rb +51 -0
- data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +25 -0
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +106 -0
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +39 -0
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +117 -0
- data/lib/rubocop/cop/rails/http_status.rb +160 -0
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +94 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +246 -0
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +175 -0
- data/lib/rubocop/cop/rails/link_to_blank.rb +98 -0
- data/lib/rubocop/cop/rails/not_null_column.rb +67 -0
- data/lib/rubocop/cop/rails/output.rb +49 -0
- data/lib/rubocop/cop/rails/output_safety.rb +99 -0
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +107 -0
- data/lib/rubocop/cop/rails/presence.rb +148 -0
- data/lib/rubocop/cop/rails/present.rb +153 -0
- data/lib/rubocop/cop/rails/rake_environment.rb +91 -0
- data/lib/rubocop/cop/rails/read_write_attribute.rb +74 -0
- data/lib/rubocop/cop/rails/redundant_allow_nil.rb +111 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +136 -0
- data/lib/rubocop/cop/rails/reflection_class_name.rb +37 -0
- data/lib/rubocop/cop/rails/refute_methods.rb +76 -0
- data/lib/rubocop/cop/rails/relative_date_constant.rb +102 -0
- data/lib/rubocop/cop/rails/request_referer.rb +56 -0
- data/lib/rubocop/cop/rails/reversible_migration.rb +284 -0
- data/lib/rubocop/cop/rails/safe_navigation.rb +85 -0
- data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +48 -0
- data/lib/rubocop/cop/rails/save_bang.rb +331 -0
- data/lib/rubocop/cop/rails/scope_args.rb +29 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +87 -0
- data/lib/rubocop/cop/rails/time_zone.rb +249 -0
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +105 -0
- data/lib/rubocop/cop/rails/unknown_env.rb +84 -0
- data/lib/rubocop/cop/rails/validation.rb +147 -0
- data/lib/rubocop/cop/rails_cops.rb +61 -0
- data/lib/rubocop/rails.rb +12 -0
- data/lib/rubocop/rails/inject.rb +18 -0
- data/lib/rubocop/rails/version.rb +10 -0
- metadata +148 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks that `if` and `only` (or `except`) are not used together
|
7
|
+
# as options of `skip_*` action filter.
|
8
|
+
#
|
9
|
+
# The `if` option will be ignored when `if` and `only` are used together.
|
10
|
+
# Similarly, the `except` option will be ignored when `if` and `except`
|
11
|
+
# are used together.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# class MyPageController < ApplicationController
|
16
|
+
# skip_before_action :login_required,
|
17
|
+
# only: :show, if: :trusted_origin?
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# class MyPageController < ApplicationController
|
22
|
+
# skip_before_action :login_required,
|
23
|
+
# if: -> { trusted_origin? && action_name == "show" }
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# # bad
|
28
|
+
# class MyPageController < ApplicationController
|
29
|
+
# skip_before_action :login_required,
|
30
|
+
# except: :admin, if: :trusted_origin?
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# # good
|
34
|
+
# class MyPageController < ApplicationController
|
35
|
+
# skip_before_action :login_required,
|
36
|
+
# if: -> { trusted_origin? && action_name != "admin" }
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# @see https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options
|
40
|
+
class IgnoredSkipActionFilterOption < Cop
|
41
|
+
MSG = <<~MSG.chomp.freeze
|
42
|
+
`%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
|
43
|
+
MSG
|
44
|
+
|
45
|
+
FILTERS = %w[
|
46
|
+
:skip_after_action
|
47
|
+
:skip_around_action
|
48
|
+
:skip_before_action
|
49
|
+
:skip_action_callback
|
50
|
+
].freeze
|
51
|
+
|
52
|
+
def_node_matcher :filter_options, <<~PATTERN
|
53
|
+
(send
|
54
|
+
nil?
|
55
|
+
{#{FILTERS.join(' ')}}
|
56
|
+
_
|
57
|
+
$_)
|
58
|
+
PATTERN
|
59
|
+
|
60
|
+
def on_send(node)
|
61
|
+
options = filter_options(node)
|
62
|
+
return unless options
|
63
|
+
return unless options.hash_type?
|
64
|
+
|
65
|
+
options = options_hash(options)
|
66
|
+
|
67
|
+
if if_and_only?(options)
|
68
|
+
add_offense(options[:if],
|
69
|
+
message: format(MSG, prefer: :only, ignore: :if))
|
70
|
+
elsif if_and_except?(options)
|
71
|
+
add_offense(options[:except],
|
72
|
+
message: format(MSG, prefer: :if, ignore: :except))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def options_hash(options)
|
79
|
+
options.pairs
|
80
|
+
.select { |pair| pair.key.sym_type? }
|
81
|
+
.map { |pair| [pair.key.value, pair] }.to_h
|
82
|
+
end
|
83
|
+
|
84
|
+
def if_and_only?(options)
|
85
|
+
options.key?(:if) && options.key?(:only)
|
86
|
+
end
|
87
|
+
|
88
|
+
def if_and_except?(options)
|
89
|
+
options.key?(:if) && options.key?(:except)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop looks for has_(one|many) and belongs_to associations where
|
7
|
+
# Active Record can't automatically determine the inverse association
|
8
|
+
# because of a scope or the options used. Using the blog with order scope
|
9
|
+
# example below, traversing the a Blog's association in both directions
|
10
|
+
# with `blog.posts.first.blog` would cause the `blog` to be loaded from
|
11
|
+
# the database twice.
|
12
|
+
#
|
13
|
+
# `:inverse_of` must be manually specified for Active Record to use the
|
14
|
+
# associated object in memory, or set to `false` to opt-out. Note that
|
15
|
+
# setting `nil` does not stop Active Record from trying to determine the
|
16
|
+
# inverse automatically, and is not considered a valid value for this.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# # good
|
20
|
+
# class Blog < ApplicationRecord
|
21
|
+
# has_many :posts
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# class Post < ApplicationRecord
|
25
|
+
# belongs_to :blog
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# # bad
|
30
|
+
# class Blog < ApplicationRecord
|
31
|
+
# has_many :posts, -> { order(published_at: :desc) }
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# class Post < ApplicationRecord
|
35
|
+
# belongs_to :blog
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # good
|
39
|
+
# class Blog < ApplicationRecord
|
40
|
+
# has_many(:posts,
|
41
|
+
# -> { order(published_at: :desc) },
|
42
|
+
# inverse_of: :blog)
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# class Post < ApplicationRecord
|
46
|
+
# belongs_to :blog
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# # good
|
50
|
+
# class Blog < ApplicationRecord
|
51
|
+
# with_options inverse_of: :blog do
|
52
|
+
# has_many :posts, -> { order(published_at: :desc) }
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# class Post < ApplicationRecord
|
57
|
+
# belongs_to :blog
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# # good
|
61
|
+
# # When you don't want to use the inverse association.
|
62
|
+
# class Blog < ApplicationRecord
|
63
|
+
# has_many(:posts,
|
64
|
+
# -> { order(published_at: :desc) },
|
65
|
+
# inverse_of: false)
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# # bad
|
70
|
+
# class Picture < ApplicationRecord
|
71
|
+
# belongs_to :imageable, polymorphic: true
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# class Employee < ApplicationRecord
|
75
|
+
# has_many :pictures, as: :imageable
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# class Product < ApplicationRecord
|
79
|
+
# has_many :pictures, as: :imageable
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# # good
|
83
|
+
# class Picture < ApplicationRecord
|
84
|
+
# belongs_to :imageable, polymorphic: true
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# class Employee < ApplicationRecord
|
88
|
+
# has_many :pictures, as: :imageable, inverse_of: :imageable
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# class Product < ApplicationRecord
|
92
|
+
# has_many :pictures, as: :imageable, inverse_of: :imageable
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
# # bad
|
97
|
+
# # However, RuboCop can not detect this pattern...
|
98
|
+
# class Physician < ApplicationRecord
|
99
|
+
# has_many :appointments
|
100
|
+
# has_many :patients, through: :appointments
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# class Appointment < ApplicationRecord
|
104
|
+
# belongs_to :physician
|
105
|
+
# belongs_to :patient
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# class Patient < ApplicationRecord
|
109
|
+
# has_many :appointments
|
110
|
+
# has_many :physicians, through: :appointments
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# # good
|
114
|
+
# class Physician < ApplicationRecord
|
115
|
+
# has_many :appointments
|
116
|
+
# has_many :patients, through: :appointments
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# class Appointment < ApplicationRecord
|
120
|
+
# belongs_to :physician, inverse_of: :appointments
|
121
|
+
# belongs_to :patient, inverse_of: :appointments
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# class Patient < ApplicationRecord
|
125
|
+
# has_many :appointments
|
126
|
+
# has_many :physicians, through: :appointments
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# @see https://guides.rubyonrails.org/association_basics.html#bi-directional-associations
|
130
|
+
# @see https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
|
131
|
+
class InverseOf < Cop
|
132
|
+
extend TargetRailsVersion
|
133
|
+
|
134
|
+
minimum_target_rails_version 4.1
|
135
|
+
|
136
|
+
SPECIFY_MSG = 'Specify an `:inverse_of` option.'
|
137
|
+
NIL_MSG = 'You specified `inverse_of: nil`, you probably meant to ' \
|
138
|
+
'use `inverse_of: false`.'
|
139
|
+
|
140
|
+
def_node_matcher :association_recv_arguments, <<~PATTERN
|
141
|
+
(send $_ {:has_many :has_one :belongs_to} _ $...)
|
142
|
+
PATTERN
|
143
|
+
|
144
|
+
def_node_matcher :options_from_argument, <<~PATTERN
|
145
|
+
(hash $...)
|
146
|
+
PATTERN
|
147
|
+
|
148
|
+
def_node_matcher :conditions_option?, <<~PATTERN
|
149
|
+
(pair (sym :conditions) !nil)
|
150
|
+
PATTERN
|
151
|
+
|
152
|
+
def_node_matcher :through_option?, <<~PATTERN
|
153
|
+
(pair (sym :through) !nil)
|
154
|
+
PATTERN
|
155
|
+
|
156
|
+
def_node_matcher :polymorphic_option?, <<~PATTERN
|
157
|
+
(pair (sym :polymorphic) !nil)
|
158
|
+
PATTERN
|
159
|
+
|
160
|
+
def_node_matcher :as_option?, <<~PATTERN
|
161
|
+
(pair (sym :as) !nil)
|
162
|
+
PATTERN
|
163
|
+
|
164
|
+
def_node_matcher :foreign_key_option?, <<~PATTERN
|
165
|
+
(pair (sym :foreign_key) !nil)
|
166
|
+
PATTERN
|
167
|
+
|
168
|
+
def_node_matcher :inverse_of_option?, <<~PATTERN
|
169
|
+
(pair (sym :inverse_of) !nil)
|
170
|
+
PATTERN
|
171
|
+
|
172
|
+
def_node_matcher :inverse_of_nil_option?, <<~PATTERN
|
173
|
+
(pair (sym :inverse_of) nil)
|
174
|
+
PATTERN
|
175
|
+
|
176
|
+
def on_send(node)
|
177
|
+
recv, arguments = association_recv_arguments(node)
|
178
|
+
return unless arguments
|
179
|
+
|
180
|
+
with_options = with_options_arguments(recv, node)
|
181
|
+
|
182
|
+
options = arguments.concat(with_options).flat_map do |arg|
|
183
|
+
options_from_argument(arg)
|
184
|
+
end
|
185
|
+
return if options_ignoring_inverse_of?(options)
|
186
|
+
|
187
|
+
return unless scope?(arguments) ||
|
188
|
+
options_requiring_inverse_of?(options)
|
189
|
+
|
190
|
+
return if options_contain_inverse_of?(options)
|
191
|
+
|
192
|
+
add_offense(node, message: message(options), location: :selector)
|
193
|
+
end
|
194
|
+
|
195
|
+
def scope?(arguments)
|
196
|
+
arguments.any?(&:block_type?)
|
197
|
+
end
|
198
|
+
|
199
|
+
def options_requiring_inverse_of?(options)
|
200
|
+
required = options.any? do |opt|
|
201
|
+
conditions_option?(opt) ||
|
202
|
+
foreign_key_option?(opt)
|
203
|
+
end
|
204
|
+
|
205
|
+
return required if target_rails_version >= 5.2
|
206
|
+
|
207
|
+
required || options.any? { |opt| as_option?(opt) }
|
208
|
+
end
|
209
|
+
|
210
|
+
def options_ignoring_inverse_of?(options)
|
211
|
+
options.any? do |opt|
|
212
|
+
through_option?(opt) || polymorphic_option?(opt)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def options_contain_inverse_of?(options)
|
217
|
+
options.any? { |opt| inverse_of_option?(opt) }
|
218
|
+
end
|
219
|
+
|
220
|
+
def with_options_arguments(recv, node)
|
221
|
+
blocks = node.each_ancestor(:block).select do |block|
|
222
|
+
block.send_node.command?(:with_options) &&
|
223
|
+
same_context_in_with_options?(block.arguments.first, recv)
|
224
|
+
end
|
225
|
+
blocks.flat_map { |n| n.send_node.arguments }
|
226
|
+
end
|
227
|
+
|
228
|
+
def same_context_in_with_options?(arg, recv)
|
229
|
+
return true if arg.nil? && recv.nil?
|
230
|
+
|
231
|
+
arg && recv && arg.children[0] == recv.children[0]
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
def message(options)
|
237
|
+
if options.any? { |opt| inverse_of_nil_option?(opt) }
|
238
|
+
NIL_MSG
|
239
|
+
else
|
240
|
+
SPECIFY_MSG
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks that methods specified in the filter's `only` or
|
7
|
+
# `except` options are defined within the same class or module.
|
8
|
+
#
|
9
|
+
# You can technically specify methods of superclass or methods added by
|
10
|
+
# mixins on the filter, but these can confuse developers. If you specify
|
11
|
+
# methods that are defined in other classes or modules, you should
|
12
|
+
# define the filter in that class or module.
|
13
|
+
#
|
14
|
+
# If you rely on behaviour defined in the superclass actions, you must
|
15
|
+
# remember to invoke `super` in the subclass actions.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # bad
|
19
|
+
# class LoginController < ApplicationController
|
20
|
+
# before_action :require_login, only: %i[index settings logout]
|
21
|
+
#
|
22
|
+
# def index
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# class LoginController < ApplicationController
|
28
|
+
# before_action :require_login, only: %i[index settings logout]
|
29
|
+
#
|
30
|
+
# def index
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def settings
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# def logout
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# # bad
|
42
|
+
# module FooMixin
|
43
|
+
# extend ActiveSupport::Concern
|
44
|
+
#
|
45
|
+
# included do
|
46
|
+
# before_action proc { authenticate }, only: :foo
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# # good
|
51
|
+
# module FooMixin
|
52
|
+
# extend ActiveSupport::Concern
|
53
|
+
#
|
54
|
+
# included do
|
55
|
+
# before_action proc { authenticate }, only: :foo
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# def foo
|
59
|
+
# # something
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# class ContentController < ApplicationController
|
65
|
+
# def update
|
66
|
+
# @content.update(content_attributes)
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# class ArticlesController < ContentController
|
71
|
+
# before_action :load_article, only: [:update]
|
72
|
+
#
|
73
|
+
# # the cop requires this method, but it relies on behaviour defined
|
74
|
+
# # in the superclass, so needs to invoke `super`
|
75
|
+
# def update
|
76
|
+
# super
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# private
|
80
|
+
#
|
81
|
+
# def load_article
|
82
|
+
# @content = Article.find(params[:article_id])
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
class LexicallyScopedActionFilter < Cop
|
86
|
+
MSG = '%<action>s not explicitly defined on the %<type>s.'
|
87
|
+
|
88
|
+
FILTERS = %w[
|
89
|
+
:after_action
|
90
|
+
:append_after_action
|
91
|
+
:append_around_action
|
92
|
+
:append_before_action
|
93
|
+
:around_action
|
94
|
+
:before_action
|
95
|
+
:prepend_after_action
|
96
|
+
:prepend_around_action
|
97
|
+
:prepend_before_action
|
98
|
+
:skip_after_action
|
99
|
+
:skip_around_action
|
100
|
+
:skip_before_action
|
101
|
+
:skip_action_callback
|
102
|
+
].freeze
|
103
|
+
|
104
|
+
def_node_matcher :only_or_except_filter_methods, <<~PATTERN
|
105
|
+
(send
|
106
|
+
nil?
|
107
|
+
{#{FILTERS.join(' ')}}
|
108
|
+
_
|
109
|
+
(hash
|
110
|
+
(pair
|
111
|
+
(sym {:only :except})
|
112
|
+
$_)))
|
113
|
+
PATTERN
|
114
|
+
|
115
|
+
def on_send(node)
|
116
|
+
methods_node = only_or_except_filter_methods(node)
|
117
|
+
return unless methods_node
|
118
|
+
|
119
|
+
parent = node.each_ancestor(:class, :module).first
|
120
|
+
return unless parent
|
121
|
+
|
122
|
+
block = parent.each_child_node(:begin).first
|
123
|
+
return unless block
|
124
|
+
|
125
|
+
defined_methods = block.each_child_node(:def).map(&:method_name)
|
126
|
+
methods = array_values(methods_node).reject do |method|
|
127
|
+
defined_methods.include?(method)
|
128
|
+
end
|
129
|
+
|
130
|
+
message = message(methods, parent)
|
131
|
+
add_offense(node, message: message) unless methods.empty?
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# @param node [RuboCop::AST::Node]
|
137
|
+
# @return [Array<Symbol>]
|
138
|
+
def array_values(node) # rubocop:disable Metrics/MethodLength
|
139
|
+
case node.type
|
140
|
+
when :str
|
141
|
+
[node.str_content.to_sym]
|
142
|
+
when :sym
|
143
|
+
[node.value]
|
144
|
+
when :array
|
145
|
+
node.values.map do |v|
|
146
|
+
case v.type
|
147
|
+
when :str
|
148
|
+
v.str_content.to_sym
|
149
|
+
when :sym
|
150
|
+
v.value
|
151
|
+
end
|
152
|
+
end.compact
|
153
|
+
else
|
154
|
+
[]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# @param methods [Array<String>]
|
159
|
+
# @param parent [RuboCop::AST::Node]
|
160
|
+
# @return [String]
|
161
|
+
def message(methods, parent)
|
162
|
+
if methods.size == 1
|
163
|
+
format(MSG,
|
164
|
+
action: "`#{methods[0]}` is",
|
165
|
+
type: parent.type)
|
166
|
+
else
|
167
|
+
format(MSG,
|
168
|
+
action: "`#{methods.join('`, `')}` are",
|
169
|
+
type: parent.type)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|