contracts-lite 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +30 -0
  3. data/.gitignore +6 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +131 -0
  6. data/.travis.yml +22 -0
  7. data/Gemfile +8 -4
  8. data/README.md +13 -3
  9. data/Rakefile +1 -0
  10. data/TUTORIAL.md +6 -6
  11. data/bin/console +11 -0
  12. data/{script → bin}/rubocop +1 -2
  13. data/contracts.gemspec +1 -1
  14. data/docs/_config.yml +1 -0
  15. data/docs/index.md +112 -0
  16. data/lib/contracts.rb +8 -210
  17. data/lib/contracts/args_validator.rb +96 -0
  18. data/lib/contracts/builtin_contracts.rb +42 -35
  19. data/lib/contracts/contract.rb +136 -0
  20. data/lib/contracts/contract/call_with.rb +119 -0
  21. data/lib/contracts/contract/failure_callback.rb +61 -0
  22. data/lib/contracts/{validators.rb → contract/validators.rb} +9 -14
  23. data/lib/contracts/core.rb +4 -25
  24. data/lib/contracts/decorators.rb +1 -1
  25. data/lib/contracts/engine/base.rb +4 -5
  26. data/lib/contracts/engine/eigenclass.rb +3 -4
  27. data/lib/contracts/engine/target.rb +1 -3
  28. data/lib/contracts/error_formatter.rb +6 -6
  29. data/lib/contracts/errors.rb +2 -2
  30. data/lib/contracts/formatters.rb +20 -21
  31. data/lib/contracts/invariants.rb +10 -6
  32. data/lib/contracts/method_handler.rb +26 -31
  33. data/lib/contracts/method_reference.rb +13 -14
  34. data/lib/contracts/support.rb +2 -16
  35. data/lib/contracts/version.rb +1 -1
  36. data/spec/contracts_spec.rb +25 -6
  37. data/spec/error_formatter_spec.rb +0 -1
  38. data/spec/fixtures/fixtures.rb +5 -5
  39. data/spec/ruby_version_specific/contracts_spec_1.9.rb +17 -1
  40. data/spec/ruby_version_specific/contracts_spec_2.0.rb +1 -1
  41. data/spec/ruby_version_specific/contracts_spec_2.1.rb +1 -1
  42. data/spec/spec_helper.rb +17 -68
  43. metadata +15 -8
  44. data/TODO.markdown +0 -6
  45. data/lib/contracts/call_with.rb +0 -97
@@ -2,8 +2,6 @@ module Contracts
2
2
  module Engine
3
3
  # Represents class in question
4
4
  class Target
5
- # Creates new instance of Target
6
- #
7
5
  # @param [Class] target - class in question
8
6
  def initialize(target)
9
7
  @target = target
@@ -54,7 +52,7 @@ module Contracts
54
52
  self.class.new(eigenclass).apply(Eigenclass)
55
53
  eigenclass.extend(MethodDecorators)
56
54
  # FIXME; this should detect what user uses `include Contracts` or
57
- # `include Contracts;;Core`
55
+ # `include Contracts::Core`
58
56
  eigenclass.send(:include, Contracts)
59
57
  end
60
58
 
@@ -1,5 +1,8 @@
1
1
  module Contracts
2
2
  class ErrorFormatters
3
+ # # Given a hash, prints out a failure message.
4
+ # # This function is used by the default #failure_callback method
5
+ # # and uses the hash passed into the failure_callback method.
3
6
  def self.failure_msg(data)
4
7
  class_for(data).new(data).message
5
8
  end
@@ -55,7 +58,7 @@ module Contracts
55
58
  class KeywordArgsErrorFormatter < DefaultErrorFormatter
56
59
  def message
57
60
  s = []
58
- s << "#{header}"
61
+ s << header.to_s
59
62
  s << " Expected: #{expected}"
60
63
  s << " Actual: #{data[:arg].inspect}"
61
64
  s << " Missing Contract: #{missing_contract_info}" unless missing_contract_info.empty?
@@ -101,11 +104,8 @@ module Contracts
101
104
  end
