rbi 0.0.4 → 0.0.8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad10c5d34005d6a802652c77c317986852dedeb6fb32203f04cb54af2e3390a5
4
- data.tar.gz: d502093f1ea3f802c525fa7c9c5065f5b83c164031a1a9882e783200cb58c06f
3
+ metadata.gz: 37be6daaf86810c60a865b98ac94ff089184e990e168bc405a0d167c641477ed
4
+ data.tar.gz: 95de3a3ffb035c40d9dd8da3055d808b1b0ae365add26bbc0754ae997c1c09c9
5
5
  SHA512:
6
- metadata.gz: 5e551429e890318d60b66b3331d001e1e9b0489746fbf218f5f347a6e2647e168c76eddcf5e4628e3128d7cda28d77a29bc872bd696cdfb347b78e98a839a2ba
7
- data.tar.gz: 60ec408e5cfc4b94157b6f8028c0b699ebf5e01632dc691adeff7f22c245a45abbe44d400533ddf526b32d8e9de9b4fbfa1f1e06a7ce450cf77911754633579e
6
+ metadata.gz: 3b794985ed143c257080ed69ab6ac8458e0cfcad5e988bfe412566dd2146ea009a8ceabfb274759089e3a687eb42e15b27c31a884c04e78a6b413b424d97d0b1
7
+ data.tar.gz: bc41f792fd147ceb04dee6852e046a68e5f90f886ce4b2937ceaa642ef4d9d46996f1560067177acfedc52b73b60878aeb03cd64deda17be516d04516e37ed63
data/Gemfile CHANGED
@@ -8,10 +8,11 @@ gemspec
8
8
  group(:development, :test) do
9
9
  gem("byebug")
10
10
  gem("minitest")
11
+ gem("minitest-reporters")
11
12
  gem("rake", "~> 13.0")
12
13
  gem("rubocop", "~> 1.7", require: false)
13
14
  gem("rubocop-shopify", require: false)
14
15
  gem("rubocop-sorbet", require: false)
15
- gem("sorbet", require: false)
16
- gem("tapioca", require: false, github: "Shopify/tapioca", branch: "master")
16
+ gem("sorbet", ">= 0.5.9204", require: false)
17
+ gem("tapioca", "0.5.2", require: false)
17
18
  end
data/lib/rbi/index.rb CHANGED
@@ -29,9 +29,9 @@ module RBI
29
29
  @index[id] ||= []
30
30
  end
31
31
 
32
- sig { params(node: T.all(Indexable, Node)).void }
33
- def index(node)
34
- node.index_ids.each { |id| self[id] << node }
32
+ sig { params(nodes: Node).void }
33
+ def index(*nodes)
34
+ nodes.each { |node| visit(node) }
35
35
  end
36
36
 
37
37
  sig { override.params(node: T.nilable(Node)).void }
@@ -40,14 +40,21 @@ module RBI
40
40
 
41
41
  case node
42
42
  when Scope
43
- index(node)
43
+ index_node(node)
44
44
  visit_all(node.nodes)
45
45
  when Tree
46
46
  visit_all(node.nodes)
47
47
  when Indexable
48
- index(node)
48
+ index_node(node)
49
49
  end
50
50
  end
51
+
52
+ private
53
+
54
+ sig { params(node: T.all(Indexable, Node)).void }
55
+ def index_node(node)
56
+ node.index_ids.each { |id| self[id] << node }
57
+ end
51
58
  end
52
59
 
53
60
  class Tree
@@ -154,6 +161,16 @@ module RBI
154
161
  end
155
162
  end
156
163
 
164
+ class Send
165
+ extend T::Sig
166
+ include Indexable
167
+
168
+ sig { override.returns(T::Array[String]) }
169
+ def index_ids
170
+ ["#{parent_scope&.fully_qualified_name}.#{method}"]
171
+ end
172
+ end
173
+
157
174
  class TStructConst
158
175
  extend T::Sig
159
176
  include Indexable
data/lib/rbi/model.rb CHANGED
@@ -66,7 +66,8 @@ module RBI
66
66
  end
67
67
  end
68
68
 
69
- class EmptyComment < Comment
69
+ # An arbitrary blank line that can be added both in trees and comments
70
+ class BlankLine < Comment
70
71
  extend T::Sig
71
72
 
72
73
  sig { params(loc: T.nilable(Loc)).void }
@@ -89,6 +90,13 @@ module RBI
89
90
  super(loc: loc)
90
91
  @comments = comments
91
92
  end
93
+
94
+ sig { returns(T::Array[String]) }
95
+ def annotations
96
+ comments
97
+ .select { |comment| comment.text.start_with?("@") }
98
+ .map { |comment| T.must(comment.text[1..]) }
99
+ end
92
100
  end
93
101
 
94
102
  class Tree < NodeWithComments
@@ -126,10 +134,10 @@ module RBI
126
134
  extend T::Sig
127
135
 
128
136
  sig { returns(Tree) }
129
- attr_reader :root
137
+ attr_accessor :root
130
138
 
131
139
  sig { returns(T.nilable(String)) }
132
- attr_reader :strictness
140
+ attr_accessor :strictness
133
141
 
134
142
  sig { returns(T::Array[Comment]) }
135
143
  attr_accessor :comments
@@ -152,6 +160,11 @@ module RBI
152
160
  def <<(node)
