rbi 0.0.2 → 0.0.6

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