yard_types 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +5 -0
- data/lib/yard_types/parser.rb +96 -0
- data/lib/yard_types/types.rb +293 -0
- data/lib/yard_types/version.rb +3 -0
- data/lib/yard_types.rb +55 -0
- data/spec/errors_spec.rb +18 -0
- data/spec/parsing_spec.rb +175 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/type_checking_spec.rb +245 -0
- data/yard_type.gemspec +26 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 722cc485964dc469732d1ef265f650bae6c029ee
|
4
|
+
data.tar.gz: 4e85d42f94b0d31a7991e076f62acdcc717c1c27
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 154fb6a437ecd17d7c331836224e6706e4edb411f4de4dd3723fc98131f20b9487b5bfb4c6d33ad0bbd53c9f23577fdbc3bd44af58cea4c35c491b96737dc387
|
7
|
+
data.tar.gz: 11800a82a3a26ddc32c4472f086fa4ba20f53d9b98f8ebb03e2098d495b2cbef1c504142d4976c48d6f92b4c8731ba6095178ccb85cacbe8a899ff78acb0f173
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Kyle Hargraves
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# yard_types
|
2
|
+
|
3
|
+
[](https://travis-ci.org/pd/yard_types)
|
4
|
+
|
5
|
+
Parse YARD type description strings -- eg `Array<#to_sym>` -- and use the
|
6
|
+
resulting types to check type correctness of objects at runtime.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
Like everything else these days:
|
10
|
+
|
11
|
+
~~~ruby
|
12
|
+
gem 'yard_types'
|
13
|
+
~~~
|
14
|
+
|
15
|
+
Note that the `yard` gem may automatically require anything named `yard_*` or
|
16
|
+
`yard-*` on your load path, and attempt to use it as a plugin. You could see
|
17
|
+
errors along the lines of `failed to load plugin yard_types`; this is harmless,
|
18
|
+
as best I can tell.
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
Parse a type description string, and test an object against it:
|
22
|
+
|
23
|
+
~~~ruby
|
24
|
+
type = YardTypes.parse('#quack') #=> #<YardTypes::TypeConstraint ...>
|
25
|
+
|
26
|
+
type.check(Object.new)
|
27
|
+
#=> false
|
28
|
+
|
29
|
+
obj = Object.new
|
30
|
+
def obj.quack; 'quack!'; end
|
31
|
+
type.check(obj)
|
32
|
+
#=> true
|
33
|
+
~~~
|
34
|
+
|
35
|
+
## Caveats
|
36
|
+
YARD does not officially specify a syntax for its type descriptions; the syntax
|
37
|
+
used by its own documentation varies between files. The syntax supported in
|
38
|
+
this gem aims to follow the rules given by the [YARD Type Parser][type-parser].
|
39
|
+
|
40
|
+
In the wild, people seem to use a wide variety of different syntaxes, many of
|
41
|
+
which are unlikely to be supported right now. If you find any such examples,
|
42
|
+
feel free to file an issue -- or better yet, write a test, implement the feature,
|
43
|
+
and send me a pull request.
|
44
|
+
|
45
|
+
## Tests
|
46
|
+
Pretty standard. Just run `rake` or `rspec`.
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
1. Fork it ( http://github.com/pd/yard_types/fork )
|
51
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
52
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
53
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
54
|
+
5. Create new Pull Request
|
55
|
+
|
56
|
+
## Credits
|
57
|
+
The bulk of the parser was [written by lsegal](lsegal-parser); unfortunately, it
|
58
|
+
was never released as a gem, and has sat untouched for 5 years. I've only modified
|
59
|
+
the parser to better support `Hash<A, B>` syntax and to use more consistent
|
60
|
+
naming patterns.
|
61
|
+
|
62
|
+
[type-parser]: http://yardoc.org/types
|
63
|
+
[lsegel-parser]: https://github.com/lsegal/yard-types-parser
|
data/Rakefile
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "yard_types/types"
|
2
|
+
|
3
|
+
module YardTypes
|
4
|
+
|
5
|
+
# Initial code taken from https://github.com/lsegal/yard-types-parser --
|
6
|
+
# unfortunately that was never released as a gem; and the code on master
|
7
|
+
# doesn't actually run.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
# @see YardTypes.parse
|
11
|
+
class Parser
|
12
|
+
TOKENS = {
|
13
|
+
collection_start: /</,
|
14
|
+
collection_end: />/,
|
15
|
+
tuple_start: /\(/,
|
16
|
+
tuple_end: /\)/,
|
17
|
+
type_name: /#\w+|((::)?\w+)+/,
|
18
|
+
type_next: /[,;]/,
|
19
|
+
whitespace: /\s+/,
|
20
|
+
hash_start: /\{/,
|
21
|
+
hash_next: /=>/,
|
22
|
+
hash_end: /\}/,
|
23
|
+
parse_end: nil
|
24
|
+
}
|
25
|
+
|
26
|
+
def self.parse(string)
|
27
|
+
TypeConstraint.new(new(string).parse)
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(string)
|
31
|
+
@scanner = StringScanner.new(string)
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse
|
35
|
+
types = []
|
36
|
+
type = nil
|
37
|
+
name = nil
|
38
|
+
|
39
|
+
loop do
|
40
|
+
found = false
|
41
|
+
TOKENS.each do |token_type, match|
|
42
|
+
if (match.nil? && @scanner.eos?) || (match && token = @scanner.scan(match))
|
43
|
+
found = true
|
44
|
+
case token_type
|
45
|
+
when :type_name
|
46
|
+
raise SyntaxError, "expecting END, got name '#{token}'" if name
|
47
|
+
name = token
|
48
|
+
|
49
|
+
when :type_next
|
50
|
+
raise SyntaxError, "expecting name, got '#{token}' at #{@scanner.pos}" if name.nil?
|
51
|
+
unless type
|
52
|
+
type = Type.for(name)
|
53
|
+
end
|
54
|
+
types << type
|
55
|
+
type = nil
|
56
|
+
name = nil
|
57
|
+
|
58
|
+
when :tuple_start, :collection_start
|
59
|
+
name ||=
|
60
|
+
token_type == :collection_start ? 'Array' : '<generic-tuple>'
|
61
|
+
|
62
|
+
type =
|
63
|
+
if name == 'Hash' && token_type == :collection_start
|
64
|
+
contents = parse
|
65
|
+
if contents.length != 2
|
66
|
+
raise SyntaxError, "expected 2 types for key/value; got #{contents.length}"
|
67
|
+
end
|
68
|
+
|
69
|
+
HashType.new(name, [contents[0]], [contents[1]])
|
70
|
+
elsif token_type == :collection_start
|
71
|
+
CollectionType.new(name, parse)
|
72
|
+
else
|
73
|
+
TupleType.new(name, parse)
|
74
|
+
end
|
75
|
+
|
76
|
+
when :hash_start
|
77
|
+
name ||= "Hash"
|
78
|
+
type = HashType.new(name, parse, parse)
|
79
|
+
|
80
|
+
when :hash_next, :hash_end, :tuple_end, :collection_end, :parse_end
|
81
|
+
raise SyntaxError, "expecting name, got '#{token}'" if name.nil?
|
82
|
+
unless type
|
83
|
+
type = Type.for(name)
|
84
|
+
end
|
85
|
+
types << type
|
86
|
+
return types
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
raise SyntaxError, "invalid character at #{@scanner.peek(1)}" unless found
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,293 @@
|
|
1
|
+
module YardTypes
|
2
|
+
|
3
|
+
# A +TypeConstraint+ specifies the set of acceptable types
|
4
|
+
# which can satisfy the constraint. Parsing any YARD type
|
5
|
+
# description will return a +TypeConstraint+ instance.
|
6
|
+
#
|
7
|
+
# @see YardTypes.parse
|
8
|
+
class TypeConstraint
|
9
|
+
# @return [Array<Type>]
|
10
|
+
attr_reader :accepted_types
|
11
|
+
|
12
|
+
# @param types [Array<Type>] the list of acceptable types
|
13
|
+
def initialize(types)
|
14
|
+
@accepted_types = types
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param i [Fixnum]
|
18
|
+
# @return [Type] the type at index +i+
|
19
|
+
# @todo deprecate this; remnant from original TDD'd API.
|
20
|
+
def [](i)
|
21
|
+
accepted_types[i]
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Type] the first type
|
25
|
+
# @todo deprecate this; remnant from original TDD'd API.
|
26
|
+
def first
|
27
|
+
self[0]
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param obj [Object] Any object.
|
31
|
+
# @return [Type, nil] The first type which matched +obj+,
|
32
|
+
# or +nil+ if none.
|
33
|
+
def check(obj)
|
34
|
+
accepted_types.find { |t| t.check(obj) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String] A YARD type string describing this set of
|
38
|
+
# types.
|
39
|
+
def to_s
|
40
|
+
accepted_types.map(&:to_s).join(', ')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# The base class for all supported types.
|
45
|
+
class Type
|
46
|
+
# @return [String]
|
47
|
+
attr_accessor :name
|
48
|
+
|
49
|
+
# @todo This interface was just hacked into place while
|
50
|
+
# enhancing the parser to return {DuckType}, {KindType}, etc.
|
51
|
+
# @api private
|
52
|
+
def self.for(name)
|
53
|
+
case name
|
54
|
+
when /^#/
|
55
|
+
DuckType.new(name)
|
56
|
+
when *LiteralType.names
|
57
|
+
LiteralType.new(name)
|
58
|
+
else
|
59
|
+
KindType.new(name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param name [String]
|
64
|
+
def initialize(name)
|
65
|
+
@name = name
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [String] a YARD type string describing this type.
|
69
|
+
def to_s
|
70
|
+
name
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param obj [Object] Any object.
|
74
|
+
# @return [Boolean] whether the object is of this type.
|
75
|
+
# @raise [NotImplementedError] must be handled by the subclasses.
|
76
|
+
def check(obj)
|
77
|
+
raise NotImplementedError
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# A {DuckType} constraint is specified as +#some_message+,
|
82
|
+
# and indicates that the object must respond to the method
|
83
|
+
# +some_message+.
|
84
|
+
class DuckType < Type
|
85
|
+
# @return [String] The method the object must respond to;
|
86
|
+
# this does not include the leading +#+ character.
|
87
|
+
attr_reader :message
|
88
|
+
|
89
|
+
# @param name [String] The YARD identifier, eg +#some_message+.
|
90
|
+
def initialize(name)
|
91
|
+
@name = name
|
92
|
+
@message = name[1..-1]
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param (see Type#check)
|
96
|
+
# @return [Boolean] +true+ if the object responds to +message+.
|
97
|
+
def check(obj)
|
98
|
+
obj.respond_to? message
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# A {KindType} constraint is specified as +SomeModule+ or
|
103
|
+
# +SomeClass+, and indicates that the object must be a kind of that
|
104
|
+
# module.
|
105
|
+
class KindType < Type
|
106
|
+
# Type checks a given object. Special consideration is given to
|
107
|
+
# the pseudo-class +Boolean+, which does not actually exist in Ruby,
|
108
|
+
# but is commonly used to mean +TrueClass, FalseClass+.
|
109
|
+
#
|
110
|
+
# @param (see Type#check)
|
111
|
+
# @return [Boolean] +true+ if +obj.kind_of?(constant)+.
|
112
|
+
def check(obj)
|
113
|
+
if name == 'Boolean'
|
114
|
+
obj == true || obj == false
|
115
|
+
else
|
116
|
+
obj.kind_of? constant
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [Module] the constant specified by +name+.
|
121
|
+
# @raise [TypeError] if the constant is neither a module nor a class
|
122
|
+
# @raise [NameError] if the specified constant could not be loaded.
|
123
|
+
def constant
|
124
|
+
@constant ||=
|
125
|
+
begin
|
126
|
+
const = name.split('::').reduce(Object) { |namespace, const|
|
127
|
+
namespace.const_get(const)
|
128
|
+
}
|
129
|
+
|
130
|
+
unless const.kind_of?(Module)
|
131
|
+
raise TypeError, "class or module required; #{name} is a #{const.class}"
|
132
|
+
end
|
133
|
+
|
134
|
+
const
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# A {LiteralType} constraint is specified by the name of one of YARD's
|
140
|
+
# supported "literals": +true+, +false+, +nil+, +void+, and +self+, and
|
141
|
+
# indicates that the object must be exactly one of those values.
|
142
|
+
#
|
143
|
+
# However, +void+ and +self+ have no particular meaning: +void+ is typically
|
144
|
+
# used solely to specify that a method returns no meaningful types; and
|
145
|
+
# +self+ is used to specify that a method returns its receiver, generally
|
146
|
+
# to indicate that calls can be chained. All values type check as valid
|
147
|
+
# objects for +void+ and +self+ literals.
|
148
|
+
class LiteralType < Type
|
149
|
+
# @return [Array<String>] the list of supported literal identifiers.
|
150
|
+
def self.names
|
151
|
+
@literal_names ||= %w(true false nil void self)
|
152
|
+
end
|
153
|
+
|
154
|
+
# @param (see Type#check)
|
155
|
+
# @return [Boolean] +true+ if the object is exactly +true+, +false+, or
|
156
|
+
# +nil+ (depending on the value of +name+); for +void+ and +self+
|
157
|
+
# types, this method *always* returns +true+.
|
158
|
+
# @raise [NotImplementedError] if an unsupported literal name is to be
|
159
|
+
# tested against.
|
160
|
+
def check(obj)
|
161
|
+
case name
|
162
|
+
when 'true' then obj == true
|
163
|
+
when 'false' then obj == false
|
164
|
+
when 'nil' then obj == nil
|
165
|
+
when 'self', 'void' then true
|
166
|
+
else raise NotImplementedError, "Unsupported literal type: #{name.inspect}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# A {CollectionType} is specified with the syntax +Kind<Some, #thing>+, and
|
172
|
+
# indicates that the object is a kind of +Kind+, containing only objects which
|
173
|
+
# type check against +Some+ or +#thing+.
|
174
|
+
#
|
175
|
+
# @todo The current implementation of type checking here requires that the collection
|
176
|
+
# respond to +all?+; this may not be ideal.
|
177
|
+
class CollectionType < Type
|
178
|
+
# @return [Array<Type>] the acceptable types for this collection's contents.
|
179
|
+
attr_accessor :types
|
180
|
+
|
181
|
+
# @param name [String] the name of the module the collection must be a kind of.
|
182
|
+
# @param types [Array<Type>] the acceptable types for the collection's contents.
|
183
|
+
def initialize(name, types)
|
184
|
+
@name = name
|
185
|
+
@types = types
|
186
|
+
end
|
187
|
+
|
188
|
+
# @return (see Type#to_s)
|
189
|
+
def to_s
|
190
|
+
"%s<%s>" % [name, types.map(&:to_s).join(', ')]
|
191
|
+
end
|
192
|
+
|
193
|
+
# @param (see Type#check)
|
194
|
+
# @return [Boolean] +true+ if the object is both a kind of +name+, and all of
|
195
|
+
# its contents (if any) are of the types in +types+. Any combination, order,
|
196
|
+
# and count of content types is acceptable.
|
197
|
+
def check(obj)
|
198
|
+
return false unless KindType.new(name).check(obj)
|
199
|
+
|
200
|
+
obj.all? do |el|
|
201
|
+
# TODO -- could probably just use another TypeConstraint here
|
202
|
+
types.any? { |type| type.check(el) }
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# A {TupleType} is specified with the syntax +(Some, Types, #here)+, and indicates
|
208
|
+
# that the contents of the collection must be exactly that size, and each element
|
209
|
+
# must be of the exact type specified for that index.
|
210
|
+
#
|
211
|
+
# @todo The current implementation of type checking here requires that the collection
|
212
|
+
# respond to both +length+ and +[]+; this may not be ideal.
|
213
|
+
class TupleType < CollectionType
|
214
|
+
def initialize(name, types)
|
215
|
+
@name = name == '<generic-tuple>' ? nil : name
|
216
|
+
@types = types
|
217
|
+
end
|
218
|
+
|
219
|
+
# @return (see Type#to_s)
|
220
|
+
def to_s
|
221
|
+
"%s(%s)" % [name, types.map(&:to_s).join(', ')]
|
222
|
+
end
|
223
|
+
|
224
|
+
# @param (see Type#check)
|
225
|
+
# @return [Boolean] +true+ if the collection's +length+ is exactly the length of
|
226
|
+
# the expected +types+, and each element with the collection is of the type
|
227
|
+
# specified for that index by +types+.
|
228
|
+
def check(obj)
|
229
|
+
return false unless name.nil? || KindType.new(name).check(obj)
|
230
|
+
return false unless obj.respond_to?(:length) && obj.respond_to?(:[])
|
231
|
+
return false unless obj.length == types.length
|
232
|
+
|
233
|
+
enum = types.to_enum
|
234
|
+
enum.with_index.all? do |t, i|
|
235
|
+
t.check(obj[i])
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# A {HashType} is specified with the syntax +{KeyType =>
|
241
|
+
# ValueType}+, and indicates that all keys in the hash must be of
|
242
|
+
# type +KeyType+, and all values must be of type +ValueType+.
|
243
|
+
#
|
244
|
+
# An alternate syntax for {HashType} is also available as +Hash<A,
|
245
|
+
# B>+, but its usage is not recommended; it is less capable than the
|
246
|
+
# +{A => B}+ syntax, as some inner type constraints can not be
|
247
|
+
# parsed reliably.
|
248
|
+
#
|
249
|
+
# A {HashType} actually only requires that the object respond to
|
250
|
+
# both +keys+ and +values+; it should be capable of type checking
|
251
|
+
# any object which conforms to that interface.
|
252
|
+
#
|
253
|
+
# @todo Enforce kind, eg +HashWithIndifferentAccess{#to_sym => Array}+,
|
254
|
+
# in case you _really_ care that it's indifferent. Maybe?
|
255
|
+
class HashType < Type
|
256
|
+
# @return [Array<Type>] the set of acceptable types for keys
|
257
|
+
attr_reader :key_types
|
258
|
+
|
259
|
+
# @return [Array<Type>] the set of acceptable types for values
|
260
|
+
attr_reader :value_types
|
261
|
+
|
262
|
+
# @param name [String] the kind of the expected object; currently unused.
|
263
|
+
# @param key_types [Array<Type>] the set of acceptable types for keys
|
264
|
+
# @param value_types [Array<Type>] the set of acceptable types for values
|
265
|
+
def initialize(name, key_types, value_types)
|
266
|
+
@name = name
|
267
|
+
@key_types = key_types
|
268
|
+
@value_types = value_types
|
269
|
+
end
|
270
|
+
|
271
|
+
# Unlike the other types, {HashType} can result from two alternate syntaxes;
|
272
|
+
# however, this method will *only* return the +{A => B}+ syntax.
|
273
|
+
#
|
274
|
+
# @return (see Type#to_s)
|
275
|
+
def to_s
|
276
|
+
"{%s => %s}" % [
|
277
|
+
key_types.map(&:to_s).join(', '),
|
278
|
+
value_types.map(&:to_s).join(', ')
|
279
|
+
]
|
280
|
+
end
|
281
|
+
|
282
|
+
# @param (see Type#check)
|
283
|
+
# @return [Boolean] +true+ if the object responds to both +keys+ and +values+,
|
284
|
+
# and every key type checks against a type in +key_types+, and every value
|
285
|
+
# type checks against a type in +value_types+.
|
286
|
+
def check(obj)
|
287
|
+
return false unless obj.respond_to?(:keys) && obj.respond_to?(:values)
|
288
|
+
obj.keys.all? { |key| key_types.any? { |t| t.check(key) } } &&
|
289
|
+
obj.values.all? { |value| value_types.any? { |t| t.check(value) } }
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
data/lib/yard_types.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require "yard_types/version"
|
2
|
+
require "yard_types/types"
|
3
|
+
require "yard_types/parser"
|
4
|
+
|
5
|
+
module YardTypes
|
6
|
+
extend self
|
7
|
+
|
8
|
+
class Result
|
9
|
+
def initialize(pass = false)
|
10
|
+
@pass = pass
|
11
|
+
end
|
12
|
+
|
13
|
+
def success?
|
14
|
+
@pass == true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Success < Result
|
19
|
+
def initialize
|
20
|
+
super(true)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Failure < Result
|
25
|
+
def initialize
|
26
|
+
super(false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Parse a type string using the {Parser}, and return a
|
31
|
+
# {TypeConstraint} instance representing the described
|
32
|
+
# type.
|
33
|
+
#
|
34
|
+
# @param type [String, Array<String>] The YARD type description
|
35
|
+
# @return [TypeConstraint]
|
36
|
+
# @raise [SyntaxError] if the string could not be parsed
|
37
|
+
# @example
|
38
|
+
# type = YardTypes.parse('MyClass, #quacks_like_my_class')
|
39
|
+
# type.check(some_object)
|
40
|
+
def parse(type)
|
41
|
+
type = type.join(', ') if type.respond_to?(:join)
|
42
|
+
Parser.parse(type)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Result]
|
46
|
+
# @todo deprecate; rename it +check+ to match everything else.
|
47
|
+
def validate(type, obj)
|
48
|
+
constraint = parse(type)
|
49
|
+
if constraint.check(obj)
|
50
|
+
Success.new
|
51
|
+
else
|
52
|
+
Failure.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/errors_spec.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Defensive error raising' do
|
4
|
+
specify 'Type#check raises NotImplementedError' do
|
5
|
+
type = YardTypes::Type.new('Foo')
|
6
|
+
expect { type.check(nil) }.to raise_error(NotImplementedError)
|
7
|
+
end
|
8
|
+
|
9
|
+
specify 'LiteralType raises when checking for an unsupported literal' do
|
10
|
+
type = YardTypes::LiteralType.new('zero')
|
11
|
+
expect { type.check(0) }.to raise_error(NotImplementedError, /zero/)
|
12
|
+
end
|
13
|
+
|
14
|
+
specify 'KindType raises when its constant is neither module nor class' do
|
15
|
+
type = YardTypes::KindType.new('Math::PI')
|
16
|
+
expect { type.check(:anything) }.to raise_error(TypeError, 'class or module required; Math::PI is a Float')
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe YardTypes, 'parsing' do
|
4
|
+
def parse(type_string)
|
5
|
+
YardTypes.parse(type_string)
|
6
|
+
end
|
7
|
+
|
8
|
+
matcher :be_type_class do |type|
|
9
|
+
def type_class(type_identifier)
|
10
|
+
YardTypes.const_get("#{type_identifier.to_s.capitalize}Type")
|
11
|
+
end
|
12
|
+
|
13
|
+
match do |type_string|
|
14
|
+
result = YardTypes.parse(type_string)
|
15
|
+
result.first.instance_of? type_class(type)
|
16
|
+
end
|
17
|
+
|
18
|
+
description do |type_string|
|
19
|
+
"'#{type_string}' parses into a #{type_class(type).name} instance"
|
20
|
+
end
|
21
|
+
|
22
|
+
failure_message do |type_string|
|
23
|
+
"expected '#{type_string}' to parse into a #{type_class(type).name} instance"
|
24
|
+
end
|
25
|
+
|
26
|
+
failure_message_when_negated do |type_string|
|
27
|
+
"expected '#{type_string}' not to parse into a #{type_class(type).name} instance, but did"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
matcher :have_inner_types do |*expected_inner_types|
|
32
|
+
def collection?(type)
|
33
|
+
type.respond_to?(:types)
|
34
|
+
end
|
35
|
+
|
36
|
+
def matching_count?(actual, expected)
|
37
|
+
actual.size == expected.size
|
38
|
+
end
|
39
|
+
|
40
|
+
def matching_type_classes?(actual, expected)
|
41
|
+
actual.zip(expected).all? do |type, (klass_name, type_name)|
|
42
|
+
klass = YardTypes.const_get("#{klass_name.to_s.capitalize}Type")
|
43
|
+
type.is_a?(klass) && type.name == type_name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
match do |type_string|
|
48
|
+
type = YardTypes.parse(type_string).first
|
49
|
+
collection?(type) &&
|
50
|
+
matching_count?(type.types, expected_inner_types) &&
|
51
|
+
matching_type_classes?(type.types, expected_inner_types)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
specify 'literals' do
|
56
|
+
expect('true').to be_type_class(:literal)
|
57
|
+
expect('false').to be_type_class(:literal)
|
58
|
+
expect('nil').to be_type_class(:literal)
|
59
|
+
expect('void').to be_type_class(:literal)
|
60
|
+
expect('self').to be_type_class(:literal)
|
61
|
+
end
|
62
|
+
|
63
|
+
specify 'duck' do
|
64
|
+
expect('#foo').to be_type_class(:duck)
|
65
|
+
end
|
66
|
+
|
67
|
+
specify 'kind' do
|
68
|
+
expect('Foo').to be_type_class(:kind)
|
69
|
+
expect('Array').to be_type_class(:kind)
|
70
|
+
expect('Hash').to be_type_class(:kind)
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'parameterized array' do
|
74
|
+
specify 'not bare `Array` type' do
|
75
|
+
expect('Array').not_to be_type_class(:collection)
|
76
|
+
end
|
77
|
+
|
78
|
+
specify 'Array<...>' do
|
79
|
+
expect('Array<String>').to be_type_class(:collection)
|
80
|
+
expect('Array<#foo>').to be_type_class(:collection)
|
81
|
+
expect('Array<#a, #b>').to be_type_class(:collection)
|
82
|
+
end
|
83
|
+
|
84
|
+
specify 'inner types' do
|
85
|
+
expect('Array<String>').to have_inner_types([:kind, 'String'])
|
86
|
+
|
87
|
+
expect('Array<String, #to_date>').to have_inner_types([:kind, 'String'],
|
88
|
+
[:duck, '#to_date'])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'tuples' do
|
93
|
+
specify '(...)' do
|
94
|
+
expect('(String)').to be_type_class(:tuple)
|
95
|
+
expect('(String, #to_date, true)').to be_type_class(:tuple)
|
96
|
+
end
|
97
|
+
|
98
|
+
specify 'inner types' do
|
99
|
+
expect('(String)').to have_inner_types([:kind, 'String'])
|
100
|
+
|
101
|
+
expect('(String, #to_date, true)').to have_inner_types([:kind, 'String'],
|
102
|
+
[:duck, '#to_date'],
|
103
|
+
[:literal, 'true'])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'hashes' do
|
108
|
+
specify 'Hash<a, b>' do
|
109
|
+
expect('Hash<#a, #b>').to be_type_class(:hash)
|
110
|
+
expect('Hash<Fixnum, String>').to be_type_class(:hash)
|
111
|
+
expect('Hash<(#some, #tuple), Array<#to_date>>').to be_type_class(:hash)
|
112
|
+
end
|
113
|
+
|
114
|
+
specify 'Hash<a> | Hash<a, b, c> => SyntaxError' do
|
115
|
+
expect { parse('Hash<a>') }.to raise_error(SyntaxError)
|
116
|
+
expect { parse('Hash<a, b, c>') }.to raise_error(SyntaxError)
|
117
|
+
end
|
118
|
+
|
119
|
+
specify '{a => b}' do
|
120
|
+
expect('{A => B}').to be_type_class(:hash)
|
121
|
+
expect('{#a, #b => #to_date}').to be_type_class(:hash)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe YardTypes::Type, '.parse' do
|
127
|
+
it "can accept a single YARD type string" do
|
128
|
+
constraint = YardTypes.parse('Array, Hash')
|
129
|
+
expect(constraint).to be_instance_of(YardTypes::TypeConstraint)
|
130
|
+
expect(constraint.to_s).to eq('Array, Hash')
|
131
|
+
end
|
132
|
+
|
133
|
+
it "can accept an array of individual type strings, and return a single Constraint" do
|
134
|
+
constraint = YardTypes.parse(['Array', 'Hash'])
|
135
|
+
expect(constraint).to be_instance_of(YardTypes::TypeConstraint)
|
136
|
+
expect(constraint.to_s).to eq('Array, Hash')
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe YardTypes::Type, '#to_s' do
|
141
|
+
[
|
142
|
+
# Kind
|
143
|
+
'String', 'Boolean', 'Array', 'String, Symbol',
|
144
|
+
|
145
|
+
# Duck
|
146
|
+
'#foo', '#foo, #bar',
|
147
|
+
|
148
|
+
# Literals
|
149
|
+
'true', 'false', 'self', 'nil', 'void', 'true, false, nil',
|
150
|
+
|
151
|
+
# Collection
|
152
|
+
'Array<Fixnum>', 'Array<Fixnum, (#to_i, #to_f)>', 'Set<Date>',
|
153
|
+
|
154
|
+
# Tuple
|
155
|
+
'(String, Boolean)', '(A, B), (C, D)',
|
156
|
+
|
157
|
+
# Hash
|
158
|
+
'{String => Symbol}', '{#a, #b => (A, B)}', '{#foo => #bar}, {Fixnum => String}',
|
159
|
+
|
160
|
+
# Crazy
|
161
|
+
'(Array<(#foo, #bar), {String => Symbol}>, #to_sym, (nil, Boolean))'
|
162
|
+
].each do |string|
|
163
|
+
|
164
|
+
specify string do
|
165
|
+
parsed = YardTypes.parse(string)
|
166
|
+
expect(parsed.to_s).to eq(string)
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
it "does not preserve Hash<> notation" do
|
172
|
+
parsed = YardTypes.parse('Hash<(#a, #b), Symbol>')
|
173
|
+
expect(parsed.to_s).to eq('{(#a, #b) => Symbol}')
|
174
|
+
end
|
175
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter "/spec/"
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'yard_types'
|
7
|
+
require 'pry'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.order = :rand
|
11
|
+
config.filter_run focus: true
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
describe YardTypes, 'type checking' do
|
5
|
+
matcher :type_check do |obj|
|
6
|
+
match do |type|
|
7
|
+
result = YardTypes.validate(type, obj)
|
8
|
+
result.success?
|
9
|
+
end
|
10
|
+
|
11
|
+
description do |type|
|
12
|
+
"type checks against #{type.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
failure_message do |type|
|
16
|
+
"expected `#{obj.inspect}` to type check against #{type.inspect}"
|
17
|
+
end
|
18
|
+
|
19
|
+
failure_message_when_negated do |type|
|
20
|
+
"expected `#{obj.inspect}` not to type check against #{type.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'ducks' do
|
25
|
+
specify 'responds' do
|
26
|
+
expect('#to_s').to type_check(nil)
|
27
|
+
expect('#reverse').to type_check('foo')
|
28
|
+
expect('#name').to type_check(Class)
|
29
|
+
end
|
30
|
+
|
31
|
+
specify 'does not respond' do
|
32
|
+
expect('#bogus').not_to type_check(nil)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'kinds' do
|
37
|
+
specify 'is kind_of' do
|
38
|
+
expect('String').to type_check('')
|
39
|
+
expect('Object').to type_check([])
|
40
|
+
end
|
41
|
+
|
42
|
+
specify 'is not kind_of' do
|
43
|
+
expect('String').not_to type_check([])
|
44
|
+
end
|
45
|
+
|
46
|
+
specify 'Boolean == true || false' do
|
47
|
+
expect('Boolean').to type_check(true)
|
48
|
+
expect('Boolean').to type_check(false)
|
49
|
+
expect('Boolean').not_to type_check(nil)
|
50
|
+
end
|
51
|
+
|
52
|
+
specify 'constant resolution' do
|
53
|
+
expect('YardTypes::DuckType').to type_check(YardTypes::DuckType.new('#foo'))
|
54
|
+
end
|
55
|
+
|
56
|
+
specify 'unknown constant' do
|
57
|
+
expect {
|
58
|
+
type = YardTypes.parse('ReversedString')[0] # mind the typo
|
59
|
+
type.check('gnirts')
|
60
|
+
}.to raise_error(NameError)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'arrays' do
|
65
|
+
specify 'inner type' do
|
66
|
+
# Empty always passes
|
67
|
+
expect('Array<String>').to type_check([])
|
68
|
+
|
69
|
+
# Every element passes
|
70
|
+
expect('Array<#reverse>').to type_check(['foo', 'bar'])
|
71
|
+
expect('Array<#reverse>').to type_check([['a'], 'foo'])
|
72
|
+
|
73
|
+
# Every element fails
|
74
|
+
expect('Array<#reverse>').not_to type_check([1])
|
75
|
+
|
76
|
+
# Some element fails
|
77
|
+
expect('Array<#reverse>').not_to type_check(['foo', 1])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'alternate collection types' do
|
82
|
+
specify 'Set<Symbol>' do
|
83
|
+
array = [:foo, :bar]
|
84
|
+
set = Set.new(array)
|
85
|
+
|
86
|
+
expect('Set<Symbol>').to type_check(set)
|
87
|
+
expect('Set<Symbol>').not_to type_check(array)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'tuples' do
|
92
|
+
class ::MyTuple < Array
|
93
|
+
end
|
94
|
+
|
95
|
+
let(:type) { '(String, Fixnum, #reverse)' }
|
96
|
+
|
97
|
+
specify 'matches' do
|
98
|
+
expect(type).to type_check(['foo', 1, []])
|
99
|
+
end
|
100
|
+
|
101
|
+
specify 'one type is wrong' do
|
102
|
+
expect(type).not_to type_check([:nope, 1, []])
|
103
|
+
expect(type).not_to type_check(['foo', 1.0, []])
|
104
|
+
expect(type).not_to type_check(['foo', 1, nil])
|
105
|
+
end
|
106
|
+
|
107
|
+
specify 'invalid length' do
|
108
|
+
expect(type).not_to type_check([])
|
109
|
+
expect(type).not_to type_check(['foo'])
|
110
|
+
expect(type).not_to type_check(['foo', 1])
|
111
|
+
expect(type).not_to type_check(['foo', 1, [], true])
|
112
|
+
end
|
113
|
+
|
114
|
+
specify 'unspecified kind accepts any kind' do
|
115
|
+
tuple = MyTuple.new
|
116
|
+
tuple[0] = 'hi'
|
117
|
+
tuple[1] = 1
|
118
|
+
tuple[2] = []
|
119
|
+
|
120
|
+
expect(type).to type_check(tuple)
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'specified kind' do
|
124
|
+
let(:type) { 'MyTuple(String, Fixnum)' }
|
125
|
+
|
126
|
+
specify 'kind + contents match' do
|
127
|
+
tuple = MyTuple.new
|
128
|
+
tuple[0] = 'hi'
|
129
|
+
tuple[1] = 1
|
130
|
+
|
131
|
+
expect(type).to type_check(tuple)
|
132
|
+
end
|
133
|
+
|
134
|
+
specify 'kind matches, contents do not' do
|
135
|
+
expect(type).not_to type_check(MyTuple.new)
|
136
|
+
end
|
137
|
+
|
138
|
+
specify 'contents match, but kind does not' do
|
139
|
+
expect(type).not_to type_check(['hi', 1])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'hash' do
|
145
|
+
context 'Hash<> syntax' do
|
146
|
+
let(:type) { 'Hash<Fixnum, String>' }
|
147
|
+
|
148
|
+
specify 'matches' do
|
149
|
+
expect(type).to type_check({ 1 => 'foo', 2 => 'bar' })
|
150
|
+
end
|
151
|
+
|
152
|
+
specify 'wrong key type' do
|
153
|
+
expect(type).not_to type_check({ 1.0 => 'foo' })
|
154
|
+
expect(type).not_to type_check({ 1 => 'foo', :wrong => 'bar' })
|
155
|
+
end
|
156
|
+
|
157
|
+
specify 'wrong value type' do
|
158
|
+
expect(type).not_to type_check({ 1 => :foo })
|
159
|
+
expect(type).not_to type_check({ 1 => 'foo', 2 => :bar })
|
160
|
+
end
|
161
|
+
|
162
|
+
specify 'quacks like a hash' do
|
163
|
+
map_type = Struct.new(:keys, :values)
|
164
|
+
hash_map = map_type.new([1, 2], ['three', 'four'])
|
165
|
+
expect(type).to type_check(hash_map)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'Hash{} syntax' do
|
170
|
+
let(:type) { '{Boolean => #reverse}' }
|
171
|
+
|
172
|
+
specify 'matches' do
|
173
|
+
expect(type).to type_check(false => [])
|
174
|
+
expect(type).to type_check(true => 'foo', false => [])
|
175
|
+
end
|
176
|
+
|
177
|
+
specify 'wrong key type' do
|
178
|
+
expect(type).not_to type_check(:false => [])
|
179
|
+
expect(type).not_to type_check(false => [], 'true' => 'bar')
|
180
|
+
end
|
181
|
+
|
182
|
+
specify 'wrong value type' do
|
183
|
+
expect(type).not_to type_check(true => :fail)
|
184
|
+
expect(type).not_to type_check(true => 'pass', false => :fail)
|
185
|
+
end
|
186
|
+
|
187
|
+
specify 'quacks like a hash' do
|
188
|
+
map_type = Struct.new(:keys, :values)
|
189
|
+
hash_map = map_type.new([true, false], ['three', [:four]])
|
190
|
+
expect(type).to type_check(hash_map)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'literals' do
|
196
|
+
specify 'nil' do
|
197
|
+
expect('nil').to type_check(nil)
|
198
|
+
expect('nil').not_to type_check(false)
|
199
|
+
end
|
200
|
+
|
201
|
+
specify 'true' do
|
202
|
+
expect('true').to type_check(true)
|
203
|
+
expect('true').not_to type_check(false)
|
204
|
+
expect('true').not_to type_check(nil)
|
205
|
+
end
|
206
|
+
|
207
|
+
specify 'false' do
|
208
|
+
expect('false').to type_check(false)
|
209
|
+
expect('false').not_to type_check(true)
|
210
|
+
expect('false').not_to type_check(nil)
|
211
|
+
end
|
212
|
+
|
213
|
+
specify 'void' do
|
214
|
+
expect('void').to type_check(nil)
|
215
|
+
expect('void').to type_check('')
|
216
|
+
expect('void').to type_check(['anything', :really])
|
217
|
+
end
|
218
|
+
|
219
|
+
specify 'self' do
|
220
|
+
expect('self').to type_check(nil)
|
221
|
+
expect('self').to type_check('')
|
222
|
+
expect('self').to type_check(['anything', :really])
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'multiple acceptable types' do
|
227
|
+
specify 'String, Symbol' do
|
228
|
+
expect('String, Symbol').to type_check('foo')
|
229
|
+
expect('String, Symbol').to type_check(:foo)
|
230
|
+
expect('String, Symbol').not_to type_check([])
|
231
|
+
expect('String, Symbol').not_to type_check(['foo', :foo])
|
232
|
+
end
|
233
|
+
|
234
|
+
specify 'Array<A, B>' do
|
235
|
+
expect('Array<Fixnum, #to_i>').to type_check([])
|
236
|
+
expect('Array<Fixnum, #to_i>').to type_check([1])
|
237
|
+
expect('Array<Fixnum, #to_i>').to type_check(['1'])
|
238
|
+
expect('Array<Fixnum, #to_i>').to type_check([nil])
|
239
|
+
|
240
|
+
expect('Array<Fixnum, #to_i>').not_to type_check([:oops])
|
241
|
+
expect('Array<Fixnum, #to_i>').not_to type_check(nil)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
data/yard_type.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'yard_types/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "yard_types"
|
8
|
+
spec.version = YardTypes::VERSION
|
9
|
+
spec.authors = ["Kyle Hargraves"]
|
10
|
+
spec.email = ["pd@krh.me"]
|
11
|
+
spec.summary = %q{Parse and validate objects against YARD type descriptions.}
|
12
|
+
spec.description = %q{Your API docs say you return Array<#to_date>, but do you really?}
|
13
|
+
spec.homepage = "https://github.com/pd/yard_types"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
24
|
+
spec.add_development_dependency "simplecov", "~> 0.7.1"
|
25
|
+
spec.add_development_dependency "pry", "> 0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yard_types
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kyle Hargraves
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.7.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.7.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Your API docs say you return Array<#to_date>, but do you really?
|
84
|
+
email:
|
85
|
+
- pd@krh.me
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".travis.yml"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/yard_types.rb
|
97
|
+
- lib/yard_types/parser.rb
|
98
|
+
- lib/yard_types/types.rb
|
99
|
+
- lib/yard_types/version.rb
|
100
|
+
- spec/errors_spec.rb
|
101
|
+
- spec/parsing_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/type_checking_spec.rb
|
104
|
+
- yard_type.gemspec
|
105
|
+
homepage: https://github.com/pd/yard_types
|
106
|
+
licenses:
|
107
|
+
- MIT
|
108
|
+
metadata: {}
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options: []
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 2.2.0
|
126
|
+
signing_key:
|
127
|
+
specification_version: 4
|
128
|
+
summary: Parse and validate objects against YARD type descriptions.
|
129
|
+
test_files:
|
130
|
+
- spec/errors_spec.rb
|
131
|
+
- spec/parsing_spec.rb
|
132
|
+
- spec/spec_helper.rb
|
133
|
+
- spec/type_checking_spec.rb
|