153
161
  @root << node
154
162
  end
163
+
164
+ sig { returns(T::Boolean) }
165
+ def empty?
166
+ @root.empty?
167
+ end
155
168
  end
156
169
 
157
170
  # Scopes
@@ -901,6 +914,106 @@ module RBI
901
914
  end
902
915
  end
903
916
 
917
+ # Sends
918
+
919
+ class Send < NodeWithComments
920
+ extend T::Sig
921
+
922
+ sig { returns(String) }
923
+ attr_reader :method
924
+
925
+ sig { returns(T::Array[Arg]) }
926
+ attr_reader :args
927
+
928
+ sig do
929
+ params(
930
+ method: String,
931
+ args: T::Array[Arg],
932
+ loc: T.nilable(Loc),
933
+ comments: T::Array[Comment],
934
+ block: T.nilable(T.proc.params(node: Send).void)
935
+ ).void
936
+ end
937
+ def initialize(method, args = [], loc: nil, comments: [], &block)
938
+ super(loc: loc, comments: comments)
939
+ @method = method
940
+ @args = args
941
+ block&.call(self)
942
+ end
943
+
944
+ sig { params(arg: Arg).void }
945
+ def <<(arg)
946
+ @args << arg
947
+ end
948
+
949
+ sig { params(other: T.nilable(Object)).returns(T::Boolean) }
950
+ def ==(other)
951
+ Send === other && method == other.method && args == other.args
952
+ end
953
+
954
+ sig { returns(String) }
955
+ def to_s
956
+ "#{parent_scope&.fully_qualified_name}.#{method}(#{args.join(", ")})"
957
+ end
958
+ end
959
+
960
+ class Arg < Node
961
+ extend T::Sig
962
+
963
+ sig { returns(String) }
964
+ attr_reader :value
965
+
966
+ sig do
967
+ params(
968
+ value: String,
969
+ loc: T.nilable(Loc)
970
+ ).void
971
+ end
972
+ def initialize(value, loc: nil)
973
+ super(loc: loc)
974
+ @value = value
975
+ end
976
+
977
+ sig { params(other: T.nilable(Object)).returns(T::Boolean) }
978
+ def ==(other)
979
+ Arg === other && value == other.value
980
+ end
981
+
982
+ sig { returns(String) }
983
+ def to_s
984
+ value
985
+ end
986
+ end
987
+
988
+ class KwArg < Arg
989
+ extend T::Sig
990
+
991
+ sig { returns(String) }
992
+ attr_reader :keyword
993
+
994
+ sig do
995
+ params(
996
+ keyword: String,
997
+ value: String,
998
+ loc: T.nilable(Loc)
999
+ ).void
1000
+ end
1001
+ def initialize(keyword, value, loc: nil)
1002
+ super(value, loc: loc)
1003
+ @keyword = keyword
1004
+ end
1005
+
1006
+ sig { params(other: T.nilable(Object)).returns(T::Boolean) }
1007
+ def ==(other)
1008
+ KwArg === other && value == other.value && keyword == other.keyword
1009
+ end
1010
+
1011
+ sig { returns(String) }
1012
+ def to_s
1013
+ "#{keyword}: #{value}"
1014
+ end
1015
+ end
1016
+
904
1017
  # Sorbet's sigs
905
1018
 
906
1019
  class Sig < Node
data/lib/rbi/parser.rb CHANGED
@@ -1,7 +1,7 @@
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
7
  class ParseError < StandardError
@@ -27,6 +27,12 @@ module RBI
27
27
  ::Parser::Builders::Default.emit_index = true
28
28
  ::Parser::Builders::Default.emit_arg_inside_procarg0 = true
29
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
+
30
36
  sig { params(string: String).returns(Tree) }
31
37
  def self.parse_string(string)
32
38
  Parser.new.parse_string(string)
@@ -37,11 +43,23 @@ module RBI
37
43
  Parser.new.parse_file(path)
38
44
  end
39
45
 
46
+ sig { params(paths: T::Array[String]).returns(T::Array[Tree]) }
47
+ def self.parse_files(paths)
48
+ parser = Parser.new
49
+ paths.map { |path| parser.parse_file(path) }
50
+ end
51
+
40
52
  sig { params(string: String).returns(Tree) }
41
53
  def parse_string(string)
42
54
  parse(string, file: "-")
43
55
  end
44
56
 
57
+ sig { params(strings: T::Array[String]).returns(T::Array[Tree]) }
58
+ def self.parse_strings(strings)
59
+ parser = Parser.new
60
+ strings.map { |string| parser.parse_string(string) }
61
+ end
62
+
45
63
  sig { params(path: String).returns(Tree) }
46
64
  def parse_file(path)
47
65
  parse(::File.read(path), file: path)
@@ -53,10 +71,9 @@ module RBI
53
71
  def parse(content, file:)
54
72
  node, comments = Unparser.parse_with_comments(content)
55
73
  assoc = ::Parser::Source::Comment.associate_locations(node, comments)
56
- builder = TreeBuilder.new(file: file, comments: assoc)
57
- builder.separate_header_comments
74
+ builder = TreeBuilder.new(file: file, comments: comments, nodes_comments_assoc: assoc)
58
75
  builder.visit(node)
59
- builder.assoc_dangling_comments(comments)
76
+ builder.post_process
60
77
  builder.tree
