rbi 0.0.17 → 0.1.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.
data/lib/rbi/parser.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parser"
4
+ require "yarp"
5
5
 
6
6
  module RBI
7
7
  class ParseError < StandardError
@@ -53,19 +53,6 @@ module RBI
53
53
  class Parser
54
54
  extend T::Sig
55
55
 
56
- # opt-in to most recent AST format
57
- ::Parser::Builders::Default.emit_lambda = true
58
- ::Parser::Builders::Default.emit_procarg0 = true
59
- ::Parser::Builders::Default.emit_encoding = true
60
- ::Parser::Builders::Default.emit_index = true
61
- ::Parser::Builders::Default.emit_arg_inside_procarg0 = true
62
-
63
- sig { void }
64
- def initialize
65
- # Delay load unparser and only if it has not been loaded already.
66
- require "unparser" unless defined?(::Unparser)
67
- end
68
-
69
56
  class << self
70
57
  extend T::Sig
71
58
 
@@ -104,22 +91,27 @@ module RBI
104
91
 
105
92
  private
106
93
 
107
- sig { params(content: String, file: String).returns(Tree) }
108
- def parse(content, file:)
109
- node, comments = Unparser.parse_with_comments(content)
110
- assoc = ::Parser::Source::Comment.associate_locations(node, comments)
111
- builder = TreeBuilder.new(file: file, comments: comments, nodes_comments_assoc: assoc)
112
- builder.visit(node)
113
- builder.post_process
114
- builder.tree
115
- rescue ::Parser::SyntaxError => e
116
- raise ParseError.new(e.message, Loc.from_ast_loc(file, e.diagnostic.location))
94
+ sig { params(source: String, file: String).returns(Tree) }
95
+ def parse(source, file:)
96
+ result = YARP.parse(source)
97
+ unless result.success?
98
+ message = result.errors.map { |e| "#{e.message}." }.join(" ")
99
+ error = result.errors.first
100
+ location = Loc.new(file: file, begin_line: error.location.start_line, begin_column: error.location.start_column)
101
+ raise ParseError.new(message, location)
102
+ end
103
+
104
+ visitor = TreeBuilder.new(source, comments: result.comments, file: file)
105
+ visitor.visit(result.value)
106
+ visitor.tree
107
+ rescue YARP::ParseError => e
108
+ raise ParseError.new(e.message, Loc.from_yarp(file, e.location))
117
109
  rescue ParseError => e
118
110
  raise e
119
111
  rescue => e
120
- last_node = builder&.last_node
112
+ last_node = visitor&.last_node
121
113
  last_location = if last_node
122
- Loc.from_ast_loc(file, last_node.location)
114
+ Loc.from_yarp(file, last_node.location)
123
115
  else
124
116
  Loc.new(file: file)
125
117
  end
@@ -128,630 +120,692 @@ module RBI
128
120
  exception.print_debug
129
121
  raise exception
130
122
  end
131
- end
132
123
 
133
- class ASTVisitor
134
- extend T::Helpers
135
- extend T::Sig
124
+ class Visitor < YARP::Visitor
125
+ extend T::Sig
136
126
 
137
- abstract!
127
+ sig { params(source: String, file: String).void }
128
+ def initialize(source, file:)
129
+ super()
138
130
 
139
- sig { params(nodes: T::Array[AST::Node]).void }
140
- def visit_all(nodes)
141
- nodes.each { |node| visit(node) }
142
- end
131
+ @source = source
132
+ @file = file
133
+ end
143
134
 
144
- sig { abstract.params(node: T.nilable(AST::Node)).void }
145
- def visit(node); end
135
+ private
146
136
 
147
- private
137
+ sig { params(node: YARP::Node).returns(Loc) }
138
+ def node_loc(node)
139
+ Loc.from_yarp(@file, node.location)
140
+ end
148
141
 
149
- sig { params(node: AST::Node).returns(String) }
150
- def parse_name(node)
151
- T.must(ConstBuilder.visit(node))
152
- end
142
+ sig { params(node: T.nilable(YARP::Node)).returns(T.nilable(String)) }
143
+ def node_string(node)
144
+ return unless node
153
145
 
154
- sig { params(node: AST::Node).returns(String) }
155
- def parse_expr(node)
156
- Unparser.unparse(node)
146
+ node.slice
147
+ end
148
+
149
+ sig { params(node: YARP::Node).returns(String) }
150
+ def node_string!(node)
151
+ T.must(node_string(node))
152
+ end
157
153
  end
158
- end
159
154
 
160
- class TreeBuilder < ASTVisitor
161
- extend T::Sig
155
+ class TreeBuilder < Visitor
156
+ extend T::Sig
162
157
 
163
- sig { returns(Tree) }
164
- attr_reader :tree
158
+ sig { returns(Tree) }
159
+ attr_reader :tree
165
160
 
166
- sig { returns(T.nilable(::AST::Node)) }
167
- attr_reader :last_node
161
+ sig { returns(T.nilable(YARP::Node)) }
162
+ attr_reader :last_node
168
163
 
169
- sig do
170
- params(
171
- file: String,
172
- comments: T::Array[::Parser::Source::Comment],
173
- nodes_comments_assoc: T::Hash[::Parser::Source::Map, T::Array[::Parser::Source::Comment]],
174
- ).void
175
- end
176
- def initialize(file:, comments: [], nodes_comments_assoc: {})
177
- super()
178
- @file = file
179
- @comments = comments
180
- @nodes_comments_assoc = nodes_comments_assoc
181
- @tree = T.let(Tree.new, Tree)
182
- @scopes_stack = T.let([@tree], T::Array[Tree])
183
- @last_node = T.let(nil, T.nilable(::AST::Node))
184
- @last_sigs = T.let([], T::Array[RBI::Sig])
185
- @last_sigs_comments = T.let([], T::Array[Comment])
186
-
187
- separate_header_comments
188
- end
164
+ sig { params(source: String, comments: T::Array[YARP::Comment], file: String).void }
165
+ def initialize(source, comments:, file:)
166
+ super(source, file: file)
189
167
 
