tapioca 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|