61
78
  rescue ::Parser::SyntaxError => e
62
79
  raise ParseError.new(e.message, Loc.from_ast_loc(file, e.diagnostic.location))
@@ -99,16 +116,26 @@ module RBI
99
116
  sig do
100
117
  params(
101
118
  file: String,
102
- comments: T::Hash[::Parser::Source::Map, T::Array[::Parser::Source::Comment]]
119
+ comments: T::Array[::Parser::Source::Comment],
120
+ nodes_comments_assoc: T::Hash[::Parser::Source::Map, T::Array[::Parser::Source::Comment]]
103
121
  ).void
104
122
  end
105
- def initialize(file:, comments: {})
123
+ def initialize(file:, comments: [], nodes_comments_assoc: {})
106
124
  super()
107
125
  @file = file
108
126
  @comments = comments
127
+ @nodes_comments_assoc = nodes_comments_assoc
109
128
  @tree = T.let(Tree.new, Tree)
110
129
  @scopes_stack = T.let([@tree], T::Array[Tree])
111
130
  @last_sigs = T.let([], T::Array[RBI::Sig])
131
+
132
+ separate_header_comments
133
+ end
134
+
135
+ sig { void }
136
+ def post_process
137
+ assoc_dangling_comments
138
+ set_root_tree_loc
112
139
  end
113
140
 
114
141
  sig { override.params(node: T.nilable(Object)).void }
@@ -140,46 +167,6 @@ module RBI
140
167
  end
141
168
  end
142
169
 
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
-
165
- sig { params(comments: T::Array[::Parser::Source::Comment]).void }
166
- def assoc_dangling_comments(comments)
167
- last_line = T.let(nil, T.nilable(Integer))
168
- (comments - @comments.values.flatten).each do |comment|
169
- comment_line = comment.location.last_line
170
- text = comment.text[1..-1].strip
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
-
178
- tree.comments << Comment.new(text, loc: loc)
179
- last_line = comment_line
180
- end
181
- end
182
-
183
170
  private
184
171
 
185
172
  sig { params(node: AST::Node).returns(Scope) }
@@ -326,10 +313,28 @@ module RBI
326
313
  name, type, default_value = parse_tstruct_prop(node)
327
314
  TStructConst.new(name, type, default: default_value, loc: loc, comments: comments)
328
315
  else
329
- raise ParseError.new("Unsupported send node with name `#{method_name}`", loc)
316
+ args = parse_send_args(node)
317
+ Send.new(method_name.to_s, args, loc: loc, comments: comments)
330
318
  end
331
319
  end
332
320
 
321
+ sig { params(node: AST::Node).returns(T::Array[Arg]) }
322
+ def parse_send_args(node)
323
+ args = T.let([], T::Array[Arg])
324
+ node.children[2..-1].each do |child|
325
+ if child.type == :kwargs
326
+ child.children.each do |pair|
327
+ keyword = pair.children.first.children.last.to_s
328
+ value = parse_expr(pair.children.last)
329
+ args << KwArg.new(keyword, value)
330
+ end
331
+ else
332
+ args << Arg.new(parse_expr(child))
333
+ end
334
+ end
335
+ args
336
+ end
337
+
333
338
  sig { params(node: AST::Node).returns(T.nilable(RBI::Node)) }
334
339
  def parse_block(node)
335
340
  name = node.children[0].children[1]
@@ -429,7 +434,7 @@ module RBI
429
434
 
430
435
  sig { params(node: AST::Node).returns(T::Array[Comment]) }
431
436
  def node_comments(node)
432
- comments = @comments[node.location]
437
+ comments = @nodes_comments_assoc[node.location]
433
438
  return [] unless comments
434
439
  comments.map do |comment|
435
440
  text = comment.text[1..-1].strip
@@ -449,6 +454,60 @@ module RBI
449
454
  @last_sigs.clear
450
455
  sigs
451
456
  end
457
+
458
+ sig { void }
459
+ def assoc_dangling_comments
460
+ last_line = T.let(nil, T.nilable(Integer))
461
+ (@comments - @nodes_comments_assoc.values.flatten).each do |comment|
462
+ comment_line = comment.location.last_line
463
+ text = comment.text[1..-1].strip
464
+ loc = Loc.from_ast_loc(@file, comment.location)
465
+
466
+ if last_line && comment_line > last_line + 1
467
+ # Preserve empty lines in file headers
468
+ tree.comments << BlankLine.new(loc: loc)
469
+ end
470
+
471
+ tree.comments << Comment.new(text, loc: loc)
472
+ last_line = comment_line
473
+ end
474
+ end
475
+
476
+ sig { void }
477
+ def separate_header_comments
478
+ return if @nodes_comments_assoc.empty?
479
+
480
+ keep = []
481
+ node = T.must(@nodes_comments_assoc.keys.first)
482
+ comments = T.must(@nodes_comments_assoc.values.first)
483
+
484
+ last_line = T.let(nil, T.nilable(Integer))
485
+ comments.reverse.each do |comment|
486
+ comment_line = comment.location.last_line
487
+
488
+ break if last_line && comment_line < last_line - 1 ||
489
+ !last_line && comment_line < node.first_line - 1
490
+
491
+ keep << comment
492
+ last_line = comment_line
493
+ end
494
+
495
+ @nodes_comments_assoc[node] = keep.reverse
496
+ end
497
+
498
+ sig { void }
499
+ def set_root_tree_loc
500
+ first_loc = tree.nodes.first&.loc
501
+ last_loc = tree.nodes.last&.loc
502
+
503
+ @tree.loc = Loc.new(
504
+ file: @file,
505
+ begin_line: first_loc&.begin_line || 0,
506
+ begin_column: first_loc&.begin_column || 0,
507
+ end_line: last_loc&.end_line || 0,
508
+ end_column: last_loc&.end_column || 0
509
+ )
510
+ end
452
511
  end
