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:
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 1b5d5a1..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,6 +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.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 a4303d0..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 %{
@@ -93,17 +97,17 @@ 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
# ===
@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,19 +249,21 @@ 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
# 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/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 9252c79..eff5687 100644
--- a/lib/contracts/call_with.rb
+++ b/lib/contracts/call_with.rb
@@ -1,17 +1,19 @@
+# frozen_string_literal: true
+
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|
@@ -20,14 +22,16 @@ def call_with_inner(returns, this, *args, &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,14 +61,18 @@ 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)
+ # rubocop:disable Style/SoleNestedConditional
+ 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,
+ })
+ # rubocop:enable Style/SoleNestedConditional
end
if contract.is_a?(Contracts::Func)
@@ -76,24 +84,27 @@ 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)
+ 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 56d2d82..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)
@@ -46,10 +48,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..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
@@ -107,13 +111,13 @@ 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
# 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
@@ -130,17 +134,20 @@ 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)
+ 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 0bc68f8..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.
@@ -39,8 +41,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/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
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