190
- sig { void }
191
- def post_process
192
- assoc_dangling_comments
193
- set_root_tree_loc
194
- end
168
+ @comments_by_line = T.let(comments.to_h { |c| [c.location.start_line, c] }, T::Hash[Integer, YARP::Comment])
169
+ @tree = T.let(Tree.new, Tree)
170
+
171
+ @scopes_stack = T.let([@tree], T::Array[Tree])
172
+ @last_node = T.let(nil, T.nilable(YARP::Node))
173
+ @last_sigs = T.let([], T::Array[RBI::Sig])
174
+ @last_sigs_comments = T.let([], T::Array[Comment])
175
+ end
176
+
177
+ sig { override.params(node: T.nilable(YARP::Node)).void }
178
+ def visit(node)
179
+ return unless node
195
180
 
196
- sig { override.params(node: T.nilable(Object)).void }
197
- def visit(node)
198
- return unless node.is_a?(AST::Node)
181
+ @last_node = node
182
+ super
183
+ end
199
184
 
200
- @last_node = node
185
+ sig { override.params(node: YARP::ClassNode).void }
186
+ def visit_class_node(node)
187
+ scope = Class.new(
188
+ node_string!(node.constant_path),
189
+ superclass_name: node_string(node.superclass),
190
+ loc: node_loc(node),
191
+ comments: node_comments(node),
192
+ )
201
193
 
202
- case node.type
203
- when :module, :class, :sclass
204
- scope = parse_scope(node)
205
194
  current_scope << scope
206
195
  @scopes_stack << scope
207
- visit_all(node.children)
196
+ visit(node.body)
197
+ collect_dangling_comments(node)
208
198
  @scopes_stack.pop
209
- when :casgn
210
- current_scope << parse_const_assign(node)
211
- when :def, :defs
212
- current_scope << parse_def(node)
213
- when :send
214
- node = parse_send(node)
215
- current_scope << node if node
216
- when :block
217
- rbi_node = parse_block(node)
218
- if rbi_node.is_a?(Sig)
219
- @last_sigs << rbi_node
220
- @last_sigs_comments.concat(node_comments(node))
221
- elsif rbi_node
222
- current_scope << rbi_node
223
- end
224
- else
225
- visit_all(node.children)
226
199
  end
227
200
 
228
- @last_node = nil
229
- end
230
-
231
- private
232
-
233
- sig { params(node: AST::Node).returns(Scope) }
234
- def parse_scope(node)
235
- loc = node_loc(node)
236
- comments = node_comments(node)
237
-
238
- case node.type
239
- when :module
240
- name = parse_name(node.children[0])
241
- Module.new(name, loc: loc, comments: comments)
242
- when :class
243
- name = parse_name(node.children[0])
244
- superclass_name = ConstBuilder.visit(node.children[1])
245
- Class.new(name, superclass_name: superclass_name, loc: loc, comments: comments)
246
- when :sclass
247
- SingletonClass.new(loc: loc, comments: comments)
248
- else
249
- raise ParseError.new("Unsupported scope node type `#{node.type}`", loc)
201
+ sig { override.params(node: YARP::ConstantWriteNode).void }
202
+ def visit_constant_write_node(node)
203
+ visit_constant_assign(node)
250
204
  end
251
- end
252
205
 
253
- sig { params(node: AST::Node).returns(RBI::Node) }
254
- def parse_const_assign(node)
255
- node_value = node.children[2]
256
- if struct_definition?(node_value)
257
- parse_struct(node)
258
- elsif type_variable_definition?(node_value)
259
- parse_type_variable(node)
260
- else
261
- name = parse_name(node)
262
- value = parse_expr(node_value)
263
- loc = node_loc(node)
264
- comments = node_comments(node)
265
- Const.new(name, value, loc: loc, comments: comments)
206
+ sig { override.params(node: YARP::ConstantPathWriteNode).void }
207
+ def visit_constant_path_write_node(node)
208
+ visit_constant_assign(node)
266
209
  end
267
- end
268
210
 
269
- sig { params(node: AST::Node).returns(Method) }
270
- def parse_def(node)
271
- loc = node_loc(node)
211
+ sig { params(node: T.any(YARP::ConstantWriteNode, YARP::ConstantPathWriteNode)).void }
212
+ def visit_constant_assign(node)
213
+ struct = parse_struct(node)
214
+
215
+ current_scope << if struct
216
+ struct
217
+ elsif type_variable_definition?(node.value)
218
+ TypeMember.new(
219
+ case node
220
+ when YARP::ConstantWriteNode
221
+ node.name.to_s
222
+ when YARP::ConstantPathWriteNode
223
+ node_string!(node.target)
224
+ end,
225
+ node_string!(node.value),
226
+ loc: node_loc(node),
227
+ comments: node_comments(node),
228
+ )
229
+ else
230
+ Const.new(
231
+ case node
232
+ when YARP::ConstantWriteNode
233
+ node.name.to_s
234
+ when YARP::ConstantPathWriteNode
235
+ node_string!(node.target)
236
+ end,
237
+ node_string!(node.value),
238
+ loc: node_loc(node),
239
+ comments: node_comments(node),
240
+ )
241
+ end
242
+ end
272
243
 
