spoom 1.5.0 → 1.7.2

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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -0
  3. data/lib/spoom/backtrace_filter/minitest.rb +3 -4
  4. data/lib/spoom/cli/deadcode.rb +1 -2
  5. data/lib/spoom/cli/helper.rb +41 -31
  6. data/lib/spoom/cli/srb/assertions.rb +48 -0
  7. data/lib/spoom/cli/srb/bump.rb +1 -2
  8. data/lib/spoom/cli/srb/coverage.rb +1 -1
  9. data/lib/spoom/cli/srb/metrics.rb +68 -0
  10. data/lib/spoom/cli/srb/sigs.rb +209 -0
  11. data/lib/spoom/cli/srb/tc.rb +16 -1
  12. data/lib/spoom/cli/srb.rb +16 -4
  13. data/lib/spoom/cli.rb +1 -2
  14. data/lib/spoom/colors.rb +2 -6
  15. data/lib/spoom/context/bundle.rb +8 -9
  16. data/lib/spoom/context/exec.rb +3 -6
  17. data/lib/spoom/context/file_system.rb +12 -19
  18. data/lib/spoom/context/git.rb +14 -19
  19. data/lib/spoom/context/sorbet.rb +14 -27
  20. data/lib/spoom/context.rb +4 -8
  21. data/lib/spoom/counters.rb +22 -0
  22. data/lib/spoom/coverage/d3/base.rb +6 -8
  23. data/lib/spoom/coverage/d3/circle_map.rb +6 -16
  24. data/lib/spoom/coverage/d3/pie.rb +14 -19
  25. data/lib/spoom/coverage/d3/timeline.rb +46 -47
  26. data/lib/spoom/coverage/d3.rb +2 -4
  27. data/lib/spoom/coverage/report.rb +41 -79
  28. data/lib/spoom/coverage/snapshot.rb +8 -14
  29. data/lib/spoom/coverage.rb +3 -5
  30. data/lib/spoom/deadcode/definition.rb +12 -14
  31. data/lib/spoom/deadcode/erb.rb +10 -8
  32. data/lib/spoom/deadcode/index.rb +21 -25
  33. data/lib/spoom/deadcode/indexer.rb +5 -6
  34. data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -3
  35. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +2 -3
  36. data/lib/spoom/deadcode/plugins/actionpack.rb +19 -22
  37. data/lib/spoom/deadcode/plugins/active_model.rb +2 -3
  38. data/lib/spoom/deadcode/plugins/active_record.rb +62 -53
  39. data/lib/spoom/deadcode/plugins/active_support.rb +3 -2
  40. data/lib/spoom/deadcode/plugins/base.rb +29 -32
  41. data/lib/spoom/deadcode/plugins/graphql.rb +2 -3
  42. data/lib/spoom/deadcode/plugins/minitest.rb +4 -4
  43. data/lib/spoom/deadcode/plugins/namespaces.rb +5 -5
  44. data/lib/spoom/deadcode/plugins/rails.rb +5 -5
  45. data/lib/spoom/deadcode/plugins/rubocop.rb +5 -5
  46. data/lib/spoom/deadcode/plugins/ruby.rb +3 -4
  47. data/lib/spoom/deadcode/plugins/sorbet.rb +12 -6
  48. data/lib/spoom/deadcode/plugins/thor.rb +2 -3
  49. data/lib/spoom/deadcode/plugins.rb +23 -31
  50. data/lib/spoom/deadcode/remover.rb +58 -79
  51. data/lib/spoom/deadcode/send.rb +2 -8
  52. data/lib/spoom/file_collector.rb +11 -19
  53. data/lib/spoom/file_tree.rb +36 -51
  54. data/lib/spoom/location.rb +9 -20
  55. data/lib/spoom/model/builder.rb +54 -17
  56. data/lib/spoom/model/model.rb +71 -74
  57. data/lib/spoom/model/namespace_visitor.rb +4 -3
  58. data/lib/spoom/model/reference.rb +4 -8
  59. data/lib/spoom/model/references_visitor.rb +50 -30
  60. data/lib/spoom/parse.rb +4 -4
  61. data/lib/spoom/poset.rb +22 -24
  62. data/lib/spoom/printer.rb +10 -13
  63. data/lib/spoom/rbs.rb +77 -0
  64. data/lib/spoom/sorbet/config.rb +17 -24
  65. data/lib/spoom/sorbet/errors.rb +87 -45
  66. data/lib/spoom/sorbet/lsp/base.rb +10 -16
  67. data/lib/spoom/sorbet/lsp/errors.rb +8 -16
  68. data/lib/spoom/sorbet/lsp/structures.rb +65 -91
  69. data/lib/spoom/sorbet/lsp.rb +20 -22
  70. data/lib/spoom/sorbet/metrics/code_metrics_visitor.rb +236 -0
  71. data/lib/spoom/sorbet/metrics/metrics_file_parser.rb +34 -0
  72. data/lib/spoom/sorbet/metrics.rb +2 -32
  73. data/lib/spoom/sorbet/sigils.rb +16 -23
  74. data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb +242 -0
  75. data/lib/spoom/sorbet/translate/sorbet_assertions_to_rbs_comments.rb +123 -0
  76. data/lib/spoom/sorbet/translate/sorbet_sigs_to_rbs_comments.rb +293 -0
  77. data/lib/spoom/sorbet/translate/strip_sorbet_sigs.rb +23 -0
  78. data/lib/spoom/sorbet/translate/translator.rb +71 -0
  79. data/lib/spoom/sorbet/translate.rb +49 -0
  80. data/lib/spoom/sorbet.rb +6 -12
  81. data/lib/spoom/source/rewriter.rb +167 -0
  82. data/lib/spoom/source.rb +4 -0
  83. data/lib/spoom/timeline.rb +4 -6
  84. data/lib/spoom/version.rb +1 -1
  85. data/lib/spoom/visitor.rb +298 -151
  86. data/lib/spoom.rb +4 -3
  87. data/rbi/spoom.rbi +3567 -0
  88. metadata +62 -8
