sorbet 0.5.5841
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 +7 -0
- data/bin/srb +150 -0
- data/bin/srb-rbi +237 -0
- data/lib/constant_cache.rb +226 -0
- data/lib/create-config.rb +37 -0
- data/lib/fetch-rbis.rb +129 -0
- data/lib/find-gem-rbis.rb +52 -0
- data/lib/gem-generator-tracepoint.rb +54 -0
- data/lib/gem-generator-tracepoint/tracepoint_serializer.rb +267 -0
- data/lib/gem-generator-tracepoint/tracer.rb +189 -0
- data/lib/gem_loader.rb +1249 -0
- data/lib/gem_loader.rbi +15 -0
- data/lib/hidden-definition-finder.rb +456 -0
- data/lib/real_stdlib.rb +84 -0
- data/lib/require_everything.rb +181 -0
- data/lib/serialize.rb +362 -0
- data/lib/status.rb +21 -0
- data/lib/step_interface.rb +11 -0
- data/lib/suggest-typed.rb +59 -0
- data/lib/t.rb +57 -0
- data/lib/todo-rbi.rb +48 -0
- metadata +133 -0
data/lib/gem_loader.rbi
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
# Write this as an RBI because the gem_loader.rb file itself cannot be typed.
|
4
|
+
# By nature, it references constants in other gems in an attempt to load them.
|
5
|
+
# They will not load in this project. It's marked as `typed: ignore`, and this
|
6
|
+
# file exists so that we can call into gem_loader from typed code.
|
7
|
+
|
8
|
+
class Sorbet::Private::GemLoader
|
9
|
+
sig {params(gem: String).void}
|
10
|
+
def self.require_gem(gem)
|
11
|
+
end
|
12
|
+
|
13
|
+
sig {void}
|
14
|
+
def self.require_all_gems; end
|
15
|
+
end
|
@@ -0,0 +1,456 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
# typed: true
|
4
|
+
|
5
|
+
class Sorbet; end
|
6
|
+
module Sorbet::Private; end
|
7
|
+
|
8
|
+
require_relative './t'
|
9
|
+
require_relative './step_interface'
|
10
|
+
require_relative './serialize'
|
11
|
+
require_relative './constant_cache'
|
12
|
+
require_relative './require_everything'
|
13
|
+
require_relative './real_stdlib'
|
14
|
+
|
15
|
+
require 'fileutils'
|
16
|
+
require 'json'
|
17
|
+
require 'set'
|
18
|
+
require 'tmpdir'
|
19
|
+
|
20
|
+
class Sorbet::Private::HiddenMethodFinder
|
21
|
+
PATH = "sorbet/rbi/hidden-definitions/"
|
22
|
+
TMP_PATH = Dir.mktmpdir + "/"
|
23
|
+
TMP_RBI = TMP_PATH + "reflection.rbi"
|
24
|
+
DIFF_RBI = TMP_PATH + "hidden.rbi.tmp"
|
25
|
+
RBI_CONSTANTS = TMP_PATH + "reflection.json"
|
26
|
+
RBI_CONSTANTS_ERR = RBI_CONSTANTS + ".err"
|
27
|
+
SOURCE_CONSTANTS = TMP_PATH + "from-source.json"
|
28
|
+
SOURCE_CONSTANTS_ERR = SOURCE_CONSTANTS + ".err"
|
29
|
+
|
30
|
+
HIDDEN_RBI = PATH + "hidden.rbi"
|
31
|
+
ERRORS_RBI = PATH + "errors.txt"
|
32
|
+
|
33
|
+
HEADER = Sorbet::Private::Serialize.header('autogenerated', 'hidden-definitions')
|
34
|
+
|
35
|
+
include Sorbet::Private::StepInterface
|
36
|
+
|
37
|
+
def self.main
|
38
|
+
self.new.main
|
39
|
+
end
|
40
|
+
|
41
|
+
def main
|
42
|
+
mk_dir
|
43
|
+
require_everything
|
44
|
+
classes, aliases = all_modules_and_aliases
|
45
|
+
gen_source_rbi(classes, aliases)
|
46
|
+
rm_rbis
|
47
|
+
write_constants
|
48
|
+
source, rbi = read_constants
|
49
|
+
write_diff(source, rbi)
|
50
|
+
split_rbi
|
51
|
+
rm_dir
|
52
|
+
end
|
53
|
+
|
54
|
+
def mk_dir
|
55
|
+
FileUtils.mkdir_p(PATH) unless Dir.exist?(PATH)
|
56
|
+
end
|
57
|
+
|
58
|
+
def rm_dir
|
59
|
+
FileUtils.rm_r(TMP_PATH)
|
60
|
+
end
|
61
|
+
|
62
|
+
def require_everything
|
63
|
+
puts "Requiring all of your code"
|
64
|
+
Sorbet::Private::RequireEverything.require_everything
|
65
|
+
end
|
66
|
+
|
67
|
+
def constant_cache
|
68
|
+
@cache ||= Sorbet::Private::ConstantLookupCache.new
|
69
|
+
@cache
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def all_modules_and_aliases
|
74
|
+
puts "Naming all Modules"
|
75
|
+
[constant_cache.all_module_names.sort, constant_cache.all_module_aliases]
|
76
|
+
end
|
77
|
+
|
78
|
+
def real_name(mod)
|
79
|
+
constant_cache.name_by_class(mod)
|
80
|
+
end
|
81
|
+
|
82
|
+
def gen_source_rbi(classes, aliases)
|
83
|
+
puts "Generating #{TMP_RBI} with #{classes.count} modules and #{aliases.count} aliases"
|
84
|
+
serializer = Sorbet::Private::Serialize.new(constant_cache)
|
85
|
+
buffer = []
|
86
|
+
buffer << Sorbet::Private::Serialize.header
|
87
|
+
|
88
|
+
# should we do something with these errors?
|
89
|
+
capture_stderr do
|
90
|
+
classes.each do |class_name|
|
91
|
+
buffer << serializer.class_or_module(class_name)
|
92
|
+
end
|
93
|
+
aliases.each do |base, other_names|
|
94
|
+
other_names.each do |other_name|
|
95
|
+
buffer << serializer.alias(base, other_name)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
File.write(TMP_RBI, buffer.join("\n"))
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_constants
|
103
|
+
puts "Printing your code's symbol table into #{SOURCE_CONSTANTS}"
|
104
|
+
io = IO.popen(
|
105
|
+
[
|
106
|
+
File.realpath("#{__dir__}/../bin/srb"),
|
107
|
+
'tc',
|
108
|
+
'--print=symbol-table-full-json',
|
109
|
+
'--stdout-hup-hack',
|
110
|
+
'--silence-dev-message',
|
111
|
+
'--no-error-count',
|
112
|
+
'-e', # this is additive with any files / dirs
|
113
|
+
'""',
|
114
|
+
],
|
115
|
+
err: SOURCE_CONSTANTS_ERR
|
116
|
+
)
|
117
|
+
File.write(SOURCE_CONSTANTS, io.read)
|
118
|
+
io.close
|
119
|
+
raise "Your source can't be read by Sorbet.\nYou can try `find . -type f | xargs -L 1 -t bundle exec srb tc --no-config --error-white-list 1000` and hopefully the last file it is processing before it dies is the culprit.\nIf not, maybe the errors in this file will help: #{SOURCE_CONSTANTS_ERR}" if File.read(SOURCE_CONSTANTS).empty?
|
120
|
+
|
121
|
+
puts "Printing #{TMP_RBI}'s symbol table into #{RBI_CONSTANTS}"
|
122
|
+
io = IO.popen(
|
123
|
+
[
|
124
|
+
File.realpath("#{__dir__}/../bin/srb"),
|
125
|
+
'tc',
|
126
|
+
# Make sure we don't load a sorbet/config in your cwd
|
127
|
+
'--no-config',
|
128
|
+
'--print=symbol-table-full-json',
|
129
|
+
# The hidden-definition serializer is not smart enough to put T::Enum
|
130
|
+
# constants it discovers inside an `enums do` block. We probably want
|
131
|
+
# to come up with a better long term solution here.
|
132
|
+
'--error-black-list=3506',
|
133
|
+
# Method redefined with mismatched argument is ok since sometime
|
134
|
+
# people monkeypatch over method
|
135
|
+
'--error-black-list=4010',
|
136
|
+
# Redefining constant is needed because we serialize things both as
|
137
|
+
# aliases and in-class constants.
|
138
|
+
'--error-black-list=4012',
|
139
|
+
# Invalid nesting is ok because we don't generate all the intermediate
|
140
|
+
# namespaces for aliases
|
141
|
+
'--error-black-list=4015',
|
142
|
+
'--stdout-hup-hack',
|
143
|
+
'--silence-dev-message',
|
144
|
+
'--no-error-count',
|
145
|
+
TMP_RBI,
|
146
|
+
],
|
147
|
+
err: RBI_CONSTANTS_ERR
|
148
|
+
)
|
149
|
+
File.write(RBI_CONSTANTS, io.read)
|
150
|
+
io.close
|
151
|
+
raise "#{TMP_RBI} had unexpected errors. Check this file for a clue: #{RBI_CONSTANTS_ERR}" unless $?.success?
|
152
|
+
end
|
153
|
+
|
154
|
+
def read_constants
|
155
|
+
puts "Reading #{SOURCE_CONSTANTS}"
|
156
|
+
source = JSON.parse(File.read(SOURCE_CONSTANTS))
|
157
|
+
puts "Reading #{RBI_CONSTANTS}"
|
158
|
+
rbi = JSON.parse(File.read(RBI_CONSTANTS))
|
159
|
+
[source, rbi]
|
160
|
+
end
|
161
|
+
|
162
|
+
def write_diff(source, rbi)
|
163
|
+
puts "Building rbi id to symbol map"
|
164
|
+
rbi_symbols = symbols_id_to_name(rbi, '')
|
165
|
+
puts "Building source id to symbol map"
|
166
|
+
source_symbols = symbols_id_to_name(source, '')
|
167
|
+
puts "Writing #{DIFF_RBI}"
|
168
|
+
diff = serialize_constants(
|
169
|
+
source.fetch("children", []),
|
170
|
+
rbi.fetch("children", []),
|
171
|
+
Object, false, source_symbols, rbi_symbols)
|
172
|
+
File.write(DIFF_RBI, diff)
|
173
|
+
end
|
174
|
+
|
175
|
+
def symbols_id_to_name(entry, prefix)
|
176
|
+
ret = {}
|
177
|
+
symbols_id_to_name_real(entry, prefix, ret)
|
178
|
+
ret
|
179
|
+
end
|
180
|
+
|
181
|
+
private def symbols_id_to_name_real(entry, prefix, ret)
|
182
|
+
name = entry["name"]["name"]
|
183
|
+
if prefix == '' || prefix == "<root>"
|
184
|
+
fqn = name.to_s
|
185
|
+
else
|
186
|
+
fqn = "#{prefix}::#{name}"
|
187
|
+
end
|
188
|
+
|
189
|
+
ret[entry["id"]] = fqn
|
190
|
+
entry.fetch("children", []).each do |child|
|
191
|
+
symbols_id_to_name_real(child, fqn, ret)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def serialize_constants(source, rbi, klass, is_singleton, source_symbols, rbi_symbols)
|
196
|
+
source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h
|
197
|
+
ret = []
|
198
|
+
|
199
|
+
rbi.each do |rbi_entry|
|
200
|
+
# skip duplicated constant fields
|
201
|
+
next if rbi_entry["name"]["kind"] == "UNIQUE" and rbi_entry["name"]["unique"] == "MANGLE_RENAME"
|
202
|
+
|
203
|
+
source_entry = source_by_name[rbi_entry["name"]["name"]]
|
204
|
+
|
205
|
+
ret << serialize_alias(source_entry, rbi_entry, klass, source_symbols, rbi_symbols)
|
206
|
+
ret << serialize_class(source_entry, rbi_entry, klass, source_symbols, rbi_symbols, source_by_name)
|
207
|
+
end
|
208
|
+
|
209
|
+
ret.compact.join("\n")
|
210
|
+
end
|
211
|
+
|
212
|
+
def serialize_class(source_entry, rbi_entry, klass, source_symbols, rbi_symbols, source_by_name)
|
213
|
+
return if rbi_entry["kind"] != "CLASS_OR_MODULE"
|
214
|
+
|
215
|
+
name = rbi_entry["name"]["name"]
|
216
|
+
if name.start_with?('<Class:')
|
217
|
+
name = name.sub('<Class:', '').sub('>', '')
|
218
|
+
my_klass_is_singleton = true
|
219
|
+
else
|
220
|
+
my_klass_is_singleton = false
|
221
|
+
end
|
222
|
+
begin
|
223
|
+
my_klass = klass.const_get(name, false) # rubocop:disable PrisonGuard/NoDynamicConstAccess
|
224
|
+
rescue LoadError, NameError, ArgumentError => e
|
225
|
+
return "# #{e.message.gsub("\n", "\n# ")}"
|
226
|
+
end
|
227
|
+
|
228
|
+
return if !Sorbet::Private::RealStdlib.real_is_a?(my_klass, Class) && !Sorbet::Private::RealStdlib.real_is_a?(my_klass, Module)
|
229
|
+
|
230
|
+
# We specifically don't typecheck anything in T:: since it is hardcoded
|
231
|
+
# into sorbet. We don't include anything in Sorbet::Private:: because
|
232
|
+
# it's private.
|
233
|
+
return if ['T', 'Sorbet::Private'].include?(real_name(my_klass))
|
234
|
+
|
235
|
+
source_type = nil
|
236
|
+
if !source_entry
|
237
|
+
if source_by_name[name]
|
238
|
+
source_type = source_by_name[name]["kind"]
|
239
|
+
end
|
240
|
+
else
|
241
|
+
source_type = source_entry["kind"]
|
242
|
+
end
|
243
|
+
if source_type && source_type != "CLASS_OR_MODULE"
|
244
|
+
return "# The source says #{real_name(my_klass)} is a #{source_type} but reflection says it is a #{rbi_entry['kind']}"
|
245
|
+
end
|
246
|
+
|
247
|
+
if !source_entry
|
248
|
+
source_children = []
|
249
|
+
source_mixins = []
|
250
|
+
is_stub = true
|
251
|
+
else
|
252
|
+
source_children = source_entry.fetch("children", [])
|
253
|
+
source_mixins = source_entry.fetch("mixins", [])
|
254
|
+
is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule'
|
255
|
+
end
|
256
|
+
rbi_children = rbi_entry.fetch("children", [])
|
257
|
+
rbi_mixins = rbi_entry.fetch("mixins", [])
|
258
|
+
|
259
|
+
methods = serialize_methods(source_children, rbi_children, my_klass, my_klass_is_singleton)
|
260
|
+
includes = serialize_includes(source_mixins, rbi_mixins, my_klass, my_klass_is_singleton, source_symbols, rbi_symbols)
|
261
|
+
values = serialize_values(source_children, rbi_children, my_klass, source_symbols)
|
262
|
+
|
263
|
+
ret = []
|
264
|
+
if !without_errors(methods).empty? || !without_errors(includes).empty? || !without_errors(values).empty? || is_stub
|
265
|
+
fqn = real_name(my_klass)
|
266
|
+
if fqn
|
267
|
+
klass_str = String.new
|
268
|
+
klass_str << (Sorbet::Private::RealStdlib.real_is_a?(my_klass, Class) ? "class #{fqn}\n" : "module #{fqn}\n")
|
269
|
+
klass_str << includes.join("\n")
|
270
|
+
klass_str << "\n" unless klass_str.end_with?("\n")
|
271
|
+
klass_str << methods.join("\n")
|
272
|
+
klass_str << "\n" unless klass_str.end_with?("\n")
|
273
|
+
klass_str << values.join("\n")
|
274
|
+
klass_str << "\n" unless klass_str.end_with?("\n")
|
275
|
+
klass_str << "end\n"
|
276
|
+
ret << klass_str
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
children = serialize_constants(source_children, rbi_children, my_klass, my_klass_is_singleton, source_symbols, rbi_symbols)
|
281
|
+
if children != ""
|
282
|
+
ret << children
|
283
|
+
end
|
284
|
+
|
285
|
+
ret.empty? ? nil : ret.join("\n")
|
286
|
+
end
|
287
|
+
|
288
|
+
private def without_errors(lines)
|
289
|
+
lines.reject {|line| line.start_with?("#")}
|
290
|
+
end
|
291
|
+
|
292
|
+
def serialize_alias(source_entry, rbi_entry, my_klass, source_symbols, rbi_symbols)
|
293
|
+
return if rbi_entry["kind"] != "STATIC_FIELD"
|
294
|
+
return if source_entry == rbi_entry
|
295
|
+
if source_entry
|
296
|
+
is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule'
|
297
|
+
if !is_stub
|
298
|
+
return
|
299
|
+
end
|
300
|
+
end
|
301
|
+
return if !rbi_entry["aliasTo"]
|
302
|
+
|
303
|
+
fqn = rbi_symbols[rbi_entry["id"]]
|
304
|
+
other_fqn = rbi_symbols[rbi_entry["aliasTo"]]
|
305
|
+
return if looks_like_stub_name(fqn)
|
306
|
+
ret = String.new
|
307
|
+
ret << "#{fqn} = #{other_fqn}\n"
|
308
|
+
return ret
|
309
|
+
end
|
310
|
+
|
311
|
+
def looks_like_stub_name(name)
|
312
|
+
name.include?('$')
|
313
|
+
end
|
314
|
+
|
315
|
+
private def serialize_values(source, rbi, klass, source_symbols)
|
316
|
+
source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h
|
317
|
+
ret = []
|
318
|
+
rbi.each do |rbi_entry|
|
319
|
+
name = rbi_entry["name"]["name"]
|
320
|
+
source_entry = source_by_name[name]
|
321
|
+
if source_entry
|
322
|
+
is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule'
|
323
|
+
next unless is_stub
|
324
|
+
end
|
325
|
+
next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{Sorbet::Private::RealStdlib.real_name(klass)}::#{name}")
|
326
|
+
begin
|
327
|
+
my_value = klass.const_get(name, false) # rubocop:disable PrisonGuard/NoDynamicConstAccess
|
328
|
+
rescue StandardError, LoadError => e
|
329
|
+
ret << "# #{e.message.gsub("\n", "\n# ")}"
|
330
|
+
next
|
331
|
+
end
|
332
|
+
next if Sorbet::Private::RealStdlib.real_is_a?(my_value, Class) || Sorbet::Private::RealStdlib.real_is_a?(my_value, Module)
|
333
|
+
if defined?(T::Types) && Sorbet::Private::RealStdlib.real_is_a?(my_value, T::Types::TypeMember)
|
334
|
+
ret << (my_value.variance == :invariant ? " #{name} = type_member" : " #{name} = type_member(#{my_value.variance.inspect})")
|
335
|
+
elsif defined?(T::Types) && Sorbet::Private::RealStdlib.real_is_a?(my_value, T::Types::TypeTemplate)
|
336
|
+
ret << (my_value.variance == :invariant ? " #{name} = type_template" : " #{name} = type_template(#{my_value.variance.inspect})")
|
337
|
+
else
|
338
|
+
ret << " #{name} = ::T.let(nil, ::T.untyped)"
|
339
|
+
end
|
340
|
+
end
|
341
|
+
ret
|
342
|
+
end
|
343
|
+
|
344
|
+
# These methods are defined in C++ and we want our C++ definition to
|
345
|
+
# win instead of a shim.
|
346
|
+
BLACKLIST = Set.new([
|
347
|
+
[Class.object_id, "new"],
|
348
|
+
[BasicObject.object_id, "initialize"],
|
349
|
+
]).freeze
|
350
|
+
|
351
|
+
private def serialize_methods(source, rbi, klass, is_singleton)
|
352
|
+
source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h
|
353
|
+
ret = []
|
354
|
+
maker = Sorbet::Private::Serialize.new(constant_cache)
|
355
|
+
rbi.each do |rbi_entry|
|
356
|
+
next if rbi_entry["kind"] != "METHOD"
|
357
|
+
name = rbi_entry["name"]["name"]
|
358
|
+
next if source_by_name[name]
|
359
|
+
|
360
|
+
next if BLACKLIST.include?([klass.object_id, name])
|
361
|
+
next if name.start_with?('<') && name.end_with?('>')
|
362
|
+
|
363
|
+
begin
|
364
|
+
if is_singleton
|
365
|
+
method = klass.singleton_method(name)
|
366
|
+
else
|
367
|
+
method = klass.instance_method(name)
|
368
|
+
end
|
369
|
+
rescue => e
|
370
|
+
ret << "# #{e.message.gsub("\n", "\n# ")}"
|
371
|
+
next
|
372
|
+
end
|
373
|
+
|
374
|
+
super_method = method.super_method
|
375
|
+
# next if super_method && T::AbstractUtils.abstract_method?(method) == T::AbstractUtils.abstract_method?(super_method)
|
376
|
+
|
377
|
+
errors = capture_stderr do
|
378
|
+
ret << maker.serialize_method(method, is_singleton, with_sig: false)
|
379
|
+
end
|
380
|
+
errors.split("\n").each do |line|
|
381
|
+
ret << "# #{line}"
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
ret
|
386
|
+
end
|
387
|
+
|
388
|
+
private def serialize_includes(source, rbi, klass, is_singleton, source_symbols, rbi_symbols)
|
389
|
+
ret = []
|
390
|
+
source_mixins = source.map {|id| source_symbols[id]}
|
391
|
+
rbi_mixins = rbi.map {|id| rbi_symbols[id]}
|
392
|
+
rbi_mixins.each do |rbi_mixin|
|
393
|
+
if !source_mixins.include?(rbi_mixin)
|
394
|
+
keyword = is_singleton ? "extend" : "include"
|
395
|
+
ret << " #{keyword} ::#{rbi_mixin}"
|
396
|
+
end
|
397
|
+
end
|
398
|
+
ret
|
399
|
+
end
|
400
|
+
|
401
|
+
def capture_stderr
|
402
|
+
real_stderr = $stderr
|
403
|
+
$stderr = StringIO.new
|
404
|
+
yield
|
405
|
+
$stderr.string
|
406
|
+
ensure
|
407
|
+
$stderr = real_stderr
|
408
|
+
end
|
409
|
+
|
410
|
+
private def rm_rbis
|
411
|
+
File.delete(HIDDEN_RBI) if File.exist?(HIDDEN_RBI)
|
412
|
+
File.delete(ERRORS_RBI) if File.exist?(ERRORS_RBI)
|
413
|
+
end
|
414
|
+
|
415
|
+
private def split_rbi
|
416
|
+
puts "Generating split RBIs into #{PATH}"
|
417
|
+
output = {
|
418
|
+
hidden: String.new,
|
419
|
+
errors: String.new,
|
420
|
+
}
|
421
|
+
|
422
|
+
valid = File.read(DIFF_RBI)
|
423
|
+
cur_output = T.let(nil, T.untyped)
|
424
|
+
|
425
|
+
valid.split("\n").each do |line|
|
426
|
+
category = categorize(line)
|
427
|
+
if category == :errors
|
428
|
+
# Don't ever switch to errors output permanantly
|
429
|
+
output[category] << line + "\n"
|
430
|
+
next
|
431
|
+
end
|
432
|
+
if !category.nil?
|
433
|
+
cur_output = output[category]
|
434
|
+
end
|
435
|
+
cur_output << line + "\n"
|
436
|
+
end
|
437
|
+
|
438
|
+
File.write(HIDDEN_RBI, HEADER + "\n" + output[:hidden])
|
439
|
+
File.write(ERRORS_RBI, HEADER + "\n" + output[:errors])
|
440
|
+
end
|
441
|
+
|
442
|
+
private def categorize(line)
|
443
|
+
if line.start_with?('#')
|
444
|
+
return :errors
|
445
|
+
end
|
446
|
+
return :hidden
|
447
|
+
end
|
448
|
+
|
449
|
+
def self.output_file
|
450
|
+
PATH
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
if $PROGRAM_NAME == __FILE__
|
455
|
+
Sorbet::Private::HiddenMethodFinder.main
|
456
|
+
end
|