rbi 0.0.17 → 0.1.0

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,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