tapioca 0.8.3 → 0.9.0

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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -2
  3. data/README.md +182 -36
  4. data/lib/tapioca/cli.rb +122 -65
  5. data/lib/tapioca/commands/annotations.rb +165 -33
  6. data/lib/tapioca/commands/check_shims.rb +91 -0
  7. data/lib/tapioca/commands/{init.rb → configure.rb} +1 -1
  8. data/lib/tapioca/commands/dsl.rb +1 -1
  9. data/lib/tapioca/commands/gem.rb +5 -5
  10. data/lib/tapioca/commands.rb +2 -1
  11. data/lib/tapioca/dsl/compiler.rb +1 -13
  12. data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
  13. data/lib/tapioca/dsl/compilers/active_record_relations.rb +17 -0
  14. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +5 -4
  15. data/lib/tapioca/dsl/compilers/protobuf.rb +6 -0
  16. data/lib/tapioca/dsl/compilers.rb +0 -4
  17. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +2 -2
  18. data/lib/tapioca/dsl/pipeline.rb +0 -2
  19. data/lib/tapioca/dsl.rb +8 -0
  20. data/lib/tapioca/executor.rb +0 -3
  21. data/lib/tapioca/gem/events.rb +16 -2
  22. data/lib/tapioca/gem/listeners/base.rb +11 -0
  23. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +5 -0
  24. data/lib/tapioca/gem/listeners/foreign_constants.rb +65 -0
  25. data/lib/tapioca/gem/listeners/methods.rb +6 -17
  26. data/lib/tapioca/gem/listeners/mixins.rb +31 -10
  27. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +5 -0
  28. data/lib/tapioca/gem/listeners/sorbet_enums.rb +5 -0
  29. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +5 -0
  30. data/lib/tapioca/gem/listeners/sorbet_props.rb +5 -0
  31. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +5 -0
  32. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +6 -1
  33. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +5 -0
  34. data/lib/tapioca/gem/listeners/subconstants.rb +5 -0
  35. data/lib/tapioca/gem/listeners/yard_doc.rb +5 -0
  36. data/lib/tapioca/gem/listeners.rb +1 -0
  37. data/lib/tapioca/gem/pipeline.rb +58 -15
  38. data/lib/tapioca/gem.rb +6 -0
  39. data/lib/tapioca/gemfile.rb +7 -6
  40. data/lib/tapioca/helpers/cli_helper.rb +8 -2
  41. data/lib/tapioca/helpers/config_helper.rb +0 -2
  42. data/lib/tapioca/helpers/env_helper.rb +16 -0
  43. data/lib/tapioca/helpers/rbi_files_helper.rb +252 -0
  44. data/lib/tapioca/helpers/rbi_helper.rb +74 -94
  45. data/lib/tapioca/helpers/sorbet_helper.rb +2 -3
  46. data/lib/tapioca/helpers/test/content.rb +0 -2
  47. data/lib/tapioca/helpers/test/template.rb +0 -2
  48. data/lib/tapioca/internal.rb +34 -12
  49. data/lib/tapioca/rbi_ext/model.rb +2 -15
  50. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +18 -16
  51. data/lib/tapioca/runtime/reflection.rb +27 -0
  52. data/lib/tapioca/runtime/trackers/constant_definition.rb +17 -7
  53. data/lib/tapioca/runtime/trackers/mixin.rb +49 -14
  54. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +1 -4
  55. data/lib/tapioca/static/requires_compiler.rb +0 -2
  56. data/lib/tapioca/static/symbol_loader.rb +26 -30
  57. data/lib/tapioca/static/symbol_table_parser.rb +0 -3
  58. data/lib/tapioca/version.rb +1 -1
  59. data/lib/tapioca.rb +3 -0
  60. metadata +22 -7
  61. data/lib/tapioca/dsl/helpers/param_helper.rb +0 -55
  62. data/lib/tapioca/helpers/shims_helper.rb +0 -115
  63. data/lib/tapioca/helpers/signatures_helper.rb +0 -17
  64. 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 SorbetHelper
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", "initializes folder structure"
24
+ desc "init", "get project ready for type checking"
26
25
  def init