453
512
 
454
513
  class ConstBuilder < ASTVisitor
data/lib/rbi/printer.rb CHANGED
@@ -97,7 +97,7 @@ module RBI
97
97
  v.visit_all(comments)
98
98
  end
99
99
 
100
- unless root.empty?
100
+ unless root.empty? && root.comments.empty?
101
101
  v.printn if strictness || !comments.empty?
102
102
  v.visit(root)
103
103
  end
@@ -136,6 +136,15 @@ module RBI
136
136
  out.string
137
137
  end
138
138
 
139
+ sig { params(v: Printer).void }
140
+ def print_blank_line_before(v)
141
+ previous_node = v.previous_node
142
+ return unless previous_node
143
+ return if previous_node.is_a?(BlankLine)
144
+ return if previous_node.oneline? && oneline?
145
+ v.printn
146
+ end
147
+
139
148
  sig { returns(T::Boolean) }
140
149
  def oneline?
141
150
  true
@@ -156,14 +165,22 @@ module RBI
156
165
 
157
166
  sig { override.params(v: Printer).void }
158
167
  def accept_printer(v)
159
- text = self.text.strip
160
- v.printt("#")
161
- v.print(" #{text}") unless text.empty?
162
- v.printn
168
+ lines = text.lines
169
+
170
+ if lines.empty?
171
+ v.printl("#")
172
+ end
173
+
174
+ lines.each do |line|
175
+ text = line.strip
176
+ v.printt("#")
177
+ v.print(" #{text}") unless text.empty?
178
+ v.printn
179
+ end
163
180
  end
164
181
  end
165
182
 
166
- class EmptyComment
183
+ class BlankLine
167
184
  extend T::Sig
168
185
 
169
186
  sig { override.params(v: Printer).void }
@@ -193,8 +210,7 @@ module RBI
193
210
 
194
211
  sig { override.params(v: Printer).void }
195
212
  def accept_printer(v)
196
- previous_node = v.previous_node
197
- v.printn if previous_node && (!previous_node.oneline? || !oneline?)
213
+ print_blank_line_before(v)
198
214
 
199
215
  v.printl("# #{loc}") if loc && v.print_locs
200
216
  v.visit_all(comments)
@@ -287,8 +303,7 @@ module RBI
287
303
 
288
304
  sig { override.params(v: Printer).void }
289
305
  def accept_printer(v)
290
- previous_node = v.previous_node
291
- v.printn if previous_node && (!previous_node.oneline? || !oneline?)
306
+ print_blank_line_before(v)
292
307
 
293
308
  v.printl("# #{loc}") if loc && v.print_locs
294
309
  v.visit_all(comments)
@@ -301,8 +316,7 @@ module RBI
301
316
 
302
317
  sig { override.params(v: Printer).void }
303
318
  def accept_printer(v)
304
- previous_node = v.previous_node
305
- v.printn if previous_node && (!previous_node.oneline? || !oneline?)
319
+ print_blank_line_before(v)
306
320
 
307
321
  v.visit_all(comments)
308
322
  sigs.each { |sig| v.visit(sig) }
@@ -338,8 +352,7 @@ module RBI
338
352
 
339
353
  sig { override.params(v: Printer).void }
340
354
  def accept_printer(v)
341
- previous_node = v.previous_node
342
- v.printn if previous_node && (!previous_node.oneline? || !oneline?)
355
+ print_blank_line_before(v)
343
356
 
344
357
  v.visit_all(comments)
345
358
  v.visit_all(sigs)
@@ -366,13 +379,14 @@ module RBI
366
379
  v.printt
367
380
  v.visit(param)
368
381
  v.print(",") if pindex < params.size - 1
369
- param.comments.each_with_index do |comment, cindex|
382
+
383
+ param.comments_lines.each_with_index do |comment, cindex|
370
384
  if cindex > 0
371
385
  param.print_comment_leading_space(v, last: pindex == params.size - 1)
372
386
  else
373
387
  v.print(" ")
374
388
  end
375
- v.print("# #{comment.text.strip}")
389
+ v.print("# #{comment}")
376
390
  end
377
391
  v.printn
378
392
  end
@@ -410,6 +424,11 @@ module RBI
410
424
  v.print(" " * (name.size + 1))
411
425
  v.print(" ") unless last
412
426
  end
427
+
428
+ sig { returns(T::Array[String]) }
429
+ def comments_lines
430
+ comments.flat_map { |comment| comment.text.lines.map(&:strip) }
431
+ end
413
432
  end
414
433
 
415
434
  class OptParam
@@ -507,8 +526,7 @@ module RBI
507
526
 
508
527
  sig { override.params(v: Printer).void }
509
528
  def accept_printer(v)