273
- case node.type
274
- when :def
275
- Method.new(
276
- node.children[0].to_s,
277
- params: node.children[1].children.map { |child| parse_param(child) },
244
+ sig { override.params(node: YARP::DefNode).void }
245
+ def visit_def_node(node)
246
+ current_scope << Method.new(
247
+ node.name.to_s,
248
+ params: parse_params(node.parameters),
278
249
  sigs: current_sigs,
279
- loc: loc,
250
+ loc: node_loc(node),
280
251
  comments: current_sigs_comments + node_comments(node),
252
+ is_singleton: !!node.receiver,
281
253
  )
282
- when :defs
283
- Method.new(
284
- node.children[1].to_s,
285
- params: node.children[2].children.map { |child| parse_param(child) },
286
- is_singleton: true,
287
- sigs: current_sigs,
288
- loc: loc,
289
- comments: current_sigs_comments + node_comments(node),
254
+ end
255
+
256
+ sig { override.params(node: YARP::ModuleNode).void }
257
+ def visit_module_node(node)
258
+ scope = Module.new(
259
+ node_string!(node.constant_path),
260
+ loc: node_loc(node),
261
+ comments: node_comments(node),
290
262
  )
291
- else
292
- raise ParseError.new("Unsupported def node type `#{node.type}`", loc)
263
+
264
+ current_scope << scope
265
+ @scopes_stack << scope
266
+ visit(node.body)
267
+ collect_dangling_comments(node)
268
+ @scopes_stack.pop
293
269
  end
294
- end
295
270
 
296
- sig { params(node: AST::Node).returns(Param) }
297
- def parse_param(node)
298
- name = node.children[0].to_s
299
- loc = node_loc(node)
300
- comments = node_comments(node)
301
-
302
- case node.type
303
- when :arg
304
- ReqParam.new(name, loc: loc, comments: comments)
305
- when :optarg
306
- value = parse_expr(node.children[1])
307
- OptParam.new(name, value, loc: loc, comments: comments)
308
- when :restarg
309
- RestParam.new(name, loc: loc, comments: comments)
310
- when :kwarg
311
- KwParam.new(name, loc: loc, comments: comments)
312
- when :kwoptarg
313
- value = parse_expr(node.children[1])
314
- KwOptParam.new(name, value, loc: loc, comments: comments)
315
- when :kwrestarg
316
- KwRestParam.new(name, loc: loc, comments: comments)
317
- when :blockarg
318
- BlockParam.new(name, loc: loc, comments: comments)
319
- else
320
- raise ParseError.new("Unsupported param node type `#{node.type}`", loc)
271
+ sig { override.params(node: YARP::ProgramNode).void }
272
+ def visit_program_node(node)
273
+ super
274
+
275
+ collect_orphan_comments
276
+ separate_header_comments
277
+ set_root_tree_loc
321
278
  end
322
- end
323
279
 
324
- sig { params(node: AST::Node).returns(T.nilable(RBI::Node)) }
325
- def parse_send(node)
326
- recv = node.children[0]
327
- return if recv && recv != :self
328
-
329
- method_name = node.children[1]
330
- loc = node_loc(node)
331
- comments = node_comments(node)
332
-
333
- case method_name
334
- when :attr_reader
335
- symbols = node.children[2..-1].map { |child| child.children[0] }
336
- AttrReader.new(*symbols, sigs: current_sigs, loc: loc, comments: current_sigs_comments + comments)
337
- when :attr_writer
338
- symbols = node.children[2..-1].map { |child| child.children[0] }
339
- AttrWriter.new(*symbols, sigs: current_sigs, loc: loc, comments: current_sigs_comments + comments)
340
- when :attr_accessor
341
- symbols = node.children[2..-1].map { |child| child.children[0] }
342
- AttrAccessor.new(*symbols, sigs: current_sigs, loc: loc, comments: current_sigs_comments + comments)
343
- when :include
344
- names = node.children[2..-1].map { |child| parse_expr(child) }
345
- Include.new(*names, loc: loc, comments: comments)
346
- when :extend
347
- names = node.children[2..-1].map { |child| parse_expr(child) }
348
- Extend.new(*names, loc: loc, comments: comments)
349
- when :abstract!, :sealed!, :interface!
350
- Helper.new(method_name.to_s.delete_suffix("!"), loc: loc, comments: comments)
351
- when :mixes_in_class_methods
352
- names = node.children[2..-1].map { |child| parse_name(child) }
353
- MixesInClassMethods.new(*names, loc: loc, comments: comments)
354
- when :public, :protected, :private
355
- visibility = case method_name
356
- when :protected
357
- Protected.new(loc: loc)
358
- when :private
359
- Private.new(loc: loc)
360
- else
361
- Public.new(loc: loc)
362
- end
363
- nested_node = node.children[2]
364
- case nested_node&.type
365
- when :def, :defs
366
- method = parse_def(nested_node)
367
- method.visibility = visibility
368
- method
369
- when :send
370
- snode = parse_send(nested_node)
371
- raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc) unless snode.is_a?(Attr)
372
-
373
- snode.visibility = visibility
374
- snode
375
- when nil
376
- visibility
377
- else
378
- raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc)
379
- end
380
- when :prop
381
- name, type, default_value = parse_tstruct_prop(node)
382
- TStructProp.new(name, type, default: default_value, loc: loc, comments: comments)
383
- when :const
384
- name, type, default_value = parse_tstruct_prop(node)
385
- TStructConst.new(name, type, default: default_value, loc: loc, comments: comments)
386
- else
387
- args = parse_send_args(node)
388
- Send.new(method_name.to_s, args, loc: loc, comments: comments)
280
+ sig { override.params(node: YARP::SingletonClassNode).void }
281
+ def visit_singleton_class_node(node)
282
+ scope = SingletonClass.new(
283
+ loc: node_loc(node),
284
+ comments: node_comments(node),
285
+ )
286
+
287
+ current_scope << scope
288
+ @scopes_stack << scope
289
+ visit(node.body)
290
+ collect_dangling_comments(node)
291
+ @scopes_stack.pop
389
292
  end
390
- end
391
293
 
