rbi 0.0.17 → 0.1.0

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