102
105
 
103
106
  def check_contract(contract, value)
104
- if contract.respond_to?(:valid?)
105
- contract.valid?(value)
106
- else
107
- value.is_a?(contract)
108
- end
107
+ return contract.valid?(value) if contract.respond_to?(:valid?)
108
+ value.is_a?(contract)
109
109
  rescue
110
110
  false
111
111
  end
@@ -59,10 +59,10 @@ module Contracts
59
59
  # you can use `Contract` definition here now
60
60
  end
61
61
  end
62
- ```}
62
+ ```}.freeze
63
63
 
64
64
  attr_reader :message
65
- alias_method :to_s, :message
65
+ alias to_s message
66
66
 
67
67
  def initialize(message = DEFAULT_MESSAGE)
68
68
  @message = message
@@ -6,18 +6,15 @@ module Contracts
6
6
  # @param full [Boolean] if false only unique `to_s` values will be output,
7
7
  # non unique values become empty string.
8
8
  def initialize(contract, full = true)
9
- @contract, @full = contract, full
9
+ @contract = contract
10
+ @full = full
10
11
  end
11
12
 
12
13
  # Formats any type of Contract.
13
14
  def contract(contract = @contract)
14
- if contract.is_a?(Hash)
15
- hash_contract(contract)
16
- elsif contract.is_a?(Array)
17
- array_contract(contract)
18
- else
19
- InspectWrapper.create(contract, @full)
20
- end
15
+ return hash_contract(contract) if contract.is_a?(Hash)
16
+ return array_contract(contract) if contract.is_a?(Array)
17
+ InspectWrapper.create(contract, @full)
21
18
  end
22
19
 
23
20
  # Formats Hash contracts.
@@ -41,17 +38,19 @@ module Contracts
41
38
  # InspectWrapper is a factory, will never be an instance
42
39
  # @return [ClassInspectWrapper, ObjectInspectWrapper]
43
40
  def self.create(value, full = true)
44
- if value.class == Class
45
- ClassInspectWrapper
46
- else
47
- ObjectInspectWrapper
48
- end.new(value, full)
41
+ inspector_klass(value).new(value, full)
42
+ end
43
+
44
+ def self.inspector_klass(value)
45
+ return ClassInspectWrapper if value.class == Class
46
+ ObjectInspectWrapper
49
47
  end
50
48
 
51
49
  # @param full [Boolean] if false only unique `to_s` values will be output,
52
50
  # non unique values become empty string.
53
51
  def initialize(value, full)
54
- @value, @full = value, full
52
+ @value = value
53
+ @full = full
55
54
  end
56
55
 
57
56
  # Inspect different types of contract values.
@@ -61,17 +60,17 @@ module Contracts
61
60
  # Primitive values e.g. 42, true, nil will be left alone.
62
61
  def inspect
63
62
  return "" unless full?
64
- return @value.inspect if empty_val?
65
- return @value.to_s if plain?
63
+ return @value.inspect if empty_val?
64
+ return @value.to_s if plain?
66
65
  return delim(@value.to_s) if useful_to_s?
67
66
  useful_inspect
68
67
  end
69
68
 
70
69
  def delim(value)
71
- @full ? "(#{value})" : "#{value}"
70
+ @full ? "(#{value})" : value.to_s
72
71
  end
73
72
 
74
- # Eliminates eronious quotes in output that plain inspect includes.
73
+ # Eliminates erroneous quotes in output that plain inspect includes.
75
74
  def to_s
76
75
  inspect
77
76
  end
@@ -84,17 +83,17 @@ module Contracts
84
83
 
85
84
  def full?
86
85
  @full ||
87
- @value.is_a?(Hash) || @value.is_a?(Array) ||
86
+ @value.is_a?(Hash) ||
87
+ @value.is_a?(Array) ||
88
88
  (!plain? && useful_to_s?)
89
89
  end
90
90
 
91
+ # Not a type of contract that can have a custom to_s defined
91
92
  def plain?
92
- # Not a type of contract that can have a custom to_s defined
93
93
  !@value.is_a?(Builtin::CallableClass) && @value.class != Class