510
- previous_node = v.previous_node
511
- v.printn if previous_node && (!previous_node.oneline? || !oneline?)
529
+ print_blank_line_before(v)
512
530
 
513
531
  v.printl("# #{loc}") if loc && v.print_locs
514
532
  v.visit_all(comments)
@@ -529,8 +547,7 @@ module RBI
529
547
 
530
548
  sig { override.params(v: Printer).void }
531
549
  def accept_printer(v)
532
- previous_node = v.previous_node
533
- v.printn if previous_node && (!previous_node.oneline? || !oneline?)
550
+ print_blank_line_before(v)
534
551
 
535
552
  v.printl("# #{loc}") if loc && v.print_locs
536
553
  v.visit_all(comments)
@@ -538,6 +555,47 @@ module RBI
538
555
  end
539
556
  end
540
557
 
558
+ class Send
559
+ extend T::Sig
560
+
561
+ sig { override.params(v: Printer).void }
562
+ def accept_printer(v)
563
+ print_blank_line_before(v)
564
+
565
+ v.printl("# #{loc}") if loc && v.print_locs
566
+ v.visit_all(comments)
567
+ v.printt(method)
568
+ unless args.empty?
569
+ v.print(" ")
570
+ args.each_with_index do |arg, index|
571
+ v.visit(arg)
572
+ v.print(", ") if index < args.size - 1
573
+ end
574
+ end
575
+ v.printn
576
+ end
577
+ end
578
+
579
+ class Arg
580
+ extend T::Sig
581
+
582
+ sig { override.params(v: Printer).void }
583
+ def accept_printer(v)
584
+ v.print(value)
585
+ end
586
+ end
587
+
588
+ class KwArg
589
+ extend T::Sig
590
+
591
+ sig { override.params(v: Printer).void }
592
+ def accept_printer(v)
593
+ v.print(keyword)
594
+ v.print(": ")
595
+ v.print(value)
596
+ end
597
+ end
598
+
541
599
  class Sig
542
600
  extend T::Sig
543
601
 
@@ -576,13 +634,13 @@ module RBI
576
634
  v.printt
577
635
  v.visit(param)
578
636
  v.print(",") if pindex < params.size - 1
579
- param.comments.each_with_index do |comment, cindex|
637
+ param.comments_lines.each_with_index do |comment, cindex|
580
638
  if cindex == 0
581
639
  v.print(" ")
582
640
  else
583
641
  param.print_comment_leading_space(v, last: pindex == params.size - 1)
584
642
  end
585
- v.print("# #{comment.text.strip}")
643
+ v.print("# #{comment}")
586
644
  end
587
645
  v.printn
588
646
  end
@@ -633,6 +691,11 @@ module RBI
633
691
  v.print(" " * (name.size + type.size + 3))
634
692
  v.print(" ") unless last
635
693
  end
694
+
695
+ sig { returns(T::Array[String]) }
696
+ def comments_lines
697
+ comments.flat_map { |comment| comment.text.lines.map(&:strip) }
698
+ end
636
699
  end
637
700
 
638
701
  class TStructField
@@ -640,8 +703,7 @@ module RBI
640
703
 
641
704
  sig { override.params(v: Printer).void }
642
705
  def accept_printer(v)
643
- previous_node = v.previous_node
644
- v.printn if previous_node && (!previous_node.oneline? || !oneline?)
706
+ print_blank_line_before(v)
645
707
 
646
708
  v.printl("# #{loc}") if loc && v.print_locs
647
709
  v.visit_all(comments)
@@ -680,8 +742,7 @@ module RBI
680
742
 
681
743
  sig { override.params(v: Printer).void }
682
744
  def accept_printer(v)
683
- previous_node = v.previous_node
684
- v.printn if previous_node && (!previous_node.oneline? || !oneline?)
745
+ print_blank_line_before(v)
685
746
 
686
747
  v.printl("# #{loc}") if loc && v.print_locs
687
748
  v.visit_all(comments)
@@ -694,8 +755,7 @@ module RBI
694
755
 
695
756
  sig { override.params(v: Printer).void }
696
757
  def accept_printer(v)
697
- previous_node = v.previous_node
698
- v.printn if previous_node && (!previous_node.oneline? || !oneline?)
758
+ print_blank_line_before(v)
699
759
 
700
760
  v.printl("# #{loc}") if loc && v.print_locs
701
761
  v.visit_all(comments)
