rbi 0.0.17 → 0.1.1

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