rbi 0.0.2 → 0.0.6

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,13 +1,24 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "unparser"
4
+ require "parser"
5
5
 
6
6
  module RBI
7
- class Parser
7
+ class ParseError < StandardError
8
8
  extend T::Sig
9
9
 
10
- class Error < StandardError; end
10
+ sig { returns(Loc) }
11
+ attr_reader :location
12
+
13
+ sig { params(message: String, location: Loc).void }
14
+ def initialize(message, location)
15
+ super(message)
16
+ @location = location
17
+ end
18
+ end
19
+
20
+ class Parser
21
+ extend T::Sig
11
22
 
12
23
  # opt-in to most recent AST format
13
24
  ::Parser::Builders::Default.emit_lambda = true
@@ -16,6 +27,12 @@ module RBI
16
27
  ::Parser::Builders::Default.emit_index = true
17
28
  ::Parser::Builders::Default.emit_arg_inside_procarg0 = true
18
29
 
30
+ sig { void }
31
+ def initialize
32
+ # Delay load unparser and only if it has not been loaded already.
33
+ require "unparser" unless defined?(::Unparser)
34
+ end
35
+
19
36
  sig { params(string: String).returns(Tree) }
20
37
  def self.parse_string(string)
21
38
  Parser.new.parse_string(string)
@@ -29,15 +46,11 @@ module RBI
29
46
  sig { params(string: String).returns(Tree) }
30
47
  def parse_string(string)
31
48
  parse(string, file: "-")
32
- rescue ::Parser::SyntaxError => e
33
- raise Error, e.message
34
49
  end
35
50
 
36
51
  sig { params(path: String).returns(Tree) }
37
52
  def parse_file(path)
38
53
  parse(::File.read(path), file: path)
39
- rescue ::Parser::SyntaxError => e
40
- raise Error, e.message
41
54
  end
42
55
 
43
56
  private
@@ -47,9 +60,12 @@ module RBI
47
60
  node, comments = Unparser.parse_with_comments(content)
48
61
  assoc = ::Parser::Source::Comment.associate_locations(node, comments)
49
62
  builder = TreeBuilder.new(file: file, comments: assoc)
63
+ builder.separate_header_comments
50
64
  builder.visit(node)
51
65
  builder.assoc_dangling_comments(comments)
52
66
  builder.tree
67
+ rescue ::Parser::SyntaxError => e
68
+ raise ParseError.new(e.message, Loc.from_ast_loc(file, e.diagnostic.location))
53
69
  end
54
70
  end
55
71
 
@@ -70,12 +86,12 @@ module RBI
70
86
  private
71
87
 
72
88
  sig { params(node: AST::Node).returns(String) }
73
- def visit_name(node)
89
+ def parse_name(node)
74
90
  T.must(ConstBuilder.visit(node))
75
91
  end
76
92
 
77
93
  sig { params(node: AST::Node).returns(String) }
78
- def visit_expr(node)
94
+ def parse_expr(node)
79
95
  Unparser.unparse(node)
80
96
  end
81
97
  end
@@ -89,10 +105,10 @@ module RBI
89
105
  sig do
90
106
  params(
91
107
  file: String,
92
- comments: T.nilable(T::Hash[::Parser::Source::Map, T::Array[::Parser::Source::Comment]])
108
+ comments: T::Hash[::Parser::Source::Map, T::Array[::Parser::Source::Comment]]
93
109
  ).void
94
110
  end
95
- def initialize(file:, comments: nil)
111
+ def initialize(file:, comments: {})
96
112
  super()
97
113
  @file = file
98
114
  @comments = comments
@@ -106,130 +122,171 @@ module RBI
106
122
  return unless node.is_a?(AST::Node)
107
123
  case node.type
108
124
  when :module, :class, :sclass
109
- visit_scope(node)
125
+ scope = parse_scope(node)
126
+ current_scope << scope
127
+ @scopes_stack << scope
128
+ visit_all(node.children)
129
+ @scopes_stack.pop
110
130
  when :casgn
111
- visit_const_assign(node)
131
+ current_scope << parse_const_assign(node)
112
132
  when :def, :defs
