rbi 0.0.2 → 0.0.3
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/README.md +1 -1
- data/lib/rbi/model.rb +429 -57
- data/lib/rbi/parser.rb +200 -87
- data/lib/rbi/printer.rb +123 -29
- data/lib/rbi/rewriters/add_sig_templates.rb +71 -0
- data/lib/rbi/rewriters/merge_trees.rb +48 -8
- data/lib/rbi/rewriters/nest_non_public_methods.rb +5 -5
- data/lib/rbi/rewriters/sort_nodes.rb +16 -16
- data/lib/rbi/version.rb +1 -1
- data/lib/rbi.rb +1 -0
- metadata +4 -3
data/lib/rbi/parser.rb
CHANGED
@@ -4,10 +4,21 @@
|
|
4
4
|
require "unparser"
|
5
5
|
|
6
6
|
module RBI
|
7
|
-
class
|
7
|
+
class ParseError < StandardError
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
|
10
|
+
sig { returns(Loc) }
|
11
|
+
attr_reader :location
|
12
|
+
|
13
|
+
sig { params(message: String, location: Loc).void }
|
14
|
+
def initialize(message, location)
|
15
|
+
super(message)
|
16
|
+
@location = location
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Parser
|
21
|
+
extend T::Sig
|
11
22
|
|
12
23
|
# opt-in to most recent AST format
|
13
24
|
::Parser::Builders::Default.emit_lambda = true
|
@@ -29,15 +40,11 @@ module RBI
|
|
29
40
|
sig { params(string: String).returns(Tree) }
|
30
41
|
def parse_string(string)
|
31
42
|
parse(string, file: "-")
|
32
|
-
rescue ::Parser::SyntaxError => e
|
33
|
-
raise Error, e.message
|
34
43
|
end
|
35
44
|
|
36
45
|
sig { params(path: String).returns(Tree) }
|
37
46
|
def parse_file(path)
|
38
47
|
parse(::File.read(path), file: path)
|
39
|
-
rescue ::Parser::SyntaxError => e
|
40
|
-
raise Error, e.message
|
41
48
|
end
|
42
49
|
|
43
50
|
private
|
@@ -47,9 +54,12 @@ module RBI
|
|
47
54
|
node, comments = Unparser.parse_with_comments(content)
|
48
55
|
assoc = ::Parser::Source::Comment.associate_locations(node, comments)
|
49
56
|
builder = TreeBuilder.new(file: file, comments: assoc)
|
57
|
+
builder.separate_header_comments
|
50
58
|
builder.visit(node)
|
51
59
|
builder.assoc_dangling_comments(comments)
|
52
60
|
builder.tree
|
61
|
+
rescue ::Parser::SyntaxError => e
|
62
|
+
raise ParseError.new(e.message, Loc.from_ast_loc(file, e.diagnostic.location))
|
53
63
|
end
|
54
64
|
end
|
55
65
|
|
@@ -70,12 +80,12 @@ module RBI
|
|
70
80
|
private
|
71
81
|
|
72
82
|
sig { params(node: AST::Node).returns(String) }
|
73
|
-
def
|
83
|
+
def parse_name(node)
|
74
84
|
T.must(ConstBuilder.visit(node))
|
75
85
|
end
|
76
86
|
|
77
87
|
sig { params(node: AST::Node).returns(String) }
|
78
|
-
def
|
88
|
+
def parse_expr(node)
|
79
89
|
Unparser.unparse(node)
|
80
90
|
end
|
81
91
|
end
|
@@ -89,10 +99,10 @@ module RBI
|
|
89
99
|
sig do
|
90
100
|
params(
|
91
101
|
file: String,
|
92
|
-
comments: T
|
102
|
+
comments: T::Hash[::Parser::Source::Map, T::Array[::Parser::Source::Comment]]
|
93
103
|
).void
|
94
104
|
end
|
95
|
-
def initialize(file:, comments:
|
105
|
+
def initialize(file:, comments: {})
|
96
106
|
super()
|
97
107
|
@file = file
|
98
108
|
@comments = comments
|
@@ -106,130 +116,171 @@ module RBI
|
|
106
116
|
return unless node.is_a?(AST::Node)
|
107
117
|
case node.type
|
108
118
|
when :module, :class, :sclass
|
109
|
-
|
119
|
+
scope = parse_scope(node)
|
120
|
+
current_scope << scope
|
121
|
+
@scopes_stack << scope
|
122
|
+
visit_all(node.children)
|
123
|
+
@scopes_stack.pop
|
110
124
|
when :casgn
|
111
|
-
|
125
|
+
current_scope << parse_const_assign(node)
|
112
126
|
when :def, :defs
|
113
|
-
|
127
|
+
current_scope << parse_def(node)
|
114
128
|
when :send
|
115
|
-
|
129
|
+
node = parse_send(node)
|
130
|
+
current_scope << node if node
|
116
131
|
when :block
|
117
|
-
|
132
|
+
node = parse_block(node)
|
133
|
+
if node.is_a?(Sig)
|
134
|
+
@last_sigs << node
|
135
|
+
elsif node
|
136
|
+
current_scope << node
|
137
|
+
end
|
118
138
|
else
|
119
139
|
visit_all(node.children)
|
120
140
|
end
|
121
141
|
end
|
122
142
|
|
143
|
+
sig { void }
|
144
|
+
def separate_header_comments
|
145
|
+
return if @comments.empty?
|
146
|
+
|
147
|
+
keep = []
|
148
|
+
node = T.must(@comments.keys.first)
|
149
|
+
comments = T.must(@comments.values.first)
|
150
|
+
|
151
|
+
last_line = T.let(nil, T.nilable(Integer))
|
152
|
+
comments.reverse.each do |comment|
|
153
|
+
comment_line = comment.location.last_line
|
154
|
+
|
155
|
+
break if last_line && comment_line < last_line - 1 ||
|
156
|
+
!last_line && comment_line < node.first_line - 1
|
157
|
+
|
158
|
+
keep << comment
|
159
|
+
last_line = comment_line
|
160
|
+
end
|
161
|
+
|
162
|
+
@comments[node] = keep.reverse
|
163
|
+
end
|
164
|
+
|
123
165
|
sig { params(comments: T::Array[::Parser::Source::Comment]).void }
|
124
166
|
def assoc_dangling_comments(comments)
|
125
|
-
|
126
|
-
comments.each do |comment|
|
167
|
+
last_line = T.let(nil, T.nilable(Integer))
|
168
|
+
(comments - @comments.values.flatten).each do |comment|
|
169
|
+
comment_line = comment.location.last_line
|
127
170
|
text = comment.text[1..-1].strip
|
128
|
-
loc =
|
171
|
+
loc = Loc.from_ast_loc(@file, comment.location)
|
172
|
+
|
173
|
+
if last_line && comment_line > last_line + 1
|
174
|
+
# Preserve empty lines in file headers
|
175
|
+
tree.comments << EmptyComment.new(loc: loc)
|
176
|
+
end
|
177
|
+
|
129
178
|
tree.comments << Comment.new(text, loc: loc)
|
179
|
+
last_line = comment_line
|
130
180
|
end
|
131
181
|
end
|
132
182
|
|
133
183
|
private
|
134
184
|
|
135
|
-
sig { params(node: AST::Node).
|
136
|
-
def
|
185
|
+
sig { params(node: AST::Node).returns(Scope) }
|
186
|
+
def parse_scope(node)
|
137
187
|
loc = node_loc(node)
|
138
188
|
comments = node_comments(node)
|
139
189
|
|
140
|
-
|
190
|
+
case node.type
|
141
191
|
when :module
|
142
|
-
name =
|
192
|
+
name = parse_name(node.children[0])
|
143
193
|
Module.new(name, loc: loc, comments: comments)
|
144
194
|
when :class
|
145
|
-
name =
|
195
|
+
name = parse_name(node.children[0])
|
146
196
|
superclass_name = ConstBuilder.visit(node.children[1])
|
147
197
|
Class.new(name, superclass_name: superclass_name, loc: loc, comments: comments)
|
148
198
|
when :sclass
|
149
199
|
SingletonClass.new(loc: loc, comments: comments)
|
150
200
|
else
|
151
|
-
raise "Unsupported node
|
201
|
+
raise ParseError.new("Unsupported scope node type `#{node.type}`", loc)
|
152
202
|
end
|
153
|
-
|
203
|
+
end
|
154
204
|
|
155
|
-
|
156
|
-
|
157
|
-
|
205
|
+
sig { params(node: AST::Node).returns(RBI::Node) }
|
206
|
+
def parse_const_assign(node)
|
207
|
+
node_value = node.children[2]
|
208
|
+
if struct_definition?(node_value)
|
209
|
+
parse_struct(node)
|
210
|
+
else
|
211
|
+
name = parse_name(node)
|
212
|
+
value = parse_expr(node_value)
|
213
|
+
loc = node_loc(node)
|
214
|
+
comments = node_comments(node)
|
215
|
+
Const.new(name, value, loc: loc, comments: comments)
|
216
|
+
end
|
158
217
|
end
|
159
218
|
|
160
|
-
sig { params(node: AST::Node).
|
161
|
-
def
|
162
|
-
name = visit_name(node)
|
163
|
-
value = visit_expr(node.children[2])
|
219
|
+
sig { params(node: AST::Node).returns(Method) }
|
220
|
+
def parse_def(node)
|
164
221
|
loc = node_loc(node)
|
165
|
-
comments = node_comments(node)
|
166
222
|
|
167
|
-
|
168
|
-
end
|
169
|
-
|
170
|
-
sig { params(node: AST::Node).void }
|
171
|
-
def visit_def(node)
|
172
|
-
current_scope << case node.type
|
223
|
+
case node.type
|
173
224
|
when :def
|
174
225
|
Method.new(
|
175
226
|
node.children[0].to_s,
|
176
|
-
params: node.children[1].children.map { |child|
|
227
|
+
params: node.children[1].children.map { |child| parse_param(child) },
|
177
228
|
sigs: current_sigs,
|
178
|
-
loc:
|
229
|
+
loc: loc,
|
179
230
|
comments: node_comments(node)
|
180
231
|
)
|
181
232
|
when :defs
|
182
233
|
Method.new(
|
183
234
|
node.children[1].to_s,
|
184
|
-
params: node.children[2].children.map { |child|
|
235
|
+
params: node.children[2].children.map { |child| parse_param(child) },
|
185
236
|
is_singleton: true,
|
186
237
|
sigs: current_sigs,
|
187
|
-
loc:
|
238
|
+
loc: loc,
|
188
239
|
comments: node_comments(node)
|
189
240
|
)
|
190
241
|
else
|
191
|
-
raise "Unsupported node
|
242
|
+
raise ParseError.new("Unsupported def node type `#{node.type}`", loc)
|
192
243
|
end
|
193
244
|
end
|
194
245
|
|
195
246
|
sig { params(node: AST::Node).returns(Param) }
|
196
|
-
def
|
247
|
+
def parse_param(node)
|
197
248
|
name = node.children[0].to_s
|
198
249
|
loc = node_loc(node)
|
199
250
|
comments = node_comments(node)
|
200
251
|
|
201
252
|
case node.type
|
202
253
|
when :arg
|
203
|
-
|
254
|
+
ReqParam.new(name, loc: loc, comments: comments)
|
204
255
|
when :optarg
|
205
|
-
value =
|
256
|
+
value = parse_expr(node.children[1])
|
206
257
|
OptParam.new(name, value, loc: loc, comments: comments)
|
207
258
|
when :restarg
|
208
259
|
RestParam.new(name, loc: loc, comments: comments)
|
209
260
|
when :kwarg
|
210
261
|
KwParam.new(name, loc: loc, comments: comments)
|
211
262
|
when :kwoptarg
|
212
|
-
value =
|
263
|
+
value = parse_expr(node.children[1])
|
213
264
|
KwOptParam.new(name, value, loc: loc, comments: comments)
|
214
265
|
when :kwrestarg
|
215
266
|
KwRestParam.new(name, loc: loc, comments: comments)
|
216
267
|
when :blockarg
|
217
268
|
BlockParam.new(name, loc: loc, comments: comments)
|
218
269
|
else
|
219
|
-
raise "Unsupported node
|
270
|
+
raise ParseError.new("Unsupported param node type `#{node.type}`", loc)
|
220
271
|
end
|
221
272
|
end
|
222
273
|
|
223
|
-
sig { params(node: AST::Node).
|
224
|
-
def
|
274
|
+
sig { params(node: AST::Node).returns(T.nilable(RBI::Node)) }
|
275
|
+
def parse_send(node)
|
225
276
|
recv = node.children[0]
|
226
|
-
return if recv && recv != :self
|
277
|
+
return nil if recv && recv != :self
|
227
278
|
|
228
279
|
method_name = node.children[1]
|
229
280
|
loc = node_loc(node)
|
230
281
|
comments = node_comments(node)
|
231
282
|
|
232
|
-
|
283
|
+
case method_name
|
233
284
|
when :attr_reader
|
234
285
|
symbols = node.children[2..-1].map { |child| child.children[0] }
|
235
286
|
AttrReader.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
|
@@ -240,53 +291,114 @@ module RBI
|
|
240
291
|
symbols = node.children[2..-1].map { |child| child.children[0] }
|
241
292
|
AttrAccessor.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
|
242
293
|
when :include
|
243
|
-
names = node.children[2..-1].map { |child|
|
294
|
+
names = node.children[2..-1].map { |child| parse_name(child) }
|
244
295
|
Include.new(*names, loc: loc, comments: comments)
|
245
296
|
when :extend
|
246
|
-
names = node.children[2..-1].map { |child|
|
297
|
+
names = node.children[2..-1].map { |child| parse_name(child) }
|
247
298
|
Extend.new(*names, loc: loc, comments: comments)
|
248
299
|
when :abstract!, :sealed!, :interface!
|
249
300
|
Helper.new(method_name.to_s.delete_suffix("!"), loc: loc, comments: comments)
|
250
301
|
when :mixes_in_class_methods
|
251
|
-
names = node.children[2..-1].map { |child|
|
302
|
+
names = node.children[2..-1].map { |child| parse_name(child) }
|
252
303
|
MixesInClassMethods.new(*names, loc: loc, comments: comments)
|
253
304
|
when :public, :protected, :private
|
254
|
-
Visibility.new(method_name, loc: loc)
|
305
|
+
visibility = Visibility.new(method_name, loc: loc)
|
306
|
+
nested_node = node.children[2]
|
307
|
+
case nested_node&.type
|
308
|
+
when :def, :defs
|
309
|
+
method = parse_def(nested_node)
|
310
|
+
method.visibility = visibility
|
311
|
+
method
|
312
|
+
when :send
|
313
|
+
snode = parse_send(nested_node)
|
314
|
+
raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc) unless snode.is_a?(Attr)
|
315
|
+
snode.visibility = visibility
|
316
|
+
snode
|
317
|
+
when nil
|
318
|
+
visibility
|
319
|
+
else
|
320
|
+
raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc)
|
321
|
+
end
|
255
322
|
when :prop
|
256
|
-
name, type, default_value =
|
323
|
+
name, type, default_value = parse_tstruct_prop(node)
|
257
324
|
TStructProp.new(name, type, default: default_value, loc: loc, comments: comments)
|
258
325
|
when :const
|
259
|
-
name, type, default_value =
|
326
|
+
name, type, default_value = parse_tstruct_prop(node)
|
260
327
|
TStructConst.new(name, type, default: default_value, loc: loc, comments: comments)
|
261
328
|
else
|
262
|
-
raise "Unsupported
|
329
|
+
raise ParseError.new("Unsupported send node with name `#{method_name}`", loc)
|
263
330
|
end
|
264
331
|
end
|
265
332
|
|
266
|
-
sig { params(node: AST::Node).
|
267
|
-
def
|
333
|
+
sig { params(node: AST::Node).returns(T.nilable(RBI::Node)) }
|
334
|
+
def parse_block(node)
|
268
335
|
name = node.children[0].children[1]
|
269
336
|
|
270
337
|
case name
|
271
338
|
when :sig
|
272
|
-
|
339
|
+
parse_sig(node)
|
273
340
|
when :enums
|
274
|
-
|
341
|
+
parse_enum(node)
|
275
342
|
else
|
276
|
-
raise "Unsupported node
|
343
|
+
raise ParseError.new("Unsupported block node type `#{name}`", node_loc(node))
|
277
344
|
end
|
278
345
|
end
|
279
346
|
|
347
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
348
|
+
def struct_definition?(node)
|
349
|
+
(node.type == :send && node.children[0]&.type == :const && node.children[0].children[1] == :Struct) ||
|
350
|
+
(node.type == :block && struct_definition?(node.children[0]))
|
351
|
+
end
|
352
|
+
|
353
|
+
sig { params(node: AST::Node).returns(RBI::Struct) }
|
354
|
+
def parse_struct(node)
|
355
|
+
name = parse_name(node)
|
356
|
+
loc = node_loc(node)
|
357
|
+
comments = node_comments(node)
|
358
|
+
|
359
|
+
send = node.children[2]
|
360
|
+
body = []
|
361
|
+
|
362
|
+
if send.type == :block
|
363
|
+
if send.children[2].type == :begin
|
364
|
+
body = send.children[2].children
|
365
|
+
else
|
366
|
+
body << send.children[2]
|
367
|
+
end
|
368
|
+
send = send.children[0]
|
369
|
+
end
|
370
|
+
|
371
|
+
members = []
|
372
|
+
keyword_init = T.let(false, T::Boolean)
|
373
|
+
send.children[2..].each do |child|
|
374
|
+
if child.type == :sym
|
375
|
+
members << child.children[0]
|
376
|
+
elsif child.type == :kwargs
|
377
|
+
pair = child.children[0]
|
378
|
+
if pair.children[0].children[0] == :keyword_init
|
379
|
+
keyword_init = true if pair.children[1].type == :true
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
struct = Struct.new(name, members: members, keyword_init: keyword_init, loc: loc, comments: comments)
|
385
|
+
@scopes_stack << struct
|
386
|
+
visit_all(body)
|
387
|
+
@scopes_stack.pop
|
388
|
+
|
389
|
+
struct
|
390
|
+
end
|
391
|
+
|
280
392
|
sig { params(node: AST::Node).returns([String, String, T.nilable(String)]) }
|
281
|
-
def
|
393
|
+
def parse_tstruct_prop(node)
|
282
394
|
name = node.children[2].children[0].to_s
|
283
|
-
type =
|
395
|
+
type = parse_expr(node.children[3])
|
284
396
|
has_default = node.children[4]
|
285
397
|
&.children&.fetch(0, nil)
|
286
398
|
&.children&.fetch(0, nil)
|
287
399
|
&.children&.fetch(0, nil) == :default
|
288
400
|
default_value = if has_default
|
289
|
-
|
401
|
+
parse_expr(node.children.fetch(4, nil)
|
290
402
|
&.children&.fetch(0, nil)
|
291
403
|
&.children&.fetch(1, nil))
|
292
404
|
end
|
@@ -294,17 +406,17 @@ module RBI
|
|
294
406
|
end
|
295
407
|
|
296
408
|
sig { params(node: AST::Node).returns(Sig) }
|
297
|
-
def
|
409
|
+
def parse_sig(node)
|
298
410
|
sig = SigBuilder.build(node)
|
299
411
|
sig.loc = node_loc(node)
|
300
412
|
sig
|
301
413
|
end
|
302
414
|
|
303
415
|
sig { params(node: AST::Node).returns(TEnumBlock) }
|
304
|
-
def
|
416
|
+
def parse_enum(node)
|
305
417
|
enum = TEnumBlock.new
|
306
418
|
node.children[2].children.each do |child|
|
307
|
-
enum <<
|
419
|
+
enum << parse_name(child)
|
308
420
|
end
|
309
421
|
enum.loc = node_loc(node)
|
310
422
|
enum
|
@@ -312,28 +424,16 @@ module RBI
|
|
312
424
|
|
313
425
|
sig { params(node: AST::Node).returns(Loc) }
|
314
426
|
def node_loc(node)
|
315
|
-
|
316
|
-
end
|
317
|
-
|
318
|
-
sig { params(ast_loc: ::Parser::Source::Map).returns(Loc) }
|
319
|
-
def ast_to_rbi_loc(ast_loc)
|
320
|
-
Loc.new(
|
321
|
-
file: @file,
|
322
|
-
begin_line: ast_loc.line,
|
323
|
-
begin_column: ast_loc.column,
|
324
|
-
end_line: ast_loc.last_line,
|
325
|
-
end_column: ast_loc.last_column
|
326
|
-
)
|
427
|
+
Loc.from_ast_loc(@file, node.location)
|
327
428
|
end
|
328
429
|
|
329
430
|
sig { params(node: AST::Node).returns(T::Array[Comment]) }
|
330
431
|
def node_comments(node)
|
331
|
-
return [] unless @comments
|
332
432
|
comments = @comments[node.location]
|
333
433
|
return [] unless comments
|
334
434
|
comments.map do |comment|
|
335
435
|
text = comment.text[1..-1].strip
|
336
|
-
loc =
|
436
|
+
loc = Loc.from_ast_loc(@file, comment.location)
|
337
437
|
Comment.new(text, loc: loc)
|
338
438
|
end
|
339
439
|
end
|
@@ -434,11 +534,11 @@ module RBI
|
|
434
534
|
when :params
|
435
535
|
node.children[2].children.each do |child|
|
436
536
|
name = child.children[0].children[0].to_s
|
437
|
-
type =
|
537
|
+
type = parse_expr(child.children[1])
|
438
538
|
@current << SigParam.new(name, type)
|
439
539
|
end
|
440
540
|
when :returns
|
441
|
-
@current.return_type =
|
541
|
+
@current.return_type = parse_expr(node.children[2])
|
442
542
|
when :void
|
443
543
|
@current.return_type = nil
|
444
544
|
else
|
@@ -446,4 +546,17 @@ module RBI
|
|
446
546
|
end
|
447
547
|
end
|
448
548
|
end
|
549
|
+
|
550
|
+
class Loc
|
551
|
+
sig { params(file: String, ast_loc: T.any(::Parser::Source::Map, ::Parser::Source::Range)).returns(Loc) }
|
552
|
+
def self.from_ast_loc(file, ast_loc)
|
553
|
+
Loc.new(
|
554
|
+
file: file,
|
555
|
+
begin_line: ast_loc.line,
|
556
|
+
begin_column: ast_loc.column,
|
557
|
+
end_line: ast_loc.last_line,
|
558
|
+
end_column: ast_loc.last_column
|
559
|
+
)
|
560
|
+
end
|
561
|
+
end
|
449
562
|
end
|