@@ -0,0 +1,57 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RBI
5
+ module Rewriters
6
+ class Annotate < Visitor
7
+ extend T::Sig
8
+
9
+ sig { params(annotation: String, annotate_scopes: T::Boolean, annotate_properties: T::Boolean).void }
10
+ def initialize(annotation, annotate_scopes: false, annotate_properties: false)
11
+ super()
12
+ @annotation = annotation
13
+ @annotate_scopes = annotate_scopes
14
+ @annotate_properties = annotate_properties
15
+ end
16
+
17
+ sig { override.params(node: T.nilable(Node)).void }
18
+ def visit(node)
19
+ case node
20
+ when Scope
21
+ annotate_node(node) if @annotate_scopes || root?(node)
22
+ when Const, Attr, Method, TStructField, TypeMember
23
+ annotate_node(node) if @annotate_properties
24
+ end
25
+ visit_all(node.nodes) if node.is_a?(Tree)
26
+ end
27
+
28
+ private
29
+
30
+ sig { params(node: NodeWithComments).void }
31
+ def annotate_node(node)
32
+ return if node.annotations.one?(@annotation)
33
+ node.comments << Comment.new("@#{@annotation}")
34
+ end
35
+
36
+ sig { params(node: Node).returns(T::Boolean) }
37
+ def root?(node)
38
+ parent = node.parent_tree
39
+ parent.is_a?(Tree) && parent.parent_tree.nil?
40
+ end
41
+ end
42
+ end
43
+
44
+ class Tree
45
+ extend T::Sig
46
+
47
+ sig { params(annotation: String, annotate_scopes: T::Boolean, annotate_properties: T::Boolean).void }
48
+ def annotate!(annotation, annotate_scopes: false, annotate_properties: false)
49
+ visitor = Rewriters::Annotate.new(
50
+ annotation,
51
+ annotate_scopes: annotate_scopes,
52
+ annotate_properties: annotate_properties
53
+ )
54
+ visitor.visit(self)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RBI
5
+ module Rewriters
6
+ class Deannotate < Visitor
7
+ extend T::Sig
8
+
9
+ sig { params(annotation: String).void }
10
+ def initialize(annotation)
11
+ super()
12
+ @annotation = annotation
13
+ end
14
+
15
+ sig { override.params(node: T.nilable(Node)).void }
16
+ def visit(node)
17
+ case node
18
+ when Scope, Const, Attr, Method, TStructField, TypeMember
19
+ deannotate_node(node)
20
+ end
21
+ visit_all(node.nodes) if node.is_a?(Tree)
22
+ end
23
+
24
+ private
25
+
26
+ sig { params(node: NodeWithComments).void }
27
+ def deannotate_node(node)
28
+ return unless node.annotations.one?(@annotation)
29
+ node.comments.reject! do |comment|
30
+ comment.text == "@#{@annotation}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class Tree
37
+ extend T::Sig
38
+
39
+ sig { params(annotation: String).void }
40
+ def deannotate!(annotation)
41
+ visitor = Rewriters::Deannotate.new(annotation)
42
+ visitor.visit(self)
43
+ end
44
+ end
45
+ end
@@ -55,6 +55,8 @@ module RBI
55
55
  Group::Kind::TypeMembers
56
56
  when MixesInClassMethods
57
57
  Group::Kind::MixesInClassMethods
58
+ when Send
59
+ Group::Kind::Sends
58
60
  when TStructField
59
61
  Group::Kind::TStructFields
60
62
  when TEnumBlock
@@ -67,6 +69,8 @@ module RBI
67
69
  else
68
70
  Group::Kind::Methods
69
71
  end
72
+ when SingletonClass
73
+ Group::Kind::SingletonClasses
70
74
  when Scope, Const
71
75
  Group::Kind::Consts
72
76
  else
@@ -93,10 +97,12 @@ module RBI
93
97
  Helpers = new
94
98
  TypeMembers = new
95
99
  MixesInClassMethods = new
100
+ Sends = new
96
101
  TStructFields = new
97
102
  TEnums = new
98
103
  Inits = new
99
104
  Methods = new
105
+ SingletonClasses = new
100
106
  Consts = new
101
107
  end
102
108
  end
@@ -47,7 +47,7 @@ module RBI
47
47
  end
48
48
  end
49
49
 
50
- sig { params(left: Tree, right: Tree, left_name: String, right_name: String, keep: Keep).returns(Tree) }
50
+ sig { params(left: Tree, right: Tree, left_name: String, right_name: String, keep: Keep).returns(MergeTree) }
51
51
  def self.merge_trees(left, right, left_name: "left", right_name: "right", keep: Keep::NONE)
52
52
  left.nest_singleton_methods!
53
53
  right.nest_singleton_methods!
@@ -59,7 +59,7 @@ module RBI
59
59
  tree
60
60
  end
61
61
 
62
- sig { returns(Tree) }
62
+ sig { returns(MergeTree) }
63
63
  attr_reader :tree
64
64
 
65
65
  sig { params(left_name: String, right_name: String, keep: Keep).void }
@@ -67,15 +67,15 @@ module RBI
67
67
  @left_name = left_name
68
68
  @right_name = right_name
69
69
  @keep = keep
70
- @tree = T.let(Tree.new, Tree)
70
+ @tree = T.let(MergeTree.new, MergeTree)
71
71
  @scope_stack = T.let([@tree], T::Array[Tree])
72
72
  end
73
73
 
74
- sig { params(tree: Tree).returns(T::Array[Conflict]) }
74
+ sig { params(tree: Tree).void }
75
75
  def merge(tree)
76
76
  v = TreeMerger.new(@tree, left_name: @left_name, right_name: @right_name, keep: @keep)
77
77
  v.visit(tree)
78
- v.conflicts
78
+ @tree.conflicts.concat(v.conflicts)
79
79
  end
80
80
 
81
81
  # Used for logging / error displaying purpose
@@ -314,9 +314,31 @@ module RBI
314
314
  class Tree
315
315
  extend T::Sig
316
316
 