94
94
  end
95
95
 
96
96
  def useful_to_s?
97
- # Useless to_s value or no custom to_s behavious defined
98
97
  !empty_to_s? && custom_to_s?
99
98
  end
100
99
 
@@ -36,7 +36,9 @@ module Contracts
36
36
 
37
37
  class Invariant
38
38
  def initialize(klass, name, &condition)
39
- @klass, @name, @condition = klass, name, condition
39
+ @klass = klass
40
+ @name = name
41
+ @condition = condition
40
42
  end
41
43
 
42
44
  def expected
@@ -46,14 +48,16 @@ module Contracts
46
48
  def check_on(target, method)
47
49
  return if target.instance_eval(&@condition)
48
50
 
49
- self.class.failure_callback(:expected => expected,
50
- :actual => false,
51
- :target => target,
52
- :method => method)
51
+ self.class.failure_callback(
52
+ :expected => expected,
53
+ :actual => false,
54
+ :target => target,
55
+ :method => method
56
+ )
53
57
  end
54
58
 
55
59
  def self.failure_callback(data)
56
- fail InvariantError, failure_msg(data)
60
+ raise InvariantError, failure_msg(data)
57
61
  end
58
62
 
59
63
  def self.failure_msg(data)
@@ -3,14 +3,14 @@ module Contracts
3
3
  # Represents single such method
4
4
  class MethodHandler
5
5
  METHOD_REFERENCE_FACTORY = {
6
- :class_methods => SingletonMethodReference,
6
+ :class_methods => SingletonMethodReference,
7
7
  :instance_methods => MethodReference
8
- }
8
+ }.freeze
9
9
 
10
10
  RAW_METHOD_STRATEGY = {
11
- :class_methods => lambda { |target, name| target.method(name) },
11
+ :class_methods => lambda { |target, name| target.method(name) },
12
12
  :instance_methods => lambda { |target, name| target.instance_method(name) }
13
- }
13
+ }.freeze
14
14
 
15
15
  # Creates new instance of MethodHandler
16
16
  #
@@ -18,9 +18,9 @@ module Contracts
18
18
  # @param [Bool] is_class_method
19
19
  # @param [Class] target - class that method got added to
20
20
  def initialize(method_name, is_class_method, target)
21
- @method_name = method_name
21
+ @method_name = method_name
22
22
  @is_class_method = is_class_method
23
- @target = target
23
+ @target = target
24
24
  end
25
25
 
26
26
  # Handles method addition
@@ -58,7 +58,7 @@ module Contracts
58
58
  end
59
59
  # _method_type is required for assigning it to local variable with
60
60
  # the same name. See: #redefine_method
61
- alias_method :_method_type, :method_type
61
+ alias _method_type method_type
62
62
 
63
63
  def method_reference
64
64
  @_method_reference ||= METHOD_REFERENCE_FACTORY[method_type].new(method_name, raw_method)
@@ -102,8 +102,8 @@ module Contracts
102
102
  return if ignore_decorators?
103
103
 
104
104
  # Those are required for instance_eval to be able to refer them
105
- name = method_name
106
- method_type = _method_type
105
+ name = method_name
106
+ method_type = _method_type
107
107
  current_engine = engine
108
108
 
109
109
  # We are gonna redefine original method here
@@ -113,11 +113,15 @@ module Contracts
113
113
  # If we weren't able to find any ancestor that has decorated methods
114
114
  # FIXME : this looks like untested code (commenting it out doesn't make specs red)
115
115
  unless engine
116
- fail "Couldn't find decorator for method " + self.class.name + ":#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
116
+ raise "Couldn't find decorator for method " + self.class.name + ":#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
117
117
  end
118
118
 
119
119
  # Fetch decorated methods out of the contracts engine
120
120
  decorated_methods = engine.decorated_methods_for(method_type, name)
121
+ expected_error = decorated_methods[0].failure_exception
122
+ error_to_return = nil
123
+ result = nil
124
+ success = nil
121
125
 
122
126
  # This adds support for overloading methods. Here we go