27
- command = Commands::Init.new(
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: TAPIOCA_CONFIG_FILE,
30
- default_postrequire: DEFAULT_POSTREQUIRE_FILE
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: 120
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]),
@@ -194,9 +211,16 @@ module Tapioca
194
211
  option :rbi_max_line_length,
195
212
  type: :numeric,
196
213
  desc: "Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped",
197
- default: 120
214
+ default: DEFAULT_RBI_MAX_LINE_LENGTH
215
+ option :environment,
216
+ aliases: ["-e"],
217
+ type: :string,
218
+ desc: "The Rack/Rails environment to use when generating RBIs",
219
+ default: DEFAULT_ENVIRONMENT
198
220
  def gem(*gems)
199
221
  Tapioca.silence_warnings do
222
+ set_environment(options)
223
+
200
224
  all = options[:all]
201
225
  verify = options[:verify]
202
226
 
@@ -208,7 +232,7 @@ module Tapioca
208
232
  typed_overrides: options[:typed_overrides],
209
233
  outpath: Pathname.new(options[:outdir]),
210
234
  file_header: options[:file_header],
211
- doc: options[:doc],
235
+ include_doc: options[:doc],
212
236
  include_exported_rbis: options[:exported_gem_rbis],
213
237
  number_of_workers: options[:workers],
214
238
  auto_strictness: options[:auto_strictness],
@@ -243,68 +267,45 @@ module Tapioca
243
267
  option :gem_rbi_dir, type: :string, desc: "Path to gem RBIs", default: DEFAULT_GEM_DIR
244
268
  option :dsl_rbi_dir, type: :string, desc: "Path to DSL RBIs", default: DEFAULT_DSL_DIR
245
269
  option :shim_rbi_dir, type: :string, desc: "Path to shim RBIs", default: DEFAULT_SHIM_DIR
270
+ option :annotations_rbi_dir, type: :string, desc: "Path to annotations RBIs", default: DEFAULT_ANNOTATIONS_DIR
271
+ option :todo_rbi_file, type: :string, desc: "Path to the generated todo RBI file", default: DEFAULT_TODO_FILE
246
272
  option :payload, type: :boolean, desc: "Check shims against Sorbet's payload", default: true
247
273
  def check_shims
248
- index = RBI::Index.new
249
-
250
- shim_rbi_dir = options[:shim_rbi_dir]
251
- if !Dir.exist?(shim_rbi_dir) || Dir.empty?(shim_rbi_dir)
252
- say("No shim RBIs to check", :green)
253
- exit(0)
254
- end
255
-
256
- payload_path = T.let(nil, T.nilable(String))
257
-
258
- if options[:payload]
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
274
+ command = Commands::CheckShims.new(
275
+ gem_rbi_dir: options[:gem_rbi_dir],
276
+ dsl_rbi_dir: options[:dsl_rbi_dir],
277
+ shim_rbi_dir: options[:shim_rbi_dir],
278
+ annotations_rbi_dir: options[:annotations_rbi_dir],
279
+ todo_rbi_file: options[:todo_rbi_file],
280
+ payload: options[:payload]
281
+ )
282
+ command.execute
283
+ end
279
284
 
