sorbet 0.4.4250 → 0.4.4253
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/srb +117 -0
- data/bin/srb-rbi +232 -0
- data/lib/constant_cache.rb +193 -0
- data/lib/create-config.rb +32 -0
- data/lib/fetch-rbis.rb +127 -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 +256 -0
- data/lib/gem-generator-tracepoint/tracer.rb +180 -0
- data/lib/gem_loader.rb +579 -0
- data/lib/gem_loader.rbi +15 -0
- data/lib/hidden-definition-finder.rb +441 -0
- data/lib/real_stdlib.rb +79 -0
- data/lib/require_everything.rb +128 -0
- data/lib/serialize.rb +360 -0
- data/lib/status.rb +21 -0
- data/lib/step_interface.rb +11 -0
- data/lib/suggest-typed.rb +41 -0
- data/lib/t.rb +58 -0
- data/lib/todo-rbi.rb +48 -0
- metadata +101 -11
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative './step_interface'
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
class Sorbet; end
|
9
|
+
module Sorbet::Private; end
|
10
|
+
class Sorbet::Private::CreateConfig
|
11
|
+
SORBET_DIR = 'sorbet'
|
12
|
+
SORBET_CONFIG_FILE = "#{SORBET_DIR}/config"
|
13
|
+
|
14
|
+
include Sorbet::Private::StepInterface
|
15
|
+
|
16
|
+
def self.main
|
17
|
+
FileUtils.mkdir_p(SORBET_DIR)
|
18
|
+
|
19
|
+
File.open(SORBET_CONFIG_FILE, 'w') do |f|
|
20
|
+
f.puts('--dir')
|
21
|
+
f.puts('.')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.output_file
|
26
|
+
SORBET_CONFIG_FILE
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if $PROGRAM_NAME == __FILE__
|
31
|
+
Sorbet::Private::CreateConfig.main
|
32
|
+
end
|
data/lib/fetch-rbis.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# typed: false
|
3
|
+
|
4
|
+
require_relative './step_interface'
|
5
|
+
require_relative './t'
|
6
|
+
|
7
|
+
require 'bundler'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'set'
|
10
|
+
|
11
|
+
class Sorbet; end
|
12
|
+
module Sorbet::Private; end
|
13
|
+
class Sorbet::Private::FetchRBIs
|
14
|
+
SORBET_DIR = 'sorbet'
|
15
|
+
SORBET_CONFIG_FILE = "#{SORBET_DIR}/config"
|
16
|
+
SORBET_RBI_LIST = "#{SORBET_DIR}/rbi_list"
|
17
|
+
SORBET_RBI_SORBET_TYPED = "#{SORBET_DIR}/rbi/sorbet-typed/"
|
18
|
+
|
19
|
+
XDG_CACHE_HOME = ENV['XDG_CACHE_HOME'] || "#{ENV['HOME']}/.cache"
|
20
|
+
RBI_CACHE_DIR = "#{XDG_CACHE_HOME}/sorbet/sorbet-typed"
|
21
|
+
|
22
|
+
SORBET_TYPED_REPO = 'https://github.com/sorbet/sorbet-typed.git'
|
23
|
+
SORBET_TYPED_REVISION = ENV['SRB_SORBET_TYPED_REVISION'] || 'origin/master'
|
24
|
+
|
25
|
+
HEADER = Sorbet::Private::Serialize.header(false, 'sorbet-typed')
|
26
|
+
|
27
|
+
include Sorbet::Private::StepInterface
|
28
|
+
|
29
|
+
# Ensure our cache is up-to-date
|
30
|
+
T::Sig::WithoutRuntime.sig {void}
|
31
|
+
def self.fetch_sorbet_typed
|
32
|
+
if !File.directory?(RBI_CACHE_DIR)
|
33
|
+
IO.popen(["git", "clone", SORBET_TYPED_REPO, RBI_CACHE_DIR]) {|pipe| pipe.read}
|
34
|
+
raise "Failed to git pull" if $?.exitstatus != 0
|
35
|
+
end
|
36
|
+
|
37
|
+
FileUtils.cd(RBI_CACHE_DIR) do
|
38
|
+
IO.popen(%w{git fetch origin}) {|pipe| pipe.read}
|
39
|
+
raise "Failed to git fetch" if $?.exitstatus != 0
|
40
|
+
IO.popen(%w{git checkout -q} + [SORBET_TYPED_REVISION]) {|pipe| pipe.read}
|
41
|
+
raise "Failed to git checkout" if $?.exitstatus != 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# List of directories whose names satisfy the given Gem::Version (+ 'all/')
|
46
|
+
T::Sig::WithoutRuntime.sig do
|
47
|
+
params(
|
48
|
+
root: String,
|
49
|
+
version: Gem::Version,
|
50
|
+
)
|
51
|
+
.returns(T::Array[String])
|
52
|
+
end
|
53
|
+
def self.matching_version_directories(root, version)
|
54
|
+
paths = Dir.glob("#{root}/*/").select do |dir|
|
55
|
+
basename = File.basename(dir.chomp('/'))
|
56
|
+
requirements = basename.split(/[,&-]/) # split using ',', '-', or '&'
|
57
|
+
requirements.all? do |requirement|
|
58
|
+
Gem::Requirement::PATTERN =~ requirement &&
|
59
|
+
Gem::Requirement.create(requirement).satisfied_by?(version)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
paths = paths.map {|dir| dir.chomp('/')}
|
63
|
+
all_dir = "#{root}/all"
|
64
|
+
paths << all_dir if Dir.exist?(all_dir)
|
65
|
+
paths
|
66
|
+
end
|
67
|
+
|
68
|
+
# List of directories in lib/ruby whose names satisfy the current RUBY_VERSION
|
69
|
+
T::Sig::WithoutRuntime.sig {params(ruby_version: Gem::Version).returns(T::Array[String])}
|
70
|
+
def self.paths_for_ruby_version(ruby_version)
|
71
|
+
ruby_dir = "#{RBI_CACHE_DIR}/lib/ruby"
|
72
|
+
matching_version_directories(ruby_dir, ruby_version)
|
73
|
+
end
|
74
|
+
|
75
|
+
# List of directories in lib/gemspec.name whose names satisfy gemspec.version
|
76
|
+
T::Sig::WithoutRuntime.sig {params(gemspec: T.untyped).returns(T::Array[String])}
|
77
|
+
def self.paths_for_gem_version(gemspec)
|
78
|
+
local_dir = "#{RBI_CACHE_DIR}/lib/#{gemspec.name}"
|
79
|
+
matching_version_directories(local_dir, gemspec.version)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Copy the relevant RBIs into their repo, with matching folder structure.
|
83
|
+
T::Sig::WithoutRuntime.sig {params(vendor_paths: T::Array[String]).void}
|
84
|
+
def self.vendor_rbis_within_paths(vendor_paths)
|
85
|
+
vendor_paths.each do |vendor_path|
|
86
|
+
relative_vendor_path = vendor_path.sub(RBI_CACHE_DIR, '')
|
87
|
+
|
88
|
+
dest = "#{SORBET_RBI_SORBET_TYPED}/#{relative_vendor_path}"
|
89
|
+
FileUtils.mkdir_p(dest)
|
90
|
+
|
91
|
+
Dir.glob("#{vendor_path}/*.rbi").each do |rbi|
|
92
|
+
extra_header = "#
|
93
|
+
# If you would like to make changes to this file, great! Please upstream any changes you make here:
|
94
|
+
#
|
95
|
+
# https://github.com/sorbet/sorbet-typed/edit/master#{relative_vendor_path}/#{File.basename(rbi)}
|
96
|
+
#
|
97
|
+
"
|
98
|
+
File.write("#{dest}/#{File.basename(rbi)}", HEADER + extra_header + File.read(rbi))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
T::Sig::WithoutRuntime.sig {void}
|
104
|
+
def self.main
|
105
|
+
fetch_sorbet_typed
|
106
|
+
|
107
|
+
gemspecs = Bundler.load.specs.sort_by(&:name)
|
108
|
+
|
109
|
+
vendor_paths = T.let([], T::Array[String])
|
110
|
+
vendor_paths += paths_for_ruby_version(Gem::Version.create(RUBY_VERSION))
|
111
|
+
gemspecs.each do |gemspec|
|
112
|
+
vendor_paths += paths_for_gem_version(gemspec)
|
113
|
+
end
|
114
|
+
|
115
|
+
if vendor_paths.length > 0
|
116
|
+
vendor_rbis_within_paths(vendor_paths)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.output_file
|
121
|
+
SORBET_RBI_SORBET_TYPED
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
if $PROGRAM_NAME == __FILE__
|
126
|
+
Sorbet::Private::FetchRBIs.main
|
127
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# typed: false
|
3
|
+
|
4
|
+
require_relative './step_interface'
|
5
|
+
require_relative './t'
|
6
|
+
|
7
|
+
require 'bundler'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'set'
|
10
|
+
require 'digest'
|
11
|
+
|
12
|
+
class Sorbet; end
|
13
|
+
module Sorbet::Private; end
|
14
|
+
class Sorbet::Private::FindGemRBIs
|
15
|
+
XDG_CACHE_HOME = ENV['XDG_CACHE_HOME'] || "#{ENV['HOME']}/.cache"
|
16
|
+
RBI_CACHE_DIR = "#{XDG_CACHE_HOME}/sorbet/gem-rbis/"
|
17
|
+
GEM_DIR = 'rbi'
|
18
|
+
|
19
|
+
HEADER = Sorbet::Private::Serialize.header(false, 'find-gem-rbis')
|
20
|
+
|
21
|
+
include Sorbet::Private::StepInterface
|
22
|
+
|
23
|
+
# List of rbi folders in the gem's source
|
24
|
+
T::Sig::WithoutRuntime.sig {params(gemspec: T.untyped).returns(T.nilable(String))}
|
25
|
+
def self.paths_within_gem_sources(gemspec)
|
26
|
+
gem_rbi = "#{gemspec.full_gem_path}/#{GEM_DIR}"
|
27
|
+
gem_rbi if Dir.exist?(gem_rbi)
|
28
|
+
end
|
29
|
+
|
30
|
+
T::Sig::WithoutRuntime.sig {void}
|
31
|
+
def self.main
|
32
|
+
FileUtils.mkdir_p(RBI_CACHE_DIR) unless Dir.exist?(RBI_CACHE_DIR)
|
33
|
+
output_file = File.exist?('Gemfile.lock') ? RBI_CACHE_DIR + Digest::MD5.hexdigest(File.read('Gemfile.lock')) : nil
|
34
|
+
return unless output_file
|
35
|
+
gemspecs = Bundler.load.specs.sort_by(&:name)
|
36
|
+
|
37
|
+
gem_source_paths = T.let([], T::Array[String])
|
38
|
+
gemspecs.each do |gemspec|
|
39
|
+
gem_source_paths << paths_within_gem_sources(gemspec)
|
40
|
+
end
|
41
|
+
|
42
|
+
File.write(output_file, gem_source_paths.compact.join("\n"))
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.output_file
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if $PROGRAM_NAME == __FILE__
|
51
|
+
Sorbet::Private::FindGemRBIs.main
|
52
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
# typed: true
|
5
|
+
|
6
|
+
class Sorbet; end
|
7
|
+
module Sorbet::Private; end
|
8
|
+
|
9
|
+
require_relative './t'
|
10
|
+
require_relative './step_interface'
|
11
|
+
require_relative './require_everything'
|
12
|
+
require_relative './gem-generator-tracepoint/tracer'
|
13
|
+
require_relative './gem-generator-tracepoint/tracepoint_serializer'
|
14
|
+
|
15
|
+
require 'set'
|
16
|
+
|
17
|
+
# TODO switch the Struct handling to:
|
18
|
+
#
|
19
|
+
# class Subclass < Struct(:key1, :key2)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# generating:
|
23
|
+
#
|
24
|
+
# TemporaryStruct = Struct(:key1, :key2)
|
25
|
+
# class Subclass < TemporaryStruct
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# instead of manually defining every getter/setter
|
29
|
+
|
30
|
+
module Sorbet::Private
|
31
|
+
module GemGeneratorTracepoint
|
32
|
+
OUTPUT = 'sorbet/rbi/gems/'
|
33
|
+
|
34
|
+
include Sorbet::Private::StepInterface
|
35
|
+
|
36
|
+
T::Sig::WithoutRuntime.sig {params(output_dir: String).void}
|
37
|
+
def self.main(output_dir = OUTPUT)
|
38
|
+
trace_results = Tracer.trace do
|
39
|
+
Sorbet::Private::RequireEverything.require_everything
|
40
|
+
end
|
41
|
+
|
42
|
+
FileUtils.rm_r(output_dir) if Dir.exist?(output_dir)
|
43
|
+
TracepointSerializer.new(trace_results).serialize(output_dir)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.output_file
|
47
|
+
OUTPUT
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if $PROGRAM_NAME == __FILE__
|
53
|
+
Sorbet::Private::GemGeneratorTracepoint.main
|
54
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
require_relative '../serialize'
|
5
|
+
require_relative '../real_stdlib'
|
6
|
+
|
7
|
+
require 'set'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
|
11
|
+
module Sorbet::Private
|
12
|
+
module GemGeneratorTracepoint
|
13
|
+
ClassDefinition = Struct.new(:id, :klass, :defs)
|
14
|
+
|
15
|
+
class TracepointSerializer
|
16
|
+
SPECIAL_METHOD_NAMES = %w[! ~ +@ ** -@ * / % + - << >> & | ^ < <= => > >= == === != =~ !~ <=> [] []= `]
|
17
|
+
|
18
|
+
# These methods don't match the signatures of their parents, so if we let
|
19
|
+
# them monkeypatch, they won't be subtypes anymore. Just don't support the
|
20
|
+
# bad monkeypatches.
|
21
|
+
BAD_METHODS = [
|
22
|
+
['activesupport', 'Time', :to_s],
|
23
|
+
['activesupport', 'Time', :initialize],
|
24
|
+
]
|
25
|
+
|
26
|
+
HEADER = Sorbet::Private::Serialize.header('true', 'gems')
|
27
|
+
|
28
|
+
T::Sig::WithoutRuntime.sig {params(files: T::Hash, delegate_classes: T::Hash).void}
|
29
|
+
def initialize(files:, delegate_classes:)
|
30
|
+
@files = files
|
31
|
+
@delegate_classes = delegate_classes
|
32
|
+
|
33
|
+
@anonymous_map = {}
|
34
|
+
@prev_anonymous_id = 0
|
35
|
+
end
|
36
|
+
|
37
|
+
T::Sig::WithoutRuntime.sig {params(output_dir: String).void}
|
38
|
+
def serialize(output_dir)
|
39
|
+
gem_class_defs = preprocess(@files)
|
40
|
+
|
41
|
+
FileUtils.mkdir_p(output_dir) unless gem_class_defs.empty?
|
42
|
+
|
43
|
+
gem_class_defs.each do |gem, klass_ids|
|
44
|
+
File.open("#{File.join(output_dir, gem[:gem])}.rbi", 'w') do |f|
|
45
|
+
f.write(HEADER)
|
46
|
+
f.write("#
|
47
|
+
# If you would like to make changes to this file, great! Please create the gem's shim here:
|
48
|
+
#
|
49
|
+
# https://github.com/sorbet/sorbet-typed/new/master?filename=lib/#{gem[:gem]}/all/#{gem[:gem]}.rbi
|
50
|
+
#
|
51
|
+
")
|
52
|
+
f.write("# #{gem[:gem]}-#{gem[:version]}\n")
|
53
|
+
klass_ids.each do |klass_id, class_def|
|
54
|
+
klass = class_def.klass
|
55
|
+
|
56
|
+
f.write("#{Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? 'class' : 'module'} #{class_name(klass)}")
|
57
|
+
f.write(" < #{class_name(klass.superclass)}") if Sorbet::Private::RealStdlib.real_is_a?(klass, Class) && ![Object, nil].include?(klass.superclass)
|
58
|
+
f.write("\n")
|
59
|
+
|
60
|
+
rows = class_def.defs.map do |item|
|
61
|
+
case item[:type]
|
62
|
+
when :method
|
63
|
+
if !valid_method_name?(item[:method])
|
64
|
+
# warn("Invalid method name: #{klass}.#{item[:method]}")
|
65
|
+
next
|
66
|
+
end
|
67
|
+
if BAD_METHODS.include?([gem[:gem], class_name(klass), item[:method]])
|
68
|
+
next
|
69
|
+
end
|
70
|
+
begin
|
71
|
+
method = item[:singleton] ? klass.method(item[:method]) : klass.instance_method(item[:method])
|
72
|
+
"#{generate_method(method, !item[:singleton])}"
|
73
|
+
rescue NameError
|
74
|
+
end
|
75
|
+
when :include, :extend
|
76
|
+
name = class_name(item[item[:type]])
|
77
|
+
" #{item[:type]} #{name}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
rows = rows.compact.sort
|
81
|
+
f.write(rows.join("\n"))
|
82
|
+
f.write("\n") if !rows.empty?
|
83
|
+
f.write("end\n")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def preprocess(files)
|
92
|
+
gem_class_defs = files_to_gem_class_defs(files)
|
93
|
+
filter_unused(gem_class_defs)
|
94
|
+
end
|
95
|
+
|
96
|
+
def files_to_gem_class_defs(files)
|
97
|
+
# Transform tracer output into hash of gems to class definitions
|
98
|
+
files.each_with_object({}) do |(path, defined), gem_class_defs|
|
99
|
+
gem = gem_from_location(path)
|
100
|
+
if gem.nil?
|
101
|
+
warn("Can't find gem for #{path}") unless path.start_with?(Dir.pwd)
|
102
|
+
next
|
103
|
+
end
|
104
|
+
next if gem[:gem] == 'ruby'
|
105
|
+
# We're currently ignoring bundler, because we can't easily pin
|
106
|
+
# everyone to the same version of bundler in tests and in CI.
|
107
|
+
# There is an RBI for bundler in sorbet-typed.
|
108
|
+
next if gem[:gem] == 'bundler'
|
109
|
+
|
110
|
+
gem_class_defs[gem] ||= {}
|
111
|
+
defined.each do |item|
|
112
|
+
klass = item[:module]
|
113
|
+
klass_id = Sorbet::Private::RealStdlib.real_object_id(klass)
|
114
|
+
class_def = gem_class_defs[gem][klass_id] ||= ClassDefinition.new(klass_id, klass, [])
|
115
|
+
class_def.defs << item unless item[:type] == :module
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def filter_unused(gem_class_defs)
|
121
|
+
used = detect_used(gem_class_defs)
|
122
|
+
|
123
|
+
gem_class_defs.each_with_object({}) do |(gem, klass_defs), hsh|
|
124
|
+
hsh[gem] = klass_defs.select do |klass_id, klass_def|
|
125
|
+
klass = klass_def.klass
|
126
|
+
|
127
|
+
# Unused anon classes
|
128
|
+
next if !((Sorbet::Private::RealStdlib.real_is_a?(klass, Module) && !Sorbet::Private::RealStdlib.real_name(klass).nil?) || used?(klass_id, used))
|
129
|
+
|
130
|
+
# Anon delegate classes
|
131
|
+
next if Sorbet::Private::RealStdlib.real_is_a?(klass, Class) && klass.superclass == Delegator && !klass.name
|
132
|
+
|
133
|
+
# TODO should this be here?
|
134
|
+
# next if [Object, BasicObject, Hash].include?(klass)
|
135
|
+
true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def detect_used(gem_class_defs)
|
141
|
+
# subclassed, included, or extended
|
142
|
+
used = {}
|
143
|
+
|
144
|
+
gem_class_defs.each do |gem, klass_ids|
|
145
|
+
klass_ids.each do |klass_id, class_def|
|
146
|
+
klass = class_def.klass
|
147
|
+
|
148
|
+
# only add an anon module if it's used as a superclass of a non-anon module, or is included/extended by a non-anon module
|
149
|
+
used_value = Sorbet::Private::RealStdlib.real_is_a?(klass, Module) && !Sorbet::Private::RealStdlib.real_name(klass).nil? ? true : Sorbet::Private::RealStdlib.real_object_id(klass) # if non-anon, set it to true
|
150
|
+
(used[Sorbet::Private::RealStdlib.real_object_id(klass.superclass)] ||= Set.new) << used_value if Sorbet::Private::RealStdlib.real_is_a?(klass, Class)
|
151
|
+
# otherwise link to next anon class
|
152
|
+
class_def.defs.each do |item|
|
153
|
+
(used[item[item[:type]].object_id] ||= Set.new) << used_value if [:extend, :include].include?(item[:type])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
used
|
159
|
+
end
|
160
|
+
|
161
|
+
def used?(klass, used)
|
162
|
+
used_by = used[klass] || []
|
163
|
+
used_by.any? { |user| user == true || used?(user, used) }
|
164
|
+
end
|
165
|
+
|
166
|
+
def generate_method(method, instance, spaces = 2)
|
167
|
+
# method.parameters is an array of:
|
168
|
+
# a [:req, :a]
|
169
|
+
# b = 1 [:opt, :b]
|
170
|
+
# c: [:keyreq, :c]
|
171
|
+
# d: 1 [:key, :d]
|
172
|
+
# *e [:rest, :e]
|
173
|
+
# **f [:keyrest, :f]
|
174
|
+
# &g [:block, :g]
|
175
|
+
prefix = ' ' * spaces
|
176
|
+
parameters = method.parameters.map.with_index do |(type, name), index|
|
177
|
+
name = "arg#{index}" if name.nil? || name.empty?
|
178
|
+
case type
|
179
|
+
when :req
|
180
|
+
name
|
181
|
+
when :opt
|
182
|
+
"#{name} = nil"
|
183
|
+
when :keyreq
|
184
|
+
"#{name}:"
|
185
|
+
when :key
|
186
|
+
"#{name}: nil"
|
187
|
+
when :rest
|
188
|
+
"*#{name}"
|
189
|
+
when :keyrest
|
190
|
+
"**#{name}"
|
191
|
+
when :block
|
192
|
+
"&#{name}"
|
193
|
+
else
|
194
|
+
raise "Unknown parameter type: #{type}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
parameters = parameters.join(', ')
|
198
|
+
parameters = "(#{parameters})" unless parameters.empty?
|
199
|
+
"#{prefix}def #{instance ? '' : 'self.'}#{method.name}#{parameters}; end"
|
200
|
+
end
|
201
|
+
|
202
|
+
def anonymous_id
|
203
|
+
@prev_anonymous_id += 1
|
204
|
+
end
|
205
|
+
|
206
|
+
def gem_from_location(location)
|
207
|
+
match =
|
208
|
+
location&.match(/^.*\/(ruby)\/([\d.]+)\//) || # ruby stdlib
|
209
|
+
location&.match(/^.*\/(site_ruby)\/([\d.]+)\//) || # rubygems
|
210
|
+
location&.match(/^.*\/gems\/(?:ruby-)?[\d.]+(?:\/bundler)?\/gems\/([^\/]+)-([^-\/]+)\//i) # gem
|
211
|
+
if match.nil?
|
212
|
+
# uncomment to generate files for methods outside of gems
|
213
|
+
# {
|
214
|
+
# path: location,
|
215
|
+
# gem: location.gsub(/[\/\.]/, '_'),
|
216
|
+
# version: '1.0.0',
|
217
|
+
# }
|
218
|
+
nil
|
219
|
+
else
|
220
|
+
{
|
221
|
+
path: match[0],
|
222
|
+
gem: match[1],
|
223
|
+
version: match[2],
|
224
|
+
}
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def class_name(klass)
|
229
|
+
klass = @delegate_classes[Sorbet::Private::RealStdlib.real_object_id(klass)] || klass
|
230
|
+
name = Sorbet::Private::RealStdlib.real_name(klass) if Sorbet::Private::RealStdlib.real_is_a?(klass, Module)
|
231
|
+
|
232
|
+
# class/module has no name; it must be anonymous
|
233
|
+
if name.nil? || name == ""
|
234
|
+
middle = Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? klass.superclass : klass.class
|
235
|
+
id = @anonymous_map[Sorbet::Private::RealStdlib.real_object_id(klass)] ||= anonymous_id
|
236
|
+
return "Anonymous_#{class_name(middle).gsub('::', '_')}_#{id}"
|
237
|
+
end
|
238
|
+
|
239
|
+
# if the name doesn't only contain word characters and ':', or any part doesn't start with a capital, Sorbet doesn't support it
|
240
|
+
if name !~ /^[\w:]+$/ || !name.split('::').all? { |part| part =~ /^[A-Z]/ }
|
241
|
+
# warn("Invalid class name: #{name}")
|
242
|
+
id = @anonymous_map[Sorbet::Private::RealStdlib.real_object_id(klass)] ||= anonymous_id
|
243
|
+
return "InvalidName_#{name.gsub(/[^\w]/, '_').gsub(/0x([0-9a-f]+)/, '0x00')}_#{id}"
|
244
|
+
end
|
245
|
+
|
246
|
+
name
|
247
|
+
end
|
248
|
+
|
249
|
+
def valid_method_name?(symbol)
|
250
|
+
string = symbol.to_s
|
251
|
+
return true if SPECIAL_METHOD_NAMES.include?(string)
|
252
|
+
string =~ /^[[:word:]]+[?!=]?$/
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|