392
- sig { params(node: AST::Node).returns(T::Array[Arg]) }
393
- def parse_send_args(node)
394
- args = T.let([], T::Array[Arg])
395
- node.children[2..-1].each do |child|
396
- if child.type == :kwargs
397
- child.children.each do |pair|
398
- keyword = pair.children.first.children.last.to_s
399
- value = parse_expr(pair.children.last)
400
- args << KwArg.new(keyword, value)
294
+ sig { params(node: YARP::CallNode).void }
295
+ def visit_call_node(node)
296
+ message = node.name
297
+ case message
298
+ when "abstract!", "sealed!", "interface!"
299
+ current_scope << Helper.new(
300
+ message.delete_suffix("!"),
301
+ loc: node_loc(node),
302
+ comments: node_comments(node),
303
+ )
304
+ when "attr_reader"
305
+ args = node.arguments
306
+ return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any?
307
+
308
+ current_scope << AttrReader.new(
309
+ *T.unsafe(args.arguments.map { |arg| node_string!(arg).delete_prefix(":").to_sym }),
310
+ sigs: current_sigs,
311
+ loc: node_loc(node),
312
+ comments: current_sigs_comments + node_comments(node),
313
+ )
314
+ when "attr_writer"
315
+ args = node.arguments
316
+ return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any?
317
+
318
+ current_scope << AttrWriter.new(
319
+ *T.unsafe(args.arguments.map { |arg| node_string!(arg).delete_prefix(":").to_sym }),
320
+ sigs: current_sigs,
321
+ loc: node_loc(node),
322
+ comments: current_sigs_comments + node_comments(node),
323
+ )
324
+ when "attr_accessor"
325
+ args = node.arguments
326
+ return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any?
327
+
328
+ current_scope << AttrAccessor.new(
329
+ *T.unsafe(args.arguments.map { |arg| node_string!(arg).delete_prefix(":").to_sym }),
330
+ sigs: current_sigs,
331
+ loc: node_loc(node),
332
+ comments: current_sigs_comments + node_comments(node),
333
+ )
334
+ when "enums"
335
+ block = node.block
336
+ return unless block.is_a?(YARP::BlockNode)
337
+
338
+ body = block.body
339
+ return unless body.is_a?(YARP::StatementsNode)
340
+
341
+ current_scope << TEnumBlock.new(
342
+ body.body.map { |stmt| T.cast(stmt, YARP::ConstantWriteNode).name.to_s },
343
+ loc: node_loc(node),
344
+ comments: node_comments(node),
345
+ )
346
+ when "extend"
347
+ args = node.arguments
348
+ return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any?
349
+
350
+ current_scope << Extend.new(
351
+ *T.unsafe(args.arguments.map { |arg| node_string!(arg) }),
352
+ loc: node_loc(node),
353
+ comments: node_comments(node),
354
+ )
355
+ when "include"
356
+ args = node.arguments
357
+ return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any?
358
+
359
+ current_scope << Include.new(
360
+ *T.unsafe(args.arguments.map { |arg| node_string!(arg) }),
361
+ loc: node_loc(node),
362
+ comments: node_comments(node),
363
+ )
364
+ when "mixes_in_class_methods"
365
+ args = node.arguments
366
+ return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any?
367
+
368
+ current_scope << MixesInClassMethods.new(
369
+ *T.unsafe(args.arguments.map { |arg| node_string!(arg) }),
370
+ loc: node_loc(node),
371
+ comments: node_comments(node),
372
+ )
373
+ when "private", "protected", "public"
374
+ args = node.arguments
375
+ if args.is_a?(YARP::ArgumentsNode) && args.arguments.any?
376
+ visit(node.arguments)
377
+ last_node = @scopes_stack.last&.nodes&.last
378
+ case last_node
379
+ when Method, Attr
380
+ last_node.visibility = parse_visibility(node.name, node)
381
+ else
382
+ raise ParseError.new(
383
+ "Unexpected token `#{node.message}` before `#{last_node&.string&.strip}`",
384
+ node_loc(node),
385
+ )
386
+ end
387
+ else
388
+ current_scope << parse_visibility(node.name, node)
401
389
  end
390
+ when "prop", "const"
391
+ parse_tstruct_field(node)
392
+ when "requires_ancestor"
393
+ block = node.block
394
+ return unless block.is_a?(YARP::BlockNode)
395
+
396
+ body = block.body
397
+ return unless body.is_a?(YARP::StatementsNode)
398
+
399
+ current_scope << RequiresAncestor.new(
400
+ node_string!(body),
401
+ loc: node_loc(node),
402
+ comments: node_comments(node),
403
+ )
404
+ when "sig"
405
+ @last_sigs << parse_sig(node)
402
406
  else
403
- args << Arg.new(parse_expr(child))
407
+ current_scope << Send.new(
408
+ message,
409
+ parse_send_args(node.arguments),
410
+ loc: node_loc(node),
411
+ comments: node_comments(node),
412
+ )
404
413
  end
405
414
  end
406
- args
407
- end
408
415
 
409
- sig { params(node: AST::Node).returns(T.nilable(RBI::Node)) }
410
- def parse_block(node)
411
- name = node.children[0].children[1]
412
-
413
- case name
414
- when :sig
415
- parse_sig(node)
416
- when :enums
417
- parse_enum(node)
418
- when :requires_ancestor
419
- parse_requires_ancestor(node)
420
- else
421
- raise ParseError.new("Unsupported block node type `#{name}`", node_loc(node))
422
- end
423
- end
416
+ private
424
417
 
425
- sig { params(node: AST::Node).returns(T::Boolean) }
426
- def struct_definition?(node)
427
- (node.type == :send && node.children[0]&.type == :const && node.children[0].children[1] == :Struct) ||
428
- (node.type == :block && struct_definition?(node.children[0]))
429
- end
418
+ # Collect all the remaining comments within a node
419
+ sig { params(node: YARP::Node).void }
420
+ def collect_dangling_comments(node)
421
+ first_line = node.location.start_line
422
+ last_line = node.location.end_line
430
423
 
