spoom 1.7.16 → 1.8.0
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 +4 -4
- data/lib/spoom/deadcode/plugins/actionpack.rb +4 -2
- data/lib/spoom/deadcode/plugins/active_model.rb +44 -2
- data/lib/spoom/deadcode/plugins/active_record.rb +10 -7
- data/lib/spoom/deadcode/plugins/base.rb +4 -3
- data/lib/spoom/deadcode/plugins/graphql.rb +42 -5
- data/lib/spoom/deadcode/plugins/minitest.rb +3 -3
- data/lib/spoom/deadcode/plugins/ruby.rb +1 -1
- data/lib/spoom/ext/prism_types.rb +14 -0
- data/lib/spoom/model/builder.rb +3 -3
- data/lib/spoom/rbs.rb +20 -2
- data/lib/spoom/sorbet/metrics/code_metrics_visitor.rb +3 -3
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs/base_translator.rb +484 -0
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs/human_readable_translator.rb +72 -0
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs/line_matching_translator.rb +115 -0
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs/options.rb +78 -0
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb +35 -419
- data/lib/spoom/sorbet/translate/sorbet_sigs_to_rbs_comments.rb +2 -2
- data/lib/spoom/sorbet/translate/validator.rb +214 -0
- data/lib/spoom/sorbet/translate.rb +1 -0
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +2 -0
- data/rbi/spoom.rbi +306 -20
- metadata +8 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Spoom
|
|
5
|
+
module Sorbet
|
|
6
|
+
module Translate
|
|
7
|
+
module RBSCommentsToSorbetSigs
|
|
8
|
+
# @abstract
|
|
9
|
+
class BaseRBIFormat
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class HumanReadableRBIFormat < BaseRBIFormat
|
|
13
|
+
#: Integer?
|
|
14
|
+
attr_reader :max_line_length
|
|
15
|
+
|
|
16
|
+
#: (
|
|
17
|
+
#| ?max_line_length: Integer?,
|
|
18
|
+
#| ) -> void
|
|
19
|
+
def initialize(max_line_length: nil)
|
|
20
|
+
super()
|
|
21
|
+
@max_line_length = max_line_length
|
|
22
|
+
|
|
23
|
+
freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@default = new #: HumanReadableRBIFormat
|
|
27
|
+
class << self
|
|
28
|
+
#: HumanReadableRBIFormat
|
|
29
|
+
attr_reader :default
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class LineMatchedRBIFormat < BaseRBIFormat
|
|
34
|
+
@default = new #: LineMatchedRBIFormat
|
|
35
|
+
class << self
|
|
36
|
+
#: LineMatchedRBIFormat
|
|
37
|
+
attr_reader :default
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Options
|
|
42
|
+
#: Symbol
|
|
43
|
+
attr_reader :overloads_strategy
|
|
44
|
+
|
|
45
|
+
ALLOWED_OVERLOAD_STRATEGIES = [:translate_all, :translate_last, :raise].freeze
|
|
46
|
+
|
|
47
|
+
#: BaseRBIFormat
|
|
48
|
+
attr_reader :output_format
|
|
49
|
+
|
|
50
|
+
#: (
|
|
51
|
+
#| ?overloads_strategy: Symbol,
|
|
52
|
+
#| ?output_format: BaseRBIFormat,
|
|
53
|
+
#| ) -> void
|
|
54
|
+
def initialize(
|
|
55
|
+
overloads_strategy: :translate_all,
|
|
56
|
+
output_format: HumanReadableRBIFormat.default
|
|
57
|
+
)
|
|
58
|
+
unless ALLOWED_OVERLOAD_STRATEGIES.include?(overloads_strategy)
|
|
59
|
+
raise ArgumentError, "Unknown overloads_strategy: #{overloads_strategy.inspect}. " \
|
|
60
|
+
"Must be one of: #{ALLOWED_OVERLOAD_STRATEGIES.map(&:inspect).join(", ")}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
@overloads_strategy = overloads_strategy
|
|
64
|
+
@output_format = output_format
|
|
65
|
+
|
|
66
|
+
freeze
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@default = new #: Options
|
|
70
|
+
class << self
|
|
71
|
+
#: Options
|
|
72
|
+
attr_reader :default
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -4,438 +4,54 @@
|
|
|
4
4
|
module Spoom
|
|
5
5
|
module Sorbet
|
|
6
6
|
module Translate
|
|
7
|
-
|
|
8
|
-
include Spoom::RBS::ExtractRBSComments
|
|
9
|
-
|
|
10
|
-
RBS_ANNOTATION_MARKERS = [
|
|
11
|
-
"# @abstract",
|
|
12
|
-
"# @interface",
|
|
13
|
-
"# @sealed",
|
|
14
|
-
"# @final",
|
|
15
|
-
"# @requires_ancestor:",
|
|
16
|
-
"# @override",
|
|
17
|
-
"# @overridable",
|
|
18
|
-
"# @without_runtime",
|
|
19
|
-
].freeze #: Array[String]
|
|
20
|
-
RBS_REWRITE_PATTERN = Regexp.union(["#:", "#|", *RBS_ANNOTATION_MARKERS]).freeze #: Regexp
|
|
21
|
-
private_constant :RBS_ANNOTATION_MARKERS, :RBS_REWRITE_PATTERN
|
|
22
|
-
|
|
23
|
-
ALLOWED_OVERLOAD_STRATEGIES = [:translate_all, :translate_last, :raise].freeze #: Array[Symbol]
|
|
24
|
-
|
|
7
|
+
module RBSCommentsToSorbetSigs
|
|
25
8
|
class << self
|
|
9
|
+
RBS_ANNOTATION_MARKERS = [
|
|
10
|
+
"# @abstract",
|
|
11
|
+
"# @interface",
|
|
12
|
+
"# @sealed",
|
|
13
|
+
"# @final",
|
|
14
|
+
"# @requires_ancestor:",
|
|
15
|
+
"# @override",
|
|
16
|
+
"# @overridable",
|
|
17
|
+
"# @without_runtime",
|
|
18
|
+
].freeze #: Array[String]
|
|
19
|
+
RBS_REWRITE_PATTERN = Regexp.union(["#:", "#|", *RBS_ANNOTATION_MARKERS]).freeze #: Regexp
|
|
20
|
+
private_constant :RBS_ANNOTATION_MARKERS, :RBS_REWRITE_PATTERN
|
|
21
|
+
|
|
26
22
|
#: (String source) -> bool
|
|
27
23
|
def contains_rbs_syntax?(source)
|
|
28
24
|
Sigils.contains_valid_sigil?(source) && source.match?(RBS_REWRITE_PATTERN)
|
|
29
25
|
end
|
|
30
26
|
|
|
31
|
-
#: (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
super(ruby_contents, file: file)
|
|
42
|
-
|
|
43
|
-
unless ALLOWED_OVERLOAD_STRATEGIES.include?(overloads_strategy)
|
|
44
|
-
raise ArgumentError, "Unknown overloads_strategy: #{overloads_strategy.inspect}. " \
|
|
45
|
-
"Must be one of: #{ALLOWED_OVERLOAD_STRATEGIES.map(&:inspect).join(", ")}"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
@max_line_length = max_line_length
|
|
49
|
-
@overloads_strategy = overloads_strategy
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# @override
|
|
53
|
-
#: (Prism::ProgramNode node) -> void
|
|
54
|
-
def visit_program_node(node)
|
|
55
|
-
# Process all type aliases from the entire file first
|
|
56
|
-
apply_type_aliases(@comments)
|
|
57
|
-
|
|
58
|
-
# Now process the rest of the file with type aliases available
|
|
59
|
-
super
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# @override
|
|
63
|
-
#: (Prism::ClassNode node) -> void
|
|
64
|
-
def visit_class_node(node)
|
|
65
|
-
apply_class_annotations(node)
|
|
66
|
-
|
|
67
|
-
super
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# @override
|
|
71
|
-
#: (Prism::ModuleNode node) -> void
|
|
72
|
-
def visit_module_node(node)
|
|
73
|
-
apply_class_annotations(node)
|
|
74
|
-
|
|
75
|
-
super
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# @override
|
|
79
|
-
#: (Prism::SingletonClassNode node) -> void
|
|
80
|
-
def visit_singleton_class_node(node)
|
|
81
|
-
apply_class_annotations(node)
|
|
82
|
-
|
|
83
|
-
super
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# @override
|
|
87
|
-
#: (Prism::DefNode node) -> void
|
|
88
|
-
def visit_def_node(node)
|
|
89
|
-
rewrite_def(node, node_rbs_comments(node))
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# @override
|
|
93
|
-
#: (Prism::CallNode node) -> void
|
|
94
|
-
def visit_call_node(node)
|
|
95
|
-
case node.message
|
|
96
|
-
when "attr_reader", "attr_writer", "attr_accessor"
|
|
97
|
-
visit_attr(node)
|
|
98
|
-
else
|
|
99
|
-
def_node = node.arguments&.arguments&.first
|
|
100
|
-
if def_node&.is_a?(Prism::DefNode)
|
|
101
|
-
rewrite_def(def_node, node_rbs_comments(node))
|
|
102
|
-
return
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
super
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
private
|
|
110
|
-
|
|
111
|
-
#: (Prism::CallNode) -> void
|
|
112
|
-
def visit_attr(node)
|
|
113
|
-
comments = node_rbs_comments(node)
|
|
114
|
-
return if comments.empty?
|
|
115
|
-
|
|
116
|
-
return if comments.signatures.empty?
|
|
117
|
-
|
|
118
|
-
signatures = apply_overloads_strategy(
|
|
119
|
-
comments.signatures,
|
|
120
|
-
method_name: node.message.to_s,
|
|
121
|
-
location: "#{@file}:#{node.location.start_line}",
|
|
27
|
+
#: (
|
|
28
|
+
#| String ruby_contents,
|
|
29
|
+
#| file: String,
|
|
30
|
+
#| ?max_line_length: Integer?,
|
|
31
|
+
#| ?overloads_strategy: Symbol) -> String
|
|
32
|
+
def rewrite_if_needed(
|
|
33
|
+
ruby_contents,
|
|
34
|
+
file:,
|
|
35
|
+
max_line_length: nil,
|
|
36
|
+
overloads_strategy: :translate_all
|
|
122
37
|
)
|
|
38
|
+
return ruby_contents unless contains_rbs_syntax?(ruby_contents)
|
|
123
39
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if node.arguments&.arguments&.size != 1
|
|
130
|
-
raise Error, "AttrWriter must have exactly one name"
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
name = node.arguments&.arguments&.first #: as Prism::SymbolNode
|
|
134
|
-
sig.params << RBI::SigParam.new(
|
|
135
|
-
name.slice[1..-1], #: as String
|
|
136
|
-
RBI::RBS::TypeTranslator.translate(attr_type),
|
|
137
|
-
)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
sig.return_type = RBI::RBS::TypeTranslator.translate(attr_type)
|
|
141
|
-
|
|
142
|
-
apply_member_annotations(comments.method_annotations, sig)
|
|
143
|
-
|
|
144
|
-
@rewriter << Source::Replace.new(
|
|
145
|
-
signature.location.start_offset,
|
|
146
|
-
signature.location.end_offset,
|
|
147
|
-
sig.string(max_line_length: @max_line_length),
|
|
148
|
-
)
|
|
149
|
-
rescue ::RBS::ParsingError, ::RBI::Error
|
|
150
|
-
# Ignore signatures with errors
|
|
151
|
-
next
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
#: (Prism::DefNode, RBS::Comments) -> void
|
|
156
|
-
def rewrite_def(def_node, comments)
|
|
157
|
-
return if comments.empty?
|
|
158
|
-
return if comments.signatures.empty?
|
|
159
|
-
|
|
160
|
-
signatures = apply_overloads_strategy(
|
|
161
|
-
comments.signatures,
|
|
162
|
-
method_name: def_node.name.to_s,
|
|
163
|
-
location: "#{@file}:#{def_node.location.start_line}",
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
builder = RBI::Parser::TreeBuilder.new(@ruby_contents, comments: [], file: @file)
|
|
167
|
-
builder.visit(def_node)
|
|
168
|
-
rbi_node = builder.tree.nodes.first #: as RBI::Method
|
|
169
|
-
|
|
170
|
-
signatures.each do |signature|
|
|
171
|
-
begin
|
|
172
|
-
method_type = ::RBS::Parser.parse_method_type(signature.string)
|
|
173
|
-
rescue ::RBS::ParsingError
|
|
174
|
-
next
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
translator = RBI::RBS::MethodTypeTranslator.new(rbi_node)
|
|
178
|
-
|
|
179
|
-
begin
|
|
180
|
-
translator.visit(method_type)
|
|
181
|
-
rescue ::RBI::Error
|
|
182
|
-
next
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
sig = translator.result
|
|
186
|
-
|
|
187
|
-
apply_member_annotations(comments.method_annotations, sig)
|
|
188
|
-
|
|
189
|
-
# Sorbet runtime doesn't support `sig` on `method_added` or
|
|
190
|
-
# `singleton_method_added`, so we always use `without_runtime` for them.
|
|
191
|
-
if def_node.name == :method_added || def_node.name == :singleton_method_added
|
|
192
|
-
sig.without_runtime = true
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
@rewriter << Source::Replace.new(
|
|
196
|
-
signature.location.start_offset,
|
|
197
|
-
signature.location.end_offset,
|
|
198
|
-
sig.string(max_line_length: @max_line_length),
|
|
199
|
-
)
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
#: (Array[RBS::Signature], method_name: String, location: String) -> Array[RBS::Signature]
|
|
204
|
-
def apply_overloads_strategy(signatures, method_name:, location:)
|
|
205
|
-
return signatures if signatures.size <= 1
|
|
206
|
-
|
|
207
|
-
case @overloads_strategy
|
|
208
|
-
when :translate_all
|
|
209
|
-
signatures
|
|
210
|
-
when :translate_last
|
|
211
|
-
kept = signatures.last #: as RBS::Signature
|
|
212
|
-
others = signatures[0...-1] #: as !nil
|
|
213
|
-
|
|
214
|
-
# Delete all the signatures we didn't keep
|
|
215
|
-
others.each do |signature|
|
|
216
|
-
from = adjust_to_line_start(signature.location.start_offset)
|
|
217
|
-
to = adjust_to_line_end(signature.location.end_offset)
|
|
218
|
-
@rewriter << Source::Delete.new(from, to)
|
|
219
|
-
end
|
|
220
|
-
[kept]
|
|
221
|
-
else # :raise
|
|
222
|
-
raise Error, "Method `#{method_name}` at #{location} has multiple overloaded signatures"
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
#: (Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode) -> void
|
|
227
|
-
def apply_class_annotations(node)
|
|
228
|
-
comments = node_rbs_comments(node)
|
|
229
|
-
return if comments.empty?
|
|
230
|
-
|
|
231
|
-
indent = " " * (node.location.start_column + 2)
|
|
232
|
-
insert_pos = case node
|
|
233
|
-
when Prism::ClassNode
|
|
234
|
-
(node.superclass || node.constant_path).location.end_offset
|
|
235
|
-
when Prism::ModuleNode
|
|
236
|
-
node.constant_path.location.end_offset
|
|
237
|
-
when Prism::SingletonClassNode
|
|
238
|
-
node.expression.location.end_offset
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
class_annotations = comments.class_annotations
|
|
242
|
-
if class_annotations.any?
|
|
243
|
-
unless already_extends?(node, /^(::)?T::Helpers$/)
|
|
244
|
-
@rewriter << Source::Insert.new(insert_pos, "\n#{indent}extend T::Helpers\n")
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
class_annotations.reverse_each do |annotation|
|
|
248
|
-
from = adjust_to_line_start(annotation.location.start_offset)
|
|
249
|
-
to = adjust_to_line_end(annotation.location.end_offset)
|
|
250
|
-
|
|
251
|
-
content = case annotation.string
|
|
252
|
-
when "@abstract"
|
|
253
|
-
"abstract!"
|
|
254
|
-
when "@interface"
|
|
255
|
-
"interface!"
|
|
256
|
-
when "@sealed"
|
|
257
|
-
"sealed!"
|
|
258
|
-
when "@final"
|
|
259
|
-
"final!"
|
|
260
|
-
when /^@requires_ancestor: /
|
|
261
|
-
srb_type = ::RBS::Parser.parse_type(annotation.string.delete_prefix("@requires_ancestor: "))
|
|
262
|
-
rbs_type = RBI::RBS::TypeTranslator.translate(srb_type)
|
|
263
|
-
"requires_ancestor { #{rbs_type} }"
|
|
264
|
-
else
|
|
265
|
-
next
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
@rewriter << Source::Delete.new(from, to)
|
|
269
|
-
|
|
270
|
-
newline = node.body.nil? ? "" : "\n"
|
|
271
|
-
@rewriter << Source::Insert.new(insert_pos, "\n#{indent}#{content}#{newline}")
|
|
272
|
-
rescue ::RBS::ParsingError, ::RBI::Error
|
|
273
|
-
# Ignore annotations with errors
|
|
274
|
-
next
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
signatures = comments.signatures
|
|
279
|
-
if signatures.any?
|
|
280
|
-
signatures.each do |signature|
|
|
281
|
-
# Only type param signatures (e.g. `#: [A, B]`) are valid on class/module nodes
|
|
282
|
-
next unless signature.string.start_with?("[")
|
|
283
|
-
|
|
284
|
-
type_params = ::RBS::Parser.parse_type_params(signature.string)
|
|
285
|
-
next if type_params.empty?
|
|
286
|
-
|
|
287
|
-
from = adjust_to_line_start(signature.location.start_offset)
|
|
288
|
-
to = adjust_to_line_end(signature.location.end_offset)
|
|
289
|
-
@rewriter << Source::Delete.new(from, to)
|
|
290
|
-
|
|
291
|
-
unless already_extends?(node, /^(::)?T::Generic$/)
|
|
292
|
-
@rewriter << Source::Insert.new(insert_pos, "\n#{indent}extend T::Generic\n")
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
type_params.each do |type_param|
|
|
296
|
-
type_member = "#{type_param.name} = type_member"
|
|
297
|
-
|
|
298
|
-
case type_param.variance
|
|
299
|
-
when :covariant
|
|
300
|
-
type_member = "#{type_member}(:out)"
|
|
301
|
-
when :contravariant
|
|
302
|
-
type_member = "#{type_member}(:in)"
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
if type_param.upper_bound || type_param.default_type
|
|
306
|
-
if type_param.upper_bound
|
|
307
|
-
rbs_type = RBI::RBS::TypeTranslator.translate(type_param.upper_bound)
|
|
308
|
-
type_member = "#{type_member} {{ upper: #{rbs_type} }}"
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
if type_param.default_type
|
|
312
|
-
rbs_type = RBI::RBS::TypeTranslator.translate(type_param.default_type)
|
|
313
|
-
type_member = "#{type_member} {{ fixed: #{rbs_type} }}"
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
newline = node.body.nil? ? "" : "\n"
|
|
318
|
-
@rewriter << Source::Insert.new(insert_pos, "\n#{indent}#{type_member}#{newline}")
|
|
319
|
-
rescue ::RBS::ParsingError, ::RBI::Error
|
|
320
|
-
# Ignore signatures with errors
|
|
321
|
-
next
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
#: (Array[RBS::Annotation], RBI::Sig) -> void
|
|
328
|
-
def apply_member_annotations(annotations, sig)
|
|
329
|
-
annotations.each do |annotation|
|
|
330
|
-
case annotation.string
|
|
331
|
-
when "@abstract"
|
|
332
|
-
sig.is_abstract = true
|
|
333
|
-
when "@final"
|
|
334
|
-
sig.is_final = true
|
|
335
|
-
when "@override"
|
|
336
|
-
sig.is_override = true
|
|
337
|
-
when "@override(allow_incompatible: true)"
|
|
338
|
-
sig.is_override = true
|
|
339
|
-
sig.allow_incompatible_override = true
|
|
340
|
-
when "@override(allow_incompatible: :visibility)"
|
|
341
|
-
sig.is_override = true
|
|
342
|
-
sig.allow_incompatible_override_visibility = true
|
|
343
|
-
when "@overridable"
|
|
344
|
-
sig.is_overridable = true
|
|
345
|
-
when "@without_runtime"
|
|
346
|
-
sig.without_runtime = true
|
|
347
|
-
end
|
|
348
|
-
end
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
#: (Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode, Regexp) -> bool
|
|
352
|
-
def already_extends?(node, constant_regex)
|
|
353
|
-
node.child_nodes.any? do |c|
|
|
354
|
-
next false unless c.is_a?(Prism::CallNode)
|
|
355
|
-
next false unless c.message == "extend"
|
|
356
|
-
next false unless c.receiver.nil? || c.receiver.is_a?(Prism::SelfNode)
|
|
357
|
-
next false unless c.arguments&.arguments&.size == 1
|
|
358
|
-
|
|
359
|
-
arg = c.arguments&.arguments&.first
|
|
360
|
-
next false unless arg.is_a?(Prism::ConstantPathNode)
|
|
361
|
-
next false unless arg.slice.match?(constant_regex)
|
|
362
|
-
|
|
363
|
-
true
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
#: (Array[Prism::Comment]) -> Array[RBS::TypeAlias]
|
|
368
|
-
def collect_type_aliases(comments)
|
|
369
|
-
type_aliases = [] #: Array[RBS::TypeAlias]
|
|
370
|
-
|
|
371
|
-
return type_aliases if comments.empty?
|
|
372
|
-
|
|
373
|
-
continuation_comments = [] #: Array[Prism::Comment]
|
|
374
|
-
|
|
375
|
-
comments.reverse_each do |comment|
|
|
376
|
-
string = comment.slice
|
|
377
|
-
|
|
378
|
-
if string.start_with?("#:")
|
|
379
|
-
string = string.delete_prefix("#:").strip
|
|
380
|
-
location = comment.location
|
|
381
|
-
|
|
382
|
-
if string.start_with?("type ")
|
|
383
|
-
continuation_comments.reverse_each do |continuation_comment|
|
|
384
|
-
string = "#{string}#{continuation_comment.slice.delete_prefix("#|")}"
|
|
385
|
-
location = location.join(continuation_comment.location)
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
type_aliases << Spoom::RBS::TypeAlias.new(string, location)
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
# Clear the continuation comments regardless of whether we found a type alias or not
|
|
392
|
-
continuation_comments.clear
|
|
393
|
-
elsif string.start_with?("#|")
|
|
394
|
-
continuation_comments << comment
|
|
395
|
-
else
|
|
396
|
-
continuation_comments.clear
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
type_aliases
|
|
401
|
-
end
|
|
402
|
-
|
|
403
|
-
#: (Array[Prism::Comment]) -> void
|
|
404
|
-
def apply_type_aliases(comments)
|
|
405
|
-
type_aliases = collect_type_aliases(comments)
|
|
406
|
-
|
|
407
|
-
type_aliases.each do |type_alias|
|
|
408
|
-
indent = " " * type_alias.location.start_column
|
|
409
|
-
insert_pos = adjust_to_line_start(type_alias.location.start_offset)
|
|
410
|
-
|
|
411
|
-
from = insert_pos
|
|
412
|
-
to = adjust_to_line_end(type_alias.location.end_offset)
|
|
413
|
-
|
|
414
|
-
*, decls = ::RBS::Parser.parse_signature(type_alias.string)
|
|
415
|
-
|
|
416
|
-
# We only expect there to be a single type alias declaration
|
|
417
|
-
next unless decls.size == 1 && decls.first.is_a?(::RBS::AST::Declarations::TypeAlias)
|
|
418
|
-
|
|
419
|
-
rbs_type = decls.first
|
|
420
|
-
sorbet_type = RBI::RBS::TypeTranslator.translate(rbs_type.type)
|
|
421
|
-
|
|
422
|
-
alias_name = ::RBS::TypeName.new(
|
|
423
|
-
namespace: rbs_type.name.namespace,
|
|
424
|
-
name: rbs_type.name.name.to_s.gsub(/(?:^|_)([a-z\d]*)/i) do |match|
|
|
425
|
-
match = match.delete_prefix("_")
|
|
426
|
-
!match.empty? ? match[0].upcase.concat(match[1..-1]) : +""
|
|
427
|
-
end,
|
|
40
|
+
options = Options.new(
|
|
41
|
+
overloads_strategy:,
|
|
42
|
+
output_format: HumanReadableRBIFormat.new(
|
|
43
|
+
max_line_length:,
|
|
44
|
+
),
|
|
428
45
|
)
|
|
429
46
|
|
|
430
|
-
|
|
431
|
-
content = "#{indent}#{alias_name} = T.type_alias { #{sorbet_type.to_rbi} }\n"
|
|
432
|
-
@rewriter << Source::Insert.new(insert_pos, content)
|
|
433
|
-
rescue ::RBS::ParsingError, ::RBI::Error
|
|
434
|
-
# Ignore type aliases with errors
|
|
435
|
-
next
|
|
47
|
+
HumanReadableTranslator.new(ruby_contents, file:, options:).rewrite
|
|
436
48
|
end
|
|
437
49
|
end
|
|
438
50
|
end
|
|
439
51
|
end
|
|
440
52
|
end
|
|
441
53
|
end
|
|
54
|
+
|
|
55
|
+
require "spoom/sorbet/translate/rbs_comments_to_sorbet_sigs/options"
|
|
56
|
+
require "spoom/sorbet/translate/rbs_comments_to_sorbet_sigs/human_readable_translator"
|
|
57
|
+
require "spoom/sorbet/translate/rbs_comments_to_sorbet_sigs/line_matching_translator"
|
|
@@ -134,7 +134,7 @@ module Spoom
|
|
|
134
134
|
|
|
135
135
|
private
|
|
136
136
|
|
|
137
|
-
#: (
|
|
137
|
+
#: (PrismTypes::anyScopeNode) { -> void } -> void
|
|
138
138
|
def visit_scope(node, &block)
|
|
139
139
|
old_class_annotations = @class_annotations
|
|
140
140
|
@class_annotations = []
|
|
@@ -229,7 +229,7 @@ module Spoom
|
|
|
229
229
|
end
|
|
230
230
|
end
|
|
231
231
|
|
|
232
|
-
#: (
|
|
232
|
+
#: (PrismTypes::anyScopeNode, Prism::CallNode) -> void
|
|
233
233
|
def apply_class_annotation(parent, node)
|
|
234
234
|
unless node.message == "abstract!" || node.message == "interface!" || node.message == "sealed!" ||
|
|
235
235
|
node.message == "final!" || node.message == "requires_ancestor"
|