317
- sig { params(other: Tree).returns(Tree) }
318
- def merge(other)
319
- Rewriters::Merge.merge_trees(self, other)
317
+ sig { params(other: Tree, left_name: String, right_name: String, keep: Rewriters::Merge::Keep).returns(MergeTree) }
318
+ def merge(other, left_name: "left", right_name: "right", keep: Rewriters::Merge::Keep::NONE)
319
+ Rewriters::Merge.merge_trees(self, other, left_name: left_name, right_name: right_name, keep: keep)
320
+ end
321
+ end
322
+
323
+ # A tree that _might_ contain conflicts
324
+ class MergeTree < Tree
325
+ extend T::Sig
326
+
327
+ sig { returns(T::Array[Rewriters::Merge::Conflict]) }
328
+ attr_reader :conflicts
329
+
330
+ sig do
331
+ params(
332
+ loc: T.nilable(Loc),
333
+ comments: T::Array[Comment],
334
+ conflicts: T::Array[Rewriters::Merge::Conflict],
335
+ block: T.nilable(T.proc.params(node: Tree).void)
336
+ ).void
337
+ end
338
+ def initialize(loc: nil, comments: [], conflicts: [], &block)
339
+ super(loc: loc, comments: comments)
340
+ @conflicts = conflicts
341
+ block&.call(self)
320
342
  end
321
343
  end
322
344
 
@@ -490,6 +512,15 @@ module RBI
490
512
  end
491
513
  end
492
514
 
515
+ class Send
516
+ extend T::Sig
517
+
518
+ sig { override.params(other: Node).returns(T::Boolean) }
519
+ def compatible_with?(other)
520
+ other.is_a?(Send) && method == other.method && args == other.args
521
+ end
522
+ end
523
+
493
524
  class TStructField
494
525
  extend T::Sig
495
526
 
@@ -0,0 +1,125 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RBI
5
+ module Rewriters
6
+ # Remove all definitions existing in the index from the current tree
7
+ #
8
+ # Let's create an `Index` from two different `Tree`s:
9
+ # ~~~rb
10
+ # tree1 = Parse.parse_string(<<~RBI)
11
+ # class Foo
12
+ # def foo; end
13
+ # end
14
+ # RBI
15
+ #
16
+ # tree2 = Parse.parse_string(<<~RBI)
17
+ # FOO = 10
18
+ # RBI
19
+ #
20
+ # index = Index.index(tree1, tree2)
21
+ # ~~~
22
+ #
23
+ # We can use `RemoveKnownDefinitions` to remove the definitions found in the `index` from the `Tree` to clean:
24
+ # ~~~rb
25
+ # tree_to_clean = Parser.parse_string(<<~RBI)
26
+ # class Foo
27
+ # def foo; end
28
+ # def bar; end
29
+ # end
30
+ # FOO = 10
31
+ # BAR = 42
32
+ # RBI
33
+ #
34
+ # cleaned_tree, operations = RemoveKnownDefinitions.remove(tree_to_clean, index)
35
+ #
36
+ # assert_equal(<<~RBI, cleaned_tree)
37
+ # class Foo
38
+ # def bar; end
39
+ # end
40
+ # BAR = 42
41
+ # RBI
42
+ #
43
+ # assert_equal(<<~OPERATIONS, operations.join("\n"))
44
+ # Deleted ::Foo#foo at -:2:2-2-16 (duplicate from -:2:2-2:16)
45
+ # Deleted ::FOO at -:5:0-5:8 (duplicate from -:1:0-1:8)
46
+ # OPERATIONS
47
+ # ~~~
48
+ class RemoveKnownDefinitions < Visitor
49
+ extend T::Sig
50
+
51
+ sig do
52
+ params(
53
+ tree: RBI::Tree,
54
+ index: RBI::Index
55
+ ).returns([RBI::Tree, T::Array[Operation]])
56
+ end
57
+ def self.remove(tree, index)
58
+ v = RemoveKnownDefinitions.new(index)
59
+ v.visit(tree)
60
+ [tree, v.operations]
61
+ end
62
+
63
+ sig { returns(T::Array[Operation]) }
64
+ attr_reader :operations
65
+
66
+ sig { params(index: RBI::Index).void }
67
+ def initialize(index)
68
+ super()
69
+ @index = index
70
+ @operations = T.let([], T::Array[Operation])
71
+ end
72
+
73
+ sig { params(nodes: T::Array[RBI::Node]).void }
74
+ def visit_all(nodes)
75
+ nodes.dup.each { |node| visit(node) }
76
+ end
77
+
78
+ sig { override.params(node: T.nilable(RBI::Node)).void }
79
+ def visit(node)
80
+ return unless node
81
+
82
+ case node
83
+ when RBI::Scope
84
+ visit_all(node.nodes)
85
+ previous = previous_definition_for(node)
86
+ delete_node(node, previous) if previous && node.empty?
87
+ when RBI::Tree
88
+ visit_all(node.nodes)
89
+ when RBI::Indexable
90
+ previous = previous_definition_for(node)
91
+ delete_node(node, previous) if previous
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ sig { params(node: RBI::Indexable).returns(T.nilable(RBI::Node)) }
98
+ def previous_definition_for(node)
99
+ node.index_ids.each do |id|
100
+ previous = @index[id].first
101
+ return previous if previous
102
+ end
103
+ nil
104
+ end
105
+
106
+ sig { params(node: RBI::Node, previous: RBI::Node).void }
107
+ def delete_node(node, previous)
108
+ node.detach
109
+ @operations << Operation.new(deleted_node: node, duplicate_of: previous)
110
+ end
111
+
112
+ class Operation < T::Struct
113
+ extend T::Sig
114
+
115
+ const :deleted_node, RBI::Node
116
+ const :duplicate_of, RBI::Node
117
+
118
+ sig { returns(String) }
119
+ def to_s
120
+ "Deleted #{duplicate_of} at #{deleted_node.loc} (duplicate from #{duplicate_of.loc})"
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -12,10 +12,16 @@ module RBI
12
12
  visit_all(node.nodes)
