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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 488651a8c09596441a817bbc49958ea158308fb028e5e96af6c8d2671625dd23
4
- data.tar.gz: 6603a05ec2eeb1f5d90c8c0627caf5dcb254947f32b8421bdec2cc6cd81e6458
2
+ SHA1:
3
+ metadata.gz: 4a5b41ca48527927442222de4701655747e785db
4
+ data.tar.gz: 3f85bed09a6177796913319e2d6742a61cbb0b4d
5
5
  SHA512:
6
- metadata.gz: 93874fba0b23854f5d6daca67017208aa9bfcfb02b79123e9a8b2f4a88371a34b4e68370976585c0440d4fe194dd1e50bc916b8f3b2bd960834beb5ac5eee5d7
7
- data.tar.gz: a856dab916f3d965a80eb27b579ef9ad3a42f0caa01492b6312dbfe4a83b3aa42c4a1ee53ae0d2380a5b50fe269163e27b7e1a57d3658e9ef825ada0635316b5
6
+ metadata.gz: 38f9dd516cea9f1a20448972395c6f19bc27b14fc9c2629631bfc99d587f11d8567663f0b2b41bfefba2c8e06ec26830d54a5e370c04771863ab99300e965582
7
+ data.tar.gz: d457e3ba50b5a6d75813cfa0ac2803e05ffe322576a48e211b7b35dfb3a3d78a042d5dec01755b8a4f8af2638cd9034afce85eb641c32cf0ba050ba634cc7ed2
data/bin/srb ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env bash
2
+ #!/usr/bin/env ruby
3
+
4
+ # A total hack, but this file is both a valid ruby script and a bash script.
5
+ =begin 2>/dev/null
6
+
7
+ # From here on in, it is all bash until the last line
8
+
9
+ help_and_exit() {
10
+ cat <<EOF
11
+ A type checker for Ruby
12
+
13
+ Usage:
14
+ srb Same as "srb t"
15
+ srb (init | initialize) Initializes the \`sorbet\` directory
16
+ srb rbi [options] Manage the \`sorbet\` directory
17
+ srb (t | tc | typecheck) [options] Typechecks the code
18
+
19
+ Options:
20
+ -h, --help View help for this subcommand.
21
+ --version Show version.
22
+
23
+ For full help:
24
+ https://sorbet.org
25
+ EOF
26
+ exit 0
27
+ }
28
+
29
+ subcommand=$1
30
+ shift
31
+
32
+ # /path/to/gems/sorbet-0.0.1/bin/srb
33
+ srb_path="${BASH_SOURCE[0]}"
34
+
35
+ compute_md5() {
36
+ if command -v md5sum > /dev/null; then
37
+ md5sum "$1"
38
+ else
39
+ md5 -r "$1"
40
+ fi
41
+ }
42
+
43
+ typecheck() {
44
+ args=""
45
+ cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/sorbet/gem-rbis"
46
+
47
+ if [ -f Gemfile.lock ]; then
48
+ [ -d "$cache_dir" ] || mkdir "$cache_dir"
49
+ cache_hash=$(compute_md5 Gemfile.lock | awk '{ print $1 }')
50
+ cache_file="${cache_dir}/${cache_hash}"
51
+ if [ ! -f "$cache_file" ]; then
52
+ $0 rbi find-gem-rbis > /dev/null
53
+ fi
54
+ args="@$cache_file "
55
+ fi
56
+ args="$args$@"
57
+
58
+ if [ -n "$SRB_SORBET_EXE" ]; then
59
+ # shellcheck disable=SC2086
60
+ "$SRB_SORBET_EXE" $args
61
+ else
62
+ # We're using bash string operations here to avoid forking.
63
+ # Using dirname / basename / etc. would mean ~15ms for each call.
64
+
65
+ # /path/to/gems/sorbet-0.0.1
66
+ without_bin_srb="${srb_path%/bin/srb}"
67
+ # -0.0.1
68
+ version_suffix="${without_bin_srb##*/sorbet}"
69
+ # /path/to/gems
70
+ gems_path="${without_bin_srb%/sorbet*}"
71
+ # /path/to/gems/sorbet-static-0.0.1-darwin-17/libexec/sorbet
72
+ # (assumes people only have one platform-depdendent gem installed per version)
73
+ sorbet=("$gems_path/sorbet-static$version_suffix"*/libexec/sorbet)
74
+
75
+ # shellcheck disable=SC2086
76
+ "${sorbet[0]}" $args
77
+ fi
78
+ }
79
+
80
+ # Dynamically computing the path to the srb-rbi script lets this script work
81
+ # the same way in local development and when packaged as a gem. Either:
82
+ #
83
+ # /path/to/gems/sorbet-0.0.1/bin/srb-rbi
84
+ # path/to/bin/srb-rbi
85
+ srb_rbi_path="$srb_path-rbi"
86
+
87
+ case $subcommand in
88
+ "initialize" | "init")
89
+ "$srb_rbi_path"
90
+ ;;
91
+
92
+ "rbi")
93
+ "$srb_rbi_path" "$@"
94
+ ;;
95
+
96
+ "" | "typecheck" | "tc" | "t")
97
+ if [ ! -d sorbet ] && [ "$#" -eq 0 ]; then
98
+ echo "No sorbet/ directory found. Maybe you want to run 'srb init'?"
99
+ echo
100
+ help_and_exit
101
+ fi
102
+ typecheck "$@"
103
+ ;;
104
+
105
+ "--version")
106
+ typecheck --version
107
+ ;;
108
+
109
+ *)
110
+ echo "Unknown subcommand \`$subcommand\`"
111
+ help_and_exit
112
+ esac
113
+
114
+ exit $?
115
+ # The closing comment for ruby to be ok with this file
116
+ =end
117
+ exec(__FILE__, *ARGV)
data/bin/srb-rbi ADDED
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ class Sorbet; end
4
+ module Sorbet::Private; end
5
+
6
+ require_relative '../lib/t'
7
+ require_relative '../lib/step_interface'
8
+ require_relative '../lib/create-config'
9
+ require_relative '../lib/gem-generator-tracepoint'
10
+ require_relative '../lib/hidden-definition-finder'
11
+ require_relative '../lib/fetch-rbis'
12
+ require_relative '../lib/find-gem-rbis'
13
+ require_relative '../lib/suggest-typed'
14
+ require_relative '../lib/todo-rbi'
15
+
16
+ module Sorbet::Private::Main
17
+ def self.emojify(emoji, msg)
18
+ if STDOUT.isatty && RUBY_PLATFORM =~ /darwin/
19
+ "#{emoji} #{msg}"
20
+ else
21
+ msg
22
+ end
23
+ end
24
+
25
+ def self.yellow(msg)
26
+ if STDOUT.isatty
27
+ "\u001b[0;33m#{msg}\u001b[0m"
28
+ else
29
+ msg
30
+ end
31
+ end
32
+
33
+ def self.cyan(msg)
34
+ if STDOUT.isatty
35
+ "\u001b[0;36m#{msg}\u001b[0m"
36
+ else
37
+ msg
38
+ end
39
+ end
40
+
41
+ def self.main(argv)
42
+ command = parse_command(argv)
43
+ return command.call if command
44
+
45
+ puts "
46
+ #{emojify("👋", "Hey there!")}
47
+
48
+ This script will get this project ready to use with Sorbet by creating a
49
+ sorbet/ folder for your project. It will contain:
50
+
51
+ - a config file
52
+ - a bunch of 'RBI files'
53
+
54
+ RBI stands for 'Ruby Interface'; these files define classes, methods, and
55
+ constants that exist, but which Sorbet doesn't always know about.
56
+
57
+ #{emojify("⚠️ ", "Heads up:")}
58
+
59
+ To set up your project, this script will take two potentially destructive
60
+ actions:
61
+
62
+ 1. It will #{yellow("require every file in your project")}. Specifically, every script in
63
+ your project will be run, unless that script checks #{cyan("if __FILE__ == $PROGRAM_NAME")}
64
+ before running any code, or has the magic comment #{cyan("# typed: ignore")} in it.
65
+
66
+ 2. It will add a #{yellow("comment to the top of every file")} (like #{cyan("# typed: false")} or
67
+ #{cyan("# typed: true")}, depending on how many errors were found in that file.)
68
+
69
+ "
70
+ if ENV['SRB_YES'] != nil
71
+ puts "SRB_YES set, continuing"
72
+ else
73
+ STDOUT.write(emojify("❔", "Would you like to continue? [Y/n] "))
74
+ if STDIN.isatty && STDOUT.isatty
75
+ begin
76
+ input = Kernel.gets&.strip
77
+ if input.nil? || (input != '' && input != 'y' && input != 'Y')
78
+ puts "\nAborting"
79
+ Kernel.exit(1)
80
+ end
81
+ rescue Interrupt
82
+ puts "\nAborting"
83
+ Kernel.exit(1)
84
+ end
85
+ else
86
+ puts "\nNot running interactivly. Set SRB_YES=1 environment variable to proceed"
87
+ Kernel.exit(1)
88
+ end
89
+ end
90
+
91
+ # Create sorbet/config file
92
+ make_step(Sorbet::Private::CreateConfig).call
93
+
94
+ # Pull in the hand-written RBIs
95
+ make_step(Sorbet::Private::FetchRBIs).call
96
+
97
+ # Generate the RBIs from bundler
98
+ make_step(Sorbet::Private::GemGeneratorTracepoint).call
99
+
100
+ # Find the hidden methods
101
+ make_step(Sorbet::Private::HiddenMethodFinder).call
102
+
103
+ # Run sorbet and make constants to fix errors
104
+ make_step(Sorbet::Private::TodoRBI).call
105
+
106
+ # Run type suggestion once, and then generate the todo.rbi again.
107
+ #
108
+ # The first time we suggest typed sigils, a few files may be ignored if they
109
+ # use Ruby features Sorbet does not support. However, since we ran the
110
+ # todo.rbi generation before they were ignored, we discovered the constants
111
+ # that they defined and did not add the constants to todo.rbi file.
112
+ #
113
+ # Regenerating after one run of sigil suggestion will put those constants
114
+ # into the todo.rbi file.
115
+ Sorbet::Private::SuggestTyped.suggest_typed
116
+ Sorbet::Private::TodoRBI.main
117
+
118
+ # Put some `typed:` sigils
119
+ make_step(Sorbet::Private::SuggestTyped).call
120
+
121
+ puts "
122
+ #{emojify("✅", "Done!")}
123
+
124
+ This project is now set up for use with Sorbet. The sorbet/ folder should exist
125
+ and look something like this:
126
+
127
+ sorbet/
128
+ #{emojify("├──", " ")}config # Default options to passed to sorbet on every run
129
+ #{emojify("└──", " ")}rbi/
130
+ #{emojify("├──", " ")}sorbet-typed/ # Community writen type definition files for your gems
131
+ #{emojify("├──", " ")}gems/ # Autogenerated type definitions for your gems (from reflection)
132
+ #{emojify("├──", " ")}hidden-definitions/ # All definitions that exist at runtime, but Sorbet couldn't see statically
133
+ #{emojify("└──", " ")}todo.rbi # Constants which were still missing, even after the three steps above.
134
+
135
+ Please check this whole folder into version control.
136
+
137
+ #{emojify("➡️ ", "What's next?")}
138
+
139
+ Up to you! First things first, you'll probably want to typecheck your project:
140
+
141
+ #{cyan("srb tc")}
142
+
143
+ Other than that, it's up to you!
144
+ We recommend skimming these docs to get a feel for how to use Sorbet:
145
+
146
+ - Gradual Type Checking (#{cyan("https://sorbet.org/docs/gradual")})
147
+ - Enabling Static Checks (#{cyan("https://sorbet.org/docs/static")})
148
+ - RBI Files (#{cyan("https://sorbet.org/docs/rbi")})
149
+
150
+ If instead you want to explore your files locally, here are some things to try:
151
+
152
+ - Upgrade a file marked # typed: false to # typed: true.
153
+ Then, run #{cyan("srb tc")} and try to fix any errors.
154
+ - Add signatures to your methods with `sig`.
155
+ For how, read: #{cyan("https://sorbet.org/docs/sigs")}
156
+ - Check whether things that show up in the TODO RBI file actually exist in your project.
157
+ It's possible some of these constants are typos.
158
+ - Upgrade a file marked #{cyan("# typed: ignore")} to #{cyan("# typed: false")}.
159
+ Then, run #{cyan("srb tc")} and try to fix any errors.
160
+
161
+ #{emojify("🙌", "Please don't hesitate to give us your feedback!")}
162
+ "
163
+ end
164
+
165
+ def self.parse_command(argv)
166
+ return nil if argv.length == 0
167
+
168
+ banner = "Usage: srb rbi <command>
169
+ This script gets your current directory ready for using Sorbet by making all sorts of files in ./sorbet/. You should commit them to version control.
170
+
171
+ We recommend running it without any options which will execute all the commands in order. If you only need a certain piece, you can pass a command to just run that part.
172
+
173
+ You should re-run this script if your program ever stops typechecking due to dynamic code in your project."
174
+ commands = "
175
+ Common Commands:
176
+ help Print this message
177
+ <empty> | update Run all initialization commands
178
+
179
+ Specific Commands:
180
+ config Recreate sorbet/config
181
+ sorbet-typed Download community maintained type definitions for gems
182
+ gems Generate arity-only type definitions by requiring the gem
183
+ hidden-definitions Load all your code and generate type definitions for any dynamic code
184
+ todo Run Sorbet and generate constants which Sorbet errors on
185
+ suggest-typed Put the highest `typed:` sigil in each of your files
186
+ find-gem-rbis Find all `rbi/` direcotries in your gems and list them in `~/.cache/sorbet`
187
+ "
188
+
189
+ command = case (argv[0])
190
+ when 'help', '--help'
191
+ puts banner
192
+ puts commands
193
+ exit(1)
194
+ when 'config', Sorbet::Private::CreateConfig.output_file
195
+ make_step(Sorbet::Private::CreateConfig)
196
+ when 'sorbet-typed', Sorbet::Private::FetchRBIs.output_file, Sorbet::Private::FetchRBIs::SORBET_RBI_LIST
197
+ make_step(Sorbet::Private::FetchRBIs)
198
+ when 'gems', Sorbet::Private::GemGeneratorTracepoint.output_file
199
+ make_step(Sorbet::Private::GemGeneratorTracepoint)
200
+ when 'hidden-definitions', Sorbet::Private::HiddenMethodFinder.output_file
201
+ make_step(Sorbet::Private::HiddenMethodFinder)
202
+ when 'todo', Sorbet::Private::TodoRBI.output_file
203
+ make_step(Sorbet::Private::TodoRBI)
204
+ when 'suggest-typed'
205
+ make_step(Sorbet::Private::SuggestTyped)
206
+
207
+ when 'find-gem-rbis', "#{ENV['HOME']}/.cache/sorbet/gem-rbis", "#{ENV['HOME']}/.cache/sorbet/gem-rbis/"
208
+ make_step(Sorbet::Private::FindGemRBIs)
209
+
210
+ when 'update'
211
+ nil
212
+
213
+ else
214
+ puts "Unknown comand: #{argv[0]}"
215
+ puts commands
216
+ exit(1)
217
+ end
218
+
219
+ puts "Running command: #{argv[0]}"
220
+ command
221
+ end
222
+
223
+ T::Sig::WithoutRuntime.sig {params(step: Sorbet::Private::StepInterface).returns(T.proc.void)}
224
+ def self.make_step(step)
225
+ -> do
226
+ puts "Generating: #{step.output_file}" if step.output_file
227
+ step.main
228
+ end
229
+ end
230
+ end
231
+
232
+ Sorbet::Private::Main.main(ARGV)
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require_relative './real_stdlib'
5
+ require_relative './status'
6
+
7
+ # This class walks global namespace to find all modules and discover all of their names.
8
+ # At the time you ask for it it, it takes a "spashot" of the world.
9
+ # If new modules were defined after an instance of this class was created,
10
+ # they won't be visible through a previously returned instance.
11
+ class Sorbet::Private::ConstantLookupCache
12
+
13
+ ConstantEntry = Struct.new(:const_name, :found_name, :primary_name, :aliases, :const, :owner)
14
+
15
+ # We won't be able to see if someone mankeypatches these.
16
+ DEPRECATED_CONSTANTS = [
17
+ '::Bignum',
18
+ '::Config',
19
+ '::Data',
20
+ '::FALSE',
21
+ '::Fixnum',
22
+ '::NIL',
23
+ '::TRUE',
24
+ '::TimeoutError',
25
+ 'ERB::Compiler::SimpleScanner2',
26
+ 'Net::OpenSSL',
27
+ 'Object::Bignum',
28
+ 'Object::Config',
29
+ 'Object::Data',
30
+ 'Object::FALSE',
31
+ 'Object::Fixnum',
32
+ 'Object::NIL',
33
+ 'Object::TRUE',
34
+ 'Object::TimeoutError',
35
+ 'OpenSSL::Cipher::Cipher',
36
+ 'OpenSSL::Cipher::Digest',
37
+ 'OpenSSL::Digest::Cipher',
38
+ 'OpenSSL::Digest::Digest',
39
+ 'OpenSSL::SSL::SSLContext::METHODS',
40
+ 'Pry::Platform',
41
+ 'Pry::Prompt::MAP',
42
+ 'Sequel::BeforeHookFailed',
43
+ 'Sequel::Database::ResetIdentifierMangling',
44
+ 'Sequel::Error::AdapterNotFound',
45
+ 'Sequel::Error::InvalidOperation',
46
+ 'Sequel::Error::InvalidValue',
47
+ 'Sequel::Error::PoolTimeoutError',
48
+ 'Sequel::Error::Rollback',
49
+ 'Sequel::Model::ANONYMOUS_MODEL_CLASSES',
50
+ 'Sequel::Model::ANONYMOUS_MODEL_CLASSES_MUTEX',
51
+ 'Sequel::UnbindDuplicate',
52
+ 'Sequel::Unbinder',
53
+ 'YARD::Parser::Ruby::Legacy::RipperParser',
54
+ ].freeze
55
+
56
+ def initialize
57
+ @all_constants = {}
58
+ dfs_module(Object, nil, @all_constants, nil)
59
+ Sorbet::Private::Status.done
60
+ @consts_by_name = {}
61
+ @all_constants.each_value do |struct|
62
+ fill_primary_name(struct)
63
+ @consts_by_name[struct.primary_name] = struct.const
64
+ end
65
+ end
66
+
67
+ def all_module_names
68
+ ret = @all_constants.select {|_k, v| Sorbet::Private::RealStdlib.real_is_a?(v.const, Module)}.map do |_key, struct|
69
+ raise "should never happen" if !struct.primary_name
70
+ struct.primary_name
71
+ end
72
+ ret
73
+ end
74
+
75
+ def all_named_modules
76
+ ret = @all_constants.select {|_k, v| Sorbet::Private::RealStdlib.real_is_a?(v.const, Module)}.map do |_key, struct|
77
+ raise "should never happen" if !struct.primary_name
78
+ struct.const
79
+ end
80
+ ret
81
+ end
82
+
83
+ def all_module_aliases
84
+ ret = {}
85
+
86
+ @all_constants.map do |_key, struct|
87
+ next if struct.nil? || !Sorbet::Private::RealStdlib.real_is_a?(struct.const, Module) || struct.aliases.size < 2
88
+ ret[struct.primary_name] = struct.aliases.reject {|name| name == struct.primary_name}
89
+ end
90
+ ret
91
+ end
92
+
93
+ def class_by_name(name)
94
+ @consts_by_name[name]
95
+ end
96
+
97
+ def name_by_class(klass)
98
+ @all_constants[Sorbet::Private::RealStdlib.real_object_id(klass)]&.primary_name
99
+ end
100
+
101
+ private def dfs_module(mod, prefix, ret, owner)
102
+ raise "error #{prefix}: #{mod} is not a module" if !Sorbet::Private::RealStdlib.real_is_a?(mod, Module)
103
+ name = Sorbet::Private::RealStdlib.real_name(mod)
104
+ Sorbet::Private::Status.say("Naming #{name}", print_without_tty: false)
105
+ return if name == 'RSpec::ExampleGroups' # These are all anonymous classes and will be quadratic in the number of classes to name them. We also know they don't have any hidden definitions
106
+ begin
107
+ constants = Sorbet::Private::RealStdlib.real_constants(mod)
108
+ rescue TypeError
109
+ puts "Failed to call `constants` on #{prefix}"
110
+ return nil
111
+ end
112
+ go_deeper = [] # make this a bfs to prefer shorter names
113
+ constants.each do |nested|
114
+ begin
115
+ next if Sorbet::Private::RealStdlib.real_autoload?(mod, nested) # some constants aren't autoloaded even after require_everything, e.g. StateMachine::Graph
116
+
117
+ begin
118
+ next if DEPRECATED_CONSTANTS.include?("#{prefix}::#{nested}") # avoid stdout spew
119
+ rescue NameError
120
+ # If it isn't to_s-able, that is ok, it won't be in our deprecated list
121
+ nil
122
+ end
123
+
124
+ begin
125
+ nested_constant = Sorbet::Private::RealStdlib.real_const_get(mod, nested, false) # rubocop:disable PrisonGuard/NoDynamicConstAccess
126
+ rescue LoadError
127
+ puts "Failed to load #{name}::#{nested}"
128
+ next
129
+ rescue NameError, ArgumentError
130
+ puts "Failed to load #{name}::#{nested}"
131
+ # some stuff fails to load, like
132
+ # `const_get': uninitialized constant YARD::Parser::Ruby::Legacy::RipperParser (NameError)
133
+ # Did you mean? YARD::Parser::Ruby::Legacy::RipperParser
134
+ end
135
+
136
+ object_id = Sorbet::Private::RealStdlib.real_object_id(nested_constant)
137
+ maybe_seen_already = ret[object_id]
138
+ if Object.eql?(mod) || !prefix
139
+ nested_name = nested.to_s
140
+ else
141
+ nested_name = prefix + "::" + nested.to_s
142
+ end
143
+ if maybe_seen_already
144
+ if nested_name != maybe_seen_already.primary_name
145
+ maybe_seen_already.aliases << nested_name
146
+ end
147
+ if maybe_seen_already.primary_name.nil? && Sorbet::Private::RealStdlib.real_is_a?(nested_constant, Module)
148
+ realName = Sorbet::Private::RealStdlib.real_name(nested_constant)
149
+ maybe_seen_already.primary_name = realName
150
+ end
151
+ else
152
+ entry = ConstantEntry.new(nested, nested_name, nil, [nested_name], nested_constant, owner)
153
+ ret[object_id] = entry
154
+
155
+ if Sorbet::Private::RealStdlib.real_is_a?(nested_constant, Module) && Object != nested_constant
156
+ go_deeper << [entry, nested_constant, nested_name]
157
+ end
158
+ end
159
+
160
+ end
161
+ end
162
+
163
+ # Horrible horrible hack to get private constants that are `include`d.
164
+ # This doesn't recurse so you can't get private constants inside the private
165
+ # constants, but I really hope you don't need those...
166
+ Sorbet::Private::RealStdlib.real_ancestors(mod).each do |ancestor|
167
+ object_id = Sorbet::Private::RealStdlib.real_object_id(ancestor)
168
+ name = Sorbet::Private::RealStdlib.real_name(ancestor)
169
+ next unless name
170
+ next if ret[object_id]
171
+ prefix, _, const_name = name.rpartition('::')
172
+ entry = ConstantEntry.new(const_name.to_sym, name, name, [name], ancestor, nil)
173
+ ret[object_id] = entry
174
+ end
175
+ sorted_deeper = go_deeper.sort_by do |entry, nested_constant, nested_name|
176
+ Sorbet::Private::RealStdlib.real_name(nested_constant)
177
+ end
178
+ sorted_deeper.each do |entry, nested_constant, nested_name|
179
+ dfs_module(nested_constant, nested_name, ret, entry)
180
+ end
181
+ nil
182
+ end
183
+
184
+ private def fill_primary_name(struct)
185
+ return if struct.primary_name
186
+ if !struct.owner
187
+ struct.primary_name = struct.found_name
188
+ else
189
+ fill_primary_name(struct.owner)
190
+ struct.primary_name = struct.owner.primary_name + "::" + struct.const_name.to_s
191
+ end
192
+ end
193
+ end