Class: RuboCop::Cop::Lint::DuplicateMethods
- Includes:
- ProjectIndexHelp
- Defined in:
- lib/rubocop/cop/lint/duplicate_methods.rb
Overview
Checks for duplicated instance (or singleton) method definitions.
NOTE: Aliasing a method to itself is allowed, as it indicates that the developer intends to suppress Ruby's method redefinition warnings. See https://bugs.ruby-lang.org/issues/13574.
By default the cop can only detect duplicates within a single file.
When AllCops/UseProjectIndex is enabled and the rubydex gem is installed,
the cop additionally consults the project-wide index and reports methods
whose duplicate definition lives in another file.
NOTE: The project index does not record whether a definition in another
file is wrapped in a conditional, so a platform-specific redefinition in
another file may still be reported. Aliasing the method to itself (see above)
before redefining marks the redefinition as intentional and is respected
across files. With AllCops/ActiveSupportExtensionsEnabled: true, Active
Support's redefinition markers (silence_redefinition_of_method and
redefine_method) are honored the same way.
NOTE: Methods defined with define_method are not recorded in the project
index, so a duplicate whose other definition uses define_method cannot
be detected across files.
Cross-file duplicates whose other definition lives in a file matching one
of the AllowedCrossFilePaths patterns are not reported. This suits files
that redefine application methods but are never loaded together with them,
such as standalone scripts. Patterns are matched with the same glob (or
regexp) semantics as Exclude, against the other file's path relative to
the directory of the .rubocop.yml that configures the cop; absolute
patterns are matched against the absolute path. Offenses inside such files
themselves are best silenced with an ordinary per-cop Exclude.
Constant Summary collapse
- MSG =
'Method `%<method>s` is defined at both %<defined>s and %<current>s.'- INDEXABLE_METHOD_NAME =
Method names the cop registers that can be looked up in the project index: a fully qualified namespace followed by
#(instance) or.(singleton) and the method name. /\A(?<owner>[A-Z]\w*(?:::[A-Z]\w*)*)(?<separator>[#.])(?<name>[^#.]+)\z/.freeze
Constants included from ProjectIndexHelp
ProjectIndexHelp::BUILTIN_DOCUMENT_URI, ProjectIndexHelp::FILE_URI_PREFIX, ProjectIndexHelp::WINDOWS_DRIVE_PREFIX
Constants inherited from Base
Instance Attribute Summary
Attributes inherited from Base
#config, #processed_source, #project_index
Instance Method Summary collapse
-
#active_support_redefinition_marker(node) ⇒ Object
Matches Active Support's markers of an intentional method redefinition:
silence_redefinition_of_method :namesuppresses Ruby's redefinition warning for a subsequent definition ofname, andredefine_method(:name) { ... }both silences the warning and redefines. - #alias_method?(node) ⇒ Object
- #class_new_block?(node) ⇒ Object
- #class_or_module_new_block?(node) ⇒ Object
-
#delegate_args(node) ⇒ Object
Matches the argument shape of an Active Support
delegatecall (delegate :a, :b, to: :target), regardless of the method name; the name is checked separately againstDelegatingMethods. - #delegator?(node) ⇒ Object
- #delegators?(node) ⇒ Object
-
#initialize(config = nil, options = nil) ⇒ DuplicateMethods
constructor
No
RESTRICT_ON_SEND: the delegating method names are configurable (DelegatingMethods), soon_sendmust see every call. - #method_alias?(node) ⇒ Object
- #on_alias(node) ⇒ Object
- #on_def(node) ⇒ Object
- #on_defs(node) ⇒ Object
- #on_new_investigation ⇒ Object
-
#on_send(node) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity.
- #sym_name(node) ⇒ Object
Methods included from ProjectIndexHelp
Methods inherited from Base
#active_support_extensions_enabled?, #add_global_offense, #add_offense, #always_autocorrect?, autocorrect_incompatible_with, badge, #begin_investigation, #callbacks_needed, callbacks_needed, #config_to_allow_offenses, #config_to_allow_offenses=, #contextual_autocorrect?, #cop_config, cop_name, #cop_name, department, documentation_url, exclude_from_registry, #excluded_file?, #external_dependency_checksum, inherited, #inspect, joining_forces, lint?, match?, #message, #offenses, #on_investigation_end, #on_other_file, #parse, #parser_engine, #ready, #relevant_file?, requires_gem, #string_literals_frozen_by_default?, support_autocorrect?, support_multiple_source?, #target_gem_version, #target_rails_version, #target_ruby_version
Methods included from ExcludeLimit
cop_dir_for, #exclude_limit, read_limits
Methods included from AutocorrectLogic
#autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #autocorrect_with_disable_uncorrectable?, #correctable?, #disable_uncorrectable?, #safe_autocorrect?, #skipped_unsafe_correction_with_disable_uncorrectable?
Methods included from IgnoredNode
#ignore_node, #ignored_node?, #part_of_ignored_node?
Methods included from Util
Constructor Details
#initialize(config = nil, options = nil) ⇒ DuplicateMethods
No RESTRICT_ON_SEND: the delegating method names are configurable
(DelegatingMethods), so on_send must see every call. The handler
returns quickly for calls that are not method definitions, and
benchmarks show no measurable cost over the previous fixed list.
192 193 194 195 196 197 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 192 def initialize(config = nil, = nil) super @definitions = {} @scopes = Hash.new { |hash, key| hash[key] = [] } @intentionally_redefined = Set.new end |
Instance Method Details
#active_support_redefinition_marker(node) ⇒ Object
Matches Active Support's markers of an intentional method redefinition:
silence_redefinition_of_method :name suppresses Ruby's redefinition
warning for a subsequent definition of name, and
redefine_method(:name) { ... } both silences the warning and redefines.
283 284 285 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 283 def_node_matcher :active_support_redefinition_marker, <<~PATTERN (send nil? {:silence_redefinition_of_method :redefine_method} ({sym str} $_) ...) PATTERN |
#alias_method?(node) ⇒ Object
245 246 247 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 245 def_node_matcher :alias_method?, <<~PATTERN (send nil? :alias_method (sym $_name) (sym $_original_name)) PATTERN |
#class_new_block?(node) ⇒ Object
298 299 300 301 302 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 298 def_node_matcher :class_new_block?, <<~PATTERN (block (send (const _ :Class) :new ...) ...) PATTERN |
#class_or_module_new_block?(node) ⇒ Object
291 292 293 294 295 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 291 def_node_matcher :class_or_module_new_block?, <<~PATTERN (block (send (const _ {:Class :Module}) :new ...) ...) PATTERN |
#delegate_args(node) ⇒ Object
Matches the argument shape of an Active Support delegate call
(delegate :a, :b, to: :target), regardless of the method name; the
name is checked separately against DelegatingMethods.
253 254 255 256 257 258 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 253 def_node_matcher :delegate_args, <<~PATTERN (send nil? _ ({sym str} $_)+ (hash <(pair (sym :to) {sym str}) ...>) ) PATTERN |
#delegator?(node) ⇒ Object
261 262 263 264 265 266 267 268 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 261 def_node_matcher :delegator?, <<~PATTERN (send nil? {:def_delegator :def_instance_delegator} { {sym str} ({sym str} $_) | {sym str} {sym str} ({sym str} $_) } ) PATTERN |
#delegators?(node) ⇒ Object
271 272 273 274 275 276 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 271 def_node_matcher :delegators?, <<~PATTERN (send nil? {:def_delegators :def_instance_delegators} {sym str} ({sym str} $_)+ ) PATTERN |
#method_alias?(node) ⇒ Object
227 228 229 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 227 def_node_matcher :method_alias?, <<~PATTERN (alias (sym $_name) (sym $_original_name)) PATTERN |
#on_alias(node) ⇒ Object
231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 231 def on_alias(node) name, original_name = method_alias?(node) return unless name && original_name if name == original_name track_intentional_redefinition(node, name) return end return if node.ancestors.any?(&:if_type?) found_instance_method(node, name) end |
#on_def(node) ⇒ Object
207 208 209 210 211 212 213 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 207 def on_def(node) # if a method definition is inside an if, it is very likely # that a different definition is used depending on platform, etc. return if node.each_ancestor.any?(&:if_type?) found_instance_method(node, node.method_name) end |
#on_defs(node) ⇒ Object
215 216 217 218 219 220 221 222 223 224 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 215 def on_defs(node) return if node.each_ancestor.any?(&:if_type?) if node.receiver.const_type? _, const_name = *node.receiver check_const_receiver(node, node.method_name, const_name) elsif node.receiver.self_type? check_self_receiver(node, node.method_name) end end |
#on_new_investigation ⇒ Object
199 200 201 202 203 204 205 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 199 def on_new_investigation # The self-alias trick and Active Support's redefinition markers declare # an intentional redefinition only within the file that uses them, so the # tracked names do not carry over. @intentionally_redefined = Set.new super end |
#on_send(node) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 304 def on_send(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity name, original_name = alias_method?(node) if name && original_name if name == original_name track_intentional_redefinition(node, name) return end return if inside_condition?(node) found_instance_method(node, name) elsif (attr = node.attribute_accessor?) on_attr(node, *attr) elsif delegating_method?(node) && (names = delegate_args(node)) return if inside_condition?(node) on_delegate(node, names) elsif (name = delegator?(node)) return if inside_condition?(node) found_instance_method(node, name) elsif (names = delegators?(node)) return if inside_condition?(node) names.each { |name| found_instance_method(node, name) } elsif (name = active_support_redefinition_marker(node)) # `redefine_method` takes a block; scope resolution must start from the # block node, since `parent_module_name` cannot see through a block # ancestor that is not a module constructor. track_intentional_redefinition(node.block_node || node, name) if active_support_extensions_enabled? end end |
#sym_name(node) ⇒ Object
288 |
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 288 def_node_matcher :sym_name, '(sym $_name)' |