431
- sig { params(node: AST::Node).returns(RBI::Struct) }
432
- def parse_struct(node)
433
- name = parse_name(node)
434
- loc = node_loc(node)
435
- comments = node_comments(node)
424
+ last_node_last_line = node.child_nodes.last&.location&.end_line
436
425
 
437
- send = node.children[2]
438
- body = []
426
+ last_line.downto(first_line) do |line|
427
+ comment = @comments_by_line[line]
428
+ next unless comment
429
+ break if last_node_last_line && line <= last_node_last_line
439
430
 
440
- if send.type == :block
441
- if send.children[2].type == :begin
442
- body = send.children[2].children
443
- else
444
- body << send.children[2]
431
+ current_scope << parse_comment(comment)
432
+ @comments_by_line.delete(line)
445
433
  end
446
- send = send.children[0]
447
- end
448
-
449
- members = []
450
- keyword_init = T.let(false, T::Boolean)
451
- send.children[2..].each do |child|
452
- if child.type == :sym
453
- members << child.children[0]
454
- elsif child.type == :kwargs
455
- pair = child.children[0]
456
- if pair.children[0].children[0] == :keyword_init
457
- keyword_init = true if pair.children[1].type == :true
434
+ end
435
+
436
+ # Collect all the remaining comments after visiting the tree
437
+ sig { void }
438
+ def collect_orphan_comments
439
+ last_line = T.let(nil, T.nilable(Integer))
440
+ last_node_end = @tree.nodes.last&.loc&.end_line
441
+
442
+ @comments_by_line.each do |line, comment|
443
+ # Associate the comment either with the header or the file or as a dangling comment at the end
444
+ recv = if last_node_end && line >= last_node_end
445
+ @tree
446
+ else
447
+ @tree.comments
458
448
  end
449
+
450
+ # Preserve blank lines in comments
451
+ if last_line && line > last_line + 1
452
+ recv << BlankLine.new(loc: Loc.from_yarp(@file, comment.location))
453
+ end
454
+
455
+ recv << parse_comment(comment)
456
+ last_line = line
459
457
  end
460
458
  end
461
459
 
462
- struct = Struct.new(name, members: members, keyword_init: keyword_init, loc: loc, comments: comments)
463
- @scopes_stack << struct
464
- visit_all(body)
465
- @scopes_stack.pop
466
-
467
- struct
468
- end
460
+ sig { returns(Tree) }
461
+ def current_scope
462
+ T.must(@scopes_stack.last) # Should never be nil since we create a Tree as the root
463
+ end
469
464
 
470
- sig { params(node: AST::Node).returns(T::Boolean) }
471
- def type_variable_definition?(node)
472
- (node.type == :send && node.children[0].nil? && (node.children[1] == :type_member ||
473
- node.children[1] == :type_template)) ||
474
- (node.type == :block && type_variable_definition?(node.children[0]))
475
- end
465
+ sig { returns(T::Array[Sig]) }
466
+ def current_sigs
467
+ sigs = @last_sigs.dup
468
+ @last_sigs.clear
469
+ sigs
470
+ end
476
471
 
477
- sig { params(node: AST::Node).returns(RBI::TypeMember) }
478
- def parse_type_variable(node)
479
- name = parse_name(node)
480
- loc = node_loc(node)
481
- comments = node_comments(node)
472
+ sig { returns(T::Array[Comment]) }
473
+ def current_sigs_comments
474
+ comments = @last_sigs_comments.dup
475
+ @last_sigs_comments.clear
476
+ comments
477
+ end
482
478
 
483
- send = node.children[2]
479
+ sig { params(node: YARP::Node).returns(T::Array[Comment]) }
480
+ def node_comments(node)
481
+ comments = []
484
482
 
485
- TypeMember.new(name, send.location.expression.source, loc: loc, comments: comments)
486
- end
483
+ start_line = node.location.start_line
484
+ start_line -= 1 unless @comments_by_line.key?(start_line)
487
485
 
488
- sig { params(node: AST::Node).returns([String, String, T.nilable(String)]) }
489
- def parse_tstruct_prop(node)
490
- name = node.children[2].children[0].to_s
491
- type = parse_expr(node.children[3])
492
- has_default = node.children[4]
493
- &.children&.fetch(0, nil)
494
- &.children&.fetch(0, nil)
495
- &.children&.fetch(0, nil) == :default
496
- default_value = if has_default
497
- parse_expr(node.children.fetch(4, nil)
498
- &.children&.fetch(0, nil)
499
- &.children&.fetch(1, nil))
500
- end
501
- [name, type, default_value]
502
- end
486
+ start_line.downto(1) do |line|
487
+ comment = @comments_by_line[line]
488
+ break unless comment
503
489
 
504
- sig { params(node: AST::Node).returns(Sig) }
505
- def parse_sig(node)
506
- sig = SigBuilder.build(node)
507
- sig.loc = node_loc(node)
508
- sig
509
- end
490
+ comments.unshift(parse_comment(comment))
491
+ @comments_by_line.delete(line)
492
+ end
510
493
 
511
- sig { params(node: AST::Node).returns(TEnumBlock) }
512
- def parse_enum(node)
513
- enum = TEnumBlock.new
494
+ comments
495
+ end
514
496
 