@@ -0,0 +1,123 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Sorbet
6
+ module Translate
7
+ # Translates Sorbet assertions to RBS comments.
8
+ class SorbetAssertionsToRBSComments < Translator
9
+ LINE_BREAK = "\n".ord #: Integer
10
+
11
+ # @override
12
+ #: (Prism::CallNode) -> void
13
+ def visit_call_node(node)
14
+ return super unless t_annotation?(node)
15
+ return super unless at_end_of_line?(node)
16
+
17
+ value = T.must(node.arguments&.arguments&.first)
18
+ rbs_annotation = build_rbs_annotation(node)
19
+
20
+ start_offset = node.location.start_offset
21
+ end_offset = node.location.end_offset
22
+ @rewriter << Source::Replace.new(start_offset, end_offset - 1, "#{dedent_value(node, value)} #{rbs_annotation}")
23
+ end
24
+
25
+ private
26
+
27
+ #: (Prism::CallNode) -> void
28
+ def build_rbs_annotation(call)
29
+ case call.name
30
+ when :let
31
+ srb_type = call.arguments&.arguments&.last #: as !nil
32
+ rbs_type = RBI::Type.parse_node(srb_type).rbs_string
33
+ "#: #{rbs_type}"
34
+ when :cast
35
+ srb_type = call.arguments&.arguments&.last #: as !nil
36
+ rbs_type = RBI::Type.parse_node(srb_type).rbs_string
37
+ "#: as #{rbs_type}"
38
+ when :must
39
+ "#: as !nil"
40
+ when :unsafe
41
+ "#: as untyped"
42
+ else
43
+ raise "Unknown annotation method: #{call.name}"
44
+ end
45
+ end
46
+
47
+ # Is this node a `T` or `::T` constant?
48
+ #: (Prism::Node?) -> bool
49
+ def t?(node)
50
+ case node
51
+ when Prism::ConstantReadNode
52
+ node.name == :T
53
+ when Prism::ConstantPathNode
54
+ node.parent.nil? && node.name == :T
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ # Is this node a `T.let` or `T.cast`?
61
+ #: (Prism::CallNode) -> bool
62
+ def t_annotation?(node)
63
+ return false unless t?(node.receiver)
64
+
65
+ case node.name
66
+ when :let, :cast
67
+ return node.arguments&.arguments&.size == 2
68
+ when :must, :unsafe
69
+ return node.arguments&.arguments&.size == 1
70
+ end
71
+
72
+ false
73
+ end
74
+
75
+ #: (Prism::Node) -> bool
76
+ def at_end_of_line?(node)
77
+ end_offset = node.location.end_offset
78
+ end_offset += 1 while (@ruby_bytes[end_offset] == " ".ord) && (end_offset < @ruby_bytes.size)
79
+ @ruby_bytes[end_offset] == LINE_BREAK
80
+ end
81
+
82
+ #: (Prism::Node, Prism::Node) -> String
83
+ def dedent_value(assign, value)
84
+ if value.location.start_line == assign.location.start_line
85
+ # The value is on the same line as the assign, so we can just return the slice as is:
86
+ # ```rb
87
+ # a = T.let(nil, T.nilable(String))
88
+ # ```
89
+ # becomes
90
+ # ```rb
91
+ # a = nil #: String?
92
+ # ```
93
+ return value.slice
94
+ end
95
+
96
+ # The value is on a different line, so we need to dedent it:
97
+ # ```rb
98
+ # a = T.let(
99
+ # [
100
+ # 1, 2, 3,
101
+ # ],
102
+ # T::Array[Integer],
103
+ # )
104
+ # ```
105
+ # becomes
106
+ # ```rb
107
+ # a = [
108
+ # 1, 2, 3,
109
+ # ] #: Array[Integer]
110
+ # ```
111
+ indent = value.location.start_line - assign.location.start_line
112
+ lines = value.slice.lines
113
+ if lines.size > 1
114
+ lines[1..]&.each_with_index do |line, i|
115
+ lines[i + 1] = line.delete_prefix(" " * indent)
116
+ end
117
+ end
118
+ lines.join
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,293 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Sorbet
6
+ module Translate
7
+ # Converts all `sig` nodes to RBS comments in the given Ruby code.
8
+ # It also handles type members and class annotations.
9
+ class SorbetSigsToRBSComments < Translator
10
+ #: (String, file: String, positional_names: bool) -> void
11
+ def initialize(ruby_contents, file:, positional_names:)
12
+ super(ruby_contents, file: file)
13
+
14
+ @positional_names = positional_names #: bool
15
+ @nesting = [] #: Array[Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode]
16
+ @last_sigs = [] #: Array[[Prism::CallNode, RBI::Sig]]
17
+ @type_members = [] #: Array[String]
18
+ end
19
+
20
+ # @override
21
+ #: (Prism::ClassNode) -> void
22
+ def visit_class_node(node)
23
+ visit_scope(node) { super }
24
+ end
25
+
26
+ # @override
27
+ #: (Prism::ModuleNode) -> void
28
+ def visit_module_node(node)
29
+ visit_scope(node) { super }
30
+ end
31
+
32
+ # @override
33
+ #: (Prism::SingletonClassNode) -> void
34
+ def visit_singleton_class_node(node)
35
+ visit_scope(node) { super }
36
+ end
37
+
38
+ # @override
39
+ #: (Prism::DefNode) -> void
40
+ def visit_def_node(node)
41
+ return if @last_sigs.empty?
42
+ return if @last_sigs.any? { |_, sig| sig.is_abstract }
43
+
44
+ apply_member_annotations(@last_sigs)
45
+
46
+ # Build the RBI::Method node so we can print the method signature as RBS.
47
+ builder = RBI::Parser::TreeBuilder.new(@ruby_contents, comments: [], file: @file)
48
+ builder.visit(node)
49
+ rbi_node = builder.tree.nodes.first #: as RBI::Method
50
+
51
+ @last_sigs.each do |node, sig|
52
+ out = StringIO.new
53
+ p = RBI::RBSPrinter.new(out: out, indent: node.location.start_column, positional_names: @positional_names)
54
+ p.print("#: ")
55
+ p.send(:print_method_sig, rbi_node, sig)
56
+ p.print("\n")
57
+ @rewriter << Source::Replace.new(node.location.start_offset, node.location.end_offset, out.string)
58
+ end
59
+
60
+ @last_sigs.clear
61
+ end
62
+
63
+ # @override
64
+ #: (Prism::CallNode) -> void
65
+ def visit_call_node(node)
66
+ case node.message
67
+ when "sig"
68
+ visit_sig(node)
69
+ when "attr_reader", "attr_writer", "attr_accessor"
70
+ visit_attr(node)
71
+ when "extend"
72
+ visit_extend(node)
73
+ when "abstract!", "interface!", "sealed!", "final!", "requires_ancestor"
74
+ visit_class_annotation(node)
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ # @override
81
+ #: (Prism::ConstantWriteNode) -> void
82
+ def visit_constant_write_node(node)
83
+ call = node.value
84
+ return super unless call.is_a?(Prism::CallNode)
85
+ return super unless call.message == "type_member"
86
+
87
+ @type_members << build_type_member_string(node)
88
+
89
+ from = adjust_to_line_start(node.location.start_offset)
90
+ to = adjust_to_line_end(node.location.end_offset)
91
+ to = adjust_to_new_line(to)
92
+
93
+ @rewriter << Source::Delete.new(from, to)
94
+ end
95
+
96
+ private
97
+
98
+ #: (Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode) { -> void } -> void
99
+ def visit_scope(node, &block)
100
+ @nesting << node
101
+ old_type_members = @type_members
102
+ @type_members = []
103
+
104
+ yield
105
+
106
+ if @type_members.any?
107
+ indent = " " * node.location.start_column
108
+ @rewriter << Source::Insert.new(node.location.start_offset, "#: [#{@type_members.join(", ")}]\n#{indent}")
109
+ end
110
+
111
+ @type_members = old_type_members
112
+ @nesting.pop
113
+ end
114
+
115
+ #: (Prism::CallNode) -> void
116
+ def visit_sig(node)
117
+ return unless sorbet_sig?(node)
118
+
119
+ builder = RBI::Parser::SigBuilder.new(@ruby_contents, file: @file)
120
+ builder.current.loc = node.location
121
+ builder.visit_call_node(node)
122
+ builder.current.comments = []
123
+
124
+ @last_sigs << [node, builder.current]
125
+ end
126
+
127
+ #: (Prism::CallNode) -> void
128
+ def visit_attr(node)
129
+ unless node.message == "attr_reader" || node.message == "attr_writer" || node.message == "attr_accessor"
130
+ raise Error, "Expected attr_reader, attr_writer, or attr_accessor"
131
+ end
132
+
133
+ return if @last_sigs.empty?
134
+ return if @last_sigs.any? { |_, sig| sig.is_abstract }
135
+
136
+ apply_member_annotations(@last_sigs)
137
+
138
+ builder = RBI::Parser::TreeBuilder.new(@ruby_contents, comments: [], file: @file)
139
+ builder.visit(node)
140
+ rbi_node = builder.tree.nodes.first #: as RBI::Attr
141
+
142
+ @last_sigs.each do |node, sig|
143
+ out = StringIO.new
144
+ p = RBI::RBSPrinter.new(out: out, indent: node.location.start_column, positional_names: @positional_names)
145
+ p.print("#: ")
146
+ p.print_attr_sig(rbi_node, sig)
147
+ p.print("\n")
148
+ @rewriter << Source::Replace.new(node.location.start_offset, node.location.end_offset, out.string)
149
+ end
150
+
151
+ @last_sigs.clear
152
+ end
153
+
154
+ #: (Prism::CallNode node) -> void
155
+ def visit_extend(node)
156
+ raise Error, "Expected extend" unless node.message == "extend"
157
+
158
+ return unless node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)
159
+ return unless node.arguments&.arguments&.size == 1
160
+
161
+ arg = node.arguments&.arguments&.first
162
+ return unless arg.is_a?(Prism::ConstantPathNode)
163
+ return unless arg.slice.match?(/^(::)?T::Helpers$/) || arg.slice.match?(/^(::)?T::Generic$/)
164
+
165
+ from = adjust_to_line_start(node.location.start_offset)
166
+ to = adjust_to_line_end(node.location.end_offset)
167
+ to = adjust_to_new_line(to)
168
+ @rewriter << Source::Delete.new(from, to)
169
+ end
170
+
171
+ #: (Prism::CallNode node) -> void
172
+ def visit_class_annotation(node)
173
+ unless node.message == "abstract!" || node.message == "interface!" || node.message == "sealed!" ||
174
+ node.message == "final!" || node.message == "requires_ancestor"
175
+ raise Error, "Expected abstract!, interface!, sealed!, final!, or requires_ancestor"
176
+ end
177
+
178
+ return unless node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)
179
+ return unless node.arguments.nil?
180
+
181
+ klass = @nesting.last #: as Prism::Node
182
+ indent = " " * klass.location.start_column
183
+
184
+ case node.message
185
+ when "abstract!"
186
+ @rewriter << Source::Insert.new(klass.location.start_offset, "# @abstract\n#{indent}")
187
+ when "interface!"
188
+ @rewriter << Source::Insert.new(klass.location.start_offset, "# @interface\n#{indent}")
189
+ when "sealed!"
190
+ @rewriter << Source::Insert.new(klass.location.start_offset, "# @sealed\n#{indent}")
191
+ when "final!"
192
+ @rewriter << Source::Insert.new(klass.location.start_offset, "# @final\n#{indent}")
193
+ when "requires_ancestor"
194
+ block = node.block
195
+ return unless block.is_a?(Prism::BlockNode)
196
+
197
+ body = block.body
198
+ return unless body.is_a?(Prism::StatementsNode)
199
+ return unless body.body.size == 1
200
+
201
+ arg = body.body.first #: as Prism::Node
202
+ srb_type = RBI::Type.parse_node(arg)
203
+ @rewriter << Source::Insert.new(klass.location.start_offset, "# @requires_ancestor: #{srb_type.rbs_string}\n#{indent}")
204
+ end
205
+
206
+ from = adjust_to_line_start(node.location.start_offset)
207
+ to = adjust_to_line_end(node.location.end_offset)
208
+ to = adjust_to_new_line(to)
209
+
210
+ @rewriter << Source::Delete.new(from, to)
211
+ end
212
+
213
+ #: (Array[[Prism::CallNode, RBI::Sig]]) -> void
214
+ def apply_member_annotations(sigs)
215
+ return if sigs.empty?
216
+
217
+ node, _sig = sigs.first #: as [Prism::CallNode, RBI::Sig]
218
+ insert_pos = node.location.start_offset
219
+
220
+ if sigs.any? { |_, sig| sig.without_runtime }
221
+ @rewriter << Source::Insert.new(insert_pos, "# @without_runtime\n")
222
+ end
223
+
224
+ if sigs.any? { |_, sig| sig.is_final }
225
+ @rewriter << Source::Insert.new(insert_pos, "# @final\n")
226
+ end
227
+
228
+ if sigs.any? { |_, sig| sig.is_abstract }
229
+ @rewriter << Source::Insert.new(insert_pos, "# @abstract\n")
230
+ end
231
+
232
+ if sigs.any? { |_, sig| sig.is_override }
233
+ @rewriter << if sigs.any? { |_, sig| sig.allow_incompatible_override }
234
+ Source::Insert.new(insert_pos, "# @override(allow_incompatible: true)\n")
235
+ else
236
+ Source::Insert.new(insert_pos, "# @override\n")
237
+ end
238
+ end
239
+
240
+ if sigs.any? { |_, sig| sig.is_overridable }
241
+ @rewriter << Source::Insert.new(insert_pos, "# @overridable\n")
242
+ end
243
+ end
244
+
245
+ #: (Prism::ConstantWriteNode) -> String
246
+ def build_type_member_string(node)
247
+ call = node.value
248
+ raise Error, "Expected a call node" unless call.is_a?(Prism::CallNode)
249
+ raise Error, "Expected type_member" unless call.message == "type_member"
250
+
251
+ type_member = node.name.to_s
252
+
253
+ arg = call.arguments&.arguments&.first
254
+ if arg.is_a?(Prism::SymbolNode)
255
+ case arg.slice
256
+ when ":in"
257
+ type_member = "in #{type_member}"
258
+ when ":out"
259
+ type_member = "out #{type_member}"
260
+ else
261
+ raise Error, "Unknown type member variance: #{arg.slice}"
262
+ end
263
+ end
264
+
265
+ block = call.block
266
+ return type_member unless block.is_a?(Prism::BlockNode)
267
+
268
+ body = block.body
269
+ return type_member unless body.is_a?(Prism::StatementsNode)
270
+ return type_member unless body.body.size == 1
271
+
272
+ hash = body.body.first
273
+ return type_member unless hash.is_a?(Prism::HashNode)
274
+
275
+ hash.elements.each do |element|
276
+ next unless element.is_a?(Prism::AssocNode)
277
+
278
+ type = RBI::Type.parse_node(element.value)
279
+
280
+ case element.key.slice
281
+ when "upper:"
282
+ type_member = "#{type_member} < #{type.rbs_string}"
283
+ when "fixed:"
284
+ type_member = "#{type_member} = #{type.rbs_string}"
285
+ end
286
+ end
287
+
288
+ type_member
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Sorbet
6
+ module Translate
7
+ # Deletes all `sig` nodes from the given Ruby code.
8
+ # It doesn't handle type members and class annotations.
9
+ class StripSorbetSigs < Translator
10
+ # @override
11
+ #: (Prism::CallNode node) -> void
12
+ def visit_call_node(node)
13
+ return unless sorbet_sig?(node)
14
+
15
+ @rewriter << Source::Delete.new(
16
+ adjust_to_line_start(node.location.start_offset),
17
+ adjust_to_line_end(node.location.end_offset),
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Sorbet
6
+ module Translate
7
+ # @abstract
8
+ class Translator < Spoom::Visitor
9
+ #: (String, file: String) -> void
10
+ def initialize(ruby_contents, file:)
11
+ super()
12
+
13
+ @file = file #: String
14
+
15
+ @original_encoding = ruby_contents.encoding #: Encoding
16
+ @ruby_contents = if @original_encoding == "UTF-8"
17
+ ruby_contents
18
+ else
19
+ ruby_contents.encode("UTF-8")
20
+ end #: String
21
+
22
+ node = Spoom.parse_ruby(ruby_contents, file: file, comments: true)
23
+ @node = node #: Prism::Node
24
+ @ruby_bytes = ruby_contents.bytes #: Array[Integer]
25
+ @rewriter = Spoom::Source::Rewriter.new #: Source::Rewriter
26
+ end
27
+
28
+ #: -> String
29
+ def rewrite
30
+ visit(@node)
31
+ @rewriter.rewrite!(@ruby_bytes)
32
+ @ruby_bytes.pack("C*").force_encoding(@original_encoding)
33
+ end
34
+
35
+ private
36
+
37
+ #: (Prism::CallNode node) -> bool
38
+ def sorbet_sig?(node)
39
+ return false unless node.message == "sig"
40
+
41
+ recv = node.receiver
42
+ return false if recv && !recv.is_a?(Prism::SelfNode) && !recv.slice.match?(/(::)?T::Sig::WithoutRuntime/)
43
+
44
+ true
45
+ end
46
+
47
+ #: (Integer) -> Integer
48
+ def adjust_to_line_start(offset)
49
+ offset -= 1 while offset > 0 && @ruby_bytes[offset - 1] != "\n".ord
50
+ offset
51
+ end
52
+
53
+ #: (Integer) -> Integer
54
+ def adjust_to_line_end(offset)
55
+ offset += 1 while offset < @ruby_bytes.size && @ruby_bytes[offset] != "\n".ord
56
+ offset
57
+ end
58
+
59
+ # Consume the next blank line if any
60
+ #: (Integer) -> Integer
61
+ def adjust_to_new_line(offset)
62
+ if offset + 1 < @ruby_bytes.size && @ruby_bytes[offset + 1] == "\n".ord
63
+ offset += 1
64
+ end
65
+
66
+ offset
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,49 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rbi"
5
+
6
+ require "spoom/source/rewriter"
7
+ require "spoom/sorbet/translate/translator"
8
+ require "spoom/sorbet/translate/rbs_comments_to_sorbet_sigs"
9
+ require "spoom/sorbet/translate/sorbet_assertions_to_rbs_comments"
10
+ require "spoom/sorbet/translate/sorbet_sigs_to_rbs_comments"
11
+ require "spoom/sorbet/translate/strip_sorbet_sigs"
12
+
13
+ module Spoom
14
+ module Sorbet
15
+ module Translate
16
+ class Error < Spoom::Error; end
17
+
18
+ class << self
19
+ # Deletes all `sig` nodes from the given Ruby code.
20
+ # It doesn't handle type members and class annotations.
21
+ #: (String ruby_contents, file: String) -> String
22
+ def strip_sorbet_sigs(ruby_contents, file:)
23
+ StripSorbetSigs.new(ruby_contents, file: file).rewrite
24
+ end
25
+
26
+ # Converts all `sig` nodes to RBS comments in the given Ruby code.
27
+ # It also handles type members and class annotations.
28
+ #: (String ruby_contents, file: String, ?positional_names: bool) -> String
29
+ def sorbet_sigs_to_rbs_comments(ruby_contents, file:, positional_names: true)
30
+ SorbetSigsToRBSComments.new(ruby_contents, file: file, positional_names: positional_names).rewrite
31
+ end
32
+
33
+ # Converts all the RBS comments in the given Ruby code to `sig` nodes.
34
+ # It also handles type members and class annotations.
35
+ #: (String ruby_contents, file: String) -> String
36
+ def rbs_comments_to_sorbet_sigs(ruby_contents, file:)
37
+ RBSCommentsToSorbetSigs.new(ruby_contents, file: file).rewrite
38
+ end
39
+
40
+ # Converts all `T.let` and `T.cast` nodes to RBS comments in the given Ruby code.
41
+ # It also handles type members and class annotations.
42
+ #: (String ruby_contents, file: String) -> String
43
+ def sorbet_assertions_to_rbs_comments(ruby_contents, file:)
44
+ SorbetAssertionsToRBSComments.new(ruby_contents, file: file).rewrite
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
data/lib/spoom/sorbet.rb CHANGED
@@ -6,26 +6,20 @@ require "spoom/sorbet/errors"
6
6
  require "spoom/sorbet/lsp"
7
7
  require "spoom/sorbet/metrics"
8
8
  require "spoom/sorbet/sigils"
9
+ require "spoom/sorbet/translate"
9
10
 
10
11
  require "open3"
11
12
 
12
13
  module Spoom
13
14
  module Sorbet
14
15
  class Error < Spoom::Error
15
- extend T::Sig
16
-
17
16
  class Killed < Error; end
18
17
  class Segfault < Error; end
19
18
 
20
- sig { returns(ExecResult) }
19
+ #: ExecResult
21
20
  attr_reader :result
22
21
 
23
- sig do
24
- params(
25
- message: String,
26
- result: ExecResult,
27
- ).void
28
- end
22
+ #: (String message, ExecResult result) -> void
29
23
  def initialize(message, result)
30
24
  super(message)
31
25
 
@@ -34,9 +28,9 @@ module Spoom
34
28
  end
35
29
 
36
30
  CONFIG_PATH = "sorbet/config"
37
- GEM_PATH = T.let(Gem::Specification.find_by_name("sorbet-static").full_gem_path, String)
38
- GEM_VERSION = T.let(Gem::Specification.find_by_name("sorbet-static-and-runtime").version.to_s, String)
39
- BIN_PATH = T.let((Pathname.new(GEM_PATH) / "libexec" / "sorbet").to_s, String)
31
+ GEM_PATH = Gem::Specification.find_by_name("sorbet-static").full_gem_path #: String
32
+ GEM_VERSION = Gem::Specification.find_by_name("sorbet-static-and-runtime").version.to_s #: String
33
+ BIN_PATH = (Pathname.new(GEM_PATH) / "libexec" / "sorbet").to_s #: String
40
34
 
41
35
  KILLED_CODE = 137
42
36
  SEGFAULT_CODE = 139