tapioca 0.9.0 → 0.9.1
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 +9 -2
- data/lib/tapioca/commands/annotations.rb +2 -1
- data/lib/tapioca/commands/check_shims.rb +17 -7
- data/lib/tapioca/commands/gem.rb +11 -6
- data/lib/tapioca/dsl/compilers/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +19 -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 +60 -0
- data/lib/tapioca/gem/listeners.rb +1 -0
- data/lib/tapioca/gem/pipeline.rb +6 -4
- data/lib/tapioca/helpers/rbi_files_helper.rb +37 -34
- data/lib/tapioca/helpers/rbi_helper.rb +4 -1
- data/lib/tapioca/internal.rb +1 -0
- data/lib/tapioca/runtime/reflection.rb +5 -6
- data/lib/tapioca/runtime/trackers/constant_definition.rb +38 -20
- data/lib/tapioca/runtime/trackers/mixin.rb +1 -1
- data/lib/tapioca/sorbet_ext/name_patch.rb +15 -5
- data/lib/tapioca/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e53d805bebee7cb1898eb795daebc5c7f586b5dea934a6056dfc2e1037168e83
|
4
|
+
data.tar.gz: a9baac8cc53cc87f24495961dfe738603900e9818542c722b79211da2aaea84a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 101e3405eb9fbc011e3ddb8ad33a622c74f18cde1d3b6be204aa7840fc7d1f89b1c61e61585cfe9d26c397e67839d09fcd85671c5c0b92f2c8b78741ee4e5ded
|
7
|
+
data.tar.gz: 0a7ad144e5860f72158c194de01213809a5294b69eb2a6de12288d827ca574efdc8937c967ec5c578b1a7c429d4f9e29bd5a1ee6738a373cf4cc57ca2ed8163a
|
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,6 +275,7 @@ 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
|
274
280
|
command = Commands::CheckShims.new(
|
275
281
|
gem_rbi_dir: options[:gem_rbi_dir],
|
@@ -277,7 +283,8 @@ module Tapioca
|
|
277
283
|
shim_rbi_dir: options[:shim_rbi_dir],
|
278
284
|
annotations_rbi_dir: options[:annotations_rbi_dir],
|
279
285
|
todo_rbi_file: options[:todo_rbi_file],
|
280
|
-
payload: options[:payload]
|
286
|
+
payload: options[:payload],
|
287
|
+
number_of_workers: options[:workers]
|
281
288
|
)
|
282
289
|
command.execute
|
283
290
|
end
|
@@ -193,7 +193,8 @@ module Tapioca
|
|
193
193
|
# Please run `#{default_command(:annotations)}` to update it.
|
194
194
|
COMMENT
|
195
195
|
|
196
|
-
contents
|
196
|
+
# Split contents into newlines and ensure trailing empty lines are included
|
197
|
+
contents = content.split("\n", -1)
|
197
198
|
if contents[0]&.start_with?("# typed:") && contents[1]&.empty?
|
198
199
|
contents.insert(2, header).join("\n")
|
199
200
|
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 }
|
@@ -51,7 +61,7 @@ module Tapioca
|
|
51
61
|
exit(1)
|
52
62
|
end
|
53
63
|
|
54
|
-
|
64
|
+
index_rbis(index, "payload", payload_path, number_of_workers: @number_of_workers)
|
55
65
|
end
|
56
66
|
else
|
57
67
|
say_error("The version of Sorbet used in your Gemfile.lock does not support `--print=payload-sources`")
|
@@ -62,10 +72,10 @@ module Tapioca
|
|
62
72
|
end
|
63
73
|
|
64
74
|
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)
|
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)
|
69
79
|
|
70
80
|
duplicates = duplicated_nodes_from_index(index, shim_rbi_dir: @shim_rbi_dir, todo_rbi_file: @todo_rbi_file)
|
71
81
|
unless duplicates.empty?
|
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
|
|
@@ -182,7 +185,7 @@ module Tapioca
|
|
182
185
|
default_command(:gem, gem.name),
|
183
186
|
reason: "types exported from the `#{gem.name}` gem",) if @file_header
|
184
187
|
|
185
|
-
rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc).compile
|
188
|
+
rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc, include_loc: @include_loc).compile
|
186
189
|
|
187
190
|
merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
|
188
191
|
|
@@ -201,11 +204,13 @@ module Tapioca
|
|
201
204
|
end
|
202
205
|
end
|
203
206
|
|
204
|
-
sig { void }
|
205
|
-
def perform_sync_verification
|
207
|
+
sig { params(exclude: T::Array[String]).void }
|
208
|
+
def perform_sync_verification(exclude: [])
|
206
209
|
diff = {}
|
207
210
|
|
208
211
|
removed_rbis.each do |gem_name|
|
212
|
+
next if exclude.include?(gem_name)
|
213
|
+
|
209
214
|
filename = existing_rbi(gem_name)
|
210
215
|
diff[filename] = :removed
|
211
216
|
end
|
@@ -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"
|
@@ -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,16 @@ 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
|
+
case column_type.subtype
|
123
|
+
when ActiveRecord::Type::Integer
|
124
|
+
"T.any(::String, ::Symbol, ::Integer)"
|
125
|
+
else
|
126
|
+
"T.any(::String, ::Symbol)"
|
127
|
+
end
|
128
|
+
end
|
111
129
|
end
|
112
130
|
end
|
113
131
|
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,60 @@
|
|
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
|
+
path = if path.realpath.to_s.start_with?(gem.full_gem_path)
|
49
|
+
"#{gem.name}-#{gem.version}/#{path.realpath.relative_path_from(gem.full_gem_path)}"
|
50
|
+
else
|
51
|
+
path.sub("#{Bundler.bundle_path}/gems/", "")
|
52
|
+
end
|
53
|
+
|
54
|
+
node.comments << RBI::Comment.new("") if node.comments.any?
|
55
|
+
node.comments << RBI::Comment.new("source://#{path}:#{line}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
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) }
|
@@ -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
|
|
@@ -150,19 +153,19 @@ module Tapioca
|
|
150
153
|
|
151
154
|
private
|
152
155
|
|
153
|
-
sig { params(index: RBI::Index, files: T::Array[String]).void }
|
154
|
-
def parse_and_index_files(index, files)
|
155
|
-
files
|
156
|
-
|
156
|
+
sig { params(index: RBI::Index, files: T::Array[String], number_of_workers: T.nilable(Integer)).void }
|
157
|
+
def parse_and_index_files(index, files, number_of_workers:)
|
158
|
+
executor = Executor.new(files, number_of_workers: number_of_workers)
|
159
|
+
|
160
|
+
trees = executor.run_in_parallel do |file|
|
161
|
+
next if Spoom::Sorbet::Sigils.file_strictness(file) == "ignore"
|
162
|
+
|
163
|
+
RBI::Parser.parse_file(file)
|
164
|
+
rescue RBI::ParseError => e
|
165
|
+
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
157
166
|
end
|
158
|
-
end
|
159
167
|
|
160
|
-
|
161
|
-
def parse_and_index_file(index, file)
|
162
|
-
tree = RBI::Parser.parse_file(file)
|
163
|
-
index.visit(tree)
|
164
|
-
rescue RBI::ParseError => e
|
165
|
-
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
168
|
+
index.visit_all(trees)
|
166
169
|
end
|
167
170
|
|
168
171
|
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String, todo_rbi_file: String).returns(T::Boolean) }
|
@@ -93,7 +93,10 @@ module Tapioca
|
|
93
93
|
|
94
94
|
sig { params(name: String).returns(T::Boolean) }
|
95
95
|
def valid_method_name?(name)
|
96
|
-
|
96
|
+
name == "==" || !(
|
97
|
+
name.to_sym.inspect.start_with?(':"', ":@", ":$") ||
|
98
|
+
name.delete_suffix("=").to_sym.inspect.start_with?(':"', ":@", ":$")
|
99
|
+
)
|
97
100
|
end
|
98
101
|
|
99
102
|
sig { params(name: String).returns(T::Boolean) }
|
data/lib/tapioca/internal.rb
CHANGED
@@ -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(constant: Module).returns(T.nilable(String)) }
|
@@ -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
23
|
TracePoint.trace(:class) do |tp|
|
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))
|
24
|
+
next if tp.self.singleton_class?
|
25
|
+
|
26
|
+
key = tp.self
|
27
|
+
|
28
|
+
if tp.path == "(eval)"
|
29
|
+
caller_location = T.must(caller_locations)
|
30
|
+
.drop_while { |loc| loc.path == "(eval)" }
|
31
|
+
.first
|
32
|
+
|
33
|
+
next unless caller_location
|
34
|
+
|
35
|
+
loc = ConstantLocation.new(path: caller_location.absolute_path || "", lineno: caller_location.lineno)
|
36
|
+
else
|
37
|
+
loc = build_constant_location(tp, caller_locations)
|
40
38
|
end
|
39
|
+
|
40
|
+
(@class_files[key] ||= Set.new) << loc
|
41
|
+
end
|
42
|
+
|
43
|
+
TracePoint.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
|
|
data/lib/tapioca/version.rb
CHANGED
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.1
|
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-07-
|
14
|
+
date: 2022-07-14 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
|