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