13
13
  original_order = node.nodes.map.with_index.to_h
14
14
  node.nodes.sort! do |a, b|
15
+ # First we try to compare the nodes by their node rank (based on the node type)
15
16
  res = node_rank(a) <=> node_rank(b)
16
- res = node_name(a) <=> node_name(b) if res == 0
17
- res = (original_order[a] || 0) <=> (original_order[b] || 0) if res == 0
18
- res || 0
17
+ next res if res != 0 # we can sort the nodes by their rank, let's stop here
18
+
19
+ # Then, if the nodes ranks are the same (res == 0), we try to compare the nodes by their name
20
+ res = node_name(a) <=> node_name(b)
21
+ next res if res && res != 0 # we can sort the nodes by their name, let's stop here
22
+
23
+ # Finally, if the two nodes have the same rank and the same name or at least one node is anonymous then,
24
+ T.must(original_order[a]) <=> T.must(original_order[b]) # we keep the original order
19
25
  end
20
26
  end
21
27
 
@@ -29,19 +35,21 @@ module RBI
29
35
  when Helper then 20
30
36
  when TypeMember then 30
31
37
  when MixesInClassMethods then 40
32
- when TStructField then 50
33
- when TEnumBlock then 60
38
+ when Send then 50
39
+ when TStructField then 60
40
+ when TEnumBlock then 70
34
41
  when Method
35
42
  if node.name == "initialize"
36
- 71
43
+ 81
37
44
  elsif !node.is_singleton
38
- 72
45
+ 82
39
46
  else
40
- 73
47
+ 83
41
48
  end
42
- when Scope, Const then 80
49
+ when SingletonClass then 90
50
+ when Scope, Const then 100
43
51
  else
44
- 100
52
+ 110
45
53
  end
46
54
  end
47
55
 
@@ -52,11 +60,13 @@ module RBI
52
60
  when Group::Kind::Helpers then 1
53
61
  when Group::Kind::TypeMembers then 2
54
62
  when Group::Kind::MixesInClassMethods then 3
55
- when Group::Kind::TStructFields then 4
56
- when Group::Kind::TEnums then 5
57
- when Group::Kind::Inits then 6
58
- when Group::Kind::Methods then 7
59
- when Group::Kind::Consts then 8
63
+ when Group::Kind::Sends then 5
64
+ when Group::Kind::TStructFields then 6
65
+ when Group::Kind::TEnums then 7
66
+ when Group::Kind::Inits then 8
67
+ when Group::Kind::Methods then 9
68
+ when Group::Kind::SingletonClasses then 10
69
+ when Group::Kind::Consts then 11
60
70
  else
61
71
  T.absurd(kind)
62
72
  end
data/lib/rbi/version.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # typed: true
2
- # typed: false
3
2
  # frozen_string_literal: true
4
3
 
5
4
  module RBI
6
- VERSION = "0.0.4"
5
+ VERSION = "0.0.8"
7
6
  end
data/lib/rbi.rb CHANGED
@@ -13,10 +13,13 @@ require "rbi/model"
13
13
  require "rbi/visitor"
14
14
  require "rbi/index"
15
15
  require "rbi/rewriters/add_sig_templates"
16
+ require "rbi/rewriters/annotate"
17
+ require "rbi/rewriters/deannotate"
16
18
  require "rbi/rewriters/merge_trees"
17
19
  require "rbi/rewriters/nest_singleton_methods"
18
20
  require "rbi/rewriters/nest_non_public_methods"
19
21
  require "rbi/rewriters/group_nodes"
22
+ require "rbi/rewriters/remove_known_definitions"
20
23
  require "rbi/rewriters/sort_nodes"
21
24
  require "rbi/parser"
22
25
  require "rbi/printer"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre Terrasa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-08 00:00:00.000000000 Z
11
+ date: 2021-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ast
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 0.5.9204
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 0.5.9204
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: unparser
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -83,10 +83,13 @@ files:
83
83
  - lib/rbi/parser.rb
84
84
  - lib/rbi/printer.rb
85
85
  - lib/rbi/rewriters/add_sig_templates.rb
86
+ - lib/rbi/rewriters/annotate.rb
87
+ - lib/rbi/rewriters/deannotate.rb
86
88
  - lib/rbi/rewriters/group_nodes.rb
87
89
  - lib/rbi/rewriters/merge_trees.rb
88
90
  - lib/rbi/rewriters/nest_non_public_methods.rb
89
91
  - lib/rbi/rewriters/nest_singleton_methods.rb
92
+ - lib/rbi/rewriters/remove_known_definitions.rb
90
93
  - lib/rbi/rewriters/sort_nodes.rb
91
94
  - lib/rbi/version.rb
92
95
  - lib/rbi/visitor.rb