123
127
  # through each method and call it with the arguments.
@@ -125,38 +129,29 @@ module Contracts
125
129
  # function. Otherwise we return the result.
126
130
  # If we run out of functions, we raise the last error, but
127
131
  # convert it to_contract_error.
128
- success = false
129
- i = 0
130
- result = nil
131
- expected_error = decorated_methods[0].failure_exception
132
-
133
- until success
134
- decorated_method = decorated_methods[i]
135
- i += 1
132
+ decorated_methods.any? do |decorated_method|
136
133
  begin
137
- success = true
138
134
  result = decorated_method.call_with(self, *args, &blk)
135
+ success = true
139
136
  rescue expected_error => error
140
- success = false
141
- unless decorated_methods[i]
142
- begin
143
- ::Contract.failure_callback(error.data, false)
144
- rescue expected_error => final_error
145
- raise final_error.to_contract_error
146
- end
147
- end
137
+ error_to_return = error
138
+ nil
148
139
  end
149
140
  end
141
+ return result if success
150
142
 
151
- # Return the result of successfully called method
152
- result
143
+ begin
144
+ ::Contract.failure_callback(error_to_return.data, false)
145
+ rescue expected_error
146
+ raise error_to_return.to_contract_error
147
+ end
153
148
  end
154
149
  end
155
150
 
156
151
  def validate_decorators!
157
152
  return if decorators.size == 1
158
153
 
