Skip to content
This repository has been archived by the owner on Nov 30, 2024. It is now read-only.

Commit

Permalink
[bugfix] Properly detect kwargs hashes vs optional positional args
Browse files Browse the repository at this point in the history
  • Loading branch information
malcolmohare committed Feb 25, 2024
1 parent 8cbf7f9 commit ef33ab7
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 7 deletions.
33 changes: 28 additions & 5 deletions lib/rspec/support/method_signature_verifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,35 @@ def invalid_kw_args_from(given_kw_args)
given_kw_args - @allowed_kw_args
end

# 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.
# Considering the arg types, are there kw_args?
def has_kw_args_in?(args)
Hash === args.last &&
could_contain_kw_args?(args) &&
(RubyFeatures.kw_arg_separation? || args.last.empty? || args.last.keys.any? { |x| x.is_a?(Symbol) })
if RubyFeatures.kw_arg_separation?
# 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) }
else
# 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
end

# Without considering what the last arg is, could it
Expand Down
4 changes: 2 additions & 2 deletions spec/rspec/support/method_signature_verifier_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ def valid_non_kw_args?(arity)
described_class.new(signature, [nil] * arity).valid?
end

def valid?(*args)
ruby2_keywords def valid?(*args)
described_class.new(signature, args).valid?
end

def error_description
described_class.new(signature).error_message[/Expected (.*),/, 1]
end

def error_for(*args)
ruby2_keywords def error_for(*args)
described_class.new(signature, args).error_message
end

Expand Down

0 comments on commit ef33ab7

Please sign in to comment.