123456789_123456789_123456789_123456789_123456789_

Module: RuboCop::Cop::IndexedMethodArity

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Included In:
Defined in: lib/rubocop/cop/mixin/indexed_method_arity.rb

Overview

Shared machinery for cops that compare a call site’s arguments against a method signature from the project index (Rubydex::Signature).

A signature is reduced to a shape - [min, max, has_keywords] where max is nil for a rest parameter - and the number of positional arguments a call passes is counted with Ruby’s semantics for trailing keyword hashes: keywords passed to a method with no keyword parameters collapse into a single positional hash.

Instance Method Summary

Instance Method Details

#arity_satisfied?(given, min, max) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 16

def arity_satisfied?(given, min, max)
  given >= min && (max.nil? || given <= max)
end

#double_splat?(hash_node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 89

def double_splat?(hash_node)
  hash_node.each_child_node.any?(&:kwsplat_type?)
end

#expected_range(min, max) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 20

def expected_range(min, max)
  return "#{min}+" if max.nil?
  return min.to_s if min == max

  "#{min}..#{max}"
end

#forwards_arguments?(arguments) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 79

def forwards_arguments?(arguments)
  arguments.any? do |argument|
    argument.type?(:splat, :forwarded_args, :forwarded_restarg, :forwarded_kwrestarg)
  end
end

#indexed_member_shape(member) (private)

The agreed [min, max, has_keywords] shape of an indexed member’s method definitions, or nil when the member is not backed by plain method definitions (attr, alias), has no signature, forwards arguments, or its definitions disagree on arity (e.g. reopened with a different one).

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 32

def indexed_member_shape(member)
  definitions = member.definitions.grep(Rubydex::MethodDefinition)
  return nil if definitions.empty?

  signature_shape(definitions.flat_map(&:signatures))
end

#keyword_hash(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 85

def keyword_hash(node)
  node if node&.hash_type? && !node.braces?
end

#keyword_parameters?(signature) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 58

def keyword_parameters?(signature)
  !signature.keyword_parameters.empty? ||
    !signature.optional_keyword_parameters.empty? ||
    !signature.rest_keyword_parameter.nil?
end

#maximum_positional(signature, min) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 52

def maximum_positional(signature, min)
  return nil if signature.rest_positional_parameter

  min + signature.optional_positional_parameters.size
end

#positional_argument_count(node, callee_has_keywords) (private)

The number of positional arguments the call passes, or nil when it cannot be determined statically (argument forwarding).

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 66

def positional_argument_count(node, callee_has_keywords)
  arguments = node.arguments
  return nil if forwards_arguments?(arguments)

  arguments = arguments[0...-1] if arguments.last&.block_pass_type?
  return arguments.size unless (keyword_hash = keyword_hash(arguments.last))
  return nil if double_splat?(keyword_hash)

  # A keyword-style hash passed to a method with no keyword parameters is
  # received as a single positional hash argument.
  callee_has_keywords ? arguments.size - 1 : arguments.size
end

#shape_for(signature) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 44

def shape_for(signature)
  return nil if signature.forward_parameter

  min = signature.positional_parameters.size + signature.post_parameters.size

  [min, maximum_positional(signature, min), keyword_parameters?(signature)]
end

#signature_shape(signatures) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/indexed_method_arity.rb', line 39

def signature_shape(signatures)
  shapes = signatures.filter_map { |signature| shape_for(signature) }.uniq
  shapes.one? ? shapes.first : nil
end