113
- visit_def(node)
133
+ current_scope << parse_def(node)
114
134
  when :send
115
- visit_send(node)
135
+ node = parse_send(node)
136
+ current_scope << node if node
116
137
  when :block
117
- visit_block(node)
138
+ node = parse_block(node)
139
+ if node.is_a?(Sig)
140
+ @last_sigs << node
141
+ elsif node
142
+ current_scope << node
143
+ end
118
144
  else
119
145
  visit_all(node.children)
120
146
  end
121
147
  end
122
148
 
149
+ sig { void }
150
+ def separate_header_comments
151
+ return if @comments.empty?
152
+
153
+ keep = []
154
+ node = T.must(@comments.keys.first)
155
+ comments = T.must(@comments.values.first)
156
+
157
+ last_line = T.let(nil, T.nilable(Integer))
158
+ comments.reverse.each do |comment|
159
+ comment_line = comment.location.last_line
160
+
161
+ break if last_line && comment_line < last_line - 1 ||
162
+ !last_line && comment_line < node.first_line - 1
163
+
164
+ keep << comment
165
+ last_line = comment_line
166
+ end
167
+
168
+ @comments[node] = keep.reverse
169
+ end
170
+
123
171
  sig { params(comments: T::Array[::Parser::Source::Comment]).void }
124
172
  def assoc_dangling_comments(comments)
125
- return unless tree.empty?
126
- comments.each do |comment|
173
+ last_line = T.let(nil, T.nilable(Integer))
174
+ (comments - @comments.values.flatten).each do |comment|
175
+ comment_line = comment.location.last_line
127
176
  text = comment.text[1..-1].strip
128
- loc = ast_to_rbi_loc(comment.location)
177
+ loc = Loc.from_ast_loc(@file, comment.location)
178
+
179
+ if last_line && comment_line > last_line + 1
180
+ # Preserve empty lines in file headers
181
+ tree.comments << EmptyComment.new(loc: loc)
182
+ end
183
+
129
184
  tree.comments << Comment.new(text, loc: loc)
185
+ last_line = comment_line
130
186
  end
131
187
  end
132
188
 
133
189
  private
134
190
 
135
- sig { params(node: AST::Node).void }
136
- def visit_scope(node)
191
+ sig { params(node: AST::Node).returns(Scope) }
192
+ def parse_scope(node)
137
193
  loc = node_loc(node)
138
194
  comments = node_comments(node)
139
195
 
140
- scope = case node.type
196
+ case node.type
141
197
  when :module
142
- name = visit_name(node.children[0])
198
+ name = parse_name(node.children[0])
143
199
  Module.new(name, loc: loc, comments: comments)
144
200
  when :class
145
- name = visit_name(node.children[0])
201
+ name = parse_name(node.children[0])
146
202
  superclass_name = ConstBuilder.visit(node.children[1])
147
203
  Class.new(name, superclass_name: superclass_name, loc: loc, comments: comments)
148
204
  when :sclass
149
205
  SingletonClass.new(loc: loc, comments: comments)
150
206
  else
151
- raise "Unsupported node #{node.type}"
207
+ raise ParseError.new("Unsupported scope node type `#{node.type}`", loc)
152
208
  end
153
- current_scope << scope
209
+ end
154
210
 
155
- @scopes_stack << scope
156
- visit_all(node.children)
157
- @scopes_stack.pop
211
+ sig { params(node: AST::Node).returns(RBI::Node) }
212
+ def parse_const_assign(node)
213
+ node_value = node.children[2]
214
+ if struct_definition?(node_value)
215
+ parse_struct(node)
216
+ else
217
+ name = parse_name(node)
218
+ value = parse_expr(node_value)
219
+ loc = node_loc(node)
220
+ comments = node_comments(node)
221
+ Const.new(name, value, loc: loc, comments: comments)
222
+ end
158
223
  end
159
224
 
160
- sig { params(node: AST::Node).void }
161
- def visit_const_assign(node)
162
- name = visit_name(node)
163
- value = visit_expr(node.children[2])
225
+ sig { params(node: AST::Node).returns(Method) }
226
+ def parse_def(node)
164
227
  loc = node_loc(node)