159
- fail %{
154
+ raise %{
160
155
  Oops, it looks like method '#{name}' has multiple contracts:
161
156
  #{decorators.map { |x| x[1][0].inspect }.join("\n")}
162
157
 
@@ -181,7 +176,7 @@ https://github.com/egonSchiele/contracts.ruby/issues
181
176
 
182
177
  return if matched.empty?
183
178
 
184
- fail ContractError.new(%{
179
+ raise ContractError.new(%{
185
180
  It looks like you are trying to use pattern-matching, but
186
181
  multiple definitions for function '#{method_name}' have the same
187
182
  contract for input parameters:
@@ -7,21 +7,20 @@ module Contracts
7
7
  # name - name of the method
8
8
  # method - method object
9
9
  def initialize(name, method)
10
- @name = name
10
+ @name = name
11
11
  @method = method
12
12
  end
13
13
 
14
- # Returns method_position, delegates to Support.method_position
15
14
  def method_position
16
15
  Support.method_position(@method)
17
16
  end
18
17
 
19
18
  # Makes a method re-definition in proper way
20
19
  def make_definition(this, &blk)
21
- is_private = private?(this)
20
+ is_private = private?(this)
22
21
  is_protected = protected?(this)
23
22
  alias_target(this).send(:define_method, name, &blk)
24
- make_private(this) if is_private
23
+ make_private(this) if is_private
25
24
  make_protected(this) if is_protected
26
25
  end
27
26
 
@@ -45,18 +44,18 @@ module Contracts
45
44
 
46
45
  private
47
46
 
48
- # Makes a method private
49
- def make_private(this)
50
- original_name = name
51
- alias_target(this).class_eval { private original_name }
52
- end
53
-
54
47
  def private?(this)
55
- this.private_instance_methods.map(&:to_sym).include?(name)
48
+ this.private_instance_methods.include?(name)
56
49
  end
57
50
 
58
51
  def protected?(this)
59
- this.protected_instance_methods.map(&:to_sym).include?(name)
52
+ this.protected_instance_methods.include?(name)
53
+ end
54
+
55
+ # Makes a method private
56
+ def make_private(this)
57
+ original_name = name
58
+ alias_target(this).class_eval { private original_name }
60
59
  end
61
60
 
62
61
  # Makes a method protected
@@ -85,11 +84,11 @@ module Contracts
85
84
  private
86
85
 
87
86
  def private?(this)
88
- this.private_methods.map(&:to_sym).include?(name)
87
+ this.private_methods.include?(name)
89
88
  end
90
89
 
91
90
  def protected?(this)
92
- this.protected_methods.map(&:to_sym).include?(name)
91
+ this.protected_methods.include?(name)
93
92
  end
94
93
 
95
94
  # Return alias target for singleton methods.
@@ -3,17 +3,8 @@ module Contracts
3
3
  class << self
4
4
  def method_position(method)
5
5
  return method.method_position if method.is_a?(MethodReference)
6
-
7
- if RUBY_VERSION =~ /^1\.8/
8
- if method.respond_to?(:__file__)
9
- method.__file__ + ":" + method.__line__.to_s
10
- else
11
- method.inspect
12
- end
13
- else
14
- file, line = method.source_location
15
- file + ":" + line.to_s
16
- end
6
+ file, line = method.source_location
7
+ "#{file}:#{line}"
17
8
  end
18
9
 
19
10
  def method_name(method)
@@ -33,11 +24,6 @@ module Contracts
33
24
  contract.object_id
34
25
  end
35
26
 
36
- def eigenclass_hierarchy_supported?
37
- return false if RUBY_PLATFORM == "java" && RUBY_VERSION.to_f < 2.0
38
- RUBY_VERSION.to_f > 1.8
39
- end
40
-
41
27
  def eigenclass_of(target)
42
28
  class << target; self; end
43
29
  end
@@ -1,3 +1,3 @@
1
1
  module Contracts
2
- VERSION = "0.14.0"
2
+ VERSION = "0.15.0".freeze
3
3
  end
@@ -7,12 +7,23 @@ RSpec.describe "Contracts:" do
7
7
  it "should fail for insufficient arguments" do
8
8
  expect do
9
9
  @o.hello
10
- end.to raise_error
10
+ end.to raise_error(ArgumentError)
11
11
  end
12
12
 
13
13
  it "should fail for insufficient contracts" do
14
14
  expect { @o.bad_double(2) }.to raise_error(ContractError)
15
15
  end
16
+
17
+ it "requires last argument to be a hash for more than one contracts" do
18
+ expect{
19
+ Class.new(GenericExample) do
20
+ Contract C::Num, C::Num
21
+ def no_args_bad_contract(num)
22
+ 1
23
+ end
24
+ end
25
+ }.to raise_error(RuntimeError, Regexp.new("A contract should be written as"))
26
+ end
16
27
  end
17
28
 
18
29
  describe "contracts for functions with no arguments" do
@@ -32,7 +43,7 @@ RSpec.describe "Contracts:" do
32
43
  1
33
44
  end
34
45
  end
35
- end.to raise_error
46
+ end.to raise_error(NameError)
36
47
  end
37
48
  end
38
49
 
@@ -462,9 +473,17 @@ RSpec.describe "Contracts:" do
462
473
  @o.with_partial_sums(1, 2, 3)
463
474
  end.to raise_error(ContractError, /Actual: nil/)
464
475
 
465
- expect do
466
- @o.with_partial_sums(1, 2, 3, lambda { |x| x })
467
- end.to raise_error(ContractError, /Actual: nil/)
476
+ to_call = lambda {
477
+ expect do
478
+ # because lambda is not a valid &block, it is considered to belong to the splat argument, and raises for num contract!
479
+ @o.with_partial_sums(1, 2, 3, lambda { |x| x })
480
+ end
481
+ }
482
+ to_call.call.to raise_error(ContractError, /Contract violation for argument 4 of 5/)
483
+ to_call.call.to raise_error(ContractError, /Actual: #<Proc/)
484
+ to_call.call.to raise_error(ContractError, Regexp.new("Expected: \\(SplatArgs\\[Contracts::Builtin::Num\\]\\)"))
485
+
486
+
468
487
  end
469
488
 
470
489
  context "when block has Func contract" do
@@ -696,7 +715,7 @@ RSpec.describe "Contracts:" do
696
715
  it "should apply the contract to an inherited method" do
697
716
  c = Child.new
698
717
  expect { c.double(2) }.to_not raise_error
699
- expect { c.double("asd") }.to raise_error
718
+ expect { c.double("asd") }.to raise_error(ParamContractError)
700
719
  end
701
720
  end
702
721