rbi 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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