165
- comments = node_comments(node)
166
-
167
- current_scope << Const.new(name, value, loc: loc, comments: comments)
168
- end
169
228
 
170
- sig { params(node: AST::Node).void }
171
- def visit_def(node)
172
- current_scope << case node.type
229
+ case node.type
173
230
  when :def
174
231
  Method.new(
175
232
  node.children[0].to_s,
176
- params: node.children[1].children.map { |child| visit_param(child) },
233
+ params: node.children[1].children.map { |child| parse_param(child) },
177
234
  sigs: current_sigs,
178
- loc: node_loc(node),
235
+ loc: loc,
179
236
  comments: node_comments(node)
180
237
  )
181
238
  when :defs
182
239
  Method.new(
183
240
  node.children[1].to_s,
184
- params: node.children[2].children.map { |child| visit_param(child) },
241
+ params: node.children[2].children.map { |child| parse_param(child) },
185
242
  is_singleton: true,
186
243
  sigs: current_sigs,
187
- loc: node_loc(node),
244
+ loc: loc,
188
245
  comments: node_comments(node)
189
246
  )
190
247
  else
191
- raise "Unsupported node #{node.type}"
248
+ raise ParseError.new("Unsupported def node type `#{node.type}`", loc)
192
249
  end
193
250
  end
194
251
 
195
252
  sig { params(node: AST::Node).returns(Param) }
196
- def visit_param(node)
253
+ def parse_param(node)
197
254
  name = node.children[0].to_s
198
255
  loc = node_loc(node)
199
256
  comments = node_comments(node)
200
257
 
201
258
  case node.type
202
259
  when :arg
203
- Param.new(name, loc: loc, comments: comments)
260
+ ReqParam.new(name, loc: loc, comments: comments)
204
261
  when :optarg
205
- value = visit_expr(node.children[1])
262
+ value = parse_expr(node.children[1])
206
263
  OptParam.new(name, value, loc: loc, comments: comments)
207
264
  when :restarg
208
265
  RestParam.new(name, loc: loc, comments: comments)
209
266
  when :kwarg
210
267
  KwParam.new(name, loc: loc, comments: comments)
211
268
  when :kwoptarg
212
- value = visit_expr(node.children[1])
269
+ value = parse_expr(node.children[1])
213
270
  KwOptParam.new(name, value, loc: loc, comments: comments)
214
271
  when :kwrestarg
215
272
  KwRestParam.new(name, loc: loc, comments: comments)
216
273
  when :blockarg
217
274
  BlockParam.new(name, loc: loc, comments: comments)
218
275
  else
219
- raise "Unsupported node #{node.type}"
276
+ raise ParseError.new("Unsupported param node type `#{node.type}`", loc)
220
277
  end
221
278
  end
222
279
 
223
- sig { params(node: AST::Node).void }
224
- def visit_send(node)
280
+ sig { params(node: AST::Node).returns(T.nilable(RBI::Node)) }
281
+ def parse_send(node)
225
282
  recv = node.children[0]
226
- return if recv && recv != :self
283
+ return nil if recv && recv != :self
227
284
 
228
285
  method_name = node.children[1]
229
286
  loc = node_loc(node)
230
287
  comments = node_comments(node)
231
288
 
232
- current_scope << case method_name
289
+ case method_name
233
290
  when :attr_reader
234
291
  symbols = node.children[2..-1].map { |child| child.children[0] }
235
292
  AttrReader.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
@@ -240,53 +297,114 @@ module RBI
240
297
  symbols = node.children[2..-1].map { |child| child.children[0] }
241
298
  AttrAccessor.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
242
299
  when :include
243
- names = node.children[2..-1].map { |child| visit_name(child) }
300
+ names = node.children[2..-1].map { |child| parse_name(child) }
244
301
  Include.new(*names, loc: loc, comments: comments)
245
302
  when :extend