280
- index_rbis(index, "shim", shim_rbi_dir)
281
- index_rbis(index, "gem", options[:gem_rbi_dir])
282
- index_rbis(index, "dsl", options[:dsl_rbi_dir])
283
-
284
- duplicates = duplicated_nodes_from_index(index, shim_rbi_dir)
285
- unless duplicates.empty?
286
- duplicates.each do |key, nodes|
287
- say_error("\nDuplicated RBI for #{key}:", :red)
288
- nodes.each do |node|
289
- node_loc = node.loc
290
- next unless node_loc
291
-
292
- loc_string = location_to_payload_url(node_loc, path_prefix: payload_path)
293
- say_error(" * #{loc_string}", :red)
294
- end
295
- end
296
- say_error("\nPlease remove the duplicated definitions from the #{shim_rbi_dir} directory.", :red)
285
+ desc "annotations", "Pull gem RBI annotations from remote sources"
286
+ option :sources, type: :array, default: [CENTRAL_REPO_ROOT_URI],
287
+ desc: "URIs of the sources to pull gem RBI annotations from"
288
+ option :netrc, type: :boolean, default: true, desc: "Use .netrc to authenticate to private sources"
289
+ option :netrc_file, type: :string, desc: "Path to .netrc file"
290
+ option :auth, type: :string, default: nil, desc: "HTTP authorization header for private sources"
291
+ option :typed_overrides,
292
+ aliases: ["--typed", "-t"],
293
+ type: :hash,
294
+ banner: "gem:level [gem:level ...]",
295
+ desc: "Override for typed sigils for pulled annotations",
296
+ default: {}
297
+ def annotations
298
+ if !options[:netrc] && options[:netrc_file]
299
+ say_error("Options `--no-netrc` and `--netrc-file` can't be used together", :bold, :red)
297
300
  exit(1)
298
301
  end
299
302
 
300
- say("\nNo duplicates found in shim RBIs", :green)
301
- exit(0)
302
- end
303
-
304
- desc "annotations", "Pull gem annotations from a central RBI repository"
305
- option :repo_uri, type: :string, desc: "Repository URI to pull annotations from", default: CENTRAL_REPO_ROOT_URI
306
- def annotations
307
- command = Commands::Annotations.new(central_repo_root_uri: options[:repo_uri])
303
+ command = Commands::Annotations.new(
304
+ central_repo_root_uris: options[:sources],
305
+ auth: options[:auth],
306
+ netrc_file: netrc_file(options),
307
+ typed_overrides: options[:typed_overrides]
308
+ )
308
309
  command.execute
309
310
  end
310
311
 
@@ -320,5 +321,61 @@ module Tapioca
320
321
  true
321
322
  end
322
323
  end
324
+
325
+ private
326
+
327
+ def print_init_next_steps
328
+ say(<<~OUTPUT)
329
+ #{set_color("This project is now set up for use with Sorbet and Tapioca", :bold)}
330
+
331
+ The sorbet/ folder should exist and look something like this:
332
+
333
+ ├── config # Default options to be passed to Sorbet on every run
334
+ └── rbi/
335
+ ├── annotations/ # Type definitions pulled from the rbi-central repository
336
+ ├── gems/ # Autogenerated type definitions for your gems
337
+ └── todo.rbi # Constants which were still missing after RBI generation
338
+ └── tapioca/
339
+ ├── config.yml # Default options to be passed to Tapioca
340
+ └── require.rb # A file where you can make requires from gems that might be needed for gem RBI generation
341
+
342
+ Please check this folder into version control.
343
+
344
+ #{set_color("🤔 What's next", :bold)}
345
+
346
+ 1. Many Ruby applications use metaprogramming DSLs to dynamically generate constants and methods.
347
+ To generate type definitions for any DSLs in your application, run:
348
+
349
+ #{set_color("bin/tapioca dsl", :cyan)}
350
+
351
+ 2. Check whether the constants in the #{set_color("sorbet/rbi/todo.rbi", :cyan)} file actually exist in your project.
352
+ It is possible that some of these constants are typos, and leaving them in #{set_color("todo.rbi", :cyan)} will
353
+ hide errors in your application. Ideally, you should be able to remove all definitions
354
+ from this file and delete it.
355
+
356
+ 3. Typecheck your project:
357
+
358
+ #{set_color("bundle exec srb tc", :cyan)}
359
+
360
+ There should not be any typechecking errors.
361
+
362
+ 4. Upgrade a file marked "#{set_color("# typed: false", :cyan)}" to "#{set_color("# typed: true", :cyan)}".
363
+ Then, run: #{set_color("bundle exec srb tc", :cyan)} and try to fix any errors.
364
+
365
+ You can use Spoom to bump files for you:
366
+
367
+ #{set_color("spoom bump --from false --to true", :cyan)}
368
+
369
+ To learn more about Spoom, visit: #{set_color("https://github.com/Shopify/spoom", :cyan)}
370
+
371
+ 5. Add signatures to your methods with #{set_color("sig", :cyan)}. To learn how, read: #{set_color("https://sorbet.org/docs/sigs", :cyan)}
372
+
373
+ #{set_color("Documentation", :bold)}
374
+ We recommend skimming these docs to get a feel for how to use Sorbet:
375
+ - Gradual Type Checking: #{set_color("https://sorbet.org/docs/gradual", :cyan)}
376
+ - Enabling Static Checks: #{set_color("https://sorbet.org/docs/static", :cyan)}
377
+ - RBI Files: #{set_color("https://sorbet.org/docs/rbi", :cyan)}
378
+ OUTPUT
379
+ end
323
380
  end
