tapioca 0.9.2 → 0.9.3
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 +0 -6
- data/lib/tapioca/cli.rb +2 -9
- data/lib/tapioca/commands/annotations.rb +2 -3
- data/lib/tapioca/commands/check_shims.rb +7 -17
- data/lib/tapioca/commands/gem.rb +6 -11
- data/lib/tapioca/dsl/compiler.rb +0 -1
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +0 -1
- data/lib/tapioca/dsl/compilers/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +1 -19
- data/lib/tapioca/gem/events.rb +1 -6
- data/lib/tapioca/gem/listeners/foreign_constants.rb +3 -3
- data/lib/tapioca/gem/listeners/methods.rb +1 -1
- data/lib/tapioca/gem/listeners.rb +0 -1
- data/lib/tapioca/gem/pipeline.rb +4 -6
- data/lib/tapioca/helpers/rbi_files_helper.rb +46 -37
- data/lib/tapioca/helpers/rbi_helper.rb +5 -26
- data/lib/tapioca/internal.rb +0 -2
- data/lib/tapioca/rbi_ext/model.rb +1 -1
- data/lib/tapioca/rbi_formatter.rb +1 -1
- data/lib/tapioca/runtime/reflection.rb +13 -13
- data/lib/tapioca/runtime/trackers/constant_definition.rb +20 -38
- data/lib/tapioca/runtime/trackers/mixin.rb +2 -2
- data/lib/tapioca/sorbet_ext/name_patch.rb +5 -15
- data/lib/tapioca/version.rb +1 -1
- metadata +2 -4
- data/lib/tapioca/gem/listeners/source_location.rb +0 -67
- data/lib/tapioca/sorbet_ext/proc_bind_patch.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92e743973e21c5d863f898255a4ccfcf620deea13cbb4807d43ad0c58085285e
|
4
|
+
data.tar.gz: 4a8e05c974c11d4d201e1f2342e1769260a63ceaff18d54a71167dd929f6f23a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92b8a9e0306b22869147b1f0a3912f6fb88e3583e54caab6dbc37450996d15046ae049bea5fcfd46090575e94c7b4b16ff4ca833b63fdc125b30601211a2603e
|
7
|
+
data.tar.gz: d76e8b15e040f6fe60cb940ef3654dffd45eaf3edaaa563a6404a178014bc767866c5668d0dc2560610e75adb5034ac6ae0c94d19d6fa1c5d4812c5769a78a74
|
data/README.md
CHANGED
@@ -152,8 +152,6 @@ 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
|
157
155
|
[--exported-gem-rbis], [--no-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
|
158
156
|
# Default: true
|
159
157
|
-w, [--workers=N] # EXPERIMENTAL: Number of parallel workers to use when generating RBIs
|
@@ -742,8 +740,6 @@ Options:
|
|
742
740
|
# Default: sorbet/rbi/todo.rbi
|
743
741
|
[--payload], [--no-payload] # Check shims against Sorbet's payload
|
744
742
|
# Default: true
|
745
|
-
-w, [--workers=N] # EXPERIMENTAL: Number of parallel workers
|
746
|
-
# Default: 1
|
747
743
|
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
748
744
|
# Default: sorbet/tapioca/config.yml
|
749
745
|
-V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
|
@@ -806,7 +802,6 @@ gem:
|
|
806
802
|
activesupport: 'false'
|
807
803
|
verify: false
|
808
804
|
doc: true
|
809
|
-
loc: true
|
810
805
|
exported_gem_rbis: true
|
811
806
|
workers: 1
|
812
807
|
auto_strictness: true
|
@@ -820,7 +815,6 @@ check_shims:
|
|
820
815
|
annotations_rbi_dir: sorbet/rbi/annotations
|
821
816
|
todo_rbi_file: sorbet/rbi/todo.rbi
|
822
817
|
payload: true
|
823
|
-
workers: 1
|
824
818
|
annotations:
|
825
819
|
sources:
|
826
820
|
- https://raw.githubusercontent.com/Shopify/rbi-central/main
|
data/lib/tapioca/cli.rb
CHANGED
@@ -190,10 +190,6 @@ 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
|
197
193
|
option :exported_gem_rbis,
|
198
194
|
type: :boolean,
|
199
195
|
desc: "Include RBIs found in the `rbi/` directory of the gem",
|
@@ -237,7 +233,6 @@ module Tapioca
|
|
237
233
|
outpath: Pathname.new(options[:outdir]),
|
238
234
|
file_header: options[:file_header],
|
239
235
|
include_doc: options[:doc],
|
240
|
-
include_loc: options[:loc],
|
241
236
|
include_exported_rbis: options[:exported_gem_rbis],
|
242
237
|
number_of_workers: options[:workers],
|
243
238
|
auto_strictness: options[:auto_strictness],
|
@@ -260,7 +255,7 @@ module Tapioca
|
|
260
255
|
end
|
261
256
|
|
262
257
|
if gems.empty? && !all
|
263
|
-
command.sync(should_verify: verify
|
258
|
+
command.sync(should_verify: verify)
|
264
259
|
else
|
265
260
|
command.execute
|
266
261
|
end
|
@@ -275,7 +270,6 @@ module Tapioca
|
|
275
270
|
option :annotations_rbi_dir, type: :string, desc: "Path to annotations RBIs", default: DEFAULT_ANNOTATIONS_DIR
|
276
271
|
option :todo_rbi_file, type: :string, desc: "Path to the generated todo RBI file", default: DEFAULT_TODO_FILE
|
277
272
|
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
|
279
273
|
def check_shims
|
280
274
|
command = Commands::CheckShims.new(
|
281
275
|
gem_rbi_dir: options[:gem_rbi_dir],
|
@@ -283,8 +277,7 @@ module Tapioca
|
|
283
277
|
shim_rbi_dir: options[:shim_rbi_dir],
|
284
278
|
annotations_rbi_dir: options[:annotations_rbi_dir],
|
285
279
|
todo_rbi_file: options[:todo_rbi_file],
|
286
|
-
payload: options[:payload]
|
287
|
-
number_of_workers: options[:workers]
|
280
|
+
payload: options[:payload]
|
288
281
|
)
|
289
282
|
command.execute
|
290
283
|
end
|
@@ -112,7 +112,7 @@ module Tapioca
|
|
112
112
|
fetchable_gems = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
|
113
113
|
|
114
114
|
gem_names.each_with_object(fetchable_gems) do |gem_name, hash|
|
115
|
-
@indexes.each { |uri, index| hash[gem_name] << uri if index.has_gem?(gem_name) }
|
115
|
+
@indexes.each { |uri, index| T.must(hash[gem_name]) << uri if index.has_gem?(gem_name) }
|
116
116
|
end
|
117
117
|
|
118
118
|
if fetchable_gems.empty?
|
@@ -193,8 +193,7 @@ module Tapioca
|
|
193
193
|
# Please run `#{default_command(:annotations)}` to update it.
|
194
194
|
COMMENT
|
195
195
|
|
196
|
-
|
197
|
-
contents = content.split("\n", -1)
|
196
|
+
contents = content.split("\n")
|
198
197
|
if contents[0]&.start_with?("# typed:") && contents[1]&.empty?
|
199
198
|
contents.insert(2, header).join("\n")
|
200
199
|
else
|
@@ -15,19 +15,10 @@ module Tapioca
|
|
15
15
|
annotations_rbi_dir: String,
|
16
16
|
shim_rbi_dir: String,
|
17
17
|
todo_rbi_file: String,
|
18
|
-
payload: T::Boolean
|
19
|
-
number_of_workers: T.nilable(Integer)
|
18
|
+
payload: T::Boolean
|
20
19
|
).void
|
21
20
|
end
|
22
|
-
def initialize(
|
23
|
-
gem_rbi_dir:,
|
24
|
-
dsl_rbi_dir:,
|
25
|
-
annotations_rbi_dir:,
|
26
|
-
shim_rbi_dir:,
|
27
|
-
todo_rbi_file:,
|
28
|
-
payload:,
|
29
|
-
number_of_workers:
|
30
|
-
)
|
21
|
+
def initialize(gem_rbi_dir:, dsl_rbi_dir:, annotations_rbi_dir:, shim_rbi_dir:, todo_rbi_file:, payload:)
|
31
22
|
super()
|
32
23
|
@gem_rbi_dir = gem_rbi_dir
|
33
24
|
@dsl_rbi_dir = dsl_rbi_dir
|
@@ -35,7 +26,6 @@ module Tapioca
|
|
35
26
|
@shim_rbi_dir = shim_rbi_dir
|
36
27
|
@todo_rbi_file = todo_rbi_file
|
37
28
|
@payload = payload
|
38
|
-
@number_of_workers = number_of_workers
|
39
29
|
end
|
40
30
|
|
41
31
|
sig { override.void }
|
@@ -61,7 +51,7 @@ module Tapioca
|
|
61
51
|
exit(1)
|
62
52
|
end
|
63
53
|
|
64
|
-
|
54
|
+
index_payload(index, payload_path)
|
65
55
|
end
|
66
56
|
else
|
67
57
|
say_error("The version of Sorbet used in your Gemfile.lock does not support `--print=payload-sources`")
|
@@ -72,10 +62,10 @@ module Tapioca
|
|
72
62
|
end
|
73
63
|
|
74
64
|
index_rbi(index, "todo", @todo_rbi_file)
|
75
|
-
index_rbis(index, "shim", @shim_rbi_dir
|
76
|
-
index_rbis(index, "gem", @gem_rbi_dir
|
77
|
-
index_rbis(index, "dsl", @dsl_rbi_dir
|
78
|
-
index_rbis(index, "annotation", @annotations_rbi_dir
|
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)
|
79
69
|
|
80
70
|
duplicates = duplicated_nodes_from_index(index, shim_rbi_dir: @shim_rbi_dir, todo_rbi_file: @todo_rbi_file)
|
81
71
|
unless duplicates.empty?
|
data/lib/tapioca/commands/gem.rb
CHANGED
@@ -17,7 +17,6 @@ module Tapioca
|
|
17
17
|
outpath: Pathname,
|
18
18
|
file_header: T::Boolean,
|
19
19
|
include_doc: T::Boolean,
|
20
|
-
include_loc: T::Boolean,
|
21
20
|
include_exported_rbis: T::Boolean,
|
22
21
|
number_of_workers: T.nilable(Integer),
|
23
22
|
auto_strictness: T::Boolean,
|
@@ -34,7 +33,6 @@ module Tapioca
|
|
34
33
|
outpath:,
|
35
34
|
file_header:,
|
36
35
|
include_doc:,
|
37
|
-
include_loc:,
|
38
36
|
include_exported_rbis:,
|
39
37
|
number_of_workers: nil,
|
40
38
|
auto_strictness: true,
|
@@ -60,7 +58,6 @@ module Tapioca
|
|
60
58
|
@existing_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
|
61
59
|
@expected_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
|
62
60
|
@include_doc = T.let(include_doc, T::Boolean)
|
63
|
-
@include_loc = T.let(include_loc, T::Boolean)
|
64
61
|
@include_exported_rbis = include_exported_rbis
|
65
62
|
end
|
66
63
|
|
@@ -97,12 +94,12 @@ module Tapioca
|
|
97
94
|
end
|
98
95
|
end
|
99
96
|
|
100
|
-
sig { params(should_verify: T::Boolean
|
101
|
-
def sync(should_verify: false
|
97
|
+
sig { params(should_verify: T::Boolean).void }
|
98
|
+
def sync(should_verify: false)
|
102
99
|
if should_verify
|
103
100
|
say("Checking for out-of-date RBIs...")
|
104
101
|
say("")
|
105
|
-
perform_sync_verification
|
102
|
+
perform_sync_verification
|
106
103
|
return
|
107
104
|
end
|
108
105
|
|
@@ -185,7 +182,7 @@ module Tapioca
|
|
185
182
|
default_command(:gem, gem.name),
|
186
183
|
reason: "types exported from the `#{gem.name}` gem",) if @file_header
|
187
184
|
|
188
|
-
rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc
|
185
|
+
rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc).compile
|
189
186
|
|
190
187
|
merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
|
191
188
|
|
@@ -204,13 +201,11 @@ module Tapioca
|
|
204
201
|
end
|
205
202
|
end
|
206
203
|
|
207
|
-
sig {
|
208
|
-
def perform_sync_verification
|
204
|
+
sig { void }
|
205
|
+
def perform_sync_verification
|
209
206
|
diff = {}
|
210
207
|
|
211
208
|
removed_rbis.each do |gem_name|
|
212
|
-
next if exclude.include?(gem_name)
|
213
|
-
|
214
209
|
filename = existing_rbi(gem_name)
|
215
210
|
diff[filename] = :removed
|
216
211
|
end
|
data/lib/tapioca/dsl/compiler.rb
CHANGED
@@ -65,7 +65,7 @@ module Tapioca
|
|
65
65
|
class FrozenRecord < Compiler
|
66
66
|
extend T::Sig
|
67
67
|
|
68
|
-
ConstantType = type_member { { fixed: T.
|
68
|
+
ConstantType = type_member { { fixed: T.class_of(::FrozenRecord::Base) } }
|
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 = constant.__tapioca_scope_names
|
100
|
+
scopes = T.unsafe(constant).__tapioca_scope_names
|
101
101
|
return if scopes.nil?
|
102
102
|
|
103
103
|
module_name = "GeneratedRelationMethods"
|
@@ -38,20 +38,12 @@ module Tapioca
|
|
38
38
|
"::Time"
|
39
39
|
when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
|
40
40
|
"::ActiveSupport::TimeWithZone"
|
41
|
-
when ActiveRecord::Enum::EnumType
|
42
|
-
"::String"
|
43
41
|
else
|
44
42
|
handle_unknown_type(column_type)
|
45
43
|
end
|
46
44
|
|
47
45
|
column = @constant.columns_hash[column_name]
|
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
|
46
|
+
setter_type = getter_type
|
55
47
|
|
56
48
|
if column&.null
|
57
49
|
return [as_nilable_type(getter_type), as_nilable_type(setter_type)]
|
@@ -116,16 +108,6 @@ module Tapioca
|
|
116
108
|
|
117
109
|
first_argument_type.to_s
|
118
110
|
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
|
129
111
|
end
|
130
112
|
end
|
131
113
|
end
|
data/lib/tapioca/gem/events.rb
CHANGED
@@ -105,9 +105,6 @@ module Tapioca
|
|
105
105
|
class MethodNodeAdded < NodeAdded
|
106
106
|
extend T::Sig
|
107
107
|
|
108
|
-
sig { returns(UnboundMethod) }
|
109
|
-
attr_reader :method
|
110
|
-
|
111
108
|
sig { returns(RBI::Method) }
|
112
109
|
attr_reader :node
|
113
110
|
|
@@ -121,16 +118,14 @@ module Tapioca
|
|
121
118
|
params(
|
122
119
|
symbol: String,
|
123
120
|
constant: Module,
|
124
|
-
method: UnboundMethod,
|
125
121
|
node: RBI::Method,
|
126
122
|
signature: T.untyped,
|
127
123
|
parameters: T::Array[[Symbol, String]]
|
128
124
|
).void.checked(:never)
|
129
125
|
end
|
130
|
-
def initialize(symbol, constant,
|
126
|
+
def initialize(symbol, constant, node, signature, parameters)
|
131
127
|
super(symbol, constant)
|
132
128
|
@node = node
|
133
|
-
@method = method
|
134
129
|
@signature = signature
|
135
130
|
@parameters = parameters
|
136
131
|
end
|
@@ -35,10 +35,10 @@ module Tapioca
|
|
35
35
|
# base constant. Then, generate RBIs as if the base constant is extending the mixin,
|
36
36
|
# which is functionally equivalent to including or prepending to the singleton class.
|
37
37
|
if !name && constant.singleton_class?
|
38
|
-
|
39
|
-
next unless
|
38
|
+
attached_class = attached_class_of(constant)
|
39
|
+
next unless attached_class
|
40
40
|
|
41
|
-
constant =
|
41
|
+
constant = attached_class
|
42
42
|
end
|
43
43
|
|
44
44
|
@pipeline.push_foreign_constant(name, constant) if name
|
@@ -134,7 +134,7 @@ module Tapioca
|
|
134
134
|
end
|
135
135
|
end
|
136
136
|
|
137
|
-
@pipeline.push_method(symbol_name, constant,
|
137
|
+
@pipeline.push_method(symbol_name, constant, rbi_method, signature, sanitized_parameters)
|
138
138
|
tree << rbi_method
|
139
139
|
end
|
140
140
|
|
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
|
17
|
-
def initialize(gem, include_doc: false
|
16
|
+
sig { params(gem: Gemfile::GemSpec, include_doc: T::Boolean).void }
|
17
|
+
def initialize(gem, include_doc: 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,7 +40,6 @@ 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
|
44
43
|
@node_listeners << Gem::Listeners::RemoveEmptyPayloadScopes.new(self)
|
45
44
|
end
|
46
45
|
|
@@ -88,14 +87,13 @@ module Tapioca
|
|
88
87
|
params(
|
89
88
|
symbol: String,
|
90
89
|
constant: Module,
|
91
|
-
method: UnboundMethod,
|
92
90
|
node: RBI::Method,
|
93
91
|
signature: T.untyped,
|
94
92
|
parameters: T::Array[[Symbol, String]]
|
95
93
|
).void.checked(:never)
|
96
94
|
end
|
97
|
-
def push_method(symbol, constant,
|
98
|
-
@events << Gem::MethodNodeAdded.new(symbol, constant,
|
95
|
+
def push_method(symbol, constant, node, signature, parameters)
|
96
|
+
@events << Gem::MethodNodeAdded.new(symbol, constant, node, signature, parameters)
|
99
97
|
end
|
100
98
|
|
101
99
|
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
|
+
|
12
22
|
sig { params(index: RBI::Index, kind: String, file: String).void }
|
13
23
|
def index_rbi(index, kind, file)
|
14
24
|
return unless File.exist?(file)
|
15
25
|
|
16
26
|
say("Loading #{kind} RBIs from #{file}... ")
|
17
|
-
|
18
|
-
|
19
|
-
end
|
20
|
-
say(" Done ", :green)
|
21
|
-
say("(#{time.round(2)}s)")
|
27
|
+
parse_and_index_file(index, file)
|
28
|
+
say(" Done", :green)
|
22
29
|
end
|
23
30
|
|
24
|
-
sig { params(index: RBI::Index, kind: String, dir: String
|
25
|
-
def index_rbis(index, kind, dir
|
31
|
+
sig { params(index: RBI::Index, kind: String, dir: String).void }
|
32
|
+
def index_rbis(index, kind, dir)
|
26
33
|
return unless Dir.exist?(dir) && !Dir.empty?(dir)
|
27
34
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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)")
|
35
|
+
say("Loading #{kind} RBIs from #{dir}... ")
|
36
|
+
files = Dir.glob("#{dir}/**/*.rbi").sort
|
37
|
+
parse_and_index_files(index, files)
|
38
|
+
say(" Done", :green)
|
39
39
|
end
|
40
40
|
|
41
41
|
sig do
|
@@ -48,16 +48,13 @@ 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
|
-
|
54
|
-
next unless shims_or_todos_have_duplicates?(nodes, shim_rbi_dir: shim_rbi_dir, todo_rbi_file: todo_rbi_file)
|
51
|
+
index.keys.each do |key|
|
52
|
+
nodes = index[key]
|
53
|
+
next unless shims_or_todos_have_duplicates?(nodes, shim_rbi_dir: shim_rbi_dir, todo_rbi_file: todo_rbi_file)
|
55
54
|
|
56
|
-
|
57
|
-
end
|
55
|
+
duplicates[key] = nodes
|
58
56
|
end
|
59
|
-
say(" Done
|
60
|
-
say("(#{time.round(2)}s)")
|
57
|
+
say(" Done", :green)
|
61
58
|
duplicates
|
62
59
|
end
|
63
60
|
|
@@ -153,19 +150,21 @@ module Tapioca
|
|
153
150
|
|
154
151
|
private
|
155
152
|
|
156
|
-
sig { params(index: RBI::Index, files: T::Array[String]
|
157
|
-
def parse_and_index_files(index, files
|
158
|
-
|
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)
|
153
|
+
sig { params(index: RBI::Index, files: T::Array[String]).void }
|
154
|
+
def parse_and_index_files(index, files)
|
155
|
+
files.each do |file|
|
156
|
+
parse_and_index_file(index, file)
|
166
157
|
end
|
158
|
+
end
|
159
|
+
|
160
|
+
sig { params(index: RBI::Index, file: String).void }
|
161
|
+
def parse_and_index_file(index, file)
|
162
|
+
return if Spoom::Sorbet::Sigils.file_strictness(file) == "ignore"
|
167
163
|
|
168
|
-
|
164
|
+
tree = RBI::Parser.parse_file(file)
|
165
|
+
index.visit(tree)
|
166
|
+
rescue RBI::ParseError => e
|
167
|
+
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
169
168
|
end
|
170
169
|
|
171
170
|
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String, todo_rbi_file: String).returns(T::Boolean) }
|
@@ -178,6 +177,9 @@ module Tapioca
|
|
178
177
|
shims_or_todos_empty_scopes = extract_empty_scopes(shims_or_todos)
|
179
178
|
return true unless shims_or_todos_empty_scopes.empty?
|
180
179
|
|
180
|
+
mixins = extract_mixins(shims_or_todos)
|
181
|
+
return true unless mixins.empty?
|
182
|
+
|
181
183
|
props = extract_methods_and_attrs(shims_or_todos)
|
182
184
|
return false if props.empty?
|
183
185
|
|
@@ -215,6 +217,13 @@ module Tapioca
|
|
215
217
|
end, T::Array[T.any(RBI::Method, RBI::Attr)])
|
216
218
|
end
|
217
219
|
|
220
|
+
sig { params(nodes: T::Array[RBI::Node]).returns(T::Array[T.any(RBI::Mixin, RBI::RequiresAncestor)]) }
|
221
|
+
def extract_mixins(nodes)
|
222
|
+
T.cast(nodes.select do |node|
|
223
|
+
node.is_a?(RBI::Mixin) || node.is_a?(RBI::RequiresAncestor)
|
224
|
+
end, T::Array[T.all(RBI::Mixin, RBI::RequiresAncestor)])
|
225
|
+
end
|
226
|
+
|
218
227
|
sig { params(nodes: T::Array[T.any(RBI::Method, RBI::Attr)]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
|
219
228
|
def extract_nodes_with_sigs(nodes)
|
220
229
|
nodes.reject { |node| node.sigs.empty? }
|
@@ -93,36 +93,15 @@ module Tapioca
|
|
93
93
|
|
94
94
|
sig { params(name: String).returns(T::Boolean) }
|
95
95
|
def valid_method_name?(name)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
96
|
+
name == "==" || !(
|
97
|
+
name.to_sym.inspect.start_with?(':"', ":@", ":$") ||
|
98
|
+
name.delete_suffix("=").to_sym.inspect.start_with?(':"', ":@", ":$")
|
99
|
+
)
|
106
100
|
end
|
107
101
|
|
108
102
|
sig { params(name: String).returns(T::Boolean) }
|
109
103
|
def valid_parameter_name?(name)
|
110
|
-
|
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
|
104
|
+
/^([[:lower:]]|_|[^[[:ascii:]]])([[:alnum:]]|_|[^[[:ascii:]]])*$/.match?(name)
|
126
105
|
end
|
127
106
|
end
|
128
107
|
end
|
data/lib/tapioca/internal.rb
CHANGED
@@ -7,7 +7,6 @@ require "tapioca"
|
|
7
7
|
require "tapioca/runtime/reflection"
|
8
8
|
require "tapioca/runtime/trackers"
|
9
9
|
|
10
|
-
require "benchmark"
|
11
10
|
require "bundler"
|
12
11
|
require "erb"
|
13
12
|
require "etc"
|
@@ -34,7 +33,6 @@ require "tapioca/helpers/rbi_helper"
|
|
34
33
|
require "tapioca/sorbet_ext/fixed_hash_patch"
|
35
34
|
require "tapioca/sorbet_ext/name_patch"
|
36
35
|
require "tapioca/sorbet_ext/generic_name_patch"
|
37
|
-
require "tapioca/sorbet_ext/proc_bind_patch"
|
38
36
|
require "tapioca/runtime/generic_type_registry"
|
39
37
|
|
40
38
|
require "tapioca/helpers/cli_helper"
|
@@ -101,7 +101,7 @@ module RBI
|
|
101
101
|
|
102
102
|
sig { returns(T::Hash[String, RBI::Node]) }
|
103
103
|
def nodes_cache
|
104
|
-
|
104
|
+
@nodes_cache ||= T.let({}, T.nilable(T::Hash[String, Node]))
|
105
105
|
end
|
106
106
|
|
107
107
|
sig { params(node: RBI::Node).returns(RBI::Node) }
|
@@ -22,7 +22,7 @@ module Tapioca
|
|
22
22
|
def write_empty_body_comment!(file)
|
23
23
|
file.comments << RBI::BlankLine.new unless file.comments.empty?
|
24
24
|
file.comments << RBI::Comment.new("THIS IS AN EMPTY RBI FILE.")
|
25
|
-
file.comments << RBI::Comment.new("see https://github.com/Shopify/tapioca
|
25
|
+
file.comments << RBI::Comment.new("see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem")
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -158,25 +158,25 @@ 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 {
|
162
|
-
def
|
161
|
+
sig { returns(String) }
|
162
|
+
def required_from_location
|
163
|
+
locations = Kernel.caller_locations
|
163
164
|
return "" unless locations
|
164
165
|
|
165
|
-
|
166
|
-
return "" unless
|
166
|
+
required_location = locations.find { |loc| REQUIRED_FROM_LABELS.include?(loc.label) }
|
167
|
+
return "" unless required_location
|
167
168
|
|
168
|
-
|
169
|
+
required_location.absolute_path || ""
|
169
170
|
end
|
170
171
|
|
171
|
-
sig { params(
|
172
|
-
def
|
173
|
-
|
174
|
-
|
172
|
+
sig { params(singleton_class: Module).returns(T.nilable(Module)) }
|
173
|
+
def attached_class_of(singleton_class)
|
174
|
+
# https://stackoverflow.com/a/36622320/98634
|
175
|
+
result = ObjectSpace.each_object(singleton_class).find do |klass|
|
176
|
+
singleton_class_of(T.cast(klass, Module)) == singleton_class
|
177
|
+
end
|
175
178
|
|
176
|
-
|
177
|
-
def constant_from_singleton_class(constant)
|
178
|
-
constant_name = constant_name_from_singleton_class(constant)
|
179
|
-
constantize(constant_name) if constant_name
|
179
|
+
T.cast(result, Module)
|
180
180
|
end
|
181
181
|
end
|
182
182
|
end
|
@@ -17,54 +17,36 @@ module Tapioca
|
|
17
17
|
const :path, String
|
18
18
|
end
|
19
19
|
|
20
|
-
@class_files = {}
|
20
|
+
@class_files = {}
|
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
|
-
|
24
|
+
unless tp.self.singleton_class?
|
25
|
+
key = name_of(tp.self)
|
26
|
+
file = tp.path
|
27
|
+
lineno = tp.lineno
|
28
|
+
|
29
|
+
if file == "(eval)"
|
30
|
+
caller_location = T.must(caller_locations)
|
31
|
+
.drop_while { |loc| loc.path == "(eval)" }
|
32
|
+
.first
|
33
|
+
|
34
|
+
file = caller_location&.path
|
35
|
+
lineno = caller_location&.lineno
|
36
|
+
end
|
37
|
+
|
38
|
+
@class_files[key] ||= Set.new
|
39
|
+
@class_files[key] << ConstantLocation.new(path: T.must(file), lineno: T.must(lineno))
|
38
40
|
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)
|
57
41
|
end
|
58
42
|
|
59
43
|
# Returns the files in which this class or module was opened. Doesn't know
|
60
44
|
# about situations where the class was opened prior to +require+ing,
|
61
45
|
# or where metaprogramming was used via +eval+, etc.
|
62
46
|
def self.files_for(klass)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
def self.locations_for(klass)
|
67
|
-
@class_files.fetch(klass, Set.new)
|
47
|
+
name = String === klass ? klass : name_of(klass)
|
48
|
+
files = @class_files.fetch(name, [])
|
49
|
+
files.map(&:path).to_set
|
68
50
|
end
|
69
51
|
end
|
70
52
|
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.required_from_location
|
46
46
|
|
47
47
|
constants = constants_with_mixin(mixin)
|
48
48
|
constants.fetch(mixin_type).store(constant, location)
|
@@ -103,7 +103,7 @@ class Module
|
|
103
103
|
# this mixin can be found whether searching for an include/prepend on the singleton class
|
104
104
|
# or an extend on the attached class.
|
105
105
|
def register_extend_on_attached_class(constant)
|
106
|
-
attached_class = Tapioca::Runtime::Reflection.
|
106
|
+
attached_class = Tapioca::Runtime::Reflection.attached_class_of(constant)
|
107
107
|
|
108
108
|
Tapioca::Runtime::Trackers::Mixin.register(
|
109
109
|
T.cast(attached_class, Module),
|
@@ -1,28 +1,18 @@
|
|
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
|
+
|
4
8
|
module T
|
5
9
|
module Types
|
6
10
|
class Simple
|
7
11
|
module NamePatch
|
8
|
-
NAME_METHOD = T.let(Module.instance_method(:name), UnboundMethod)
|
9
|
-
|
10
12
|
def name
|
11
13
|
# Sorbet memoizes this method into the `@name` instance variable but
|
12
14
|
# doing so means that types get memoized before this patch is applied
|
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
|
15
|
+
::Tapioca::Runtime::Reflection.qualified_name_of(@raw_type)
|
26
16
|
end
|
27
17
|
end
|
28
18
|
|
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.3
|
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-
|
14
|
+
date: 2022-08-19 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -224,7 +224,6 @@ 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
|
228
227
|
- lib/tapioca/gem/listeners/subconstants.rb
|
229
228
|
- lib/tapioca/gem/listeners/yard_doc.rb
|
230
229
|
- lib/tapioca/gem/pipeline.rb
|
@@ -256,7 +255,6 @@ files:
|
|
256
255
|
- lib/tapioca/sorbet_ext/fixed_hash_patch.rb
|
257
256
|
- lib/tapioca/sorbet_ext/generic_name_patch.rb
|
258
257
|
- lib/tapioca/sorbet_ext/name_patch.rb
|
259
|
-
- lib/tapioca/sorbet_ext/proc_bind_patch.rb
|
260
258
|
- lib/tapioca/static/requires_compiler.rb
|
261
259
|
- lib/tapioca/static/symbol_loader.rb
|
262
260
|
- lib/tapioca/static/symbol_table_parser.rb
|
@@ -1,67 +0,0 @@
|
|
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
|
@@ -1,40 +0,0 @@
|
|
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
|