From 3d093381e200a203ef62d4d68617ff54a74c12b3 Mon Sep 17 00:00:00 2001 From: Roman Heinrich Date: Thu, 22 Dec 2016 16:29:52 +0100 Subject: [PATCH 01/86] make specs less noisy to read by using declarative expectations (#245) * make specs less noisy to read by using declarative expectations problem: - specs are a bit hard to read solution: - use `fails` / `passes` methods to indicate desired expectation - specs become more declarative and descriptive --- spec/builtin_contracts_spec.rb | 226 ++++++++++++++++----------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/spec/builtin_contracts_spec.rb b/spec/builtin_contracts_spec.rb index c3580cd..be718ad 100644 --- a/spec/builtin_contracts_spec.rb +++ b/spec/builtin_contracts_spec.rb @@ -3,296 +3,304 @@ @o = GenericExample.new end + def fails(&some) + expect { some.call }.to raise_error(ContractError) + end + + def passes(&some) + expect { some.call }.to_not raise_error + end + describe "DescendantOf:" do it "should pass for Array" do - expect { @o.enumerable_descendant_test(Array) }.to_not raise_error + passes { @o.enumerable_descendant_test(Array) } end it "should pass for a hash" do - expect { @o.enumerable_descendant_test(Hash) }.to_not raise_error + passes { @o.enumerable_descendant_test(Hash) } end it "should fail for a number class" do - expect { @o.enumerable_descendant_test(Integer) }.to raise_error(ContractError) + fails { @o.enumerable_descendant_test(Integer) } end it "should fail for a non-class" do - expect { @o.enumerable_descendant_test(1) }.to raise_error(ContractError) + fails { @o.enumerable_descendant_test(1) } end end describe "Num:" do it "should pass for Fixnums" do - expect { @o.double(2) }.to_not raise_error + passes { @o.double(2) } end it "should pass for Floats" do - expect { @o.double(2.2) }.to_not raise_error + passes { @o.double(2.2) } end it "should fail for nil and other data types" do - expect { @o.double(nil) }.to raise_error(ContractError) - expect { @o.double(:x) }.to raise_error(ContractError) - expect { @o.double("x") }.to raise_error(ContractError) - expect { @o.double(/x/) }.to raise_error(ContractError) + fails { @o.double(nil) } + fails { @o.double(:x) } + fails { @o.double("x") } + fails { @o.double(/x/) } end end describe "Pos:" do it "should pass for positive numbers" do - expect { @o.pos_test(1) }.to_not raise_error - expect { @o.pos_test(1.6) }.to_not raise_error + passes { @o.pos_test(1) } + passes { @o.pos_test(1.6) } end it "should fail for 0" do - expect { @o.pos_test(0) }.to raise_error(ContractError) + fails { @o.pos_test(0) } end it "should fail for negative numbers" do - expect { @o.pos_test(-1) }.to raise_error(ContractError) - expect { @o.pos_test(-1.6) }.to raise_error(ContractError) + fails { @o.pos_test(-1) } + fails { @o.pos_test(-1.6) } end it "should fail for nil and other data types" do - expect { @o.pos_test(nil) }.to raise_error(ContractError) - expect { @o.pos_test(:x) }.to raise_error(ContractError) - expect { @o.pos_test("x") }.to raise_error(ContractError) - expect { @o.pos_test(/x/) }.to raise_error(ContractError) + fails { @o.pos_test(nil) } + fails { @o.pos_test(:x) } + fails { @o.pos_test("x") } + fails { @o.pos_test(/x/) } end end describe "Neg:" do it "should pass for negative numbers" do - expect { @o.neg_test(-1) }.to_not raise_error - expect { @o.neg_test(-1.6) }.to_not raise_error + passes { @o.neg_test(-1) } + passes { @o.neg_test(-1.6) } end it "should fail for 0" do - expect { @o.neg_test(0) }.to raise_error(ContractError) + fails { @o.neg_test(0) } end it "should fail for positive numbers" do - expect { @o.neg_test(1) }.to raise_error(ContractError) - expect { @o.neg_test(1.6) }.to raise_error(ContractError) + fails { @o.neg_test(1) } + fails { @o.neg_test(1.6) } end it "should fail for nil and other data types" do - expect { @o.neg_test(nil) }.to raise_error(ContractError) - expect { @o.neg_test(:x) }.to raise_error(ContractError) - expect { @o.neg_test("x") }.to raise_error(ContractError) - expect { @o.neg_test(/x/) }.to raise_error(ContractError) + fails { @o.neg_test(nil) } + fails { @o.neg_test(:x) } + fails { @o.neg_test("x") } + fails { @o.neg_test(/x/) } end end describe "Nat:" do it "should pass for 0" do - expect { @o.nat_test(0) }.to_not raise_error + passes { @o.nat_test(0) } end it "should pass for positive whole numbers" do - expect { @o.nat_test(1) }.to_not raise_error + passes { @o.nat_test(1) } end it "should fail for positive non-whole numbers" do - expect { @o.nat_test(1.5) }.to raise_error(ContractError) + fails { @o.nat_test(1.5) } end it "should fail for negative numbers" do - expect { @o.nat_test(-1) }.to raise_error(ContractError) - expect { @o.nat_test(-1.6) }.to raise_error(ContractError) + fails { @o.nat_test(-1) } + fails { @o.nat_test(-1.6) } end it "should fail for nil and other data types" do - expect { @o.nat_test(nil) }.to raise_error(ContractError) - expect { @o.nat_test(:x) }.to raise_error(ContractError) - expect { @o.nat_test("x") }.to raise_error(ContractError) - expect { @o.nat_test(/x/) }.to raise_error(ContractError) + fails { @o.nat_test(nil) } + fails { @o.nat_test(:x) } + fails { @o.nat_test("x") } + fails { @o.nat_test(/x/) } end end describe "Any:" do it "should pass for numbers" do - expect { @o.show(1) }.to_not raise_error + passes { @o.show(1) } end it "should pass for strings" do - expect { @o.show("bad") }.to_not raise_error + passes { @o.show("bad") } end it "should pass for procs" do - expect { @o.show(lambda {}) }.to_not raise_error + passes { @o.show(lambda {}) } end it "should pass for nil" do - expect { @o.show(nil) }.to_not raise_error + passes { @o.show(nil) } end end describe "None:" do it "should fail for numbers" do - expect { @o.fail_all(1) }.to raise_error(ContractError) + fails { @o.fail_all(1) } end it "should fail for strings" do - expect { @o.fail_all("bad") }.to raise_error(ContractError) + fails { @o.fail_all("bad") } end it "should fail for procs" do - expect { @o.fail_all(lambda {}) }.to raise_error(ContractError) + fails { @o.fail_all(lambda {}) } end it "should fail for nil" do - expect { @o.fail_all(nil) }.to raise_error(ContractError) + fails { @o.fail_all(nil) } end end describe "Or:" do it "should pass for nums" do - expect { @o.num_or_string(1) }.to_not raise_error + passes { @o.num_or_string(1) } end it "should pass for strings" do - expect { @o.num_or_string("bad") }.to_not raise_error + passes { @o.num_or_string("bad") } end it "should fail for nil" do - expect { @o.num_or_string(nil) }.to raise_error(ContractError) + fails { @o.num_or_string(nil) } end end describe "Xor:" do it "should pass for an object with a method :good" do - expect { @o.xor_test(A.new) }.to_not raise_error + passes { @o.xor_test(A.new) } end it "should pass for an object with a method :bad" do - expect { @o.xor_test(B.new) }.to_not raise_error + passes { @o.xor_test(B.new) } end it "should fail for an object with neither method" do - expect { @o.xor_test(1) }.to raise_error(ContractError) + fails { @o.xor_test(1) } end it "should fail for an object with both methods :good and :bad" do - expect { @o.xor_test(F.new) }.to raise_error(ContractError) + fails { @o.xor_test(F.new) } end end describe "And:" do it "should pass for an object of class A that has a method :good" do - expect { @o.and_test(A.new) }.to_not raise_error + passes { @o.and_test(A.new) } end it "should fail for an object that has a method :good but isn't of class A" do - expect { @o.and_test(F.new) }.to raise_error(ContractError) + fails { @o.and_test(F.new) } end end describe "Enum:" do it "should pass for an object that is included" do - expect { @o.enum_test(:a) }.to_not raise_error + passes { @o.enum_test(:a) } end it "should fail for an object that is not included" do - expect { @o.enum_test(:z) }.to raise_error(ContractError) + fails { @o.enum_test(:z) } end end describe "RespondTo:" do it "should pass for an object that responds to :good" do - expect { @o.responds_test(A.new) }.to_not raise_error + passes { @o.responds_test(A.new) } end it "should fail for an object that doesn't respond to :good" do - expect { @o.responds_test(B.new) }.to raise_error(ContractError) + fails { @o.responds_test(B.new) } end end describe "Send:" do it "should pass for an object that returns true for method :good" do - expect { @o.send_test(A.new) }.to_not raise_error + passes { @o.send_test(A.new) } end it "should fail for an object that returns false for method :good" do - expect { @o.send_test(F.new) }.to raise_error(ContractError) + fails { @o.send_test(F.new) } end end describe "Exactly:" do it "should pass for an object that is exactly a Parent" do - expect { @o.exactly_test(Parent.new) }.to_not raise_error + passes { @o.exactly_test(Parent.new) } end it "should fail for an object that inherits from Parent" do - expect { @o.exactly_test(Child.new) }.to raise_error(ContractError) + fails { @o.exactly_test(Child.new) } end it "should fail for an object that is not related to Parent at all" do - expect { @o.exactly_test(A.new) }.to raise_error(ContractError) + fails { @o.exactly_test(A.new) } end end describe "Eq:" do it "should pass for a class" do - expect { @o.eq_class_test(Foo) } + passes { @o.eq_class_test(Foo) } end it "should pass for a module" do - expect { @o.eq_module_test(Bar) } + passes { @o.eq_module_test(Bar) } end it "should pass for other values" do - expect { @o.eq_value_test(Baz) } + passes { @o.eq_value_test(Baz) } end it "should fail when not equal" do - expect { @o.eq_class_test(Bar) }.to raise_error(ContractError) + fails { @o.eq_class_test(Bar) } end it "should fail when given instance of class" do - expect { @o.eq_class_test(Foo.new) }.to raise_error(ContractError) + fails { @o.eq_class_test(Foo.new) } end end describe "Not:" do it "should pass for an argument that isn't nil" do - expect { @o.not_nil(1) }.to_not raise_error + passes { @o.not_nil(1) } end it "should fail for nil" do - expect { @o.not_nil(nil) }.to raise_error(ContractError) + fails { @o.not_nil(nil) } end end describe "ArrayOf:" do it "should pass for an array of nums" do - expect { @o.product([1, 2, 3]) }.to_not raise_error + passes { @o.product([1, 2, 3]) } end it "should fail for an array with one non-num" do - expect { @o.product([1, 2, 3, "bad"]) }.to raise_error(ContractError) + fails { @o.product([1, 2, 3, "bad"]) } end it "should fail for a non-array" do - expect { @o.product(1) }.to raise_error(ContractError) + fails { @o.product(1) } end end describe "RangeOf:" do require "date" it "should pass for a range of nums" do - expect { @o.first_in_range_num(3..10) }.to_not raise_error + passes { @o.first_in_range_num(3..10) } end it "should pass for a range of dates" do d1 = Date.today d2 = d1 + 18 - expect { @o.first_in_range_date(d1..d2) }.to_not raise_error + passes { @o.first_in_range_date(d1..d2) } end it "should fail for a non-range" do - expect { @o.first_in_range_num("foo") }.to raise_error(ContractError) - expect { @o.first_in_range_num(:foo) }.to raise_error(ContractError) - expect { @o.first_in_range_num(5) }.to raise_error(ContractError) - expect { @o.first_in_range_num(nil) }.to raise_error(ContractError) + fails { @o.first_in_range_num("foo") } + fails { @o.first_in_range_num(:foo) } + fails { @o.first_in_range_num(5) } + fails { @o.first_in_range_num(nil) } end it "should fail for a range with incorrect data type" do - expect { @o.first_in_range_num("a".."z") }.to raise_error(ContractError) + fails { @o.first_in_range_num("a".."z") } end it "should fail for a badly-defined range" do @@ -302,8 +310,8 @@ # This test guards against ranges with inconsistent data types. begin d1 = Date.today - expect { @o.first_in_range_date(d1..10).to raise_error(ContractError) } - expect { @o.first_in_range_num(d1..10).to raise_error(ContractError) } + fails { @o.first_in_range_date(d1..10) } + fails { @o.first_in_range_num(d1..10) } rescue ArgumentError # If Ruby doesn't like the range, we ignore the test. :nop @@ -313,26 +321,26 @@ describe "SetOf:" do it "should pass for a set of nums" do - expect { @o.product_from_set(Set.new([1, 2, 3])) }.to_not raise_error + passes { @o.product_from_set(Set.new([1, 2, 3])) } end it "should fail for an array with one non-num" do - expect { @o.product_from_set(Set.new([1, 2, 3, "bad"])) }.to raise_error(ContractError) + fails { @o.product_from_set(Set.new([1, 2, 3, "bad"])) } end it "should fail for a non-array" do - expect { @o.product_from_set(1) }.to raise_error(ContractError) + fails { @o.product_from_set(1) } end end describe "Bool:" do it "should pass for an argument that is a boolean" do - expect { @o.bool_test(true) }.to_not raise_error - expect { @o.bool_test(false) }.to_not raise_error + passes { @o.bool_test(true) } + passes { @o.bool_test(false) } end it "should fail for nil" do - expect { @o.bool_test(nil) }.to raise_error(ContractError) + fails { @o.bool_test(nil) } end end @@ -346,31 +354,31 @@ end it "should fail for strings" do - expect { @o.maybe_double("foo") }.to raise_error(ContractError) + fails { @o.maybe_double("foo") } end end describe "KeywordArgs:" do it "should pass for exact correct input" do - expect { @o.person_keywordargs(:name => "calvin", :age => 10) }.to_not raise_error + passes { @o.person_keywordargs(:name => "calvin", :age => 10) } end it "should fail if some keys don't have contracts" do - expect { @o.person_keywordargs(:name => "calvin", :age => 10, :foo => "bar") }.to raise_error(ContractError) + fails { @o.person_keywordargs(:name => "calvin", :age => 10, :foo => "bar") } end it "should fail if a key with a contract on it isn't provided" do - expect { @o.person_keywordargs(:name => "calvin") }.to raise_error(ContractError) + fails { @o.person_keywordargs(:name => "calvin") } end it "should fail for incorrect input" do - expect { @o.person_keywordargs(:name => 50, :age => 10) }.to raise_error(ContractError) - expect { @o.hash_keywordargs(:hash => nil) }.to raise_error(ContractError) - expect { @o.hash_keywordargs(:hash => 1) }.to raise_error(ContractError) + fails { @o.person_keywordargs(:name => 50, :age => 10) } + fails { @o.hash_keywordargs(:hash => nil) } + fails { @o.hash_keywordargs(:hash => 1) } end it "should pass if a method is overloaded with non-KeywordArgs" do - expect { @o.person_keywordargs("name", 10) }.to_not raise_error + passes { @o.person_keywordargs("name", 10) } end end @@ -402,10 +410,10 @@ def something(hash) end context "given an unfulfilled contract" do - it { expect { @o.gives_max_value(:panda => "1", :bamboo => "2") }.to raise_error(ContractError) } - it { expect { @o.gives_max_value(nil) }.to raise_error(ContractError) } - it { expect { @o.gives_max_value(1) }.to raise_error(ContractError) } - it { expect { @o.pretty_gives_max_value(:panda => "1", :bamboo => "2") }.to raise_error(ContractError) } + it { fails { @o.gives_max_value(:panda => "1", :bamboo => "2") } } + it { fails { @o.gives_max_value(nil) } } + it { fails { @o.gives_max_value(1) } } + it { fails { @o.pretty_gives_max_value(:panda => "1", :bamboo => "2") } } end describe "#to_s" do @@ -422,39 +430,31 @@ def something(hash) describe "StrictHash:" do context "when given an exact correct input" do it "does not raise an error" do - expect do - @o.strict_person(:name => "calvin", :age => 10) - end.to_not raise_error + passes { @o.strict_person(:name => "calvin", :age => 10) } end end context "when given an input with correct keys but wrong types" do it "raises an error" do - expect do - @o.strict_person(:name => "calvin", :age => "10") - end.to raise_error(ContractError) + fails { @o.strict_person(:name => "calvin", :age => "10") } end end context "when given an input with missing keys" do it "raises an error" do - expect do - @o.strict_person(:name => "calvin") - end.to raise_error(ContractError) + fails { @o.strict_person(:name => "calvin") } end end context "when given an input with extra keys" do it "raises an error" do - expect do - @o.strict_person(:name => "calvin", :age => "10", :soft => true) - end.to raise_error(ContractError) + fails { @o.strict_person(:name => "calvin", :age => "10", :soft => true) } end end context "when given not a hash" do it "raises an error" do - expect { @o.strict_person(1337) }.to raise_error(ContractError) + fails { @o.strict_person(1337) } end end end From 13e56bdf859a54c569d1a7fe4680251880c6d089 Mon Sep 17 00:00:00 2001 From: Piotr Szmielew Date: Fri, 24 Feb 2017 17:54:19 +0100 Subject: [PATCH 02/86] fixes issue #242 - contract's return value is now enforced with blocks properly (#251) * fixes issue #242 - contract's return value is now enforced with blocks properly * more robust if condition * handle block with keyword arguments and change Gemfile syntax to work with travis properly --- Gemfile | 2 +- lib/contracts/call_with.rb | 7 +++-- spec/methods_spec.rb | 54 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 spec/methods_spec.rb diff --git a/Gemfile b/Gemfile index e0c8a59..2ca6c2f 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ group :test do gem "rspec" gem "aruba" gem "cucumber", "~> 1.3.20" - gem "rubocop", "~> 0.29.1", :platform => [:ruby_20, :ruby_21, :ruby_22, :ruby_23] + gem "rubocop", "~> 0.29.1" if RUBY_VERSION >= "2" end group :development do diff --git a/lib/contracts/call_with.rb b/lib/contracts/call_with.rb index 29a609f..c8a8b62 100644 --- a/lib/contracts/call_with.rb +++ b/lib/contracts/call_with.rb @@ -26,7 +26,9 @@ def call_with(this, *args, &blk) :return_value => false) end - if contract.is_a?(Contracts::Func) + if contract.is_a?(Contracts::Func) && blk && !nil_block_appended + blk = Contract.new(klass, arg, *contract.contracts) + elsif contract.is_a?(Contracts::Func) args[i] = Contract.new(klass, arg, *contract.contracts) end end @@ -73,7 +75,8 @@ def call_with(this, *args, &blk) method.call(*args, &blk) else # original method name referrence - method.send_to(this, *args, &blk) + added_block = blk ? lambda { |*params| blk.call(*params) } : nil + method.send_to(this, *args, &added_block) end unless @ret_validator[result] diff --git a/spec/methods_spec.rb b/spec/methods_spec.rb new file mode 100644 index 0000000..334fb4e --- /dev/null +++ b/spec/methods_spec.rb @@ -0,0 +1,54 @@ +RSpec.describe "Contracts:" do + describe "method called with blocks" do + module FuncTest + include Contracts::Core + include Contracts::Builtin + + Contract Func[Num=>Num] => nil + def foo(&blk) + _ = blk.call(2) + nil + end + + Contract Num, Func[Num=>Num] => nil + def foo2(a, &blk) + _ = blk.call(2) + nil + end + + Contract Func[Num=>Num] => nil + def bar(blk) + _ = blk.call(2) + nil + end + + Contract Num, Func[Num=>Num] => nil + def bar2(a, blk) + _ = blk.call(2) + nil + end + end + + def obj + Object.new.tap do |o| + o.extend(FuncTest) + end + end + + it "should enforce return value inside block with no other parameter" do + expect { obj.foo(&:to_s) }.to raise_error + end + + it "should enforce return value inside block with other parameter" do + expect { obj.foo2(2) { |x| x.to_s } }.to raise_error + end + + it "should enforce return value inside lambda with no other parameter" do + expect { obj.bar lambda { |x| x.to_s } }.to raise_error + end + + it "should enforce return value inside lambda with other parameter" do + expect { obj.bar2(2, lambda { |x| x.to_s }) }.to raise_error + end + end +end From bd9f2ce806e76eca28362008a74050e9eaea2a36 Mon Sep 17 00:00:00 2001 From: Piotr Szmielew Date: Fri, 24 Feb 2017 15:25:57 +0100 Subject: [PATCH 03/86] add to readme.md info about running `rspec` `rake spec` don't run required `ruby_version_specific` spec, therefore command actually running that is `rspec`. --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 420d105..653733f 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,12 @@ To get started do the following: 2. Run our test suite - `bundle exec rake` - + `bundle exec rspec` + +3. Run our code style checks + + `bundle exec rubocop` + ## Performance Using contracts.ruby results in very little slowdown. Check out [this blog post](http://adit.io/posts/2013-03-04-How-I-Made-My-Ruby-Project-10x-Faster.html#seconds-6) for more info. From 7e59ffd9e10aadd07ec342aff9dd8af2d39d1740 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Fri, 24 Feb 2017 09:01:29 -0800 Subject: [PATCH 04/86] version 0.15.0 --- CHANGELOG.markdown | 2 ++ lib/contracts/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index f5d6d95..f8615ed 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,4 +1,6 @@ ## v0.15.0 +- Bugfix: Func contract's return value isn't enforced with blocks - [Piotr Szmielew](https://github.com/esse) [#251](https://github.com/egonSchiele/contracts.ruby/pull/251) +- Bugfx: Fix contracts used in AR-models - [Gert Goet](https://github.com/eval) [#237](https://github.com/egonSchiele/contracts.ruby/pull/237) ## v0.14.0 - Enhancement: Add StrictHash contract - [Fyodor](https://github.com/cbrwizard) [#236](https://github.com/egonSchiele/contracts.ruby/pull/236) diff --git a/lib/contracts/version.rb b/lib/contracts/version.rb index 9bfcf97..4e7b2f2 100644 --- a/lib/contracts/version.rb +++ b/lib/contracts/version.rb @@ -1,3 +1,3 @@ module Contracts - VERSION = "0.14.0" + VERSION = "0.15.0" end From 2422baf92aa59903aeab619aa63d72d0087cdc76 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Mon, 27 Feb 2017 12:57:04 -0800 Subject: [PATCH 05/86] adding task to add tag for new versions --- Rakefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Rakefile b/Rakefile index f87b0e6..a04777e 100644 --- a/Rakefile +++ b/Rakefile @@ -7,5 +7,10 @@ else task :default => [:spec] end + +task :add_tag do + `git tag -a v#{Contracts::VERSION} -m 'v#{Contracts::VERSION}'` +end + require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) From be093677b9e994be818250309ee54e1fb7403491 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Wed, 29 Mar 2017 12:53:46 -0400 Subject: [PATCH 06/86] Fix broken contracts.coffee link in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 653733f..5ac86ee 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Michael Tomer ## Credits -Inspired by [contracts.coffee](http://disnetdev.com/contracts.coffee/). +Inspired by [contracts.coffee](http://disnet.github.io/contracts.coffee/). Copyright 2012-2015 [Aditya Bhargava](http://adit.io). Major improvements by [Alexey Fedorov](https://github.com/waterlink). From 8e6d4eec01bc64bff50192a63ac236ee34449401 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Thu, 20 Apr 2017 11:31:09 -0400 Subject: [PATCH 07/86] Drop support for Ruby 1.8. (#256) * Drop support for Ruby 1.8. As per the conversation in: https://github.com/egonSchiele/contracts.ruby/pull/255 * Address rubocop violation. * Mention 1.8 support drop in changelog. --- .travis.yml | 4 ---- CHANGELOG.markdown | 4 ++++ README.md | 2 +- Rakefile | 1 - lib/contracts/support.rb | 15 +++------------ 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index d2284ff..433be36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,7 @@ bundler_args: --without development matrix: include: - - rvm: "1.8.7" - dist: precise - rvm: "1.9.2" dist: precise - - rvm: jruby-18mode # JRuby in 1.8 mode - dist: precise - rvm: jruby-19mode # JRuby in 1.9 mode dist: precise diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index f8615ed..3ec68c3 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,3 +1,7 @@ +## v0.16.0 (unreleased) + +- Support for Ruby 1.8 has been discontinued - [#256](https://github.com/egonSchiele/contracts.ruby/pull/256) + ## v0.15.0 - Bugfix: Func contract's return value isn't enforced with blocks - [Piotr Szmielew](https://github.com/esse) [#251](https://github.com/egonSchiele/contracts.ruby/pull/251) - Bugfx: Fix contracts used in AR-models - [Gert Goet](https://github.com/eval) [#237](https://github.com/egonSchiele/contracts.ruby/pull/237) diff --git a/README.md b/README.md index 5ac86ee..3484504 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Using contracts.ruby results in very little slowdown. Check out [this blog post] **Q.** What Rubies can I use this with? -**A.** It's been tested with `1.8.7`, `1.9.2`, `1.9.3`, `2.0.0`, `2.1`, `2.2`, and `jruby` (both 1.8 and 1.9 modes). +**A.** It's been tested with `1.9.2`, `1.9.3`, `2.0.0`, `2.1`, `2.2`, and `jruby` (1.9 mode). If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :) diff --git a/Rakefile b/Rakefile index a04777e..d7e9405 100644 --- a/Rakefile +++ b/Rakefile @@ -7,7 +7,6 @@ else task :default => [:spec] end - task :add_tag do `git tag -a v#{Contracts::VERSION} -m 'v#{Contracts::VERSION}'` end diff --git a/lib/contracts/support.rb b/lib/contracts/support.rb index 8bc6fed..780455c 100644 --- a/lib/contracts/support.rb +++ b/lib/contracts/support.rb @@ -4,16 +4,8 @@ class << self def method_position(method) return method.method_position if method.is_a?(MethodReference) - if RUBY_VERSION =~ /^1\.8/ - if method.respond_to?(:__file__) - method.__file__ + ":" + method.__line__.to_s - else - method.inspect - end - else - file, line = method.source_location - file + ":" + line.to_s - end + file, line = method.source_location + file + ":" + line.to_s end def method_name(method) @@ -34,8 +26,7 @@ def contract_id(contract) end def eigenclass_hierarchy_supported? - return false if RUBY_PLATFORM == "java" && RUBY_VERSION.to_f < 2.0 - RUBY_VERSION.to_f > 1.8 + RUBY_PLATFORM != "java" || RUBY_VERSION.to_f >= 2.0 end def eigenclass_of(target) From c408bcc915ec8ef8f1c39828014c23f324b3ed96 Mon Sep 17 00:00:00 2001 From: Maciej Malecki Date: Thu, 20 Apr 2017 18:18:55 +0200 Subject: [PATCH 08/86] Fix StrictHash contract for extra keys (#254) * Fix StrictHash contract for extra keys `Contracts::StrictHash` didn't complain about extra entries in a given hash. This was because of the missing assertion for keys. Specs hadn't caught this case because `age` key had a wrong type anyway. Compare keys for contract and a given hash and return false if they are equal. * Do not use a second lookup for the contract in StrictHash assertion `all?` iterator already provides contract for the `valid?` check. Use it instead of accessing `contract_hash` again. --- lib/contracts/builtin_contracts.rb | 5 +++-- spec/builtin_contracts_spec.rb | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/contracts/builtin_contracts.rb b/lib/contracts/builtin_contracts.rb index cb81c60..25d36b0 100644 --- a/lib/contracts/builtin_contracts.rb +++ b/lib/contracts/builtin_contracts.rb @@ -405,9 +405,10 @@ def initialize(contract_hash) def valid?(arg) return false unless arg.is_a?(Hash) + return false unless arg.keys.sort.eql?(contract_hash.keys.sort) - contract_hash.all? do |key, _v| - contract_hash.key?(key) && Contract.valid?(arg[key], contract_hash[key]) + contract_hash.all? do |key, contract| + contract_hash.key?(key) && Contract.valid?(arg[key], contract) end end end diff --git a/spec/builtin_contracts_spec.rb b/spec/builtin_contracts_spec.rb index be718ad..00cf495 100644 --- a/spec/builtin_contracts_spec.rb +++ b/spec/builtin_contracts_spec.rb @@ -448,7 +448,7 @@ def something(hash) context "when given an input with extra keys" do it "raises an error" do - fails { @o.strict_person(:name => "calvin", :age => "10", :soft => true) } + fails { @o.strict_person(:name => "calvin", :age => 10, :soft => true) } end end From 56c2075d924406358a38461de252144b29ec2c59 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Thu, 20 Apr 2017 17:21:44 -0400 Subject: [PATCH 09/86] Add a `Contracts::Attrs` module containing attribute w/ contracts utilities. (#255) * Add `Attrs` module containing attribute w/ contracts utilities. * Add documentation for new `Attrs` module. * Add tests for new `Attrs` module. --- TUTORIAL.md | 26 +++++++++++++++ lib/contracts.rb | 1 + lib/contracts/attrs.rb | 20 +++++++++++ spec/attrs_spec.rb | 75 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 lib/contracts/attrs.rb create mode 100644 spec/attrs_spec.rb diff --git a/TUTORIAL.md b/TUTORIAL.md index a635e0f..c74ce06 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -606,6 +606,32 @@ Possible validator overrides: Default validators can be found here: [lib/contracts/validators.rb](https://github.com/egonSchiele/contracts.ruby/blob/master/lib/contracts/validators.rb). +## Contracts with attributes + +You can include the `Contracts::Attrs` module in your class/module to get access to attribute utilities: + +- `attr_reader_with_contract ..., ` + - Wraps `attr_reader`, validates contract upon 'getting' +- `attr_writer_with_contract ..., ` + - Wraps `attr_writer`, validates contract upon 'setting' +- `attr_accessor_with_contract ..., ` + - Wraps `attr_accessor`, validates contract upon 'getting' or 'setting' + +### Example + +```ruby +class Person + include Contracts::Core + include Contracts::Attrs + + attr_accessor_with_contract :name, String +end + +person = Person.new +person.name = 'Jane' +person.name = 1.4 # This results in a contract error! +``` + ## Disabling contracts If you want to disable contracts, set the `NO_CONTRACTS` environment variable. This will disable contracts and you won't have a performance hit. Pattern matching will still work if you disable contracts in this way! With NO_CONTRACTS only pattern-matching contracts are defined. diff --git a/lib/contracts.rb b/lib/contracts.rb index f7367d5..baf2acf 100644 --- a/lib/contracts.rb +++ b/lib/contracts.rb @@ -1,3 +1,4 @@ +require "contracts/attrs" require "contracts/builtin_contracts" require "contracts/decorators" require "contracts/errors" diff --git a/lib/contracts/attrs.rb b/lib/contracts/attrs.rb new file mode 100644 index 0000000..d57bf9b --- /dev/null +++ b/lib/contracts/attrs.rb @@ -0,0 +1,20 @@ +module Contracts + module Attrs + def attr_reader_with_contract(*names, contract) + Contract Contracts::None => contract + attr_reader(*names) + end + + def attr_writer_with_contract(*names, contract) + Contract contract => contract + attr_writer(*names) + end + + def attr_accessor_with_contract(*names, contract) + attr_reader_with_contract(*names, contract) + attr_writer_with_contract(*names, contract) + end + end + + include Attrs +end diff --git a/spec/attrs_spec.rb b/spec/attrs_spec.rb new file mode 100644 index 0000000..2780976 --- /dev/null +++ b/spec/attrs_spec.rb @@ -0,0 +1,75 @@ +RSpec.describe "Contracts:" do + describe "Attrs:" do + class Person + include Contracts::Core + include Contracts::Attrs + include Contracts::Builtin + + def initialize(name) + @name_r = name + @name_w = name + @name_rw = name + end + + attr_reader_with_contract :name_r, String + attr_writer_with_contract :name_w, String + attr_accessor_with_contract :name_rw, String + end + + context "attr_reader_with_contract" do + it "getting valid type" do + expect(Person.new("bob").name_r) + .to(eq("bob")) + end + + it "getting invalid type" do + expect { Person.new(1.3).name_r } + .to(raise_error(ReturnContractError)) + end + + it "setting" do + expect { Person.new("bob").name_r = "alice" } + .to(raise_error(NoMethodError)) + end + end + + context "attr_writer_with_contract" do + it "getting" do + expect { Person.new("bob").name_w } + .to(raise_error(NoMethodError)) + end + + it "setting valid type" do + expect(Person.new("bob").name_w = "alice") + .to(eq("alice")) + end + + it "setting invalid type" do + expect { Person.new("bob").name_w = 1.2 } + .to(raise_error(ParamContractError)) + end + end + + context "attr_accessor_with_contract" do + it "getting valid type" do + expect(Person.new("bob").name_rw) + .to(eq("bob")) + end + + it "getting invalid type" do + expect { Person.new(1.2).name_rw } + .to(raise_error(ReturnContractError)) + end + + it "setting valid type" do + expect(Person.new("bob").name_rw = "alice") + .to(eq("alice")) + end + + it "setting invalid type" do + expect { Person.new("bob").name_rw = 1.2 } + .to(raise_error(ParamContractError)) + end + end + end +end From c840d5702cd6c690cf6d21794b957144631a83e4 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Thu, 20 Apr 2017 14:22:31 -0700 Subject: [PATCH 10/86] FIX: methods created with attr_* don't have a source_location. In that case just return the empty string. --- lib/contracts/support.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/contracts/support.rb b/lib/contracts/support.rb index 780455c..f3efa20 100644 --- a/lib/contracts/support.rb +++ b/lib/contracts/support.rb @@ -5,7 +5,11 @@ def method_position(method) return method.method_position if method.is_a?(MethodReference) file, line = method.source_location - file + ":" + line.to_s + if file.nil? || line.nil? + "" + else + file + ":" + line.to_s + end end def method_name(method) From a198313657a2455f9e3e67cbf02aade77bd55f6f Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Mon, 24 Apr 2017 11:33:01 -0400 Subject: [PATCH 11/86] Add a couple CHANGELOG entries. (#257) --- CHANGELOG.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 3ec68c3..5d41a59 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,6 +1,8 @@ ## v0.16.0 (unreleased) - Support for Ruby 1.8 has been discontinued - [#256](https://github.com/egonSchiele/contracts.ruby/pull/256) +- Add a `Contracts::Attrs` module containing attribute w/ contracts utilities - [#255](https://github.com/egonSchiele/contracts.ruby/pull/255) +- Fix StrictHash contract for extra keys - [#254](https://github.com/egonSchiele/contracts.ruby/pull/254) ## v0.15.0 - Bugfix: Func contract's return value isn't enforced with blocks - [Piotr Szmielew](https://github.com/esse) [#251](https://github.com/egonSchiele/contracts.ruby/pull/251) From 98cad25741b2dc0462229a417e3c6660e563cd84 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Mon, 24 Apr 2017 08:42:39 -0700 Subject: [PATCH 12/86] v0.16.0 --- CHANGELOG.markdown | 8 ++++---- lib/contracts/version.rb | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 5d41a59..d1d9fe3 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,8 +1,8 @@ -## v0.16.0 (unreleased) +## v0.16.0 -- Support for Ruby 1.8 has been discontinued - [#256](https://github.com/egonSchiele/contracts.ruby/pull/256) -- Add a `Contracts::Attrs` module containing attribute w/ contracts utilities - [#255](https://github.com/egonSchiele/contracts.ruby/pull/255) -- Fix StrictHash contract for extra keys - [#254](https://github.com/egonSchiele/contracts.ruby/pull/254) +- **Support for Ruby 1.8 has been discontinued** - [Corey Farwell](https://github.com/frewsxcv) [#256](https://github.com/egonSchiele/contracts.ruby/pull/256) +- Enhancement: Add a `Contracts::Attrs` module containing attribute w/ contracts utilities - [Corey Farwell](https://github.com/frewsxcv) [#255](https://github.com/egonSchiele/contracts.ruby/pull/255) +- Bugfix: Fix StrictHash contract for extra keys - [Maciej Malecki](https://github.com/smt116) [#254](https://github.com/egonSchiele/contracts.ruby/pull/254) ## v0.15.0 - Bugfix: Func contract's return value isn't enforced with blocks - [Piotr Szmielew](https://github.com/esse) [#251](https://github.com/egonSchiele/contracts.ruby/pull/251) diff --git a/lib/contracts/version.rb b/lib/contracts/version.rb index 4e7b2f2..4d84451 100644 --- a/lib/contracts/version.rb +++ b/lib/contracts/version.rb @@ -1,3 +1,3 @@ module Contracts - VERSION = "0.15.0" + VERSION = "0.16.0" end From 726409b02c6c604e98976469678f8fcf9eb08b73 Mon Sep 17 00:00:00 2001 From: Kevin Yeh Date: Tue, 23 May 2017 13:17:12 -0400 Subject: [PATCH 13/86] Upgrade ruby-1.9.2 dist to trusty --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 433be36..813e2de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ dist: trusty language: ruby rvm: + - "1.9.2" - "1.9.3" - "2.0.0" - "2.1" @@ -20,7 +21,5 @@ bundler_args: --without development matrix: include: - - rvm: "1.9.2" - dist: precise - rvm: jruby-19mode # JRuby in 1.9 mode dist: precise From 2bd5c3b408dec94667748afd7e24d74d2ac461d6 Mon Sep 17 00:00:00 2001 From: Kevin Yeh Date: Tue, 23 May 2017 16:12:47 -0400 Subject: [PATCH 14/86] Apply contracts to each name for multi-attribute calls (#259) * Fix multi-attribute contracts * fix rubocop --- lib/contracts/attrs.rb | 12 ++++++---- spec/attrs_spec.rb | 50 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/contracts/attrs.rb b/lib/contracts/attrs.rb index d57bf9b..320837f 100644 --- a/lib/contracts/attrs.rb +++ b/lib/contracts/attrs.rb @@ -1,13 +1,17 @@ module Contracts module Attrs def attr_reader_with_contract(*names, contract) - Contract Contracts::None => contract - attr_reader(*names) + names.each do |name| + Contract Contracts::None => contract + attr_reader(name) + end end def attr_writer_with_contract(*names, contract) - Contract contract => contract - attr_writer(*names) + names.each do |name| + Contract contract => contract + attr_writer(name) + end end def attr_accessor_with_contract(*names, contract) diff --git a/spec/attrs_spec.rb b/spec/attrs_spec.rb index 2780976..66ec15c 100644 --- a/spec/attrs_spec.rb +++ b/spec/attrs_spec.rb @@ -9,11 +9,15 @@ def initialize(name) @name_r = name @name_w = name @name_rw = name + + @name_r_2 = name + @name_w_2 = name + @name_rw_2 = name end - attr_reader_with_contract :name_r, String - attr_writer_with_contract :name_w, String - attr_accessor_with_contract :name_rw, String + attr_reader_with_contract :name_r, :name_r_2, String + attr_writer_with_contract :name_w, :name_w_2, String + attr_accessor_with_contract :name_rw, :name_rw_2, String end context "attr_reader_with_contract" do @@ -27,6 +31,16 @@ def initialize(name) .to(raise_error(ReturnContractError)) end + it "getting valid type for second val" do + expect(Person.new("bob").name_r_2) + .to(eq("bob")) + end + + it "getting invalid type for second val" do + expect { Person.new(1.3).name_r_2 } + .to(raise_error(ReturnContractError)) + end + it "setting" do expect { Person.new("bob").name_r = "alice" } .to(raise_error(NoMethodError)) @@ -48,6 +62,16 @@ def initialize(name) expect { Person.new("bob").name_w = 1.2 } .to(raise_error(ParamContractError)) end + + it "setting valid type for second val" do + expect(Person.new("bob").name_w_2 = "alice") + .to(eq("alice")) + end + + it "setting invalid type for second val" do + expect { Person.new("bob").name_w_2 = 1.2 } + .to(raise_error(ParamContractError)) + end end context "attr_accessor_with_contract" do @@ -70,6 +94,26 @@ def initialize(name) expect { Person.new("bob").name_rw = 1.2 } .to(raise_error(ParamContractError)) end + + it "getting valid type for second val" do + expect(Person.new("bob").name_rw_2) + .to(eq("bob")) + end + + it "getting invalid type for second val" do + expect { Person.new(1.2).name_rw_2 } + .to(raise_error(ReturnContractError)) + end + + it "setting valid type for second val" do + expect(Person.new("bob").name_rw_2 = "alice") + .to(eq("alice")) + end + + it "setting invalid type for second val" do + expect { Person.new("bob").name_rw_2 = 1.2 } + .to(raise_error(ParamContractError)) + end end end end From 8d85645e2cea2f3977211eece31f6560bf08c856 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sun, 24 Sep 2017 10:18:40 +0200 Subject: [PATCH 15/86] README: use SVG build badge [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3484504..2a7e3ef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# contracts.ruby [![Build Status](https://travis-ci.org/egonSchiele/contracts.ruby.png?branch=master)](https://travis-ci.org/egonSchiele/contracts.ruby) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby) +# contracts.ruby [![Build Status](https://travis-ci.org/egonSchiele/contracts.ruby.svg?branch=master)](https://travis-ci.org/egonSchiele/contracts.ruby) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby) Contracts let you clearly – even beautifully – express how your code behaves, and free you from writing tons of boilerplate, defensive code. From 5e1f237d49f31d4fe0baaf075f8afd0a76662f35 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sun, 24 Sep 2017 10:27:02 +0200 Subject: [PATCH 16/86] Gemfile: use https RubyGems source [ci skip] --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 2ca6c2f..e2edd07 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" gemspec From b193837591848ced7123a76682bd14f7a18000d2 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sun, 24 Sep 2017 10:52:00 +0200 Subject: [PATCH 17/86] CHANGELOG: add compare links, release dates - Compare http://keepachangelog.com/en/1.0.0/ --- CHANGELOG.markdown | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index d1d9fe3..cf791e2 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,14 +1,22 @@ -## v0.16.0 +## [v0.16.0] - 2017-04-24 + +[v0.16.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.15.0...98cad25741b2dc0462229a417e3c6660e563cd84 - **Support for Ruby 1.8 has been discontinued** - [Corey Farwell](https://github.com/frewsxcv) [#256](https://github.com/egonSchiele/contracts.ruby/pull/256) - Enhancement: Add a `Contracts::Attrs` module containing attribute w/ contracts utilities - [Corey Farwell](https://github.com/frewsxcv) [#255](https://github.com/egonSchiele/contracts.ruby/pull/255) - Bugfix: Fix StrictHash contract for extra keys - [Maciej Malecki](https://github.com/smt116) [#254](https://github.com/egonSchiele/contracts.ruby/pull/254) -## v0.15.0 +## [v0.15.0] - 2017-02-24 + +[v0.15.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.14.0...v0.15.0 + - Bugfix: Func contract's return value isn't enforced with blocks - [Piotr Szmielew](https://github.com/esse) [#251](https://github.com/egonSchiele/contracts.ruby/pull/251) - Bugfx: Fix contracts used in AR-models - [Gert Goet](https://github.com/eval) [#237](https://github.com/egonSchiele/contracts.ruby/pull/237) -## v0.14.0 +## [v0.14.0] - 2016-04-26 + +[v0.14.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.13.0...v0.14.0 + - Enhancement: Add StrictHash contract - [Fyodor](https://github.com/cbrwizard) [#236](https://github.com/egonSchiele/contracts.ruby/pull/236) - Bugfix: dont fail if something other than a hash is passed to a KeywordArgs - [Dan Padilha](https://github.com/dpad) [#234](https://github.com/egonSchiele/contracts.ruby/pull/234) - LICENSE ADDED: Simplified BSD (same as what is specified in the readme) - [Charles Dale](https://github.com/chuckd) [#233](https://github.com/egonSchiele/contracts.ruby/pull/233) @@ -16,20 +24,26 @@ - Bugfix for err case when KeywordArgs and Proc are used together - [Aditya Bhargava](https://github.com/egonSchiele) [#230](https://github.com/egonSchiele/contracts.ruby/pull/230) - Enhancement: Add DescendantOf contract - [Miguel Palhas](https://github.com/naps62) [#227](https://github.com/egonSchiele/contracts.ruby/pull/227) -## v0.13.0 +## [v0.13.0] - 2016-01-25 + +[v0.13.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.12.0...v0.13.0 - Enhancement: Add support for Ruby 2.3 - [Oleksii Fedorov](https://github.com/waterlink) [#216](https://github.com/egonSchiele/contracts.ruby/pull/216) - Enhancement: Added Int, Nat and NatPos builtin contracts - [Simon George](https://github.com/sfcgeorge) [#212](https://github.com/egonSchiele/contracts.ruby/pull/212) - Bugfix: Allow contracts on singleton of subclass - [Oleksii Federov](https://github.com/waterlink) [#211](https://github.com/egonSchiele/contracts.ruby/pull/211) -## v0.12.0 +## [v0.12.0] - 2015-09-15 + +[v0.12.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.11.0...v0.12.0 - Feature: add `Regexp` validator - [Gert Goet](https://github.com/eval) [#196](https://github.com/egonSchiele/contracts.ruby/pull/196) - Docs: bootstrap cucumber/aruba/relish setup - [Oleksii Fedorov](https://github.com/waterlink) [#195](https://github.com/egonSchiele/contracts.ruby/pull/195) - Bugfix: allow to `extend` module, that has `Contracts` or `Contracts::Core` included without harming current module/class `Contracts` functionality, see: [#176](https://github.com/egonSchiele/contracts.ruby/issues/176) - [Oleksii Fedorov](https://github.com/waterlink) [#198](https://github.com/egonSchiele/contracts.ruby/pull/198) - Enhancement: add `include Contracts::Builtin` to allow users to use builtin contracts without `Contracts::` prefix together with `include Contracts::Core` - [PikachuEXE](https://github.com/PikachuEXE) [#199](https://github.com/egonSchiele/contracts.ruby/pull/199) -## v0.11.0 +## [v0.11.0] - 2015-07-30 + +[v0.11.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.10.1...v0.11.0 - Enhancement: add `include Contracts::Core` that doesn't pollute the namespace as much as `include Contracts` - [Oleksii Federov](https://github.com/waterlink) [#185](https://github.com/egonSchiele/contracts.ruby/pull/185) - Bugfix: fail if a non-hash is provided to a `HashOf` contract - [Abe Voelker](https://github.com/abevoelker) [#190](https://github.com/egonSchiele/contracts.ruby/pull/190) @@ -38,11 +52,15 @@ - Feature: range contract added - [Oleksii Fedorov](https://github.com/waterlink) [#184](https://github.com/egonSchiele/contracts.ruby/pull/184) - Feature: enum contract added - [Dennis Günnewig](https://github.com/dg-ratiodata) [#181](https://github.com/egonSchiele/contracts.ruby/pull/181) -## v0.10.1 +## [v0.10.1] - 2015-07-16 + +[v0.10.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.10...v0.10.1 - Enhancement: make `@pattern_match` instance variable not render ruby warning. Required to use new aruba versions in rspec tests - [Dennis Günnewig](https://github.com/dg-ratiodata) [#179](https://github.com/egonSchiele/contracts.ruby/pull/179) -## v0.10 +## [v0.10] - 2015-07-07 + +[v0.10]: https://github.com/egonSchiele/contracts.ruby/compare/v0.9...v0.10 - Bugfix: make `Maybe[Proc]` work correctly - [Simon George](https://github.com/sfcgeorge) [#142](https://github.com/egonSchiele/contracts.ruby/pull/142) - Bugfix: make `Func` contract verified when used as return contract - [Rob Rosenbaum](https://github.com/robnormal) [#145](https://github.com/egonSchiele/contracts.ruby/pull/145) @@ -53,7 +71,9 @@ - Feature: custom validators with `Contract.override_validator` - [Oleksii Fedorov](https://github.com/waterlink) [#159](https://github.com/egonSchiele/contracts.ruby/pull/159) - Feature: add builtin `RangeOf[...]` contract - [Gavin Sinclair](https://github.com/gsinclair) [#171](https://github.com/egonSchiele/contracts.ruby/pull/171) -## v0.9 +## [v0.9] - 2015-04-24 + +[v0.9]: https://github.com/egonSchiele/contracts.ruby/compare/0.8...v0.9 - MAJOR fix in pattern-matching: If the return contract for a pattern-matched function fails, it should NOT try the next pattern-match function. Pattern-matching is only for params, not return values. - raise an error if multiple defns have the same contract for pattern matching. @@ -74,7 +94,7 @@ - Add `SetOf` contract - various small fixes -## v0.8 +## v0.8 - 2015-04-03 - code refactored (very slight loss of performance, big increase in readability) - fail when defining a contract on a module without `include Contracts::Modules` From e0ff1291e7b26253d2420bb3a505ae1531a8bfcd Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sun, 24 Sep 2017 19:08:58 +0200 Subject: [PATCH 18/86] Fix misspellings in comments - found using find . | misspellings -f - --- lib/contracts/call_with.rb | 2 +- spec/ruby_version_specific/contracts_spec_2.1.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/contracts/call_with.rb b/lib/contracts/call_with.rb index c8a8b62..8c66d22 100644 --- a/lib/contracts/call_with.rb +++ b/lib/contracts/call_with.rb @@ -74,7 +74,7 @@ def call_with(this, *args, &blk) # proc, block, lambda, etc method.call(*args, &blk) else - # original method name referrence + # original method name reference added_block = blk ? lambda { |*params| blk.call(*params) } : nil method.send_to(this, *args, &added_block) end diff --git a/spec/ruby_version_specific/contracts_spec_2.1.rb b/spec/ruby_version_specific/contracts_spec_2.1.rb index 168cfd0..36b6ede 100644 --- a/spec/ruby_version_specific/contracts_spec_2.1.rb +++ b/spec/ruby_version_specific/contracts_spec_2.1.rb @@ -51,7 +51,7 @@ def complicated(a, b = true, *c, d, e:, f:2, **g, &h) end.to raise_error(ContractError) end - it "should fail when passed nil to an optional argument which contract shouldnt accept nil" do + it "should fail when passed nil to an optional argument which contract shouldn't accept nil" do expect do @o.complicated("a", true, :b, :c, 2.0, e: (1..5), f: nil, g: :d) do |x| x From d1ae4bf2f0da15262fa9d757d5a7b6efdab8b0f9 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sun, 24 Sep 2017 19:22:41 +0200 Subject: [PATCH 19/86] RuboCop 0.41.2; last version to support Ruby 1.9 --- .rubocop.yml | 2 ++ Gemfile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3f42d56..c5897ea 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,6 @@ AllCops: + TargetRubyVersion: 1.9 + DisplayCopNames: true Exclude: - "tmp/**/*" diff --git a/Gemfile b/Gemfile index e2edd07..af86768 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ group :test do gem "rspec" gem "aruba" gem "cucumber", "~> 1.3.20" - gem "rubocop", "~> 0.29.1" if RUBY_VERSION >= "2" + gem "rubocop", "~> 0.41.2" if RUBY_VERSION >= "2" end group :development do From b17c844d56506322deedab256a26bf168e443787 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sun, 24 Sep 2017 19:25:27 +0200 Subject: [PATCH 20/86] RuboCop TODO added; excl version-specific files --- .rubocop.yml | 3 ++ .rubocop_todo.yml | 135 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 .rubocop_todo.yml diff --git a/.rubocop.yml b/.rubocop.yml index c5897ea..6690484 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,8 +1,11 @@ +inherit_from: .rubocop_todo.yml + AllCops: TargetRubyVersion: 1.9 DisplayCopNames: true Exclude: - "tmp/**/*" + - "spec/ruby_version_specific/*.rb" # forces method defs to have params in parens Style/MethodDefParentheses: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..1f2767c --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,135 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2017-09-24 19:22:52 +0200 using RuboCop version 0.41.2. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 2 +Lint/NonLocalExitFromIterator: + Exclude: + - 'lib/contracts/call_with.rb' + +# Offense count: 7 +# Cop supports --auto-correct. +Performance/RedundantBlockCall: + Exclude: + - 'spec/builtin_contracts_spec.rb' + - 'spec/fixtures/fixtures.rb' + - 'spec/methods_spec.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: prefer_alias, prefer_alias_method +Style/Alias: + Exclude: + - 'lib/contracts/builtin_contracts.rb' + - 'lib/contracts/errors.rb' + - 'lib/contracts/method_handler.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AllowAdjacentOneLineDefs. +Style/EmptyLineBetweenDefs: + Exclude: + - 'benchmarks/wrap_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. +Style/ExtraSpacing: + Exclude: + - 'spec/builtin_contracts_spec.rb' + +# Offense count: 1 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Exclude: + - 'lib/contracts.rb' + +# Offense count: 1 +Style/IfInsideElse: + Exclude: + - 'lib/contracts/validators.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: symmetrical, new_line, same_line +Style/MultilineHashBraceLayout: + Exclude: + - 'spec/contracts_spec.rb' + - 'spec/fixtures/fixtures.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +Style/MutableConstant: + Exclude: + - 'lib/contracts/builtin_contracts.rb' + - 'lib/contracts/errors.rb' + - 'lib/contracts/method_handler.rb' + - 'lib/contracts/version.rb' + +# Offense count: 7 +# Cop supports --auto-correct. +Style/NestedParenthesizedCalls: + Exclude: + - 'spec/contracts_spec.rb' + - 'spec/support_spec.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +Style/ParallelAssignment: + Exclude: + - 'lib/contracts.rb' + - 'lib/contracts/formatters.rb' + - 'lib/contracts/invariants.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +Style/RedundantParentheses: + Exclude: + - 'benchmarks/hash.rb' + - 'lib/contracts.rb' + - 'spec/fixtures/fixtures.rb' + - 'spec/override_validators_spec.rb' + - 'spec/ruby_version_specific/contracts_spec_1.9.rb' + +# Offense count: 17 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: only_raise, only_fail, semantic +Style/SignalException: + Exclude: + - 'benchmarks/bench.rb' + - 'benchmarks/hash.rb' + - 'lib/contracts.rb' + - 'lib/contracts/builtin_contracts.rb' + - 'lib/contracts/engine/eigenclass.rb' + - 'lib/contracts/invariants.rb' + - 'lib/contracts/method_handler.rb' + - 'spec/contracts_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +# IgnoredMethods: respond_to, define_method +Style/SymbolProc: + Exclude: + - 'lib/contracts/decorators.rb' + - 'spec/methods_spec.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: AllowNamedUnderscoreVariables. +Style/TrailingUnderscoreVariable: + Exclude: + - 'lib/contracts/builtin_contracts.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/UnneededInterpolation: + Exclude: + - 'lib/contracts/formatters.rb' From 246d98d90cc73c11250105370ac43469678f2828 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 26 Sep 2017 01:19:13 +0200 Subject: [PATCH 21/86] Travis: Create a separate RuboCop "Build Stage" (#271) Run RuboCop in a separate stage so the rest of the tests don't run if RuboCop fails. Simplify travis script, add more rubies to test. --- .rubocop.yml | 1 + .travis.yml | 36 +++++++++++++++++++++++------------- Rakefile | 9 +-------- script/cucumber | 5 ----- 4 files changed, 25 insertions(+), 26 deletions(-) delete mode 100755 script/cucumber diff --git a/.rubocop.yml b/.rubocop.yml index 6690484..49aa730 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,6 +5,7 @@ AllCops: DisplayCopNames: true Exclude: - "tmp/**/*" + - "vendor/**/*" - "spec/ruby_version_specific/*.rb" # forces method defs to have params in parens diff --git a/.travis.yml b/.travis.yml index 813e2de..9d2b296 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,35 @@ dist: trusty language: ruby + +before_install: + - gem install bundler + +cache: + bundler: true + +script: "bundle exec rspec && bundle exec cucumber" + rvm: - "1.9.2" - "1.9.3" - "2.0.0" - "2.1" - - "2.2" - - "ruby-2.3.0" - -before_install: - - gem install bundler - -script: - - bundle exec rspec - - script/cucumber - - ruby script/rubocop.rb + - "2.2.8" + - "2.3.5" + - "2.4.2" bundler_args: --without development -matrix: +jobs: include: - - rvm: jruby-19mode # JRuby in 1.9 mode - dist: precise + - stage: test + rvm: "jruby-1.7.27" + script: bundle exec rspec + - stage: codestyle + script: bundle exec rubocop + rvm: "2.4.2" + +stages: + - codestyle + - test diff --git a/Rakefile b/Rakefile index d7e9405..439b075 100644 --- a/Rakefile +++ b/Rakefile @@ -1,11 +1,4 @@ -if RUBY_VERSION >= "2" - task :default => [:spec, :rubocop] - - require "rubocop/rake_task" - RuboCop::RakeTask.new -else - task :default => [:spec] -end +task :default => [:spec] task :add_tag do `git tag -a v#{Contracts::VERSION} -m 'v#{Contracts::VERSION}'` diff --git a/script/cucumber b/script/cucumber deleted file mode 100755 index 054ef73..0000000 --- a/script/cucumber +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -if ruby -e 'exit(1) unless RUBY_VERSION.to_f >= 2.0'; then - bundle exec cucumber -fi From 78be9e20218d60f4619d5ea1a16fc7892064f500 Mon Sep 17 00:00:00 2001 From: Venky Iyer Date: Tue, 10 Oct 2017 20:50:39 -0700 Subject: [PATCH 22/86] Fix typo in exception AFAICT, `name` is not defined at that point --- lib/contracts/method_handler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contracts/method_handler.rb b/lib/contracts/method_handler.rb index ee16b6b..f47926b 100644 --- a/lib/contracts/method_handler.rb +++ b/lib/contracts/method_handler.rb @@ -157,7 +157,7 @@ def validate_decorators! return if decorators.size == 1 fail %{ -Oops, it looks like method '#{name}' has multiple contracts: +Oops, it looks like method '#{method_name}' has multiple contracts: #{decorators.map { |x| x[1][0].inspect }.join("\n")} Did you accidentally put more than one contract on a single function, like so? From f8f99bdffa9fc9cd43409c0a37dad3d04525ac6e Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Fri, 10 Nov 2017 17:20:38 +0000 Subject: [PATCH 23/86] Don't use exceptions for control flow --- lib/contracts/call_with.rb | 22 +++++++++++++-------- lib/contracts/method_handler.rb | 34 +++++++++++++-------------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/lib/contracts/call_with.rb b/lib/contracts/call_with.rb index 8c66d22..c9336a1 100644 --- a/lib/contracts/call_with.rb +++ b/lib/contracts/call_with.rb @@ -1,6 +1,10 @@ module Contracts module CallWith def call_with(this, *args, &blk) + call_with_inner(false, this, *args, &blk) + end + + def call_with_inner(returns, this, *args, &blk) args << blk if blk # Explicitly append blk=nil if nil != Proc contract violation anticipated @@ -16,14 +20,16 @@ def call_with(this, *args, &blk) validator = @args_validators[i] unless validator && validator[arg] - return unless Contract.failure_callback(:arg => arg, - :contract => contract, - :class => klass, - :method => method, - :contracts => self, - :arg_pos => i+1, - :total_args => args.size, - :return_value => false) + data = {:arg => arg, + :contract => contract, + :class => klass, + :method => method, + :contracts => self, + :arg_pos => i+1, + :total_args => args.size, + :return_value => false} + return ParamContractError.new("as return value", data) if returns + return unless Contract.failure_callback(data) end if contract.is_a?(Contracts::Func) && blk && !nil_block_appended diff --git a/lib/contracts/method_handler.rb b/lib/contracts/method_handler.rb index f47926b..d57e37e 100644 --- a/lib/contracts/method_handler.rb +++ b/lib/contracts/method_handler.rb @@ -125,31 +125,23 @@ def redefine_method # function. Otherwise we return the result. # If we run out of functions, we raise the last error, but # convert it to_contract_error. - success = false - i = 0 - result = nil + expected_error = decorated_methods[0].failure_exception + last_error = nil - until success - decorated_method = decorated_methods[i] - i += 1 - begin - success = true - result = decorated_method.call_with(self, *args, &blk) - rescue expected_error => error - success = false - unless decorated_methods[i] - begin - ::Contract.failure_callback(error.data, false) - rescue expected_error => final_error - raise final_error.to_contract_error - end - end - end + decorated_methods.each do |decorated_method| + result = decorated_method.call_with_inner(true, self, *args, &blk) + return result unless result.is_a?(ParamContractError) + last_error = result end - # Return the result of successfully called method - result + begin + if ::Contract.failure_callback(last_error.data, false) + decorated_methods.last.call_with_inner(false, self, *args, &blk) + end + rescue expected_error => final_error + raise final_error.to_contract_error + end end end From da2ad647705f063ed5913fac08a84de46e03d672 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Thu, 30 Nov 2017 08:33:45 -0800 Subject: [PATCH 24/86] looking for a new maintainer msg --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2a7e3ef..db8a0e3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +This project is looking for a new maintainer! [More details here](https://github.com/egonSchiele/contracts.ruby/issues/249) + # contracts.ruby [![Build Status](https://travis-ci.org/egonSchiele/contracts.ruby.svg?branch=master)](https://travis-ci.org/egonSchiele/contracts.ruby) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby) Contracts let you clearly – even beautifully – express how your code behaves, and free you from writing tons of boilerplate, defensive code. From 1bd58dfb284885816cd4214e163b7c9528498046 Mon Sep 17 00:00:00 2001 From: md-work <> Date: Thu, 14 Dec 2017 11:37:18 +0100 Subject: [PATCH 25/86] Wrapping &blocks only when there is a Func check. (bug 278) --- lib/contracts/call_with.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/contracts/call_with.rb b/lib/contracts/call_with.rb index c9336a1..9252c79 100644 --- a/lib/contracts/call_with.rb +++ b/lib/contracts/call_with.rb @@ -81,8 +81,10 @@ def call_with_inner(returns, this, *args, &blk) method.call(*args, &blk) else # original method name reference - added_block = blk ? lambda { |*params| blk.call(*params) } : nil - method.send_to(this, *args, &added_block) + # Don't reassign blk, else Travis CI shows "stack level too deep". + target_blk = blk + target_blk = lambda { |*params| blk.call(*params) } if blk && blk.is_a?(Contract) + method.send_to(this, *args, &target_blk) end unless @ret_validator[result] From f45260ac74858600435053b91127e4f3498421a3 Mon Sep 17 00:00:00 2001 From: Denis Defreyne Date: Sun, 4 Mar 2018 09:27:23 +0100 Subject: [PATCH 26/86] Test on all the latest Rubies --- .travis.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9d2b296..49dab8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,12 @@ script: "bundle exec rspec && bundle exec cucumber" rvm: - "1.9.2" - "1.9.3" - - "2.0.0" + - "2.0" - "2.1" - - "2.2.8" - - "2.3.5" - - "2.4.2" + - "2.2" + - "2.3" + - "2.4" + - "2.5" bundler_args: --without development From da7655f9e2d18ede6df8f2dc47076b4ba3fe7df2 Mon Sep 17 00:00:00 2001 From: Victor Martins Date: Wed, 14 Mar 2018 16:39:50 +0000 Subject: [PATCH 27/86] Update README.md Changing the documentation since we now test for Ruby versions 2.3, 2.4 and 2.5. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db8a0e3..cba1a82 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Using contracts.ruby results in very little slowdown. Check out [this blog post] **Q.** What Rubies can I use this with? -**A.** It's been tested with `1.9.2`, `1.9.3`, `2.0.0`, `2.1`, `2.2`, and `jruby` (1.9 mode). +**A.** It's been tested with `1.9.2`, `1.9.3`, `2.0.0`, `2.1`, `2.2`, `2.3`, `2.4`, `2.5` and `jruby` (1.9 mode). If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :) From 0a85410ed1aa180bcd458d3508a67363afbe04f7 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 23 Oct 2018 10:55:58 -0400 Subject: [PATCH 28/86] Pretty-print contracts in error messages. --- features/basics/pretty-print.feature | 241 +++++++++++++++++++++++++++ lib/contracts.rb | 51 +++++- lib/contracts/formatters.rb | 6 +- lib/contracts/support.rb | 7 + spec/contracts_spec.rb | 22 +++ spec/fixtures/fixtures.rb | 24 +++ 6 files changed, 341 insertions(+), 10 deletions(-) create mode 100644 features/basics/pretty-print.feature diff --git a/features/basics/pretty-print.feature b/features/basics/pretty-print.feature new file mode 100644 index 0000000..af88f49 --- /dev/null +++ b/features/basics/pretty-print.feature @@ -0,0 +1,241 @@ +Feature: Pretty printing Contract violations + + Scenario: Big array argument being passed to big array method parameter + Given a file named "example.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + class << self + Contract [ + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol] + ] => nil + def run(data) + nil + end + end + end + + puts Example.run([ + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"] + ]) + """ + When I run `ruby example.rb` + Then the output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: [(String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol)], + Actual: [["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"]] + Value guarded in: Example::run + With Contract: Array => NilClass + At: example.rb:17 + """ + + Scenario: Big array value being returned from method expecting different big array type + Given a file named "example.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + class << self + Contract C::None => [ + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol] + ] + def run + [ + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"] + ] + end + end + end + + puts Example.run + """ + When I run `ruby example.rb` + Then the output should contain: + """ + : Contract violation for return value: (ReturnContractError) + Expected: [(String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol)], + Actual: [["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"]] + Value guarded in: Example::run + With Contract: None => Array + At: example.rb:17 + """ + + Scenario: Big hash argument being passed to big hash method parameter + Given a file named "example.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + class << self + Contract ({ + a: C::Or[String, Symbol], + b: C::Or[String, Symbol], + c: C::Or[String, Symbol], + d: C::Or[String, Symbol], + e: C::Or[String, Symbol], + f: C::Or[String, Symbol], + g: C::Or[String, Symbol] + }) => nil + def run(data) + nil + end + end + end + + puts Example.run({ + a: ["foo", "foo"], + b: ["foo", "foo"], + c: ["foo", "foo"], + d: ["foo", "foo"], + e: ["foo", "foo"], + f: ["foo", "foo"], + g: ["foo", "foo"] + }) + """ + When I run `ruby example.rb` + Then the output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: {:a=>(String or Symbol), + :b=>(String or Symbol), + :c=>(String or Symbol), + :d=>(String or Symbol), + :e=>(String or Symbol), + :f=>(String or Symbol), + :g=>(String or Symbol)}, + Actual: {:a=>["foo", "foo"], + :b=>["foo", "foo"], + :c=>["foo", "foo"], + :d=>["foo", "foo"], + :e=>["foo", "foo"], + :f=>["foo", "foo"], + :g=>["foo", "foo"]} + Value guarded in: Example::run + With Contract: Hash => NilClass + At: example.rb:17 + """ + + Scenario: Big hash value being returned from method expecting different big hash type + Given a file named "example.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + class << self + Contract C::None => ({ + a: C::Or[String, Symbol], + b: C::Or[String, Symbol], + c: C::Or[String, Symbol], + d: C::Or[String, Symbol], + e: C::Or[String, Symbol], + f: C::Or[String, Symbol], + g: C::Or[String, Symbol] + }) + def run + { + a: ["foo", "foo"], + b: ["foo", "foo"], + c: ["foo", "foo"], + d: ["foo", "foo"], + e: ["foo", "foo"], + f: ["foo", "foo"], + g: ["foo", "foo"] + } + end + end + end + + puts Example.run + """ + When I run `ruby example.rb` + Then the output should contain: + """ + : Contract violation for return value: (ReturnContractError) + Expected: {:a=>(String or Symbol), + :b=>(String or Symbol), + :c=>(String or Symbol), + :d=>(String or Symbol), + :e=>(String or Symbol), + :f=>(String or Symbol), + :g=>(String or Symbol)}, + Actual: {:a=>["foo", "foo"], + :b=>["foo", "foo"], + :c=>["foo", "foo"], + :d=>["foo", "foo"], + :e=>["foo", "foo"], + :f=>["foo", "foo"], + :g=>["foo", "foo"]} + Value guarded in: Example::run + With Contract: None => Hash + At: example.rb:17 + """ diff --git a/lib/contracts.rb b/lib/contracts.rb index baf2acf..a4303d0 100644 --- a/lib/contracts.rb +++ b/lib/contracts.rb @@ -116,22 +116,57 @@ def to_s # This function is used by the default #failure_callback method # and uses the hash passed into the failure_callback method. def self.failure_msg(data) - expected = Contracts::Formatters::Expected.new(data[:contract]).contract - position = Contracts::Support.method_position(data[:method]) + indent_amount = 8 method_name = Contracts::Support.method_name(data[:method]) + # Header header = if data[:return_value] "Contract violation for return value:" else "Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:" end - %{#{header} - Expected: #{expected}, - Actual: #{data[:arg].inspect} - Value guarded in: #{data[:class]}::#{method_name} - With Contract: #{data[:contracts]} - At: #{position} } + # Expected + expected_prefix = "Expected: " + expected_value = Contracts::Support.indent_string( + Contracts::Formatters::Expected.new(data[:contract]).contract.pretty_inspect, + expected_prefix.length + ).strip + expected_line = expected_prefix + expected_value + "," + + # Actual + actual_prefix = "Actual: " + actual_value = Contracts::Support.indent_string( + data[:arg].pretty_inspect, + actual_prefix.length + ).strip + actual_line = actual_prefix + actual_value + + # Value guarded in + value_prefix = "Value guarded in: " + value_value = "#{data[:class]}::#{method_name}" + value_line = value_prefix + value_value + + # Contract + contract_prefix = "With Contract: " + contract_value = data[:contracts].to_s + contract_line = contract_prefix + contract_value + + # Position + position_prefix = "At: " + position_value = Contracts::Support.method_position(data[:method]) + position_line = position_prefix + position_value + + header + + "\n" + + Contracts::Support.indent_string( + [expected_line, + actual_line, + value_line, + contract_line, + position_line].join("\n"), + indent_amount + ) end # Callback for when a contract fails. By default it raises diff --git a/lib/contracts/formatters.rb b/lib/contracts/formatters.rb index db9b107..2d4f70b 100644 --- a/lib/contracts/formatters.rb +++ b/lib/contracts/formatters.rb @@ -1,3 +1,5 @@ +require "pp" + module Contracts # A namespace for classes related to formatting. module Formatters @@ -25,13 +27,13 @@ def hash_contract(hash) @full = true # Complex values output completely, overriding @full hash.inject({}) do |repr, (k, v)| repr.merge(k => InspectWrapper.create(contract(v), @full)) - end.inspect + end end # Formats Array contracts. def array_contract(array) @full = true - array.map { |v| InspectWrapper.create(contract(v), @full) }.inspect + array.map { |v| InspectWrapper.create(contract(v), @full) } end end diff --git a/lib/contracts/support.rb b/lib/contracts/support.rb index f3efa20..cda3a55 100644 --- a/lib/contracts/support.rb +++ b/lib/contracts/support.rb @@ -42,6 +42,13 @@ def eigenclass?(target) target <= eigenclass_of(Object) end + def indent_string(string, amount) + string.gsub( + /^(?!$)/, + (string[/^[ \t]/] || " ") * amount + ) + end + private # Module eigenclass can be detected by its ancestor chain diff --git a/spec/contracts_spec.rb b/spec/contracts_spec.rb index d37c40e..56be470 100644 --- a/spec/contracts_spec.rb +++ b/spec/contracts_spec.rb @@ -637,6 +637,28 @@ def delim(match) end.to raise_error(ContractError, not_s(delim "String or Symbol")) end + it "should wrap and pretty print for long param contracts" do + expect do + @o.long_array_param_contracts(true) + end.to( + raise_error( + ParamContractError, + /\[\(String or Symbol\),\n \(String or Symbol\),/ + ) + ) + end + + it "should wrap and pretty print for long return contracts" do + expect do + @o.long_array_return_contracts + end.to( + raise_error( + ReturnContractError, + /\[\(String or Symbol\),\n \(String or Symbol\),/ + ) + ) + end + it "should not contain Contracts:: module prefix" do expect do @o.double("bad") diff --git a/spec/fixtures/fixtures.rb b/spec/fixtures/fixtures.rb index b6d2bea..67b5740 100644 --- a/spec/fixtures/fixtures.rb +++ b/spec/fixtures/fixtures.rb @@ -152,6 +152,30 @@ def array_complex_contracts(data) def nested_array_complex_contracts(data) end + Contract [ + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol] + ] => nil + def long_array_param_contracts(data) + end + + Contract C::None => [ + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol] + ] + def long_array_return_contracts + end + Contract Proc => C::Any def do_call(&block) block.call From 9b3c7272e6d34bf72d918fbb3303ab364998f195 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 18 Nov 2020 14:27:14 +0800 Subject: [PATCH 29/86] * Create GitHub Workflow config to simulate Travis builds --- .github/workflows/code_style_checks.yaml | 36 +++++++++++++++++ .github/workflows/tests.yaml | 50 ++++++++++++++++++++++++ .gitignore | 1 + 3 files changed, 87 insertions(+) create mode 100644 .github/workflows/code_style_checks.yaml create mode 100644 .github/workflows/tests.yaml diff --git a/.github/workflows/code_style_checks.yaml b/.github/workflows/code_style_checks.yaml new file mode 100644 index 0000000..3ad3bd3 --- /dev/null +++ b/.github/workflows/code_style_checks.yaml @@ -0,0 +1,36 @@ +name: Code Style Checks + +on: + pull_request: + branches: + - master + paths-ignore: + - 'README.md' + push: + branches: + - master + paths-ignore: + - 'README.md' + +jobs: + rubocop: + name: Rubocop + if: "contains(github.event.commits[0].message, '[ci skip]') == false" + strategy: + fail-fast: false + matrix: + os: + - ubuntu + ruby: + - "jruby" + runs-on: ${{ matrix.os }}-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Run Rubocop + run: bundle exec rubocop diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..1b0e34a --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,50 @@ +name: Tests + +on: + pull_request: + branches: + - master + paths-ignore: + - 'README.md' + push: + branches: + - master + paths-ignore: + - 'README.md' + +jobs: + unit_tests: + name: Unit Tests + if: "contains(github.event.commits[0].message, '[ci skip]') == false" + strategy: + fail-fast: false + matrix: + os: + - ubuntu + ruby: + - "1.9.2" + - "1.9.3" + - "2.0" + - "2.1" + - "2.2" + - "2.3" + - "2.4" + - "2.5" + - "2.6" + - "2.7" + test_command: ["bundle exec rspec && bundle exec cucumber"] + include: + - os: ubuntu + ruby: "2.4.2" + test_command: "bundle exec rspec" + runs-on: ${{ matrix.os }}-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Test + run: ${{ test_command }} diff --git a/.gitignore b/.gitignore index 5607ef1..7dab97b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.swp *.swo .* +!.github Gemfile.lock tmp/aruba From 5a2a691615c0e402d96d6a148b6a1a07f1675262 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 9 Dec 2020 13:15:47 +0800 Subject: [PATCH 30/86] Fix GH Action config --- .github/workflows/code_style_checks.yaml | 2 +- .github/workflows/tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_style_checks.yaml b/.github/workflows/code_style_checks.yaml index 3ad3bd3..780c83e 100644 --- a/.github/workflows/code_style_checks.yaml +++ b/.github/workflows/code_style_checks.yaml @@ -22,7 +22,7 @@ jobs: os: - ubuntu ruby: - - "jruby" + - "2.7" runs-on: ${{ matrix.os }}-latest steps: - name: Checkout diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1b0e34a..76ce9c2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -47,4 +47,4 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Test - run: ${{ test_command }} + run: ${{ matrix.test_command }} From 23166870e200dc285b5d6f4c858da06b41e6b27a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 9 Dec 2020 13:18:25 +0800 Subject: [PATCH 31/86] Update GH Action config to remove unsupported ruby versions [ci skip] --- .github/workflows/tests.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 76ce9c2..251f5c0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -22,9 +22,6 @@ jobs: os: - ubuntu ruby: - - "1.9.2" - - "1.9.3" - - "2.0" - "2.1" - "2.2" - "2.3" From b6da70fb07131b2e4f51300a7e933c028bacd09a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 9 Dec 2020 13:22:45 +0800 Subject: [PATCH 32/86] Update README to have badge for GH Action instead of TravisCI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cba1a82..0912ae4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ This project is looking for a new maintainer! [More details here](https://github.com/egonSchiele/contracts.ruby/issues/249) -# contracts.ruby [![Build Status](https://travis-ci.org/egonSchiele/contracts.ruby.svg?branch=master)](https://travis-ci.org/egonSchiele/contracts.ruby) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby) +# contracts.ruby [![GitHub Build Status](https://img.shields.io/github/workflow/status/egonSchiele/contracts.ruby/Tests?style=flat-square)](https://github.com/egonSchiele/contracts.ruby/actions?query=workflow%3ATests) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby) Contracts let you clearly – even beautifully – express how your code behaves, and free you from writing tons of boilerplate, defensive code. From 6dbbcd53ff72b18d09a5d56b6f2c461055310166 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 9 Dec 2020 13:30:44 +0800 Subject: [PATCH 33/86] + Add config for GH Dependent Bot https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/ --- dependabot.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 dependabot.yml diff --git a/dependabot.yml b/dependabot.yml new file mode 100644 index 0000000..586e9f9 --- /dev/null +++ b/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 +updates: + - package-ecosystem: bundler + directory: "/" + schedule: + interval: monthly + time: "06:00" + timezone: Asia/Hong_Kong + open-pull-requests-limit: 10 + labels: + - "dependencies" + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: monthly + time: "06:00" + timezone: Asia/Hong_Kong + open-pull-requests-limit: 10 + labels: + - "dependencies" From cf8775a9d8af79271daf9889d71dac60daedef49 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 9 Dec 2020 15:48:59 +0800 Subject: [PATCH 34/86] Remove TravisCI config file [ci skip] --- .travis.yml | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 49dab8a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -dist: trusty - -language: ruby - -before_install: - - gem install bundler - -cache: - bundler: true - -script: "bundle exec rspec && bundle exec cucumber" - -rvm: - - "1.9.2" - - "1.9.3" - - "2.0" - - "2.1" - - "2.2" - - "2.3" - - "2.4" - - "2.5" - -bundler_args: --without development - -jobs: - include: - - stage: test - rvm: "jruby-1.7.27" - script: bundle exec rspec - - stage: codestyle - script: bundle exec rubocop - rvm: "2.4.2" - -stages: - - codestyle - - test From 0aa0bc4239964eb6126a4158b4ba43bd3a5c5046 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 25 Jan 2021 17:26:45 +0800 Subject: [PATCH 35/86] * Update spec to specify error class to suppress RSpec warning --- spec/contracts_spec.rb | 6 +++--- spec/methods_spec.rb | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/contracts_spec.rb b/spec/contracts_spec.rb index 56be470..af3df57 100644 --- a/spec/contracts_spec.rb +++ b/spec/contracts_spec.rb @@ -7,7 +7,7 @@ it "should fail for insufficient arguments" do expect do @o.hello - end.to raise_error + end.to raise_error ArgumentError end it "should fail for insufficient contracts" do @@ -32,7 +32,7 @@ def no_args_bad_contract 1 end end - end.to raise_error + end.to raise_error NameError end end @@ -718,7 +718,7 @@ def delim(match) it "should apply the contract to an inherited method" do c = Child.new expect { c.double(2) }.to_not raise_error - expect { c.double("asd") }.to raise_error + expect { c.double("asd") }.to raise_error ParamContractError end end diff --git a/spec/methods_spec.rb b/spec/methods_spec.rb index 334fb4e..d1b5d47 100644 --- a/spec/methods_spec.rb +++ b/spec/methods_spec.rb @@ -36,19 +36,19 @@ def obj end it "should enforce return value inside block with no other parameter" do - expect { obj.foo(&:to_s) }.to raise_error + expect { obj.foo(&:to_s) }.to raise_error ReturnContractError end it "should enforce return value inside block with other parameter" do - expect { obj.foo2(2) { |x| x.to_s } }.to raise_error + expect { obj.foo2(2) { |x| x.to_s } }.to raise_error ReturnContractError end it "should enforce return value inside lambda with no other parameter" do - expect { obj.bar lambda { |x| x.to_s } }.to raise_error + expect { obj.bar lambda { |x| x.to_s } }.to raise_error ReturnContractError end it "should enforce return value inside lambda with other parameter" do - expect { obj.bar2(2, lambda { |x| x.to_s }) }.to raise_error + expect { obj.bar2(2, lambda { |x| x.to_s }) }.to raise_error ReturnContractError end end end From 7dfb48c9f9153386a02cccf5ed38fa0cc795fb9c Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 25 Jan 2021 17:38:49 +0800 Subject: [PATCH 36/86] * Update CI config to test against MRI 3.0 --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 251f5c0..86156e5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -29,6 +29,7 @@ jobs: - "2.5" - "2.6" - "2.7" + - "3.0" test_command: ["bundle exec rspec && bundle exec cucumber"] include: - os: ubuntu From 7080d1f5502ec47f7a01fe24104104ab5de917eb Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 12 Apr 2021 16:52:49 +0800 Subject: [PATCH 37/86] Revert "* Update CI config to test against MRI 3.0" This reverts commit 7dfb48c9f9153386a02cccf5ed38fa0cc795fb9c. --- .github/workflows/tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 86156e5..251f5c0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -29,7 +29,6 @@ jobs: - "2.5" - "2.6" - "2.7" - - "3.0" test_command: ["bundle exec rspec && bundle exec cucumber"] include: - os: ubuntu From 1b9d18faceb6652c11b23e7370d3a5122d273720 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 12 Apr 2021 16:53:56 +0800 Subject: [PATCH 38/86] * Update gemspec post install message to notify about gem's future plan --- contracts.gemspec | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts.gemspec b/contracts.gemspec index d4ad547..1b5d5a1 100644 --- a/contracts.gemspec +++ b/contracts.gemspec @@ -8,6 +8,10 @@ Gem::Specification.new do |s| s.author = "Aditya Bhargava" s.email = "bluemangroupie@gmail.com" s.files = `git ls-files`.split("\n") - s.homepage = "http://github.com/egonSchiele/contracts.ruby" + s.homepage = "https://github.com/egonSchiele/contracts.ruby" s.license = "BSD-2-Clause" + s.post_install_message = " + 0.16.x will be the supporting Ruby 2.x and be feature frozen (only fixes will be released) + For Ruby 3.x use 0.17.x or later (might not be released yet) + " end From 0a15e39e46edde8ec00cb9efb6bb60327ba32b7a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 12 Apr 2021 17:11:10 +0800 Subject: [PATCH 39/86] ~ Update README for tested ruby version [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0912ae4..a2f01e9 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Using contracts.ruby results in very little slowdown. Check out [this blog post] **Q.** What Rubies can I use this with? -**A.** It's been tested with `1.9.2`, `1.9.3`, `2.0.0`, `2.1`, `2.2`, `2.3`, `2.4`, `2.5` and `jruby` (1.9 mode). +**A.** It's been tested with `2.1`, `2.2`, `2.3`, `2.4`, `2.5`, `2.6` and `2.7`. If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :) From 33a7c72b30d89f87385983251327d81aec412888 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 12 Apr 2021 17:18:42 +0800 Subject: [PATCH 40/86] ~ Prepare changelog for release [ci skip] --- CHANGELOG.markdown | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index cf791e2..a930975 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,6 +1,13 @@ + +## Unreleased + +- Enhancement: Pretty-print contracts in error messages - [Corey Farwell](https://github.com/frewsxcv) [#289](https://github.com/egonSchiele/contracts.ruby/pull/289) +- Bugfix: Fix `attr_accessor_with_contract` with multiple attribute names input - [Kevin Yeh](https://github.com/kyeah) [#259](https://github.com/egonSchiele/contracts.ruby/pull/259) +- Bugfix: Fix "stack level too deep" in CI builds - [md-work](https://github.com/md-work) [#283](https://github.com/egonSchiele/contracts.ruby/pull/283) + ## [v0.16.0] - 2017-04-24 -[v0.16.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.15.0...98cad25741b2dc0462229a417e3c6660e563cd84 +[v0.16.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.15.0...v0.16.0 - **Support for Ruby 1.8 has been discontinued** - [Corey Farwell](https://github.com/frewsxcv) [#256](https://github.com/egonSchiele/contracts.ruby/pull/256) - Enhancement: Add a `Contracts::Attrs` module containing attribute w/ contracts utilities - [Corey Farwell](https://github.com/frewsxcv) [#255](https://github.com/egonSchiele/contracts.ruby/pull/255) From dbdd91da27795b576bb8480467b52878ef463ed7 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 16 Apr 2021 10:11:45 +0800 Subject: [PATCH 41/86] ~ Update README's outdated tutorial URL github.com subdomain deprecated, github.io subdomain should be used [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2f01e9..e3f3776 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Instead of throwing an exception, you could log it, print a clean error message ## Tutorial -Check out [this awesome tutorial](http://egonschiele.github.com/contracts.ruby). +Check out [this awesome tutorial](https://egonschiele.github.io/contracts.ruby/). ## Use Cases From d9e02a191fff23f22599105da5f2127e3bf95040 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Sat, 17 Apr 2021 12:51:00 -0500 Subject: [PATCH 42/86] v0.16.1 --- CHANGELOG.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index a930975..e4e7229 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,5 +1,5 @@ -## Unreleased +## [v0.16.1] - 2021-04-17 - Enhancement: Pretty-print contracts in error messages - [Corey Farwell](https://github.com/frewsxcv) [#289](https://github.com/egonSchiele/contracts.ruby/pull/289) - Bugfix: Fix `attr_accessor_with_contract` with multiple attribute names input - [Kevin Yeh](https://github.com/kyeah) [#259](https://github.com/egonSchiele/contracts.ruby/pull/259) From 7d0d5be29950bcc163f6dad06822323f32a69671 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Sat, 17 Apr 2021 12:55:03 -0500 Subject: [PATCH 43/86] version bump to 0.16.1 --- lib/contracts/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contracts/version.rb b/lib/contracts/version.rb index 4d84451..f268776 100644 --- a/lib/contracts/version.rb +++ b/lib/contracts/version.rb @@ -1,3 +1,3 @@ module Contracts - VERSION = "0.16.0" + VERSION = "0.16.1" end From 2368c1198f21fbf07bceb5c2fb8a29a186c07e1d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 20 Apr 2021 16:11:15 +0800 Subject: [PATCH 44/86] ~ Fix CHANGELOG for latest version link [ci skip] --- CHANGELOG.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index e4e7229..09bb4ef 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,6 +1,8 @@ ## [v0.16.1] - 2021-04-17 +[v0.16.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.16.0...v0.16.1 + - Enhancement: Pretty-print contracts in error messages - [Corey Farwell](https://github.com/frewsxcv) [#289](https://github.com/egonSchiele/contracts.ruby/pull/289) - Bugfix: Fix `attr_accessor_with_contract` with multiple attribute names input - [Kevin Yeh](https://github.com/kyeah) [#259](https://github.com/egonSchiele/contracts.ruby/pull/259) - Bugfix: Fix "stack level too deep" in CI builds - [md-work](https://github.com/md-work) [#283](https://github.com/egonSchiele/contracts.ruby/pull/283) From e83c18bf8b193fad6457d04a78a2c1ec5f868b2a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 26 Jan 2021 17:37:59 +0800 Subject: [PATCH 45/86] * Update implementation & spec to be 3.0 compatible Mainly around the breaking behaviour change about keyword arguments --- lib/contracts.rb | 14 ++++----- lib/contracts/call_with.rb | 31 ++++++++++--------- lib/contracts/invariants.rb | 10 +++--- lib/contracts/method_handler.rb | 6 ++-- lib/contracts/method_reference.rb | 4 +-- spec/builtin_contracts_spec.rb | 20 +++++------- spec/contracts_spec.rb | 21 +++++++------ spec/fixtures/fixtures.rb | 9 ++---- spec/override_validators_spec.rb | 6 ++-- .../contracts_spec_2.0.rb | 4 +-- spec/validators_spec.rb | 2 +- 11 files changed, 63 insertions(+), 64 deletions(-) diff --git a/lib/contracts.rb b/lib/contracts.rb index a4303d0..867c0b1 100644 --- a/lib/contracts.rb +++ b/lib/contracts.rb @@ -93,9 +93,9 @@ def initialize(klass, method, *contracts) last_contract = args_contracts.last penultimate_contract = args_contracts[-2] @has_options_contract = if @has_proc_contract - penultimate_contract.is_a?(Hash) || penultimate_contract.is_a?(Contracts::Builtin::KeywordArgs) + penultimate_contract.is_a?(Contracts::Builtin::KeywordArgs) else - last_contract.is_a?(Hash) || last_contract.is_a?(Contracts::Builtin::KeywordArgs) + last_contract.is_a?(Contracts::Builtin::KeywordArgs) end # === @@ -249,12 +249,12 @@ def maybe_append_block! args, blk # Same thing for when we have named params but didn't pass any in. # returns true if it appended nil - def maybe_append_options! args, blk + def maybe_append_options! args, kargs, blk return false unless @has_options_contract - if @has_proc_contract && (args_contracts[-2].is_a?(Hash) || args_contracts[-2].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-2].is_a?(Hash) - args.insert(-2, {}) - elsif (args_contracts[-1].is_a?(Hash) || args_contracts[-1].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-1].is_a?(Hash) - args << {} + if @has_proc_contract && args_contracts[-2].is_a?(Contracts::Builtin::KeywordArgs) + args.insert(-2, kargs) + elsif args_contracts[-1].is_a?(Contracts::Builtin::KeywordArgs) + args << kargs end true end diff --git a/lib/contracts/call_with.rb b/lib/contracts/call_with.rb index 9252c79..224b357 100644 --- a/lib/contracts/call_with.rb +++ b/lib/contracts/call_with.rb @@ -1,17 +1,17 @@ module Contracts module CallWith - def call_with(this, *args, &blk) - call_with_inner(false, this, *args, &blk) + def call_with(this, *args, **kargs, &blk) + call_with_inner(false, this, *args, **kargs, &blk) end - def call_with_inner(returns, this, *args, &blk) + def call_with_inner(returns, this, *args, **kargs, &blk) args << blk if blk # Explicitly append blk=nil if nil != Proc contract violation anticipated nil_block_appended = maybe_append_block!(args, blk) # Explicitly append options={} if Hash contract is present - maybe_append_options!(args, blk) + kargs_appended = maybe_append_options!(args, kargs, blk) # Loop forward validating the arguments up to the splat (if there is one) (@args_contract_index || args.size).times do |i| @@ -57,14 +57,16 @@ def call_with_inner(returns, this, *args, &blk) validator = @args_validators[args_contracts.size - 1 - j] unless validator && validator[arg] - return unless Contract.failure_callback(:arg => arg, - :contract => contract, - :class => klass, - :method => method, - :contracts => self, - :arg_pos => i-1, - :total_args => args.size, - :return_value => false) + return unless Contract.failure_callback({ + :arg => arg, + :contract => contract, + :class => klass, + :method => method, + :contracts => self, + :arg_pos => i - 1, + :total_args => args.size, + :return_value => false, + }) end if contract.is_a?(Contracts::Func) @@ -76,15 +78,16 @@ def call_with_inner(returns, this, *args, &blk) # If we put the block into args for validating, restore the args # OR if we added a fake nil at the end because a block wasn't passed in. args.slice!(-1) if blk || nil_block_appended + args.slice!(-1) if kargs_appended result = if method.respond_to?(:call) # proc, block, lambda, etc - method.call(*args, &blk) + method.call(*args, **kargs, &blk) else # original method name reference # Don't reassign blk, else Travis CI shows "stack level too deep". target_blk = blk target_blk = lambda { |*params| blk.call(*params) } if blk && blk.is_a?(Contract) - method.send_to(this, *args, &target_blk) + method.send_to(this, *args, **kargs, &target_blk) end unless @ret_validator[result] diff --git a/lib/contracts/invariants.rb b/lib/contracts/invariants.rb index 56d2d82..215cb89 100644 --- a/lib/contracts/invariants.rb +++ b/lib/contracts/invariants.rb @@ -46,10 +46,12 @@ def expected def check_on(target, method) return if target.instance_eval(&@condition) - self.class.failure_callback(:expected => expected, - :actual => false, - :target => target, - :method => method) + self.class.failure_callback({ + :expected => expected, + :actual => false, + :target => target, + :method => method, + }) end def self.failure_callback(data) diff --git a/lib/contracts/method_handler.rb b/lib/contracts/method_handler.rb index d57e37e..0fa3593 100644 --- a/lib/contracts/method_handler.rb +++ b/lib/contracts/method_handler.rb @@ -107,7 +107,7 @@ def redefine_method current_engine = engine # We are gonna redefine original method here - method_reference.make_definition(target) do |*args, &blk| + method_reference.make_definition(target) do |*args, **kargs, &blk| engine = current_engine.nearest_decorated_ancestor # If we weren't able to find any ancestor that has decorated methods @@ -130,14 +130,14 @@ def redefine_method last_error = nil decorated_methods.each do |decorated_method| - result = decorated_method.call_with_inner(true, self, *args, &blk) + result = decorated_method.call_with_inner(true, self, *args, **kargs, &blk) return result unless result.is_a?(ParamContractError) last_error = result end begin if ::Contract.failure_callback(last_error.data, false) - decorated_methods.last.call_with_inner(false, self, *args, &blk) + decorated_methods.last.call_with_inner(false, self, *args, **kargs, &blk) end rescue expected_error => final_error raise final_error.to_contract_error diff --git a/lib/contracts/method_reference.rb b/lib/contracts/method_reference.rb index 0bc68f8..0c0c03f 100644 --- a/lib/contracts/method_reference.rb +++ b/lib/contracts/method_reference.rb @@ -39,8 +39,8 @@ def make_alias(this) # Calls original method on specified `this` argument with # specified arguments `args` and block `&blk`. - def send_to(this, *args, &blk) - this.send(aliased_name, *args, &blk) + def send_to(this, *args, **kargs, &blk) + this.send(aliased_name, *args, **kargs, &blk) end private diff --git a/spec/builtin_contracts_spec.rb b/spec/builtin_contracts_spec.rb index 00cf495..a9f7257 100644 --- a/spec/builtin_contracts_spec.rb +++ b/spec/builtin_contracts_spec.rb @@ -376,10 +376,6 @@ def passes(&some) fails { @o.hash_keywordargs(:hash => nil) } fails { @o.hash_keywordargs(:hash => 1) } end - - it "should pass if a method is overloaded with non-KeywordArgs" do - passes { @o.person_keywordargs("name", 10) } - end end describe "Optional:" do @@ -405,15 +401,15 @@ def something(hash) end context "given a fulfilled contract" do - it { expect(@o.gives_max_value(:panda => 1, :bamboo => 2)).to eq(2) } - it { expect(@o.pretty_gives_max_value(:panda => 1, :bamboo => 2)).to eq(2) } + it { expect(@o.gives_max_value({ :panda => 1, :bamboo => 2 })).to eq(2) } + it { expect(@o.pretty_gives_max_value({ :panda => 1, :bamboo => 2 })).to eq(2) } end context "given an unfulfilled contract" do - it { fails { @o.gives_max_value(:panda => "1", :bamboo => "2") } } + it { fails { @o.gives_max_value({ :panda => "1", :bamboo => "2" }) } } it { fails { @o.gives_max_value(nil) } } it { fails { @o.gives_max_value(1) } } - it { fails { @o.pretty_gives_max_value(:panda => "1", :bamboo => "2") } } + it { fails { @o.pretty_gives_max_value({ :panda => "1", :bamboo => "2" }) } } end describe "#to_s" do @@ -430,25 +426,25 @@ def something(hash) describe "StrictHash:" do context "when given an exact correct input" do it "does not raise an error" do - passes { @o.strict_person(:name => "calvin", :age => 10) } + passes { @o.strict_person({ :name => "calvin", :age => 10 }) } end end context "when given an input with correct keys but wrong types" do it "raises an error" do - fails { @o.strict_person(:name => "calvin", :age => "10") } + fails { @o.strict_person({ :name => "calvin", :age => "10" }) } end end context "when given an input with missing keys" do it "raises an error" do - fails { @o.strict_person(:name => "calvin") } + fails { @o.strict_person({ :name => "calvin" }) } end end context "when given an input with extra keys" do it "raises an error" do - fails { @o.strict_person(:name => "calvin", :age => 10, :soft => true) } + fails { @o.strict_person({ :name => "calvin", :age => 10, :soft => true }) } end end diff --git a/spec/contracts_spec.rb b/spec/contracts_spec.rb index af3df57..edc138f 100644 --- a/spec/contracts_spec.rb +++ b/spec/contracts_spec.rb @@ -349,19 +349,19 @@ def self.greeting(name) describe "Hashes" do it "should pass for exact correct input" do - expect { @o.person(:name => "calvin", :age => 10) }.to_not raise_error + expect { @o.person({ :name => "calvin", :age => 10 }) }.to_not raise_error end it "should pass even if some keys don't have contracts" do - expect { @o.person(:name => "calvin", :age => 10, :foo => "bar") }.to_not raise_error + expect { @o.person({ :name => "calvin", :age => 10, :foo => "bar" }) }.to_not raise_error end it "should fail if a key with a contract on it isn't provided" do - expect { @o.person(:name => "calvin") }.to raise_error(ContractError) + expect { @o.person({ :name => "calvin" }) }.to raise_error(ContractError) end it "should fail for incorrect input" do - expect { @o.person(:name => 50, :age => 10) }.to raise_error(ContractError) + expect { @o.person({ :name => 50, :age => 10 }) }.to raise_error(ContractError) end end @@ -612,16 +612,19 @@ def delim(match) it "should contain to_s representation within a Hash contract" do expect do - @o.hash_complex_contracts(:rigged => "bad") + @o.hash_complex_contracts({ :rigged => "bad" }) end.to raise_error(ContractError, not_s(delim "TrueClass or FalseClass")) end it "should contain to_s representation within a nested Hash contract" do expect do - @o.nested_hash_complex_contracts(:rigged => true, - :contents => { - :kind => 0, - :total => 42 }) + @o.nested_hash_complex_contracts({ + :rigged => true, + :contents => { + :kind => 0, + :total => 42, + }, + }) end.to raise_error(ContractError, not_s(delim "String or Symbol")) end diff --git a/spec/fixtures/fixtures.rb b/spec/fixtures/fixtures.rb index 67b5740..55638c2 100644 --- a/spec/fixtures/fixtures.rb +++ b/spec/fixtures/fixtures.rb @@ -120,16 +120,11 @@ def nested_hash_complex_contracts(data) end Contract C::KeywordArgs[:name => String, :age => Fixnum] => nil - def person_keywordargs(data) - end - - # Testing overloaded method - Contract String, Fixnum => nil - def person_keywordargs(name, age) + def person_keywordargs(name: "name", age: 10) end Contract C::KeywordArgs[:hash => C::HashOf[Symbol, C::Num]] => nil - def hash_keywordargs(data) + def hash_keywordargs(hash:) end Contract (/foo/) => nil diff --git a/spec/override_validators_spec.rb b/spec/override_validators_spec.rb index 293c84c..25af373 100644 --- a/spec/override_validators_spec.rb +++ b/spec/override_validators_spec.rb @@ -30,15 +30,15 @@ def something(opts) obj = klass.new expect do - obj.something(:a => 35, :b => "hello") + obj.something({ :a => 35, :b => "hello" }) end.to raise_error(ContractError) expect do - obj.something( + obj.something({ :a => 35, :b => "hello", :it_is_a_hash => true - ) + }) end.not_to raise_error end diff --git a/spec/ruby_version_specific/contracts_spec_2.0.rb b/spec/ruby_version_specific/contracts_spec_2.0.rb index 78c5e69..c2b3d69 100644 --- a/spec/ruby_version_specific/contracts_spec_2.0.rb +++ b/spec/ruby_version_specific/contracts_spec_2.0.rb @@ -1,10 +1,10 @@ class GenericExample - Contract C::Args[String], { repeat: C::Maybe[C::Num] } => C::ArrayOf[String] + Contract C::Args[String], C::KeywordArgs[ repeat: C::Maybe[C::Num] ] => C::ArrayOf[String] def splat_then_optional_named(*vals, repeat: 2) vals.map { |v| v * repeat } end - Contract ({foo: C::Nat}) => nil + Contract C::KeywordArgs[ foo: C::Nat ] => nil def nat_test_with_kwarg(foo: 10) end diff --git a/spec/validators_spec.rb b/spec/validators_spec.rb index 588580e..22dc2a9 100644 --- a/spec/validators_spec.rb +++ b/spec/validators_spec.rb @@ -34,7 +34,7 @@ describe "within a hash" do it "should pass for a matching string" do - expect { o.hash_containing_foo(:host => "foo.example.org") }.to_not raise_error + expect { o.hash_containing_foo({ :host => "foo.example.org" }) }.to_not raise_error end end From e7a9ef4b2ac9ec26bd55c54c113474d60235bdc6 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 20 Apr 2021 17:03:48 +0800 Subject: [PATCH 46/86] * Update GH Action to only run against Ruby 3.0 --- .github/workflows/code_style_checks.yaml | 2 +- .github/workflows/tests.yaml | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/code_style_checks.yaml b/.github/workflows/code_style_checks.yaml index 780c83e..14b2c41 100644 --- a/.github/workflows/code_style_checks.yaml +++ b/.github/workflows/code_style_checks.yaml @@ -22,7 +22,7 @@ jobs: os: - ubuntu ruby: - - "2.7" + - "3.0" runs-on: ${{ matrix.os }}-latest steps: - name: Checkout diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 251f5c0..1454991 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -22,17 +22,11 @@ jobs: os: - ubuntu ruby: - - "2.1" - - "2.2" - - "2.3" - - "2.4" - - "2.5" - - "2.6" - - "2.7" + - "3.0" test_command: ["bundle exec rspec && bundle exec cucumber"] include: - os: ubuntu - ruby: "2.4.2" + ruby: "3.0" test_command: "bundle exec rspec" runs-on: ${{ matrix.os }}-latest steps: From 8e7bdc9140d5545d1c75d68655dde0adbab9f499 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 20 Apr 2021 17:05:27 +0800 Subject: [PATCH 47/86] * Update gemspec to require ruby 3.x --- contracts.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts.gemspec b/contracts.gemspec index 1b5d5a1..0f670d2 100644 --- a/contracts.gemspec +++ b/contracts.gemspec @@ -10,6 +10,7 @@ Gem::Specification.new do |s| s.files = `git ls-files`.split("\n") s.homepage = "https://github.com/egonSchiele/contracts.ruby" s.license = "BSD-2-Clause" + s.required_ruby_version = ">= 3", "< 4" s.post_install_message = " 0.16.x will be the supporting Ruby 2.x and be feature frozen (only fixes will be released) For Ruby 3.x use 0.17.x or later (might not be released yet) From 028de0df0b6fbf9407474334cd735ba651923af8 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 21 Apr 2021 16:31:35 +0800 Subject: [PATCH 48/86] * Update RuboCop config & code with Rubocop violations --- .rubocop.yml | 40 ++++++++++++++++++++----- .rubocop_todo.yml | 11 ++++--- Gemfile | 12 +++++--- Rakefile | 2 ++ contracts.gemspec | 4 ++- features/support/env.rb | 2 ++ lib/contracts.rb | 41 ++++++++++++++++---------- lib/contracts/attrs.rb | 2 ++ lib/contracts/builtin_contracts.rb | 47 +++++++++++++++++++++++++----- lib/contracts/call_with.rb | 38 ++++++++++++++---------- lib/contracts/core.rb | 4 ++- lib/contracts/decorators.rb | 7 +++-- lib/contracts/engine.rb | 2 ++ lib/contracts/engine/base.rb | 7 +++-- lib/contracts/engine/eigenclass.rb | 5 ++-- lib/contracts/engine/target.rb | 2 ++ lib/contracts/errors.rb | 3 ++ lib/contracts/formatters.rb | 24 ++++++++------- lib/contracts/invariants.rb | 10 ++++--- lib/contracts/method_handler.rb | 22 ++++++++++---- lib/contracts/method_reference.rb | 2 ++ lib/contracts/support.rb | 6 ++-- lib/contracts/validators.rb | 8 +++-- lib/contracts/version.rb | 2 ++ 24 files changed, 217 insertions(+), 86 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 49aa730..0a4457d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,12 +1,17 @@ inherit_from: .rubocop_todo.yml AllCops: - TargetRubyVersion: 1.9 + TargetRubyVersion: 3.0 DisplayCopNames: true + NewCops: disable Exclude: + - "benchmarks/**/*" - "tmp/**/*" - "vendor/**/*" + - "script/**/*.rb" + - "spec/fixtures/*.rb" - "spec/ruby_version_specific/*.rb" + - "spec/*.rb" # forces method defs to have params in parens Style/MethodDefParentheses: @@ -38,7 +43,7 @@ Lint/UnusedMethodArgument: Enabled: false # changes x ** 2 to x**2 -Style/SpaceAroundOperators: +Layout/SpaceAroundOperators: Enabled: false # doesn't allow vars starting with _ @@ -56,7 +61,7 @@ Style/Documentation: # enforces line length of 80 # TODO enable -Metrics/LineLength: +Layout/LineLength: Enabled: false # triggered by Contract ({ :name => String, :age => Fixnum }) => nil @@ -101,7 +106,7 @@ Lint/DuplicateMethods: Style/TrivialAccessors: Enabled: false -Style/MultilineOperationIndentation: +Layout/MultilineOperationIndentation: EnforcedStyle: indented # Asks you to use %w{array of words} if possible. @@ -111,12 +116,12 @@ Style/WordArray: # conflicts with contracts # we define contracts like `Baz = 1` -Style/ConstantName: +Naming/ConstantName: Enabled: false # `Contract` violates this, otherwise a good cop (enforces snake_case method names) # TODO possible to get this enabled but ignore `Contract`? -Style/MethodName: +Naming/MethodName: Enabled: false # checks for !! @@ -129,9 +134,30 @@ Metrics/ParameterLists: Enabled: false # Checks that braces used for hash literals have or don't have surrounding space depending on configuration. -Style/SpaceInsideHashLiteralBraces: +Layout/SpaceInsideHashLiteralBraces: Enabled: false # TODO enable Style/SpecialGlobalVars: Enabled: false + +Style/IfUnlessModifier: + Enabled: false + +Naming/MemoizedInstanceVariableName: + Enabled: false + +Layout/FirstHashElementIndentation: + EnforcedStyle: consistent + +Layout/HashAlignment: + EnforcedColonStyle: table + +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: consistent_comma + +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: consistent_comma + +Style/TrailingCommaInArguments: + EnforcedStyleForMultiline: consistent_comma diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1f2767c..2005c69 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,6 +6,9 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +require: + - rubocop-performance + # Offense count: 2 Lint/NonLocalExitFromIterator: Exclude: @@ -32,14 +35,14 @@ Style/Alias: # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: AllowAdjacentOneLineDefs. -Style/EmptyLineBetweenDefs: +Layout/EmptyLineBetweenDefs: Exclude: - 'benchmarks/wrap_test.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. -Style/ExtraSpacing: +Layout/ExtraSpacing: Exclude: - 'spec/builtin_contracts_spec.rb' @@ -58,7 +61,7 @@ Style/IfInsideElse: # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: symmetrical, new_line, same_line -Style/MultilineHashBraceLayout: +Layout/MultilineHashBraceLayout: Exclude: - 'spec/contracts_spec.rb' - 'spec/fixtures/fixtures.rb' @@ -130,6 +133,6 @@ Style/TrailingUnderscoreVariable: # Offense count: 1 # Cop supports --auto-correct. -Style/UnneededInterpolation: +Style/RedundantInterpolation: Exclude: - 'lib/contracts/formatters.rb' diff --git a/Gemfile b/Gemfile index af86768..540761b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,17 +1,21 @@ +# frozen_string_literal: true + source "https://rubygems.org" gemspec group :test do - gem "rspec" gem "aruba" gem "cucumber", "~> 1.3.20" - gem "rubocop", "~> 0.41.2" if RUBY_VERSION >= "2" + gem "rspec" + + gem "rubocop", ">= 1.0.0" + gem "rubocop-performance", ">= 1.0.0" end group :development do - gem "relish" gem "method_profiler" - gem "ruby-prof" gem "rake" + gem "relish" + gem "ruby-prof" end diff --git a/Rakefile b/Rakefile index 439b075..fd4014c 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + task :default => [:spec] task :add_tag do diff --git a/contracts.gemspec b/contracts.gemspec index 0f670d2..9310e61 100644 --- a/contracts.gemspec +++ b/contracts.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require File.expand_path(File.join(__FILE__, "../lib/contracts/version")) Gem::Specification.new do |s| @@ -10,7 +12,7 @@ Gem::Specification.new do |s| s.files = `git ls-files`.split("\n") s.homepage = "https://github.com/egonSchiele/contracts.ruby" s.license = "BSD-2-Clause" - s.required_ruby_version = ">= 3", "< 4" + s.required_ruby_version = [">= 3.0", "< 4"] s.post_install_message = " 0.16.x will be the supporting Ruby 2.x and be feature frozen (only fixes will be released) For Ruby 3.x use 0.17.x or later (might not be released yet) diff --git a/features/support/env.rb b/features/support/env.rb index f22aa9f..55e18c5 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "aruba/cucumber" require "aruba/jruby" if RUBY_PLATFORM == "java" diff --git a/lib/contracts.rb b/lib/contracts.rb index 867c0b1..e8bab85 100644 --- a/lib/contracts.rb +++ b/lib/contracts.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "contracts/attrs" require "contracts/builtin_contracts" require "contracts/decorators" @@ -51,7 +53,9 @@ class Contract < Contracts::Decorator end attr_reader :args_contracts, :ret_contract, :klass, :method + def initialize(klass, method, *contracts) + super(klass, method) unless contracts.last.is_a?(Hash) unless contracts.one? fail %{ @@ -102,8 +106,8 @@ def initialize(klass, method, *contracts) @klass, @method = klass, method end - def pretty_contract c - c.is_a?(Class) ? c.name : c.class.name + def pretty_contract contract + contract.is_a?(Class) ? contract.name : contract.class.name end def to_s @@ -130,15 +134,15 @@ def self.failure_msg(data) expected_prefix = "Expected: " expected_value = Contracts::Support.indent_string( Contracts::Formatters::Expected.new(data[:contract]).contract.pretty_inspect, - expected_prefix.length + expected_prefix.length, ).strip - expected_line = expected_prefix + expected_value + "," + expected_line = "#{expected_prefix}#{expected_value}," # Actual actual_prefix = "Actual: " actual_value = Contracts::Support.indent_string( data[:arg].pretty_inspect, - actual_prefix.length + actual_prefix.length, ).strip actual_line = actual_prefix + actual_value @@ -157,16 +161,19 @@ def self.failure_msg(data) position_value = Contracts::Support.method_position(data[:method]) position_line = position_prefix + position_value - header + - "\n" + + [ + header, Contracts::Support.indent_string( - [expected_line, - actual_line, - value_line, - contract_line, - position_line].join("\n"), - indent_amount - ) + [ + expected_line, + actual_line, + value_line, + contract_line, + position_line, + ].join("\n"), + indent_amount, + ), + ].join("\n") end # Callback for when a contract fails. By default it raises @@ -182,7 +189,7 @@ def self.failure_msg(data) # puts failure_msg(data) # exit # end - def self.failure_callback(data, use_pattern_matching = true) + def self.failure_callback(data, use_pattern_matching: true) if data[:contracts].pattern_match? && use_pattern_matching return DEFAULT_FAILURE_CALLBACK.call(data) end @@ -242,7 +249,8 @@ def call(*args, &blk) # returns true if it appended nil def maybe_append_block! args, blk return false unless @has_proc_contract && !blk && - (@args_contract_index || args.size < args_contracts.size) + (@args_contract_index || args.size < args_contracts.size) + args << nil true end @@ -251,6 +259,7 @@ def maybe_append_block! args, blk # returns true if it appended nil def maybe_append_options! args, kargs, blk return false unless @has_options_contract + if @has_proc_contract && args_contracts[-2].is_a?(Contracts::Builtin::KeywordArgs) args.insert(-2, kargs) elsif args_contracts[-1].is_a?(Contracts::Builtin::KeywordArgs) diff --git a/lib/contracts/attrs.rb b/lib/contracts/attrs.rb index 320837f..e32a7e1 100644 --- a/lib/contracts/attrs.rb +++ b/lib/contracts/attrs.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module Attrs def attr_reader_with_contract(*names, contract) diff --git a/lib/contracts/builtin_contracts.rb b/lib/contracts/builtin_contracts.rb index 25d36b0..43fd293 100644 --- a/lib/contracts/builtin_contracts.rb +++ b/lib/contracts/builtin_contracts.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "contracts/formatters" require "set" @@ -30,35 +32,35 @@ def self.valid? val # Check that an argument is a positive number. class Pos def self.valid? val - val && val.is_a?(Numeric) && val > 0 + val.is_a?(Numeric) && val.positive? end end # Check that an argument is a negative number. class Neg def self.valid? val - val && val.is_a?(Numeric) && val < 0 + val.is_a?(Numeric) && val.negative? end end # Check that an argument is an +Integer+. class Int def self.valid? val - val && val.is_a?(Integer) + val.is_a?(Integer) end end # Check that an argument is a natural number (includes zero). class Nat def self.valid? val - val && val.is_a?(Integer) && val >= 0 + val.is_a?(Integer) && val >= 0 end end # Check that an argument is a positive natural number (excludes zero). class NatPos def self.valid? val - val && val.is_a?(Integer) && val > 0 + val.is_a?(Integer) && val.positive? end end @@ -96,6 +98,7 @@ def self.[](*vals) # Example: Or[Fixnum, Float] class Or < CallableClass def initialize(*vals) + super() @vals = vals end @@ -107,9 +110,11 @@ def valid?(val) end def to_s + # rubocop:disable Style/StringConcatenation @vals[0, @vals.size-1].map do |x| InspectWrapper.create(x) end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s + # rubocop:enable Style/StringConcatenation end end @@ -118,6 +123,7 @@ def to_s # Example: Xor[Fixnum, Float] class Xor < CallableClass def initialize(*vals) + super() @vals = vals end @@ -130,9 +136,11 @@ def valid?(val) end def to_s + # rubocop:disable Style/StringConcatenation @vals[0, @vals.size-1].map do |x| InspectWrapper.create(x) end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s + # rubocop:enable Style/StringConcatenation end end @@ -141,6 +149,7 @@ def to_s # Example: And[Fixnum, Float] class And < CallableClass def initialize(*vals) + super() @vals = vals end @@ -152,9 +161,11 @@ def valid?(val) end def to_s + # rubocop:disable Style/StringConcatenation @vals[0, @vals.size-1].map do |x| InspectWrapper.create(x) end.join(", ") + " and " + InspectWrapper.create(@vals[-1]).to_s + # rubocop:enable Style/StringConcatenation end end @@ -164,6 +175,7 @@ def to_s # Example: RespondTo[:password, :credit_card] class RespondTo < CallableClass def initialize(*meths) + super() @meths = meths end @@ -185,6 +197,7 @@ def to_s # Example: Send[:valid?] class Send < CallableClass def initialize(*meths) + super() @meths = meths end @@ -204,11 +217,12 @@ def to_s # Example: Exactly[Numeric] class Exactly < CallableClass def initialize(cls) + super() @cls = cls end def valid?(val) - val.class == @cls + val.instance_of?(@cls) end def to_s @@ -222,6 +236,7 @@ def to_s # Example: Enum[:a, :b, :c]? class Enum < CallableClass def initialize(*vals) + super() @vals = vals end @@ -235,6 +250,7 @@ def valid?(val) # Example: Eq[Class] class Eq < CallableClass def initialize(value) + super() @value = value end @@ -252,6 +268,7 @@ def to_s # Example: Not[nil] class Not < CallableClass def initialize(*vals) + super() @vals = vals end @@ -275,12 +292,14 @@ def to_s # Example: CollectionOf[Array, Num] class CollectionOf < CallableClass def initialize(collection_class, contract) + super() @collection_class = collection_class @contract = contract end def valid?(vals) return false unless vals.is_a?(@collection_class) + vals.all? do |val| res, _ = Contract.valid?(val, @contract) res @@ -298,7 +317,7 @@ def initialize(collection_class, &before_new) end def new(contract) - @before_new && @before_new.call + @before_new&.call CollectionOf.new(@collection_class, contract) end @@ -324,7 +343,9 @@ def new(contract) # Example: Args[Or[String, Num]] class Args < CallableClass attr_reader :contract + def initialize(contract) + super() @contract = contract end @@ -343,6 +364,7 @@ def self.valid? val # Example: RangeOf[Nat], RangeOf[Date], ... class RangeOf < CallableClass def initialize(contract) + super() @contract = contract end @@ -364,6 +386,7 @@ class HashOf < CallableClass INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract" def initialize(key, value = nil) + super() if value @key = key @value = value @@ -376,6 +399,7 @@ def initialize(key, value = nil) def valid?(hash) return false unless hash.is_a?(Hash) + keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all? vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all? @@ -400,6 +424,7 @@ class StrictHash < CallableClass attr_reader :contract_hash def initialize(contract_hash) + super() @contract_hash = contract_hash end @@ -417,12 +442,14 @@ def valid?(arg) # Example: KeywordArgs[ e: Range, f: Optional[Num] ] class KeywordArgs < CallableClass def initialize(options) + super() @options = options end def valid?(hash) return false unless hash.is_a?(Hash) return false unless hash.keys - options.keys == [] + options.all? do |key, contract| Optional._valid?(hash, key, contract) end @@ -445,6 +472,7 @@ def inspect # Example: DescendantOf[ e: Range, f: Optional[Num] ] class DescendantOf < CallableClass def initialize(parent_class) + super() @parent_class = parent_class end @@ -473,11 +501,13 @@ class Optional < CallableClass def self._valid?(hash, key, contract) return Contract.valid?(hash[key], contract) unless contract.is_a?(Optional) + contract.within_opt_hash! !hash.key?(key) || Contract.valid?(hash[key], contract) end def initialize(contract) + super() @contract = contract @within_opt_hash = false end @@ -506,6 +536,7 @@ def inspect def ensure_within_opt_hash return if within_opt_hash + fail ArgumentError, UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH end @@ -531,7 +562,9 @@ def include_proc? # Example: Func[Num => Num] # the function should take a number and return a number class Func < CallableClass attr_reader :contracts + def initialize(*contracts) + super() @contracts = contracts end end diff --git a/lib/contracts/call_with.rb b/lib/contracts/call_with.rb index 224b357..eff5687 100644 --- a/lib/contracts/call_with.rb +++ b/lib/contracts/call_with.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module CallWith def call_with(this, *args, **kargs, &blk) @@ -20,14 +22,16 @@ def call_with_inner(returns, this, *args, **kargs, &blk) validator = @args_validators[i] unless validator && validator[arg] - data = {:arg => arg, - :contract => contract, - :class => klass, - :method => method, - :contracts => self, - :arg_pos => i+1, - :total_args => args.size, - :return_value => false} + data = { + arg: arg, + contract: contract, + class: klass, + method: method, + contracts: self, + arg_pos: i+1, + total_args: args.size, + return_value: false, + } return ParamContractError.new("as return value", data) if returns return unless Contract.failure_callback(data) end @@ -57,6 +61,7 @@ def call_with_inner(returns, this, *args, **kargs, &blk) validator = @args_validators[args_contracts.size - 1 - j] unless validator && validator[arg] + # rubocop:disable Style/SoleNestedConditional return unless Contract.failure_callback({ :arg => arg, :contract => contract, @@ -67,6 +72,7 @@ def call_with_inner(returns, this, *args, **kargs, &blk) :total_args => args.size, :return_value => false, }) + # rubocop:enable Style/SoleNestedConditional end if contract.is_a?(Contracts::Func) @@ -86,17 +92,19 @@ def call_with_inner(returns, this, *args, **kargs, &blk) # original method name reference # Don't reassign blk, else Travis CI shows "stack level too deep". target_blk = blk - target_blk = lambda { |*params| blk.call(*params) } if blk && blk.is_a?(Contract) + target_blk = lambda { |*params| blk.call(*params) } if blk.is_a?(Contract) method.send_to(this, *args, **kargs, &target_blk) end unless @ret_validator[result] - Contract.failure_callback(:arg => result, - :contract => ret_contract, - :class => klass, - :method => method, - :contracts => self, - :return_value => true) + Contract.failure_callback({ + arg: result, + contract: ret_contract, + class: klass, + method: method, + contracts: self, + return_value: true, + }) end this.verify_invariants!(method) if this.respond_to?(:verify_invariants!) diff --git a/lib/contracts/core.rb b/lib/contracts/core.rb index 176b873..d251236 100644 --- a/lib/contracts/core.rb +++ b/lib/contracts/core.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module Core def self.included(base) @@ -25,7 +27,7 @@ def functype(funcname) # NOTE: Workaround for `defined?(super)` bug in ruby 1.9.2 # source: http://stackoverflow.com/a/11181685 # bug: https://bugs.ruby-lang.org/issues/6644 - base.class_eval <<-RUBY + base.class_eval <<-RUBY, __FILE__, __LINE__ + 1 # TODO: deprecate # Required when contracts are included in global scope def Contract(*args) diff --git a/lib/contracts/decorators.rb b/lib/contracts/decorators.rb index 11cd7b5..d436df6 100644 --- a/lib/contracts/decorators.rb +++ b/lib/contracts/decorators.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module MethodDecorators def self.extended(klass) @@ -25,6 +27,7 @@ class Decorator class << self; attr_accessor :decorators; end def self.inherited(klass) + super name = klass.name.gsub(/^./) { |m| m.downcase } return if name =~ /^[^A-Za-z_]/ || name =~ /[^0-9A-Za-z_]/ @@ -33,11 +36,11 @@ def self.inherited(klass) # make a new method that is the name of your decorator. # that method accepts random args and a block. # inside, `decorate` is called with those params. - MethodDecorators.module_eval <<-ruby_eval, __FILE__, __LINE__ + 1 + MethodDecorators.module_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{klass}(*args, &blk) ::Contracts::Engine.fetch_from(self).decorate(#{klass}, *args, &blk) end - ruby_eval + RUBY_EVAL end def initialize(klass, method) diff --git a/lib/contracts/engine.rb b/lib/contracts/engine.rb index 063a397..c047637 100644 --- a/lib/contracts/engine.rb +++ b/lib/contracts/engine.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "contracts/engine/base" require "contracts/engine/target" require "contracts/engine/eigenclass" diff --git a/lib/contracts/engine/base.rb b/lib/contracts/engine/base.rb index cd86630..4b45763 100644 --- a/lib/contracts/engine/base.rb +++ b/lib/contracts/engine/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module Engine # Contracts engine @@ -90,7 +92,7 @@ def add_method_decorator(type, name, decorator) def nearest_decorated_ancestor current = klass current_engine = self - ancestors = current.ancestors[1..-1] + ancestors = current.ancestors[1..] while current && current_engine && !current_engine.decorated_methods? current = ancestors.shift @@ -109,8 +111,7 @@ def decorated_methods end # No-op because it is safe to add decorators to normal classes - def validate! - end + def validate!; end def pop_decorators decorators.tap { clear_decorators } diff --git a/lib/contracts/engine/eigenclass.rb b/lib/contracts/engine/eigenclass.rb index 7011631..98f9956 100644 --- a/lib/contracts/engine/eigenclass.rb +++ b/lib/contracts/engine/eigenclass.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module Engine # Special case of contracts engine for eigenclasses @@ -27,8 +29,7 @@ def self.lift(eigenclass, owner) end # No-op for eigenclasses - def set_eigenclass_owner - end + def set_eigenclass_owner; end # Fetches just eigenclasses decorators def all_decorators diff --git a/lib/contracts/engine/target.rb b/lib/contracts/engine/target.rb index e8a0a84..dba096b 100644 --- a/lib/contracts/engine/target.rb +++ b/lib/contracts/engine/target.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module Engine # Represents class in question diff --git a/lib/contracts/errors.rb b/lib/contracts/errors.rb index c224905..4060668 100644 --- a/lib/contracts/errors.rb +++ b/lib/contracts/errors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # @private # Base class for Contract errors # @@ -65,6 +67,7 @@ class << self alias_method :to_s, :message def initialize(message = DEFAULT_MESSAGE) + super @message = message end end diff --git a/lib/contracts/formatters.rb b/lib/contracts/formatters.rb index 2d4f70b..3ee27bc 100644 --- a/lib/contracts/formatters.rb +++ b/lib/contracts/formatters.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "pp" module Contracts @@ -7,18 +9,19 @@ module Formatters class Expected # @param full [Boolean] if false only unique `to_s` values will be output, # non unique values become empty string. - def initialize(contract, full = true) + def initialize(contract, full: true) @contract, @full = contract, full end # Formats any type of Contract. def contract(contract = @contract) - if contract.is_a?(Hash) + case contract + when Hash hash_contract(contract) - elsif contract.is_a?(Array) + when Array array_contract(contract) else - InspectWrapper.create(contract, @full) + InspectWrapper.create(contract, full: @full) end end @@ -26,14 +29,14 @@ def contract(contract = @contract) def hash_contract(hash) @full = true # Complex values output completely, overriding @full hash.inject({}) do |repr, (k, v)| - repr.merge(k => InspectWrapper.create(contract(v), @full)) + repr.merge(k => InspectWrapper.create(contract(v), full: @full)) end end # Formats Array contracts. def array_contract(array) @full = true - array.map { |v| InspectWrapper.create(contract(v), @full) } + array.map { |v| InspectWrapper.create(contract(v), full: @full) } end end @@ -42,8 +45,8 @@ def array_contract(array) module InspectWrapper # InspectWrapper is a factory, will never be an instance # @return [ClassInspectWrapper, ObjectInspectWrapper] - def self.create(value, full = true) - if value.class == Class + def self.create(value, full: true) + if value.instance_of?(Class) ClassInspectWrapper else ObjectInspectWrapper @@ -66,6 +69,7 @@ def inspect return @value.inspect if empty_val? return @value.to_s if plain? return delim(@value.to_s) if useful_to_s? + useful_inspect end @@ -96,7 +100,7 @@ def plain? end def useful_to_s? - # Useless to_s value or no custom to_s behavious defined + # Useless to_s value or no custom to_s behaviour defined !empty_to_s? && custom_to_s? end @@ -125,7 +129,7 @@ class ObjectInspectWrapper include InspectWrapper def custom_to_s? - !@value.to_s.match(/#\<\w+:.+\>/) + !@value.to_s.match(/#<\w+:.+>/) end def useful_inspect diff --git a/lib/contracts/invariants.rb b/lib/contracts/invariants.rb index 215cb89..4dec30d 100644 --- a/lib/contracts/invariants.rb +++ b/lib/contracts/invariants.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module Invariants def self.included(base) @@ -47,10 +49,10 @@ def check_on(target, method) return if target.instance_eval(&@condition) self.class.failure_callback({ - :expected => expected, - :actual => false, - :target => target, - :method => method, + expected: expected, + actual: false, + target: target, + method: method, }) end diff --git a/lib/contracts/method_handler.rb b/lib/contracts/method_handler.rb index 0fa3593..5378e6a 100644 --- a/lib/contracts/method_handler.rb +++ b/lib/contracts/method_handler.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + module Contracts # Handles class and instance methods addition # Represents single such method class MethodHandler METHOD_REFERENCE_FACTORY = { :class_methods => SingletonMethodReference, - :instance_methods => MethodReference + :instance_methods => MethodReference, } RAW_METHOD_STRATEGY = { :class_methods => lambda { |target, name| target.method(name) }, - :instance_methods => lambda { |target, name| target.instance_method(name) } + :instance_methods => lambda { |target, name| target.instance_method(name) }, } # Creates new instance of MethodHandler @@ -78,11 +80,13 @@ def decorated_methods def pattern_matching? return @_pattern_matching if defined?(@_pattern_matching) + @_pattern_matching = decorated_methods.any? { |x| x.method != method_reference } end def mark_pattern_matching_decorators return unless pattern_matching? + decorated_methods.each(&:pattern_match!) end @@ -113,7 +117,7 @@ def redefine_method # If we weren't able to find any ancestor that has decorated methods # FIXME : this looks like untested code (commenting it out doesn't make specs red) unless engine - 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." + 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." end # Fetch decorated methods out of the contracts engine @@ -132,15 +136,18 @@ def redefine_method decorated_methods.each do |decorated_method| result = decorated_method.call_with_inner(true, self, *args, **kargs, &blk) return result unless result.is_a?(ParamContractError) + last_error = result end begin - if ::Contract.failure_callback(last_error.data, false) + if ::Contract.failure_callback(last_error&.data, use_pattern_matching: false) decorated_methods.last.call_with_inner(false, self, *args, **kargs, &blk) end + # rubocop:disable Naming/RescuedExceptionsVariableName rescue expected_error => final_error raise final_error.to_contract_error + # rubocop:enable Naming/RescuedExceptionsVariableName end end end @@ -173,7 +180,8 @@ def validate_pattern_matching! return if matched.empty? - fail ContractError.new(%{ + fail ContractError.new( + %{ It looks like you are trying to use pattern-matching, but multiple definitions for function '#{method_name}' have the same contract for input parameters: @@ -181,7 +189,9 @@ def validate_pattern_matching! #{(matched + [decorator]).map(&:to_s).join("\n")} Each definition needs to have a different contract for the parameters. - }, {}) + }, + {}, + ) end end end diff --git a/lib/contracts/method_reference.rb b/lib/contracts/method_reference.rb index 0c0c03f..f2c09b7 100644 --- a/lib/contracts/method_reference.rb +++ b/lib/contracts/method_reference.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts # MethodReference represents original method reference that was # decorated by contracts.ruby. Used for instance methods. diff --git a/lib/contracts/support.rb b/lib/contracts/support.rb index cda3a55..0e8922e 100644 --- a/lib/contracts/support.rb +++ b/lib/contracts/support.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module Support class << self @@ -8,7 +10,7 @@ def method_position(method) if file.nil? || line.nil? "" else - file + ":" + line.to_s + "#{file}:#{line}" end end @@ -45,7 +47,7 @@ def eigenclass?(target) def indent_string(string, amount) string.gsub( /^(?!$)/, - (string[/^[ \t]/] || " ") * amount + (string[/^[ \t]/] || " ") * amount, ) end diff --git a/lib/contracts/validators.rb b/lib/contracts/validators.rb index b006e78..e9d7d6a 100644 --- a/lib/contracts/validators.rb +++ b/lib/contracts/validators.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts module Validators DEFAULT_VALIDATOR_STRATEGIES = { @@ -9,6 +11,7 @@ module Validators Array => lambda do |contract| lambda do |arg| return false unless arg.is_a?(Array) && arg.length == contract.length + arg.zip(contract).all? do |_arg, _contract| Contract.valid?(_arg, _contract) end @@ -19,6 +22,7 @@ module Validators Hash => lambda do |contract| lambda do |arg| return false unless arg.is_a?(Hash) + contract.keys.all? do |k| Contract.valid?(arg[k], contract[k]) end @@ -59,7 +63,7 @@ module Validators :default => lambda do |contract| lambda { |arg| contract == arg } - end + end, }.freeze # Allows to override validator with custom one. @@ -90,7 +94,7 @@ def make_validator!(contract) else if contract.respond_to? :valid? :valid - elsif klass == Class || klass == Module + elsif [Class, Module].include?(klass) :class else :default diff --git a/lib/contracts/version.rb b/lib/contracts/version.rb index f268776..01ae3af 100644 --- a/lib/contracts/version.rb +++ b/lib/contracts/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Contracts VERSION = "0.16.1" end From 987ec3593e2f530ce0edb6d60650ff29ea635250 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Fri, 7 May 2021 09:16:04 -0500 Subject: [PATCH 49/86] contracts v0.17 --- lib/contracts/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contracts/version.rb b/lib/contracts/version.rb index 01ae3af..e44959d 100644 --- a/lib/contracts/version.rb +++ b/lib/contracts/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Contracts - VERSION = "0.16.1" + VERSION = "0.17" end From 76fa518887c326a6d6a1fd801320fbffa4584f62 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 27 May 2021 11:57:47 +0800 Subject: [PATCH 50/86] ~ Update README about Ruby version support for 0.17.x --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e3f3776..4f11ba6 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ Contracts let you clearly – even beautifully – express how your code behaves You can think of contracts as `assert` on steroids. +## 0.17.x = Ruby 3.x only + +0.17.x only supports Ruby 3.x +Looking for Ruby 2.x support? +Use 0.16.x + ## Installation gem install contracts From 93bac0cce1e92dd497249367e87e7c5cb6509714 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 25 Jan 2022 10:23:04 +0800 Subject: [PATCH 51/86] * Update GH Action config to remove meaningless `include` --- .github/workflows/tests.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1454991..dc04a61 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -23,11 +23,8 @@ jobs: - ubuntu ruby: - "3.0" - test_command: ["bundle exec rspec && bundle exec cucumber"] - include: - - os: ubuntu - ruby: "3.0" - test_command: "bundle exec rspec" + test_command: + - "bundle exec rspec && bundle exec cucumber" runs-on: ${{ matrix.os }}-latest steps: - name: Checkout From b4a5549acdca139b292929f1dd8b2ade24cac5dd Mon Sep 17 00:00:00 2001 From: Peter Goldstein Date: Mon, 24 Jan 2022 18:28:14 -0800 Subject: [PATCH 52/86] Add 3.1 after merge --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index dc04a61..eff45e8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -22,6 +22,7 @@ jobs: os: - ubuntu ruby: + - "3.1" - "3.0" test_command: - "bundle exec rspec && bundle exec cucumber" From 7455aa9d3e8fccae7ca8fa06670ed5a3c46fa129 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 5 Sep 2022 14:56:32 +0800 Subject: [PATCH 53/86] ~ Update README about "What Rubies can I use this with?" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f11ba6..f88b3ec 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Using contracts.ruby results in very little slowdown. Check out [this blog post] **Q.** What Rubies can I use this with? -**A.** It's been tested with `2.1`, `2.2`, `2.3`, `2.4`, `2.5`, `2.6` and `2.7`. +**A.** It's been tested with `3.0` and `3.1`. (In case this list becomes outdated see [`.github/workflows/tests.yaml`](/.github/workflows/tests.yaml)) If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :) From 88fd1d841615e59c873d7da64d050d3a251634dd Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 5 Oct 2022 10:27:41 +0800 Subject: [PATCH 54/86] * Update all references to Fixnum to Integer Deprecated in ruby 2.4 --- .rubocop.yml | 2 +- TUTORIAL.md | 28 ++++++++++++++-------------- lib/contracts/builtin_contracts.rb | 6 +++--- spec/builtin_contracts_spec.rb | 2 +- spec/fixtures/fixtures.rb | 18 +++++++++--------- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 0a4457d..75539f8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -64,7 +64,7 @@ Style/Documentation: Layout/LineLength: Enabled: false -# triggered by Contract ({ :name => String, :age => Fixnum }) => nil +# triggered by Contract ({ :name => String, :age => Integer }) => nil Lint/ParenthesesAsGroupedExpression: Enabled: false diff --git a/TUTORIAL.md b/TUTORIAL.md index c74ce06..3dc68af 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -80,8 +80,8 @@ contracts.ruby comes with a lot of built-in contracts, including the following: * Logical combinations * [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Maybe) – specifies that a value _may be_ nil, e.g. `Maybe[String]` (equivalent to `Or[String,nil]`) - * [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Or) – passes if any of the given contracts pass, e.g. `Or[Fixnum, Float]` - * [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Fixnum, Float]` + * [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Or) – passes if any of the given contracts pass, e.g. `Or[Integer, Float]` + * [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Integer, Float]` * [`And`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/And) – passes if all contracts pass, e.g. `And[Nat, -> (n) { n.even? }]` * [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]` @@ -89,7 +89,7 @@ contracts.ruby comes with a lot of built-in contracts, including the following: * [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]` * [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]` * [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]` or `HashOf[Symbol,String]` - * [`StrictHash`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/StrictHash) – checks that the argument is a hash, and every key passed is present in the given contract, e.g. `StrictHash[{ :description => String, :number => Fixnum }]` + * [`StrictHash`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/StrictHash) – checks that the argument is a hash, and every key passed is present in the given contract, e.g. `StrictHash[{ :description => String, :number => Integer }]` * [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]` * [`Enum`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Enum) – checks that the argument is part of a given collection of objects, e.g. `Enum[:a, :b, :c]` @@ -152,7 +152,7 @@ end You always need to specify a contract for the return value. In this example, `hello` doesn't return anything, so the contract is `nil`. Now you know that you can use a constant like `nil` as the end of a contract. Valid values for a contract are: -- the name of a class (like `String` or `Fixnum`) +- the name of a class (like `String` or `Integer`) - a constant (like `nil` or `1`) - a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not - a class that responds to the `valid?` class method (more on this later) @@ -161,32 +161,32 @@ You always need to specify a contract for the return value. In this example, `he ### A Double Function ```ruby -Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float] +Contract C::Or[Integer, Float] => C::Or[Integer, Float] def double(x) 2 * x end ``` Sometimes you want to be able to choose between a few contracts. `Or` takes a variable number of contracts and checks the argument against all of them. If it passes for any of the contracts, then the `Or` contract passes. -This introduces some new syntax. One of the valid values for a contract is an instance of a class that responds to the `valid?` method. This is what `Or[Fixnum, Float]` is. The longer way to write it would have been: +This introduces some new syntax. One of the valid values for a contract is an instance of a class that responds to the `valid?` method. This is what `Or[Integer, Float]` is. The longer way to write it would have been: ```ruby -Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float) +Contract C::Or.new(Integer, Float) => C::Or.new(Integer, Float) ``` All the built-in contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write ```ruby -Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float] +Contract C::Or[Integer, Float] => C::Or[Integer, Float] ``` or ```ruby -Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float) +Contract C::Or.new(Integer, Float) => C::Or.new(Integer, Float) ``` -whichever you prefer. They both mean the same thing here: make a new instance of `Or` with `Fixnum` and `Float`. Use that instance to validate the argument. +whichever you prefer. They both mean the same thing here: make a new instance of `Or` with `Integer` and `Float`. Use that instance to validate the argument. ### A Product Function @@ -455,7 +455,7 @@ Now you can use `Person` wherever you would have used `Or[Hash, nil]`. Your code Contracts are very easy to define. To re-iterate, there are 5 kinds of contracts: -- the name of a class (like `String` or `Fixnum`) +- the name of a class (like `String` or `Integer`) - a constant (like `nil` or `1`) - a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not - a class that responds to the `valid?` class method (more on this later) @@ -511,7 +511,7 @@ The `Or` contract takes a sequence of contracts, and passes if any of them pass. This class inherits from `CallableClass`, which allows us to use `[]` when using the class: ```ruby -Contract C::Or[Fixnum, Float] => C::Num +Contract C::Or[Integer, Float] => C::Num def double(x) 2 * x end @@ -520,7 +520,7 @@ end Without `CallableClass`, we would have to use `.new` instead: ```ruby -Contract C::Or.new(Fixnum, Float) => C::Num +Contract C::Or.new(Integer, Float) => C::Num def double(x) # etc ``` @@ -723,7 +723,7 @@ class MyBirthday < Struct.new(:day, :month) invariant(:day) { 1 <= day && day <= 31 } invariant(:month) { 1 <= month && month <= 12 } - Contract C::None => Fixnum + Contract C::None => Integer def silly_next_day! self.day += 1 end diff --git a/lib/contracts/builtin_contracts.rb b/lib/contracts/builtin_contracts.rb index 43fd293..f1ea934 100644 --- a/lib/contracts/builtin_contracts.rb +++ b/lib/contracts/builtin_contracts.rb @@ -95,7 +95,7 @@ def self.[](*vals) # Takes a variable number of contracts. # The contract passes if any of the contracts pass. - # Example: Or[Fixnum, Float] + # Example: Or[Integer, Float] class Or < CallableClass def initialize(*vals) super() @@ -120,7 +120,7 @@ def to_s # Takes a variable number of contracts. # The contract passes if exactly one of those contracts pass. - # Example: Xor[Fixnum, Float] + # Example: Xor[Integer, Float] class Xor < CallableClass def initialize(*vals) super() @@ -146,7 +146,7 @@ def to_s # Takes a variable number of contracts. # The contract passes if all contracts pass. - # Example: And[Fixnum, Float] + # Example: And[Integer, Float] class And < CallableClass def initialize(*vals) super() diff --git a/spec/builtin_contracts_spec.rb b/spec/builtin_contracts_spec.rb index a9f7257..1e571e9 100644 --- a/spec/builtin_contracts_spec.rb +++ b/spec/builtin_contracts_spec.rb @@ -30,7 +30,7 @@ def passes(&some) end describe "Num:" do - it "should pass for Fixnums" do + it "should pass for Integers" do passes { @o.double(2) } end diff --git a/spec/fixtures/fixtures.rb b/spec/fixtures/fixtures.rb index 55638c2..bf2042a 100644 --- a/spec/fixtures/fixtures.rb +++ b/spec/fixtures/fixtures.rb @@ -100,11 +100,11 @@ def sum_three(vals) end end - Contract ({ :name => String, :age => Fixnum }) => nil + Contract ({ :name => String, :age => Integer }) => nil def person(data) end - Contract C::StrictHash[{ :name => String, :age => Fixnum }] => nil + Contract C::StrictHash[{ :name => String, :age => Integer }] => nil def strict_person(data) end @@ -119,7 +119,7 @@ def hash_complex_contracts(data) def nested_hash_complex_contracts(data) end - Contract C::KeywordArgs[:name => String, :age => Fixnum] => nil + Contract C::KeywordArgs[:name => String, :age => Integer] => nil def person_keywordargs(name: "name", age: 10) end @@ -529,30 +529,30 @@ def initialize(day, month) @month = month end - Contract C::None => Fixnum + Contract C::None => Integer def silly_next_day! self.day += 1 end - Contract C::None => Fixnum + Contract C::None => Integer def silly_next_month! self.month += 1 end - Contract C::None => Fixnum + Contract C::None => Integer def clever_next_day! return clever_next_month! if day == 31 self.day += 1 end - Contract C::None => Fixnum + Contract C::None => Integer def clever_next_month! return next_year! if month == 12 self.month += 1 self.day = 1 end - Contract C::None => Fixnum + Contract C::None => Integer def next_year! self.month = 1 self.day = 1 @@ -610,7 +610,7 @@ def on_response(status, body) body + "!" end - Contract Fixnum, String => String + Contract Integer, String => String def on_response(status, body) "error #{status}: #{body}" end From 1b024a83b3ba5941e5438c91e32f43fc2199f77d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 5 Oct 2022 10:30:12 +0800 Subject: [PATCH 55/86] * Update CI to test against ruby 3.2.0-preview1 --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index eff45e8..27e1eb5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -22,6 +22,7 @@ jobs: os: - ubuntu ruby: + - "3.2.0-preview1" - "3.1" - "3.0" test_command: From f47bce043c364dfc24d238e1a847aeba04a5d056 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Tue, 18 Oct 2022 19:20:09 +0300 Subject: [PATCH 56/86] Remove outdated message in post_install_message 0.17 is already released. --- contracts.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts.gemspec b/contracts.gemspec index 9310e61..8ac2f85 100644 --- a/contracts.gemspec +++ b/contracts.gemspec @@ -15,6 +15,6 @@ Gem::Specification.new do |s| s.required_ruby_version = [">= 3.0", "< 4"] s.post_install_message = " 0.16.x will be the supporting Ruby 2.x and be feature frozen (only fixes will be released) - For Ruby 3.x use 0.17.x or later (might not be released yet) + For Ruby 3.x use 0.17.x or later " end From 58a59e7c859b67ffced290328804c1e1290e2900 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 6 Mar 2023 11:12:04 +0800 Subject: [PATCH 57/86] Update README to replace outdated badge --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f88b3ec..2b0b518 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ This project is looking for a new maintainer! [More details here](https://github.com/egonSchiele/contracts.ruby/issues/249) -# contracts.ruby [![GitHub Build Status](https://img.shields.io/github/workflow/status/egonSchiele/contracts.ruby/Tests?style=flat-square)](https://github.com/egonSchiele/contracts.ruby/actions?query=workflow%3ATests) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby) + + +# contracts.ruby [![GitHub Build Status](https://img.shields.io/github/actions/workflow/status/egonSchiele/contracts.ruby/tests.yaml?branch=master&style=flat-square)](https://github.com/egonSchiele/contracts.ruby/actions/workflows/tests.yaml) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby) Contracts let you clearly – even beautifully – express how your code behaves, and free you from writing tons of boilerplate, defensive code. From 4abfb3dad1200153606537cce3ddf60f40c055da Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 26 Aug 2024 16:44:39 +0800 Subject: [PATCH 58/86] * Update CI about ruby & action versions --- .github/workflows/code_style_checks.yaml | 2 +- .github/workflows/tests.yaml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/code_style_checks.yaml b/.github/workflows/code_style_checks.yaml index 14b2c41..ab2a245 100644 --- a/.github/workflows/code_style_checks.yaml +++ b/.github/workflows/code_style_checks.yaml @@ -26,7 +26,7 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 27e1eb5..7d36288 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -22,7 +22,8 @@ jobs: os: - ubuntu ruby: - - "3.2.0-preview1" + - "3.3" + - "3.2" - "3.1" - "3.0" test_command: @@ -30,7 +31,7 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: From 2f8dd91ffbcd82e192afeeee7292c1f8d4cf8b02 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 27 Aug 2024 09:09:41 +0800 Subject: [PATCH 59/86] ^ Update workflow for code style check about ruby version --- .github/workflows/code_style_checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_style_checks.yaml b/.github/workflows/code_style_checks.yaml index ab2a245..0be451a 100644 --- a/.github/workflows/code_style_checks.yaml +++ b/.github/workflows/code_style_checks.yaml @@ -22,7 +22,7 @@ jobs: os: - ubuntu ruby: - - "3.0" + - "3.3" runs-on: ${{ matrix.os }}-latest steps: - name: Checkout From 9029faaf627f4e0be3188c1fcb9b81ac28f8d18e Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 27 Aug 2024 09:34:59 +0800 Subject: [PATCH 60/86] ! Fix CI due to changed `#inspect` output --- features/builtin_contracts/none.feature | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/features/builtin_contracts/none.feature b/features/builtin_contracts/none.feature index 6b5cd85..4a16ec3 100644 --- a/features/builtin_contracts/none.feature +++ b/features/builtin_contracts/none.feature @@ -26,7 +26,8 @@ Feature: None def autorescue yield rescue => e - puts e.inspect + # Since ruby 3.2 the `#inspect` output becomes a bit different + puts e.inspect.gsub(/^# Symbol + def a_symbol(*args) + :a_symbol + end end """ @@ -61,14 +67,14 @@ Feature: None Given a file named "anything.rb" with: """ruby require "./none_usage" - autorescue { Example.a_symbol(nil) } - autorescue { Example.a_symbol(12) } - autorescue { Example.a_symbol(37.5) } - autorescue { Example.a_symbol("foo") } - autorescue { Example.a_symbol(:foo) } - autorescue { Example.a_symbol({}) } - autorescue { Example.a_symbol([]) } - autorescue { Example.a_symbol(Object) } + autorescue { Example.new.a_symbol(nil) } + autorescue { Example.new.a_symbol(12) } + autorescue { Example.new.a_symbol(37.5) } + autorescue { Example.new.a_symbol("foo") } + autorescue { Example.new.a_symbol(:foo) } + autorescue { Example.new.a_symbol({}) } + autorescue { Example.new.a_symbol([]) } + autorescue { Example.new.a_symbol(Object) } """ When I run `ruby anything.rb` From 8be37e85bded22c00422d515efa1a01265d0ff52 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 27 Aug 2024 09:36:59 +0800 Subject: [PATCH 61/86] $ Fix RuboCop violations --- lib/contracts/formatters.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/contracts/formatters.rb b/lib/contracts/formatters.rb index 3ee27bc..6644ba1 100644 --- a/lib/contracts/formatters.rb +++ b/lib/contracts/formatters.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true +# rubocop:disable Lint/RedundantRequireStatement +# If removed, spec/module_spec fails require "pp" +# rubocop:enable Lint/RedundantRequireStatement module Contracts # A namespace for classes related to formatting. From b0eccc38a2822d2a2bae6d1a812e0a09fcf3d2b4 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 27 Aug 2024 10:52:11 +0800 Subject: [PATCH 62/86] ! Fix keyword arguments contract when used with optional positional arguments --- ...args_with_optional_positional_args.feature | 50 +++++++++++++++++++ lib/contracts.rb | 30 +++-------- lib/contracts/call_with.rb | 17 +++++-- 3 files changed, 70 insertions(+), 27 deletions(-) create mode 100644 features/builtin_contracts/keyword_args_with_optional_positional_args.feature diff --git a/features/builtin_contracts/keyword_args_with_optional_positional_args.feature b/features/builtin_contracts/keyword_args_with_optional_positional_args.feature new file mode 100644 index 0000000..52a02f5 --- /dev/null +++ b/features/builtin_contracts/keyword_args_with_optional_positional_args.feature @@ -0,0 +1,50 @@ +Feature: KeywordArgs when used with optional positional arguments + + Checks that the argument is an options hash, and all required keyword arguments are present, and all values pass their respective contracts + + ```ruby + Contract Any, KeywordArgs[:number => Num, :description => Optional[String]] => Any + ``` + + Background: + Given a file named "keyword_args_with_optional_positional_args_usage.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + Contract C::Any, String, C::KeywordArgs[b: C::Optional[String]] => Symbol + def foo(output, a = 'a', b: 'b') + p [a, b] + output + end + end + """ + + Scenario: Accepts arguments when only require arguments filled and valid + Given a file named "accepts_all_filled_valid_args.rb" with: + """ruby + require "./keyword_args_with_optional_positional_args_usage" + puts Example.new.foo(:output) + """ + When I run `ruby accepts_all_filled_valid_args.rb` + Then output should contain: + """ + ["a", "b"] + output + """ + + Scenario: Accepts arguments when all filled and valid + Given a file named "accepts_all_filled_valid_args.rb" with: + """ruby + require "./keyword_args_with_optional_positional_args_usage" + puts Example.new.foo(:output, 'c', b: 'd') + """ + When I run `ruby accepts_all_filled_valid_args.rb` + Then output should contain: + """ + ["c", "d"] + output + """ diff --git a/lib/contracts.rb b/lib/contracts.rb index e8bab85..e69e6ff 100644 --- a/lib/contracts.rb +++ b/lib/contracts.rb @@ -52,7 +52,7 @@ class Contract < Contracts::Decorator end end - attr_reader :args_contracts, :ret_contract, :klass, :method + attr_reader :args_contracts, :kargs_contract, :ret_contract, :klass, :method def initialize(klass, method, *contracts) super(klass, method) @@ -69,6 +69,9 @@ def initialize(klass, method, *contracts) # internally we just convert that return value syntax back to an array @args_contracts = contracts[0, contracts.size - 1] + contracts[-1].keys + # Extract contract for keyword arguments + @kargs_contract = args_contracts.find { |c| c.is_a?(Contracts::Builtin::KeywordArgs) } + args_contracts.delete(kargs_contract) if kargs_contract @ret_contract = contracts[-1].values[0] @@ -76,6 +79,8 @@ def initialize(klass, method, *contracts) Contract.make_validator(contract) end + @kargs_validator = kargs_contract ? Contract.make_validator(kargs_contract) : nil + @args_contract_index = args_contracts.index do |contract| contract.is_a? Contracts::Args end @@ -93,16 +98,6 @@ def initialize(klass, method, *contracts) # ==== - # == @has_options_contract - last_contract = args_contracts.last - penultimate_contract = args_contracts[-2] - @has_options_contract = if @has_proc_contract - penultimate_contract.is_a?(Contracts::Builtin::KeywordArgs) - else - last_contract.is_a?(Contracts::Builtin::KeywordArgs) - end - # === - @klass, @method = klass, method end @@ -255,19 +250,6 @@ def maybe_append_block! args, blk true end - # Same thing for when we have named params but didn't pass any in. - # returns true if it appended nil - def maybe_append_options! args, kargs, blk - return false unless @has_options_contract - - if @has_proc_contract && args_contracts[-2].is_a?(Contracts::Builtin::KeywordArgs) - args.insert(-2, kargs) - elsif args_contracts[-1].is_a?(Contracts::Builtin::KeywordArgs) - args << kargs - end - true - end - # Used to determine type of failure exception this contract should raise in case of failure def failure_exception if pattern_match? diff --git a/lib/contracts/call_with.rb b/lib/contracts/call_with.rb index eff5687..1bc9d6f 100644 --- a/lib/contracts/call_with.rb +++ b/lib/contracts/call_with.rb @@ -12,8 +12,20 @@ def call_with_inner(returns, this, *args, **kargs, &blk) # Explicitly append blk=nil if nil != Proc contract violation anticipated nil_block_appended = maybe_append_block!(args, blk) - # Explicitly append options={} if Hash contract is present - kargs_appended = maybe_append_options!(args, kargs, blk) + if @kargs_validator && !@kargs_validator[kargs] + data = { + arg: kargs, + contract: kargs_contract, + class: klass, + method: method, + contracts: self, + arg_pos: :keyword, + total_args: args.size, + return_value: false, + } + return ParamContractError.new("as return value", data) if returns + return unless Contract.failure_callback(data) + end # Loop forward validating the arguments up to the splat (if there is one) (@args_contract_index || args.size).times do |i| @@ -84,7 +96,6 @@ def call_with_inner(returns, this, *args, **kargs, &blk) # If we put the block into args for validating, restore the args # OR if we added a fake nil at the end because a block wasn't passed in. args.slice!(-1) if blk || nil_block_appended - args.slice!(-1) if kargs_appended result = if method.respond_to?(:call) # proc, block, lambda, etc method.call(*args, **kargs, &blk) From 79d3f78d50830b56757afc5f671f5958b6aff400 Mon Sep 17 00:00:00 2001 From: Vlad Pisanov Date: Sun, 22 Sep 2024 07:20:07 -0400 Subject: [PATCH 63/86] Always load version.rb, suppress legacy deprecation warning --- contracts.gemspec | 4 ---- lib/contracts.rb | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts.gemspec b/contracts.gemspec index 8ac2f85..320e8d7 100644 --- a/contracts.gemspec +++ b/contracts.gemspec @@ -13,8 +13,4 @@ Gem::Specification.new do |s| s.homepage = "https://github.com/egonSchiele/contracts.ruby" s.license = "BSD-2-Clause" s.required_ruby_version = [">= 3.0", "< 4"] - s.post_install_message = " - 0.16.x will be the supporting Ruby 2.x and be feature frozen (only fixes will be released) - For Ruby 3.x use 0.17.x or later - " end diff --git a/lib/contracts.rb b/lib/contracts.rb index e8bab85..7a9d9eb 100644 --- a/lib/contracts.rb +++ b/lib/contracts.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "contracts/version" require "contracts/attrs" require "contracts/builtin_contracts" require "contracts/decorators" From 02fc8ef53562ddd1d39ce7bd704c08038392f79b Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 23 Sep 2024 11:26:42 +0800 Subject: [PATCH 64/86] * Add more test cases to cover more possible combinations --- ...args_with_optional_positional_args.feature | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/features/builtin_contracts/keyword_args_with_optional_positional_args.feature b/features/builtin_contracts/keyword_args_with_optional_positional_args.feature index 52a02f5..b260070 100644 --- a/features/builtin_contracts/keyword_args_with_optional_positional_args.feature +++ b/features/builtin_contracts/keyword_args_with_optional_positional_args.feature @@ -48,3 +48,29 @@ Feature: KeywordArgs when used with optional positional arguments ["c", "d"] output """ + + Scenario: Accepts arguments when only require arguments & optional keyword arguments filled and valid + Given a file named "accepts_all_filled_valid_args.rb" with: + """ruby + require "./keyword_args_with_optional_positional_args_usage" + puts Example.new.foo(:output, b: 'd') + """ + When I run `ruby accepts_all_filled_valid_args.rb` + Then output should contain: + """ + ["a", "d"] + output + """ + + Scenario: Accepts arguments when only require arguments & optional positional arguments filled and valid + Given a file named "accepts_all_filled_valid_args.rb" with: + """ruby + require "./keyword_args_with_optional_positional_args_usage" + puts Example.new.foo(:output, 'c') + """ + When I run `ruby accepts_all_filled_valid_args.rb` + Then output should contain: + """ + ["c", "b"] + output + """ From dd6ac00d6ede9ecee1b9cd58cc98774fafda7852 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Sat, 5 Oct 2024 11:51:30 -0500 Subject: [PATCH 65/86] v0.17.1 --- lib/contracts/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contracts/version.rb b/lib/contracts/version.rb index e44959d..9d8f6cd 100644 --- a/lib/contracts/version.rb +++ b/lib/contracts/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Contracts - VERSION = "0.17" + VERSION = "0.17.1" end From 36f8903989da6cff329ccb3e3090f3e101917ae6 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 7 Oct 2024 13:49:03 +0800 Subject: [PATCH 66/86] ~ Update CHANGELOG --- CHANGELOG.markdown | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 09bb4ef..024a111 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,4 +1,18 @@ +## [v0.17.1] - 2024-10-06 + +[v0.17.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.17...v0.17.1 + +- Bugfix: Fix keyword arguments contract when used with optional positional arguments - [PikachuEXE](https://github.com/PikachuEXE) [#305](https://github.com/egonSchiele/contracts.ruby/pull/305) +- Enhancement: Always load version.rb, suppress legacy deprecation warning - [Vlad Pisanov](https://github.com/vlad-pisanov) [#301](https://github.com/egonSchiele/contracts.ruby/pull/306) +- Enhancement: Update doc & spec about deprecated `Fixnum` to `Integer` - [PikachuEXE](https://github.com/PikachuEXE) [#301](https://github.com/egonSchiele/contracts.ruby/pull/301) + +## [v0.17] - 2021-09-28 + +[v0.17]: https://github.com/egonSchiele/contracts.ruby/compare/v0.16.1...v0.17 + +- Update implementation & spec to be 3.0 compatible **Support for Ruby 2 has been discontinued** - [PikachuEXE](https://github.com/PikachuEXE) [#295](https://github.com/egonSchiele/contracts.ruby/pull/295) + ## [v0.16.1] - 2021-04-17 [v0.16.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.16.0...v0.16.1 @@ -20,7 +34,7 @@ [v0.15.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.14.0...v0.15.0 - Bugfix: Func contract's return value isn't enforced with blocks - [Piotr Szmielew](https://github.com/esse) [#251](https://github.com/egonSchiele/contracts.ruby/pull/251) -- Bugfx: Fix contracts used in AR-models - [Gert Goet](https://github.com/eval) [#237](https://github.com/egonSchiele/contracts.ruby/pull/237) +- Bugfix: Fix contracts used in AR-models - [Gert Goet](https://github.com/eval) [#237](https://github.com/egonSchiele/contracts.ruby/pull/237) ## [v0.14.0] - 2016-04-26 From 0c578a4650c349d6afb724ad291295a88cb95bb3 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 7 Oct 2024 13:49:36 +0800 Subject: [PATCH 67/86] * Don't run CI with doc change --- .github/workflows/code_style_checks.yaml | 2 ++ .github/workflows/tests.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/code_style_checks.yaml b/.github/workflows/code_style_checks.yaml index 0be451a..6fe6f7a 100644 --- a/.github/workflows/code_style_checks.yaml +++ b/.github/workflows/code_style_checks.yaml @@ -6,11 +6,13 @@ on: - master paths-ignore: - 'README.md' + - 'CHANGELOG.markdown' push: branches: - master paths-ignore: - 'README.md' + - 'CHANGELOG.markdown' jobs: rubocop: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 7d36288..a1ca427 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -6,11 +6,13 @@ on: - master paths-ignore: - 'README.md' + - 'CHANGELOG.markdown' push: branches: - master paths-ignore: - 'README.md' + - 'CHANGELOG.markdown' jobs: unit_tests: From 7ccb4918f16b627f2ac108d850150a5056586f99 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 8 Oct 2024 08:55:07 +0800 Subject: [PATCH 68/86] ! Fix pattern matching --- features/advanced/pattern_matching.feature | 104 +++++++++++++++++++++ lib/contracts/method_handler.rb | 4 +- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 features/advanced/pattern_matching.feature diff --git a/features/advanced/pattern_matching.feature b/features/advanced/pattern_matching.feature new file mode 100644 index 0000000..20289ca --- /dev/null +++ b/features/advanced/pattern_matching.feature @@ -0,0 +1,104 @@ +Feature: Method Overloading + + You can use contracts for method overloading! This is commonly called "pattern matching" in functional programming languages. + + ```ruby + Contract 1 => 1 + def fact x + x + end + + Contract C::Num => C::Num + def fact x + x * fact(x - 1) + end + ``` + + Background: + Given a file named "method_overloading_with_positional_args_usage.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + Contract 1 => 1 + def fact(x) + x + end + + Contract C::Num => C::Num + def fact(x) + x * fact(x - 1) + end + end + """ + + Given a file named "method_overloading_with_keyword_args_usage.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + Contract C::KeywordArgs[age: Integer, size: Symbol] => String + def speak(age:, size:) + "age: #{age} size: #{size}" + end + + Contract C::KeywordArgs[sound: String] => String + def speak(sound:) + "sound: #{sound}" + end + end + """ + + Scenario: Positional Args Method 1 + Given a file named "positional_args_method_1.rb" with: + """ruby + require "./method_overloading_with_positional_args_usage" + puts Example.new.fact(1) + """ + When I run `ruby positional_args_method_1.rb` + Then the output should contain: + """ + 1 + """ + + Scenario: Positional Args Method 2 + Given a file named "positional_args_method_2.rb" with: + """ruby + require "./method_overloading_with_positional_args_usage" + puts Example.new.fact(4) + """ + When I run `ruby positional_args_method_2.rb` + Then the output should contain: + """ + 24 + """ + + Scenario: Keyword Args Method 1 + Given a file named "keyword_args_method_1.rb" with: + """ruby + require "./method_overloading_with_keyword_args_usage" + puts Example.new.speak(age: 5, size: :large) + """ + When I run `ruby keyword_args_method_1.rb` + Then the output should contain: + """ + age: 5 size: large + """ + + Scenario: Keyword Args Method 2 + Given a file named "keyword_args_method_2.rb" with: + """ruby + require "./method_overloading_with_keyword_args_usage" + puts Example.new.speak(sound: "woof") + """ + When I run `ruby keyword_args_method_2.rb` + Then the output should contain: + """ + sound: woof + """ diff --git a/lib/contracts/method_handler.rb b/lib/contracts/method_handler.rb index 5378e6a..1d683e2 100644 --- a/lib/contracts/method_handler.rb +++ b/lib/contracts/method_handler.rb @@ -174,8 +174,10 @@ def foo x def validate_pattern_matching! new_args_contract = decorator.args_contracts + new_kargs_contract = decorator.kargs_contract matched = decorated_methods.select do |contract| - contract.args_contracts == new_args_contract + contract.args_contracts == new_args_contract && + contract.kargs_contract == new_kargs_contract end return if matched.empty? From 43457aa16a23c41806eca8176a7ae725500073c9 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 9 Oct 2024 08:00:37 +0800 Subject: [PATCH 69/86] * Add test case for incorrect order of method overloading --- features/advanced/pattern_matching.feature | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/features/advanced/pattern_matching.feature b/features/advanced/pattern_matching.feature index 20289ca..68b0e7e 100644 --- a/features/advanced/pattern_matching.feature +++ b/features/advanced/pattern_matching.feature @@ -102,3 +102,32 @@ Feature: Method Overloading """ sound: woof """ + + Scenario: Incorrect Positional Args Method + Given a file named "incorrect_positional_args_method.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + # Notice that this method's contract is wider than the one below + # This would cause this method to be called every time but never the one below + Contract C::Num => C::Num + def fact(x) + x * fact(x - 1) + end + + Contract 1 => 1 + def fact(x) + x + end + end + puts Example.new.fact(4) + """ + When I run `ruby incorrect_positional_args_method.rb` + Then the output should contain: + """ + stack level too deep (SystemStackError) + """ From 0f24626161cfd905d7acff6a615319d8ff7fede9 Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Tue, 15 Oct 2024 09:22:53 -0500 Subject: [PATCH 70/86] v0.17.2 --- lib/contracts/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contracts/version.rb b/lib/contracts/version.rb index 9d8f6cd..94b89b8 100644 --- a/lib/contracts/version.rb +++ b/lib/contracts/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Contracts - VERSION = "0.17.1" + VERSION = "0.17.2" end From 8329024bc87f1aa018a58d84b2775d23632fc716 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 16 Oct 2024 08:39:27 +0800 Subject: [PATCH 71/86] ~ Update CHANGELOG --- CHANGELOG.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 024a111..634f585 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,4 +1,10 @@ +## [v0.17.2] - 2024-10-06 + +[v0.17.2]: https://github.com/egonSchiele/contracts.ruby/compare/v0.17.1...v0.17.2 + +- Bugfix: Fix pattern matching - [PikachuEXE](https://github.com/PikachuEXE) [#308](https://github.com/egonSchiele/contracts.ruby/pull/308) + ## [v0.17.1] - 2024-10-06 [v0.17.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.17...v0.17.1 From b2ffcca90ffa9fbf4a7293065a8926e7b13ac9f2 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 27 Nov 2024 09:39:28 +0800 Subject: [PATCH 72/86] ! Fix a failed test case in 3.4 due to error message format diff in ruby 3.4 --- spec/ruby_version_specific/contracts_spec_2.1.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/ruby_version_specific/contracts_spec_2.1.rb b/spec/ruby_version_specific/contracts_spec_2.1.rb index 36b6ede..01180c8 100644 --- a/spec/ruby_version_specific/contracts_spec_2.1.rb +++ b/spec/ruby_version_specific/contracts_spec_2.1.rb @@ -56,7 +56,11 @@ def complicated(a, b = true, *c, d, e:, f:2, **g, &h) @o.complicated("a", true, :b, :c, 2.0, e: (1..5), f: nil, g: :d) do |x| x end - end.to raise_error(ContractError, /Expected: \(KeywordArgs\[{:e=>Range, :f=>Optional\[Num\], :g=>Symbol}\]\)/) + end.to raise_error( + ContractError, + # 3.3- vs 3.4+ error message format + /(Expected: \(KeywordArgs\[{:e=>Range, :f=>Optional\[Num\], :g=>Symbol}\]\)|Expected: \(KeywordArgs\[{e: Range, f: Optional\[Num\], g: Symbol}\]\))/, + ) end end end From 727faf43081edc063e45c34956c0b8e2e48c3e98 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 27 Nov 2024 09:44:12 +0800 Subject: [PATCH 73/86] ^ Update cucumber 1.x > 9.x to fix failure in ruby 3.4 --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 540761b..a31ba67 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ gemspec group :test do gem "aruba" - gem "cucumber", "~> 1.3.20" + gem "cucumber", "~> 9.2" gem "rspec" gem "rubocop", ">= 1.0.0" From f3df8cf3264870e48cc7127c23d345d65ee0e846 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 27 Nov 2024 09:48:58 +0800 Subject: [PATCH 74/86] * Update CI to run tests on ruby-head too --- .github/workflows/tests.yaml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index a1ca427..e4832f1 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -22,15 +22,21 @@ jobs: fail-fast: false matrix: os: - - ubuntu + - ubuntu-latest ruby: - "3.3" - "3.2" - "3.1" - "3.0" - test_command: - - "bundle exec rspec && bundle exec cucumber" - runs-on: ${{ matrix.os }}-latest + allow_failures: + - false + include: + - os: ubuntu-latest + ruby: ruby-head + allow_failures: true + env: + ALLOW_FAILURES: "${{ matrix.allow_failures }}" + runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v4 @@ -40,4 +46,4 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Test - run: ${{ matrix.test_command }} + run: "bundle exec rspec && bundle exec cucumber || $ALLOW_FAILURES" From c9dcb82a92416285a9cdfefb1896a1f76180c0d3 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Sat, 30 Nov 2024 14:26:23 +0800 Subject: [PATCH 75/86] + Add config for dependabot --- .github/dependabot.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..2e78381 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + + - package-ecosystem: bundler + directory: "/" + schedule: + interval: monthly + time: "06:00" + timezone: Asia/Hong_Kong + open-pull-requests-limit: 10 + labels: + - "dependencies" + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: monthly + time: "06:00" + timezone: Asia/Hong_Kong + open-pull-requests-limit: 10 + labels: + - "dependencies" From c9d75e6dd296e75c0189df4caa2cc133d1b88eec Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 26 Dec 2024 08:51:59 +0800 Subject: [PATCH 76/86] * Update CI for ruby 3.4 --- .github/workflows/code_style_checks.yaml | 2 +- .github/workflows/tests.yaml | 7 ++++--- Gemfile | 8 +++++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/code_style_checks.yaml b/.github/workflows/code_style_checks.yaml index 6fe6f7a..ed193e9 100644 --- a/.github/workflows/code_style_checks.yaml +++ b/.github/workflows/code_style_checks.yaml @@ -24,7 +24,7 @@ jobs: os: - ubuntu ruby: - - "3.3" + - "3.4" runs-on: ${{ matrix.os }}-latest steps: - name: Checkout diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e4832f1..bf29bde 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -24,10 +24,11 @@ jobs: os: - ubuntu-latest ruby: - - "3.3" - - "3.2" - - "3.1" - "3.0" + - "3.1" + - "3.2" + - "3.3" + - "3.4" allow_failures: - false include: diff --git a/Gemfile b/Gemfile index a31ba67..2fc27a1 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,13 @@ gemspec group :test do gem "aruba" - gem "cucumber", "~> 9.2" + if RUBY_VERSION >= '3.4' + # Cucumber is broken on Ruby 3.4, requires the fix in + # https://github.com/cucumber/cucumber-ruby/pull/1757 + gem "cucumber", ">= 9.2", git: 'https://github.com/cucumber/cucumber-ruby' + else + gem "cucumber", "~> 9.2" + end gem "rspec" gem "rubocop", ">= 1.0.0" From 233b08fcba010bff0b39e6c25137285a14d1e588 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 26 Dec 2024 08:53:28 +0800 Subject: [PATCH 77/86] $ Fix RuboCop violations --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 2fc27a1..e346981 100644 --- a/Gemfile +++ b/Gemfile @@ -6,10 +6,10 @@ gemspec group :test do gem "aruba" - if RUBY_VERSION >= '3.4' + if RUBY_VERSION >= "3.4" # Cucumber is broken on Ruby 3.4, requires the fix in # https://github.com/cucumber/cucumber-ruby/pull/1757 - gem "cucumber", ">= 9.2", git: 'https://github.com/cucumber/cucumber-ruby' + gem "cucumber", ">= 9.2", git: "https://github.com/cucumber/cucumber-ruby" else gem "cucumber", "~> 9.2" end From e2428539e4011c8cb13717ef96cdd6e0f57c0c22 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 26 Dec 2024 09:35:02 +0800 Subject: [PATCH 78/86] ! Fix cucumber for ruby 3.4 --- .github/workflows/tests.yaml | 2 +- Gemfile | 2 +- Rakefile | 8 + ... => pretty-print_@before_ruby_3_3.feature} | 3 +- .../pretty-print_after_ruby_3_4.feature | 242 ++++++++++++++++++ 5 files changed, 254 insertions(+), 3 deletions(-) rename features/basics/{pretty-print.feature => pretty-print_@before_ruby_3_3.feature} (98%) create mode 100644 features/basics/pretty-print_after_ruby_3_4.feature diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index bf29bde..4245abe 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -47,4 +47,4 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Test - run: "bundle exec rspec && bundle exec cucumber || $ALLOW_FAILURES" + run: "bundle exec rspec && bundle exec rake cucumber || $ALLOW_FAILURES" diff --git a/Gemfile b/Gemfile index e346981..639b63f 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ group :test do if RUBY_VERSION >= "3.4" # Cucumber is broken on Ruby 3.4, requires the fix in # https://github.com/cucumber/cucumber-ruby/pull/1757 - gem "cucumber", ">= 9.2", git: "https://github.com/cucumber/cucumber-ruby" + gem "cucumber", ">= 9.2", git: "https://github.com/cucumber/cucumber-ruby", ref: "a468bc682eec68ef5b5660a17c4c0e7e52cfc67b" else gem "cucumber", "~> 9.2" end diff --git a/Rakefile b/Rakefile index fd4014c..5723879 100644 --- a/Rakefile +++ b/Rakefile @@ -2,6 +2,14 @@ task :default => [:spec] +task :cucumber do + if RUBY_VERSION >= "3.4" + sh "cucumber --tags 'not @before_ruby_3_3'" + else + sh "cucumber --tags 'not @after_ruby_3_4'" + end +end + task :add_tag do `git tag -a v#{Contracts::VERSION} -m 'v#{Contracts::VERSION}'` end diff --git a/features/basics/pretty-print.feature b/features/basics/pretty-print_@before_ruby_3_3.feature similarity index 98% rename from features/basics/pretty-print.feature rename to features/basics/pretty-print_@before_ruby_3_3.feature index af88f49..76d3915 100644 --- a/features/basics/pretty-print.feature +++ b/features/basics/pretty-print_@before_ruby_3_3.feature @@ -1,4 +1,5 @@ -Feature: Pretty printing Contract violations +@before_ruby_3_3 +Feature: Pretty printing Contract violations (Ruby 3.3-) Scenario: Big array argument being passed to big array method parameter Given a file named "example.rb" with: diff --git a/features/basics/pretty-print_after_ruby_3_4.feature b/features/basics/pretty-print_after_ruby_3_4.feature new file mode 100644 index 0000000..4280af8 --- /dev/null +++ b/features/basics/pretty-print_after_ruby_3_4.feature @@ -0,0 +1,242 @@ +@after_ruby_3_4 +Feature: Pretty printing Contract violations (Ruby 3.4+) + + Scenario: Big array argument being passed to big array method parameter + Given a file named "example.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + class << self + Contract [ + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol] + ] => nil + def run(data) + nil + end + end + end + + puts Example.run([ + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"] + ]) + """ + When I run `ruby example.rb` + Then the output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: [(String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol)], + Actual: [["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"]] + Value guarded in: Example::run + With Contract: Array => NilClass + At: example.rb:17 + """ + + Scenario: Big array value being returned from method expecting different big array type + Given a file named "example.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + class << self + Contract C::None => [ + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol], + C::Or[String, Symbol] + ] + def run + [ + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"] + ] + end + end + end + + puts Example.run + """ + When I run `ruby example.rb` + Then the output should contain: + """ + : Contract violation for return value: (ReturnContractError) + Expected: [(String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol), + (String or Symbol)], + Actual: [["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"], + ["foo", "foo"]] + Value guarded in: Example::run + With Contract: None => Array + At: example.rb:17 + """ + + Scenario: Big hash argument being passed to big hash method parameter + Given a file named "example.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + class << self + Contract ({ + a: C::Or[String, Symbol], + b: C::Or[String, Symbol], + c: C::Or[String, Symbol], + d: C::Or[String, Symbol], + e: C::Or[String, Symbol], + f: C::Or[String, Symbol], + g: C::Or[String, Symbol] + }) => nil + def run(data) + nil + end + end + end + + puts Example.run({ + a: ["foo", "foo"], + b: ["foo", "foo"], + c: ["foo", "foo"], + d: ["foo", "foo"], + e: ["foo", "foo"], + f: ["foo", "foo"], + g: ["foo", "foo"] + }) + """ + When I run `ruby example.rb` + Then the output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: {a: (String or Symbol), + b: (String or Symbol), + c: (String or Symbol), + d: (String or Symbol), + e: (String or Symbol), + f: (String or Symbol), + g: (String or Symbol)}, + Actual: {a: ["foo", "foo"], + b: ["foo", "foo"], + c: ["foo", "foo"], + d: ["foo", "foo"], + e: ["foo", "foo"], + f: ["foo", "foo"], + g: ["foo", "foo"]} + Value guarded in: Example::run + With Contract: Hash => NilClass + At: example.rb:17 + """ + + Scenario: Big hash value being returned from method expecting different big hash type + Given a file named "example.rb" with: + """ruby + require "contracts" + C = Contracts + + class Example + include Contracts::Core + + class << self + Contract C::None => ({ + a: C::Or[String, Symbol], + b: C::Or[String, Symbol], + c: C::Or[String, Symbol], + d: C::Or[String, Symbol], + e: C::Or[String, Symbol], + f: C::Or[String, Symbol], + g: C::Or[String, Symbol] + }) + def run + { + a: ["foo", "foo"], + b: ["foo", "foo"], + c: ["foo", "foo"], + d: ["foo", "foo"], + e: ["foo", "foo"], + f: ["foo", "foo"], + g: ["foo", "foo"] + } + end + end + end + + puts Example.run + """ + When I run `ruby example.rb` + Then the output should contain: + """ + : Contract violation for return value: (ReturnContractError) + Expected: {a: (String or Symbol), + b: (String or Symbol), + c: (String or Symbol), + d: (String or Symbol), + e: (String or Symbol), + f: (String or Symbol), + g: (String or Symbol)}, + Actual: {a: ["foo", "foo"], + b: ["foo", "foo"], + c: ["foo", "foo"], + d: ["foo", "foo"], + e: ["foo", "foo"], + f: ["foo", "foo"], + g: ["foo", "foo"]} + Value guarded in: Example::run + With Contract: None => Hash + At: example.rb:17 + """ From 4ffd358a05d7ea5a3ea06c8680f71d75c6fc573f Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Jan 2025 11:09:14 +0800 Subject: [PATCH 79/86] * Use new released version of cucumber 9 for ruby 3.4 instead of git --- Gemfile | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index 639b63f..4dbbd17 100644 --- a/Gemfile +++ b/Gemfile @@ -6,13 +6,7 @@ gemspec group :test do gem "aruba" - if RUBY_VERSION >= "3.4" - # Cucumber is broken on Ruby 3.4, requires the fix in - # https://github.com/cucumber/cucumber-ruby/pull/1757 - gem "cucumber", ">= 9.2", git: "https://github.com/cucumber/cucumber-ruby", ref: "a468bc682eec68ef5b5660a17c4c0e7e52cfc67b" - else - gem "cucumber", "~> 9.2" - end + gem "cucumber", ">= 9.2.1" gem "rspec" gem "rubocop", ">= 1.0.0" From f9f9d2ff743a727f50837534cad16d8c4c3460b3 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 1 Sep 2025 09:28:15 +0800 Subject: [PATCH 80/86] * Update rubocop config to use `plugins` instead of `require` --- .rubocop_todo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2005c69..2235d05 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,7 +6,7 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -require: +plugins: - rubocop-performance # Offense count: 2 From 075671150364f4a737595da5d19b0993010d49cd Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 1 Sep 2025 09:29:47 +0800 Subject: [PATCH 81/86] $ Remove now not needed rubocop cop disable comment --- lib/contracts/formatters.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/contracts/formatters.rb b/lib/contracts/formatters.rb index 6644ba1..5f10a44 100644 --- a/lib/contracts/formatters.rb +++ b/lib/contracts/formatters.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true -# rubocop:disable Lint/RedundantRequireStatement # If removed, spec/module_spec fails require "pp" -# rubocop:enable Lint/RedundantRequireStatement module Contracts # A namespace for classes related to formatting. From edce7796ad4429a6a418c328d4a1260a4688e89f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 01:31:53 +0000 Subject: [PATCH 82/86] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/code_style_checks.yaml | 2 +- .github/workflows/tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_style_checks.yaml b/.github/workflows/code_style_checks.yaml index ed193e9..2f1aae3 100644 --- a/.github/workflows/code_style_checks.yaml +++ b/.github/workflows/code_style_checks.yaml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4245abe..a3506a8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -40,7 +40,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: From b88e79c3cba1560e2b50e7898c4ea5ebbbe353d2 Mon Sep 17 00:00:00 2001 From: Taketo Takashima Date: Sat, 29 Nov 2025 00:42:29 +0900 Subject: [PATCH 83/86] Relax `required_ruby_version` to support Ruby 4.0 Add Ruby 4.0 to CI Matrix Co-authored-by: PikachuEXE --- .github/workflows/tests.yaml | 1 + contracts.gemspec | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index a3506a8..926d1d7 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -29,6 +29,7 @@ jobs: - "3.2" - "3.3" - "3.4" + - "4.0" allow_failures: - false include: diff --git a/contracts.gemspec b/contracts.gemspec index 320e8d7..f05b290 100644 --- a/contracts.gemspec +++ b/contracts.gemspec @@ -12,5 +12,5 @@ Gem::Specification.new do |s| s.files = `git ls-files`.split("\n") s.homepage = "https://github.com/egonSchiele/contracts.ruby" s.license = "BSD-2-Clause" - s.required_ruby_version = [">= 3.0", "< 4"] + s.required_ruby_version = [">= 3.0", "< 5"] end From 02fadfbc174c7313742ebb991325f09c4ff22957 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Nov 2025 22:02:00 +0000 Subject: [PATCH 84/86] Bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/code_style_checks.yaml | 2 +- .github/workflows/tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_style_checks.yaml b/.github/workflows/code_style_checks.yaml index 2f1aae3..797ed48 100644 --- a/.github/workflows/code_style_checks.yaml +++ b/.github/workflows/code_style_checks.yaml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index a3506a8..850e3f5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -40,7 +40,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: From 70e63c805e165117c7f4127a9bb6278c9b6da52f Mon Sep 17 00:00:00 2001 From: Aditya Bhargava Date: Fri, 19 Dec 2025 14:08:06 -0600 Subject: [PATCH 85/86] v0.17.3 --- CHANGELOG.markdown | 3 +++ lib/contracts/version.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 634f585..b981795 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,3 +1,6 @@ +## [v0.17.3] +- Relax `required_ruby_version` to support Ruby 4.0 +- Bump cucumber, rubocop, actions/checkout ## [v0.17.2] - 2024-10-06 diff --git a/lib/contracts/version.rb b/lib/contracts/version.rb index 94b89b8..1e4b7ca 100644 --- a/lib/contracts/version.rb +++ b/lib/contracts/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Contracts - VERSION = "0.17.2" + VERSION = "0.17.3" end From a8a88fc955b876684299049004a0bcf99615074b Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Sat, 3 Jan 2026 09:09:37 +0800 Subject: [PATCH 86/86] ~ Fix changelog --- CHANGELOG.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index b981795..7cbb63f 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,7 +1,9 @@ -## [v0.17.3] +## [v0.17.3] - 2025-12-19 - Relax `required_ruby_version` to support Ruby 4.0 - Bump cucumber, rubocop, actions/checkout +[v0.17.3]: https://github.com/egonSchiele/contracts.ruby/compare/v0.17.2...v0.17.3 + ## [v0.17.2] - 2024-10-06 [v0.17.2]: https://github.com/egonSchiele/contracts.ruby/compare/v0.17.1...v0.17.2