123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Lint::DuplicateMethods

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::RuboCop::Cop::Base, ::RuboCop::ExcludeLimit, NodePattern::Macros, RuboCop::AST::Sexp
Instance Chain:
Inherits: RuboCop::Cop::Base
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.

Examples:

# bad
def foo
  1
end

def foo
  2
end

# bad
def foo
  1
end

alias foo bar

# good
def foo
  1
end

def bar
  2
end

# good
def foo
  1
end

alias bar foo

# good
alias foo foo
def foo
  1
end

# good
alias_method :foo, :foo
def foo
  1
end

# bad
class MyClass
  extend Forwardable

  # or with: {def_instance_delegator}, {def_delegators}, {def_instance_delegators}
  def_delegator :delegation_target, :delegated_method_name

  def delegated_method_name
  end
end

# good
class MyClass
  extend Forwardable

  def_delegator :delegation_target, :delegated_method_name

  def non_duplicated_delegated_method_name
  end
end

AllCops:ActiveSupportExtensionsEnabled: false (default)

# good
def foo
  1
end

delegate :foo, to: :bar

AllCops:ActiveSupportExtensionsEnabled: true

# bad
def foo
  1
end

delegate :foo, to: :bar

# good
def foo
  1
end

delegate :baz, to: :bar

# good - delegate with splat arguments is ignored
def foo
  1
end

delegate :foo, **options

# good - delegate inside a condition is ignored
def foo
  1
end

if cond
  delegate :foo, to: :bar
end

# good - Active Support's redefinition markers signal an intentional
# redefinition of a method defined in another file
silence_redefinition_of_method :foo
def foo
  1
end

AllowedCrossFilePaths: ['script/*/'] (default: [])

# Cross-file duplicates whose other definition lives in a file
# matching one of the patterns are not reported.

# good - assuming {AppHelper#format} is also defined in
# {script/backfill.rb}, which is never loaded with this file
class AppHelper
  def format
  end
end

DelegatingMethods: ['delegate', 'expose'] (default: ['delegate'])

# A project's own `delegate`-shaped macros can be registered so the
# methods they define are recognized (with
# `AllCops/ActiveSupportExtensionsEnabled: true`).

# bad
def foo
  1
end

expose :foo, to: :bar

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

::RuboCop::Cop::ProjectIndexHelp - Included

BUILTIN_DOCUMENT_URI, FILE_URI_PREFIX, WINDOWS_DRIVE_PREFIX

Class Attribute Summary

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,
.support_autocorrect?

Returns if class supports autocorrect.

.support_multiple_source?

Override if your cop should be called repeatedly for multiple investigations Between calls to #on_new_investigation and on_investigation_end, the result of processed_source will remain constant.

Class Method Summary

::RuboCop::Cop::Base - Inherited

.autocorrect_incompatible_with

List of cops that should not try to autocorrect at the same time as this cop.

.badge

Naming.

.callbacks_needed, .cop_name, .department,
.documentation_url

Returns a url to view this cops documentation online.

.exclude_from_registry

Call for abstract Cop classes.

.inherited,
.joining_forces

Override and return the Force class(es) you need to join.

.match?

Returns true if the cop name or the cop namespace matches any of the given names.

.new,
.requires_gem

Register a version requirement for the given gem name.

.restrict_on_send

Reserved for Commissioner.

::RuboCop::ExcludeLimit - Extended

exclude_limit

Sets up a configuration option to have an exclude limit tracked.

transform

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::ProjectIndexHelp - Included

#external_dependency_checksum, #compute_project_index_signature,
#definitions_in_other_files

Returns the definitions among definitions that live in a file other than the one being inspected, ordered by path and line.

#fully_resolved_index_ancestry?

Whether every link of the declaration’s ancestry is resolved in the index: no definition of any ancestor has an unresolved superclass or mixin reference.

#indexed_singleton_member

A namespace without any singleton method has no singleton-class declaration of its own, so the lookup starts from the first ancestor that has one; its find_member covers the rest of the chain.

#indexed_singleton_of