246
- names = node.children[2..-1].map { |child| visit_name(child) }
303
+ names = node.children[2..-1].map { |child| parse_name(child) }
247
304
  Extend.new(*names, loc: loc, comments: comments)
248
305
  when :abstract!, :sealed!, :interface!
249
306
  Helper.new(method_name.to_s.delete_suffix("!"), loc: loc, comments: comments)
250
307
  when :mixes_in_class_methods
251
- names = node.children[2..-1].map { |child| visit_name(child) }
308
+ names = node.children[2..-1].map { |child| parse_name(child) }
252
309
  MixesInClassMethods.new(*names, loc: loc, comments: comments)
253
310
  when :public, :protected, :private
254
- Visibility.new(method_name, loc: loc)
311
+ visibility = Visibility.new(method_name, loc: loc)
312
+ nested_node = node.children[2]
313
+ case nested_node&.type
314
+ when :def, :defs
315
+ method = parse_def(nested_node)
316
+ method.visibility = visibility
317
+ method
318
+ when :send
319
+ snode = parse_send(nested_node)
320
+ raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc) unless snode.is_a?(Attr)
321
+ snode.visibility = visibility
322
+ snode
323
+ when nil
324
+ visibility
325
+ else
326
+ raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc)
327
+ end
255
328
  when :prop
256
- name, type, default_value = visit_struct_prop(node)
329
+ name, type, default_value = parse_tstruct_prop(node)
257
330
  TStructProp.new(name, type, default: default_value, loc: loc, comments: comments)
258
331
  when :const
259
- name, type, default_value = visit_struct_prop(node)
332
+ name, type, default_value = parse_tstruct_prop(node)
260
333
  TStructConst.new(name, type, default: default_value, loc: loc, comments: comments)
261
334
  else
262
- raise "Unsupported node #{node.type} with name #{method_name}"
335
+ raise ParseError.new("Unsupported send node with name `#{method_name}`", loc)
263
336
  end
264
337
  end
265
338
 
266
- sig { params(node: AST::Node).void }
267
- def visit_block(node)
339
+ sig { params(node: AST::Node).returns(T.nilable(RBI::Node)) }
340
+ def parse_block(node)
268
341
  name = node.children[0].children[1]
269
342
 
270
343
  case name
271
344
  when :sig
272
- @last_sigs << visit_sig(node)
345
+ parse_sig(node)
273
346
  when :enums
274
- current_scope << visit_enum(node)
347
+ parse_enum(node)
275
348
  else
276
- raise "Unsupported node #{node.type} with name #{name}"
349
+ raise ParseError.new("Unsupported block node type `#{name}`", node_loc(node))
277
350
  end
278
351
  end
279
352
 
353
+ sig { params(node: AST::Node).returns(T::Boolean) }
354
+ def struct_definition?(node)
355
+ (node.type == :send && node.children[0]&.type == :const && node.children[0].children[1] == :Struct) ||
356
+ (node.type == :block && struct_definition?(node.children[0]))
357
+ end
358
+
359
+ sig { params(node: AST::Node).returns(RBI::Struct) }
360
+ def parse_struct(node)
361
+ name = parse_name(node)
362
+ loc = node_loc(node)
363
+ comments = node_comments(node)
364
+
365
+ send = node.children[2]
366
+ body = []
367
+
368
+ if send.type == :block
369
+ if send.children[2].type == :begin
370
+ body = send.children[2].children
371
+ else
372
+ body << send.children[2]
373
+ end
374
+ send = send.children[0]
375
+ end
376
+
377
+ members = []
378
+ keyword_init = T.let(false, T::Boolean)
379
+ send.children[2..].each do |child|
380
+ if child.type == :sym
381
+ members << child.children[0]
382
+ elsif child.type == :kwargs
383
+ pair = child.children[0]
384
+ if pair.children[0].children[0] == :keyword_init
385
+ keyword_init = true if pair.children[1].type == :true
386
+ end
387
+ end
388
+ end
389
+
390
+ struct = Struct.new(name, members: members, keyword_init: keyword_init, loc: loc, comments: comments)
391
+ @scopes_stack << struct
392
+ visit_all(body)
393
+ @scopes_stack.pop
394
+
395
+ struct
396
+ end
397
+
280
398
  sig { params(node: AST::Node).returns([String, String, T.nilable(String)]) }
