tapioca 0.9.3 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -0
- data/lib/tapioca/cli.rb +12 -4
- data/lib/tapioca/commands/annotations.rb +8 -5
- data/lib/tapioca/commands/check_shims.rb +41 -20
- data/lib/tapioca/commands/dsl.rb +19 -17
- data/lib/tapioca/commands/gem.rb +27 -21
- data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +24 -9
- data/lib/tapioca/dsl/compilers/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/compilers/protobuf.rb +8 -0
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +20 -1
- data/lib/tapioca/gem/events.rb +6 -1
- data/lib/tapioca/gem/listeners/methods.rb +1 -1
- data/lib/tapioca/gem/listeners/source_location.rb +67 -0
- data/lib/tapioca/gem/listeners.rb +1 -0
- data/lib/tapioca/gem/pipeline.rb +7 -5
- data/lib/tapioca/helpers/config_helper.rb +13 -13
- data/lib/tapioca/helpers/rbi_files_helper.rb +45 -46
- data/lib/tapioca/helpers/rbi_helper.rb +26 -5
- data/lib/tapioca/helpers/test/template.rb +5 -0
- data/lib/tapioca/internal.rb +2 -0
- data/lib/tapioca/runtime/loader.rb +8 -2
- data/lib/tapioca/runtime/reflection.rb +5 -6
- data/lib/tapioca/runtime/trackers/constant_definition.rb +39 -21
- data/lib/tapioca/runtime/trackers/mixin.rb +1 -1
- data/lib/tapioca/sorbet_ext/name_patch.rb +15 -5
- data/lib/tapioca/sorbet_ext/proc_bind_patch.rb +40 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +12 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 212af3a2ba5204b2aa479e52acddf879f32755faaee95691ce51e5c26b306481
|
4
|
+
data.tar.gz: e8f957c96874e08fed7cce1580ac110c41bef0853c6f52f2e46c90bb6435bfe8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe339f96a20827fd80cb4134ccfcb258319ea43017f8b0f98170792e8732e464e468efeed7f7c0092fb095fff1e62e2f79f51113bbc6cff78e45b9cf288febc3
|
7
|
+
data.tar.gz: dafe9aeaa6118b98c1c4a5e40064a55aacf7bc883d9ffb406bca0edd8bfecacb52b7f1f0c898bb8b09fa2c3101c9a88e3ba812fbbd3f71c15837fb110b241e69
|
data/README.md
CHANGED
@@ -152,6 +152,8 @@ Options:
|
|
152
152
|
[--verify], [--no-verify] # Verify RBIs are up-to-date
|
153
153
|
[--doc], [--no-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
|
154
154
|
# Default: true
|
155
|
+
[--loc], [--no-loc] # Include comments with source location when generating RBIs
|
156
|
+
# Default: true
|
155
157
|
[--exported-gem-rbis], [--no-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
|
156
158
|
# Default: true
|
157
159
|
-w, [--workers=N] # EXPERIMENTAL: Number of parallel workers to use when generating RBIs
|
@@ -740,6 +742,8 @@ Options:
|
|
740
742
|
# Default: sorbet/rbi/todo.rbi
|
741
743
|
[--payload], [--no-payload] # Check shims against Sorbet's payload
|
742
744
|
# Default: true
|
745
|
+
-w, [--workers=N] # EXPERIMENTAL: Number of parallel workers
|
746
|
+
# Default: 1
|
743
747
|
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
744
748
|
# Default: sorbet/tapioca/config.yml
|
745
749
|
-V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
|
@@ -802,6 +806,7 @@ gem:
|
|
802
806
|
activesupport: 'false'
|
803
807
|
verify: false
|
804
808
|
doc: true
|
809
|
+
loc: true
|
805
810
|
exported_gem_rbis: true
|
806
811
|
workers: 1
|
807
812
|
auto_strictness: true
|
@@ -815,6 +820,7 @@ check_shims:
|
|
815
820
|
annotations_rbi_dir: sorbet/rbi/annotations
|
816
821
|
todo_rbi_file: sorbet/rbi/todo.rbi
|
817
822
|
payload: true
|
823
|
+
workers: 1
|
818
824
|
annotations:
|
819
825
|
sources:
|
820
826
|
- https://raw.githubusercontent.com/Shopify/rbi-central/main
|
data/lib/tapioca/cli.rb
CHANGED
@@ -190,6 +190,10 @@ module Tapioca
|
|
190
190
|
type: :boolean,
|
191
191
|
desc: "Include YARD documentation from sources when generating RBIs. Warning: this might be slow",
|
192
192
|
default: true
|
193
|
+
option :loc,
|
194
|
+
type: :boolean,
|
195
|
+
desc: "Include comments with source location when generating RBIs",
|
196
|
+
default: true
|
193
197
|
option :exported_gem_rbis,
|
194
198
|
type: :boolean,
|
195
199
|
desc: "Include RBIs found in the `rbi/` directory of the gem",
|
@@ -233,6 +237,7 @@ module Tapioca
|
|
233
237
|
outpath: Pathname.new(options[:outdir]),
|
234
238
|
file_header: options[:file_header],
|
235
239
|
include_doc: options[:doc],
|
240
|
+
include_loc: options[:loc],
|
236
241
|
include_exported_rbis: options[:exported_gem_rbis],
|
237
242
|
number_of_workers: options[:workers],
|
238
243
|
auto_strictness: options[:auto_strictness],
|
@@ -255,7 +260,7 @@ module Tapioca
|
|
255
260
|
end
|
256
261
|
|
257
262
|
if gems.empty? && !all
|
258
|
-
command.sync(should_verify: verify)
|
263
|
+
command.sync(should_verify: verify, exclude: options[:exclude])
|
259
264
|
else
|
260
265
|
command.execute
|
261
266
|
end
|
@@ -270,14 +275,18 @@ module Tapioca
|
|
270
275
|
option :annotations_rbi_dir, type: :string, desc: "Path to annotations RBIs", default: DEFAULT_ANNOTATIONS_DIR
|
271
276
|
option :todo_rbi_file, type: :string, desc: "Path to the generated todo RBI file", default: DEFAULT_TODO_FILE
|
272
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
|
273
279
|
def check_shims
|
280
|
+
Tapioca.disable_traces
|
281
|
+
|
274
282
|
command = Commands::CheckShims.new(
|
275
283
|
gem_rbi_dir: options[:gem_rbi_dir],
|
276
284
|
dsl_rbi_dir: options[:dsl_rbi_dir],
|
277
285
|
shim_rbi_dir: options[:shim_rbi_dir],
|
278
286
|
annotations_rbi_dir: options[:annotations_rbi_dir],
|
279
287
|
todo_rbi_file: options[:todo_rbi_file],
|
280
|
-
payload: options[:payload]
|
288
|
+
payload: options[:payload],
|
289
|
+
number_of_workers: options[:workers]
|
281
290
|
)
|
282
291
|
command.execute
|
283
292
|
end
|
@@ -296,8 +305,7 @@ module Tapioca
|
|
296
305
|
default: {}
|
297
306
|
def annotations
|
298
307
|
if !options[:netrc] && options[:netrc_file]
|
299
|
-
|
300
|
-
exit(1)
|
308
|
+
raise Thor::Error, set_color("Options `--no-netrc` and `--netrc-file` can't be used together", :bold, :red)
|
301
309
|
end
|
302
310
|
|
303
311
|
command = Commands::Annotations.new(
|
@@ -88,8 +88,7 @@ module Tapioca
|
|
88
88
|
end
|
89
89
|
|
90
90
|
if indexes.empty?
|
91
|
-
|
92
|
-
exit(1)
|
91
|
+
raise Thor::Error, set_color("Can't fetch annotations without sources (no index fetched)", :bold, :red)
|
93
92
|
end
|
94
93
|
|
95
94
|
indexes
|
@@ -112,12 +111,15 @@ module Tapioca
|
|
112
111
|
fetchable_gems = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
|
113
112
|
|
114
113
|
gem_names.each_with_object(fetchable_gems) do |gem_name, hash|
|
115
|
-
@indexes.each
|
114
|
+
@indexes.each do |uri, index|
|
115
|
+
T.must(hash[gem_name]) << uri if index.has_gem?(gem_name)
|
116
|
+
end
|
116
117
|
end
|
117
118
|
|
118
119
|
if fetchable_gems.empty?
|
119
120
|
say(" Nothing to do")
|
120
|
-
|
121
|
+
|
122
|
+
return []
|
121
123
|
end
|
122
124
|
|
123
125
|
say("\n")
|
@@ -193,7 +195,8 @@ module Tapioca
|
|
193
195
|
# Please run `#{default_command(:annotations)}` to update it.
|
194
196
|
COMMENT
|
195
197
|
|
196
|
-
contents
|
198
|
+
# Split contents into newlines and ensure trailing empty lines are included
|
199
|
+
contents = content.split("\n", -1)
|
197
200
|
if contents[0]&.start_with?("# typed:") && contents[1]&.empty?
|
198
201
|
contents.insert(2, header).join("\n")
|
199
202
|
else
|
@@ -15,10 +15,19 @@ module Tapioca
|
|
15
15
|
annotations_rbi_dir: String,
|
16
16
|
shim_rbi_dir: String,
|
17
17
|
todo_rbi_file: String,
|
18
|
-
payload: T::Boolean
|
18
|
+
payload: T::Boolean,
|
19
|
+
number_of_workers: T.nilable(Integer)
|
19
20
|
).void
|
20
21
|
end
|
21
|
-
def initialize(
|
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
|
+
)
|
22
31
|
super()
|
23
32
|
@gem_rbi_dir = gem_rbi_dir
|
24
33
|
@dsl_rbi_dir = dsl_rbi_dir
|
@@ -26,6 +35,7 @@ module Tapioca
|
|
26
35
|
@shim_rbi_dir = shim_rbi_dir
|
27
36
|
@todo_rbi_file = todo_rbi_file
|
28
37
|
@payload = payload
|
38
|
+
@number_of_workers = number_of_workers
|
29
39
|
end
|
30
40
|
|
31
41
|
sig { override.void }
|
@@ -34,7 +44,8 @@ module Tapioca
|
|
34
44
|
|
35
45
|
if (!Dir.exist?(@shim_rbi_dir) || Dir.empty?(@shim_rbi_dir)) && !File.exist?(@todo_rbi_file)
|
36
46
|
say("No shim RBIs to check", :green)
|
37
|
-
|
47
|
+
|
48
|
+
return
|
38
49
|
end
|
39
50
|
|
40
51
|
payload_path = T.let(nil, T.nilable(String))
|
@@ -46,45 +57,55 @@ module Tapioca
|
|
46
57
|
result = sorbet("--no-config --print=payload-sources:#{payload_path}")
|
47
58
|
|
48
59
|
unless result.status
|
49
|
-
|
50
|
-
|
51
|
-
|
60
|
+
raise Thor::Error, <<~ERROR
|
61
|
+
"Sorbet failed to dump payload"
|
62
|
+
#{result.err}
|
63
|
+
ERROR
|
52
64
|
end
|
53
65
|
|
54
|
-
|
66
|
+
index_rbis(index, "payload", payload_path, number_of_workers: @number_of_workers)
|
55
67
|
end
|
56
68
|
else
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
69
|
+
raise Thor::Error, <<~ERROR
|
70
|
+
The version of Sorbet used in your Gemfile.lock does not support `--print=payload-sources`
|
71
|
+
Current: v#{SORBET_GEM_SPEC.version}
|
72
|
+
Required: #{FEATURE_REQUIREMENTS[:print_payload_sources]}
|
73
|
+
ERROR
|
61
74
|
end
|
62
75
|
end
|
63
76
|
|
64
77
|
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)
|
78
|
+
index_rbis(index, "shim", @shim_rbi_dir, number_of_workers: @number_of_workers)
|
79
|
+
index_rbis(index, "gem", @gem_rbi_dir, number_of_workers: @number_of_workers)
|
80
|
+
index_rbis(index, "dsl", @dsl_rbi_dir, number_of_workers: @number_of_workers)
|
81
|
+
index_rbis(index, "annotation", @annotations_rbi_dir, number_of_workers: @number_of_workers)
|
69
82
|
|
70
83
|
duplicates = duplicated_nodes_from_index(index, shim_rbi_dir: @shim_rbi_dir, todo_rbi_file: @todo_rbi_file)
|
84
|
+
|
71
85
|
unless duplicates.empty?
|
86
|
+
messages = []
|
87
|
+
|
72
88
|
duplicates.each do |key, nodes|
|
73
|
-
|
89
|
+
messages << set_color("\nDuplicated RBI for #{key}:", :red)
|
90
|
+
|
74
91
|
nodes.each do |node|
|
75
92
|
node_loc = node.loc
|
93
|
+
|
76
94
|
next unless node_loc
|
77
95
|
|
78
96
|
loc_string = location_to_payload_url(node_loc, path_prefix: payload_path)
|
79
|
-
|
97
|
+
messages << set_color(" * #{loc_string}", :red)
|
80
98
|
end
|
81
99
|
end
|
82
|
-
|
83
|
-
|
100
|
+
|
101
|
+
messages << set_color(
|
102
|
+
"\nPlease remove the duplicated definitions from #{@shim_rbi_dir} and #{@todo_rbi_file}", :red
|
103
|
+
)
|
104
|
+
|
105
|
+
raise Thor::Error, messages.join("\n")
|
84
106
|
end
|
85
107
|
|
86
108
|
say("\nNo duplicates found in shim RBIs", :green)
|
87
|
-
exit(0)
|
88
109
|
end
|
89
110
|
end
|
90
111
|
end
|
data/lib/tapioca/commands/dsl.rb
CHANGED
@@ -191,6 +191,7 @@ module Tapioca
|
|
191
191
|
end
|
192
192
|
|
193
193
|
unprocessable_constants = constant_map.select { |_, v| v.nil? }
|
194
|
+
|
194
195
|
unless unprocessable_constants.empty?
|
195
196
|
unprocessable_constants.each do |name, _|
|
196
197
|
say("Error: Cannot find constant '#{name}'", :red)
|
@@ -198,7 +199,7 @@ module Tapioca
|
|
198
199
|
remove_file(filename) if File.file?(filename)
|
199
200
|
end
|
200
201
|
|
201
|
-
|
202
|
+
raise Thor::Error, ""
|
202
203
|
end
|
203
204
|
|
204
205
|
constant_map.values
|
@@ -211,12 +212,13 @@ module Tapioca
|
|
211
212
|
end
|
212
213
|
|
213
214
|
unprocessable_compilers = compiler_map.select { |_, v| v.nil? }
|
215
|
+
|
214
216
|
unless unprocessable_compilers.empty?
|
215
|
-
unprocessable_compilers.
|
216
|
-
|
217
|
-
end
|
217
|
+
message = unprocessable_compilers.map do |name, _|
|
218
|
+
set_color("Error: Cannot find compiler '#{name}'", :red)
|
219
|
+
end.join("\n")
|
218
220
|
|
219
|
-
|
221
|
+
raise Thor::Error, message
|
220
222
|
end
|
221
223
|
|
222
224
|
T.cast(compiler_map.values, T::Array[T.class_of(Tapioca::Dsl::Compiler)])
|
@@ -332,18 +334,18 @@ module Tapioca
|
|
332
334
|
if diff.empty?
|
333
335
|
say("Nothing to do, all RBIs are up-to-date.")
|
334
336
|
else
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
337
|
+
reasons = diff.group_by(&:last).sort.map do |cause, diff_for_cause|
|
338
|
+
build_error_for_files(cause, diff_for_cause.map(&:first))
|
339
|
+
end.join("\n")
|
340
|
+
|
341
|
+
raise Thor::Error, <<~ERROR
|
342
|
+
#{set_color("RBI files are out-of-date. In your development environment, please run:", :green)}
|
343
|
+
#{set_color("`#{default_command(command)}`", [:green, :bold])}
|
344
|
+
#{set_color("Once it is complete, be sure to commit and push any changes", :green)}
|
345
|
+
|
346
|
+
#{set_color("Reason:", :red)}
|
347
|
+
#{reasons}
|
348
|
+
ERROR
|
347
349
|
end
|
348
350
|
end
|
349
351
|
|
data/lib/tapioca/commands/gem.rb
CHANGED
@@ -17,6 +17,7 @@ module Tapioca
|
|
17
17
|
outpath: Pathname,
|
18
18
|
file_header: T::Boolean,
|
19
19
|
include_doc: T::Boolean,
|
20
|
+
include_loc: T::Boolean,
|
20
21
|
include_exported_rbis: T::Boolean,
|
21
22
|
number_of_workers: T.nilable(Integer),
|
22
23
|
auto_strictness: T::Boolean,
|
@@ -33,6 +34,7 @@ module Tapioca
|
|
33
34
|
outpath:,
|
34
35
|
file_header:,
|
35
36
|
include_doc:,
|
37
|
+
include_loc:,
|
36
38
|
include_exported_rbis:,
|
37
39
|
number_of_workers: nil,
|
38
40
|
auto_strictness: true,
|
@@ -58,6 +60,7 @@ module Tapioca
|
|
58
60
|
@existing_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
|
59
61
|
@expected_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
|
60
62
|
@include_doc = T.let(include_doc, T::Boolean)
|
63
|
+
@include_loc = T.let(include_loc, T::Boolean)
|
61
64
|
@include_exported_rbis = include_exported_rbis
|
62
65
|
end
|
63
66
|
|
@@ -94,12 +97,12 @@ module Tapioca
|
|
94
97
|
end
|
95
98
|
end
|
96
99
|
|
97
|
-
sig { params(should_verify: T::Boolean).void }
|
98
|
-
def sync(should_verify: false)
|
100
|
+
sig { params(should_verify: T::Boolean, exclude: T::Array[String]).void }
|
101
|
+
def sync(should_verify: false, exclude: [])
|
99
102
|
if should_verify
|
100
103
|
say("Checking for out-of-date RBIs...")
|
101
104
|
say("")
|
102
|
-
perform_sync_verification
|
105
|
+
perform_sync_verification(exclude: exclude)
|
103
106
|
return
|
104
107
|
end
|
105
108
|
|
@@ -163,11 +166,12 @@ module Tapioca
|
|
163
166
|
return bundle.dependencies if gem_names.empty?
|
164
167
|
|
165
168
|
gem_names.map do |gem_name|
|
166
|
-
gem = bundle.gem(gem_name)
|
169
|
+
gem = @bundle.gem(gem_name)
|
170
|
+
|
167
171
|
if gem.nil?
|
168
|
-
|
169
|
-
exit(1)
|
172
|
+
raise Thor::Error, set_color("Error: Cannot find gem '#{gem_name}'", :red)
|
170
173
|
end
|
174
|
+
|
171
175
|
gem
|
172
176
|
end
|
173
177
|
end
|
@@ -182,7 +186,7 @@ module Tapioca
|
|
182
186
|
default_command(:gem, gem.name),
|
183
187
|
reason: "types exported from the `#{gem.name}` gem",) if @file_header
|
184
188
|
|
185
|
-
rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc).compile
|
189
|
+
rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc, include_loc: @include_loc).compile
|
186
190
|
|
187
191
|
merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
|
188
192
|
|
@@ -201,11 +205,13 @@ module Tapioca
|
|
201
205
|
end
|
202
206
|
end
|
203
207
|
|
204
|
-
sig { void }
|
205
|
-
def perform_sync_verification
|
208
|
+
sig { params(exclude: T::Array[String]).void }
|
209
|
+
def perform_sync_verification(exclude: [])
|
206
210
|
diff = {}
|
207
211
|
|
208
212
|
removed_rbis.each do |gem_name|
|
213
|
+
next if exclude.include?(gem_name)
|
214
|
+
|
209
215
|
filename = existing_rbi(gem_name)
|
210
216
|
diff[filename] = :removed
|
211
217
|
end
|
@@ -325,18 +331,18 @@ module Tapioca
|
|
325
331
|
if diff.empty?
|
326
332
|
say("Nothing to do, all RBIs are up-to-date.")
|
327
333
|
else
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
334
|
+
reasons = diff.group_by(&:last).sort.map do |cause, diff_for_cause|
|
335
|
+
build_error_for_files(cause, diff_for_cause.map(&:first))
|
336
|
+
end.join("\n")
|
337
|
+
|
338
|
+
raise Thor::Error, <<~ERROR
|
339
|
+
#{set_color("RBI files are out-of-date. In your development environment, please run:", :green)}
|
340
|
+
#{set_color("`#{default_command(command)}`", [:green, :bold])}
|
341
|
+
#{set_color("Once it is complete, be sure to commit and push any changes", :green)}
|
342
|
+
|
343
|
+
#{set_color("Reason:", :red)}
|
344
|
+
#{reasons}
|
345
|
+
ERROR
|
340
346
|
end
|
341
347
|
end
|
342
348
|
|
@@ -42,13 +42,12 @@ module Tapioca
|
|
42
42
|
|
43
43
|
sig { override.void }
|
44
44
|
def decorate
|
45
|
-
method_names = fixture_loader.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
.flatten # merge methods into a single list
|
45
|
+
method_names = if fixture_loader.respond_to?(:fixture_sets)
|
46
|
+
method_names_from_lazy_fixture_loader
|
47
|
+
else
|
48
|
+
method_names_from_eager_fixture_loader
|
49
|
+
end
|
50
|
+
|
52
51
|
return if method_names.empty?
|
53
52
|
|
54
53
|
root.create_path(constant) do |mod|
|
@@ -69,11 +68,27 @@ module Tapioca
|
|
69
68
|
|
70
69
|
sig { returns(Class) }
|
71
70
|
def fixture_loader
|
72
|
-
Class.new do
|
71
|
+
@fixture_loader ||= T.let(Class.new do
|
73
72
|
T.unsafe(self).include(ActiveRecord::TestFixtures)
|
74
73
|
T.unsafe(self).fixture_path = Rails.root.join("test", "fixtures")
|
75
74
|
T.unsafe(self).fixtures(:all)
|
76
|
-
end
|
75
|
+
end, T.nilable(Class))
|
76
|
+
end
|
77
|
+
|
78
|
+
sig { returns(T::Array[String]) }
|
79
|
+
def method_names_from_lazy_fixture_loader
|
80
|
+
T.unsafe(fixture_loader).fixture_sets.keys
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { returns(T::Array[Symbol]) }
|
84
|
+
def method_names_from_eager_fixture_loader
|
85
|
+
fixture_loader.ancestors # get all ancestors from class that includes AR fixtures
|
86
|
+
.drop(1) # drop the anonymous class itself from the array
|
87
|
+
.reject(&:name) # only collect anonymous ancestors because fixture methods are always on an anonymous module
|
88
|
+
.map! do |mod|
|
89
|
+
[mod.private_instance_methods(false), mod.instance_methods(false)]
|
90
|
+
end
|
91
|
+
.flatten # merge methods into a single list
|
77
92
|
end
|
78
93
|
|
79
94
|
sig { params(mod: RBI::Scope, name: String).void }
|
@@ -65,7 +65,7 @@ module Tapioca
|
|
65
65
|
class FrozenRecord < Compiler
|
66
66
|
extend T::Sig
|
67
67
|
|
68
|
-
ConstantType = type_member { { fixed: T.class_of(::FrozenRecord::Base) } }
|
68
|
+
ConstantType = type_member { { fixed: T.all(T.class_of(::FrozenRecord::Base), Extensions::FrozenRecord) } }
|
69
69
|
|
70
70
|
sig { override.void }
|
71
71
|
def decorate
|
@@ -97,7 +97,7 @@ module Tapioca
|
|
97
97
|
|
98
98
|
sig { params(record: RBI::Scope).void }
|
99
99
|
def decorate_scopes(record)
|
100
|
-
scopes =
|
100
|
+
scopes = constant.__tapioca_scope_names
|
101
101
|
return if scopes.nil?
|
102
102
|
|
103
103
|
module_name = "GeneratedRelationMethods"
|
@@ -60,6 +60,14 @@ module Tapioca
|
|
60
60
|
# def number_value=(value); end
|
61
61
|
# end
|
62
62
|
# ~~~
|
63
|
+
#
|
64
|
+
# Please note that you might have to ignore the originally generated Protobuf Ruby files
|
65
|
+
# to avoid _Redefining constant_ issues when doing type checking.
|
66
|
+
# Do this by extending your Sorbet config file:
|
67
|
+
#
|
68
|
+
# ~~~
|
69
|
+
# --ignore=/path/to/proto/cart_pb.rb
|
70
|
+
# ~~~
|
63
71
|
class Protobuf < Compiler
|
64
72
|
class Field < T::Struct
|
65
73
|
prop :name, String
|
@@ -38,12 +38,20 @@ module Tapioca
|
|
38
38
|
"::Time"
|
39
39
|
when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
|
40
40
|
"::ActiveSupport::TimeWithZone"
|
41
|
+
when ActiveRecord::Enum::EnumType
|
42
|
+
"::String"
|
41
43
|
else
|
42
44
|
handle_unknown_type(column_type)
|
43
45
|
end
|
44
46
|
|
45
47
|
column = @constant.columns_hash[column_name]
|
46
|
-
setter_type =
|
48
|
+
setter_type =
|
49
|
+
case column_type
|
50
|
+
when ActiveRecord::Enum::EnumType
|
51
|
+
enum_setter_type(column_type)
|
52
|
+
else
|
53
|
+
getter_type
|
54
|
+
end
|
47
55
|
|
48
56
|
if column&.null
|
49
57
|
return [as_nilable_type(getter_type), as_nilable_type(setter_type)]
|
@@ -108,6 +116,17 @@ module Tapioca
|
|
108
116
|
|
109
117
|
first_argument_type.to_s
|
110
118
|
end
|
119
|
+
|
120
|
+
sig { params(column_type: ActiveRecord::Enum::EnumType).returns(String) }
|
121
|
+
def enum_setter_type(column_type)
|
122
|
+
# In Rails < 7 this method is private. When support for that is dropped we can call the method directly
|
123
|
+
case column_type.send(:subtype)
|
124
|
+
when ActiveRecord::Type::Integer
|
125
|
+
"T.any(::String, ::Symbol, ::Integer)"
|
126
|
+
else
|
127
|
+
"T.any(::String, ::Symbol)"
|
128
|
+
end
|
129
|
+
end
|
111
130
|
end
|
112
131
|
end
|
113
132
|
end
|
data/lib/tapioca/gem/events.rb
CHANGED
@@ -105,6 +105,9 @@ module Tapioca
|
|
105
105
|
class MethodNodeAdded < NodeAdded
|
106
106
|
extend T::Sig
|
107
107
|
|
108
|
+
sig { returns(UnboundMethod) }
|
109
|
+
attr_reader :method
|
110
|
+
|
108
111
|
sig { returns(RBI::Method) }
|
109
112
|
attr_reader :node
|
110
113
|
|
@@ -118,14 +121,16 @@ module Tapioca
|
|
118
121
|
params(
|
119
122
|
symbol: String,
|
120
123
|
constant: Module,
|
124
|
+
method: UnboundMethod,
|
121
125
|
node: RBI::Method,
|
122
126
|
signature: T.untyped,
|
123
127
|
parameters: T::Array[[Symbol, String]]
|
124
128
|
).void.checked(:never)
|
125
129
|
end
|
126
|
-
def initialize(symbol, constant, node, signature, parameters)
|
130
|
+
def initialize(symbol, constant, method, node, signature, parameters) # rubocop:disable Metrics/ParameterLists
|
127
131
|
super(symbol, constant)
|
128
132
|
@node = node
|
133
|
+
@method = method
|
129
134
|
@signature = signature
|
130
135
|
@parameters = parameters
|
131
136
|
end
|
@@ -134,7 +134,7 @@ module Tapioca
|
|
134
134
|
end
|
135
135
|
end
|
136
136
|
|
137
|
-
@pipeline.push_method(symbol_name, constant, rbi_method, signature, sanitized_parameters)
|
137
|
+
@pipeline.push_method(symbol_name, constant, method, rbi_method, signature, sanitized_parameters)
|
138
138
|
tree << rbi_method
|
139
139
|
end
|
140
140
|
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Gem
|
6
|
+
module Listeners
|
7
|
+
class SourceLocation < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
sig { override.params(event: ConstNodeAdded).void }
|
13
|
+
def on_const(event)
|
14
|
+
file, line = Object.const_source_location(event.symbol)
|
15
|
+
add_source_location_comment(event.node, file, line)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { override.params(event: ScopeNodeAdded).void }
|
19
|
+
def on_scope(event)
|
20
|
+
# Instead of using `const_source_location`, which always reports the first place where a constant is defined,
|
21
|
+
# we filter the locations tracked by ConstantDefinition. This allows us to provide the correct location for
|
22
|
+
# constants that are defined by multiple gems.
|
23
|
+
locations = Runtime::Trackers::ConstantDefinition.locations_for(event.constant)
|
24
|
+
location = locations.find do |loc|
|
25
|
+
Pathname.new(loc.path).realpath.to_s.include?(@pipeline.gem.full_gem_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
# The location may still be nil in some situations, like constant aliases (e.g.: MyAlias = OtherConst). These
|
29
|
+
# are quite difficult to attribute a correct location, given that the source location points to the original
|
30
|
+
# constants and not the alias
|
31
|
+
add_source_location_comment(event.node, location.path, location.lineno) unless location.nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { override.params(event: MethodNodeAdded).void }
|
35
|
+
def on_method(event)
|
36
|
+
file, line = event.method.source_location
|
37
|
+
add_source_location_comment(event.node, file, line)
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { params(node: RBI::NodeWithComments, file: T.nilable(String), line: T.nilable(Integer)).void }
|
41
|
+
def add_source_location_comment(node, file, line)
|
42
|
+
return unless file && line
|
43
|
+
|
44
|
+
gem = @pipeline.gem
|
45
|
+
path = Pathname.new(file)
|
46
|
+
return unless File.exist?(path)
|
47
|
+
|
48
|
+
# On native extensions, the source location may point to a shared object (.so, .bundle) file, which we cannot
|
49
|
+
# use for jump to definition. Only add source comments on Ruby files
|
50
|
+
return unless path.extname == ".rb"
|
51
|
+
|
52
|
+
path = if path.realpath.to_s.start_with?(gem.full_gem_path)
|
53
|
+
"#{gem.name}-#{gem.version}/#{path.realpath.relative_path_from(gem.full_gem_path)}"
|
54
|
+
else
|
55
|
+
path.sub("#{Bundler.bundle_path}/gems/", "").to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
# Strip out the RUBY_ROOT prefix, which is different for each user
|
59
|
+
path = path.sub(RbConfig::CONFIG["rubylibdir"], "RUBY_ROOT")
|
60
|
+
|
61
|
+
node.comments << RBI::Comment.new("") if node.comments.any?
|
62
|
+
node.comments << RBI::Comment.new("source://#{path}:#{line}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/tapioca/gem/pipeline.rb
CHANGED
@@ -13,8 +13,8 @@ module Tapioca
|
|
13
13
|
sig { returns(Gemfile::GemSpec) }
|
14
14
|
attr_reader :gem
|
15
15
|
|
16
|
-
sig { params(gem: Gemfile::GemSpec, include_doc: T::Boolean).void }
|
17
|
-
def initialize(gem, include_doc: false)
|
16
|
+
sig { params(gem: Gemfile::GemSpec, include_doc: T::Boolean, include_loc: T::Boolean).void }
|
17
|
+
def initialize(gem, include_doc: false, include_loc: false)
|
18
18
|
@root = T.let(RBI::Tree.new, RBI::Tree)
|
19
19
|
@gem = gem
|
20
20
|
@seen = T.let(Set.new, T::Set[String])
|
@@ -40,6 +40,7 @@ module Tapioca
|
|
40
40
|
@node_listeners << Gem::Listeners::Subconstants.new(self)
|
41
41
|
@node_listeners << Gem::Listeners::YardDoc.new(self) if include_doc
|
42
42
|
@node_listeners << Gem::Listeners::ForeignConstants.new(self)
|
43
|
+
@node_listeners << Gem::Listeners::SourceLocation.new(self) if include_loc
|
43
44
|
@node_listeners << Gem::Listeners::RemoveEmptyPayloadScopes.new(self)
|
44
45
|
end
|
45
46
|
|
@@ -87,13 +88,14 @@ module Tapioca
|
|
87
88
|
params(
|
88
89
|
symbol: String,
|
89
90
|
constant: Module,
|
91
|
+
method: UnboundMethod,
|
90
92
|
node: RBI::Method,
|
91
93
|
signature: T.untyped,
|
92
94
|
parameters: T::Array[[Symbol, String]]
|
93
95
|
).void.checked(:never)
|
94
96
|
end
|
95
|
-
def push_method(symbol, constant, node, signature, parameters)
|
96
|
-
@events << Gem::MethodNodeAdded.new(symbol, constant, node, signature, parameters)
|
97
|
+
def push_method(symbol, constant, method, node, signature, parameters) # rubocop:disable Metrics/ParameterLists
|
98
|
+
@events << Gem::MethodNodeAdded.new(symbol, constant, method, node, signature, parameters)
|
97
99
|
end
|
98
100
|
|
99
101
|
sig { params(symbol_name: String).returns(T::Boolean) }
|
@@ -353,7 +355,7 @@ module Tapioca
|
|
353
355
|
def get_file_candidates(constant)
|
354
356
|
wrapped_module = Pry::WrappedModule.new(constant)
|
355
357
|
|
356
|
-
wrapped_module.
|
358
|
+
wrapped_module.send(:method_candidates).flatten.filter_map(&:source_file).uniq
|
357
359
|
rescue ArgumentError, NameError
|
358
360
|
[]
|
359
361
|
end
|
@@ -86,8 +86,7 @@ module Tapioca
|
|
86
86
|
end.compact
|
87
87
|
|
88
88
|
unless errors.empty?
|
89
|
-
|
90
|
-
exit(1)
|
89
|
+
raise Thor::Error, build_error_message(config_file, errors)
|
91
90
|
end
|
92
91
|
ensure
|
93
92
|
@validating_config = false
|
@@ -173,18 +172,19 @@ module Tapioca
|
|
173
172
|
)
|
174
173
|
end
|
175
174
|
|
176
|
-
sig { params(config_file: String, errors: T::Array[ConfigError]).
|
177
|
-
def
|
178
|
-
|
179
|
-
|
180
|
-
|
175
|
+
sig { params(config_file: String, errors: T::Array[ConfigError]).returns(String) }
|
176
|
+
def build_error_message(config_file, errors)
|
177
|
+
error_messages = errors.map do |error|
|
178
|
+
"- " + error.message_parts.map do |part|
|
179
|
+
T.unsafe(self).set_color(part.message, *part.colors)
|
180
|
+
end.join
|
181
|
+
end.join("\n")
|
181
182
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
end
|
183
|
+
<<~ERROR
|
184
|
+
#{set_color("\nConfiguration file", :red)} #{set_color(config_file, :blue, :bold)} #{set_color("has the following errors:", :red)}
|
185
|
+
|
186
|
+
#{error_messages}
|
187
|
+
ERROR
|
188
188
|
end
|
189
189
|
|
190
190
|
sig do
|
@@ -9,33 +9,33 @@ module Tapioca
|
|
9
9
|
requires_ancestor { Thor::Shell }
|
10
10
|
requires_ancestor { SorbetHelper }
|
11
11
|
|
12
|
-
sig { params(index: RBI::Index, dir: String).void }
|
13
|
-
def index_payload(index, dir)
|
14
|
-
return unless Dir.exist?(dir)
|
15
|
-
|
16
|
-
say("Loading Sorbet payload... ")
|
17
|
-
files = Dir.glob("#{dir}/**/*.rbi").sort
|
18
|
-
parse_and_index_files(index, files)
|
19
|
-
say(" Done", :green)
|
20
|
-
end
|
21
|
-
|
22
12
|
sig { params(index: RBI::Index, kind: String, file: String).void }
|
23
13
|
def index_rbi(index, kind, file)
|
24
14
|
return unless File.exist?(file)
|
25
15
|
|
26
16
|
say("Loading #{kind} RBIs from #{file}... ")
|
27
|
-
|
28
|
-
|
17
|
+
time = Benchmark.realtime do
|
18
|
+
parse_and_index_files(index, [file], number_of_workers: 1)
|
19
|
+
end
|
20
|
+
say(" Done ", :green)
|
21
|
+
say("(#{time.round(2)}s)")
|
29
22
|
end
|
30
23
|
|
31
|
-
sig { params(index: RBI::Index, kind: String, dir: String).void }
|
32
|
-
def index_rbis(index, kind, dir)
|
24
|
+
sig { params(index: RBI::Index, kind: String, dir: String, number_of_workers: T.nilable(Integer)).void }
|
25
|
+
def index_rbis(index, kind, dir, number_of_workers:)
|
33
26
|
return unless Dir.exist?(dir) && !Dir.empty?(dir)
|
34
27
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
28
|
+
if kind == "payload"
|
29
|
+
say("Loading Sorbet payload... ")
|
30
|
+
else
|
31
|
+
say("Loading #{kind} RBIs from #{dir}... ")
|
32
|
+
end
|
33
|
+
time = Benchmark.realtime do
|
34
|
+
files = Dir.glob("#{dir}/**/*.rbi").sort
|
35
|
+
parse_and_index_files(index, files, number_of_workers: number_of_workers)
|
36
|
+
end
|
37
|
+
say(" Done ", :green)
|
38
|
+
say("(#{time.round(2)}s)")
|
39
39
|
end
|
40
40
|
|
41
41
|
sig do
|
@@ -48,13 +48,16 @@ module Tapioca
|
|
48
48
|
def duplicated_nodes_from_index(index, shim_rbi_dir:, todo_rbi_file:)
|
49
49
|
duplicates = {}
|
50
50
|
say("Looking for duplicates... ")
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
time = Benchmark.realtime do
|
52
|
+
index.keys.each do |key|
|
53
|
+
nodes = index[key]
|
54
|
+
next unless shims_or_todos_have_duplicates?(nodes, shim_rbi_dir: shim_rbi_dir, todo_rbi_file: todo_rbi_file)
|
54
55
|
|
55
|
-
|
56
|
+
duplicates[key] = nodes
|
57
|
+
end
|
56
58
|
end
|
57
|
-
say(" Done", :green)
|
59
|
+
say(" Done ", :green)
|
60
|
+
say("(#{time.round(2)}s)")
|
58
61
|
duplicates
|
59
62
|
end
|
60
63
|
|
@@ -97,14 +100,16 @@ module Tapioca
|
|
97
100
|
|
98
101
|
if errors.empty?
|
99
102
|
say(" No errors found\n\n", [:green, :bold])
|
103
|
+
|
100
104
|
return
|
101
105
|
end
|
102
106
|
|
103
107
|
parse_errors = errors.select { |error| error.code < 4000 }
|
104
108
|
|
105
|
-
|
106
|
-
say_error(<<~ERR, :red)
|
109
|
+
error_messages = []
|
107
110
|
|
111
|
+
if parse_errors.any?
|
112
|
+
error_messages << set_color(<<~ERR, :red)
|
108
113
|
##### INTERNAL ERROR #####
|
109
114
|
|
110
115
|
There are parse errors in the generated RBI files.
|
@@ -116,27 +121,23 @@ module Tapioca
|
|
116
121
|
|
117
122
|
Command:
|
118
123
|
#{command}
|
119
|
-
|
120
124
|
ERR
|
121
125
|
|
122
|
-
|
126
|
+
error_messages << set_color(<<~ERR, :red) if gems.any?
|
123
127
|
Gems:
|
124
128
|
#{gems.map { |gem| " #{gem.name} (#{gem.version})" }.join("\n")}
|
125
|
-
|
126
129
|
ERR
|
127
130
|
|
128
|
-
|
131
|
+
error_messages << set_color(<<~ERR, :red) if compilers.any?
|
129
132
|
Compilers:
|
130
133
|
#{compilers.map { |compiler| " #{compiler.name}" }.join("\n")}
|
131
|
-
|
132
134
|
ERR
|
133
135
|
|
134
|
-
|
136
|
+
error_messages << set_color(<<~ERR, :red)
|
135
137
|
Errors:
|
136
138
|
#{parse_errors.map { |error| " #{error}" }.join("\n")}
|
137
139
|
|
138
140
|
##########################
|
139
|
-
|
140
141
|
ERR
|
141
142
|
end
|
142
143
|
|
@@ -145,26 +146,24 @@ module Tapioca
|
|
145
146
|
update_gem_rbis_strictnesses(redef_errors, gem_dir)
|
146
147
|
end
|
147
148
|
|
148
|
-
Kernel.
|
149
|
+
Kernel.raise Thor::Error, error_messages.join("\n") if parse_errors.any?
|
149
150
|
end
|
150
151
|
|
151
152
|
private
|
152
153
|
|
153
|
-
sig { params(index: RBI::Index, files: T::Array[String]).void }
|
154
|
-
def parse_and_index_files(index, files)
|
155
|
-
files
|
156
|
-
parse_and_index_file(index, file)
|
157
|
-
end
|
158
|
-
end
|
154
|
+
sig { params(index: RBI::Index, files: T::Array[String], number_of_workers: T.nilable(Integer)).void }
|
155
|
+
def parse_and_index_files(index, files, number_of_workers:)
|
156
|
+
executor = Executor.new(files, number_of_workers: number_of_workers)
|
159
157
|
|
160
|
-
|
161
|
-
|
162
|
-
|
158
|
+
trees = executor.run_in_parallel do |file|
|
159
|
+
next if Spoom::Sorbet::Sigils.file_strictness(file) == "ignore"
|
160
|
+
|
161
|
+
RBI::Parser.parse_file(file)
|
162
|
+
rescue RBI::ParseError => e
|
163
|
+
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
164
|
+
end
|
163
165
|
|
164
|
-
|
165
|
-
index.visit(tree)
|
166
|
-
rescue RBI::ParseError => e
|
167
|
-
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
166
|
+
index.visit_all(trees)
|
168
167
|
end
|
169
168
|
|
170
169
|
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String, todo_rbi_file: String).returns(T::Boolean) }
|
@@ -93,15 +93,36 @@ module Tapioca
|
|
93
93
|
|
94
94
|
sig { params(name: String).returns(T::Boolean) }
|
95
95
|
def valid_method_name?(name)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
)
|
96
|
+
# try to parse a method definition with this name
|
97
|
+
iseq = RubyVM::InstructionSequence.compile("def #{name}; end", nil, nil, 0, false)
|
98
|
+
# pull out the first operation in the instruction sequence and its first argument
|
99
|
+
op, arg, _data = iseq.to_a.dig(-1, 0)
|
100
|
+
# make sure that the operation is a method definition and the method that was
|
101
|
+
# defined has the expected name, for example, for `def !foo; end` we don't get
|
102
|
+
# a syntax error but instead get a method defined as `"foo"`
|
103
|
+
op == :definemethod && arg == name.to_sym
|
104
|
+
rescue SyntaxError
|
105
|
+
false
|
100
106
|
end
|
101
107
|
|
102
108
|
sig { params(name: String).returns(T::Boolean) }
|
103
109
|
def valid_parameter_name?(name)
|
104
|
-
|
110
|
+
sentinel_method_name = :sentinel_method_name
|
111
|
+
# try to parse a method definition with this name as the name of a
|
112
|
+
# keyword parameter. If we use a positional parameter, then parameter names
|
113
|
+
# like `&` (and maybe others) will be treated like `def foo(&); end` and will
|
114
|
+
# thus be considered valid. Using a required keyword parameter prevents that
|
115
|
+
# confusion between Ruby syntax and parameter name.
|
116
|
+
iseq = RubyVM::InstructionSequence.compile("def #{sentinel_method_name}(#{name}:); end", nil, nil, 0, false)
|
117
|
+
# pull out the first operation in the instruction sequence and its first argument and data
|
118
|
+
op, arg, data = iseq.to_a.dig(-1, 0)
|
119
|
+
# make sure that:
|
120
|
+
# 1. a method was defined, and
|
121
|
+
# 2. the method has the expected method name, and
|
122
|
+
# 3. the method has a keyword parameter with the expected name
|
123
|
+
op == :definemethod && arg == sentinel_method_name && data.dig(11, :keyword, 0) == name.to_sym
|
124
|
+
rescue SyntaxError
|
125
|
+
false
|
105
126
|
end
|
106
127
|
end
|
107
128
|
end
|
@@ -19,6 +19,11 @@ module Tapioca
|
|
19
19
|
::Gem::Requirement.new(selector).satisfied_by?(::Gem::Version.new(RUBY_VERSION))
|
20
20
|
end
|
21
21
|
|
22
|
+
sig { params(selector: String).returns(T::Boolean) }
|
23
|
+
def rails_version(selector)
|
24
|
+
::Gem::Requirement.new(selector).satisfied_by?(ActiveSupport.gem_version)
|
25
|
+
end
|
26
|
+
|
22
27
|
sig { params(src: String).returns(String) }
|
23
28
|
def template(src)
|
24
29
|
erb = if ERB_SUPPORTS_KVARGS
|
data/lib/tapioca/internal.rb
CHANGED
@@ -7,6 +7,7 @@ require "tapioca"
|
|
7
7
|
require "tapioca/runtime/reflection"
|
8
8
|
require "tapioca/runtime/trackers"
|
9
9
|
|
10
|
+
require "benchmark"
|
10
11
|
require "bundler"
|
11
12
|
require "erb"
|
12
13
|
require "etc"
|
@@ -33,6 +34,7 @@ require "tapioca/helpers/rbi_helper"
|
|
33
34
|
require "tapioca/sorbet_ext/fixed_hash_patch"
|
34
35
|
require "tapioca/sorbet_ext/name_patch"
|
35
36
|
require "tapioca/sorbet_ext/generic_name_patch"
|
37
|
+
require "tapioca/sorbet_ext/proc_bind_patch"
|
36
38
|
require "tapioca/runtime/generic_type_registry"
|
37
39
|
|
38
40
|
require "tapioca/helpers/cli_helper"
|
@@ -6,6 +6,7 @@ module Tapioca
|
|
6
6
|
class Loader
|
7
7
|
extend(T::Sig)
|
8
8
|
include Tapioca::GemHelper
|
9
|
+
include Thor::Base
|
9
10
|
|
10
11
|
sig do
|
11
12
|
params(gemfile: Tapioca::Gemfile, initialize_file: T.nilable(String), require_file: T.nilable(String)).void
|
@@ -29,12 +30,17 @@ module Tapioca
|
|
29
30
|
silence_deprecations
|
30
31
|
|
31
32
|
if environment_load
|
32
|
-
|
33
|
+
require "./config/environment"
|
33
34
|
else
|
34
|
-
|
35
|
+
require "./config/application"
|
35
36
|
end
|
36
37
|
|
37
38
|
eager_load_rails_app if eager_load
|
39
|
+
rescue LoadError, StandardError => e
|
40
|
+
say("Tapioca attempted to load the Rails application after encountering a `config/application.rb` file, " \
|
41
|
+
"but it failed. If your application uses Rails please ensure it can be loaded correctly before generating " \
|
42
|
+
"RBIs.\n#{e}", :yellow)
|
43
|
+
say("Continuing RBI generation without loading the Rails application.")
|
38
44
|
end
|
39
45
|
|
40
46
|
private
|
@@ -158,15 +158,14 @@ module Tapioca
|
|
158
158
|
# Examines the call stack to identify the closest location where a "require" is performed
|
159
159
|
# by searching for the label "<top (required)>". If none is found, it returns the location
|
160
160
|
# labeled "<main>", which is the original call site.
|
161
|
-
sig { returns(String) }
|
162
|
-
def
|
163
|
-
locations = Kernel.caller_locations
|
161
|
+
sig { params(locations: T.nilable(T::Array[Thread::Backtrace::Location])).returns(String) }
|
162
|
+
def resolve_loc(locations)
|
164
163
|
return "" unless locations
|
165
164
|
|
166
|
-
|
167
|
-
return "" unless
|
165
|
+
resolved_loc = locations.find { |loc| REQUIRED_FROM_LABELS.include?(loc.label) }
|
166
|
+
return "" unless resolved_loc
|
168
167
|
|
169
|
-
|
168
|
+
resolved_loc.absolute_path || ""
|
170
169
|
end
|
171
170
|
|
172
171
|
sig { params(singleton_class: Module).returns(T.nilable(Module)) }
|
@@ -17,36 +17,54 @@ module Tapioca
|
|
17
17
|
const :path, String
|
18
18
|
end
|
19
19
|
|
20
|
-
@class_files = {}
|
20
|
+
@class_files = {}.compare_by_identity
|
21
21
|
|
22
22
|
# Immediately activated upon load. Observes class/module definition.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@class_files[key] ||= Set.new
|
39
|
-
@class_files[key] << ConstantLocation.new(path: T.must(file), lineno: T.must(lineno))
|
23
|
+
Tapioca.register_trace(:class) do |tp|
|
24
|
+
next if tp.self.singleton_class?
|
25
|
+
|
26
|
+
key = tp.self
|
27
|
+
|
28
|
+
path = tp.path
|
29
|
+
if File.exist?(path)
|
30
|
+
loc = build_constant_location(tp, caller_locations)
|
31
|
+
else
|
32
|
+
caller_location = T.must(caller_locations)
|
33
|
+
.find { |loc| loc.path && File.exist?(loc.path) }
|
34
|
+
|
35
|
+
next unless caller_location
|
36
|
+
|
37
|
+
loc = ConstantLocation.new(path: caller_location.absolute_path || "", lineno: caller_location.lineno)
|
40
38
|
end
|
39
|
+
|
40
|
+
(@class_files[key] ||= Set.new) << loc
|
41
|
+
end
|
42
|
+
|
43
|
+
Tapioca.register_trace(:c_return) do |tp|
|
44
|
+
next unless tp.method_id == :new
|
45
|
+
next unless Module === tp.return_value
|
46
|
+
|
47
|
+
key = tp.return_value
|
48
|
+
loc = build_constant_location(tp, caller_locations)
|
49
|
+
(@class_files[key] ||= Set.new) << loc
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.build_constant_location(tp, locations)
|
53
|
+
file = resolve_loc(caller_locations)
|
54
|
+
lineno = file == File.realpath(tp.path) ? tp.lineno : 0
|
55
|
+
|
56
|
+
ConstantLocation.new(path: file, lineno: lineno)
|
41
57
|
end
|
42
58
|
|
43
59
|
# Returns the files in which this class or module was opened. Doesn't know
|
44
60
|
# about situations where the class was opened prior to +require+ing,
|
45
61
|
# or where metaprogramming was used via +eval+, etc.
|
46
62
|
def self.files_for(klass)
|
47
|
-
|
48
|
-
|
49
|
-
|
63
|
+
locations_for(klass).map(&:path).to_set
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.locations_for(klass)
|
67
|
+
@class_files.fetch(klass, Set.new)
|
50
68
|
end
|
51
69
|
end
|
52
70
|
end
|
@@ -42,7 +42,7 @@ module Tapioca
|
|
42
42
|
def self.register(constant, mixin, mixin_type)
|
43
43
|
return unless @enabled
|
44
44
|
|
45
|
-
location = Reflection.
|
45
|
+
location = Reflection.resolve_loc(caller_locations)
|
46
46
|
|
47
47
|
constants = constants_with_mixin(mixin)
|
48
48
|
constants.fetch(mixin_type).store(constant, location)
|
@@ -1,18 +1,28 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
# We need sorbet to compile the signature for `qualified_name_of` before applying
|
5
|
-
# the patch to avoid an infinite loop.
|
6
|
-
T::Utils.signature_for_method(::Tapioca::Runtime::Reflection.method(:qualified_name_of))
|
7
|
-
|
8
4
|
module T
|
9
5
|
module Types
|
10
6
|
class Simple
|
11
7
|
module NamePatch
|
8
|
+
NAME_METHOD = T.let(Module.instance_method(:name), UnboundMethod)
|
9
|
+
|
12
10
|
def name
|
13
11
|
# Sorbet memoizes this method into the `@name` instance variable but
|
14
12
|
# doing so means that types get memoized before this patch is applied
|
15
|
-
|
13
|
+
qualified_name_of(@raw_type)
|
14
|
+
end
|
15
|
+
|
16
|
+
def qualified_name_of(constant)
|
17
|
+
name = NAME_METHOD.bind_call(constant)
|
18
|
+
name = nil if name&.start_with?("#<")
|
19
|
+
return if name.nil?
|
20
|
+
|
21
|
+
if name.start_with?("::")
|
22
|
+
name
|
23
|
+
else
|
24
|
+
"::#{name}"
|
25
|
+
end
|
16
26
|
end
|
17
27
|
end
|
18
28
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module T
|
5
|
+
module Types
|
6
|
+
module ProcBindPatch
|
7
|
+
def initialize(arg_types, returns, bind = T::Private::Methods::ARG_NOT_PROVIDED)
|
8
|
+
super(arg_types, returns)
|
9
|
+
|
10
|
+
unless bind == T::Private::Methods::ARG_NOT_PROVIDED
|
11
|
+
@bind = T.let(T::Utils.coerce(bind), T::Types::Base)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
name = super
|
17
|
+
name = name.sub("T.proc", "T.proc.bind(#{@bind})") unless @bind.nil?
|
18
|
+
name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Proc.prepend(ProcBindPatch)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module T
|
27
|
+
module Private
|
28
|
+
module Methods
|
29
|
+
module ProcBindPatch
|
30
|
+
def finalize_proc(decl)
|
31
|
+
super
|
32
|
+
|
33
|
+
T.unsafe(T::Types::Proc).new(decl.params, decl.returns, decl.bind)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
singleton_class.prepend(ProcBindPatch)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/tapioca/version.rb
CHANGED
data/lib/tapioca.rb
CHANGED
@@ -6,6 +6,18 @@ require "sorbet-runtime"
|
|
6
6
|
module Tapioca
|
7
7
|
extend T::Sig
|
8
8
|
|
9
|
+
@traces = T.let([], T::Array[TracePoint])
|
10
|
+
|
11
|
+
sig { params(trace_name: Symbol, block: T.proc.params(arg0: TracePoint).void).void }
|
12
|
+
def self.register_trace(trace_name, &block)
|
13
|
+
@traces << TracePoint.trace(trace_name, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { void }
|
17
|
+
def self.disable_traces
|
18
|
+
@traces.each(&:disable)
|
19
|
+
end
|
20
|
+
|
9
21
|
sig do
|
10
22
|
type_parameters(:Result)
|
11
23
|
.params(blk: T.proc.returns(T.type_parameter(:Result)))
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapioca
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ufuk Kayserilioglu
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2022-08-
|
14
|
+
date: 2022-08-22 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -224,6 +224,7 @@ files:
|
|
224
224
|
- lib/tapioca/gem/listeners/sorbet_required_ancestors.rb
|
225
225
|
- lib/tapioca/gem/listeners/sorbet_signatures.rb
|
226
226
|
- lib/tapioca/gem/listeners/sorbet_type_variables.rb
|
227
|
+
- lib/tapioca/gem/listeners/source_location.rb
|
227
228
|
- lib/tapioca/gem/listeners/subconstants.rb
|
228
229
|
- lib/tapioca/gem/listeners/yard_doc.rb
|
229
230
|
- lib/tapioca/gem/pipeline.rb
|
@@ -255,6 +256,7 @@ files:
|
|
255
256
|
- lib/tapioca/sorbet_ext/fixed_hash_patch.rb
|
256
257
|
- lib/tapioca/sorbet_ext/generic_name_patch.rb
|
257
258
|
- lib/tapioca/sorbet_ext/name_patch.rb
|
259
|
+
- lib/tapioca/sorbet_ext/proc_bind_patch.rb
|
258
260
|
- lib/tapioca/static/requires_compiler.rb
|
259
261
|
- lib/tapioca/static/symbol_loader.rb
|
260
262
|
- lib/tapioca/static/symbol_table_parser.rb
|