strong_json 0.9.0 → 1.0.0
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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +68 -16
- data/lib/strong_json/error_reporter.rb +130 -0
- data/lib/strong_json/type.rb +232 -86
- data/lib/strong_json/types.rb +11 -14
- data/lib/strong_json/version.rb +1 -1
- data/lib/strong_json.rb +3 -1
- data/sig/strong_json.rbi +25 -6
- data/sig/type.rbi +55 -28
- data/spec/array_spec.rb +3 -3
- data/spec/basetype_spec.rb +16 -8
- data/spec/enum_spec.rb +87 -9
- data/spec/error_spec.rb +62 -4
- data/spec/json_spec.rb +133 -7
- data/spec/object_spec.rb +122 -57
- data/spec/optional_spec.rb +4 -1
- metadata +3 -2
data/lib/strong_json/type.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
class StrongJSON
|
2
2
|
module Type
|
3
|
-
NONE = ::Object.new
|
4
|
-
|
5
3
|
module Match
|
6
4
|
def =~(value)
|
7
5
|
coerce(value)
|
@@ -15,8 +13,23 @@ class StrongJSON
|
|
15
13
|
end
|
16
14
|
end
|
17
15
|
|
16
|
+
module WithAlias
|
17
|
+
def alias
|
18
|
+
@alias
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_alias(name)
|
22
|
+
_ = dup.tap do |copy|
|
23
|
+
copy.instance_eval do
|
24
|
+
@alias = name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
class Base
|
19
31
|
include Match
|
32
|
+
include WithAlias
|
20
33
|
|
21
34
|
# @dynamic type
|
22
35
|
attr_reader :type
|
@@ -27,8 +40,6 @@ class StrongJSON
|
|
27
40
|
|
28
41
|
def test(value)
|
29
42
|
case @type
|
30
|
-
when :ignored
|
31
|
-
true
|
32
43
|
when :any
|
33
44
|
true
|
34
45
|
when :number
|
@@ -46,13 +57,10 @@ class StrongJSON
|
|
46
57
|
end
|
47
58
|
end
|
48
59
|
|
49
|
-
def coerce(value, path:
|
50
|
-
raise
|
51
|
-
raise IllegalTypeError.new(type: self) if path == [] && @type == :ignored
|
60
|
+
def coerce(value, path: ErrorPath.root(self))
|
61
|
+
raise TypeError.new(value: value, path: path) unless test(value)
|
52
62
|
|
53
63
|
case type
|
54
|
-
when :ignored
|
55
|
-
NONE
|
56
64
|
when :symbol
|
57
65
|
value.to_sym
|
58
66
|
else
|
@@ -61,32 +69,59 @@ class StrongJSON
|
|
61
69
|
end
|
62
70
|
|
63
71
|
def to_s
|
64
|
-
@type.to_s
|
72
|
+
self.alias&.to_s || @type.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
def ==(other)
|
76
|
+
if other.is_a?(Base)
|
77
|
+
# @type var other: Base<any>
|
78
|
+
other.type == type
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
__skip__ = begin
|
83
|
+
alias eql? ==
|
65
84
|
end
|
66
85
|
end
|
67
86
|
|
68
87
|
class Optional
|
69
88
|
include Match
|
89
|
+
include WithAlias
|
90
|
+
|
91
|
+
# @dynamic type
|
92
|
+
attr_reader :type
|
70
93
|
|
71
94
|
def initialize(type)
|
72
95
|
@type = type
|
73
96
|
end
|
74
97
|
|
75
|
-
def coerce(value, path:
|
76
|
-
unless value == nil
|
77
|
-
@type.coerce(value, path: path)
|
98
|
+
def coerce(value, path: ErrorPath.root(self))
|
99
|
+
unless value == nil
|
100
|
+
@type.coerce(value, path: path.expand(type: @type))
|
78
101
|
else
|
79
102
|
nil
|
80
103
|
end
|
81
104
|
end
|
82
105
|
|
83
106
|
def to_s
|
84
|
-
"optional(#{@type})"
|
107
|
+
self.alias&.to_s || "optional(#{@type})"
|
108
|
+
end
|
109
|
+
|
110
|
+
def ==(other)
|
111
|
+
if other.is_a?(Optional)
|
112
|
+
# @type var other: Optional<any>
|
113
|
+
other.type == type
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
__skip__ = begin
|
118
|
+
alias eql? ==
|
85
119
|
end
|
86
120
|
end
|
87
121
|
|
88
122
|
class Literal
|
89
123
|
include Match
|
124
|
+
include WithAlias
|
90
125
|
|
91
126
|
# @dynamic value
|
92
127
|
attr_reader :value
|
@@ -96,176 +131,287 @@ class StrongJSON
|
|
96
131
|
end
|
97
132
|
|
98
133
|
def to_s
|
99
|
-
|
134
|
+
self.alias&.to_s || (_ = @value).inspect
|
100
135
|
end
|
101
136
|
|
102
|
-
def coerce(value, path:
|
103
|
-
raise
|
137
|
+
def coerce(value, path: ErrorPath.root(self))
|
138
|
+
raise TypeError.new(path: path, value: value) unless (_ = self.value) == value
|
104
139
|
value
|
105
140
|
end
|
141
|
+
|
142
|
+
def ==(other)
|
143
|
+
if other.is_a?(Literal)
|
144
|
+
# @type var other: Literal<any>
|
145
|
+
other.value == value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
__skip__ = begin
|
150
|
+
alias eql? ==
|
151
|
+
end
|
106
152
|
end
|
107
153
|
|
108
154
|
class Array
|
109
155
|
include Match
|
156
|
+
include WithAlias
|
157
|
+
|
158
|
+
# @dynamic type
|
159
|
+
attr_reader :type
|
110
160
|
|
111
161
|
def initialize(type)
|
112
162
|
@type = type
|
113
163
|
end
|
114
164
|
|
115
|
-
def coerce(value, path:
|
165
|
+
def coerce(value, path: ErrorPath.root(self))
|
116
166
|
if value.is_a?(::Array)
|
117
167
|
value.map.with_index do |v, i|
|
118
|
-
@type.coerce(v, path: path
|
168
|
+
@type.coerce(v, path: path.dig(key: i, type: @type))
|
119
169
|
end
|
120
170
|
else
|
121
|
-
raise
|
171
|
+
raise TypeError.new(path: path, value: value)
|
122
172
|
end
|
123
173
|
end
|
124
174
|
|
125
175
|
def to_s
|
126
|
-
"array(#{@type})"
|
176
|
+
self.alias&.to_s || "array(#{@type})"
|
177
|
+
end
|
178
|
+
|
179
|
+
def ==(other)
|
180
|
+
if other.is_a?(Array)
|
181
|
+
# @type var other: Array<any>
|
182
|
+
other.type == type
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
__skip__ = begin
|
187
|
+
alias eql? ==
|
127
188
|
end
|
128
189
|
end
|
129
190
|
|
130
191
|
class Object
|
131
192
|
include Match
|
193
|
+
include WithAlias
|
132
194
|
|
133
|
-
|
195
|
+
# @dynamic fields, ignored_attributes, prohibited_attributes
|
196
|
+
attr_reader :fields, :ignored_attributes, :prohibited_attributes
|
197
|
+
|
198
|
+
def initialize(fields, ignored_attributes:, prohibited_attributes:)
|
134
199
|
@fields = fields
|
200
|
+
@ignored_attributes = ignored_attributes
|
201
|
+
@prohibited_attributes = prohibited_attributes
|
135
202
|
end
|
136
203
|
|
137
|
-
def coerce(object, path:
|
204
|
+
def coerce(object, path: ErrorPath.root(self))
|
138
205
|
unless object.is_a?(Hash)
|
139
|
-
raise
|
206
|
+
raise TypeError.new(path: path, value: object)
|
140
207
|
end
|
141
208
|
|
142
|
-
|
143
|
-
|
209
|
+
unless (intersection = Set.new(object.keys).intersection(prohibited_attributes)).empty?
|
210
|
+
raise UnexpectedAttributeError.new(path: path, attribute: intersection.to_a.first)
|
211
|
+
end
|
144
212
|
|
145
|
-
|
146
|
-
|
147
|
-
|
213
|
+
case attrs = ignored_attributes
|
214
|
+
when :any
|
215
|
+
object = object.dup
|
216
|
+
extra_keys = Set.new(object.keys) - Set.new(fields.keys)
|
217
|
+
extra_keys.each do |key|
|
218
|
+
object.delete(key)
|
219
|
+
end
|
220
|
+
when Set
|
221
|
+
object = object.dup
|
222
|
+
attrs.each do |key|
|
223
|
+
object.delete(key)
|
148
224
|
end
|
149
225
|
end
|
150
226
|
|
151
|
-
@
|
152
|
-
|
227
|
+
# @type var result: ::Hash<Symbol, any>
|
228
|
+
result = {}
|
153
229
|
|
154
|
-
|
155
|
-
|
230
|
+
object.each do |key, _|
|
231
|
+
unless fields.key?(key)
|
232
|
+
raise UnexpectedAttributeError.new(path: path, attribute: key)
|
156
233
|
end
|
157
234
|
end
|
158
235
|
|
236
|
+
fields.each do |key, type|
|
237
|
+
result[key] = type.coerce(object[key], path: path.dig(key: key, type: type))
|
238
|
+
end
|
239
|
+
|
159
240
|
_ = result
|
160
241
|
end
|
161
242
|
|
162
|
-
def
|
163
|
-
|
164
|
-
|
165
|
-
return if NONE.equal?(v) || NONE.equal?(type)
|
166
|
-
return if type.is_a?(Optional) && NONE.equal?(value)
|
167
|
-
|
168
|
-
yield(v)
|
243
|
+
def ignore(attrs)
|
244
|
+
Object.new(fields, ignored_attributes: attrs, prohibited_attributes: prohibited_attributes)
|
169
245
|
end
|
170
246
|
|
171
|
-
def
|
172
|
-
|
247
|
+
def ignore!(attrs)
|
248
|
+
@ignored_attributes = attrs
|
249
|
+
self
|
250
|
+
end
|
173
251
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
when Hash
|
178
|
-
fields
|
179
|
-
end
|
252
|
+
def prohibit(attrs)
|
253
|
+
Object.new(fields, ignored_attributes: ignored_attributes, prohibited_attributes: attrs)
|
254
|
+
end
|
180
255
|
|
181
|
-
|
256
|
+
def prohibit!(attrs)
|
257
|
+
@prohibited_attributes = attrs
|
258
|
+
self
|
182
259
|
end
|
183
260
|
|
184
|
-
def
|
185
|
-
|
186
|
-
|
187
|
-
|
261
|
+
def update_fields
|
262
|
+
fields.dup.yield_self do |fields|
|
263
|
+
yield fields
|
264
|
+
|
265
|
+
Object.new(fields, ignored_attributes: ignored_attributes, prohibited_attributes: prohibited_attributes)
|
266
|
+
end
|
188
267
|
end
|
189
268
|
|
190
269
|
def to_s
|
191
|
-
|
192
|
-
|
270
|
+
fields = @fields.map do |name, type|
|
271
|
+
"#{name}: #{type}"
|
272
|
+
end
|
193
273
|
|
194
|
-
|
195
|
-
|
274
|
+
self.alias&.to_s || "object(#{fields.join(', ')})"
|
275
|
+
end
|
276
|
+
|
277
|
+
def ==(other)
|
278
|
+
if other.is_a?(Object)
|
279
|
+
# @type var other: Object<any>
|
280
|
+
other.fields == fields &&
|
281
|
+
other.ignored_attributes == ignored_attributes &&
|
282
|
+
other.prohibited_attributes == prohibited_attributes
|
196
283
|
end
|
284
|
+
end
|
197
285
|
|
198
|
-
|
286
|
+
__skip__ = begin
|
287
|
+
alias eql? ==
|
199
288
|
end
|
200
289
|
end
|
201
290
|
|
202
291
|
class Enum
|
203
292
|
include Match
|
293
|
+
include WithAlias
|
204
294
|
|
205
|
-
# @dynamic types
|
295
|
+
# @dynamic types, detector
|
206
296
|
attr_reader :types
|
297
|
+
attr_reader :detector
|
207
298
|
|
208
|
-
def initialize(types)
|
299
|
+
def initialize(types, detector = nil)
|
209
300
|
@types = types
|
301
|
+
@detector = detector
|
210
302
|
end
|
211
303
|
|
212
304
|
def to_s
|
213
|
-
"enum(#{types.map(&:to_s).join(", ")})"
|
305
|
+
self.alias&.to_s || "enum(#{types.map(&:to_s).join(", ")})"
|
214
306
|
end
|
215
307
|
|
216
|
-
def coerce(value, path:
|
308
|
+
def coerce(value, path: ErrorPath.root(self))
|
309
|
+
if d = detector
|
310
|
+
type = d[value]
|
311
|
+
if type && types.include?(type)
|
312
|
+
return type.coerce(value, path: path.expand(type: type))
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
217
316
|
types.each do |ty|
|
218
317
|
begin
|
219
|
-
return ty.coerce(value, path: path)
|
220
|
-
rescue
|
318
|
+
return ty.coerce(value, path: path.expand(type: ty))
|
319
|
+
rescue UnexpectedAttributeError, TypeError # rubocop:disable Lint/HandleExceptions
|
221
320
|
end
|
222
321
|
end
|
223
322
|
|
224
|
-
raise
|
323
|
+
raise TypeError.new(path: path, value: value)
|
324
|
+
end
|
325
|
+
|
326
|
+
def ==(other)
|
327
|
+
if other.is_a?(Enum)
|
328
|
+
# @type var other: Enum<any>
|
329
|
+
other.types == types &&
|
330
|
+
other.detector == detector
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
__skip__ = begin
|
335
|
+
alias eql? ==
|
225
336
|
end
|
226
337
|
end
|
227
338
|
|
228
|
-
class
|
339
|
+
class UnexpectedAttributeError < StandardError
|
340
|
+
# @dynamic path, attribute
|
341
|
+
attr_reader :path, :attribute
|
342
|
+
|
343
|
+
def initialize(path:, attribute:)
|
344
|
+
@path = path
|
345
|
+
@attribute = attribute
|
346
|
+
super "UnexpectedAttributeError at #{path.to_s}: attribute=#{attribute}"
|
347
|
+
end
|
348
|
+
|
349
|
+
def type
|
350
|
+
path.type
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
class TypeError < StandardError
|
229
355
|
# @dynamic path, value
|
230
356
|
attr_reader :path, :value
|
231
357
|
|
232
|
-
def initialize(path
|
358
|
+
def initialize(path:, value:)
|
233
359
|
@path = path
|
234
360
|
@value = value
|
361
|
+
type = path.type
|
362
|
+
s = type.alias || type
|
363
|
+
super "TypeError at #{path.to_s}: expected=#{s}, value=#{value.inspect}"
|
235
364
|
end
|
236
365
|
|
237
|
-
def
|
238
|
-
|
239
|
-
"Unexpected field of #{position} (#{value})"
|
366
|
+
def type
|
367
|
+
path.type
|
240
368
|
end
|
241
369
|
end
|
242
370
|
|
243
|
-
class
|
244
|
-
# @dynamic type
|
245
|
-
attr_reader :type
|
371
|
+
class ErrorPath
|
372
|
+
# @dynamic type, parent
|
373
|
+
attr_reader :type, :parent
|
246
374
|
|
247
|
-
def initialize(type:)
|
375
|
+
def initialize(type:, parent:)
|
248
376
|
@type = type
|
377
|
+
@parent = parent
|
249
378
|
end
|
250
379
|
|
251
|
-
def
|
252
|
-
|
380
|
+
def dig(key:, type:)
|
381
|
+
# @type var parent: [Integer | Symbol | nil, ErrorPath]
|
382
|
+
parent = [key, self]
|
383
|
+
self.class.new(type: type, parent: parent)
|
253
384
|
end
|
254
|
-
end
|
255
385
|
|
256
|
-
|
257
|
-
|
258
|
-
|
386
|
+
def expand(type:)
|
387
|
+
# @type var parent: [Integer | Symbol | nil, ErrorPath]
|
388
|
+
parent = [nil, self]
|
389
|
+
self.class.new(type: type, parent: parent)
|
390
|
+
end
|
259
391
|
|
260
|
-
def
|
261
|
-
|
262
|
-
|
263
|
-
|
392
|
+
def self.root(type)
|
393
|
+
self.new(type: type, parent: nil)
|
394
|
+
end
|
395
|
+
|
396
|
+
def root?
|
397
|
+
!parent
|
264
398
|
end
|
265
399
|
|
266
400
|
def to_s
|
267
|
-
|
268
|
-
|
401
|
+
if pa = parent
|
402
|
+
if key = pa[0]
|
403
|
+
pa[1].to_s + case key
|
404
|
+
when Integer
|
405
|
+
"[#{key}]"
|
406
|
+
when Symbol
|
407
|
+
".#{key}"
|
408
|
+
end
|
409
|
+
else
|
410
|
+
pa[1].to_s
|
411
|
+
end
|
412
|
+
else
|
413
|
+
"$"
|
414
|
+
end
|
269
415
|
end
|
270
416
|
end
|
271
417
|
end
|
data/lib/strong_json/types.rb
CHANGED
@@ -2,7 +2,11 @@ class StrongJSON
|
|
2
2
|
module Types
|
3
3
|
# @type method object: (?Hash<Symbol, ty>) -> Type::Object<any>
|
4
4
|
def object(fields = {})
|
5
|
-
|
5
|
+
if fields.empty?
|
6
|
+
Type::Object.new(fields, ignored_attributes: nil, prohibited_attributes: Set.new)
|
7
|
+
else
|
8
|
+
Type::Object.new(fields, ignored_attributes: :any, prohibited_attributes: Set.new)
|
9
|
+
end
|
6
10
|
end
|
7
11
|
|
8
12
|
# @type method array: (?ty) -> Type::Array<any>
|
@@ -39,10 +43,6 @@ class StrongJSON
|
|
39
43
|
optional(any)
|
40
44
|
end
|
41
45
|
|
42
|
-
def prohibited
|
43
|
-
StrongJSON::Type::Base.new(:prohibited)
|
44
|
-
end
|
45
|
-
|
46
46
|
def symbol
|
47
47
|
StrongJSON::Type::Base.new(:symbol)
|
48
48
|
end
|
@@ -51,8 +51,8 @@ class StrongJSON
|
|
51
51
|
StrongJSON::Type::Literal.new(value)
|
52
52
|
end
|
53
53
|
|
54
|
-
def enum(*types)
|
55
|
-
StrongJSON::Type::Enum.new(types)
|
54
|
+
def enum(*types, detector: nil)
|
55
|
+
StrongJSON::Type::Enum.new(types, detector)
|
56
56
|
end
|
57
57
|
|
58
58
|
def string?
|
@@ -75,15 +75,12 @@ class StrongJSON
|
|
75
75
|
optional(symbol)
|
76
76
|
end
|
77
77
|
|
78
|
-
def ignored
|
79
|
-
StrongJSON::Type::Base.new(:ignored)
|
80
|
-
end
|
81
|
-
|
82
78
|
def array?(ty)
|
83
79
|
optional(array(ty))
|
84
80
|
end
|
85
81
|
|
86
|
-
|
82
|
+
# @type method object?: (?Hash<Symbol, ty>) -> Type::Optional<any>
|
83
|
+
def object?(fields={})
|
87
84
|
optional(object(fields))
|
88
85
|
end
|
89
86
|
|
@@ -91,8 +88,8 @@ class StrongJSON
|
|
91
88
|
optional(literal(value))
|
92
89
|
end
|
93
90
|
|
94
|
-
def enum?(*types)
|
95
|
-
optional(enum(*types))
|
91
|
+
def enum?(*types, detector: nil)
|
92
|
+
optional(enum(*types, detector: detector))
|
96
93
|
end
|
97
94
|
end
|
98
95
|
end
|
data/lib/strong_json/version.rb
CHANGED
data/lib/strong_json.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require "strong_json/version"
|
2
2
|
require "strong_json/type"
|
3
3
|
require "strong_json/types"
|
4
|
+
require "strong_json/error_reporter"
|
5
|
+
require "prettyprint"
|
4
6
|
|
5
7
|
class StrongJSON
|
6
8
|
def initialize(&block)
|
@@ -8,7 +10,7 @@ class StrongJSON
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def let(name, type)
|
11
|
-
define_singleton_method(name) { type }
|
13
|
+
define_singleton_method(name) { type.with_alias(name) }
|
12
14
|
end
|
13
15
|
|
14
16
|
include StrongJSON::Types
|
data/sig/strong_json.rbi
CHANGED
@@ -6,20 +6,30 @@ end
|
|
6
6
|
|
7
7
|
StrongJSON::VERSION: String
|
8
8
|
|
9
|
+
class StandardError
|
10
|
+
def initialize: (String) -> any
|
11
|
+
end
|
12
|
+
|
9
13
|
interface StrongJSON::_Schema<'type>
|
10
|
-
def coerce: (any, ?path: ::
|
14
|
+
def coerce: (any, ?path: Type::ErrorPath) -> 'type
|
11
15
|
def =~: (any) -> bool
|
12
16
|
def to_s: -> String
|
13
17
|
def is_a?: (any) -> bool
|
18
|
+
def alias: -> Symbol?
|
19
|
+
def with_alias: (Symbol) -> self
|
20
|
+
def ==: (any) -> bool
|
21
|
+
def yield_self: <'a> () { (self) -> 'a } -> 'a
|
14
22
|
end
|
15
23
|
|
16
24
|
type StrongJSON::ty = _Schema<any>
|
17
25
|
|
18
26
|
module StrongJSON::Types
|
19
27
|
def object: <'x> (Hash<Symbol, ty>) -> Type::Object<'x>
|
20
|
-
| () -> Type::Object<
|
28
|
+
| () -> Type::Object<{}>
|
21
29
|
def object?: <'x> (Hash<Symbol, ty>) -> Type::Optional<'x>
|
30
|
+
| () -> Type::Optional<{}>
|
22
31
|
def any: () -> Type::Base<any>
|
32
|
+
def any?: () -> Type::Optional<any>
|
23
33
|
def optional: <'x> (_Schema<'x>) -> Type::Optional<'x>
|
24
34
|
| () -> Type::Optional<any>
|
25
35
|
def string: () -> Type::Base<String>
|
@@ -37,8 +47,17 @@ module StrongJSON::Types
|
|
37
47
|
def array?: <'x> (_Schema<'x>) -> Type::Optional<::Array<'x>>
|
38
48
|
def literal: <'x> ('x) -> Type::Literal<'x>
|
39
49
|
def literal?: <'x> ('x) -> Type::Optional<'x>
|
40
|
-
def enum: <'x> (*_Schema<any
|
41
|
-
def enum?: <'x> (*_Schema<any
|
42
|
-
|
43
|
-
|
50
|
+
def enum: <'x> (*_Schema<any>, ?detector: Type::detector?) -> Type::Enum<'x>
|
51
|
+
def enum?: <'x> (*_Schema<any>, ?detector: Type::detector?) -> Type::Optional<'x>
|
52
|
+
end
|
53
|
+
|
54
|
+
class StrongJSON::ErrorReporter
|
55
|
+
attr_reader path: Type::ErrorPath
|
56
|
+
@string: String
|
57
|
+
def initialize: (path: Type::ErrorPath) -> any
|
58
|
+
def format: -> void
|
59
|
+
def (private) format_trace: (path: Type::ErrorPath, ?index: Integer) -> void
|
60
|
+
def (private) format_aliases: (path: Type::ErrorPath, where: ::Array<String>) -> ::Array<String>
|
61
|
+
def (private) pretty: (ty, any, ?expand_alias: bool) -> void
|
62
|
+
def pretty_str: (ty, ?expand_alias: bool) -> ::String
|
44
63
|
end
|