rbi 0.0.1 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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