rbi 0.2.0 → 0.2.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 +4 -4
- data/Gemfile +2 -2
- data/lib/rbi/rbs_printer.rb +1036 -0
- data/lib/rbi/version.rb +1 -1
- data/lib/rbi.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 68328047c11f18c7617e8101a773a3b3b9438be4e36a0b01072274eba2b1c583
|
4
|
+
data.tar.gz: 275b2ed35e5486461d2ebf4afae88d7b75b9f26ac1d7d8d9693ff630b17dff3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53e510f0df7f4e44c45686774f3573e06031ba7c81a29f7ff429b2dacb601c42a83be37acca12809161ef7acd50722682ad88483b12d4af8d536c6f709e540b8
|
7
|
+
data.tar.gz: 172bbba7e9c3cd29032d4d34a4fda47ce81728b971a8956d742b0d012ca393b3f555109493ec2ebfe1aa3087a142da109061fc07074f8a4b721096cf1884091d
|
data/Gemfile
CHANGED
@@ -10,9 +10,9 @@ group(:development, :test) do
|
|
10
10
|
gem("minitest")
|
11
11
|
gem("minitest-reporters")
|
12
12
|
gem("rake", "~> 13.2")
|
13
|
-
gem("rubocop", "~> 1.
|
13
|
+
gem("rubocop", "~> 1.66", require: false)
|
14
14
|
gem("rubocop-shopify", require: false)
|
15
15
|
gem("rubocop-sorbet", require: false)
|
16
16
|
gem("sorbet", ">= 0.5.9204", require: false)
|
17
|
-
gem("tapioca",
|
17
|
+
gem("tapioca", require: false)
|
18
18
|
end
|
@@ -0,0 +1,1036 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RBI
|
5
|
+
class RBSPrinter < Visitor
|
6
|
+
class Error < RBI::Error; end
|
7
|
+
|
8
|
+
sig { returns(T::Boolean) }
|
9
|
+
attr_accessor :print_locs, :in_visibility_group
|
10
|
+
|
11
|
+
sig { returns(T.nilable(Node)) }
|
12
|
+
attr_reader :previous_node
|
13
|
+
|
14
|
+
sig { returns(Integer) }
|
15
|
+
attr_reader :current_indent
|
16
|
+
|
17
|
+
sig { params(out: T.any(IO, StringIO), indent: Integer, print_locs: T::Boolean).void }
|
18
|
+
def initialize(out: $stdout, indent: 0, print_locs: false)
|
19
|
+
super()
|
20
|
+
@out = out
|
21
|
+
@current_indent = indent
|
22
|
+
@print_locs = print_locs
|
23
|
+
@in_visibility_group = T.let(false, T::Boolean)
|
24
|
+
@previous_node = T.let(nil, T.nilable(Node))
|
25
|
+
end
|
26
|
+
|
27
|
+
# Printing
|
28
|
+
|
29
|
+
sig { void }
|
30
|
+
def indent
|
31
|
+
@current_indent += 2
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { void }
|
35
|
+
def dedent
|
36
|
+
@current_indent -= 2
|
37
|
+
end
|
38
|
+
|
39
|
+
# Print a string without indentation nor `\n` at the end.
|
40
|
+
sig { params(string: String).void }
|
41
|
+
def print(string)
|
42
|
+
@out.print(string)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Print a string without indentation but with a `\n` at the end.
|
46
|
+
sig { params(string: T.nilable(String)).void }
|
47
|
+
def printn(string = nil)
|
48
|
+
print(string) if string
|
49
|
+
print("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
# Print a string with indentation but without a `\n` at the end.
|
53
|
+
sig { params(string: T.nilable(String)).void }
|
54
|
+
def printt(string = nil)
|
55
|
+
print(" " * @current_indent)
|
56
|
+
print(string) if string
|
57
|
+
end
|
58
|
+
|
59
|
+
# Print a string with indentation and `\n` at the end.
|
60
|
+
sig { params(string: String).void }
|
61
|
+
def printl(string)
|
62
|
+
printt
|
63
|
+
printn(string)
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { override.params(nodes: T::Array[Node]).void }
|
67
|
+
def visit_all(nodes)
|
68
|
+
previous_node = @previous_node
|
69
|
+
@previous_node = nil
|
70
|
+
nodes.each do |node|
|
71
|
+
visit(node)
|
72
|
+
@previous_node = node
|
73
|
+
end
|
74
|
+
@previous_node = previous_node
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { override.params(file: File).void }
|
78
|
+
def visit_file(file)
|
79
|
+
unless file.comments.empty?
|
80
|
+
visit_all(file.comments)
|
81
|
+
end
|
82
|
+
|
83
|
+
unless file.root.empty? && file.root.comments.empty?
|
84
|
+
printn unless file.comments.empty?
|
85
|
+
visit(file.root)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { override.params(node: Comment).void }
|
90
|
+
def visit_comment(node)
|
91
|
+
lines = node.text.lines
|
92
|
+
|
93
|
+
if lines.empty?
|
94
|
+
printl("#")
|
95
|
+
end
|
96
|
+
|
97
|
+
lines.each do |line|
|
98
|
+
text = line.rstrip
|
99
|
+
printt("#")
|
100
|
+
print(" #{text}") unless text.empty?
|
101
|
+
printn
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
sig { override.params(node: BlankLine).void }
|
106
|
+
def visit_blank_line(node)
|
107
|
+
printn
|
108
|
+
end
|
109
|
+
|
110
|
+
sig { override.params(node: Tree).void }
|
111
|
+
def visit_tree(node)
|
112
|
+
visit_all(node.comments)
|
113
|
+
printn if !node.comments.empty? && !node.empty?
|
114
|
+
visit_all(node.nodes)
|
115
|
+
end
|
116
|
+
|
117
|
+
sig { override.params(node: Module).void }
|
118
|
+
def visit_module(node)
|
119
|
+
visit_scope(node)
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { override.params(node: Class).void }
|
123
|
+
def visit_class(node)
|
124
|
+
visit_scope(node)
|
125
|
+
end
|
126
|
+
|
127
|
+
sig { override.params(node: Struct).void }
|
128
|
+
def visit_struct(node)
|
129
|
+
visit_scope(node)
|
130
|
+
end
|
131
|
+
|
132
|
+
sig { override.params(node: SingletonClass).void }
|
133
|
+
def visit_singleton_class(node)
|
134
|
+
visit_scope(node)
|
135
|
+
end
|
136
|
+
|
137
|
+
sig { params(node: Scope).void }
|
138
|
+
def visit_scope(node)
|
139
|
+
print_blank_line_before(node)
|
140
|
+
print_loc(node)
|
141
|
+
visit_all(node.comments)
|
142
|
+
|
143
|
+
visit_scope_header(node)
|
144
|
+
visit_scope_body(node)
|
145
|
+
end
|
146
|
+
|
147
|
+
sig { params(node: Scope).void }
|
148
|
+
def visit_scope_header(node)
|
149
|
+
node.nodes.grep(Helper).each do |helper|
|
150
|
+
visit(Comment.new("@#{helper.name}"))
|
151
|
+
end
|
152
|
+
|
153
|
+
mixins = node.nodes.grep(MixesInClassMethods)
|
154
|
+
if mixins.any?
|
155
|
+
visit(Comment.new("@mixes_in_class_methods #{mixins.map(&:names).join(", ")}"))
|
156
|
+
end
|
157
|
+
|
158
|
+
ancestors = node.nodes.grep(RequiresAncestor)
|
159
|
+
if ancestors.any?
|
160
|
+
visit(Comment.new("@requires_ancestor #{ancestors.map(&:name).join(", ")}"))
|
161
|
+
end
|
162
|
+
|
163
|
+
case node
|
164
|
+
when TStruct
|
165
|
+
printt("class #{node.name}")
|
166
|
+
when TEnum
|
167
|
+
printt("class #{node.name}")
|
168
|
+
when Module
|
169
|
+
printt("module #{node.name}")
|
170
|
+
when Class
|
171
|
+
printt("class #{node.name}")
|
172
|
+
superclass = node.superclass_name
|
173
|
+
print(" < #{superclass}") if superclass
|
174
|
+
when Struct
|
175
|
+
printt("#{node.name} = ::Struct.new")
|
176
|
+
if !node.members.empty? || node.keyword_init
|
177
|
+
print("(")
|
178
|
+
args = node.members.map { |member| ":#{member}" }
|
179
|
+
args << "keyword_init: true" if node.keyword_init
|
180
|
+
print(args.join(", "))
|
181
|
+
print(")")
|
182
|
+
end
|
183
|
+
when SingletonClass
|
184
|
+
printt("class << self")
|
185
|
+
else
|
186
|
+
raise Error, "Unhandled node: #{node.class}"
|
187
|
+
end
|
188
|
+
|
189
|
+
type_params = node.nodes.grep(TypeMember)
|
190
|
+
if type_params.any?
|
191
|
+
print("[#{type_params.map(&:name).join(", ")}]")
|
192
|
+
end
|
193
|
+
|
194
|
+
if !node.empty? && node.is_a?(Struct)
|
195
|
+
print(" do")
|
196
|
+
end
|
197
|
+
printn
|
198
|
+
end
|
199
|
+
|
200
|
+
sig { params(node: Scope).void }
|
201
|
+
def visit_scope_body(node)
|
202
|
+
unless node.empty?
|
203
|
+
indent
|
204
|
+
visit_all(node.nodes)
|
205
|
+
dedent
|
206
|
+
end
|
207
|
+
if !node.is_a?(Struct) || !node.empty?
|
208
|
+
printl("end")
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
sig { override.params(node: Const).void }
|
213
|
+
def visit_const(node)
|
214
|
+
print_blank_line_before(node)
|
215
|
+
print_loc(node)
|
216
|
+
visit_all(node.comments)
|
217
|
+
|
218
|
+
type = parse_t_let(node.value)
|
219
|
+
if type
|
220
|
+
type = parse_type(type)
|
221
|
+
printl("#{node.name}: #{type.rbs_string}")
|
222
|
+
else
|
223
|
+
printl("#{node.name}: untyped")
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
sig { override.params(node: AttrAccessor).void }
|
228
|
+
def visit_attr_accessor(node)
|
229
|
+
visit_attr(node)
|
230
|
+
end
|
231
|
+
|
232
|
+
sig { override.params(node: AttrReader).void }
|
233
|
+
def visit_attr_reader(node)
|
234
|
+
visit_attr(node)
|
235
|
+
end
|
236
|
+
|
237
|
+
sig { override.params(node: AttrWriter).void }
|
238
|
+
def visit_attr_writer(node)
|
239
|
+
visit_attr(node)
|
240
|
+
end
|
241
|
+
|
242
|
+
sig { params(node: Attr).void }
|
243
|
+
def visit_attr(node)
|
244
|
+
print_blank_line_before(node)
|
245
|
+
|
246
|
+
node.names.each do |name|
|
247
|
+
visit_all(node.comments)
|
248
|
+
print_loc(node)
|
249
|
+
printt
|
250
|
+
unless in_visibility_group || node.visibility.public? || node.visibility.protected?
|
251
|
+
self.print(node.visibility.visibility.to_s)
|
252
|
+
print(" ")
|
253
|
+
end
|
254
|
+
case node
|
255
|
+
when AttrAccessor
|
256
|
+
print("attr_accessor")
|
257
|
+
when AttrReader
|
258
|
+
print("attr_reader")
|
259
|
+
when AttrWriter
|
260
|
+
print("attr_writer")
|
261
|
+
end
|
262
|
+
print(" #{name}")
|
263
|
+
first_sig, *_rest = node.sigs # discard remaining signatures
|
264
|
+
if first_sig
|
265
|
+
print(": ")
|
266
|
+
print_attr_sig(node, first_sig)
|
267
|
+
else
|
268
|
+
print(": untyped")
|
269
|
+
end
|
270
|
+
printn
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
sig { params(node: RBI::Attr, sig: Sig).void }
|
275
|
+
def print_attr_sig(node, sig)
|
276
|
+
type = case node
|
277
|
+
when AttrAccessor, AttrReader
|
278
|
+
parse_type(sig.return_type)
|
279
|
+
else
|
280
|
+
first_arg = sig.params.first
|
281
|
+
if first_arg
|
282
|
+
parse_type(first_arg.type)
|
283
|
+
else
|
284
|
+
Type.untyped
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
print(type.rbs_string)
|
289
|
+
end
|
290
|
+
|
291
|
+
sig { override.params(node: Method).void }
|
292
|
+
def visit_method(node)
|
293
|
+
print_blank_line_before(node)
|
294
|
+
visit_all(node.comments)
|
295
|
+
|
296
|
+
if node.sigs.any?(&:is_abstract)
|
297
|
+
printl("# @abstract")
|
298
|
+
end
|
299
|
+
|
300
|
+
if node.sigs.any?(&:is_override)
|
301
|
+
printl("# @override")
|
302
|
+
end
|
303
|
+
|
304
|
+
if node.sigs.any?(&:is_overridable)
|
305
|
+
printl("# @overridable")
|
306
|
+
end
|
307
|
+
|
308
|
+
print_loc(node)
|
309
|
+
printt
|
310
|
+
unless in_visibility_group || node.visibility.public?
|
311
|
+
self.print(node.visibility.visibility.to_s)
|
312
|
+
print(" ")
|
313
|
+
end
|
314
|
+
print("def ")
|
315
|
+
print("self.") if node.is_singleton
|
316
|
+
print(node.name)
|
317
|
+
sigs = node.sigs
|
318
|
+
print(": ")
|
319
|
+
if sigs.any?
|
320
|
+
first, *rest = sigs
|
321
|
+
print_method_sig(node, T.must(first))
|
322
|
+
if rest.any?
|
323
|
+
spaces = node.name.size + 4
|
324
|
+
rest.each do |sig|
|
325
|
+
printn
|
326
|
+
printt
|
327
|
+
print("#{" " * spaces}| ")
|
328
|
+
print_method_sig(node, sig)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
else
|
332
|
+
if node.params.any?
|
333
|
+
params = node.params.reject { |param| param.is_a?(BlockParam) }
|
334
|
+
block = node.params.find { |param| param.is_a?(BlockParam) }
|
335
|
+
|
336
|
+
print("(")
|
337
|
+
params.each_with_index do |param, index|
|
338
|
+
print(", ") if index > 0
|
339
|
+
visit(param)
|
340
|
+
end
|
341
|
+
print(") ")
|
342
|
+
visit(block)
|
343
|
+
end
|
344
|
+
print("-> untyped")
|
345
|
+
end
|
346
|
+
printn
|
347
|
+
end
|
348
|
+
|
349
|
+
sig { params(node: RBI::Method, sig: Sig).void }
|
350
|
+
def print_method_sig(node, sig)
|
351
|
+
unless sig.type_params.empty?
|
352
|
+
print("[#{sig.type_params.map { |t| "TYPE_#{t}" }.join(", ")}] ")
|
353
|
+
end
|
354
|
+
|
355
|
+
block_param = node.params.find { |param| param.is_a?(BlockParam) }
|
356
|
+
sig_block_param = sig.params.find { |param| param.name == block_param&.name }
|
357
|
+
|
358
|
+
sig_params = sig.params.dup
|
359
|
+
if block_param
|
360
|
+
sig_params.reject! do |param|
|
361
|
+
param.name == block_param.name
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
unless sig_params.empty?
|
366
|
+
print("(")
|
367
|
+
sig_params.each_with_index do |param, index|
|
368
|
+
print(", ") if index > 0
|
369
|
+
print_sig_param(node, param)
|
370
|
+
end
|
371
|
+
print(") ")
|
372
|
+
end
|
373
|
+
if sig_block_param
|
374
|
+
block_type = sig_block_param.type
|
375
|
+
block_type = Type.parse_string(block_type) if block_type.is_a?(String)
|
376
|
+
|
377
|
+
block_is_nilable = false
|
378
|
+
if block_type.is_a?(Type::Nilable)
|
379
|
+
block_is_nilable = true
|
380
|
+
block_type = block_type.type
|
381
|
+
end
|
382
|
+
|
383
|
+
type_string = parse_type(block_type).rbs_string.delete_prefix("^")
|
384
|
+
|
385
|
+
skip = false
|
386
|
+
case block_type
|
387
|
+
when Type::Untyped
|
388
|
+
type_string = "(?) -> untyped"
|
389
|
+
block_is_nilable = true
|
390
|
+
when Type::Simple
|
391
|
+
if block_type.name == "Proc"
|
392
|
+
type_string = "(?) -> untyped"
|
393
|
+
end
|
394
|
+
skip = true if block_type.name == "NilClass"
|
395
|
+
end
|
396
|
+
|
397
|
+
if skip
|
398
|
+
# no-op, we skip the block definition
|
399
|
+
elsif block_is_nilable
|
400
|
+
print("?{ #{type_string} } ")
|
401
|
+
else
|
402
|
+
print("{ #{type_string} } ")
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
type = parse_type(sig.return_type)
|
407
|
+
print("-> #{type.rbs_string}")
|
408
|
+
|
409
|
+
loc = sig.loc
|
410
|
+
print(" # #{loc}") if loc && print_locs
|
411
|
+
end
|
412
|
+
|
413
|
+
sig { override.params(node: ReqParam).void }
|
414
|
+
def visit_req_param(node)
|
415
|
+
print("untyped #{node.name}")
|
416
|
+
end
|
417
|
+
|
418
|
+
sig { override.params(node: OptParam).void }
|
419
|
+
def visit_opt_param(node)
|
420
|
+
print("?untyped #{node.name}")
|
421
|
+
end
|
422
|
+
|
423
|
+
sig { override.params(node: RestParam).void }
|
424
|
+
def visit_rest_param(node)
|
425
|
+
print("*untyped #{node.name}")
|
426
|
+
end
|
427
|
+
|
428
|
+
sig { override.params(node: KwParam).void }
|
429
|
+
def visit_kw_param(node)
|
430
|
+
print("#{node.name}: untyped")
|
431
|
+
end
|
432
|
+
|
433
|
+
sig { override.params(node: KwOptParam).void }
|
434
|
+
def visit_kw_opt_param(node)
|
435
|
+
print("?#{node.name}: untyped")
|
436
|
+
end
|
437
|
+
|
438
|
+
sig { override.params(node: KwRestParam).void }
|
439
|
+
def visit_kw_rest_param(node)
|
440
|
+
print("**#{node.name}: untyped")
|
441
|
+
end
|
442
|
+
|
443
|
+
sig { override.params(node: BlockParam).void }
|
444
|
+
def visit_block_param(node)
|
445
|
+
print("{ (*untyped) -> untyped } ")
|
446
|
+
end
|
447
|
+
|
448
|
+
sig { override.params(node: Include).void }
|
449
|
+
def visit_include(node)
|
450
|
+
visit_mixin(node)
|
451
|
+
end
|
452
|
+
|
453
|
+
sig { override.params(node: Extend).void }
|
454
|
+
def visit_extend(node)
|
455
|
+
visit_mixin(node)
|
456
|
+
end
|
457
|
+
|
458
|
+
sig { params(node: Mixin).void }
|
459
|
+
def visit_mixin(node)
|
460
|
+
return if node.is_a?(MixesInClassMethods) # no-op, `mixes_in_class_methods` is not supported in RBS
|
461
|
+
|
462
|
+
print_blank_line_before(node)
|
463
|
+
print_loc(node)
|
464
|
+
visit_all(node.comments)
|
465
|
+
|
466
|
+
case node
|
467
|
+
when Include
|
468
|
+
printt("include")
|
469
|
+
when Extend
|
470
|
+
printt("extend")
|
471
|
+
end
|
472
|
+
printn(" #{node.names.join(", ")}")
|
473
|
+
end
|
474
|
+
|
475
|
+
sig { override.params(node: Public).void }
|
476
|
+
def visit_public(node)
|
477
|
+
visit_visibility(node)
|
478
|
+
end
|
479
|
+
|
480
|
+
sig { override.params(node: Protected).void }
|
481
|
+
def visit_protected(node)
|
482
|
+
# no-op, `protected` is not supported in RBS
|
483
|
+
end
|
484
|
+
|
485
|
+
sig { override.params(node: Private).void }
|
486
|
+
def visit_private(node)
|
487
|
+
visit_visibility(node)
|
488
|
+
end
|
489
|
+
|
490
|
+
sig { params(node: Visibility).void }
|
491
|
+
def visit_visibility(node)
|
492
|
+
print_blank_line_before(node)
|
493
|
+
print_loc(node)
|
494
|
+
visit_all(node.comments)
|
495
|
+
|
496
|
+
printl(node.visibility.to_s)
|
497
|
+
end
|
498
|
+
|
499
|
+
sig { override.params(node: Send).void }
|
500
|
+
def visit_send(node)
|
501
|
+
# no-op, arbitrary sends are not supported in RBS
|
502
|
+
end
|
503
|
+
|
504
|
+
sig { override.params(node: Arg).void }
|
505
|
+
def visit_arg(node)
|
506
|
+
# no-op
|
507
|
+
end
|
508
|
+
|
509
|
+
sig { override.params(node: KwArg).void }
|
510
|
+
def visit_kw_arg(node)
|
511
|
+
# no-op
|
512
|
+
end
|
513
|
+
|
514
|
+
sig { override.params(node: TStruct).void }
|
515
|
+
def visit_tstruct(node)
|
516
|
+
print_blank_line_before(node)
|
517
|
+
print_loc(node)
|
518
|
+
visit_all(node.comments)
|
519
|
+
|
520
|
+
visit_scope_header(node)
|
521
|
+
nodes = node.nodes.dup
|
522
|
+
|
523
|
+
sig = Sig.new
|
524
|
+
init = Method.new("initialize", sigs: [sig])
|
525
|
+
last_field = -1
|
526
|
+
nodes.each_with_index do |child, i|
|
527
|
+
case child
|
528
|
+
when TStructField
|
529
|
+
default = child.default
|
530
|
+
init << if default
|
531
|
+
KwOptParam.new(child.name, default)
|
532
|
+
else
|
533
|
+
KwParam.new(child.name)
|
534
|
+
end
|
535
|
+
sig << SigParam.new(child.name, child.type)
|
536
|
+
last_field = i
|
537
|
+
end
|
538
|
+
end
|
539
|
+
nodes.insert(last_field + 1, init)
|
540
|
+
|
541
|
+
indent
|
542
|
+
visit_all(nodes)
|
543
|
+
dedent
|
544
|
+
|
545
|
+
printl("end")
|
546
|
+
end
|
547
|
+
|
548
|
+
sig { override.params(node: TStructConst).void }
|
549
|
+
def visit_tstruct_const(node)
|
550
|
+
# `T::Struct.const` is not supported in RBS instead we generate an attribute reader
|
551
|
+
accessor = AttrReader.new(node.name.to_sym, comments: node.comments, sigs: [Sig.new(return_type: node.type)])
|
552
|
+
visit_attr_reader(accessor)
|
553
|
+
end
|
554
|
+
|
555
|
+
sig { override.params(node: TStructProp).void }
|
556
|
+
def visit_tstruct_prop(node)
|
557
|
+
# `T::Struct.prop` is not supported in RBS instead we generate an attribute accessor
|
558
|
+
accessor = AttrAccessor.new(node.name.to_sym, comments: node.comments, sigs: [Sig.new(return_type: node.type)])
|
559
|
+
visit_attr_accessor(accessor)
|
560
|
+
end
|
561
|
+
|
562
|
+
sig { override.params(node: TEnum).void }
|
563
|
+
def visit_tenum(node)
|
564
|
+
visit_scope(node)
|
565
|
+
end
|
566
|
+
|
567
|
+
sig { override.params(node: TEnumBlock).void }
|
568
|
+
def visit_tenum_block(node)
|
569
|
+
node.nodes.each do |child|
|
570
|
+
child = if child.is_a?(Const) && child.value == "new"
|
571
|
+
parent = node.parent_scope
|
572
|
+
Const.new(
|
573
|
+
child.name,
|
574
|
+
"T.let(nil, #{parent.is_a?(TEnum) ? parent.name : "T.untyped"})",
|
575
|
+
comments: child.comments,
|
576
|
+
)
|
577
|
+
else
|
578
|
+
child
|
579
|
+
end
|
580
|
+
visit(child)
|
581
|
+
@previous_node = child
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
sig { override.params(node: TypeMember).void }
|
586
|
+
def visit_type_member(node)
|
587
|
+
# no-op, we already show them in the scope header
|
588
|
+
end
|
589
|
+
|
590
|
+
sig { override.params(node: Helper).void }
|
591
|
+
def visit_helper(node)
|
592
|
+
# no-op, we already show them in the scope header
|
593
|
+
end
|
594
|
+
|
595
|
+
sig { override.params(node: MixesInClassMethods).void }
|
596
|
+
def visit_mixes_in_class_methods(node)
|
597
|
+
visit_mixin(node)
|
598
|
+
end
|
599
|
+
|
600
|
+
sig { override.params(node: Group).void }
|
601
|
+
def visit_group(node)
|
602
|
+
printn unless previous_node.nil?
|
603
|
+
visit_all(node.nodes)
|
604
|
+
end
|
605
|
+
|
606
|
+
sig { override.params(node: VisibilityGroup).void }
|
607
|
+
def visit_visibility_group(node)
|
608
|
+
self.in_visibility_group = true
|
609
|
+
if node.visibility.public?
|
610
|
+
printn unless previous_node.nil?
|
611
|
+
else
|
612
|
+
visit(node.visibility)
|
613
|
+
printn
|
614
|
+
end
|
615
|
+
visit_all(node.nodes)
|
616
|
+
self.in_visibility_group = false
|
617
|
+
end
|
618
|
+
|
619
|
+
sig { override.params(node: RequiresAncestor).void }
|
620
|
+
def visit_requires_ancestor(node)
|
621
|
+
# no-op, we already show them in the scope header
|
622
|
+
end
|
623
|
+
|
624
|
+
sig { override.params(node: ConflictTree).void }
|
625
|
+
def visit_conflict_tree(node)
|
626
|
+
printl("<<<<<<< #{node.left_name}")
|
627
|
+
visit(node.left)
|
628
|
+
printl("=======")
|
629
|
+
visit(node.right)
|
630
|
+
printl(">>>>>>> #{node.right_name}")
|
631
|
+
end
|
632
|
+
|
633
|
+
sig { override.params(node: ScopeConflict).void }
|
634
|
+
def visit_scope_conflict(node)
|
635
|
+
print_blank_line_before(node)
|
636
|
+
print_loc(node)
|
637
|
+
visit_all(node.comments)
|
638
|
+
|
639
|
+
printl("<<<<<<< #{node.left_name}")
|
640
|
+
visit_scope_header(node.left)
|
641
|
+
printl("=======")
|
642
|
+
visit_scope_header(node.right)
|
643
|
+
printl(">>>>>>> #{node.right_name}")
|
644
|
+
visit_scope_body(node.left)
|
645
|
+
end
|
646
|
+
|
647
|
+
private
|
648
|
+
|
649
|
+
sig { params(node: Node).void }
|
650
|
+
def print_blank_line_before(node)
|
651
|
+
previous_node = self.previous_node
|
652
|
+
return unless previous_node
|
653
|
+
return if previous_node.is_a?(BlankLine)
|
654
|
+
return if previous_node.is_a?(TypeMember) # since we skip them
|
655
|
+
return if previous_node.is_a?(Helper) # since we skip them
|
656
|
+
return if previous_node.is_a?(Protected) # since we skip them
|
657
|
+
return if previous_node.is_a?(RequiresAncestor) # since we skip them
|
658
|
+
return if previous_node.is_a?(Send) # since we skip them
|
659
|
+
return if previous_node.is_a?(Arg) # since we skip them
|
660
|
+
return if previous_node.is_a?(KwArg) # since we skip them
|
661
|
+
return if previous_node.is_a?(TEnumBlock) # since we skip them
|
662
|
+
return if previous_node.is_a?(MixesInClassMethods) # since we skip them
|
663
|
+
return if oneline?(previous_node) && oneline?(node)
|
664
|
+
|
665
|
+
printn
|
666
|
+
end
|
667
|
+
|
668
|
+
sig { params(node: Node).void }
|
669
|
+
def print_loc(node)
|
670
|
+
loc = node.loc
|
671
|
+
printl("# #{loc}") if loc && print_locs
|
672
|
+
end
|
673
|
+
|
674
|
+
sig { params(node: Method, param: SigParam).void }
|
675
|
+
def print_sig_param(node, param)
|
676
|
+
type = parse_type(param.type).rbs_string
|
677
|
+
|
678
|
+
orig_param = node.params.find { |p| p.name == param.name }
|
679
|
+
|
680
|
+
case orig_param
|
681
|
+
when ReqParam
|
682
|
+
print("#{type} #{param.name}")
|
683
|
+
when OptParam
|
684
|
+
print("?#{type} #{param.name}")
|
685
|
+
when RestParam
|
686
|
+
print("*#{type} #{param.name}")
|
687
|
+
when KwParam
|
688
|
+
print("#{param.name}: #{type}")
|
689
|
+
when KwOptParam
|
690
|
+
print("?#{param.name}: #{type}")
|
691
|
+
when KwRestParam
|
692
|
+
print("**#{type} #{param.name}")
|
693
|
+
else
|
694
|
+
raise Error, "Unexpected param type: #{orig_param.class} for param #{param.name}"
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
sig { params(node: Param, last: T::Boolean).void }
|
699
|
+
def print_param_comment_leading_space(node, last:)
|
700
|
+
printn
|
701
|
+
printt
|
702
|
+
print(" " * (node.name.size + 1))
|
703
|
+
print(" ") unless last
|
704
|
+
case node
|
705
|
+
when OptParam
|
706
|
+
print(" " * (node.value.size + 3))
|
707
|
+
when RestParam, KwParam, BlockParam
|
708
|
+
print(" ")
|
709
|
+
when KwRestParam
|
710
|
+
print(" ")
|
711
|
+
when KwOptParam
|
712
|
+
print(" " * (node.value.size + 2))
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
716
|
+
sig { params(node: SigParam, last: T::Boolean).void }
|
717
|
+
def print_sig_param_comment_leading_space(node, last:)
|
718
|
+
printn
|
719
|
+
printt
|
720
|
+
print(" " * (node.name.size + node.type.to_s.size + 3))
|
721
|
+
print(" ") unless last
|
722
|
+
end
|
723
|
+
|
724
|
+
sig { params(node: Node).returns(T::Boolean) }
|
725
|
+
def oneline?(node)
|
726
|
+
case node
|
727
|
+
when ScopeConflict
|
728
|
+
oneline?(node.left)
|
729
|
+
when Tree
|
730
|
+
false
|
731
|
+
when Attr
|
732
|
+
node.comments.empty?
|
733
|
+
when Method
|
734
|
+
node.comments.empty? && node.sigs.empty? && node.params.all? { |p| p.comments.empty? }
|
735
|
+
when Sig
|
736
|
+
node.params.all? { |p| p.comments.empty? }
|
737
|
+
when NodeWithComments
|
738
|
+
node.comments.empty?
|
739
|
+
when VisibilityGroup
|
740
|
+
false
|
741
|
+
else
|
742
|
+
true
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
sig { params(type: T.any(Type, String)).returns(Type) }
|
747
|
+
def parse_type(type)
|
748
|
+
return type if type.is_a?(Type)
|
749
|
+
|
750
|
+
Type.parse_string(type)
|
751
|
+
rescue Type::Error => e
|
752
|
+
raise Error, "Failed to parse type `#{type}` (#{e.message})"
|
753
|
+
end
|
754
|
+
|
755
|
+
# Parse a string containing a `T.let(x, X)` and extract the type
|
756
|
+
#
|
757
|
+
# Returns `nil` is the string is not a `T.let`.
|
758
|
+
sig { params(code: T.nilable(String)).returns(T.nilable(String)) }
|
759
|
+
def parse_t_let(code)
|
760
|
+
return unless code
|
761
|
+
|
762
|
+
res = Prism.parse(code)
|
763
|
+
return unless res.success?
|
764
|
+
|
765
|
+
node = res.value
|
766
|
+
return unless node.is_a?(Prism::ProgramNode)
|
767
|
+
|
768
|
+
node = node.statements.body.first
|
769
|
+
return unless node.is_a?(Prism::CallNode)
|
770
|
+
return unless node.name == :let
|
771
|
+
return unless node.receiver&.slice =~ /^(::)?T$/
|
772
|
+
|
773
|
+
node.arguments&.arguments&.fetch(1, nil)&.slice
|
774
|
+
end
|
775
|
+
|
776
|
+
sig { params(node: Type).returns(T::Boolean) }
|
777
|
+
def bare_proc?(node)
|
778
|
+
node.is_a?(Type::Simple) && node.name == "Proc"
|
779
|
+
end
|
780
|
+
|
781
|
+
sig { params(node: Type).returns(T::Boolean) }
|
782
|
+
def bare_nilable_proc?(node)
|
783
|
+
node.is_a?(Type::Nilable) && bare_proc?(node.type)
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
class TypePrinter
|
788
|
+
extend T::Sig
|
789
|
+
|
790
|
+
sig { returns(String) }
|
791
|
+
attr_reader :string
|
792
|
+
|
793
|
+
sig { void }
|
794
|
+
def initialize
|
795
|
+
@string = T.let(String.new, String)
|
796
|
+
end
|
797
|
+
|
798
|
+
sig { params(node: Type).void }
|
799
|
+
def visit(node)
|
800
|
+
case node
|
801
|
+
when Type::Simple
|
802
|
+
visit_simple(node)
|
803
|
+
when Type::Boolean
|
804
|
+
visit_boolean(node)
|
805
|
+
when Type::Generic
|
806
|
+
visit_generic(node)
|
807
|
+
when Type::Anything
|
808
|
+
visit_anything(node)
|
809
|
+
when Type::Void
|
810
|
+
visit_void(node)
|
811
|
+
when Type::NoReturn
|
812
|
+
visit_no_return(node)
|
813
|
+
when Type::Untyped
|
814
|
+
visit_untyped(node)
|
815
|
+
when Type::SelfType
|
816
|
+
visit_self_type(node)
|
817
|
+
when Type::AttachedClass
|
818
|
+
visit_attached_class(node)
|
819
|
+
when Type::Nilable
|
820
|
+
visit_nilable(node)
|
821
|
+
when Type::ClassOf
|
822
|
+
visit_class_of(node)
|
823
|
+
when Type::All
|
824
|
+
visit_all(node)
|
825
|
+
when Type::Any
|
826
|
+
visit_any(node)
|
827
|
+
when Type::Tuple
|
828
|
+
visit_tuple(node)
|
829
|
+
when Type::Shape
|
830
|
+
visit_shape(node)
|
831
|
+
when Type::Proc
|
832
|
+
visit_proc(node)
|
833
|
+
when Type::TypeParameter
|
834
|
+
visit_type_parameter(node)
|
835
|
+
when Type::Class
|
836
|
+
visit_class(node)
|
837
|
+
else
|
838
|
+
raise Error, "Unhandled node: #{node.class}"
|
839
|
+
end
|
840
|
+
end
|
841
|
+
|
842
|
+
sig { params(type: Type::Simple).void }
|
843
|
+
def visit_simple(type)
|
844
|
+
@string << translate_t_type(type.name)
|
845
|
+
end
|
846
|
+
|
847
|
+
sig { params(type: Type::Boolean).void }
|
848
|
+
def visit_boolean(type)
|
849
|
+
@string << "bool"
|
850
|
+
end
|
851
|
+
|
852
|
+
sig { params(type: Type::Generic).void }
|
853
|
+
def visit_generic(type)
|
854
|
+
@string << translate_t_type(type.name)
|
855
|
+
@string << "["
|
856
|
+
type.params.each_with_index do |arg, index|
|
857
|
+
visit(arg)
|
858
|
+
@string << ", " if index < type.params.size - 1
|
859
|
+
end
|
860
|
+
@string << "]"
|
861
|
+
end
|
862
|
+
|
863
|
+
sig { params(type: Type::Anything).void }
|
864
|
+
def visit_anything(type)
|
865
|
+
@string << "top"
|
866
|
+
end
|
867
|
+
|
868
|
+
sig { params(type: Type::Void).void }
|
869
|
+
def visit_void(type)
|
870
|
+
@string << "void"
|
871
|
+
end
|
872
|
+
|
873
|
+
sig { params(type: Type::NoReturn).void }
|
874
|
+
def visit_no_return(type)
|
875
|
+
@string << "bot"
|
876
|
+
end
|
877
|
+
|
878
|
+
sig { params(type: Type::Untyped).void }
|
879
|
+
def visit_untyped(type)
|
880
|
+
@string << "untyped"
|
881
|
+
end
|
882
|
+
|
883
|
+
sig { params(type: Type::SelfType).void }
|
884
|
+
def visit_self_type(type)
|
885
|
+
@string << "self"
|
886
|
+
end
|
887
|
+
|
888
|
+
sig { params(type: Type::AttachedClass).void }
|
889
|
+
def visit_attached_class(type)
|
890
|
+
@string << "attached_class"
|
891
|
+
end
|
892
|
+
|
893
|
+
sig { params(type: Type::Nilable).void }
|
894
|
+
def visit_nilable(type)
|
895
|
+
visit(type.type)
|
896
|
+
@string << "?"
|
897
|
+
end
|
898
|
+
|
899
|
+
sig { params(type: Type::ClassOf).void }
|
900
|
+
def visit_class_of(type)
|
901
|
+
@string << "singleton("
|
902
|
+
visit(type.type)
|
903
|
+
@string << ")"
|
904
|
+
end
|
905
|
+
|
906
|
+
sig { params(type: Type::All).void }
|
907
|
+
def visit_all(type)
|
908
|
+
@string << "("
|
909
|
+
type.types.each_with_index do |arg, index|
|
910
|
+
visit(arg)
|
911
|
+
@string << " & " if index < type.types.size - 1
|
912
|
+
end
|
913
|
+
@string << ")"
|
914
|
+
end
|
915
|
+
|
916
|
+
sig { params(type: Type::Any).void }
|
917
|
+
def visit_any(type)
|
918
|
+
@string << "("
|
919
|
+
type.types.each_with_index do |arg, index|
|
920
|
+
visit(arg)
|
921
|
+
@string << " | " if index < type.types.size - 1
|
922
|
+
end
|
923
|
+
@string << ")"
|
924
|
+
end
|
925
|
+
|
926
|
+
sig { params(type: Type::Tuple).void }
|
927
|
+
def visit_tuple(type)
|
928
|
+
@string << "["
|
929
|
+
type.types.each_with_index do |arg, index|
|
930
|
+
visit(arg)
|
931
|
+
@string << ", " if index < type.types.size - 1
|
932
|
+
end
|
933
|
+
@string << "]"
|
934
|
+
end
|
935
|
+
|
936
|
+
sig { params(type: Type::Shape).void }
|
937
|
+
def visit_shape(type)
|
938
|
+
@string << "{"
|
939
|
+
type.types.each_with_index do |(key, value), index|
|
940
|
+
@string << "#{key}: "
|
941
|
+
visit(value)
|
942
|
+
@string << ", " if index < type.types.size - 1
|
943
|
+
end
|
944
|
+
@string << "}"
|
945
|
+
end
|
946
|
+
|
947
|
+
sig { params(type: Type::Proc).void }
|
948
|
+
def visit_proc(type)
|
949
|
+
@string << "^"
|
950
|
+
if type.proc_params.any?
|
951
|
+
@string << "("
|
952
|
+
type.proc_params.each_with_index do |(key, value), index|
|
953
|
+
visit(value)
|
954
|
+
@string << " #{key}"
|
955
|
+
@string << ", " if index < type.proc_params.size - 1
|
956
|
+
end
|
957
|
+
@string << ") "
|
958
|
+
end
|
959
|
+
@string << "-> "
|
960
|
+
visit(type.proc_returns)
|
961
|
+
end
|
962
|
+
|
963
|
+
sig { params(type: Type::TypeParameter).void }
|
964
|
+
def visit_type_parameter(type)
|
965
|
+
@string << "TYPE_#{type.name}"
|
966
|
+
end
|
967
|
+
|
968
|
+
sig { params(type: Type::Class).void }
|
969
|
+
def visit_class(type)
|
970
|
+
@string << "singleton("
|
971
|
+
visit(type.type)
|
972
|
+
@string << ")"
|
973
|
+
end
|
974
|
+
|
975
|
+
private
|
976
|
+
|
977
|
+
sig { params(type_name: String).returns(String) }
|
978
|
+
def translate_t_type(type_name)
|
979
|
+
case type_name
|
980
|
+
when "T::Array"
|
981
|
+
"Array"
|
982
|
+
when "T::Hash"
|
983
|
+
"Hash"
|
984
|
+
when "T::Set"
|
985
|
+
"Set"
|
986
|
+
else
|
987
|
+
type_name
|
988
|
+
end
|
989
|
+
end
|
990
|
+
end
|
991
|
+
|
992
|
+
class File
|
993
|
+
extend T::Sig
|
994
|
+
|
995
|
+
sig { params(out: T.any(IO, StringIO), indent: Integer, print_locs: T::Boolean).void }
|
996
|
+
def rbs_print(out: $stdout, indent: 0, print_locs: false)
|
997
|
+
p = RBSPrinter.new(out: out, indent: indent, print_locs: print_locs)
|
998
|
+
p.visit_file(self)
|
999
|
+
end
|
1000
|
+
|
1001
|
+
sig { params(indent: Integer, print_locs: T::Boolean).returns(String) }
|
1002
|
+
def rbs_string(indent: 0, print_locs: false)
|
1003
|
+
out = StringIO.new
|
1004
|
+
rbs_print(out: out, indent: indent, print_locs: print_locs)
|
1005
|
+
out.string
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
class Node
|
1010
|
+
extend T::Sig
|
1011
|
+
|
1012
|
+
sig { params(out: T.any(IO, StringIO), indent: Integer, print_locs: T::Boolean).void }
|
1013
|
+
def rbs_print(out: $stdout, indent: 0, print_locs: false)
|
1014
|
+
p = RBSPrinter.new(out: out, indent: indent, print_locs: print_locs)
|
1015
|
+
p.visit(self)
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
sig { params(indent: Integer, print_locs: T::Boolean).returns(String) }
|
1019
|
+
def rbs_string(indent: 0, print_locs: false)
|
1020
|
+
out = StringIO.new
|
1021
|
+
rbs_print(out: out, indent: indent, print_locs: print_locs)
|
1022
|
+
out.string
|
1023
|
+
end
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
class Type
|
1027
|
+
extend T::Sig
|
1028
|
+
|
1029
|
+
sig { returns(String) }
|
1030
|
+
def rbs_string
|
1031
|
+
p = TypePrinter.new
|
1032
|
+
p.visit(self)
|
1033
|
+
p.string
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
end
|
data/lib/rbi/version.rb
CHANGED
data/lib/rbi.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rbi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre Terrasa
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prism
|
@@ -55,6 +55,7 @@ files:
|
|
55
55
|
- lib/rbi/model.rb
|
56
56
|
- lib/rbi/parser.rb
|
57
57
|
- lib/rbi/printer.rb
|
58
|
+
- lib/rbi/rbs_printer.rb
|
58
59
|
- lib/rbi/rewriters/add_sig_templates.rb
|
59
60
|
- lib/rbi/rewriters/annotate.rb
|
60
61
|
- lib/rbi/rewriters/attr_to_methods.rb
|
@@ -94,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
95
|
- !ruby/object:Gem::Version
|
95
96
|
version: '0'
|
96
97
|
requirements: []
|
97
|
-
rubygems_version: 3.5.
|
98
|
+
rubygems_version: 3.5.20
|
98
99
|
signing_key:
|
99
100
|
specification_version: 4
|
100
101
|
summary: RBI generation framework
|