tapioca 0.8.3 → 0.9.2
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 +4 -4
- data/Gemfile +5 -2
- data/README.md +188 -36
- data/lib/tapioca/cli.rb +130 -66
- data/lib/tapioca/commands/annotations.rb +167 -34
- data/lib/tapioca/commands/check_shims.rb +101 -0
- data/lib/tapioca/commands/{init.rb → configure.rb} +1 -1
- data/lib/tapioca/commands/dsl.rb +1 -1
- data/lib/tapioca/commands/gem.rb +15 -10
- data/lib/tapioca/commands.rb +2 -1
- data/lib/tapioca/dsl/compiler.rb +1 -13
- data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +17 -0
- data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +5 -4
- data/lib/tapioca/dsl/compilers/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/compilers/protobuf.rb +6 -0
- data/lib/tapioca/dsl/compilers.rb +0 -4
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +21 -3
- data/lib/tapioca/dsl/pipeline.rb +0 -2
- data/lib/tapioca/dsl.rb +8 -0
- data/lib/tapioca/executor.rb +0 -3
- data/lib/tapioca/gem/events.rb +22 -3
- data/lib/tapioca/gem/listeners/base.rb +11 -0
- data/lib/tapioca/gem/listeners/dynamic_mixins.rb +5 -0
- data/lib/tapioca/gem/listeners/foreign_constants.rb +65 -0
- data/lib/tapioca/gem/listeners/methods.rb +7 -18
- data/lib/tapioca/gem/listeners/mixins.rb +31 -10
- data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_helpers.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_props.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_signatures.rb +6 -1
- data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +5 -0
- data/lib/tapioca/gem/listeners/source_location.rb +67 -0
- data/lib/tapioca/gem/listeners/subconstants.rb +5 -0
- data/lib/tapioca/gem/listeners/yard_doc.rb +5 -0
- data/lib/tapioca/gem/listeners.rb +2 -0
- data/lib/tapioca/gem/pipeline.rb +64 -19
- data/lib/tapioca/gem.rb +6 -0
- data/lib/tapioca/gemfile.rb +7 -6
- data/lib/tapioca/helpers/cli_helper.rb +8 -2
- data/lib/tapioca/helpers/config_helper.rb +0 -2
- data/lib/tapioca/helpers/env_helper.rb +16 -0
- data/lib/tapioca/helpers/rbi_files_helper.rb +255 -0
- data/lib/tapioca/helpers/rbi_helper.rb +98 -94
- data/lib/tapioca/helpers/sorbet_helper.rb +2 -3
- data/lib/tapioca/helpers/test/content.rb +0 -2
- data/lib/tapioca/helpers/test/template.rb +0 -2
- data/lib/tapioca/internal.rb +36 -12
- data/lib/tapioca/rbi_ext/model.rb +2 -15
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +18 -16
- data/lib/tapioca/runtime/reflection.rb +26 -0
- data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -16
- data/lib/tapioca/runtime/trackers/mixin.rb +49 -14
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +1 -4
- data/lib/tapioca/sorbet_ext/name_patch.rb +15 -5
- data/lib/tapioca/sorbet_ext/proc_bind_patch.rb +40 -0
- data/lib/tapioca/static/requires_compiler.rb +0 -2
- data/lib/tapioca/static/symbol_loader.rb +26 -30
- data/lib/tapioca/static/symbol_table_parser.rb +0 -3
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +3 -0
- metadata +24 -7
- data/lib/tapioca/dsl/helpers/param_helper.rb +0 -55
- data/lib/tapioca/helpers/shims_helper.rb +0 -115
- data/lib/tapioca/helpers/signatures_helper.rb +0 -17
- data/lib/tapioca/helpers/type_variable_helper.rb +0 -43
data/lib/tapioca/cli.rb
CHANGED
@@ -5,8 +5,7 @@ module Tapioca
|
|
5
5
|
class Cli < Thor
|
6
6
|
include CliHelper
|
7
7
|
include ConfigHelper
|
8
|
-
include
|
9
|
-
include ShimsHelper
|
8
|
+
include EnvHelper
|
10
9
|
|
11
10
|
FILE_HEADER_OPTION_DESC = "Add a \"This file is generated\" header on top of each generated RBI file"
|
12
11
|
|
@@ -22,12 +21,23 @@ module Tapioca
|
|
22
21
|
desc: "Verbose output for debugging purposes",
|
23
22
|
default: false
|
24
23
|
|
25
|
-
desc "init", "
|
24
|
+
desc "init", "get project ready for type checking"
|
26
25
|
def init
|
27
|
-
|
26
|
+
invoke(:configure)
|
27
|
+
invoke(:annotations)
|
28
|
+
invoke(:gem)
|
29
|
+
invoke(:todo)
|
30
|
+
|
31
|
+
print_init_next_steps
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "configure", "initialize folder structure and type checking configuration"
|
35
|
+
option :postrequire, type: :string, default: DEFAULT_POSTREQUIRE_FILE
|
36
|
+
def configure
|
37
|
+
command = Commands::Configure.new(
|
28
38
|
sorbet_config: SORBET_CONFIG_FILE,
|
29
|
-
tapioca_config:
|
30
|
-
default_postrequire:
|
39
|
+
tapioca_config: options[:config],
|
40
|
+
default_postrequire: options[:postrequire]
|
31
41
|
)
|
32
42
|
command.execute
|
33
43
|
end
|
@@ -100,8 +110,15 @@ module Tapioca
|
|
100
110
|
option :rbi_max_line_length,
|
101
111
|
type: :numeric,
|
102
112
|
desc: "Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped",
|
103
|
-
default:
|
113
|
+
default: DEFAULT_RBI_MAX_LINE_LENGTH
|
114
|
+
option :environment,
|
115
|
+
aliases: ["-e"],
|
116
|
+
type: :string,
|
117
|
+
desc: "The Rack/Rails environment to use when generating RBIs",
|
118
|
+
default: DEFAULT_ENVIRONMENT
|
104
119
|
def dsl(*constants)
|
120
|
+
set_environment(options)
|
121
|
+
|
105
122
|
command = Commands::Dsl.new(
|
106
123
|
requested_constants: constants,
|
107
124
|
outpath: Pathname.new(options[:outdir]),
|
@@ -173,6 +190,10 @@ module Tapioca
|
|
173
190
|
type: :boolean,
|
174
191
|
desc: "Include YARD documentation from sources when generating RBIs. Warning: this might be slow",
|
175
192
|
default: true
|
193
|
+
option :loc,
|
194
|
+
type: :boolean,
|
195
|
+
desc: "Include comments with source location when generating RBIs",
|
196
|
+
default: true
|
176
197
|
option :exported_gem_rbis,
|
177
198
|
type: :boolean,
|
178
199
|
desc: "Include RBIs found in the `rbi/` directory of the gem",
|
@@ -194,9 +215,16 @@ module Tapioca
|
|
194
215
|
option :rbi_max_line_length,
|
195
216
|
type: :numeric,
|
196
217
|
desc: "Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped",
|
197
|
-
default:
|
218
|
+
default: DEFAULT_RBI_MAX_LINE_LENGTH
|
219
|
+
option :environment,
|
220
|
+
aliases: ["-e"],
|
221
|
+
type: :string,
|
222
|
+
desc: "The Rack/Rails environment to use when generating RBIs",
|
223
|
+
default: DEFAULT_ENVIRONMENT
|
198
224
|
def gem(*gems)
|
199
225
|
Tapioca.silence_warnings do
|
226
|
+
set_environment(options)
|
227
|
+
|
200
228
|
all = options[:all]
|
201
229
|
verify = options[:verify]
|
202
230
|
|
@@ -208,7 +236,8 @@ module Tapioca
|
|
208
236
|
typed_overrides: options[:typed_overrides],
|
209
237
|
outpath: Pathname.new(options[:outdir]),
|
210
238
|
file_header: options[:file_header],
|
211
|
-
|
239
|
+
include_doc: options[:doc],
|
240
|
+
include_loc: options[:loc],
|
212
241
|
include_exported_rbis: options[:exported_gem_rbis],
|
213
242
|
number_of_workers: options[:workers],
|
214
243
|
auto_strictness: options[:auto_strictness],
|
@@ -231,7 +260,7 @@ module Tapioca
|
|
231
260
|
end
|
232
261
|
|
233
262
|
if gems.empty? && !all
|
234
|
-
command.sync(should_verify: verify)
|
263
|
+
command.sync(should_verify: verify, exclude: options[:exclude])
|
235
264
|
else
|
236
265
|
command.execute
|
237
266
|
end
|
@@ -243,68 +272,47 @@ module Tapioca
|
|
243
272
|
option :gem_rbi_dir, type: :string, desc: "Path to gem RBIs", default: DEFAULT_GEM_DIR
|
244
273
|
option :dsl_rbi_dir, type: :string, desc: "Path to DSL RBIs", default: DEFAULT_DSL_DIR
|
245
274
|
option :shim_rbi_dir, type: :string, desc: "Path to shim RBIs", default: DEFAULT_SHIM_DIR
|
275
|
+
option :annotations_rbi_dir, type: :string, desc: "Path to annotations RBIs", default: DEFAULT_ANNOTATIONS_DIR
|
276
|
+
option :todo_rbi_file, type: :string, desc: "Path to the generated todo RBI file", default: DEFAULT_TODO_FILE
|
246
277
|
option :payload, type: :boolean, desc: "Check shims against Sorbet's payload", default: true
|
278
|
+
option :workers, aliases: ["-w"], type: :numeric, desc: "EXPERIMENTAL: Number of parallel workers", default: 1
|
247
279
|
def check_shims
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
if sorbet_supports?(:print_payload_sources)
|
260
|
-
Dir.mktmpdir do |dir|
|
261
|
-
payload_path = dir
|
262
|
-
result = sorbet("--no-config --print=payload-sources:#{payload_path}")
|
263
|
-
|
264
|
-
unless result.status
|
265
|
-
say_error("Sorbet failed to dump payload")
|
266
|
-
say_error(result.err)
|
267
|
-
exit(1)
|
268
|
-
end
|
269
|
-
|
270
|
-
index_payload(index, payload_path)
|
271
|
-
end
|
272
|
-
else
|
273
|
-
say_error("The version of Sorbet used in your Gemfile.lock does not support `--print=payload-sources`")
|
274
|
-
say_error("Current: v#{SORBET_GEM_SPEC.version}")
|
275
|
-
say_error("Required: #{FEATURE_REQUIREMENTS[:print_payload_sources]}")
|
276
|
-
exit(1)
|
277
|
-
end
|
278
|
-
end
|
280
|
+
command = Commands::CheckShims.new(
|
281
|
+
gem_rbi_dir: options[:gem_rbi_dir],
|
282
|
+
dsl_rbi_dir: options[:dsl_rbi_dir],
|
283
|
+
shim_rbi_dir: options[:shim_rbi_dir],
|
284
|
+
annotations_rbi_dir: options[:annotations_rbi_dir],
|
285
|
+
todo_rbi_file: options[:todo_rbi_file],
|
286
|
+
payload: options[:payload],
|
287
|
+
number_of_workers: options[:workers]
|
288
|
+
)
|
289
|
+
command.execute
|
290
|
+
end
|
279
291
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
end
|
296
|
-
say_error("\nPlease remove the duplicated definitions from the #{shim_rbi_dir} directory.", :red)
|
292
|
+
desc "annotations", "Pull gem RBI annotations from remote sources"
|
293
|
+
option :sources, type: :array, default: [CENTRAL_REPO_ROOT_URI],
|
294
|
+
desc: "URIs of the sources to pull gem RBI annotations from"
|
295
|
+
option :netrc, type: :boolean, default: true, desc: "Use .netrc to authenticate to private sources"
|
296
|
+
option :netrc_file, type: :string, desc: "Path to .netrc file"
|
297
|
+
option :auth, type: :string, default: nil, desc: "HTTP authorization header for private sources"
|
298
|
+
option :typed_overrides,
|
299
|
+
aliases: ["--typed", "-t"],
|
300
|
+
type: :hash,
|
301
|
+
banner: "gem:level [gem:level ...]",
|
302
|
+
desc: "Override for typed sigils for pulled annotations",
|
303
|
+
default: {}
|
304
|
+
def annotations
|
305
|
+
if !options[:netrc] && options[:netrc_file]
|
306
|
+
say_error("Options `--no-netrc` and `--netrc-file` can't be used together", :bold, :red)
|
297
307
|
exit(1)
|
298
308
|
end
|
299
309
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
def annotations
|
307
|
-
command = Commands::Annotations.new(central_repo_root_uri: options[:repo_uri])
|
310
|
+
command = Commands::Annotations.new(
|
311
|
+
central_repo_root_uris: options[:sources],
|
312
|
+
auth: options[:auth],
|
313
|
+
netrc_file: netrc_file(options),
|
314
|
+
typed_overrides: options[:typed_overrides]
|
315
|
+
)
|
308
316
|
command.execute
|
309
317
|
end
|
310
318
|
|
@@ -320,5 +328,61 @@ module Tapioca
|
|
320
328
|
true
|
321
329
|
end
|
322
330
|
end
|
331
|
+
|
332
|
+
private
|
333
|
+
|
334
|
+
def print_init_next_steps
|
335
|
+
say(<<~OUTPUT)
|
336
|
+
#{set_color("This project is now set up for use with Sorbet and Tapioca", :bold)}
|
337
|
+
|
338
|
+
The sorbet/ folder should exist and look something like this:
|
339
|
+
|
340
|
+
├── config # Default options to be passed to Sorbet on every run
|
341
|
+
└── rbi/
|
342
|
+
├── annotations/ # Type definitions pulled from the rbi-central repository
|
343
|
+
├── gems/ # Autogenerated type definitions for your gems
|
344
|
+
└── todo.rbi # Constants which were still missing after RBI generation
|
345
|
+
└── tapioca/
|
346
|
+
├── config.yml # Default options to be passed to Tapioca
|
347
|
+
└── require.rb # A file where you can make requires from gems that might be needed for gem RBI generation
|
348
|
+
|
349
|
+
Please check this folder into version control.
|
350
|
+
|
351
|
+
#{set_color("🤔 What's next", :bold)}
|
352
|
+
|
353
|
+
1. Many Ruby applications use metaprogramming DSLs to dynamically generate constants and methods.
|
354
|
+
To generate type definitions for any DSLs in your application, run:
|
355
|
+
|
356
|
+
#{set_color("bin/tapioca dsl", :cyan)}
|
357
|
+
|
358
|
+
2. Check whether the constants in the #{set_color("sorbet/rbi/todo.rbi", :cyan)} file actually exist in your project.
|
359
|
+
It is possible that some of these constants are typos, and leaving them in #{set_color("todo.rbi", :cyan)} will
|
360
|
+
hide errors in your application. Ideally, you should be able to remove all definitions
|
361
|
+
from this file and delete it.
|
362
|
+
|
363
|
+
3. Typecheck your project:
|
364
|
+
|
365
|
+
#{set_color("bundle exec srb tc", :cyan)}
|
366
|
+
|
367
|
+
There should not be any typechecking errors.
|
368
|
+
|
369
|
+
4. Upgrade a file marked "#{set_color("# typed: false", :cyan)}" to "#{set_color("# typed: true", :cyan)}".
|
370
|
+
Then, run: #{set_color("bundle exec srb tc", :cyan)} and try to fix any errors.
|
371
|
+
|
372
|
+
You can use Spoom to bump files for you:
|
373
|
+
|
374
|
+
#{set_color("spoom bump --from false --to true", :cyan)}
|
375
|
+
|
376
|
+
To learn more about Spoom, visit: #{set_color("https://github.com/Shopify/spoom", :cyan)}
|
377
|
+
|
378
|
+
5. Add signatures to your methods with #{set_color("sig", :cyan)}. To learn how, read: #{set_color("https://sorbet.org/docs/sigs", :cyan)}
|
379
|
+
|
380
|
+
#{set_color("Documentation", :bold)}
|
381
|
+
We recommend skimming these docs to get a feel for how to use Sorbet:
|
382
|
+
- Gradual Type Checking: #{set_color("https://sorbet.org/docs/gradual", :cyan)}
|
383
|
+
- Enabling Static Checks: #{set_color("https://sorbet.org/docs/static", :cyan)}
|
384
|
+
- RBI Files: #{set_color("https://sorbet.org/docs/rbi", :cyan)}
|
385
|
+
OUTPUT
|
386
|
+
end
|
323
387
|
end
|
324
388
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "net/http"
|
5
|
-
|
6
4
|
module Tapioca
|
7
5
|
module Commands
|
8
6
|
class Annotations < Command
|
@@ -10,18 +8,33 @@ module Tapioca
|
|
10
8
|
|
11
9
|
sig do
|
12
10
|
params(
|
13
|
-
|
14
|
-
|
11
|
+
central_repo_root_uris: T::Array[String],
|
12
|
+
auth: T.nilable(String),
|
13
|
+
netrc_file: T.nilable(String),
|
14
|
+
central_repo_index_path: String,
|
15
|
+
typed_overrides: T::Hash[String, String]
|
15
16
|
).void
|
16
17
|
end
|
17
|
-
def initialize(
|
18
|
+
def initialize(
|
19
|
+
central_repo_root_uris:,
|
20
|
+
auth: nil,
|
21
|
+
netrc_file: nil,
|
22
|
+
central_repo_index_path: CENTRAL_REPO_INDEX_PATH,
|
23
|
+
typed_overrides: {}
|
24
|
+
)
|
18
25
|
super()
|
19
|
-
@
|
20
|
-
@
|
26
|
+
@central_repo_root_uris = central_repo_root_uris
|
27
|
+
@auth = auth
|
28
|
+
@netrc_file = netrc_file
|
29
|
+
@netrc_info = T.let(nil, T.nilable(Netrc))
|
30
|
+
@tokens = T.let(repo_tokens, T::Hash[String, T.nilable(String)])
|
31
|
+
@indexes = T.let({}, T::Hash[String, RepoIndex])
|
32
|
+
@typed_overrides = typed_overrides
|
21
33
|
end
|
22
34
|
|
23
35
|
sig { override.void }
|
24
36
|
def execute
|
37
|
+
@indexes = fetch_indexes
|
25
38
|
project_gems = list_gemfile_gems
|
26
39
|
remove_expired_annotations(project_gems)
|
27
40
|
fetch_annotations(project_gems)
|
@@ -60,11 +73,33 @@ module Tapioca
|
|
60
73
|
say("\nDone\n\n", :green)
|
61
74
|
end
|
62
75
|
|
63
|
-
sig { returns(RepoIndex) }
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
76
|
+
sig { returns(T::Hash[String, RepoIndex]) }
|
77
|
+
def fetch_indexes
|
78
|
+
multiple_repos = @central_repo_root_uris.size > 1
|
79
|
+
repo_number = 1
|
80
|
+
indexes = T.let({}, T::Hash[String, RepoIndex])
|
81
|
+
|
82
|
+
@central_repo_root_uris.each do |uri|
|
83
|
+
index = fetch_index(uri, repo_number: multiple_repos ? repo_number : nil)
|
84
|
+
next unless index
|
85
|
+
|
86
|
+
indexes[uri] = index
|
87
|
+
repo_number += 1
|
88
|
+
end
|
89
|
+
|
90
|
+
if indexes.empty?
|
91
|
+
say_error("\nCan't fetch annotations without sources (no index fetched)", :bold, :red)
|
92
|
+
exit(1)
|
93
|
+
end
|
94
|
+
|
95
|
+
indexes
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { params(repo_uri: String, repo_number: T.nilable(Integer)).returns(T.nilable(RepoIndex)) }
|
99
|
+
def fetch_index(repo_uri, repo_number:)
|
100
|
+
say("Retrieving index from central repository#{repo_number ? " ##{repo_number}" : ""}... ", [:blue, :bold])
|
101
|
+
content = fetch_file(repo_uri, CENTRAL_REPO_INDEX_PATH)
|
102
|
+
return nil unless content
|
68
103
|
|
69
104
|
index = RepoIndex.from_json(content)
|
70
105
|
say("Done", :green)
|
@@ -74,7 +109,11 @@ module Tapioca
|
|
74
109
|
sig { params(gem_names: T::Array[String]).returns(T::Array[String]) }
|
75
110
|
def fetch_annotations(gem_names)
|
76
111
|
say("Fetching gem annotations from central repository... ", [:blue, :bold])
|
77
|
-
fetchable_gems =
|
112
|
+
fetchable_gems = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
|
113
|
+
|
114
|
+
gem_names.each_with_object(fetchable_gems) do |gem_name, hash|
|
115
|
+
@indexes.each { |uri, index| hash[gem_name] << uri if index.has_gem?(gem_name) }
|
116
|
+
end
|
78
117
|
|
79
118
|
if fetchable_gems.empty?
|
80
119
|
say(" Nothing to do")
|
@@ -82,16 +121,21 @@ module Tapioca
|
|
82
121
|
end
|
83
122
|
|
84
123
|
say("\n")
|
85
|
-
fetched_gems = fetchable_gems.select { |
|
124
|
+
fetched_gems = fetchable_gems.select { |gem_name, repo_uris| fetch_annotation(repo_uris, gem_name) }
|
86
125
|
say("\nDone", :green)
|
87
|
-
fetched_gems
|
126
|
+
fetched_gems.keys.sort
|
88
127
|
end
|
89
128
|
|
90
|
-
sig { params(gem_name: String).void }
|
91
|
-
def fetch_annotation(gem_name)
|
92
|
-
|
129
|
+
sig { params(repo_uris: T::Array[String], gem_name: String).void }
|
130
|
+
def fetch_annotation(repo_uris, gem_name)
|
131
|
+
contents = repo_uris.map do |repo_uri|
|
132
|
+
fetch_file(repo_uri, "#{CENTRAL_REPO_ANNOTATIONS_DIR}/#{gem_name}.rbi")
|
133
|
+
end
|
134
|
+
|
135
|
+
content = merge_files(gem_name, contents.compact)
|
93
136
|
return unless content
|
94
137
|
|
138
|
+
content = apply_typed_override(gem_name, content)
|
95
139
|
content = add_header(gem_name, content)
|
96
140
|
|
97
141
|
dir = DEFAULT_ANNOTATIONS_DIR
|
@@ -100,36 +144,44 @@ module Tapioca
|
|
100
144
|
create_file("#{dir}/#{gem_name}.rbi", content)
|
101
145
|
end
|
102
146
|
|
103
|
-
sig { params(path: String).returns(T.nilable(String)) }
|
104
|
-
def fetch_file(path)
|
105
|
-
if
|
106
|
-
fetch_http_file(path)
|
147
|
+
sig { params(repo_uri: String, path: String).returns(T.nilable(String)) }
|
148
|
+
def fetch_file(repo_uri, path)
|
149
|
+
if repo_uri.start_with?(%r{https?://})
|
150
|
+
fetch_http_file(repo_uri, path)
|
107
151
|
else
|
108
|
-
fetch_local_file(path)
|
152
|
+
fetch_local_file(repo_uri, path)
|
109
153
|
end
|
110
154
|
end
|
111
155
|
|
112
|
-
sig { params(path: String).returns(T.nilable(String)) }
|
113
|
-
def fetch_local_file(path)
|
114
|
-
File.read("#{
|
156
|
+
sig { params(repo_uri: String, path: String).returns(T.nilable(String)) }
|
157
|
+
def fetch_local_file(repo_uri, path)
|
158
|
+
File.read("#{repo_uri}/#{path}")
|
115
159
|
rescue => e
|
116
160
|
say_error("\nCan't fetch file `#{path}` (#{e.message})", :bold, :red)
|
117
161
|
nil
|
118
162
|
end
|
119
163
|
|
120
|
-
sig { params(path: String).returns(T.nilable(String)) }
|
121
|
-
def fetch_http_file(path)
|
122
|
-
|
123
|
-
|
164
|
+
sig { params(repo_uri: String, path: String).returns(T.nilable(String)) }
|
165
|
+
def fetch_http_file(repo_uri, path)
|
166
|
+
auth = @tokens[repo_uri]
|
167
|
+
uri = URI("#{repo_uri}/#{path}")
|
168
|
+
|
169
|
+
request = Net::HTTP::Get.new(uri)
|
170
|
+
request["Authorization"] = auth if auth
|
171
|
+
|
172
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
173
|
+
http.request(request)
|
174
|
+
end
|
175
|
+
|
124
176
|
case response
|
125
177
|
when Net::HTTPSuccess
|
126
178
|
response.body
|
127
179
|
else
|
128
|
-
|
180
|
+
say_http_error(path, repo_uri, message: response.class)
|
129
181
|
nil
|
130
182
|
end
|
131
183
|
rescue SocketError, Errno::ECONNREFUSED => e
|
132
|
-
|
184
|
+
say_http_error(path, repo_uri, message: e.message)
|
133
185
|
nil
|
134
186
|
end
|
135
187
|
|
@@ -137,11 +189,12 @@ module Tapioca
|
|
137
189
|
def add_header(name, content)
|
138
190
|
header = <<~COMMENT
|
139
191
|
# DO NOT EDIT MANUALLY
|
140
|
-
# This file was pulled from
|
192
|
+
# This file was pulled from a central RBI files repository.
|
141
193
|
# Please run `#{default_command(:annotations)}` to update it.
|
142
194
|
COMMENT
|
143
195
|
|
144
|
-
contents
|
196
|
+
# Split contents into newlines and ensure trailing empty lines are included
|
197
|
+
contents = content.split("\n", -1)
|
145
198
|
if contents[0]&.start_with?("# typed:") && contents[1]&.empty?
|
146
199
|
contents.insert(2, header).join("\n")
|
147
200
|
else
|
@@ -149,6 +202,86 @@ module Tapioca
|
|
149
202
|
content
|
150
203
|
end
|
151
204
|
end
|
205
|
+
|
206
|
+
sig { params(name: String, content: String).returns(String) }
|
207
|
+
def apply_typed_override(name, content)
|
208
|
+
strictness = @typed_overrides[name]
|
209
|
+
return content unless strictness
|
210
|
+
|
211
|
+
unless Spoom::Sorbet::Sigils.strictness_in_content(content)
|
212
|
+
return "# typed: #{strictness}\n\n#{content}"
|
213
|
+
end
|
214
|
+
|
215
|
+
Spoom::Sorbet::Sigils.update_sigil(content, strictness)
|
216
|
+
end
|
217
|
+
|
218
|
+
sig { params(gem_name: String, contents: T::Array[String]).returns(T.nilable(String)) }
|
219
|
+
def merge_files(gem_name, contents)
|
220
|
+
return nil if contents.empty?
|
221
|
+
|
222
|
+
rewriter = RBI::Rewriters::Merge.new(keep: RBI::Rewriters::Merge::Keep::NONE)
|
223
|
+
|
224
|
+
contents.each do |content|
|
225
|
+
rbi = RBI::Parser.parse_string(content)
|
226
|
+
rewriter.merge(rbi)
|
227
|
+
end
|
228
|
+
|
229
|
+
tree = rewriter.tree
|
230
|
+
return tree.string if tree.conflicts.empty?
|
231
|
+
|
232
|
+
say_error("\n\n Can't import RBI file for `#{gem_name}` as it contains conflicts:", :yellow)
|
233
|
+
|
234
|
+
tree.conflicts.each do |conflict|
|
235
|
+
say_error(" #{conflict}", :yellow)
|
236
|
+
end
|
237
|
+
|
238
|
+
nil
|
239
|
+
rescue RBI::ParseError => e
|
240
|
+
say_error("\n\n Can't import RBI file for `#{gem_name}` as it contains errors:", :yellow)
|
241
|
+
say_error(" Error: #{e.message} (#{e.location})")
|
242
|
+
nil
|
243
|
+
end
|
244
|
+
|
245
|
+
sig { returns(T::Hash[String, T.nilable(String)]) }
|
246
|
+
def repo_tokens
|
247
|
+
@netrc_info = Netrc.read(@netrc_file) if @netrc_file
|
248
|
+
@central_repo_root_uris.map do |uri|
|
249
|
+
if @auth
|
250
|
+
[uri, @auth]
|
251
|
+
else
|
252
|
+
[uri, token_for(uri)]
|
253
|
+
end
|
254
|
+
end.compact.to_h
|
255
|
+
end
|
256
|
+
|
257
|
+
sig { params(repo_uri: String).returns(T.nilable(String)) }
|
258
|
+
def token_for(repo_uri)
|
259
|
+
return nil unless @netrc_info
|
260
|
+
|
261
|
+
host = URI(repo_uri).host
|
262
|
+
return nil unless host
|
263
|
+
|
264
|
+
creds = @netrc_info[host]
|
265
|
+
return nil unless creds
|
266
|
+
|
267
|
+
token = creds.to_a.last
|
268
|
+
return nil unless token
|
269
|
+
|
270
|
+
"token #{token}"
|
271
|
+
end
|
272
|
+
|
273
|
+
sig { params(path: String, repo_uri: String, message: String).void }
|
274
|
+
def say_http_error(path, repo_uri, message:)
|
275
|
+
say_error("\nCan't fetch file `#{path}` from #{repo_uri} (#{message})\n\n", :bold, :red)
|
276
|
+
say_error(<<~ERROR)
|
277
|
+
Tapioca can't access the annotations at #{repo_uri}.
|
278
|
+
|
279
|
+
Are you trying to access a private repository?
|
280
|
+
If so, please specify your Github credentials in your ~/.netrc file or by specifying the --auth option.
|
281
|
+
|
282
|
+
See https://github.com/Shopify/tapioca#using-a-netrc-file for more details.
|
283
|
+
ERROR
|
284
|
+
end
|
152
285
|
end
|
153
286
|
end
|
154
287
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Commands
|
6
|
+
class CheckShims < Command
|
7
|
+
extend T::Sig
|
8
|
+
include SorbetHelper
|
9
|
+
include RBIFilesHelper
|
10
|
+
|
11
|
+
sig do
|
12
|
+
params(
|
13
|
+
gem_rbi_dir: String,
|
14
|
+
dsl_rbi_dir: String,
|
15
|
+
annotations_rbi_dir: String,
|
16
|
+
shim_rbi_dir: String,
|
17
|
+
todo_rbi_file: String,
|
18
|
+
payload: T::Boolean,
|
19
|
+
number_of_workers: T.nilable(Integer)
|
20
|
+
).void
|
21
|
+
end
|
22
|
+
def initialize(
|
23
|
+
gem_rbi_dir:,
|
24
|
+
dsl_rbi_dir:,
|
25
|
+
annotations_rbi_dir:,
|
26
|
+
shim_rbi_dir:,
|
27
|
+
todo_rbi_file:,
|
28
|
+
payload:,
|
29
|
+
number_of_workers:
|
30
|
+
)
|
31
|
+
super()
|
32
|
+
@gem_rbi_dir = gem_rbi_dir
|
33
|
+
@dsl_rbi_dir = dsl_rbi_dir
|
34
|
+
@annotations_rbi_dir = annotations_rbi_dir
|
35
|
+
@shim_rbi_dir = shim_rbi_dir
|
36
|
+
@todo_rbi_file = todo_rbi_file
|
37
|
+
@payload = payload
|
38
|
+
@number_of_workers = number_of_workers
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { override.void }
|
42
|
+
def execute
|
43
|
+
index = RBI::Index.new
|
44
|
+
|
45
|
+
if (!Dir.exist?(@shim_rbi_dir) || Dir.empty?(@shim_rbi_dir)) && !File.exist?(@todo_rbi_file)
|
46
|
+
say("No shim RBIs to check", :green)
|
47
|
+
exit(0)
|
48
|
+
end
|
49
|
+
|
50
|
+
payload_path = T.let(nil, T.nilable(String))
|
51
|
+
|
52
|
+
if @payload
|
53
|
+
if sorbet_supports?(:print_payload_sources)
|
54
|
+
Dir.mktmpdir do |dir|
|
55
|
+
payload_path = dir
|
56
|
+
result = sorbet("--no-config --print=payload-sources:#{payload_path}")
|
57
|
+
|
58
|
+
unless result.status
|
59
|
+
say_error("Sorbet failed to dump payload")
|
60
|
+
say_error(result.err)
|
61
|
+
exit(1)
|
62
|
+
end
|
63
|
+
|
64
|
+
index_rbis(index, "payload", payload_path, number_of_workers: @number_of_workers)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
say_error("The version of Sorbet used in your Gemfile.lock does not support `--print=payload-sources`")
|
68
|
+
say_error("Current: v#{SORBET_GEM_SPEC.version}")
|
69
|
+
say_error("Required: #{FEATURE_REQUIREMENTS[:print_payload_sources]}")
|
70
|
+
exit(1)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
index_rbi(index, "todo", @todo_rbi_file)
|
75
|
+
index_rbis(index, "shim", @shim_rbi_dir, number_of_workers: @number_of_workers)
|
76
|
+
index_rbis(index, "gem", @gem_rbi_dir, number_of_workers: @number_of_workers)
|
77
|
+
index_rbis(index, "dsl", @dsl_rbi_dir, number_of_workers: @number_of_workers)
|
78
|
+
index_rbis(index, "annotation", @annotations_rbi_dir, number_of_workers: @number_of_workers)
|
79
|
+
|
80
|
+
duplicates = duplicated_nodes_from_index(index, shim_rbi_dir: @shim_rbi_dir, todo_rbi_file: @todo_rbi_file)
|
81
|
+
unless duplicates.empty?
|
82
|
+
duplicates.each do |key, nodes|
|
83
|
+
say_error("\nDuplicated RBI for #{key}:", :red)
|
84
|
+
nodes.each do |node|
|
85
|
+
node_loc = node.loc
|
86
|
+
next unless node_loc
|
87
|
+
|
88
|
+
loc_string = location_to_payload_url(node_loc, path_prefix: payload_path)
|
89
|
+
say_error(" * #{loc_string}", :red)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
say_error("\nPlease remove the duplicated definitions from #{@shim_rbi_dir} and #{@todo_rbi_file}", :red)
|
93
|
+
exit(1)
|
94
|
+
end
|
95
|
+
|
96
|
+
say("\nNo duplicates found in shim RBIs", :green)
|
97
|
+
exit(0)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|