324
381
  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
- central_repo_root_uri: String,
14
- central_repo_index_path: String
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(central_repo_root_uri:, central_repo_index_path: CENTRAL_REPO_INDEX_PATH)
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
- @central_repo_root_uri = central_repo_root_uri
20
- @index = T.let(fetch_index, RepoIndex)
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 fetch_index
65
- say("Retrieving index from central repository... ", [:blue, :bold])
66
- content = fetch_file(CENTRAL_REPO_INDEX_PATH)
67
- exit(1) unless content
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 = gem_names.select { |gem_name| @index.has_gem?(gem_name) }
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 { |name| fetch_annotation(name) }
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
- content = fetch_file("#{CENTRAL_REPO_ANNOTATIONS_DIR}/#{gem_name}.rbi")
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 @central_repo_root_uri.start_with?(%r{https?://})
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("#{@central_repo_root_uri}/#{path}")
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
- uri = URI("#{@central_repo_root_uri}/#{path}")
123
- response = Net::HTTP.get_response(uri)
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
- say_error("\nCan't fetch file `#{path}` from #{@central_repo_root_uri} (#{response.class})", :bold, :red)
180
+ say_http_error(path, repo_uri, message: response.class)
129
181
  nil
130
182
  end
131
183
  rescue SocketError, Errno::ECONNREFUSED => e
132
- say_error("\nCan't fetch file `#{path}` from #{@central_repo_root_uri} (#{e.message})", :bold, :red)
184
+ say_http_error(path, repo_uri, message: e.message)
133
185
  nil
134
186
  end
135
187
 
@@ -137,7 +189,7 @@ 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 #{@central_repo_root_uri}.
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
 
@@ -149,6 +201,86 @@ module Tapioca
149
201
  content
150
202
  end
151
203
  end
204
+
205
+ sig { params(name: String, content: String).returns(String) }
206
+ def apply_typed_override(name, content)
207
+ strictness = @typed_overrides[name]
208
+ return content unless strictness
209
+
210
+ unless Spoom::Sorbet::Sigils.strictness_in_content(content)
211
+ return "# typed: #{strictness}\n\n#{content}"
212
+ end
213
+
214
+ Spoom::Sorbet::Sigils.update_sigil(content, strictness)
215
+ end
216
+
217
+ sig { params(gem_name: String, contents: T::Array[String]).returns(T.nilable(String)) }
218
+ def merge_files(gem_name, contents)
219
+ return nil if contents.empty?
220
+
221
+ rewriter = RBI::Rewriters::Merge.new(keep: RBI::Rewriters::Merge::Keep::NONE)
222
+
223
+ contents.each do |content|
224
+ rbi = RBI::Parser.parse_string(content)
225
+ rewriter.merge(rbi)
226
+ end
227
+
228
+ tree = rewriter.tree
229
+ return tree.string if tree.conflicts.empty?
230
+
231
+ say_error("\n\n Can't import RBI file for `#{gem_name}` as it contains conflicts:", :yellow)
232
+
233
+ tree.conflicts.each do |conflict|
234
+ say_error(" #{conflict}", :yellow)
235
+ end
236
+
237
+ nil
238
+ rescue RBI::ParseError => e
239
+ say_error("\n\n Can't import RBI file for `#{gem_name}` as it contains errors:", :yellow)
240
+ say_error(" Error: #{e.message} (#{e.location})")
241
+ nil
242
+ end
243
+
244
+ sig { returns(T::Hash[String, T.nilable(String)]) }
245
+ def repo_tokens
246
+ @netrc_info = Netrc.read(@netrc_file) if @netrc_file
247
+ @central_repo_root_uris.map do |uri|
248
+ if @auth
249
+ [uri, @auth]
250
+ else
251
+ [uri, token_for(uri)]
252
+ end
253
+ end.compact.to_h
254
+ end
255
+
256
+ sig { params(repo_uri: String).returns(T.nilable(String)) }
257
+ def token_for(repo_uri)
258
+ return nil unless @netrc_info
259
+
260
+ host = URI(repo_uri).host
261
+ return nil unless host
262
+
263
+ creds = @netrc_info[host]
264
+ return nil unless creds
265
+
266
+ token = creds.to_a.last
267
+ return nil unless token
268
+
269
+ "token #{token}"
270
+ end
271
+
272
+ sig { params(path: String, repo_uri: String, message: String).void }
273
+ def say_http_error(path, repo_uri, message:)
274
+ say_error("\nCan't fetch file `#{path}` from #{repo_uri} (#{message})\n\n", :bold, :red)
275
+ say_error(<<~ERROR)
276
+ Tapioca can't access the annotations at #{repo_uri}.
277
+
278
+ Are you trying to access a private repository?
279
+ If so, please specify your Github credentials in your ~/.netrc file or by specifying the --auth option.
280
+
281
+ See https://github.com/Shopify/tapioca#using-a-netrc-file for more details.
282
+ ERROR
283
+ end
152
284
  end
153
285
  end
154
286
  end
@@ -0,0 +1,91 @@
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
+ ).void
20
+ end
21
+ def initialize(gem_rbi_dir:, dsl_rbi_dir:, annotations_rbi_dir:, shim_rbi_dir:, todo_rbi_file:, payload:)
22
+ super()
23
+ @gem_rbi_dir = gem_rbi_dir
24
+ @dsl_rbi_dir = dsl_rbi_dir
25
+ @annotations_rbi_dir = annotations_rbi_dir
26
+ @shim_rbi_dir = shim_rbi_dir
27
+ @todo_rbi_file = todo_rbi_file
28
+ @payload = payload
29
+ end
30
+
31
+ sig { override.void }
32
+ def execute
33
+ index = RBI::Index.new
34
+
35
+ if (!Dir.exist?(@shim_rbi_dir) || Dir.empty?(@shim_rbi_dir)) && !File.exist?(@todo_rbi_file)
36
+ say("No shim RBIs to check", :green)
37
+ exit(0)
38
+ end
39
+
40
+ payload_path = T.let(nil, T.nilable(String))
41
+
42
+ if @payload
43
+ if sorbet_supports?(:print_payload_sources)
44
+ Dir.mktmpdir do |dir|
45
+ payload_path = dir
46
+ result = sorbet("--no-config --print=payload-sources:#{payload_path}")
47
+
48
+ unless result.status
49
+ say_error("Sorbet failed to dump payload")
50
+ say_error(result.err)
51
+ exit(1)
52
+ end
53
+
54
+ index_payload(index, payload_path)
55
+ end
56
+ else
57
+ say_error("The version of Sorbet used in your Gemfile.lock does not support `--print=payload-sources`")
58
+ say_error("Current: v#{SORBET_GEM_SPEC.version}")
59
+ say_error("Required: #{FEATURE_REQUIREMENTS[:print_payload_sources]}")
60
+ exit(1)
61
+ end
62
+ end
63
+
64
+ index_rbi(index, "todo", @todo_rbi_file)
65
+ index_rbis(index, "shim", @shim_rbi_dir)
66
+ index_rbis(index, "gem", @gem_rbi_dir)
67
+ index_rbis(index, "dsl", @dsl_rbi_dir)
68
+ index_rbis(index, "annotation", @annotations_rbi_dir)
69
+
70
+ duplicates = duplicated_nodes_from_index(index, shim_rbi_dir: @shim_rbi_dir, todo_rbi_file: @todo_rbi_file)
71
+ unless duplicates.empty?
72
+ duplicates.each do |key, nodes|
73
+ say_error("\nDuplicated RBI for #{key}:", :red)
74
+ nodes.each do |node|
75
+ node_loc = node.loc
76
+ next unless node_loc
77
+
78
+ loc_string = location_to_payload_url(node_loc, path_prefix: payload_path)
79
+ say_error(" * #{loc_string}", :red)
80
+ end
81
+ end
82
+ say_error("\nPlease remove the duplicated definitions from #{@shim_rbi_dir} and #{@todo_rbi_file}", :red)
83
+ exit(1)
84
+ end
85
+
86
+ say("\nNo duplicates found in shim RBIs", :green)
87
+ exit(0)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Tapioca
5
5
  module Commands
6
- class Init < Command
6
+ class Configure < Command
7
7
  sig do
8
8
  params(
9
9
  sorbet_config: String,
@@ -5,7 +5,7 @@ module Tapioca
5
5
  module Commands
6
6
  class Dsl < Command
7
7
  include SorbetHelper
8
- include RBIHelper
8
+ include RBIFilesHelper
9
9
 
10
10
  sig do
11
11
  params(
@@ -5,7 +5,7 @@ module Tapioca
5
5
  module Commands
6
6
  class Gem < Command
7
7
  include SorbetHelper
8
- include RBIHelper
8
+ include RBIFilesHelper
9
9
 
10
10
  sig do
11
11
  params(
@@ -16,7 +16,7 @@ module Tapioca
16
16
  typed_overrides: T::Hash[String, String],
17
17
  outpath: Pathname,
18
18
  file_header: T::Boolean,
19
- doc: T::Boolean,
19
+ include_doc: T::Boolean,
20
20
  include_exported_rbis: T::Boolean,
21
21
  number_of_workers: T.nilable(Integer),
22
22
  auto_strictness: T::Boolean,
@@ -32,7 +32,7 @@ module Tapioca
32
32
  typed_overrides:,
33
33
  outpath:,
34
34
  file_header:,
35
- doc:,
35
+ include_doc:,
36
36
  include_exported_rbis:,
37
37
  number_of_workers: nil,
38
38
  auto_strictness: true,
@@ -57,7 +57,7 @@ module Tapioca
57
57
  @bundle = T.let(nil, T.nilable(Gemfile))
58
58
  @existing_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
59
59
  @expected_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
60
- @doc = T.let(doc, T::Boolean)
60
+ @include_doc = T.let(include_doc, T::Boolean)
61
61
  @include_exported_rbis = include_exported_rbis
62
62
  end
63
63
 
@@ -182,7 +182,7 @@ module Tapioca
182
182
  default_command(:gem, gem.name),
183
183
  reason: "types exported from the `#{gem.name}` gem",) if @file_header
184
184
 
185
- rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @doc).compile
185
+ rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc).compile
186
186
 
187
187
  merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
188
188