strict 0.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +14 -2
  3. data/Gemfile.lock +19 -2
  4. data/README.md +165 -6
  5. data/lib/strict/accessor/attributes.rb +15 -0
  6. data/lib/strict/accessor/module.rb +45 -0
  7. data/lib/strict/assignment_error.rb +25 -0
  8. data/lib/strict/attribute.rb +71 -0
  9. data/lib/strict/attributes/class.rb +17 -0
  10. data/lib/strict/attributes/coercer.rb +32 -0
  11. data/lib/strict/attributes/configuration.rb +46 -0
  12. data/lib/strict/attributes/dsl.rb +43 -0
  13. data/lib/strict/attributes/instance.rb +68 -0
  14. data/lib/strict/coercers/array.rb +22 -0
  15. data/lib/strict/coercers/hash.rb +34 -0
  16. data/lib/strict/dsl/coercible.rb +14 -0
  17. data/lib/strict/dsl/validatable.rb +28 -0
  18. data/lib/strict/implementation_does_not_conform_error.rb +88 -0
  19. data/lib/strict/initialization_error.rb +57 -0
  20. data/lib/strict/interface.rb +21 -0
  21. data/lib/strict/interfaces/instance.rb +54 -0
  22. data/lib/strict/method.rb +72 -0
  23. data/lib/strict/method_call_error.rb +72 -0
  24. data/lib/strict/method_definition_error.rb +46 -0
  25. data/lib/strict/method_return_error.rb +25 -0
  26. data/lib/strict/methods/configuration.rb +14 -0
  27. data/lib/strict/methods/dsl.rb +56 -0
  28. data/lib/strict/methods/module.rb +26 -0
  29. data/lib/strict/methods/verifiable_method.rb +158 -0
  30. data/lib/strict/object.rb +9 -0
  31. data/lib/strict/parameter.rb +63 -0
  32. data/lib/strict/reader/attributes.rb +15 -0
  33. data/lib/strict/reader/module.rb +27 -0
  34. data/lib/strict/return.rb +28 -0
  35. data/lib/strict/validators/all_of.rb +24 -0
  36. data/lib/strict/validators/any_of.rb +24 -0
  37. data/lib/strict/validators/anything.rb +20 -0
  38. data/lib/strict/validators/array_of.rb +24 -0
  39. data/lib/strict/validators/boolean.rb +20 -0
  40. data/lib/strict/validators/hash_of.rb +25 -0
  41. data/lib/strict/validators/range_of.rb +24 -0
  42. data/lib/strict/value.rb +22 -0
  43. data/lib/strict/version.rb +1 -1
  44. data/lib/strict.rb +1 -0
  45. data/strict.gemspec +41 -0
  46. metadata +97 -2
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Coercers
5
+ class Hash
6
+ attr_reader :key_coercer, :value_coercer
7
+
8
+ def initialize(key_coercer = nil, value_coercer = nil)
9
+ @key_coercer = key_coercer
10
+ @value_coercer = value_coercer
11
+ end
12
+
13
+ def call(value)
14
+ return value if value.nil? || !value.respond_to?(:to_h)
15
+
16
+ if key_coercer && value_coercer
17
+ coerce_keys_and_values(value.to_h)
18
+ elsif key_coercer
19
+ coerce_keys(value.to_h)
20
+ elsif value_coercer
21
+ coerce_values(value.to_h)
22
+ else
23
+ value.to_h
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def coerce_keys_and_values(hash) = hash.to_h { |key, value| [key_coercer.call(key), value_coercer.call(value)] }
30
+ def coerce_keys(hash) = hash.transform_keys { |key| key_coercer.call(key) }
31
+ def coerce_values(hash) = hash.transform_values { |value| value_coercer.call(value) }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Dsl
5
+ module Coercible
6
+ # rubocop:disable Naming/MethodName
7
+
8
+ def ToArray(with: nil) = ::Strict::Coercers::Array.new(with)
9
+ def ToHash(with_keys: nil, with_values: nil) = ::Strict::Coercers::Hash.new(with_keys, with_values)
10
+
11
+ # rubocop:enable Naming/MethodName
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Dsl
5
+ module Validatable
6
+ # rubocop:disable Naming/MethodName
7
+
8
+ def AllOf(*subvalidators) = ::Strict::Validators::AllOf.new(*subvalidators)
9
+ def AnyOf(*subvalidators) = ::Strict::Validators::AnyOf.new(*subvalidators)
10
+ def Anything = ::Strict::Validators::Anything.instance
11
+ def ArrayOf(element_validator) = ::Strict::Validators::ArrayOf.new(element_validator)
12
+ def Boolean = ::Strict::Validators::Boolean.instance
13
+
14
+ def HashOf(key_validator_to_value_validator)
15
+ if key_validator_to_value_validator.size != 1
16
+ raise ArgumentError, "HashOf's usage is: HashOf(KeyValidator => ValueValidator)"
17
+ end
18
+
19
+ key_validator, value_validator = key_validator_to_value_validator.first
20
+ ::Strict::Validators::HashOf.new(key_validator, value_validator)
21
+ end
22
+
23
+ def RangeOf(element_validator) = ::Strict::Validators::RangeOf.new(element_validator)
24
+
25
+ # rubocop:enable Naming/MethodName
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ class ImplementationDoesNotConformError < Error
5
+ attr_reader :interface, :receiver, :missing_methods, :invalid_method_definitions
6
+
7
+ def initialize(interface:, receiver:, missing_methods:, invalid_method_definitions:)
8
+ super(message_from(interface:, receiver:, missing_methods:, invalid_method_definitions:))
9
+
10
+ @interface = interface
11
+ @receiver = receiver
12
+ @missing_methods = missing_methods
13
+ @invalid_method_definitions = invalid_method_definitions
14
+ end
15
+
16
+ private
17
+
18
+ def message_from(interface:, receiver:, missing_methods:, invalid_method_definitions:)
19
+ details = [
20
+ missing_methods_message_from(missing_methods),
21
+ invalid_method_definitions_message_from(invalid_method_definitions)
22
+ ].compact.join("\n")
23
+
24
+ case receiver
25
+ when ::Class, ::Module
26
+ "#{receiver}'s implementation of #{interface} does not conform:\n#{details}"
27
+ else
28
+ "#{receiver.class}'s implementation of #{interface} does not conform:\n#{details}"
29
+ end
30
+ end
31
+
32
+ def missing_methods_message_from(missing_methods)
33
+ return nil unless missing_methods
34
+
35
+ details = missing_methods.map do |method_name|
36
+ " - #{method_name}"
37
+ end.join("\n")
38
+
39
+ " Some methods exposed in the interface were not defined in the implementation:\n#{details}"
40
+ end
41
+
42
+ def invalid_method_definitions_message_from(invalid_method_definitions)
43
+ return nil if invalid_method_definitions.empty?
44
+
45
+ methods_details = invalid_method_definitions.map do |name, invalid_method_definition|
46
+ method_details = [
47
+ missing_parameters_message_from(invalid_method_definition.fetch(:missing_parameters)),
48
+ additional_parameters_message_from(invalid_method_definition.fetch(:additional_parameters)),
49
+ non_keyword_parameters_message_from(invalid_method_definition.fetch(:non_keyword_parameters))
50
+ ].compact.join("\n")
51
+
52
+ " #{name}:\n#{method_details}"
53
+ end.join("\n")
54
+
55
+ " Some methods defined in the implementation did not conform to their interface:\n#{methods_details}"
56
+ end
57
+
58
+ def missing_parameters_message_from(missing_parameters)
59
+ return nil unless missing_parameters.any?
60
+
61
+ details = missing_parameters.map do |parameter_name|
62
+ " - #{parameter_name}"
63
+ end.join("\n")
64
+
65
+ " Some parameters were expected, but were not in the parameter list:\n#{details}"
66
+ end
67
+
68
+ def additional_parameters_message_from(additional_parameters)
69
+ return nil unless additional_parameters.any?
70
+
71
+ details = additional_parameters.map do |parameter_name|
72
+ " - #{parameter_name}"
73
+ end.join("\n")
74
+
75
+ " Some parameters were not expected, but were in the parameter list:\n#{details}"
76
+ end
77
+
78
+ def non_keyword_parameters_message_from(non_keyword_parameters)
79
+ return nil unless non_keyword_parameters.any?
80
+
81
+ details = non_keyword_parameters.map do |parameter_name|
82
+ " - #{parameter_name}"
83
+ end.join("\n")
84
+
85
+ " Some parameters were not keywords, but only keywords are supported:\n#{details}"
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ class InitializationError < Error
5
+ attr_reader :remaining_attributes, :invalid_attributes, :missing_attributes
6
+
7
+ def initialize(initializable_class:, remaining_attributes:, invalid_attributes:, missing_attributes:)
8
+ super(message_from(initializable_class:, remaining_attributes:, invalid_attributes:, missing_attributes:))
9
+
10
+ @remaining_attributes = remaining_attributes
11
+ @invalid_attributes = invalid_attributes
12
+ @missing_attributes = missing_attributes
13
+ end
14
+
15
+ private
16
+
17
+ def message_from(initializable_class:, remaining_attributes:, invalid_attributes:, missing_attributes:)
18
+ details = [
19
+ invalid_attributes_message_from(invalid_attributes),
20
+ missing_attributes_message_from(missing_attributes),
21
+ remaining_attributes_message_from(remaining_attributes)
22
+ ].compact.join("\n")
23
+
24
+ "Initialization of #{initializable_class} failed because:\n#{details}"
25
+ end
26
+
27
+ def invalid_attributes_message_from(invalid_attributes)
28
+ return nil unless invalid_attributes
29
+
30
+ details = invalid_attributes.map do |attribute, value|
31
+ " - #{attribute.name}: got #{value.inspect}, expected #{attribute.validator.inspect}"
32
+ end.join("\n")
33
+
34
+ " Some attributes were invalid:\n#{details}"
35
+ end
36
+
37
+ def missing_attributes_message_from(missing_attributes)
38
+ return nil unless missing_attributes
39
+
40
+ details = missing_attributes.map do |attribute_name|
41
+ " - #{attribute_name}"
42
+ end.join("\n")
43
+
44
+ " Some attributes were missing:\n#{details}"
45
+ end
46
+
47
+ def remaining_attributes_message_from(remaining_attributes)
48
+ return nil if remaining_attributes.none?
49
+
50
+ details = remaining_attributes.map do |attribute_name|
51
+ " - #{attribute_name}"
52
+ end.join("\n")
53
+
54
+ " Some attributes were provided, but not defined:\n#{details}"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Interface
5
+ def self.extended(mod)
6
+ mod.extend(Strict::Method)
7
+ mod.include(Interfaces::Instance)
8
+ end
9
+
10
+ def expose(method_name, &)
11
+ sig = sig(&)
12
+ parameter_list = sig.parameters.map { |parameter| "#{parameter.name}:" }.join(", ")
13
+
14
+ module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
15
+ def #{method_name}(#{parameter_list}, &block) # def method_name(one:, two:, three:, &block)
16
+ implementation.#{method_name}(#{parameter_list}, &block) # implementation.method_name(one:, two:, three:, &block)
17
+ end # end
18
+ RUBY
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Interfaces
5
+ module Instance
6
+ attr_reader :implementation
7
+
8
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
9
+ def initialize(implementation)
10
+ missing_methods = nil
11
+ invalid_method_definitions = Hash.new do |h, k|
12
+ h[k] = { additional_parameters: [], missing_parameters: [], non_keyword_parameters: [] }
13
+ end
14
+
15
+ self.class.strict_instance_methods.each do |method_name, strict_method|
16
+ unless implementation.respond_to?(method_name)
17
+ missing_methods ||= []
18
+ missing_methods << method_name
19
+ next
20
+ end
21
+
22
+ expected_parameters = Set.new(strict_method.parameters.map(&:name))
23
+ defined_parameters = Set.new
24
+
25
+ implementation.method(method_name).parameters.each do |kind, parameter_name|
26
+ next if kind == :block
27
+
28
+ if expected_parameters.include?(parameter_name)
29
+ defined_parameters.add(parameter_name)
30
+ invalid_method_definitions[method_name][:non_keyword_parameters] << parameter_name if kind != :keyreq
31
+ else
32
+ invalid_method_definitions[method_name][:additional_parameters] << parameter_name
33
+ end
34
+ end
35
+
36
+ missing_parameters = expected_parameters - defined_parameters
37
+ invalid_method_definitions[method_name][:missing_parameters] = missing_parameters if missing_parameters.any?
38
+ end
39
+
40
+ if missing_methods || !invalid_method_definitions.empty?
41
+ raise Strict::ImplementationDoesNotConformError.new(
42
+ interface: self.class,
43
+ receiver: implementation,
44
+ missing_methods:,
45
+ invalid_method_definitions:
46
+ )
47
+ end
48
+
49
+ @implementation = implementation
50
+ end
51
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Method
5
+ def self.extended(mod)
6
+ return if mod.singleton_class?
7
+
8
+ mod.singleton_class.extend(self)
9
+ end
10
+
11
+ def sig(&)
12
+ instance = singleton_class? ? self : singleton_class
13
+ instance.instance_variable_set(:@__strict_method_internal_last_sig_configuration, Methods::Dsl.run(&))
14
+ end
15
+
16
+ def strict_class_methods
17
+ instance = singleton_class? ? self : singleton_class
18
+ if instance.instance_variable_defined?(:@__strict_method_internal_class_methods)
19
+ instance.instance_variable_get(:@__strict_method_internal_class_methods)
20
+ else
21
+ instance.instance_variable_set(:@__strict_method_internal_class_methods, {})
22
+ end
23
+ end
24
+
25
+ def strict_instance_methods
26
+ instance = singleton_class? ? self : singleton_class
27
+ if instance.instance_variable_defined?(:@__strict_method_internal_instance_methods)
28
+ instance.instance_variable_get(:@__strict_method_internal_instance_methods)
29
+ else
30
+ instance.instance_variable_set(:@__strict_method_internal_instance_methods, {})
31
+ end
32
+ end
33
+
34
+ # rubocop:disable Metrics/MethodLength
35
+ def singleton_method_added(method_name)
36
+ super
37
+
38
+ sig = singleton_class.instance_variable_get(:@__strict_method_internal_last_sig_configuration)
39
+ singleton_class.instance_variable_set(:@__strict_method_internal_last_sig_configuration, nil)
40
+ return unless sig
41
+
42
+ verifiable_method = Methods::VerifiableMethod.new(
43
+ method: singleton_class.instance_method(method_name),
44
+ parameters: sig.parameters,
45
+ returns: sig.returns,
46
+ instance: false
47
+ )
48
+ verifiable_method.verify_definition!
49
+ strict_class_methods[method_name] = verifiable_method
50
+ singleton_class.prepend(Methods::Module.new(verifiable_method))
51
+ end
52
+
53
+ def method_added(method_name)
54
+ super
55
+
56
+ sig = singleton_class.instance_variable_get(:@__strict_method_internal_last_sig_configuration)
57
+ singleton_class.instance_variable_set(:@__strict_method_internal_last_sig_configuration, nil)
58
+ return unless sig
59
+
60
+ verifiable_method = Methods::VerifiableMethod.new(
61
+ method: instance_method(method_name),
62
+ parameters: sig.parameters,
63
+ returns: sig.returns,
64
+ instance: true
65
+ )
66
+ verifiable_method.verify_definition!
67
+ strict_instance_methods[method_name] = verifiable_method
68
+ prepend(Methods::Module.new(verifiable_method))
69
+ end
70
+ # rubocop:enable Metrics/MethodLength
71
+ end
72
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ class MethodCallError < Error
5
+ attr_reader :verifiable_method, :remaining_args, :remaining_kwargs, :invalid_parameters, :missing_parameters
6
+
7
+ def initialize(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:)
8
+ super(
9
+ message_from(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:)
10
+ )
11
+
12
+ @verifiable_method = verifiable_method
13
+ @remaining_args = remaining_args
14
+ @remaining_kwargs = remaining_kwargs
15
+ @invalid_parameters = invalid_parameters
16
+ @missing_parameters = missing_parameters
17
+ end
18
+
19
+ private
20
+
21
+ def message_from(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:)
22
+ details = [
23
+ invalid_parameters_message_from(invalid_parameters),
24
+ missing_parameters_message_from(missing_parameters),
25
+ remaining_args_message_from(remaining_args),
26
+ remaining_kwargs_message_from(remaining_kwargs)
27
+ ].compact.join("\n")
28
+
29
+ "Calling #{verifiable_method} failed because:\n#{details}"
30
+ end
31
+
32
+ def invalid_parameters_message_from(invalid_parameters)
33
+ return nil unless invalid_parameters
34
+
35
+ details = invalid_parameters.map do |parameter, value|
36
+ " - #{parameter.name}: got #{value.inspect}, expected #{parameter.validator.inspect}"
37
+ end.join("\n")
38
+
39
+ " Some arguments were invalid:\n#{details}"
40
+ end
41
+
42
+ def missing_parameters_message_from(missing_parameters)
43
+ return nil unless missing_parameters
44
+
45
+ details = missing_parameters.map do |parameter_name|
46
+ " - #{parameter_name}"
47
+ end.join("\n")
48
+
49
+ " Some arguments were missing:\n#{details}"
50
+ end
51
+
52
+ def remaining_args_message_from(remaining_args)
53
+ return nil if remaining_args.none?
54
+
55
+ details = remaining_args.map do |arg|
56
+ " - #{arg.inspect}"
57
+ end.join("\n")
58
+
59
+ " Additional positional arguments were provided, but not defined:\n#{details}"
60
+ end
61
+
62
+ def remaining_kwargs_message_from(remaining_kwargs)
63
+ return nil if remaining_kwargs.none?
64
+
65
+ details = remaining_kwargs.map do |key, value|
66
+ " - #{key}: #{value.inspect}"
67
+ end.join("\n")
68
+
69
+ " Additional keyword arguments were provided, but not defined:\n#{details}"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ class MethodDefinitionError < Error
5
+ attr_reader :verifiable_method, :missing_parameters, :additional_parameters
6
+
7
+ def initialize(verifiable_method:, missing_parameters:, additional_parameters:)
8
+ super(message_from(verifiable_method:, missing_parameters:, additional_parameters:))
9
+
10
+ @verifiable_method = verifiable_method
11
+ @missing_parameters = missing_parameters
12
+ @additional_parameters = additional_parameters
13
+ end
14
+
15
+ private
16
+
17
+ def message_from(verifiable_method:, missing_parameters:, additional_parameters:)
18
+ details = [
19
+ missing_parameters_message_from(missing_parameters),
20
+ additional_parameters_message_from(additional_parameters)
21
+ ].compact.join("\n")
22
+
23
+ "Defining #{verifiable_method} failed because:\n#{details}"
24
+ end
25
+
26
+ def missing_parameters_message_from(missing_parameters)
27
+ return nil unless missing_parameters.any?
28
+
29
+ details = missing_parameters.map do |parameter_name|
30
+ " - #{parameter_name}"
31
+ end.join("\n")
32
+
33
+ " Some parameters were in the sig, but were not in the parameter list:\n#{details}"
34
+ end
35
+
36
+ def additional_parameters_message_from(additional_parameters)
37
+ return nil unless additional_parameters.any?
38
+
39
+ details = additional_parameters.map do |parameter_name|
40
+ " - #{parameter_name}"
41
+ end.join("\n")
42
+
43
+ " Some parameters were not in the sig, but were in the parameter list:\n#{details}"
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ class MethodReturnError < Error
5
+ attr_reader :verifiable_method, :value
6
+
7
+ def initialize(verifiable_method:, value:)
8
+ super(message_from(verifiable_method:, value:))
9
+
10
+ @verifiable_method = verifiable_method
11
+ @value = value
12
+ end
13
+
14
+ private
15
+
16
+ def message_from(verifiable_method:, value:)
17
+ details = invalid_returns_message_from(verifiable_method, value)
18
+ "#{verifiable_method}'s return value was invalid because:\n#{details}"
19
+ end
20
+
21
+ def invalid_returns_message_from(verifiable_method, value)
22
+ " - got #{value.inspect}, expected #{verifiable_method.returns.validator.inspect}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Methods
5
+ class Configuration
6
+ attr_reader :parameters, :returns
7
+
8
+ def initialize(parameters:, returns:)
9
+ @parameters = parameters
10
+ @returns = returns
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Methods
5
+ class Dsl < BasicObject
6
+ class << self
7
+ def run(&)
8
+ dsl = new
9
+ dsl.instance_eval(&)
10
+ ::Strict::Methods::Configuration.new(
11
+ parameters: dsl.__strict_dsl_internal_parameters.values,
12
+ returns: dsl.__strict_dsl_internal_returns
13
+ )
14
+ end
15
+ end
16
+
17
+ include ::Strict::Dsl::Coercible
18
+ include ::Strict::Dsl::Validatable
19
+
20
+ attr_reader :__strict_dsl_internal_parameters, :__strict_dsl_internal_returns
21
+
22
+ def initialize
23
+ @__strict_dsl_internal_parameters = {}
24
+ @__strict_dsl_internal_returns = ::Strict::Return.make
25
+ end
26
+
27
+ def returns(*args, **kwargs)
28
+ self.__strict_dsl_internal_returns = ::Strict::Return.make(*args, **kwargs)
29
+ nil
30
+ end
31
+
32
+ def strict_parameter(*args, **kwargs)
33
+ parameter = ::Strict::Parameter.make(*args, **kwargs)
34
+ __strict_dsl_internal_parameters[parameter.name] = parameter
35
+ nil
36
+ end
37
+
38
+ def method_missing(name, *args, **kwargs)
39
+ if respond_to_missing?(name)
40
+ strict_parameter(name, *args, **kwargs)
41
+ else
42
+ super
43
+ end
44
+ end
45
+
46
+ def respond_to_missing?(method_name, _include_private = nil)
47
+ first_letter = method_name.to_s.each_char.first
48
+ first_letter.eql?(first_letter.downcase)
49
+ end
50
+
51
+ private
52
+
53
+ attr_writer :__strict_dsl_internal_returns
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Methods
5
+ class Module < ::Module
6
+ attr_reader :verifiable_method
7
+
8
+ def initialize(verifiable_method)
9
+ super()
10
+
11
+ @verifiable_method = verifiable_method
12
+ define_method verifiable_method.name do |*args, **kwargs, &block|
13
+ args, kwargs = verifiable_method.verify_parameters!(*args, **kwargs)
14
+
15
+ super(*args, **kwargs, &block).tap do |value|
16
+ verifiable_method.verify_returns!(value)
17
+ end
18
+ end
19
+ end
20
+
21
+ def inspect
22
+ "#<#{self.class} (#{verifiable_method.name})>"
23
+ end
24
+ end
25
+ end
26
+ end