The declaration of `declaration’s singleton class, or nil when no singleton method is defined on it anywhere in the project.

#inherited_index_member?

Whether an ancestor of scope other than scope itself defines member_name.

#lexical_nesting_of

The lexical nesting the node’s constants resolve through, outermost first.

#prior_definition_in_other_file, #project_index_signature,
#resolve_constant_in_index

Resolves a constant node the way Ruby does: the first segment through the lexical nesting and every following segment inside the previous one.

#resolved_mixin_references?, #resolved_superclass_reference?, #same_file?

::RuboCop::Cop::Base - Inherited

#add_global_offense

Adds an offense that has no particular location.

#add_offense

Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense.

#begin_investigation

Called before any investigation.

#callbacks_needed

 — grouped under the Commissioner heading above.

#cop_config

Configuration Helpers.

#cop_name, #excluded_file?,
#external_dependency_checksum

This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:

#inspect,
#message

Gets called if no message is specified when calling add_offense or add_global_offense Cops are discouraged to override this; instead pass your message directly.

#name

Alias for Base#cop_name.

#offenses,
#on_investigation_end

Called after all on_…​

#on_new_investigation

Called before all on_…​

#on_other_file

Called instead of all on_…​

#parse

There should be very limited reasons for a Cop to do it’s own parsing.

#parser_engine,
#ready

Called between investigations.

#relevant_file?,
#target_gem_version

Returns a gems locked versions (i.e.

#target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #covering_disabled_range, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?,
#enabled_lines?

A multi-line offense is suppressed by a directive on any line of its range, not only its first line, matching the intuition that the directive is attached to the offending code.

#file_name_matches_any?, #find_message, #find_severity, #matches_absolute_include_pattern?, #range_for_original, #range_from_node_or_range,
#reset_investigation

Actually private methods.

#suppression_reason

The -- reason on the directive that suppresses offenses on this range, or nil when the directive carries none.

#use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

.new(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.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 192

def initialize(config = nil, options = nil)
  super
  @definitions = {}
  @scopes = Hash.new { |hash, key| hash[key] = [] }
  @intentionally_redefined = Set.new
end

Instance Method Details

#active_support_redefinition_marker(node)

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.

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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

#allowed_cross_file_path?(path) ⇒ Boolean (private)

Cross-file duplicates whose other definition site matches one of the AllowedCrossFilePaths patterns are skipped (e.g. standalone scripts that are never loaded together with the code they redefine). Patterns are matched like Exclude patterns, against the path relative to the directory of the config file that configures the cop; absolute patterns are matched against the absolute path.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 636

def allowed_cross_file_path?(path)
  patterns = cop_config.fetch('AllowedCrossFilePaths', [])
  return false if patterns.empty?

  relative = config.path_relative_to_config(path)
  patterns.any? { |pattern| match_path?(pattern, relative) || match_path?(pattern, path) }
end

#allowed_cross_file_redefinition?(node, key) ⇒ Boolean (private)

AllowedCrossFilePaths also covers duplicates detected without the index, when the cop instance carries definitions over from an earlier file of the same run.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 504

def allowed_cross_file_redefinition?(node, key)
  other_path = @definitions[key].source_range.source_buffer.name
  return false if other_path == node.source_range.source_buffer.name

  allowed_cross_file_path?(other_path)
end

#anon_block_identity(anon_block) (private)

Internal identity for an anonymous block, used as a scope key. Includes the source range’s begin position to distinguish blocks that share the same line (e.g. two Class.new calls separated by ;). The user-facing offense message still uses #source_location, which shows only path:line.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 716

def anon_block_identity(anon_block)
  range = anon_block.source_range
  "#{smart_path(range.source_buffer.name)}:#{range.line}:#{range.begin_pos}"
end

#anon_block_scope_id(anon_block) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 436

def anon_block_scope_id(anon_block)
  parent = anon_block.parent
  return unless parent&.type?(:any_block, :begin, :call, :casgn, :any_def)

  if (receiver = scope_receiver(parent, anon_block))
    "#{receiver.source}.#{parent.method_name}"
  elsif !parent.begin_type? || parent.parent&.any_block_type?
    anon_block_identity(anon_block)
  end
end

#anonymous_class_block(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 427

def anonymous_class_block(node)
  first_block = node.each_ancestor(:block).first
  return unless class_or_module_new_block?(first_block)
  return if first_block.parent&.type?(:lvasgn)
  return if node.each_ancestor(:sclass).any? { |s| !s.children.first.self_type? }

  first_block
end

#check_const_receiver(node, name, const_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 340

def check_const_receiver(node, name, const_name)
  qualified = lookup_constant(node, const_name)
  return unless qualified

  found_method(node, "#{qualified}.#{name}")
end

#check_cross_file_duplicate(node, method_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 606

def check_cross_file_duplicate(node, method_name)
  return unless project_index
  return if @intentionally_redefined.include?(method_name)
  return if node.each_ancestor(:any_def).any?
  return unless (prior = cross_file_prior_definition(method_name))

  message = format(MSG, method: method_name,
                        defined: index_source_location(prior),
                        current: source_location(node))
  add_offense(location(node), message: message)
end

#check_self_receiver(node, name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 347

def check_self_receiver(node, name)
  enclosing = node.parent_module_name
  if enclosing
    found_method(node, "#{enclosing}.#{name}")
  elsif (anon_block = anonymous_class_block(node))
    scope = qualified_name(anon_block.parent_module_name, nil, 'Object')
    found_method(node, "#{scope}.#{name}", scope_id: anon_block_scope_id(anon_block))
  end
end

#class_new_block?(node)

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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

#cross_file_prior_definition(method_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 618

def cross_file_prior_definition(method_name)
  return unless (match = INDEXABLE_METHOD_NAME.match(method_name))

  definitions = definitions_in_other_files(
    indexed_definitions(match[:owner], match[:separator], match[:name])
  ).reject { |definition| allowed_cross_file_path?(definition.location.to_file_path) }
  return if definitions.empty? || cross_file_self_alias_trick?(definitions)
  return if cross_file_redefinition_marker?(definitions, match[:name])

  first_indexed_definition(definitions)
end

#cross_file_redefinition_marker?(definitions, name) ⇒ Boolean (private)

An Active Support redefinition marker alongside one of the definitions in another file marks the redefinition there as intentional, like the self-alias trick. The index does not record the marker’s method-name argument, so the other file’s source is searched for a marker naming the method.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 685

def cross_file_redefinition_marker?(definitions, name)
  return false unless active_support_extensions_enabled?

  definitions.map { |definition| definition.location.to_file_path }.uniq.any? do |path|
    redefinition_marker_in_file?(path, name)
  end
end

#cross_file_self_alias_trick?(definitions) ⇒ Boolean (private)

An alias of the method alongside one of its definitions in another file may be the self-alias trick marking an intentional redefinition there, so no offense is registered. A genuine alias duplicate is still reported when the alias itself is inspected.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 671

def cross_file_self_alias_trick?(definitions)
  aliases, others = definitions.partition do |definition|
    definition.is_a?(Rubydex::MethodAliasDefinition)
  end
  alias_paths = aliases.map { |definition| definition.location.to_file_path }

  others.any? { |definition| alias_paths.include?(definition.location.to_file_path) }
end

#delegate_args(node)

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.

[ GitHub ]

  
# 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

#delegate_prefix(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 389

def delegate_prefix(node)
  kwargs_node = node.last_argument

  return unless (prefix = hash_value(kwargs_node, :prefix))

  if prefix.true_type?
    hash_value(kwargs_node, :to).value
  elsif prefix.type?(:sym, :str)
    prefix.value
  end
end

#delegating_method?(node) ⇒ Boolean (private)

Recognizing a delegating call as a method definition is Active Support behavior, so it stays gated on ActiveSupportExtensionsEnabled as it was when only delegate was supported. DelegatingMethods lets a project register its own delegate-shaped macros.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 370

def delegating_method?(node)
  active_support_extensions_enabled? &&
    delegating_methods.include?(node.method_name.to_s)
end

#delegating_methods (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 375

def delegating_methods
  cop_config.fetch('DelegatingMethods', ['delegate'])
end

#delegator?(node)

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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

#first_indexed_definition(definitions) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 702

def first_indexed_definition(definitions)
  definitions.find { |definition| !definition.is_a?(Rubydex::MethodAliasDefinition) }
end

#found_attr(node, args, readable: false, writable: false) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 558

def found_attr(node, args, readable: false, writable: false)
  args.each do |arg|
    name = sym_name(arg)
    next unless name

    found_instance_method(node, name) if readable
    found_instance_method(node, "#{name}=") if writable
  end
end

#found_instance_method(node, name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 405

def found_instance_method(node, name)
  if (scope = node.parent_module_name)
    found_method(node, "#{humanize_scope(scope)}#{name}")
  elsif (anon_block = anonymous_class_block(node))
    base = qualified_name(anon_block.parent_module_name, nil, 'Object')
    scope = node.each_ancestor(:sclass).any? ? "#<Class:#{base}>" : base
    found_method(
      node, "#{humanize_scope(scope)}#{name}", scope_id: anon_block_scope_id(anon_block)
    )
  else
    found_sclass_method(node, name)
  end
end

#found_method(node, method_name, scope_id: nil) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 476

def found_method(node, method_name, scope_id: nil)
  key = method_key(node, method_name)
  key = "#{key}@#{scope_id}" if scope_id
  scope = node.each_ancestor(:rescue, :ensure).first&.type

  if @definitions.key?(key)
    found_redefinition(node, method_name, key, scope)
  else
    @definitions[key] = node
    check_cross_file_duplicate(node, method_name) if scope_id.nil? && scope.nil?
  end
end

#found_redefinition(node, method_name, key, scope) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 489

def found_redefinition(node, method_name, key, scope)
  if scope && !@scopes[scope].include?(key)
    @definitions[key] = node
    @scopes[scope] << key
  elsif intentional_cross_file_redefinition?(node, method_name, key) ||
        allowed_cross_file_redefinition?(node, key)
    @definitions[key] = node
  else
    add_offense(location(node), message: message_for_dup(node, method_name, key))
  end
end

#found_sclass_method(node, name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 466

def found_sclass_method(node, name)
  singleton_ancestor = node.each_ancestor.find(&:sclass_type?)
  return unless singleton_ancestor

  singleton_receiver_node = singleton_ancestor.children[0]
  return unless singleton_receiver_node.send_type?

  found_method(node, "#{singleton_receiver_node.method_name}.#{name}")
end

#hash_value(node, key) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 401

def hash_value(node, key)
  node.pairs.find { |pair| pair.key.value == key }&.value
end

#humanize_scope(scope) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 419

def humanize_scope(scope)
  scope = scope.sub(
    /(?:(?<name>.*)::)#<Class:\k<name>>|#<Class:(?<name>.*)>(?:::)?/,
    '\k<name>.'
  )
  scope.end_with?('.') ? scope : "#{scope}#"
end

#index_source_location(definition) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 706

def index_source_location(definition)
  location = definition.location
  "#{smart_path(location.to_file_path)}:#{location.to_display.start_line}"
end

#indexed_declaration_definitions(namespace, name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 658

def indexed_declaration_definitions(namespace, name)
  project_index["#{namespace}##{name}()"]&.definitions.to_a
end

#indexed_definitions(owner, separator, name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 644

def indexed_definitions(owner, separator, name)
  namespace = separator == '.' ? "#{owner}::<#{owner.split('::').last}>" : owner

  if name.match?(/\A\w+=\z/)
    # Rubydex indexes `attr_writer :foo` under `foo` rather than `foo=`, so
    # writer definitions come from both the `foo=` and the `foo` declarations.
    indexed_declaration_definitions(namespace, name) +
      indexed_declaration_definitions(namespace, name.delete_suffix('='))
      .select { |definition| writer_attr_definition?(definition) }
  else
    indexed_declaration_definitions(namespace, name).grep_v(Rubydex::AttrWriterDefinition)
  end
end

#inside_condition?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 357

def inside_condition?(node)
  node.ancestors.any?(&:if_type?)
end

#intentional_cross_file_redefinition?(node, method_name, key) ⇒ Boolean (private)

The self-alias trick (alias foo foo or alias_method :foo, :foo right before a def) suppresses Ruby’s method redefinition warning, signaling an intentional redefinition of a method defined in another file. Active Support’s redefinition markers (tracked when ActiveSupportExtensionsEnabled is on) signal the same.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 515

def intentional_cross_file_redefinition?(node, method_name, key)
  @intentionally_redefined.include?(method_name) &&
    @definitions[key].source_range.source_buffer.name !=
      node.source_range.source_buffer.name
end

#location(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 536

def location(node)
  if node.any_def_type?
    node.loc.keyword.join(node.loc.name)
  else
    node.source_range
  end
end

#lookup_constant(node, const_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 568

def lookup_constant(node, const_name)
  # this method is quite imperfect and can be fooled
  # to do much better, we would need to do global analysis of the whole
  # codebase
  node.each_ancestor(:class, :module, :casgn) do |ancestor|
    namespace, mod_name = *ancestor.defined_module
    loop do
      if mod_name == const_name
        return qualified_name(ancestor.parent_module_name, namespace, mod_name)
      end

      break if namespace.nil?

      namespace, mod_name = *namespace
    end
  end
end

#message_for_dup(node, method_name, key) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 361

def message_for_dup(node, method_name, key)
  format(MSG, method: method_name, defined: source_location(@definitions[key]),
              current: source_location(node))
end

#method_alias?(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 227

def_node_matcher :method_alias?, <<~PATTERN
  (alias (sym $_name) (sym $_original_name))
PATTERN

#method_key(node, method_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 528

def method_key(node, method_name)
  if (ancestor_def = node.each_ancestor(:any_def).first)
    "#{ancestor_def.method_name}.#{method_name}"
  else
    method_name
  end
end

#named_receiver(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 461

def named_receiver(node)
  receiver = node.receiver
  receiver unless class_or_module_new_block?(receiver)
end

#on_alias(node)

[ GitHub ]

  
# 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_attr(node, attr_name, args) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 544

def on_attr(node, attr_name, args)
  case attr_name
  when :attr
    writable = args.size == 2 && args.last.true_type?
    found_attr(node, [args.first], readable: true, writable: writable)
  when :attr_reader
    found_attr(node, args, readable: true)
  when :attr_writer
    found_attr(node, args, writable: true)
  when :attr_accessor
    found_attr(node, args, readable: true, writable: true)
  end
end

#on_def(node)

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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_delegate(node, method_names) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 379

def on_delegate(node, method_names)
  name_prefix = delegate_prefix(node)

  method_names.each do |name|
    name = "#{name_prefix}_#{name}" if name_prefix

    found_instance_method(node, name)
  end
end

#on_new_investigation

[ GitHub ]

  
# 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)

Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity

[ GitHub ]

  
# 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

#qualified_name(enclosing, namespace, mod_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 586

def qualified_name(enclosing, namespace, mod_name)
  if enclosing != 'Object'
    if namespace
      "#{enclosing}::#{namespace.const_name}::#{mod_name}"
    else
      "#{enclosing}::#{mod_name}"
    end
  elsif namespace
    "#{namespace.const_name}::#{mod_name}"
  else
    mod_name
  end
end

#redefinition_marker_in_file?(path, name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 693

def redefinition_marker_in_file?(path, name)
  return false unless File.readable?(path)

  escaped = Regexp.escape(name)
  marker = /\b(?:silence_redefinition_of_method|redefine_method)\s*\(?\s*
            (?::#{escaped}|(["'])#{escaped}\1)(?![\w=!?])/x
  File.read(path).scrub.match?(marker)
end

#scope_receiver(parent, anon_block) (private)

When a Class.new block is passed as an argument to a named-receiver method call (e.g. T.cast(Class.new(Base) do …​ end, …​)), the receiver-based scope id (e.g. "T.cast") is the same for every call, causing false positives for methods defined in distinct anonymous classes. Return nil so the block falls through to the unique source-location-based scope id. Module.new blocks are excluded because they may be intentionally mixed into the same target via prepend/include/extend.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 455

def scope_receiver(parent, anon_block)
  return if class_new_block?(anon_block) && parent.call_type?

  named_receiver(parent)
end

#source_location(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 600

def source_location(node)
  range = node.source_range
  path = smart_path(range.source_buffer.name)
  "#{path}:#{range.line}"
end

#sym_name(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 288

def_node_matcher :sym_name, '(sym $_name)'

#track_intentional_redefinition(node, name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 521

def track_intentional_redefinition(node, name)
  scope = node.parent_module_name
  return unless scope

  @intentionally_redefined << "#{humanize_scope(scope)}#{name}"
end

#writer_attr_definition?(definition) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/duplicate_methods.rb', line 662

def writer_attr_definition?(definition)
  definition.is_a?(Rubydex::AttrWriterDefinition) ||
    definition.is_a?(Rubydex::AttrAccessorDefinition)
end