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/real_stdlib.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
module Sorbet::Private::RealStdlib
|
5
|
+
def self.real_is_a?(o, klass)
|
6
|
+
@real_is_a ||= Object.instance_method(:is_a?)
|
7
|
+
@real_is_a.bind(o).call(klass)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.real_constants(mod)
|
11
|
+
@real_constants ||= Module.instance_method(:constants)
|
12
|
+
@real_constants.bind(mod).call(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.real_object_id(o)
|
16
|
+
@real_object_id ||= Object.instance_method(:object_id)
|
17
|
+
@real_object_id.bind(o).call
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.real_name(o)
|
21
|
+
@real_name ||= Module.instance_method(:name)
|
22
|
+
@real_name.bind(o).call
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.real_ancestors(mod)
|
26
|
+
@real_ancestors ||= Module.instance_method(:ancestors)
|
27
|
+
@real_ancestors.bind(mod).call
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.real_instance_methods(mod, arg)
|
31
|
+
@real_instance_methods ||= Module.instance_method(:instance_methods)
|
32
|
+
@real_instance_methods.bind(mod).call(arg)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.real_singleton_methods(mod, arg)
|
36
|
+
@real_singleton_methods ||= Module.instance_method(:singleton_methods)
|
37
|
+
@real_singleton_methods.bind(mod).call(arg)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.real_private_instance_methods(mod, arg)
|
41
|
+
@real_private_instance_methods ||= Module.instance_method(:private_instance_methods)
|
42
|
+
@real_private_instance_methods.bind(mod).call(arg)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.real_singleton_class(obj)
|
46
|
+
@real_singleton_class ||= Object.instance_method(:singleton_class)
|
47
|
+
@real_singleton_class.bind(obj).call
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.real_spaceship(obj, arg)
|
51
|
+
@real_spaceship ||= Object.instance_method(:<=>)
|
52
|
+
@real_spaceship.bind(obj).call(arg)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.real_hash(o)
|
56
|
+
@real_hash ||= Object.instance_method(:hash)
|
57
|
+
@real_hash.bind(o).call
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.real_superclass(o)
|
61
|
+
@real_superclass ||= Class.instance_method(:superclass)
|
62
|
+
@real_superclass.bind(o).call
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.real_eqeq(obj, other)
|
66
|
+
@real_eqeq ||= Object.instance_method(:==)
|
67
|
+
@real_eqeq.bind(obj).call(other)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.real_autoload?(o, klass)
|
71
|
+
@real_autoload ||= Object.instance_method(:autoload?)
|
72
|
+
@real_autoload.bind(o).call(klass)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.real_const_get(obj, const, arg)
|
76
|
+
@real_const_get ||= Object.singleton_class.instance_method(:const_get)
|
77
|
+
@real_const_get.bind(obj).call(const, arg)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.real_method(obj, sym)
|
81
|
+
@real_method ||= Object.instance_method(:method)
|
82
|
+
@real_method.bind(obj).call(sym)
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
require_relative './gem_loader'
|
7
|
+
require_relative './status'
|
8
|
+
|
9
|
+
class ExitCalledError < RuntimeError
|
10
|
+
end
|
11
|
+
|
12
|
+
class Sorbet::Private::RequireEverything
|
13
|
+
# Goes through the most common ways to require all your userland code
|
14
|
+
def self.require_everything
|
15
|
+
return if @already_ran
|
16
|
+
@already_ran = true
|
17
|
+
patch_kernel
|
18
|
+
load_rails
|
19
|
+
load_bundler # this comes second since some rails projects fail `Bundler.require' before rails is loaded
|
20
|
+
require_all_files
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.load_rails
|
24
|
+
return unless rails?
|
25
|
+
require './config/application'
|
26
|
+
rails = Object.const_get(:Rails)
|
27
|
+
rails.application.require_environment!
|
28
|
+
rails.application.eager_load!
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.load_bundler
|
33
|
+
return unless File.exist?('Gemfile')
|
34
|
+
begin
|
35
|
+
require 'bundler'
|
36
|
+
rescue LoadError
|
37
|
+
return
|
38
|
+
end
|
39
|
+
Sorbet::Private::GemLoader.require_all_gems
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.require_all_files
|
43
|
+
excluded_paths = Set.new
|
44
|
+
excluded_paths += excluded_rails_files if rails?
|
45
|
+
|
46
|
+
abs_paths = rb_file_paths
|
47
|
+
errors = []
|
48
|
+
abs_paths.each_with_index do |abs_path, i|
|
49
|
+
# Executable files are likely not meant to be required.
|
50
|
+
# Some things we're trying to prevent against:
|
51
|
+
# - misbehaving require-time side effects (removing files, reading from stdin, etc.)
|
52
|
+
# - extra long runtime (making network requests, running a benchmark)
|
53
|
+
# While this isn't a perfect heuristic for these things, it's pretty good.
|
54
|
+
next if File.executable?(abs_path)
|
55
|
+
next if excluded_paths.include?(abs_path)
|
56
|
+
|
57
|
+
# Skip db/schema.rb, as requiring it can wipe the database. This is left
|
58
|
+
# out of exclude_rails_files, as it is possible to use the packages that
|
59
|
+
# generate it without using the whole rails ecosystem.
|
60
|
+
next if /db\/schema.rb$/.match(abs_path)
|
61
|
+
|
62
|
+
# Skip **/extconf.rb, as running it will emit build configuration artifacts
|
63
|
+
next if /\/extconf.rb$/.match(abs_path)
|
64
|
+
|
65
|
+
begin
|
66
|
+
my_require(abs_path, i+1, abs_paths.size)
|
67
|
+
rescue LoadError, NoMethodError, SyntaxError
|
68
|
+
next
|
69
|
+
rescue
|
70
|
+
errors << abs_path
|
71
|
+
next
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# one more chance for order dependent things
|
75
|
+
errors.each_with_index do |abs_path, i|
|
76
|
+
begin
|
77
|
+
my_require(abs_path, i+1, errors.size)
|
78
|
+
rescue
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
Sorbet::Private::Status.done
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.my_require(abs_path, numerator, denominator)
|
86
|
+
rel_path = Pathname.new(abs_path).relative_path_from(Pathname.new(Dir.pwd)).to_s
|
87
|
+
Sorbet::Private::Status.say("[#{numerator}/#{denominator}] require_relative './#{rel_path}'")
|
88
|
+
require_relative abs_path
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.patch_kernel
|
92
|
+
Kernel.send(:define_method, :exit) do |*|
|
93
|
+
puts 'Kernel#exit was called while requiring ruby source files'
|
94
|
+
raise ExitCalledError.new
|
95
|
+
end
|
96
|
+
|
97
|
+
Kernel.send(:define_method, :at_exit) do |&block|
|
98
|
+
if File.split($0).last == 'rake'
|
99
|
+
# Let `rake test` work
|
100
|
+
super
|
101
|
+
return proc {}
|
102
|
+
end
|
103
|
+
# puts "Ignoring at_exit: #{block}"
|
104
|
+
proc {}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def self.rb_file_paths
|
111
|
+
srb = File.realpath("#{__dir__}/../bin/srb")
|
112
|
+
output = IO.popen([
|
113
|
+
srb,
|
114
|
+
"tc",
|
115
|
+
"-p",
|
116
|
+
"file-table-json",
|
117
|
+
"--stop-after=parser",
|
118
|
+
"--silence-dev-message",
|
119
|
+
"--no-error-count",
|
120
|
+
"-e",
|
121
|
+
"''",
|
122
|
+
]) {|io| io.read}
|
123
|
+
# This returns a hash with structure:
|
124
|
+
# { files:
|
125
|
+
# [
|
126
|
+
# {
|
127
|
+
# "strict": ["Ignore"|"False"|"True"|"Strict"|"Strong"|"Stdlib"],
|
128
|
+
# "path": "./path/to/file",
|
129
|
+
# ...
|
130
|
+
# }
|
131
|
+
# ...
|
132
|
+
# ]
|
133
|
+
# }
|
134
|
+
parsed = JSON.parse(output)
|
135
|
+
parsed
|
136
|
+
.fetch('files', [])
|
137
|
+
.reject{|file| ["Ignore", "Stdlib"].include?(file["strict"])}
|
138
|
+
.map{|file| file["path"]}
|
139
|
+
.select{|path| File.file?(path)} # Some files have https:// paths. We ignore those here.
|
140
|
+
.select{|path| /.rb$/.match(path)}
|
141
|
+
.map{|path| File.expand_path(path)} # Requires absolute path
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.excluded_rails_files
|
145
|
+
excluded_paths = Set.new
|
146
|
+
|
147
|
+
# Exclude files that have already been loaded by rails
|
148
|
+
self.rails_load_paths.each do |path|
|
149
|
+
excluded_paths += Dir.glob("#{path}/**/*.rb")
|
150
|
+
end
|
151
|
+
|
152
|
+
# Exclude initializers, as they have already been run by rails and
|
153
|
+
# can contain side-effects like monkey-patching that should
|
154
|
+
# only be run once.
|
155
|
+
excluded_paths += Dir.glob("#{Dir.pwd}/config/initializers/**/*.rb")
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.rails_load_paths
|
159
|
+
rails = Object.const_get(:Rails)
|
160
|
+
|
161
|
+
# As per changes made to change the arity of this method:
|
162
|
+
# https://github.com/rails/rails/commit/b6e17b6a4b67ccc9fac5fe16741c3db720f00959
|
163
|
+
# This sets the `add_autoload_paths_to_load_path` parameter to `true` which will
|
164
|
+
# provide parity with older versions of Rails prior to the mentioned commit.
|
165
|
+
if Gem::Version.new(rails.version) >= Gem::Version.new('6.0.0.rc2')
|
166
|
+
rails.application.send(:_all_load_paths, true)
|
167
|
+
else
|
168
|
+
rails.application.send(:_all_load_paths)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.rails?
|
173
|
+
return false unless File.exist?('config/application.rb')
|
174
|
+
begin
|
175
|
+
require 'rails'
|
176
|
+
rescue LoadError
|
177
|
+
return false
|
178
|
+
end
|
179
|
+
true
|
180
|
+
end
|
181
|
+
end
|
data/lib/serialize.rb
ADDED
@@ -0,0 +1,362 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
require 'bigdecimal'
|
5
|
+
|
6
|
+
class Sorbet::Private::Serialize
|
7
|
+
BLACKLIST_CONSTANTS = [
|
8
|
+
['DidYouMean', :NameErrorCheckers], # https://github.com/yuki24/did_you_mean/commit/b72fdbe194401f1be21f8ad7b6e3f784a0ad197d
|
9
|
+
['Net', :OpenSSL], # https://github.com/yuki24/did_you_mean/commit/b72fdbe194401f1be21f8ad7b6e3f784a0ad197d
|
10
|
+
]
|
11
|
+
|
12
|
+
SPECIAL_METHOD_NAMES = %w[! ~ +@ ** -@ * / % + - << >> & | ^ < <= => > >= == === != =~ !~ <=> [] []= `]
|
13
|
+
|
14
|
+
def initialize(constant_cache)
|
15
|
+
@constant_cache = constant_cache
|
16
|
+
end
|
17
|
+
|
18
|
+
private def get_constants(mod, inherited=nil)
|
19
|
+
@real_constants ||= Module.instance_method(:constants)
|
20
|
+
if inherited.nil?
|
21
|
+
@real_constants.bind(mod).call
|
22
|
+
else
|
23
|
+
@real_constants.bind(mod).call(inherited)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.header(typed="true", subcommand="update")
|
28
|
+
buffer = []
|
29
|
+
buffer << "# This file is autogenerated. Do not edit it by hand. Regenerate it with:"
|
30
|
+
buffer << "# srb rbi #{subcommand}"
|
31
|
+
if typed
|
32
|
+
buffer << ""
|
33
|
+
buffer << "# typed: #{typed}"
|
34
|
+
end
|
35
|
+
buffer << ""
|
36
|
+
buffer.join("\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
def class_or_module(class_name)
|
40
|
+
if !valid_class_name(class_name)
|
41
|
+
return " # Skipping serializing #{class_name} because it is an invalid name\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
klass = @constant_cache.class_by_name(class_name)
|
45
|
+
is_nil = nil.equal?(klass)
|
46
|
+
raise "#{class_name} is not a Class or Module. Maybe it was miscategorized?" if is_nil
|
47
|
+
|
48
|
+
ret = String.new
|
49
|
+
|
50
|
+
superclass = Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? Sorbet::Private::RealStdlib.real_superclass(klass) : nil
|
51
|
+
if superclass
|
52
|
+
superclass_str = Sorbet::Private::RealStdlib.real_eqeq(superclass, Object) ? '' : @constant_cache.name_by_class(superclass)
|
53
|
+
else
|
54
|
+
superclass_str = ''
|
55
|
+
end
|
56
|
+
superclass_str = !superclass_str || superclass_str.empty? ? '' : " < #{superclass_str}"
|
57
|
+
ret << (Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? "class #{class_name}#{superclass_str}\n" : "module #{class_name}\n")
|
58
|
+
|
59
|
+
# We don't use .included_modules since that also has all the aweful things
|
60
|
+
# that are mixed into Object. This way we at least have a delimiter before
|
61
|
+
# the awefulness starts (the superclass).
|
62
|
+
Sorbet::Private::RealStdlib.real_ancestors(klass).each do |ancestor|
|
63
|
+
next if Sorbet::Private::RealStdlib.real_eqeq(ancestor, klass)
|
64
|
+
break if Sorbet::Private::RealStdlib.real_eqeq(ancestor, superclass)
|
65
|
+
ancestor_name = @constant_cache.name_by_class(ancestor)
|
66
|
+
next unless ancestor_name
|
67
|
+
next if ancestor_name == class_name
|
68
|
+
if Sorbet::Private::RealStdlib.real_is_a?(ancestor, Class)
|
69
|
+
ret << " # Skipping `include #{ancestor_name}` because it is a Class\n"
|
70
|
+
next
|
71
|
+
end
|
72
|
+
if !valid_class_name(ancestor_name)
|
73
|
+
ret << " # Skipping `include #{ancestor_name}` because it is an invalid name\n"
|
74
|
+
next
|
75
|
+
end
|
76
|
+
ret << " include ::#{ancestor_name}\n"
|
77
|
+
end
|
78
|
+
singleton_class = Sorbet::Private::RealStdlib.real_singleton_class(klass)
|
79
|
+
Sorbet::Private::RealStdlib.real_ancestors(singleton_class).each do |ancestor|
|
80
|
+
next if ancestor == Sorbet::Private::RealStdlib.real_singleton_class(klass)
|
81
|
+
break if superclass && ancestor == Sorbet::Private::RealStdlib.real_singleton_class(superclass)
|
82
|
+
break if ancestor == Module
|
83
|
+
break if ancestor == Object
|
84
|
+
ancestor_name = @constant_cache.name_by_class(ancestor)
|
85
|
+
next unless ancestor_name
|
86
|
+
if Sorbet::Private::RealStdlib.real_is_a?(ancestor, Class)
|
87
|
+
ret << " # Skipping `extend #{ancestor_name}` because it is a Class\n"
|
88
|
+
next
|
89
|
+
end
|
90
|
+
if !valid_class_name(ancestor_name)
|
91
|
+
ret << " # Skipping `extend #{ancestor_name}` because it is an invalid name\n"
|
92
|
+
next
|
93
|
+
end
|
94
|
+
ret << " extend ::#{ancestor_name}\n"
|
95
|
+
end
|
96
|
+
|
97
|
+
constants = []
|
98
|
+
# Declare all the type_members and type_templates
|
99
|
+
constants += get_constants(klass).uniq.map do |const_sym|
|
100
|
+
# We have to not pass `false` because `klass.constants` intentionally is
|
101
|
+
# pulling in all the ancestor constants
|
102
|
+
next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{class_name}::#{const_sym}")
|
103
|
+
begin
|
104
|
+
value = klass.const_get(const_sym)
|
105
|
+
rescue LoadError, NameError, RuntimeError, ArgumentError => err
|
106
|
+
ret << "# Got #{err.class} when trying to get class constant symbol #{class_name}::#{const_sym}\n"
|
107
|
+
next
|
108
|
+
end
|
109
|
+
# next if !Sorbet::Private::RealStdlib.real_is_a?(value, T::Types::TypeVariable)
|
110
|
+
next if Sorbet::Private::RealStdlib.real_is_a?(value, Module)
|
111
|
+
next if !comparable?(value)
|
112
|
+
[const_sym, value]
|
113
|
+
end
|
114
|
+
constants += get_constants(klass, false).uniq.map do |const_sym|
|
115
|
+
next if BLACKLIST_CONSTANTS.include?([class_name, const_sym])
|
116
|
+
next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{class_name}::#{const_sym}")
|
117
|
+
begin
|
118
|
+
value = klass.const_get(const_sym, false)
|
119
|
+
rescue LoadError, NameError, RuntimeError, ArgumentError => err
|
120
|
+
ret << "# Got #{err.class} when trying to get class constant symbol #{class_name}::#{const_sym}_\n"
|
121
|
+
next
|
122
|
+
end
|
123
|
+
next if Sorbet::Private::RealStdlib.real_is_a?(value, Module)
|
124
|
+
next if !comparable?(value)
|
125
|
+
[const_sym, value]
|
126
|
+
end
|
127
|
+
constants_sorted = constants.compact.sort_by do |const_sym, _value|
|
128
|
+
const_sym
|
129
|
+
end
|
130
|
+
constants_uniq = constants_sorted.uniq do |const_sym, _value|
|
131
|
+
const_sym.hash
|
132
|
+
end
|
133
|
+
constants_serialized = constants_uniq.map do |const_sym, value|
|
134
|
+
constant(const_sym, value)
|
135
|
+
end
|
136
|
+
ret << constants_serialized.join("\n")
|
137
|
+
ret << "\n\n" if !constants_serialized.empty?
|
138
|
+
|
139
|
+
methods = []
|
140
|
+
instance_methods = Sorbet::Private::RealStdlib.real_instance_methods(klass, false)
|
141
|
+
begin
|
142
|
+
initialize = klass.instance_method(:initialize)
|
143
|
+
rescue
|
144
|
+
initialize = nil
|
145
|
+
end
|
146
|
+
if initialize && initialize.owner == klass
|
147
|
+
# This method never apears in the reflection list...
|
148
|
+
instance_methods += [:initialize]
|
149
|
+
end
|
150
|
+
Sorbet::Private::RealStdlib.real_ancestors(klass).reject {|ancestor| @constant_cache.name_by_class(ancestor)}.each do |ancestor|
|
151
|
+
instance_methods += ancestor.instance_methods(false)
|
152
|
+
end
|
153
|
+
|
154
|
+
# uniq here is required because we populate additional methos from anonymous superclasses and there
|
155
|
+
# might be duplicates
|
156
|
+
methods += instance_methods.sort.uniq.map do |method_sym|
|
157
|
+
begin
|
158
|
+
method = klass.instance_method(method_sym)
|
159
|
+
rescue => e
|
160
|
+
ret << "# #{e}\n"
|
161
|
+
next
|
162
|
+
end
|
163
|
+
next if blacklisted_method(method)
|
164
|
+
next if ancestor_has_method(method, klass)
|
165
|
+
serialize_method(method)
|
166
|
+
end
|
167
|
+
# uniq is not required here, but added to be on the safe side
|
168
|
+
methods += Sorbet::Private::RealStdlib.real_singleton_methods(klass, false).sort.uniq.map do |method_sym|
|
169
|
+
begin
|
170
|
+
method = klass.singleton_method(method_sym)
|
171
|
+
rescue => e
|
172
|
+
ret << "# #{e}\n"
|
173
|
+
next
|
174
|
+
end
|
175
|
+
next if blacklisted_method(method)
|
176
|
+
next if ancestor_has_method(method, Sorbet::Private::RealStdlib.real_singleton_class(klass))
|
177
|
+
serialize_method(method, true)
|
178
|
+
end
|
179
|
+
ret << methods.join("\n")
|
180
|
+
ret << "end\n"
|
181
|
+
|
182
|
+
ret
|
183
|
+
end
|
184
|
+
|
185
|
+
def alias(base, other_name)
|
186
|
+
ret = String.new
|
187
|
+
ret << "#{other_name} = #{base}"
|
188
|
+
ret
|
189
|
+
end
|
190
|
+
|
191
|
+
def comparable?(value)
|
192
|
+
return false if Sorbet::Private::RealStdlib.real_is_a?(value, BigDecimal) && value.nan?
|
193
|
+
return false if Sorbet::Private::RealStdlib.real_is_a?(value, Float) && value.nan?
|
194
|
+
return false if Sorbet::Private::RealStdlib.real_is_a?(value, Complex)
|
195
|
+
true
|
196
|
+
end
|
197
|
+
|
198
|
+
def blacklisted_method(method)
|
199
|
+
method.name =~ /__validator__[0-9]{8}/ || method.name =~ /.*:.*/
|
200
|
+
end
|
201
|
+
|
202
|
+
def valid_method_name(name)
|
203
|
+
return true if SPECIAL_METHOD_NAMES.include?(name)
|
204
|
+
return false if name =~ /^\d/
|
205
|
+
name =~ /^[[:word:]]+[?!=]?$/
|
206
|
+
end
|
207
|
+
|
208
|
+
def ancestor_has_method(method, klass)
|
209
|
+
return false if !Sorbet::Private::RealStdlib.real_is_a?(klass, Class)
|
210
|
+
first_ancestor = klass.ancestors.find do |ancestor|
|
211
|
+
next if ancestor == klass
|
212
|
+
begin
|
213
|
+
ancestor.instance_method(method.name)
|
214
|
+
rescue NameError
|
215
|
+
nil
|
216
|
+
end
|
217
|
+
end
|
218
|
+
return false unless first_ancestor
|
219
|
+
first_ancestor.instance_method(method.name).parameters == method.parameters
|
220
|
+
end
|
221
|
+
|
222
|
+
def constant(const, value)
|
223
|
+
if KEYWORDS.include?(const.to_sym)
|
224
|
+
return "# Illegal constant name: #{const}"
|
225
|
+
end
|
226
|
+
if defined?(T::Types) && Sorbet::Private::RealStdlib.real_is_a?(value, T::Types::TypeMember)
|
227
|
+
value.variance == :invariant ? " #{const} = type_member" : " #{const} = type_member(#{value.variance.inspect})"
|
228
|
+
elsif defined?(T::Types) && Sorbet::Private::RealStdlib.real_is_a?(value, T::Types::TypeTemplate)
|
229
|
+
value.variance == :invariant ? " #{const} = type_template" : " #{const} = type_template(#{value.variance.inspect})"
|
230
|
+
else
|
231
|
+
" #{const} = ::T.let(nil, ::T.untyped)"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def serialize_method(method, static=false, with_sig: true)
|
236
|
+
name = method.name.to_s
|
237
|
+
if !valid_method_name(name)
|
238
|
+
return "# Illegal method name: #{name}\n"
|
239
|
+
end
|
240
|
+
parameters = from_method(method)
|
241
|
+
# a hack for appeasing Sorbet in the presence of the Enumerable interface
|
242
|
+
if name == 'each' && !parameters.any? {|(kind, _)| kind == :block}
|
243
|
+
parameters.push([:block, "blk"])
|
244
|
+
end
|
245
|
+
ret = String.new
|
246
|
+
ret << serialize_sig(parameters) if with_sig
|
247
|
+
args = parameters.map do |(kind, param_name)|
|
248
|
+
to_sig(kind, param_name)
|
249
|
+
end.compact.join(', ')
|
250
|
+
ret << " def #{static ? 'self.' : ''}#{name}(#{args}); end\n"
|
251
|
+
ret
|
252
|
+
end
|
253
|
+
|
254
|
+
def valid_class_name(name)
|
255
|
+
name.split("::").each do |piece|
|
256
|
+
return false if piece[0].upcase != piece[0]
|
257
|
+
end
|
258
|
+
return false if [
|
259
|
+
'Sorbet::Private::GemGeneratorTracepoint::Tracer::ClassOverride',
|
260
|
+
'Sorbet::Private::GemGeneratorTracepoint::Tracer::ModuleOverride',
|
261
|
+
'Sorbet::Private::GemGeneratorTracepoint::Tracer::ObjectOverride',
|
262
|
+
].include?(name)
|
263
|
+
true
|
264
|
+
end
|
265
|
+
|
266
|
+
def serialize_sig(parameters)
|
267
|
+
ret = String.new
|
268
|
+
if !parameters.empty?
|
269
|
+
ret << " sig do\n"
|
270
|
+
ret << " params(\n"
|
271
|
+
parameters.each do |(_kind, name)|
|
272
|
+
ret << " #{name}: ::T.untyped,\n"
|
273
|
+
end
|
274
|
+
ret << " )\n"
|
275
|
+
ret << " .returns(::T.untyped)\n"
|
276
|
+
ret << " end\n"
|
277
|
+
else
|
278
|
+
ret << " sig {returns(::T.untyped)}\n"
|
279
|
+
end
|
280
|
+
ret
|
281
|
+
end
|
282
|
+
|
283
|
+
# from https://docs.ruby-lang.org/en/2.4.0/keywords_rdoc.html
|
284
|
+
KEYWORDS = [
|
285
|
+
:__ENCODING__,
|
286
|
+
:__LINE__,
|
287
|
+
:__FILE__,
|
288
|
+
:BEGIN,
|
289
|
+
:END,
|
290
|
+
:alias,
|
291
|
+
:and,
|
292
|
+
:begin,
|
293
|
+
:break,
|
294
|
+
:case,
|
295
|
+
:class,
|
296
|
+
:def,
|
297
|
+
:defined?,
|
298
|
+
:do,
|
299
|
+
:else,
|
300
|
+
:elsif,
|
301
|
+
:end,
|
302
|
+
:ensure,
|
303
|
+
:false,
|
304
|
+
:for,
|
305
|
+
:if,
|
306
|
+
:in,
|
307
|
+
:module,
|
308
|
+
:next,
|
309
|
+
:nil,
|
310
|
+
:not,
|
311
|
+
:or,
|
312
|
+
:redo,
|
313
|
+
:rescue,
|
314
|
+
:retry,
|
315
|
+
:return,
|
316
|
+
:self,
|
317
|
+
:super,
|
318
|
+
:then,
|
319
|
+
:true,
|
320
|
+
:undef,
|
321
|
+
:unless,
|
322
|
+
:until,
|
323
|
+
:when,
|
324
|
+
:while,
|
325
|
+
:yield,
|
326
|
+
]
|
327
|
+
|
328
|
+
def from_method(method)
|
329
|
+
uniq = 0
|
330
|
+
method.parameters.map.with_index do |(kind, name), index|
|
331
|
+
if !name
|
332
|
+
arg_name = method.name.to_s[0...-1]
|
333
|
+
if (!KEYWORDS.include?(arg_name.to_sym)) && method.name.to_s.end_with?('=') && arg_name =~ /\A[a-z_][a-z0-9A-Z_]*\Z/ && index == 0
|
334
|
+
name = arg_name
|
335
|
+
else
|
336
|
+
name = '_' + (uniq == 0 ? '' : uniq.to_s)
|
337
|
+
uniq += 1
|
338
|
+
end
|
339
|
+
end
|
340
|
+
[kind, name]
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def to_sig(kind, name)
|
345
|
+
case kind
|
346
|
+
when :req
|
347
|
+
name.to_s
|
348
|
+
when :opt
|
349
|
+
"#{name}=T.unsafe(nil)"
|
350
|
+
when :rest
|
351
|
+
"*#{name}"
|
352
|
+
when :keyreq
|
353
|
+
"#{name}:"
|
354
|
+
when :key
|
355
|
+
"#{name}: T.unsafe(nil)"
|
356
|
+
when :keyrest
|
357
|
+
"**#{name}"
|
358
|
+
when :block
|
359
|
+
"&#{name}"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|