281
- def visit_struct_prop(node)
399
+ def parse_tstruct_prop(node)
282
400
  name = node.children[2].children[0].to_s
283
- type = visit_expr(node.children[3])
401
+ type = parse_expr(node.children[3])
284
402
  has_default = node.children[4]
285
403
  &.children&.fetch(0, nil)
286
404
  &.children&.fetch(0, nil)
287
405
  &.children&.fetch(0, nil) == :default
288
406
  default_value = if has_default
289
- visit_expr(node.children.fetch(4, nil)
407
+ parse_expr(node.children.fetch(4, nil)
290
408
  &.children&.fetch(0, nil)
291
409
  &.children&.fetch(1, nil))
292
410
  end
@@ -294,17 +412,17 @@ module RBI
294
412
  end
295
413
 
296
414
  sig { params(node: AST::Node).returns(Sig) }
297
- def visit_sig(node)
415
+ def parse_sig(node)
298
416
  sig = SigBuilder.build(node)
299
417
  sig.loc = node_loc(node)
300
418
  sig
301
419
  end
302
420
 
303
421
  sig { params(node: AST::Node).returns(TEnumBlock) }
304
- def visit_enum(node)
422
+ def parse_enum(node)
305
423
  enum = TEnumBlock.new
306
424
  node.children[2].children.each do |child|
307
- enum << visit_name(child)
425
+ enum << parse_name(child)
308
426
  end
309
427
  enum.loc = node_loc(node)
310
428
  enum
@@ -312,28 +430,16 @@ module RBI
312
430
 
313
431
  sig { params(node: AST::Node).returns(Loc) }
314
432
  def node_loc(node)
315
- ast_to_rbi_loc(node.location)
316
- end
317
-
318
- sig { params(ast_loc: ::Parser::Source::Map).returns(Loc) }
319
- def ast_to_rbi_loc(ast_loc)
320
- Loc.new(
321
- file: @file,
322
- begin_line: ast_loc.line,
323
- begin_column: ast_loc.column,
324
- end_line: ast_loc.last_line,
325
- end_column: ast_loc.last_column
326
- )
433
+ Loc.from_ast_loc(@file, node.location)
327
434
  end
328
435
 
329
436
  sig { params(node: AST::Node).returns(T::Array[Comment]) }
330
437
  def node_comments(node)
331
- return [] unless @comments
332
438
  comments = @comments[node.location]
333
439
  return [] unless comments
334
440
  comments.map do |comment|
335
441
  text = comment.text[1..-1].strip
336
- loc = ast_to_rbi_loc(comment.location)
442
+ loc = Loc.from_ast_loc(@file, comment.location)
337
443
  Comment.new(text, loc: loc)
338
444
  end
339
445
  end
@@ -434,11 +540,11 @@ module RBI
434
540
  when :params
435
541
  node.children[2].children.each do |child|
436
542
  name = child.children[0].children[0].to_s
437
- type = visit_expr(child.children[1])
543
+ type = parse_expr(child.children[1])
438
544
  @current << SigParam.new(name, type)
439
545
  end
440
546
  when :returns
441
- @current.return_type = visit_expr(node.children[2])
547
+ @current.return_type = parse_expr(node.children[2])
442
548
  when :void
443
549
  @current.return_type = nil
444
550
  else
@@ -446,4 +552,17 @@ module RBI
446
552
  end
447
553
  end
448
554
  end
555
+
556
+ class Loc
557
+ sig { params(file: String, ast_loc: T.any(::Parser::Source::Map, ::Parser::Source::Range)).returns(Loc) }
558
+ def self.from_ast_loc(file, ast_loc)
559
+ Loc.new(
560
+ file: file,
561
+ begin_line: ast_loc.line,
562
+ begin_column: ast_loc.column,
563
+ end_line: ast_loc.last_line,
564
+ end_column: ast_loc.last_column
565
+ )
566
+ end
567
+ end
449
568
  end