rbi 0.0.4 → 0.0.8

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