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.

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

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

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.

.builtin?

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

Cops (other than builtin) are encouraged to implement this.

.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

::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::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,
#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_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #range_for_original, #range_from_node_or_range, #reset_investigation, #use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

.new(config = nil, options = nil) ⇒ DuplicateMethods

[ GitHub ]

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

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

Instance Method Details

#alias_method?(node)

[ GitHub ]

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

def_node_matcher :alias_method?, <<~PATTERN
  (send nil? :alias_method (sym $_name) _)
PATTERN

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

[ GitHub ]

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

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

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

#check_self_receiver(node, name) (private)

[ GitHub ]

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

def check_self_receiver(node, name)
  enclosing = node.parent_module_name
  return unless enclosing

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

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

[ GitHub ]

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

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 136

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

  # Humanize the scope
  scope = scope.sub(
    /(?:(?<name>.*)::)#<Class:\k<name>>|#<Class:(?<name>.*)>(?:::)?/,
    '\k<name>.'
  )
  scope << '#' unless scope.end_with?('.')

  found_method(node, "#{scope}#{name}")
end

#found_method(node, method_name) (private)

[ GitHub ]

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

def found_method(node, method_name)
  key = method_key(node, method_name)
  scope = node.each_ancestor(:rescue, :ensure).first&.type

  if @definitions.key?(key)
    if scope && !@scopes[scope].include?(key)
      @definitions[key] = node
      @scopes[scope] << key
      return
    end

    message = message_for_dup(node, method_name, key)

    add_offense(location(node), message: message)
  else
    @definitions[key] = node
  end
end

#found_sclass_method(node, name) (private)

[ GitHub ]

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

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

#location(node) (private)

[ GitHub ]

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

def location(node)
  if DEF_TYPES.include?(node.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 218

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 131

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 85

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

#method_key(node, method_name) (private)

[ GitHub ]

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

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

#on_alias(node)

[ GitHub ]

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

def on_alias(node)
  return unless (name = method_alias?(node))
  return if node.ancestors.any?(&:if_type?)
  return if possible_dsl?(node)

  found_instance_method(node, name)
end

#on_attr(node, attr_name, args) (private)

[ GitHub ]

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

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 63

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?)
  return if possible_dsl?(node)

  found_instance_method(node, node.method_name)
end

#on_defs(node)

[ GitHub ]

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

def on_defs(node)
  return if node.each_ancestor.any?(&:if_type?)
  return if possible_dsl?(node)

  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_send(node)

[ GitHub ]

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

def on_send(node)
  if (name = alias_method?(node))
    return if node.ancestors.any?(&:if_type?)
    return if possible_dsl?(node)

    found_instance_method(node, name)
  elsif (attr = node.attribute_accessor?)
    on_attr(node, *attr)
  end
end

#possible_dsl?(node) ⇒ Boolean (private)

[ GitHub ]

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

def possible_dsl?(node)
  # DSL methods may evaluate a block in the context of a newly created
  # class or module
  # Assume that if a method definition is inside any block call which
  # we can't identify, it could be a DSL
  node.each_ancestor(:block).any? do |ancestor|
    !ancestor.method?(:class_eval) && !ancestor.class_constructor?
  end
end

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

[ GitHub ]

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

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

#source_location(node) (private)

[ GitHub ]

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

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 103

def_node_matcher :sym_name, '(sym $_name)'