rbi 0.0.1 → 0.0.5

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.
data/lib/rbi/parser.rb ADDED
@@ -0,0 +1,568 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "parser"
5
+
6
+ module RBI
7
+ class ParseError < StandardError
8
+ extend T::Sig
9
+
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
22
+
23
+ # opt-in to most recent AST format
24
+ ::Parser::Builders::Default.emit_lambda = true
25
+ ::Parser::Builders::Default.emit_procarg0 = true
26
+ ::Parser::Builders::Default.emit_encoding = true
27
+ ::Parser::Builders::Default.emit_index = true
28
+ ::Parser::Builders::Default.emit_arg_inside_procarg0 = true
29
+
30
+ sig { void }
31
+ def initialize
32
+ # Delay load unparser and only if it has not been loaded already.
33
+ require "unparser" unless defined?(::Unparser)
34
+ end
35
+
36
+ sig { params(string: String).returns(Tree) }
37
+ def self.parse_string(string)
38
+ Parser.new.parse_string(string)
39
+ end
40
+
41
+ sig { params(path: String).returns(Tree) }
42
+ def self.parse_file(path)
43
+ Parser.new.parse_file(path)
44
+ end
45
+
46
+ sig { params(string: String).returns(Tree) }
47
+ def parse_string(string)
48
+ parse(string, file: "-")
49
+ end
50
+
51
+ sig { params(path: String).returns(Tree) }
52
+ def parse_file(path)
53
+ parse(::File.read(path), file: path)
54
+ end
55
+
56
+ private
57
+
58
+ sig { params(content: String, file: String).returns(Tree) }
59
+ def parse(content, file:)
60
+ node, comments = Unparser.parse_with_comments(content)
61
+ assoc = ::Parser::Source::Comment.associate_locations(node, comments)
62
+ builder = TreeBuilder.new(file: file, comments: assoc)
63
+ builder.separate_header_comments
64
+ builder.visit(node)
65
+ builder.assoc_dangling_comments(comments)
66
+ builder.tree
67
+ rescue ::Parser::SyntaxError => e
68
+ raise ParseError.new(e.message, Loc.from_ast_loc(file, e.diagnostic.location))
69
+ end
70
+ end
71
+
72
+ class ASTVisitor
73
+ extend T::Helpers
74
+ extend T::Sig
75
+
76
+ abstract!
77
+
78
+ sig { params(nodes: T::Array[AST::Node]).void }
79
+ def visit_all(nodes)
80
+ nodes.each { |node| visit(node) }
81
+ end
82
+
83
+ sig { abstract.params(node: T.nilable(AST::Node)).void }
84
+ def visit(node); end
85
+
86
+ private
87
+
88
+ sig { params(node: AST::Node).returns(String) }
89
+ def parse_name(node)
90
+ T.must(ConstBuilder.visit(node))
91
+ end
92
+
93
+ sig { params(node: AST::Node).returns(String) }
94
+ def parse_expr(node)
95
+ Unparser.unparse(node)
96
+ end
97
+ end
98
+
99
+ class TreeBuilder < ASTVisitor
100
+ extend T::Sig
101
+
102
+ sig { returns(Tree) }
103
+ attr_reader :tree
104
+
105
+ sig do
106
+ params(
107
+ file: String,
108
+ comments: T::Hash[::Parser::Source::Map, T::Array[::Parser::Source::Comment]]
109
+ ).void
110
+ end
111
+ def initialize(file:, comments: {})
112
+ super()
113
+ @file = file
114
+ @comments = comments
115
+ @tree = T.let(Tree.new, Tree)
116
+ @scopes_stack = T.let([@tree], T::Array[Tree])
117
+ @last_sigs = T.let([], T::Array[RBI::Sig])
118
+ end
119
+
120
+ sig { override.params(node: T.nilable(Object)).void }
121
+ def visit(node)
122
+ return unless node.is_a?(AST::Node)
123
+ case node.type
124
+ when :module, :class, :sclass
125
+ scope = parse_scope(node)
126
+ current_scope << scope
127
+ @scopes_stack << scope
128
+ visit_all(node.children)
129
+ @scopes_stack.pop
130
+ when :casgn
131
+ current_scope << parse_const_assign(node)
132
+ when :def, :defs
133
+ current_scope << parse_def(node)
134
+ when :send
135
+ node = parse_send(node)
136
+ current_scope << node if node
137
+ when :block
138
+ node = parse_block(node)
139
+ if node.is_a?(Sig)
140
+ @last_sigs << node
141
+ elsif node
142
+ current_scope << node
143
+ end
144
+ else
145
+ visit_all(node.children)
146
+ end
147
+ end
148
+
149
+ sig { void }
150
+ def separate_header_comments
151
+ return if @comments.empty?
152
+
153
+ keep = []
154
+ node = T.must(@comments.keys.first)
155
+ comments = T.must(@comments.values.first)
156
+
157
+ last_line = T.let(nil, T.nilable(Integer))
158
+ comments.reverse.each do |comment|
159
+ comment_line = comment.location.last_line
160
+
161
+ break if last_line && comment_line < last_line - 1 ||
162
+ !last_line && comment_line < node.first_line - 1
163
+
164
+ keep << comment
165
+ last_line = comment_line
166
+ end
167
+
168
+ @comments[node] = keep.reverse
169
+ end
170
+
171
+ sig { params(comments: T::Array[::Parser::Source::Comment]).void }
172
+ def assoc_dangling_comments(comments)
173
+ last_line = T.let(nil, T.nilable(Integer))
174
+ (comments - @comments.values.flatten).each do |comment|
175
+ comment_line = comment.location.last_line
176
+ text = comment.text[1..-1].strip
177
+ loc = Loc.from_ast_loc(@file, comment.location)
178
+
179
+ if last_line && comment_line > last_line + 1
180
+ # Preserve empty lines in file headers
181
+ tree.comments << EmptyComment.new(loc: loc)
182
+ end
183
+
184
+ tree.comments << Comment.new(text, loc: loc)
185
+ last_line = comment_line
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ sig { params(node: AST::Node).returns(Scope) }
192
+ def parse_scope(node)
193
+ loc = node_loc(node)
194
+ comments = node_comments(node)
195
+
196
+ case node.type
197
+ when :module
198
+ name = parse_name(node.children[0])
199
+ Module.new(name, loc: loc, comments: comments)
200
+ when :class
201
+ name = parse_name(node.children[0])
202
+ superclass_name = ConstBuilder.visit(node.children[1])
203
+ Class.new(name, superclass_name: superclass_name, loc: loc, comments: comments)
204
+ when :sclass
205
+ SingletonClass.new(loc: loc, comments: comments)
206
+ else
207
+ raise ParseError.new("Unsupported scope node type `#{node.type}`", loc)
208
+ end
209
+ end
210
+
211
+ sig { params(node: AST::Node).returns(RBI::Node) }
212
+ def parse_const_assign(node)
213
+ node_value = node.children[2]
214
+ if struct_definition?(node_value)
215
+ parse_struct(node)
216
+ else
217
+ name = parse_name(node)
218
+ value = parse_expr(node_value)
219
+ loc = node_loc(node)
220
+ comments = node_comments(node)
221
+ Const.new(name, value, loc: loc, comments: comments)
222
+ end
223
+ end
224
+
225
+ sig { params(node: AST::Node).returns(Method) }
226
+ def parse_def(node)
227
+ loc = node_loc(node)
228
+
229
+ case node.type
230
+ when :def
231
+ Method.new(
232
+ node.children[0].to_s,
233
+ params: node.children[1].children.map { |child| parse_param(child) },
234
+ sigs: current_sigs,
235
+ loc: loc,
236
+ comments: node_comments(node)
237
+ )
238
+ when :defs
239
+ Method.new(
240
+ node.children[1].to_s,
241
+ params: node.children[2].children.map { |child| parse_param(child) },
242
+ is_singleton: true,
243
+ sigs: current_sigs,
244
+ loc: loc,
245
+ comments: node_comments(node)
246
+ )
247
+ else
248
+ raise ParseError.new("Unsupported def node type `#{node.type}`", loc)
249
+ end
250
+ end
251
+
252
+ sig { params(node: AST::Node).returns(Param) }
253
+ def parse_param(node)
254
+ name = node.children[0].to_s
255
+ loc = node_loc(node)
256
+ comments = node_comments(node)
257
+
258
+ case node.type
259
+ when :arg
260
+ ReqParam.new(name, loc: loc, comments: comments)
261
+ when :optarg
262
+ value = parse_expr(node.children[1])
263
+ OptParam.new(name, value, loc: loc, comments: comments)
264
+ when :restarg
265
+ RestParam.new(name, loc: loc, comments: comments)
266
+ when :kwarg
267
+ KwParam.new(name, loc: loc, comments: comments)
268
+ when :kwoptarg
269
+ value = parse_expr(node.children[1])
270
+ KwOptParam.new(name, value, loc: loc, comments: comments)
271
+ when :kwrestarg
272
+ KwRestParam.new(name, loc: loc, comments: comments)
273
+ when :blockarg
274
+ BlockParam.new(name, loc: loc, comments: comments)
275
+ else
276
+ raise ParseError.new("Unsupported param node type `#{node.type}`", loc)
277
+ end
278
+ end
279
+
280
+ sig { params(node: AST::Node).returns(T.nilable(RBI::Node)) }
281
+ def parse_send(node)
282
+ recv = node.children[0]
283
+ return nil if recv && recv != :self
284
+
285
+ method_name = node.children[1]
286
+ loc = node_loc(node)
287
+ comments = node_comments(node)
288
+
289
+ case method_name
290
+ when :attr_reader
291
+ symbols = node.children[2..-1].map { |child| child.children[0] }
292
+ AttrReader.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
293
+ when :attr_writer
294
+ symbols = node.children[2..-1].map { |child| child.children[0] }
295
+ AttrWriter.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
296
+ when :attr_accessor
297
+ symbols = node.children[2..-1].map { |child| child.children[0] }
298
+ AttrAccessor.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
299
+ when :include
300
+ names = node.children[2..-1].map { |child| parse_name(child) }
301
+ Include.new(*names, loc: loc, comments: comments)
302
+ when :extend
303
+ names = node.children[2..-1].map { |child| parse_name(child) }
304
+ Extend.new(*names, loc: loc, comments: comments)
305
+ when :abstract!, :sealed!, :interface!
306
+ Helper.new(method_name.to_s.delete_suffix("!"), loc: loc, comments: comments)
307
+ when :mixes_in_class_methods
308
+ names = node.children[2..-1].map { |child| parse_name(child) }
309
+ MixesInClassMethods.new(*names, loc: loc, comments: comments)
310
+ when :public, :protected, :private
311
+ visibility = Visibility.new(method_name, loc: loc)
312
+ nested_node = node.children[2]
313
+ case nested_node&.type
314
+ when :def, :defs
315
+ method = parse_def(nested_node)
316
+ method.visibility = visibility
317
+ method
318
+ when :send
319
+ snode = parse_send(nested_node)
320
+ raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc) unless snode.is_a?(Attr)
321
+ snode.visibility = visibility
322
+ snode
323
+ when nil
324
+ visibility
325
+ else
326
+ raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc)
327
+ end
328
+ when :prop
329
+ name, type, default_value = parse_tstruct_prop(node)
330
+ TStructProp.new(name, type, default: default_value, loc: loc, comments: comments)
331
+ when :const
332
+ name, type, default_value = parse_tstruct_prop(node)
333
+ TStructConst.new(name, type, default: default_value, loc: loc, comments: comments)
334
+ else
335
+ raise ParseError.new("Unsupported send node with name `#{method_name}`", loc)
336
+ end
337
+ end
338
+
339
+ sig { params(node: AST::Node).returns(T.nilable(RBI::Node)) }
340
+ def parse_block(node)
341
+ name = node.children[0].children[1]
342
+
343
+ case name
344
+ when :sig
345
+ parse_sig(node)
346
+ when :enums
347
+ parse_enum(node)
348
+ else
349
+ raise ParseError.new("Unsupported block node type `#{name}`", node_loc(node))
350
+ end
351
+ end
352
+
353
+ sig { params(node: AST::Node).returns(T::Boolean) }
354
+ def struct_definition?(node)
355
+ (node.type == :send && node.children[0]&.type == :const && node.children[0].children[1] == :Struct) ||
356
+ (node.type == :block && struct_definition?(node.children[0]))
357
+ end
358
+
359
+ sig { params(node: AST::Node).returns(RBI::Struct) }
360
+ def parse_struct(node)
361
+ name = parse_name(node)
362
+ loc = node_loc(node)
363
+ comments = node_comments(node)
364
+
365
+ send = node.children[2]
366
+ body = []
367
+
368
+ if send.type == :block
369
+ if send.children[2].type == :begin
370
+ body = send.children[2].children
371
+ else
372
+ body << send.children[2]
373
+ end
374
+ send = send.children[0]
375
+ end
376
+
377
+ members = []
378
+ keyword_init = T.let(false, T::Boolean)
379
+ send.children[2..].each do |child|
380
+ if child.type == :sym
381
+ members << child.children[0]
382
+ elsif child.type == :kwargs
383
+ pair = child.children[0]
384
+ if pair.children[0].children[0] == :keyword_init
385
+ keyword_init = true if pair.children[1].type == :true
386
+ end
387
+ end
388
+ end
389
+
390
+ struct = Struct.new(name, members: members, keyword_init: keyword_init, loc: loc, comments: comments)
391
+ @scopes_stack << struct
392
+ visit_all(body)
393
+ @scopes_stack.pop
394
+
395
+ struct
396
+ end
397
+
398
+ sig { params(node: AST::Node).returns([String, String, T.nilable(String)]) }
399
+ def parse_tstruct_prop(node)
400
+ name = node.children[2].children[0].to_s
401
+ type = parse_expr(node.children[3])
402
+ has_default = node.children[4]
403
+ &.children&.fetch(0, nil)
404
+ &.children&.fetch(0, nil)
405
+ &.children&.fetch(0, nil) == :default
406
+ default_value = if has_default
407
+ parse_expr(node.children.fetch(4, nil)
408
+ &.children&.fetch(0, nil)
409
+ &.children&.fetch(1, nil))
410
+ end
411
+ [name, type, default_value]
412
+ end
413
+
414
+ sig { params(node: AST::Node).returns(Sig) }
415
+ def parse_sig(node)
416
+ sig = SigBuilder.build(node)
417
+ sig.loc = node_loc(node)
418
+ sig
419
+ end
420
+
421
+ sig { params(node: AST::Node).returns(TEnumBlock) }
422
+ def parse_enum(node)
423
+ enum = TEnumBlock.new
424
+ node.children[2].children.each do |child|
425
+ enum << parse_name(child)
426
+ end
427
+ enum.loc = node_loc(node)
428
+ enum
429
+ end
430
+
431
+ sig { params(node: AST::Node).returns(Loc) }
432
+ def node_loc(node)
433
+ Loc.from_ast_loc(@file, node.location)
434
+ end
435
+
436
+ sig { params(node: AST::Node).returns(T::Array[Comment]) }
437
+ def node_comments(node)
438
+ comments = @comments[node.location]
439
+ return [] unless comments
440
+ comments.map do |comment|
441
+ text = comment.text[1..-1].strip
442
+ loc = Loc.from_ast_loc(@file, comment.location)
443
+ Comment.new(text, loc: loc)
444
+ end
445
+ end
446
+
447
+ sig { returns(Tree) }
448
+ def current_scope
449
+ T.must(@scopes_stack.last) # Should never be nil since we create a Tree as the root
450
+ end
451
+
452
+ sig { returns(T::Array[Sig]) }
453
+ def current_sigs
454
+ sigs = @last_sigs.dup
455
+ @last_sigs.clear
456
+ sigs
457
+ end
458
+ end
459
+
460
+ class ConstBuilder < ASTVisitor
461
+ extend T::Sig
462
+
463
+ sig { params(node: T.nilable(AST::Node)).returns(T.nilable(String)) }
464
+ def self.visit(node)
465
+ v = ConstBuilder.new
466
+ v.visit(node)
467
+ return nil if v.names.empty?
468
+ v.names.join("::")
469
+ end
470
+
471
+ sig { returns(T::Array[String]) }
472
+ attr_accessor :names
473
+
474
+ sig { void }
475
+ def initialize
476
+ super
477
+ @names = T.let([], T::Array[String])
478
+ end
479
+
480
+ sig { override.params(node: T.nilable(AST::Node)).void }
481
+ def visit(node)
482
+ return unless node
483
+ case node.type
484
+ when :const, :casgn
485
+ visit(node.children[0])
486
+ @names << node.children[1].to_s
487
+ when :cbase
488
+ @names << ""
489
+ when :sym
490
+ @names << ":#{node.children[0]}"
491
+ end
492
+ end
493
+ end
494
+
495
+ class SigBuilder < ASTVisitor
496
+ extend T::Sig
497
+
498
+ sig { params(node: AST::Node).returns(Sig) }
499
+ def self.build(node)
500
+ v = SigBuilder.new
501
+ v.visit_all(node.children[2..-1])
502
+ v.current
503
+ end
504
+
505
+ sig { returns(Sig) }
506
+ attr_accessor :current
507
+
508
+ sig { void }
509
+ def initialize
510
+ super
511
+ @current = T.let(Sig.new, Sig)
512
+ end
513
+
514
+ sig { override.params(node: T.nilable(AST::Node)).void }
515
+ def visit(node)
516
+ return unless node
517
+ case node.type
518
+ when :send
519
+ visit_send(node)
520
+ end
521
+ end
522
+
523
+ sig { params(node: AST::Node).void }
524
+ def visit_send(node)
525
+ visit(node.children[0]) if node.children[0]
526
+ name = node.children[1]
527
+ case name
528
+ when :abstract
529
+ @current.is_abstract = true
530
+ when :override
531
+ @current.is_override = true
532
+ when :overridable
533
+ @current.is_overridable = true
534
+ when :checked
535
+ @current.checked = node.children[2].children[0]
536
+ when :type_parameters
537
+ node.children[2..-1].each do |child|
538
+ @current.type_params << child.children[0].to_s
539
+ end
540
+ when :params
541
+ node.children[2].children.each do |child|
542
+ name = child.children[0].children[0].to_s
543
+ type = parse_expr(child.children[1])
544
+ @current << SigParam.new(name, type)
545
+ end
546
+ when :returns
547
+ @current.return_type = parse_expr(node.children[2])
548
+ when :void
549
+ @current.return_type = nil
550
+ else
551
+ raise "#{node.location.line}: Unhandled #{name}"
552
+ end
553
+ end
554
+ end
555
+
556
+ class Loc
557
+ sig { params(file: String, ast_loc: T.any(::Parser::Source::Map, ::Parser::Source::Range)).returns(Loc) }
558
+ def self.from_ast_loc(file, ast_loc)
559
+ Loc.new(
560
+ file: file,
561
+ begin_line: ast_loc.line,
562
+ begin_column: ast_loc.column,
563
+ end_line: ast_loc.last_line,
564
+ end_column: ast_loc.last_column
565
+ )
566
+ end
567
+ end
568
+ end