tapioca 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|