rbi 0.0.16 → 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,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