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

Commit ce33258

Browse files
committed
Merge pull request #250 from sleepingkingstudios/method-signature-expectation
Implement granular expectations for method signatures
2 parents c47d864 + 339f3a5 commit ce33258

File tree

2 files changed

+662
-8
lines changed

2 files changed

+662
-8
lines changed

lib/rspec/support/method_signature_verifier.rb

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module Support
88
# keyword args of a given method.
99
#
1010
# @private
11-
class MethodSignature
11+
class MethodSignature # rubocop:disable ClassLength
1212
attr_reader :min_non_kw_args, :max_non_kw_args, :optional_kw_args, :required_kw_args
1313

1414
def initialize(method)
@@ -26,9 +26,11 @@ def non_kw_args_arity_description
2626
end
2727
end
2828

29-
def valid_non_kw_args?(positional_arg_count)
29+
def valid_non_kw_args?(positional_arg_count, optional_max_arg_count=positional_arg_count)
30+
return true if positional_arg_count.nil?
31+
3032
min_non_kw_args <= positional_arg_count &&
31-
positional_arg_count <= max_non_kw_args
33+
optional_max_arg_count <= max_non_kw_args
3234
end
3335

3436
if RubyFeatures.optional_and_splat_args_supported?
@@ -74,6 +76,14 @@ def could_contain_kw_args?(args)
7476
@allows_any_kw_args || @allowed_kw_args.any?
7577
end
7678

79+
def arbitrary_kw_args?
80+
@allows_any_kw_args
81+
end
82+
83+
def unlimited_args?
84+
@max_non_kw_args == INFINITY
85+
end
86+
7787
def classify_parameters
7888
optional_non_kw_args = @min_non_kw_args = 0
7989
@allows_any_kw_args = false
@@ -119,6 +129,14 @@ def could_contain_kw_args?(*)
119129
false
120130
end
121131

132+
def arbitrary_kw_args?
133+
false
134+
end
135+
136+
def unlimited_args?
137+
false
138+
end
139+
122140
def classify_parameters
123141
arity = @method.arity
124142
if arity < 0
@@ -153,6 +171,50 @@ def classify_parameters
153171
end
154172
end
155173

174+
# Encapsulates expectations about the number of arguments and
175+
# allowed/required keyword args of a given method.
176+
#
177+
# @api private
178+
class MethodSignatureExpectation
179+
def initialize
180+
@min_count = nil
181+
@max_count = nil
182+
@keywords = []
183+
184+
@expect_unlimited_arguments = false
185+
@expect_arbitrary_keywords = false
186+
end
187+
188+
attr_reader :min_count, :max_count, :keywords
189+
190+
attr_accessor :expect_unlimited_arguments, :expect_arbitrary_keywords
191+
192+
def max_count=(number)
193+
raise ArgumentError, 'must be a non-negative integer or nil' \
194+
unless number.nil? || (number.is_a?(Integer) && number >= 0)
195+
196+
@max_count = number
197+
end
198+
199+
def min_count=(number)
200+
raise ArgumentError, 'must be a non-negative integer or nil' \
201+
unless number.nil? || (number.is_a?(Integer) && number >= 0)
202+
203+
@min_count = number
204+
end
205+
206+
def empty?
207+
@min_count.nil? &&
208+
@keywords.to_a.empty? &&
209+
!@expect_arbitrary_keywords &&
210+
!@expect_unlimited_arguments
211+
end
212+
213+
def keywords=(values)
214+
@keywords = values.to_a || []
215+
end
216+
end
217+
156218
# Deals with the slightly different semantics of block arguments.
157219
# For methods, arguments are required unless a default value is provided.
158220
# For blocks, arguments are optional, even if no default value is provided.
@@ -175,17 +237,49 @@ def classify_parameters
175237
#
176238
# @api private
177239
class MethodSignatureVerifier
178-
attr_reader :non_kw_args, :kw_args
240+
attr_reader :non_kw_args, :kw_args, :min_non_kw_args, :max_non_kw_args
179241

180242
def initialize(signature, args)
181243
@signature = signature
182244
@non_kw_args, @kw_args = split_args(*args)
245+
@min_non_kw_args = @max_non_kw_args = @non_kw_args
246+
@arbitrary_kw_args = @unlimited_args = false
247+
end
248+
249+
def with_expectation(expectation) # rubocop:disable MethodLength
250+
return self unless MethodSignatureExpectation === expectation
251+
252+
if expectation.empty?
253+
@min_non_kw_args = @max_non_kw_args = @non_kw_args = nil
254+
@kw_args = []
255+
else
256+
@min_non_kw_args = @non_kw_args = expectation.min_count || 0
257+
@max_non_kw_args = expectation.max_count || @min_non_kw_args
258+
259+
if RubyFeatures.optional_and_splat_args_supported?
260+
@unlimited_args = expectation.expect_unlimited_arguments
261+
else
262+
@unlimited_args = false
263+
end
264+
265+
if RubyFeatures.kw_args_supported?
266+
@kw_args = expectation.keywords
267+
@arbitrary_kw_args = expectation.expect_arbitrary_keywords
268+
else
269+
@kw_args = []
270+
@arbitrary_kw_args = false
271+
end
272+
end
273+
274+
self
183275
end
184276

185277
def valid?
186278
missing_kw_args.empty? &&
187279
invalid_kw_args.empty? &&
188-
valid_non_kw_args?
280+
valid_non_kw_args? &&
281+
arbitrary_kw_args? &&
282+
unlimited_args?
189283
end
190284

191285
def error_message
@@ -200,15 +294,15 @@ def error_message
200294
elsif !valid_non_kw_args?
201295
"Wrong number of arguments. Expected %s, got %s." % [
202296
@signature.non_kw_args_arity_description,
203-
non_kw_args.length
297+
non_kw_args
204298
]
205299
end
206300
end
207301

208302
private
209303

210304
def valid_non_kw_args?
211-
@signature.valid_non_kw_args?(non_kw_args.length)
305+
@signature.valid_non_kw_args?(min_non_kw_args, max_non_kw_args)
212306
end
213307

214308
def missing_kw_args
@@ -219,14 +313,22 @@ def invalid_kw_args
219313
@signature.invalid_kw_args_from(kw_args)
220314
end
221315

316+
def arbitrary_kw_args?
317+
!@arbitrary_kw_args || @signature.arbitrary_kw_args?
318+
end
319+
320+
def unlimited_args?
321+
!@unlimited_args || @signature.unlimited_args?
322+
end
323+
222324
def split_args(*args)
223325
kw_args = if @signature.has_kw_args_in?(args)
224326
args.pop.keys
225327
else
226328
[]
227329
end
228330

229-
[args, kw_args]
331+
[args.length, kw_args]
230332
end
231333
end
232334

0 commit comments

Comments
 (0)