rbi 0.0.2 → 0.0.3

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