515
- body = if node.children[2].type == :begin
516
- node.children[2].children
517
- else
518
- [node.children[2]]
497
+ sig { params(node: YARP::Comment).returns(Comment) }
498
+ def parse_comment(node)
499
+ Comment.new(node.location.slice.gsub(/^# ?/, "").rstrip, loc: Loc.from_yarp(@file, node.location))
519
500
  end
520
501
 
521
- body.each do |child|
522
- enum << parse_name(child)
502
+ sig { params(node: T.nilable(YARP::Node)).returns(T::Array[Arg]) }
503
+ def parse_send_args(node)
504
+ args = T.let([], T::Array[Arg])
505
+ return args unless node.is_a?(YARP::ArgumentsNode)
506
+
507
+ node.arguments.each do |arg|
508
+ case arg
509
+ when YARP::KeywordHashNode
510
+ arg.elements.each do |assoc|
511
+ next unless assoc.is_a?(YARP::AssocNode)
512
+
513
+ args << KwArg.new(
514
+ node_string!(assoc.key).delete_suffix(":"),
515
+ T.must(node_string(assoc.value)),
516
+ )
517
+ end
518
+ else
519
+ args << Arg.new(T.must(node_string(arg)))
520
+ end
521
+ end
522
+
523
+ args
523
524
  end
524
- enum.loc = node_loc(node)
525
- enum
526
- end
527
525
 
528
- sig { params(node: AST::Node).returns(RequiresAncestor) }
529
- def parse_requires_ancestor(node)
530
- name = parse_name(node.children[2])
531
- ra = RequiresAncestor.new(name)
532
- ra.loc = node_loc(node)
533
- ra
534
- end
526
+ sig { params(node: T.nilable(YARP::Node)).returns(T::Array[Param]) }
527
+ def parse_params(node)
528
+ params = []
529
+ return params unless node.is_a?(YARP::ParametersNode)
535
530
 
536
- sig { params(node: AST::Node).returns(Loc) }
537
- def node_loc(node)
538
- Loc.from_ast_loc(@file, node.location)
539
- end
531
+ node.requireds.each do |param|
532
+ next unless param.is_a?(YARP::RequiredParameterNode)
540
533
 
541
- sig { params(node: AST::Node).returns(T::Array[Comment]) }
542
- def node_comments(node)
543
- comments = @nodes_comments_assoc[node.location]
544
- return [] unless comments
534
+ params << ReqParam.new(
535
+ param.name.to_s,
536
+ loc: node_loc(param),
537
+ comments: node_comments(param),
538
+ )
539
+ end
545
540
 
546
- comments.map do |comment|
547
- text = comment.text[1..-1].strip
548
- loc = Loc.from_ast_loc(@file, comment.location)
549
- Comment.new(text, loc: loc)
550
- end
551
- end
541
+ node.optionals.each do |param|
542
+ next unless param.is_a?(YARP::OptionalParameterNode)
552
543
 
553
- sig { returns(Tree) }
554
- def current_scope
555
- T.must(@scopes_stack.last) # Should never be nil since we create a Tree as the root
556
- end
544
+ params << OptParam.new(
545
+ param.name.to_s,
546
+ node_string!(param.value),
547
+ loc: node_loc(param),
548
+ comments: node_comments(param),
549
+ )
550
+ end
557
551
 
558
- sig { returns(T::Array[Sig]) }
559
- def current_sigs
560
- sigs = @last_sigs.dup
561
- @last_sigs.clear
562
- sigs
563
- end
552
+ rest = node.rest
553
+ if rest.is_a?(YARP::RestParameterNode)
554
+ params << RestParam.new(
555
+ rest.name&.to_s || "*args",
556
+ loc: node_loc(rest),
557
+ comments: node_comments(rest),
558
+ )
559
+ end
564
560
 
565
- sig { returns(T::Array[Comment]) }
566
- def current_sigs_comments
567
- comments = @last_sigs_comments.dup
568
- @last_sigs_comments.clear
569
- comments
570
- end
561
+ node.keywords.each do |param|
562
+ next unless param.is_a?(YARP::KeywordParameterNode)
563
+
564
+ value = param.value
565
+ params << if value
566
+ KwOptParam.new(
567
+ param.name.to_s.delete_suffix(":"),
568
+ node_string!(value),
569
+ loc: node_loc(param),
570
+ comments: node_comments(param),
571
+ )
572
+ else
573
+ KwParam.new(
574
+ param.name.to_s.delete_suffix(":"),
575
+ loc: node_loc(param),
576
+ comments: node_comments(param),
577
+ )
578
+ end
579
+ end
571
580
 
572
- sig { void }
573
- def assoc_dangling_comments
574
- last_line = T.let(nil, T.nilable(Integer))
575
- (@comments - @nodes_comments_assoc.values.flatten).each do |comment|
576
- comment_line = comment.location.last_line
577
- text = comment.text[1..-1].strip
578
- loc = Loc.from_ast_loc(@file, comment.location)
579
-
580
- if last_line && comment_line > last_line + 1
581
- # Preserve empty lines in file headers
582
- tree.comments << BlankLine.new(loc: loc)
581
+ rest_kw = node.keyword_rest
582
+ if rest_kw.is_a?(YARP::KeywordRestParameterNode)
583
+ params << KwRestParam.new(
584
+ rest_kw.name&.to_s || "**kwargs",
585
+ loc: node_loc(rest_kw),
586
+ comments: node_comments(rest_kw),
587
+ )
583
588
  end
584
589
 
585
- tree.comments << Comment.new(text, loc: loc)
586
- last_line = comment_line
590
+ block = node.block
591
+ if block.is_a?(YARP::BlockParameterNode)
592
+ params << BlockParam.new(
593
+ block.name&.to_s || "&block",
594
+ loc: node_loc(block),
595
+ comments: node_comments(block),
596
+ )
597
+ end
598
+
599
+ params
587
600
  end
588
- end
589
601
 
590
- sig { void }
591
- def separate_header_comments
592
- return if @nodes_comments_assoc.empty?
602
+ sig { params(node: YARP::CallNode).returns(Sig) }
603
+ def parse_sig(node)
604
+ @last_sigs_comments = node_comments(node)
593
605
 
594
- keep = []
595
- node = T.must(@nodes_comments_assoc.keys.first)
596
- comments = T.must(@nodes_comments_assoc.values.first)
606
+ builder = SigBuilder.new(@source, file: @file)
607
+ builder.current.loc = node_loc(node)
608
+ builder.visit_call_node(node)
609
+ builder.current
610
+ end
597
611
 
598
- last_line = T.let(nil, T.nilable(Integer))
599
- comments.reverse.each do |comment|
600
- comment_line = comment.location.last_line
612
+ sig { params(node: T.any(YARP::ConstantWriteNode, YARP::ConstantPathWriteNode)).returns(T.nilable(Struct)) }
613
+ def parse_struct(node)
614
+ send = node.value
615
+ return unless send.is_a?(YARP::CallNode)
616
+ return unless send.message == "new"
617
+
618
+ recv = send.receiver
619
+ return unless recv
620
+ return unless node_string(recv) =~ /(::)?Struct/
621
+
622
+ members = []
623
+ keyword_init = T.let(false, T::Boolean)
624
+
625
+ args = send.arguments
626
+ if args.is_a?(YARP::ArgumentsNode)
627
+ args.arguments.each do |arg|
628
+ case arg
629
+ when YARP::SymbolNode
630
+ members << arg.value
631
+ when YARP::KeywordHashNode
632
+ arg.elements.each do |assoc|
633
+ next unless assoc.is_a?(YARP::AssocNode)
634
+
635
+ key = node_string!(assoc.key)
636
+ val = node_string(assoc.value)
637
+
638
+ keyword_init = val == "true" if key == "keyword_init:"
639
+ end
640
+ else
641
+ raise ParseError.new("Unexpected node type `#{arg.class}`", node_loc(arg))
642
+ end
643
+ end
644
+ end
601
645
 
602
- break if last_line && comment_line < last_line - 1 ||
603
- !last_line && comment_line < node.first_line - 1
646
+ name = case node
647
+ when YARP::ConstantWriteNode
648
+ node.name.to_s
649
+ when YARP::ConstantPathWriteNode
650
+ node_string!(node.target)
651
+ end
604
652
 
605
- keep << comment
606
- last_line = comment_line
653
+ loc = node_loc(node)
654
+ comments = node_comments(node)
655
+ struct = Struct.new(name, members: members, keyword_init: keyword_init, loc: loc, comments: comments)
656
+ @scopes_stack << struct
657
+ visit(send.block)
658
+ @scopes_stack.pop
659
+ struct
607
660
  end
608
661
 
609
- @nodes_comments_assoc[node] = keep.reverse
610
- end
662
+ sig { params(send: YARP::CallNode).void }
663
+ def parse_tstruct_field(send)
664
+ args = send.arguments
665
+ return unless args.is_a?(YARP::ArgumentsNode)
611
666
 
612
- sig { void }
613
- def set_root_tree_loc
614
- first_loc = tree.nodes.first&.loc
615
- last_loc = tree.nodes.last&.loc
616
-
617
- @tree.loc = Loc.new(
618
- file: @file,
619
- begin_line: first_loc&.begin_line || 0,
620
- begin_column: first_loc&.begin_column || 0,
621
- end_line: last_loc&.end_line || 0,
622
- end_column: last_loc&.end_column || 0,
623
- )
624
- end
625
- end
667
+ name_arg, type_arg, *rest = args.arguments
668
+ return unless name_arg
669
+ return unless type_arg
626
670
 
627
- class ConstBuilder < ASTVisitor
628
- extend T::Sig
671
+ name = node_string!(name_arg).delete_prefix(":")
672
+ type = node_string!(type_arg)
673
+ loc = node_loc(send)
674
+ comments = node_comments(send)
675
+ default_value = T.let(nil, T.nilable(String))
629
676
 
630
- sig { returns(T::Array[String]) }
631
- attr_accessor :names
677
+ rest&.each do |arg|
678
+ next unless arg.is_a?(YARP::KeywordHashNode)
632
679
 
633
- sig { void }
634
- def initialize
635
- super
636
- @names = T.let([], T::Array[String])
637
- end
680
+ arg.elements.each do |assoc|
681
+ next unless assoc.is_a?(YARP::AssocNode)
638
682
 
639
- class << self
640
- extend T::Sig
683
+ if node_string(assoc.key) == "default:"
684
+ default_value = node_string(assoc.value)
685
+ end
686
+ end
687
+ end
641
688
 
642
- sig { params(node: T.nilable(AST::Node)).returns(T.nilable(String)) }
643
- def visit(node)
644
- v = ConstBuilder.new
645
- v.visit(node)
646
- return if v.names.empty?
689
+ current_scope << case send.message
690
+ when "const"
691
+ TStructConst.new(name, type, default: default_value, loc: loc, comments: comments)
692
+ when "prop"
693
+ TStructProp.new(name, type, default: default_value, loc: loc, comments: comments)
694
+ else
695
+ raise ParseError.new("Unexpected message `#{send.message}`", loc)
696
+ end
697
+ end
647
698
 
648
- v.names.join("::")
699
+ sig { params(name: String, node: YARP::Node).returns(Visibility) }
700
+ def parse_visibility(name, node)
701
+ case name
702
+ when "public"
703
+ Public.new(loc: node_loc(node))
704
+ when "protected"
705
+ Protected.new(loc: node_loc(node))
706
+ when "private"
707
+ Private.new(loc: node_loc(node))
708
+ else
709
+ raise ParseError.new("Unexpected visibility `#{name}`", node_loc(node))
710
+ end
649
711
  end
650
- end
651
712
 
652
- sig { override.params(node: T.nilable(AST::Node)).void }
653
- def visit(node)
654
- return unless node
713
+ sig { void }
714
+ def separate_header_comments
715
+ current_scope.nodes.dup.each do |child_node|
716
+ break unless child_node.is_a?(Comment) || child_node.is_a?(BlankLine)
655
717
 
656
- case node.type
657
- when :const, :casgn
658
- visit(node.children[0])
659
- @names << node.children[1].to_s
660
- when :cbase
661
- @names << ""
662
- when :sym
663
- @names << ":#{node.children[0]}"
718
+ current_scope.comments << child_node
719
+ child_node.detach
720
+ end
664
721
  end
665
- end
666
- end
667
722
 
668
- class SigBuilder < ASTVisitor
669
- extend T::Sig
723
+ sig { void }
724
+ def set_root_tree_loc
725
+ first_loc = tree.nodes.first&.loc
726
+ last_loc = tree.nodes.last&.loc
727
+
728
+ @tree.loc = Loc.new(
729
+ file: @file,
730
+ begin_line: first_loc&.begin_line || 0,
731
+ begin_column: first_loc&.begin_column || 0,
732
+ end_line: last_loc&.end_line || 0,
733
+ end_column: last_loc&.end_column || 0,
734
+ )
735
+ end
670
736
 
671
- sig { returns(Sig) }
672
- attr_reader :current
737
+ sig { params(node: T.nilable(YARP::Node)).returns(T::Boolean) }
738
+ def type_variable_definition?(node)
739
+ return false unless node.is_a?(YARP::CallNode)
740
+ return false unless node.block
673
741
 
674
- sig { void }
675
- def initialize
676
- super
677
- @current = T.let(Sig.new, Sig)
742
+ node.message == "type_member" || node.message == "type_template"
743
+ end
678
744
  end
679
745
 
680
- class << self
746
+ class SigBuilder < Visitor
681
747
  extend T::Sig
682
748
 
683
- sig { params(node: AST::Node).returns(Sig) }
684
- def build(node)
685
- v = SigBuilder.new
686
- v.visit_all(node.children)
687
- v.current
688
- end
689
- end
749
+ sig { returns(Sig) }
750
+ attr_reader :current
690
751
 
691
- sig { override.params(node: T.nilable(AST::Node)).void }
692
- def visit(node)
693
- return unless node
752
+ sig { params(content: String, file: String).void }
753
+ def initialize(content, file:)
754
+ super
694
755
 
695
- case node.type
696
- when :send
697
- visit_send(node)
756
+ @current = T.let(Sig.new, Sig)
698
757
  end
699
- end
700
758
 
701
- sig { params(node: AST::Node).void }
702
- def visit_send(node)
703
- visit(node.children[0]) if node.children[0]
704
- name = node.children[1]
705
- case name
706
- when :sig
707
- arg = node.children[2]
708
- @current.is_final = arg && arg.children[0] == :final
709
- when :abstract
710
- @current.is_abstract = true
711
- when :override
712
- @current.is_override = true
713
- when :overridable
714
- @current.is_overridable = true
715
- when :checked
716
- if node.children.length >= 3
717
- @current.checked = node.children[2].children[0]
718
- end
719
- when :type_parameters
720
- node.children[2..-1].each do |child|
721
- @current.type_params << child.children[0].to_s
722
- end
723
- when :params
724
- if node.children.length >= 3
725
- node.children[2].children.each do |child|
726
- name = child.children[0].children[0].to_s
727
- type = parse_expr(child.children[1])
728
- @current << SigParam.new(name, type)
759
+ sig { override.params(node: YARP::CallNode).void }
760
+ def visit_call_node(node)
761
+ case node.message
762
+ when "sig"
763
+ args = node.arguments
764
+ if args.is_a?(YARP::ArgumentsNode)
765
+ args.arguments.each do |arg|
766
+ @current.is_final = node_string(arg) == ":final"
767
+ end
729
768
  end
769
+ when "abstract"
770
+ @current.is_abstract = true
771
+ when "checked"
772
+ args = node.arguments
773
+ if args.is_a?(YARP::ArgumentsNode)
774
+ arg = node_string(args.arguments.first)
775
+ @current.checked = arg&.delete_prefix(":")&.to_sym
776
+ end
777
+ when "override"
778
+ @current.is_override = true
779
+ when "overridable"
780
+ @current.is_overridable = true
781
+ when "params"
782
+ visit(node.arguments)
783
+ when "returns"
784
+ args = node.arguments
785
+ if args.is_a?(YARP::ArgumentsNode)
786
+ first = args.arguments.first
787
+ @current.return_type = node_string!(first) if first
788
+ end
789
+ when "type_parameters"
790
+ args = node.arguments
791
+ if args.is_a?(YARP::ArgumentsNode)
792
+ args.arguments.each do |arg|
793
+ @current.type_params << node_string!(arg).delete_prefix(":")
794
+ end
795
+ end
796
+ when "void"
797
+ @current.return_type = nil
730
798
  end
731
- when :returns
732
- if node.children.length >= 3
733
- @current.return_type = parse_expr(node.children[2])
734
- end
735
- when :void
736
- @current.return_type = nil
737
- else
738
- raise "#{node.location.line}: Unhandled #{name}"
739
- end
740
- end
741
- end
742
799
 
743
- class Loc
744
- class << self
745
- extend T::Sig
800
+ visit(node.receiver)
801
+ visit(node.block)
802
+ end
746
803
 
747
- sig { params(file: String, ast_loc: T.any(::Parser::Source::Map, ::Parser::Source::Range)).returns(Loc) }
748
- def from_ast_loc(file, ast_loc)
749
- Loc.new(
750
- file: file,
751
- begin_line: ast_loc.line,
752
- begin_column: ast_loc.column,
753
- end_line: ast_loc.last_line,
754
- end_column: ast_loc.last_column,
804
+ sig { override.params(node: YARP::AssocNode).void }
805
+ def visit_assoc_node(node)
806
+ @current.params << SigParam.new(
807
+ node_string!(node.key).delete_suffix(":"),
808
+ node_string!(T.must(node.value)),
755
809
  )
756
810
  end
757
811
  end