sord 0.8.0 → 0.9.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/.parlour +7 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +46 -0
- data/README.md +1 -0
- data/Rakefile +67 -11
- data/exe/sord +10 -35
- data/lib/sord/logging.rb +21 -30
- data/lib/sord/parlour_plugin.rb +63 -0
- data/lib/sord/rbi_generator.rb +116 -150
- data/lib/sord/resolver.rb +9 -2
- data/lib/sord/type_converter.rb +57 -19
- data/lib/sord/version.rb +1 -1
- data/lib/sord.rb +1 -0
- data/rbi/sord.rbi +96 -66
- data/sorbet/rbi/gems/parlour.rbi +214 -0
- data/sorbet/rbi/gems/rainbow.rbi +117 -0
- data/sorbet/rbi/gems/rspec-core.rbi +1 -1
- data/sorbet/rbi/gems/simplecov.rbi +3 -1
- data/sorbet/rbi/gems/sorbet-runtime.rbi +45 -22
- data/sorbet/rbi/gems/yard.rbi +3 -3
- data/sorbet/rbi/hidden-definitions/errors.txt +26 -94
- data/sorbet/rbi/hidden-definitions/hidden.rbi +16179 -6215
- data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +32 -4
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +14 -14
- data/sord.gemspec +1 -1
- metadata +16 -12
- data/sorbet/rbi/gems/colorize.rbi +0 -81
data/lib/sord/rbi_generator.rb
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
# typed: true
|
2
2
|
require 'yard'
|
3
3
|
require 'sord/type_converter'
|
4
|
-
require 'colorize'
|
5
4
|
require 'sord/logging'
|
5
|
+
require 'parlour'
|
6
|
+
require 'rainbow'
|
6
7
|
|
7
8
|
module Sord
|
8
9
|
# Converts the current working directory's YARD registry into an RBI file.
|
9
|
-
class RbiGenerator
|
10
|
-
# @return [Array<String>] The lines of the generated RBI file so far.
|
11
|
-
attr_reader :rbi_contents
|
12
|
-
|
10
|
+
class RbiGenerator
|
13
11
|
# @return [Integer] The number of objects this generator has processed so
|
14
12
|
# far.
|
15
13
|
def object_count
|
@@ -21,35 +19,35 @@ module Sord
|
|
21
19
|
# [message, item, line].
|
22
20
|
attr_reader :warnings
|
23
21
|
|
24
|
-
# @return [Boolean] A boolean indicating whether the next item is the first
|
25
|
-
# in its namespace. This is used to determine whether to insert a blank
|
26
|
-
# line before it or not.
|
27
|
-
attr_accessor :next_item_is_first_in_namespace
|
28
|
-
|
29
22
|
# Create a new RBI generator.
|
30
23
|
# @param [Hash] options
|
31
24
|
# @option options [Integer] break_params
|
32
25
|
# @option options [Boolean] replace_errors_with_untyped
|
26
|
+
# @option options [Boolean] replace_unresolved_with_untyped
|
33
27
|
# @option options [Boolean] comments
|
28
|
+
# @option options [Parlour::RbiGenerator] generator
|
29
|
+
# @option options [Parlour::RbiGenerator::Namespace] root
|
34
30
|
# @return [void]
|
35
31
|
def initialize(options)
|
36
|
-
@
|
32
|
+
@parlour = options[:parlour] || Parlour::RbiGenerator.new
|
33
|
+
@current_object = options[:root] || @parlour.root
|
34
|
+
|
37
35
|
@namespace_count = 0
|
38
36
|
@method_count = 0
|
39
|
-
@break_params = options[:break_params]
|
40
|
-
@replace_errors_with_untyped = options[:replace_errors_with_untyped]
|
41
37
|
@warnings = []
|
42
|
-
|
38
|
+
|
39
|
+
@replace_errors_with_untyped = options[:replace_errors_with_untyped]
|
40
|
+
@replace_unresolved_with_untyped = options[:replace_unresolved_with_untyped]
|
43
41
|
|
44
42
|
# Hook the logger so that messages are added as comments to the RBI file
|
45
|
-
Logging.add_hook do |type, msg, item
|
46
|
-
|
43
|
+
Logging.add_hook do |type, msg, item|
|
44
|
+
@current_object.add_comment_to_next_child("sord #{type} - #{msg}")
|
47
45
|
end if options[:comments]
|
48
46
|
|
49
47
|
# Hook the logger so that warnings are collected
|
50
|
-
Logging.add_hook do |type, msg, item
|
51
|
-
|
52
|
-
|
48
|
+
Logging.add_hook do |type, msg, item|
|
49
|
+
# TODO: is it possible to get line numbers here?
|
50
|
+
warnings << [msg, item, 0] if type == :warn
|
53
51
|
end
|
54
52
|
end
|
55
53
|
|
@@ -65,66 +63,41 @@ module Sord
|
|
65
63
|
@method_count += 1
|
66
64
|
end
|
67
65
|
|
68
|
-
# Adds a single blank line to the RBI file, unless this item is the first
|
69
|
-
# in its namespace.
|
70
|
-
# @return [void]
|
71
|
-
def add_blank
|
72
|
-
rbi_contents << '' unless next_item_is_first_in_namespace
|
73
|
-
self.next_item_is_first_in_namespace = false
|
74
|
-
end
|
75
|
-
|
76
66
|
# Given a YARD CodeObject, add lines defining its mixins (that is, extends
|
77
67
|
# and includes) to the current RBI file. Returns the number of mixins.
|
78
68
|
# @param [YARD::CodeObjects::Base] item
|
79
|
-
# @param [Integer] indent_level
|
80
69
|
# @return [Integer]
|
81
|
-
def add_mixins(item
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
extends.reverse_each do |this_extend|
|
86
|
-
rbi_contents << "#{' ' * (indent_level + 1)}extend #{this_extend.path}"
|
70
|
+
def add_mixins(item)
|
71
|
+
item.instance_mixins.reverse_each do |i|
|
72
|
+
@current_object.create_include(i.path.to_s)
|
87
73
|
end
|
88
|
-
|
89
|
-
|
74
|
+
item.class_mixins.reverse_each do |e|
|
75
|
+
@current_object.create_extend(e.path.to_s)
|
90
76
|
end
|
91
77
|
|
92
|
-
|
78
|
+
item.instance_mixins.length + item.class_mixins.length
|
93
79
|
end
|
94
80
|
|
95
|
-
# Given
|
96
|
-
#
|
97
|
-
# @param [Array<String>] params
|
98
|
-
# @param [String] returns
|
99
|
-
# @param [Integer] indent_level
|
81
|
+
# Given a YARD NamespaceObject, add lines defining constants.
|
82
|
+
# @param [YARD::CodeObjects::NamespaceObject] item
|
100
83
|
# @return [void]
|
101
|
-
def
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
rbi_contents << "#{' ' * (indent_level + 2)}params("
|
110
|
-
params.each.with_index do |param, i|
|
111
|
-
terminator = params.length - 1 == i ? '' : ','
|
112
|
-
rbi_contents << "#{' ' * (indent_level + 3)}#{param}#{terminator}"
|
113
|
-
end
|
114
|
-
rbi_contents << "#{' ' * (indent_level + 2)}).#{returns}"
|
115
|
-
rbi_contents << "#{' ' * (indent_level + 1)}end"
|
116
|
-
else
|
117
|
-
rbi_contents << "#{' ' * (indent_level + 1)}sig { params(#{params.join(', ')}).#{returns} }"
|
84
|
+
def add_constants(item)
|
85
|
+
item.constants.each do |constant|
|
86
|
+
# Take a constant (like "A::B::CONSTANT"), split it on each '::', and
|
87
|
+
# set the constant name to the last string in the array.
|
88
|
+
constant_name = constant.to_s.split('::').last
|
89
|
+
|
90
|
+
# Add the constant to the current object being generated.
|
91
|
+
@current_object.create_constant(constant_name, value: "T.let(#{constant.value}, T.untyped)")
|
118
92
|
end
|
119
93
|
end
|
120
94
|
|
121
95
|
# Given a YARD NamespaceObject, add lines defining its methods and their
|
122
96
|
# signatures to the current RBI file.
|
123
97
|
# @param [YARD::CodeObjects::NamespaceObject] item
|
124
|
-
# @param [Integer] indent_level
|
125
98
|
# @return [void]
|
126
|
-
def add_methods(item
|
127
|
-
item.meths.each do |meth|
|
99
|
+
def add_methods(item)
|
100
|
+
item.meths(inherited: false).each do |meth|
|
128
101
|
count_method
|
129
102
|
|
130
103
|
# If the method is an alias, skip it so we don't define it as a
|
@@ -133,39 +106,20 @@ module Sord
|
|
133
106
|
next
|
134
107
|
end
|
135
108
|
|
136
|
-
add_blank
|
137
|
-
|
138
|
-
parameter_list = meth.parameters.map do |name, default|
|
139
|
-
# Handle these three main cases:
|
140
|
-
# - def method(param) or def method(param:)
|
141
|
-
# - def method(param: 'default')
|
142
|
-
# - def method(param = 'default')
|
143
|
-
if default.nil?
|
144
|
-
"#{name}"
|
145
|
-
elsif !default.nil? && name.end_with?(':')
|
146
|
-
"#{name} #{default}"
|
147
|
-
else
|
148
|
-
"#{name} = #{default}"
|
149
|
-
end
|
150
|
-
end.join(", ")
|
151
|
-
|
152
109
|
# This is better than iterating over YARD's "@param" tags directly
|
153
110
|
# because it includes parameters without documentation
|
154
111
|
# (The gsubs allow for better splat-argument compatibility)
|
155
|
-
|
156
|
-
[name, meth.tags('param')
|
157
|
-
.find { |p| p.name&.gsub('*', '') == name.gsub('*', '') }]
|
112
|
+
parameter_names_and_defaults_to_tags = meth.parameters.map do |name, default|
|
113
|
+
[[name, default], meth.tags('param')
|
114
|
+
.find { |p| p.name&.gsub('*', '')&.gsub(':', '') == name.gsub('*', '').gsub(':', '') }]
|
158
115
|
end.to_h
|
159
116
|
|
160
|
-
|
161
|
-
name =
|
117
|
+
parameter_types = parameter_names_and_defaults_to_tags.map do |name_and_default, tag|
|
118
|
+
name = name_and_default.first
|
162
119
|
|
163
120
|
if tag
|
164
|
-
|
121
|
+
TypeConverter.yard_to_sorbet(tag.types, meth, @replace_errors_with_untyped, @replace_unresolved_with_untyped)
|
165
122
|
elsif name.start_with? '&'
|
166
|
-
# Cut the ampersand from the block parameter name
|
167
|
-
name = name.gsub('&', '')
|
168
|
-
|
169
123
|
# Find yieldparams and yieldreturn
|
170
124
|
yieldparams = meth.tags('yieldparam')
|
171
125
|
yieldreturn = meth.tag('yieldreturn')&.types
|
@@ -174,17 +128,17 @@ module Sord
|
|
174
128
|
|
175
129
|
# Create strings
|
176
130
|
params_string = yieldparams.map do |param|
|
177
|
-
"#{param.name.gsub('*', '')}: #{TypeConverter.yard_to_sorbet(param.types, meth,
|
131
|
+
"#{param.name.gsub('*', '')}: #{TypeConverter.yard_to_sorbet(param.types, meth, @replace_errors_with_untyped, @replace_unresolved_with_untyped)}" unless param.name.nil?
|
178
132
|
end.join(', ')
|
179
|
-
return_string = TypeConverter.yard_to_sorbet(yieldreturn, meth,
|
133
|
+
return_string = TypeConverter.yard_to_sorbet(yieldreturn, meth, @replace_errors_with_untyped, @replace_unresolved_with_untyped)
|
180
134
|
|
181
135
|
# Create proc types, if possible
|
182
136
|
if yieldparams.empty? && yieldreturn.nil?
|
183
|
-
|
137
|
+
'T.untyped'
|
184
138
|
elsif yieldreturn.nil?
|
185
|
-
"
|
139
|
+
"T.proc#{params_string.empty? ? '' : ".params(#{params_string})"}.void"
|
186
140
|
else
|
187
|
-
"
|
141
|
+
"T.proc#{params_string.empty? ? '' : ".params(#{params_string})"}.returns(#{return_string})"
|
188
142
|
end
|
189
143
|
elsif meth.path.end_with? '='
|
190
144
|
# Look for the matching getter method
|
@@ -192,114 +146,126 @@ module Sord
|
|
192
146
|
getter = item.meths.find { |m| m.path == getter_path }
|
193
147
|
|
194
148
|
unless getter
|
195
|
-
if
|
149
|
+
if parameter_names_and_defaults_to_tags.length == 1 \
|
196
150
|
&& meth.tags('param').length == 1 \
|
197
151
|
&& meth.tag('param').types
|
198
152
|
|
199
|
-
Logging.infer("argument name in single @param inferred as #{
|
200
|
-
next
|
153
|
+
Logging.infer("argument name in single @param inferred as #{parameter_names_and_defaults_to_tags.first.first.first.inspect}", meth)
|
154
|
+
next TypeConverter.yard_to_sorbet(meth.tag('param').types, meth, @replace_errors_with_untyped, @replace_unresolved_with_untyped)
|
201
155
|
else
|
202
|
-
Logging.omit("no YARD type given for #{name.inspect}, using T.untyped", meth
|
203
|
-
next
|
156
|
+
Logging.omit("no YARD type given for #{name.inspect}, using T.untyped", meth)
|
157
|
+
next 'T.untyped'
|
204
158
|
end
|
205
159
|
end
|
206
160
|
|
207
161
|
inferred_type = TypeConverter.yard_to_sorbet(
|
208
|
-
getter.tags('return').flat_map(&:types), meth,
|
162
|
+
getter.tags('return').flat_map(&:types), meth, @replace_errors_with_untyped, @replace_unresolved_with_untyped)
|
209
163
|
|
210
|
-
Logging.infer("inferred type of parameter #{name.inspect} as #{inferred_type} using getter's return type", meth
|
211
|
-
|
212
|
-
name = name.chop if name.end_with?(':')
|
213
|
-
"#{name}: #{inferred_type}"
|
164
|
+
Logging.infer("inferred type of parameter #{name.inspect} as #{inferred_type} using getter's return type", meth)
|
165
|
+
inferred_type
|
214
166
|
else
|
215
167
|
# Is this the only argument, and was a @param specified without an
|
216
168
|
# argument name? If so, infer it
|
217
|
-
if
|
169
|
+
if parameter_names_and_defaults_to_tags.length == 1 \
|
218
170
|
&& meth.tags('param').length == 1 \
|
219
171
|
&& meth.tag('param').types
|
220
172
|
|
221
|
-
Logging.infer("argument name in single @param inferred as #{
|
222
|
-
|
173
|
+
Logging.infer("argument name in single @param inferred as #{parameter_names_and_defaults_to_tags.first.first.first.inspect}", meth)
|
174
|
+
TypeConverter.yard_to_sorbet(meth.tag('param').types, meth, @replace_errors_with_untyped, @replace_unresolved_with_untyped)
|
223
175
|
else
|
224
|
-
Logging.omit("no YARD type given for #{name.inspect}, using T.untyped", meth
|
225
|
-
|
226
|
-
name = name.chop if name.end_with?(':')
|
227
|
-
"#{name}: T.untyped"
|
176
|
+
Logging.omit("no YARD type given for #{name.inspect}, using T.untyped", meth)
|
177
|
+
'T.untyped'
|
228
178
|
end
|
229
179
|
end
|
230
180
|
end
|
231
181
|
|
232
182
|
return_tags = meth.tags('return')
|
233
183
|
returns = if return_tags.length == 0
|
234
|
-
Logging.omit("no YARD return type given, using T.untyped", meth
|
235
|
-
|
184
|
+
Logging.omit("no YARD return type given, using T.untyped", meth)
|
185
|
+
'T.untyped'
|
236
186
|
elsif return_tags.length == 1 && return_tags&.first&.types&.first&.downcase == "void"
|
237
|
-
|
187
|
+
nil
|
238
188
|
else
|
239
|
-
|
189
|
+
TypeConverter.yard_to_sorbet(meth.tag('return').types, meth, @replace_errors_with_untyped, @replace_unresolved_with_untyped)
|
240
190
|
end
|
241
191
|
|
242
|
-
|
243
|
-
|
244
|
-
|
192
|
+
parlour_params = parameter_names_and_defaults_to_tags
|
193
|
+
.zip(parameter_types)
|
194
|
+
.map do |((name, default), _), type|
|
195
|
+
# If the default is "nil" but the type is not nilable, then it
|
196
|
+
# should become nilable
|
197
|
+
# (T.untyped can include nil, so don't alter that)
|
198
|
+
type = "T.nilable(#{type})" \
|
199
|
+
if default == 'nil' && !type.start_with?('T.nilable') && type != 'T.untyped'
|
200
|
+
Parlour::RbiGenerator::Parameter.new(
|
201
|
+
name.to_s,
|
202
|
+
type: type,
|
203
|
+
default: default
|
204
|
+
)
|
205
|
+
end
|
245
206
|
|
246
|
-
|
207
|
+
@current_object.create_method(
|
208
|
+
meth.name.to_s,
|
209
|
+
parameters: parlour_params,
|
210
|
+
returns: returns,
|
211
|
+
class_method: meth.scope == :class
|
212
|
+
)
|
247
213
|
end
|
248
214
|
end
|
249
215
|
|
250
216
|
# Given a YARD NamespaceObject, add lines defining its mixins, methods
|
251
217
|
# and children to the RBI file.
|
252
218
|
# @param [YARD::CodeObjects::NamespaceObject] item
|
253
|
-
# @param [Integer] indent_level
|
254
219
|
# @return [void]
|
255
|
-
def add_namespace(item
|
220
|
+
def add_namespace(item)
|
256
221
|
count_namespace
|
257
|
-
add_blank
|
258
222
|
|
259
|
-
|
260
|
-
|
261
|
-
else
|
262
|
-
rbi_contents << "#{' ' * indent_level}#{item.type} #{item.name}"
|
263
|
-
end
|
223
|
+
superclass = nil
|
224
|
+
superclass = item.superclass.path.to_s if item.type == :class && item.superclass.to_s != "Object"
|
264
225
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
add_methods(item, indent_level)
|
226
|
+
parent = @current_object
|
227
|
+
@current_object = item.type == :class \
|
228
|
+
? parent.create_class(item.name.to_s, superclass: superclass)
|
229
|
+
: parent.create_module(item.name.to_s)
|
270
230
|
|
271
|
-
item
|
272
|
-
|
231
|
+
add_mixins(item)
|
232
|
+
add_methods(item)
|
233
|
+
add_constants(item)
|
273
234
|
|
274
|
-
|
235
|
+
item.children.select { |x| [:class, :module].include?(x.type) }
|
236
|
+
.each { |child| add_namespace(child) }
|
275
237
|
|
276
|
-
|
238
|
+
@current_object = parent
|
277
239
|
end
|
278
240
|
|
279
|
-
#
|
280
|
-
#
|
281
|
-
# @return [
|
282
|
-
def
|
241
|
+
# Populates the RBI generator with the contents of the YARD registry. You
|
242
|
+
# must load the YARD registry first!
|
243
|
+
# @return [void]
|
244
|
+
def populate
|
283
245
|
# Generate top-level modules, which recurses to all modules
|
284
246
|
YARD::Registry.root.children
|
285
247
|
.select { |x| [:class, :module].include?(x.type) }
|
286
248
|
.each { |child| add_namespace(child) }
|
249
|
+
end
|
287
250
|
|
288
|
-
|
251
|
+
# Populates the RBI generator with the contents of the YARD registry, then
|
252
|
+
# uses the loaded Parlour::RbiGenerator to generate the RBI file. You must
|
253
|
+
# load the YARD registry first!
|
254
|
+
# @return [void]
|
255
|
+
def generate
|
256
|
+
populate
|
257
|
+
@parlour.rbi
|
289
258
|
end
|
290
259
|
|
291
|
-
#
|
292
|
-
#
|
293
|
-
# @param [String, nil] filename
|
260
|
+
# Loads the YARD registry, populates the RBI file, and prints any relevant
|
261
|
+
# final logs.
|
294
262
|
# @return [void]
|
295
|
-
def run
|
296
|
-
raise 'No filename specified' unless filename
|
297
|
-
|
263
|
+
def run
|
298
264
|
# Get YARD ready
|
299
265
|
YARD::Registry.load!
|
300
266
|
|
301
|
-
#
|
302
|
-
|
267
|
+
# Populate the RBI
|
268
|
+
populate
|
303
269
|
|
304
270
|
if object_count.zero?
|
305
271
|
Logging.warn("No objects processed.")
|
@@ -318,10 +284,10 @@ module Sord
|
|
318
284
|
else
|
319
285
|
Logging.warn("The types which caused them have been replaced with SORD_ERROR_ constants.")
|
320
286
|
end
|
321
|
-
Logging.warn("Please edit the file
|
287
|
+
Logging.warn("Please edit the file to fix these errors.")
|
322
288
|
Logging.warn("Alternatively, edit your YARD documentation so that your types are valid and re-run Sord.")
|
323
|
-
warnings.each do |(msg, item,
|
324
|
-
puts " #{
|
289
|
+
warnings.each do |(msg, item, _)|
|
290
|
+
puts " (#{Rainbow(item&.path).bold}) #{msg}"
|
325
291
|
end
|
326
292
|
end
|
327
293
|
rescue
|
data/lib/sord/resolver.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: false
|
1
2
|
require 'stringio'
|
2
3
|
|
3
4
|
module Sord
|
@@ -50,12 +51,18 @@ module Sord
|
|
50
51
|
# @param [Object] item
|
51
52
|
# @return [Boolean]
|
52
53
|
def self.resolvable?(name, item)
|
53
|
-
name_parts = name.split('::')
|
54
|
-
|
55
54
|
current_context = item
|
56
55
|
current_context = current_context.parent \
|
57
56
|
until current_context.is_a?(YARD::CodeObjects::NamespaceObject)
|
58
57
|
|
58
|
+
# If there is any matching object directly in the heirarchy, this is
|
59
|
+
# always true. Ruby can do the resolution.
|
60
|
+
unless name.include?('::')
|
61
|
+
return true if current_context.path.split('::').include?(name)
|
62
|
+
end
|
63
|
+
|
64
|
+
name_parts = name.split('::')
|
65
|
+
|
59
66
|
matching_paths = []
|
60
67
|
|
61
68
|
loop do
|
data/lib/sord/type_converter.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: true
|
1
2
|
require 'yaml'
|
2
3
|
require 'sord/logging'
|
3
4
|
require 'sord/resolver'
|
@@ -94,11 +95,12 @@ module Sord
|
|
94
95
|
# @param [YARD::CodeObjects::Base] item The CodeObject which the YARD type
|
95
96
|
# is associated with. This is used for logging and can be nil, but this
|
96
97
|
# will lead to less informative log messages.
|
97
|
-
# @param [Integer] indent_level
|
98
98
|
# @param [Boolean] replace_errors_with_untyped If true, T.untyped is used
|
99
99
|
# instead of SORD_ERROR_ constants for unknown types.
|
100
|
+
# @param [Boolean] replace_unresolved_with_untyped If true, T.untyped is used
|
101
|
+
# when Sord is unable to resolve a constant.
|
100
102
|
# @return [String]
|
101
|
-
def self.yard_to_sorbet(yard, item = nil,
|
103
|
+
def self.yard_to_sorbet(yard, item = nil, replace_errors_with_untyped = false, replace_unresolved_with_untyped = false)
|
102
104
|
case yard
|
103
105
|
when nil # Type not specified
|
104
106
|
"T.untyped"
|
@@ -111,15 +113,20 @@ module Sord
|
|
111
113
|
# selection of any of the types
|
112
114
|
types = yard
|
113
115
|
.reject { |x| x == 'nil' }
|
114
|
-
.map { |x| yard_to_sorbet(x, item,
|
116
|
+
.map { |x| yard_to_sorbet(x, item, replace_errors_with_untyped, replace_unresolved_with_untyped) }
|
115
117
|
.uniq
|
116
118
|
result = types.length == 1 ? types.first : "T.any(#{types.join(', ')})"
|
117
119
|
result = "T.nilable(#{result})" if yard.include?('nil')
|
118
120
|
result
|
119
121
|
when /^#{SIMPLE_TYPE_REGEX}$/
|
122
|
+
if SORBET_SINGLE_ARG_GENERIC_TYPES.include?(yard)
|
123
|
+
return "T::#{yard}[T.untyped]"
|
124
|
+
elsif yard == "Hash"
|
125
|
+
return "T::Hash[T.untyped, T.untyped]"
|
126
|
+
end
|
120
127
|
# If this doesn't begin with an uppercase letter, warn
|
121
128
|
if /^[_a-z]/ === yard
|
122
|
-
Logging.warn("#{yard} is probably not a type, but using anyway", item
|
129
|
+
Logging.warn("#{yard} is probably not a type, but using anyway", item)
|
123
130
|
end
|
124
131
|
|
125
132
|
# Check if whatever has been specified is actually resolvable; if not,
|
@@ -127,18 +134,23 @@ module Sord
|
|
127
134
|
if item && !Resolver.resolvable?(yard, item)
|
128
135
|
if Resolver.path_for(yard)
|
129
136
|
new_path = Resolver.path_for(yard)
|
130
|
-
Logging.infer("#{yard} was resolved to #{new_path}", item
|
137
|
+
Logging.infer("#{yard} was resolved to #{new_path}", item) \
|
131
138
|
unless yard == new_path
|
132
139
|
new_path
|
133
140
|
else
|
134
|
-
|
135
|
-
|
141
|
+
if replace_unresolved_with_untyped
|
142
|
+
Logging.warn("#{yard} wasn't able to be resolved to a constant in this project, replaced with T.untyped", item)
|
143
|
+
'T.untyped'
|
144
|
+
else
|
145
|
+
Logging.warn("#{yard} wasn't able to be resolved to a constant in this project", item)
|
146
|
+
yard
|
147
|
+
end
|
136
148
|
end
|
137
149
|
else
|
138
150
|
yard
|
139
151
|
end
|
140
152
|
when DUCK_TYPE_REGEX
|
141
|
-
Logging.duck("#{yard} looks like a duck type, replacing with T.untyped", item
|
153
|
+
Logging.duck("#{yard} looks like a duck type, replacing with T.untyped", item)
|
142
154
|
'T.untyped'
|
143
155
|
when /^#{GENERIC_TYPE_REGEX}$/
|
144
156
|
generic_type = $1
|
@@ -146,46 +158,72 @@ module Sord
|
|
146
158
|
|
147
159
|
if SORBET_SUPPORTED_GENERIC_TYPES.include?(generic_type)
|
148
160
|
parameters = split_type_parameters(type_parameters)
|
149
|
-
.map { |x| yard_to_sorbet(x, item,
|
161
|
+
.map { |x| yard_to_sorbet(x, item, replace_errors_with_untyped, replace_unresolved_with_untyped) }
|
150
162
|
if SORBET_SINGLE_ARG_GENERIC_TYPES.include?(generic_type) && parameters.length > 1
|
151
163
|
"T::#{generic_type}[T.any(#{parameters.join(', ')})]"
|
152
164
|
elsif generic_type == 'Class' && parameters.length == 1
|
153
165
|
"T.class_of(#{parameters.first})"
|
166
|
+
elsif generic_type == 'Hash'
|
167
|
+
if parameters.length == 2
|
168
|
+
"T::Hash[#{parameters.join(', ')}]"
|
169
|
+
else
|
170
|
+
handle_sord_error(parameters.join, "Invalid hash, must have exactly two types: #{yard.inspect}.", item, replace_errors_with_untyped)
|
171
|
+
end
|
154
172
|
else
|
155
173
|
"T::#{generic_type}[#{parameters.join(', ')}]"
|
156
174
|
end
|
157
175
|
else
|
158
|
-
|
159
|
-
|
176
|
+
return handle_sord_error(
|
177
|
+
generic_type,
|
178
|
+
"unsupported generic type #{generic_type.inspect} in #{yard.inspect}",
|
179
|
+
item,
|
180
|
+
replace_errors_with_untyped
|
181
|
+
)
|
160
182
|
end
|
161
183
|
# Converts ordered lists like Array(Symbol, String) or (Symbol, String)
|
162
184
|
# into Sorbet Tuples like [Symbol, String].
|
163
185
|
when ORDERED_LIST_REGEX
|
164
186
|
type_parameters = $1
|
165
187
|
parameters = split_type_parameters(type_parameters)
|
166
|
-
.map { |x| yard_to_sorbet(x, item,
|
188
|
+
.map { |x| yard_to_sorbet(x, item, replace_errors_with_untyped, replace_unresolved_with_untyped) }
|
167
189
|
"[#{parameters.join(', ')}]"
|
168
190
|
when SHORTHAND_HASH_SYNTAX
|
169
191
|
type_parameters = $1
|
170
192
|
parameters = split_type_parameters(type_parameters)
|
171
|
-
.map { |x| yard_to_sorbet(x, item,
|
172
|
-
|
193
|
+
.map { |x| yard_to_sorbet(x, item, replace_errors_with_untyped, replace_unresolved_with_untyped) }
|
194
|
+
# Return a warning about an invalid hash when it has more or less than two elements.
|
195
|
+
if parameters.length == 2
|
196
|
+
"T::Hash[#{parameters.join(', ')}]"
|
197
|
+
else
|
198
|
+
handle_sord_error(parameters.join, "Invalid hash, must have exactly two types: #{yard.inspect}.", item, replace_errors_with_untyped)
|
199
|
+
end
|
173
200
|
when SHORTHAND_ARRAY_SYNTAX
|
174
201
|
type_parameters = $1
|
175
202
|
parameters = split_type_parameters(type_parameters)
|
176
|
-
.map { |x| yard_to_sorbet(x, item,
|
203
|
+
.map { |x| yard_to_sorbet(x, item, replace_errors_with_untyped, replace_unresolved_with_untyped) }
|
177
204
|
parameters.one? \
|
178
|
-
? "T::Array
|
179
|
-
: "T::Array
|
205
|
+
? "T::Array[#{parameters.first}]"
|
206
|
+
: "T::Array[T.any(#{parameters.join(', ')})]"
|
180
207
|
else
|
181
208
|
# Check for literals
|
182
209
|
from_yaml = YAML.load(yard) rescue nil
|
183
210
|
return from_yaml.class.to_s \
|
184
211
|
if [Symbol, Float, Integer].include?(from_yaml.class)
|
185
212
|
|
186
|
-
|
187
|
-
replace_errors_with_untyped ? "T.untyped" : "SORD_ERROR_#{yard.gsub(/[^0-9A-Za-z_]/i, '')}"
|
213
|
+
return handle_sord_error(yard.to_s, "#{yard.inspect} does not appear to be a type", item, replace_errors_with_untyped)
|
188
214
|
end
|
189
215
|
end
|
216
|
+
|
217
|
+
# Handles SORD_ERRORs.
|
218
|
+
#
|
219
|
+
# @param [String] name
|
220
|
+
# @param [String] log_warning
|
221
|
+
# @param [YARD::CodeObjects::Base] item
|
222
|
+
# @param [Boolean] replace_errors_with_untyped
|
223
|
+
# @return [String]
|
224
|
+
def self.handle_sord_error(name, log_warning, item, replace_errors_with_untyped)
|
225
|
+
Logging.warn(log_warning, item)
|
226
|
+
return replace_errors_with_untyped ? "T.untyped" : "SORD_ERROR_#{name.gsub(/[^0-9A-Za-z_]/i, '')}"
|
227
|
+
end
|
190
228
|
end
|
191
229
|
end
|
data/lib/sord/version.rb
CHANGED