class RSpec::Support::MethodSignature

rubocop:disable Metrics/ClassLength
@private
keyword args of a given method.
Extracts info about the number of arguments and allowed/required

def arbitrary_kw_args?

def arbitrary_kw_args?
  @allows_any_kw_args
end

def arbitrary_kw_args?

def arbitrary_kw_args?
  false
end

def classify_arity(arity=@method.arity)

def classify_arity(arity=@method.arity)
  if arity < 0
    # `~` inverts the one's complement and gives us the
    # number of required args
    @min_non_kw_args = ~arity
    @max_non_kw_args = INFINITY
  else
    @min_non_kw_args = arity
    @max_non_kw_args = arity
  end
end

def classify_parameters

def classify_parameters
  optional_non_kw_args = @min_non_kw_args = 0
  @allows_any_kw_args = false
  @method.parameters.each do |(type, name)|
    case type
    # def foo(a:)
    when :keyreq  then @required_kw_args << name
    # def foo(a: 1)
    when :key     then @optional_kw_args << name
    # def foo(**kw_args)
    when :keyrest then @allows_any_kw_args = true
    # def foo(a)
    when :req     then @min_non_kw_args += 1
    # def foo(a = 1)
    when :opt     then optional_non_kw_args += 1
    # def foo(*a)
    when :rest    then optional_non_kw_args = INFINITY
    end
  end
  @max_non_kw_args = @min_non_kw_args + optional_non_kw_args
  @allowed_kw_args = @required_kw_args + @optional_kw_args
end

def classify_parameters

def classify_parameters
  super
  if (arity = @method.arity) != 0 && @method.parameters.empty?
    classify_arity(arity)
  end
end

def classify_parameters

def classify_parameters
  super
  return unless @method.arity == -1
  return unless @method.respond_to?(:owner)
  return unless @method.owner.respond_to?(:java_class)
  java_instance_methods = @method.owner.java_class.java_instance_methods
  compatible_overloads = java_instance_methods.select do |java_method|
    @method == @method.owner.instance_method(java_method.name)
  end
  if compatible_overloads.size == 1
    classify_arity(compatible_overloads.first.arity)
  end
end

def could_contain_kw_args?(args)

contain keyword arguments?
Without considering what the last arg is, could it
def could_contain_kw_args?(args)
  return false if args.count <= min_non_kw_args
  @allows_any_kw_args || @allowed_kw_args.any?
end

def could_contain_kw_args?(*)

def could_contain_kw_args?(*)
  false
end

def description

def description
  @description ||= begin
    parts = []
    unless non_kw_args_arity_description == "0"
      parts << "arity of #{non_kw_args_arity_description}"
    end
    if @optional_kw_args.any?
      parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})"
    end
    if @required_kw_args.any?
      parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})"
    end
    parts << "any additional keyword args" if @allows_any_kw_args
    parts.join(" and ")
  end
end

def description

def description
  "arity of #{non_kw_args_arity_description}"
end

def has_kw_args_in?(args)

def has_kw_args_in?(args)
  # If the last arg is a hash, depending on the signature it could be kw_args or a positional parameter.
  return false unless Hash === args.last && could_contain_kw_args?(args)
  # If the position of the hash is beyond the count of required and optional positional
  # args then it is the kwargs hash
  return true if args.count > @max_non_kw_args
  # This is the proper way to disambiguate between positional args and keywords hash
  # but relies on beginning of the call chain annotating the method with
  # ruby2_keywords, so only use it for positive feedback as without the annotation
  # this is always false
  return true if Hash.ruby2_keywords_hash?(args[-1])
  # Otherwise, the hash could be defined kw_args or an optional positional parameter
  # inspect the keys against known kwargs to determine what it is
  # Note: the problem with this is that if a user passes only invalid keyword args,
  #       rspec no longer detects is and will assign this to a positional argument
  return arbitrary_kw_args? || args.last.keys.all? { |x| @allowed_kw_args.include?(x) }
end

def has_kw_args_in?(args)

def has_kw_args_in?(args)
  # Version <= Ruby 2.7
  # If the last argument is Hash, Ruby will treat only symbol keys as keyword arguments
  # the rest will be grouped in another Hash and passed as positional argument.
  Hash === args.last &&
    could_contain_kw_args?(args) &&
    (args.last.empty? || args.last.keys.any? { |x| x.is_a?(Symbol) })
end

def has_kw_args_in?(_args)

def has_kw_args_in?(_args)
  false
end

def initialize(method)

def initialize(method)
  @method           = method
  @optional_kw_args = []
  @required_kw_args = []
  classify_parameters
end

def invalid_kw_args_from(given_kw_args)

def invalid_kw_args_from(given_kw_args)
  return [] if @allows_any_kw_args
  given_kw_args - @allowed_kw_args
end

def invalid_kw_args_from(_given_kw_args)

def invalid_kw_args_from(_given_kw_args)
  []
end

def missing_kw_args_from(given_kw_args)

def missing_kw_args_from(given_kw_args)
  @required_kw_args - given_kw_args
end

def missing_kw_args_from(_given_kw_args)

def missing_kw_args_from(_given_kw_args)
  []
end

def non_kw_args_arity_description

def non_kw_args_arity_description
  case max_non_kw_args
  when min_non_kw_args then min_non_kw_args.to_s
  when INFINITY then "#{min_non_kw_args} or more"
  else "#{min_non_kw_args} to #{max_non_kw_args}"
  end
end

def unlimited_args?

def unlimited_args?
  @max_non_kw_args == INFINITY
end

def unlimited_args?

def unlimited_args?
  false
end

def valid_non_kw_args?(positional_arg_count, optional_max_arg_count=positional_arg_count)

def valid_non_kw_args?(positional_arg_count, optional_max_arg_count=positional_arg_count)
  return true if positional_arg_count.nil?
  min_non_kw_args <= positional_arg_count &&
    optional_max_arg_count <= max_non_kw_args
end