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
@@ -0,0 +1,37 @@
|
|
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
|
+
if File.file?(SORBET_CONFIG_FILE)
|
20
|
+
puts "Reusing existing config file: #{SORBET_CONFIG_FILE}"
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
File.open(SORBET_CONFIG_FILE, 'w') do |f|
|
25
|
+
f.puts('--dir')
|
26
|
+
f.puts('.')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.output_file
|
31
|
+
SORBET_CONFIG_FILE
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if $PROGRAM_NAME == __FILE__
|
36
|
+
Sorbet::Private::CreateConfig.main
|
37
|
+
end
|
data/lib/fetch-rbis.rb
ADDED
@@ -0,0 +1,129 @@
|
|
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 --all}) {|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
|
+
# Remove the sorbet-typed directory before repopulating it.
|
116
|
+
FileUtils.rm_r(SORBET_RBI_SORBET_TYPED) if Dir.exist?(SORBET_RBI_SORBET_TYPED)
|
117
|
+
if vendor_paths.length > 0
|
118
|
+
vendor_rbis_within_paths(vendor_paths)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.output_file
|
123
|
+
SORBET_RBI_SORBET_TYPED
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
if $PROGRAM_NAME == __FILE__
|
128
|
+
Sorbet::Private::FetchRBIs.main
|
129
|
+
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,267 @@
|
|
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
|
+
require 'delegate'
|
10
|
+
|
11
|
+
|
12
|
+
module Sorbet::Private
|
13
|
+
module GemGeneratorTracepoint
|
14
|
+
ClassDefinition = Struct.new(:id, :klass, :defs)
|
15
|
+
|
16
|
+
class TracepointSerializer
|
17
|
+
SPECIAL_METHOD_NAMES = %w[! ~ +@ ** -@ * / % + - << >> & | ^ < <= => > >= == === != =~ !~ <=> [] []= `]
|
18
|
+
|
19
|
+
BAD_METHODS = [
|
20
|
+
# These methods don't match the signatures of their parents, so if we let
|
21
|
+
# them monkeypatch, they won't be subtypes anymore. Just don't support the
|
22
|
+
# bad monkeypatches.
|
23
|
+
['activesupport', 'Time', :to_s],
|
24
|
+
['activesupport', 'Time', :initialize],
|
25
|
+
|
26
|
+
# These methods cause TracepointSerializer to hang the Ruby process when
|
27
|
+
# running Ruby 2.3. See https://github.com/sorbet/sorbet/issues/1145
|
28
|
+
['activesupport', 'ActiveSupport::Deprecation', :new],
|
29
|
+
['activesupport', 'ActiveSupport::Deprecation', :allocate],
|
30
|
+
]
|
31
|
+
|
32
|
+
HEADER = Sorbet::Private::Serialize.header('true', 'gems')
|
33
|
+
|
34
|
+
T::Sig::WithoutRuntime.sig {params(files: T::Hash, delegate_classes: T::Hash).void}
|
35
|
+
def initialize(files:, delegate_classes:)
|
36
|
+
@files = files
|
37
|
+
@delegate_classes = delegate_classes
|
38
|
+
|
39
|
+
@anonymous_map = {}
|
40
|
+
@prev_anonymous_id = 0
|
41
|
+
end
|
42
|
+
|
43
|
+
T::Sig::WithoutRuntime.sig {params(output_dir: String).void}
|
44
|
+
def serialize(output_dir)
|
45
|
+
gem_class_defs = preprocess(@files)
|
46
|
+
|
47
|
+
FileUtils.mkdir_p(output_dir) unless gem_class_defs.empty?
|
48
|
+
|
49
|
+
gem_class_defs.each do |gem, klass_ids|
|
50
|
+
File.open("#{File.join(output_dir, gem[:gem])}.rbi", 'w') do |f|
|
51
|
+
f.write(HEADER)
|
52
|
+
f.write("#
|
53
|
+
# If you would like to make changes to this file, great! Please create the gem's shim here:
|
54
|
+
#
|
55
|
+
# https://github.com/sorbet/sorbet-typed/new/master?filename=lib/#{gem[:gem]}/all/#{gem[:gem]}.rbi
|
56
|
+
#
|
57
|
+
")
|
58
|
+
f.write("# #{gem[:gem]}-#{gem[:version]}\n\n")
|
59
|
+
klass_ids.each do |klass_id, class_def|
|
60
|
+
klass = class_def.klass
|
61
|
+
|
62
|
+
f.write("#{Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? 'class' : 'module'} #{class_name(klass)}")
|
63
|
+
f.write(" < #{class_name(klass.superclass)}") if Sorbet::Private::RealStdlib.real_is_a?(klass, Class) && ![Object, nil].include?(klass.superclass)
|
64
|
+
f.write("\n")
|
65
|
+
|
66
|
+
rows = class_def.defs.map do |item|
|
67
|
+
case item[:type]
|
68
|
+
when :method
|
69
|
+
if !valid_method_name?(item[:method])
|
70
|
+
# warn("Invalid method name: #{klass}.#{item[:method]}")
|
71
|
+
next
|
72
|
+
end
|
73
|
+
if BAD_METHODS.include?([gem[:gem], class_name(klass), item[:method]])
|
74
|
+
next
|
75
|
+
end
|
76
|
+
begin
|
77
|
+
method = item[:singleton] ? Sorbet::Private::RealStdlib.real_method(klass, item[:method]) : klass.instance_method(item[:method])
|
78
|
+
|
79
|
+
"#{generate_method(method, !item[:singleton])}"
|
80
|
+
rescue NameError
|
81
|
+
end
|
82
|
+
when :include, :extend
|
83
|
+
name = class_name(item[item[:type]])
|
84
|
+
" #{item[:type]} #{name}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
rows = rows.compact.sort
|
88
|
+
f.write(rows.join("\n"))
|
89
|
+
f.write("\n") if !rows.empty?
|
90
|
+
f.write("end\n")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def preprocess(files)
|
99
|
+
gem_class_defs = files_to_gem_class_defs(files)
|
100
|
+
filter_unused(gem_class_defs)
|
101
|
+
end
|
102
|
+
|
103
|
+
def files_to_gem_class_defs(files)
|
104
|
+
# Transform tracer output into hash of gems to class definitions
|
105
|
+
files.each_with_object({}) do |(path, defined), gem_class_defs|
|
106
|
+
gem = gem_from_location(path)
|
107
|
+
if gem.nil?
|
108
|
+
warn("Can't find gem for #{path}") unless path.start_with?(Dir.pwd)
|
109
|
+
next
|
110
|
+
end
|
111
|
+
next if gem[:gem] == 'ruby'
|
112
|
+
# We're currently ignoring bundler, because we can't easily pin
|
113
|
+
# everyone to the same version of bundler in tests and in CI.
|
114
|
+
# There is an RBI for bundler in sorbet-typed.
|
115
|
+
next if gem[:gem] == 'bundler'
|
116
|
+
# We ignore sorbet-runtime because because we write the RBI for it into our payload.
|
117
|
+
# For some reason, runtime reflection generates methods with incorrect arities.
|
118
|
+
next if gem[:gem] == 'sorbet-runtime'
|
119
|
+
|
120
|
+
gem_class_defs[gem] ||= {}
|
121
|
+
defined.each do |item|
|
122
|
+
klass = item[:module]
|
123
|
+
klass_id = Sorbet::Private::RealStdlib.real_object_id(klass)
|
124
|
+
class_def = gem_class_defs[gem][klass_id] ||= ClassDefinition.new(klass_id, klass, [])
|
125
|
+
class_def.defs << item unless item[:type] == :module
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def filter_unused(gem_class_defs)
|
131
|
+
used = detect_used(gem_class_defs)
|
132
|
+
|
133
|
+
gem_class_defs.each_with_object({}) do |(gem, klass_defs), hsh|
|
134
|
+
hsh[gem] = klass_defs.select do |klass_id, klass_def|
|
135
|
+
klass = klass_def.klass
|
136
|
+
|
137
|
+
# Unused anon classes
|
138
|
+
next if !((Sorbet::Private::RealStdlib.real_is_a?(klass, Module) && !Sorbet::Private::RealStdlib.real_name(klass).nil?) || used?(klass_id, used))
|
139
|
+
|
140
|
+
# Anon delegate classes
|
141
|
+
next if Sorbet::Private::RealStdlib.real_is_a?(klass, Class) && klass.superclass == Delegator && !klass.name
|
142
|
+
|
143
|
+
# TODO should this be here?
|
144
|
+
# next if [Object, BasicObject, Hash].include?(klass)
|
145
|
+
true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def detect_used(gem_class_defs)
|
151
|
+
# subclassed, included, or extended
|
152
|
+
used = {}
|
153
|
+
|
154
|
+
gem_class_defs.each do |gem, klass_ids|
|
155
|
+
klass_ids.each do |klass_id, class_def|
|
156
|
+
klass = class_def.klass
|
157
|
+
|
158
|
+
# 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
|
159
|
+
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
|
160
|
+
(used[Sorbet::Private::RealStdlib.real_object_id(klass.superclass)] ||= Set.new) << used_value if Sorbet::Private::RealStdlib.real_is_a?(klass, Class)
|
161
|
+
# otherwise link to next anon class
|
162
|
+
class_def.defs.each do |item|
|
163
|
+
(used[item[item[:type]].object_id] ||= Set.new) << used_value if [:extend, :include].include?(item[:type])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
used
|
169
|
+
end
|
170
|
+
|
171
|
+
def used?(klass, used)
|
172
|
+
used_by = used[klass] || []
|
173
|
+
used_by.any? { |user| user == true || used?(user, used) }
|
174
|
+
end
|
175
|
+
|
176
|
+
def generate_method(method, instance, spaces = 2)
|
177
|
+
# method.parameters is an array of:
|
178
|
+
# a [:req, :a]
|
179
|
+
# b = 1 [:opt, :b]
|
180
|
+
# c: [:keyreq, :c]
|
181
|
+
# d: 1 [:key, :d]
|
182
|
+
# *e [:rest, :e]
|
183
|
+
# **f [:keyrest, :f]
|
184
|
+
# &g [:block, :g]
|
185
|
+
prefix = ' ' * spaces
|
186
|
+
parameters = method.parameters.map.with_index do |(type, name), index|
|
187
|
+
name = "arg#{index}" if name.nil? || name.empty?
|
188
|
+
case type
|
189
|
+
when :req
|
190
|
+
name
|
191
|
+
when :opt
|
192
|
+
"#{name} = nil"
|
193
|
+
when :keyreq
|
194
|
+
"#{name}:"
|
195
|
+
when :key
|
196
|
+
"#{name}: nil"
|
197
|
+
when :rest
|
198
|
+
"*#{name}"
|
199
|
+
when :keyrest
|
200
|
+
"**#{name}"
|
201
|
+
when :block
|
202
|
+
"&#{name}"
|
203
|
+
else
|
204
|
+
raise "Unknown parameter type: #{type}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
parameters = parameters.join(', ')
|
208
|
+
parameters = "(#{parameters})" unless parameters.empty?
|
209
|
+
"#{prefix}def #{instance ? '' : 'self.'}#{method.name}#{parameters}; end"
|
210
|
+
end
|
211
|
+
|
212
|
+
def anonymous_id
|
213
|
+
@prev_anonymous_id += 1
|
214
|
+
end
|
215
|
+
|
216
|
+
def gem_from_location(location)
|
217
|
+
match =
|
218
|
+
location&.match(/^.*\/(ruby)\/([\d.]+)\//) || # ruby stdlib
|
219
|
+
location&.match(/^.*\/(jruby)-([\d.]+)\//) || # jvm ruby stdlib
|
220
|
+
location&.match(/^.*\/(site_ruby)\/([\d.]+)\//) || # rubygems
|
221
|
+
location&.match(/^.*\/gems\/(?:(?:j?ruby-)?[\d.]+(?:@[^\/]+)?(?:\/bundler)?\/)?gems\/([^\/]+)-([^-\/]+)\//i) # gem
|
222
|
+
if match.nil?
|
223
|
+
# uncomment to generate files for methods outside of gems
|
224
|
+
# {
|
225
|
+
# path: location,
|
226
|
+
# gem: location.gsub(/[\/\.]/, '_'),
|
227
|
+
# version: '1.0.0',
|
228
|
+
# }
|
229
|
+
nil
|
230
|
+
else
|
231
|
+
{
|
232
|
+
path: match[0],
|
233
|
+
gem: match[1],
|
234
|
+
version: match[2],
|
235
|
+
}
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def class_name(klass)
|
240
|
+
klass = @delegate_classes[Sorbet::Private::RealStdlib.real_object_id(klass)] || klass
|
241
|
+
name = Sorbet::Private::RealStdlib.real_name(klass) if Sorbet::Private::RealStdlib.real_is_a?(klass, Module)
|
242
|
+
|
243
|
+
# class/module has no name; it must be anonymous
|
244
|
+
if name.nil? || name == ""
|
245
|
+
middle = Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? klass.superclass : klass.class
|
246
|
+
id = @anonymous_map[Sorbet::Private::RealStdlib.real_object_id(klass)] ||= anonymous_id
|
247
|
+
return "Anonymous_#{class_name(middle).gsub('::', '_')}_#{id}"
|
248
|
+
end
|
249
|
+
|
250
|
+
# if the name doesn't only contain word characters and ':', or any part doesn't start with a capital, Sorbet doesn't support it
|
251
|
+
if name !~ /^[\w:]+$/ || !name.split('::').all? { |part| part =~ /^[A-Z]/ }
|
252
|
+
# warn("Invalid class name: #{name}")
|
253
|
+
id = @anonymous_map[Sorbet::Private::RealStdlib.real_object_id(klass)] ||= anonymous_id
|
254
|
+
return "InvalidName_#{name.gsub(/[^\w]/, '_').gsub(/0x([0-9a-f]+)/, '0x00')}_#{id}"
|
255
|
+
end
|
256
|
+
|
257
|
+
name
|
258
|
+
end
|
259
|
+
|
260
|
+
def valid_method_name?(symbol)
|
261
|
+
string = symbol.to_s
|
262
|
+
return true if SPECIAL_METHOD_NAMES.include?(string)
|
263
|
+
string =~ /^[[:word:]]+[?!=]?$/
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|