tapioca 0.8.3 → 0.9.2
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/Gemfile +5 -2
- data/README.md +188 -36
- data/lib/tapioca/cli.rb +130 -66
- data/lib/tapioca/commands/annotations.rb +167 -34
- data/lib/tapioca/commands/check_shims.rb +101 -0
- data/lib/tapioca/commands/{init.rb → configure.rb} +1 -1
- data/lib/tapioca/commands/dsl.rb +1 -1
- data/lib/tapioca/commands/gem.rb +15 -10
- data/lib/tapioca/commands.rb +2 -1
- data/lib/tapioca/dsl/compiler.rb +1 -13
- data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +17 -0
- data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +5 -4
- data/lib/tapioca/dsl/compilers/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/compilers/protobuf.rb +6 -0
- data/lib/tapioca/dsl/compilers.rb +0 -4
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +21 -3
- data/lib/tapioca/dsl/pipeline.rb +0 -2
- data/lib/tapioca/dsl.rb +8 -0
- data/lib/tapioca/executor.rb +0 -3
- data/lib/tapioca/gem/events.rb +22 -3
- data/lib/tapioca/gem/listeners/base.rb +11 -0
- data/lib/tapioca/gem/listeners/dynamic_mixins.rb +5 -0
- data/lib/tapioca/gem/listeners/foreign_constants.rb +65 -0
- data/lib/tapioca/gem/listeners/methods.rb +7 -18
- data/lib/tapioca/gem/listeners/mixins.rb +31 -10
- data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_helpers.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_props.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_signatures.rb +6 -1
- data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +5 -0
- data/lib/tapioca/gem/listeners/source_location.rb +67 -0
- data/lib/tapioca/gem/listeners/subconstants.rb +5 -0
- data/lib/tapioca/gem/listeners/yard_doc.rb +5 -0
- data/lib/tapioca/gem/listeners.rb +2 -0
- data/lib/tapioca/gem/pipeline.rb +64 -19
- data/lib/tapioca/gem.rb +6 -0
- data/lib/tapioca/gemfile.rb +7 -6
- data/lib/tapioca/helpers/cli_helper.rb +8 -2
- data/lib/tapioca/helpers/config_helper.rb +0 -2
- data/lib/tapioca/helpers/env_helper.rb +16 -0
- data/lib/tapioca/helpers/rbi_files_helper.rb +255 -0
- data/lib/tapioca/helpers/rbi_helper.rb +98 -94
- data/lib/tapioca/helpers/sorbet_helper.rb +2 -3
- data/lib/tapioca/helpers/test/content.rb +0 -2
- data/lib/tapioca/helpers/test/template.rb +0 -2
- data/lib/tapioca/internal.rb +36 -12
- data/lib/tapioca/rbi_ext/model.rb +2 -15
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +18 -16
- data/lib/tapioca/runtime/reflection.rb +26 -0
- data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -16
- data/lib/tapioca/runtime/trackers/mixin.rb +49 -14
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +1 -4
- data/lib/tapioca/sorbet_ext/name_patch.rb +15 -5
- data/lib/tapioca/sorbet_ext/proc_bind_patch.rb +40 -0
- data/lib/tapioca/static/requires_compiler.rb +0 -2
- data/lib/tapioca/static/symbol_loader.rb +26 -30
- data/lib/tapioca/static/symbol_table_parser.rb +0 -3
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +3 -0
- metadata +24 -7
- data/lib/tapioca/dsl/helpers/param_helper.rb +0 -55
- data/lib/tapioca/helpers/shims_helper.rb +0 -115
- data/lib/tapioca/helpers/signatures_helper.rb +0 -17
- data/lib/tapioca/helpers/type_variable_helper.rb +0 -43
@@ -18,6 +18,11 @@ module Tapioca
|
|
18
18
|
event.node << RBI::RequiresAncestor.new(ancestor.to_s)
|
19
19
|
end
|
20
20
|
end
|
21
|
+
|
22
|
+
sig { override.params(event: NodeAdded).returns(T::Boolean) }
|
23
|
+
def ignore?(event)
|
24
|
+
event.is_a?(Tapioca::Gem::ForeignScopeNodeAdded)
|
25
|
+
end
|
21
26
|
end
|
22
27
|
end
|
23
28
|
end
|
@@ -8,7 +8,7 @@ module Tapioca
|
|
8
8
|
extend T::Sig
|
9
9
|
|
10
10
|
include Runtime::Reflection
|
11
|
-
include
|
11
|
+
include RBIHelper
|
12
12
|
|
13
13
|
TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
|
14
14
|
|
@@ -73,6 +73,11 @@ module Tapioca
|
|
73
73
|
|
74
74
|
final_methods.include?(signature.method_name)
|
75
75
|
end
|
76
|
+
|
77
|
+
sig { override.params(event: NodeAdded).returns(T::Boolean) }
|
78
|
+
def ignore?(event)
|
79
|
+
event.is_a?(Tapioca::Gem::ForeignScopeNodeAdded)
|
80
|
+
end
|
76
81
|
end
|
77
82
|
end
|
78
83
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Gem
|
6
|
+
module Listeners
|
7
|
+
class SourceLocation < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
sig { override.params(event: ConstNodeAdded).void }
|
13
|
+
def on_const(event)
|
14
|
+
file, line = Object.const_source_location(event.symbol)
|
15
|
+
add_source_location_comment(event.node, file, line)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { override.params(event: ScopeNodeAdded).void }
|
19
|
+
def on_scope(event)
|
20
|
+
# Instead of using `const_source_location`, which always reports the first place where a constant is defined,
|
21
|
+
# we filter the locations tracked by ConstantDefinition. This allows us to provide the correct location for
|
22
|
+
# constants that are defined by multiple gems.
|
23
|
+
locations = Runtime::Trackers::ConstantDefinition.locations_for(event.constant)
|
24
|
+
location = locations.find do |loc|
|
25
|
+
Pathname.new(loc.path).realpath.to_s.include?(@pipeline.gem.full_gem_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
# The location may still be nil in some situations, like constant aliases (e.g.: MyAlias = OtherConst). These
|
29
|
+
# are quite difficult to attribute a correct location, given that the source location points to the original
|
30
|
+
# constants and not the alias
|
31
|
+
add_source_location_comment(event.node, location.path, location.lineno) unless location.nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { override.params(event: MethodNodeAdded).void }
|
35
|
+
def on_method(event)
|
36
|
+
file, line = event.method.source_location
|
37
|
+
add_source_location_comment(event.node, file, line)
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { params(node: RBI::NodeWithComments, file: T.nilable(String), line: T.nilable(Integer)).void }
|
41
|
+
def add_source_location_comment(node, file, line)
|
42
|
+
return unless file && line
|
43
|
+
|
44
|
+
gem = @pipeline.gem
|
45
|
+
path = Pathname.new(file)
|
46
|
+
return unless File.exist?(path)
|
47
|
+
|
48
|
+
# On native extensions, the source location may point to a shared object (.so, .bundle) file, which we cannot
|
49
|
+
# use for jump to definition. Only add source comments on Ruby files
|
50
|
+
return unless path.extname == ".rb"
|
51
|
+
|
52
|
+
path = if path.realpath.to_s.start_with?(gem.full_gem_path)
|
53
|
+
"#{gem.name}-#{gem.version}/#{path.realpath.relative_path_from(gem.full_gem_path)}"
|
54
|
+
else
|
55
|
+
path.sub("#{Bundler.bundle_path}/gems/", "").to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
# Strip out the RUBY_ROOT prefix, which is different for each user
|
59
|
+
path = path.sub(RbConfig::CONFIG["rubylibdir"], "RUBY_ROOT")
|
60
|
+
|
61
|
+
node.comments << RBI::Comment.new("") if node.comments.any?
|
62
|
+
node.comments << RBI::Comment.new("source://#{path}:#{line}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -13,4 +13,6 @@ require "tapioca/gem/listeners/sorbet_required_ancestors"
|
|
13
13
|
require "tapioca/gem/listeners/sorbet_signatures"
|
14
14
|
require "tapioca/gem/listeners/sorbet_type_variables"
|
15
15
|
require "tapioca/gem/listeners/subconstants"
|
16
|
+
require "tapioca/gem/listeners/foreign_constants"
|
16
17
|
require "tapioca/gem/listeners/yard_doc"
|
18
|
+
require "tapioca/gem/listeners/source_location"
|
data/lib/tapioca/gem/pipeline.rb
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "pathname"
|
5
|
-
|
6
4
|
module Tapioca
|
7
5
|
module Gem
|
8
6
|
class Pipeline
|
9
7
|
extend T::Sig
|
10
8
|
include Runtime::Reflection
|
11
|
-
include
|
9
|
+
include RBIHelper
|
12
10
|
|
13
11
|
IGNORED_SYMBOLS = T.let(["YAML", "MiniTest", "Mutex"], T::Array[String])
|
14
12
|
|
15
13
|
sig { returns(Gemfile::GemSpec) }
|
16
14
|
attr_reader :gem
|
17
15
|
|
18
|
-
sig { params(gem: Gemfile::GemSpec, include_doc: T::Boolean).void }
|
19
|
-
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)
|
20
18
|
@root = T.let(RBI::Tree.new, RBI::Tree)
|
21
19
|
@gem = gem
|
22
20
|
@seen = T.let(Set.new, T::Set[String])
|
@@ -25,8 +23,8 @@ module Tapioca
|
|
25
23
|
@events = T.let([], T::Array[Gem::Event])
|
26
24
|
|
27
25
|
@payload_symbols = T.let(Static::SymbolLoader.payload_symbols, T::Set[String])
|
28
|
-
@bootstrap_symbols = T.let(
|
29
|
-
|
26
|
+
@bootstrap_symbols = T.let(load_bootstrap_symbols(@gem), T::Set[String])
|
27
|
+
|
30
28
|
@bootstrap_symbols.each { |symbol| push_symbol(symbol) }
|
31
29
|
|
32
30
|
@node_listeners = T.let([], T::Array[Gem::Listeners::Base])
|
@@ -41,6 +39,8 @@ module Tapioca
|
|
41
39
|
@node_listeners << Gem::Listeners::SorbetSignatures.new(self)
|
42
40
|
@node_listeners << Gem::Listeners::Subconstants.new(self)
|
43
41
|
@node_listeners << Gem::Listeners::YardDoc.new(self) if include_doc
|
42
|
+
@node_listeners << Gem::Listeners::ForeignConstants.new(self)
|
43
|
+
@node_listeners << Gem::Listeners::SourceLocation.new(self) if include_loc
|
44
44
|
@node_listeners << Gem::Listeners::RemoveEmptyPayloadScopes.new(self)
|
45
45
|
end
|
46
46
|
|
@@ -60,27 +60,42 @@ module Tapioca
|
|
60
60
|
@events << Gem::ConstantFound.new(symbol, constant)
|
61
61
|
end
|
62
62
|
|
63
|
+
sig { params(symbol: String, constant: Module).void.checked(:never) }
|
64
|
+
def push_foreign_constant(symbol, constant)
|
65
|
+
@events << Gem::ForeignConstantFound.new(symbol, constant)
|
66
|
+
end
|
67
|
+
|
63
68
|
sig { params(symbol: String, constant: Module, node: RBI::Const).void.checked(:never) }
|
64
69
|
def push_const(symbol, constant, node)
|
65
70
|
@events << Gem::ConstNodeAdded.new(symbol, constant, node)
|
66
71
|
end
|
67
72
|
|
68
|
-
sig
|
73
|
+
sig do
|
74
|
+
params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never)
|
75
|
+
end
|
69
76
|
def push_scope(symbol, constant, node)
|
70
77
|
@events << Gem::ScopeNodeAdded.new(symbol, constant, node)
|
71
78
|
end
|
72
79
|
|
80
|
+
sig do
|
81
|
+
params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never)
|
82
|
+
end
|
83
|
+
def push_foreign_scope(symbol, constant, node)
|
84
|
+
@events << Gem::ForeignScopeNodeAdded.new(symbol, constant, node)
|
85
|
+
end
|
86
|
+
|
73
87
|
sig do
|
74
88
|
params(
|
75
89
|
symbol: String,
|
76
90
|
constant: Module,
|
91
|
+
method: UnboundMethod,
|
77
92
|
node: RBI::Method,
|
78
93
|
signature: T.untyped,
|
79
94
|
parameters: T::Array[[Symbol, String]]
|
80
95
|
).void.checked(:never)
|
81
96
|
end
|
82
|
-
def push_method(symbol, constant, node, signature, parameters)
|
83
|
-
@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)
|
84
99
|
end
|
85
100
|
|
86
101
|
sig { params(symbol_name: String).returns(T::Boolean) }
|
@@ -114,6 +129,14 @@ module Tapioca
|
|
114
129
|
|
115
130
|
private
|
116
131
|
|
132
|
+
sig { params(gem: Gemfile::GemSpec).returns(T::Set[String]) }
|
133
|
+
def load_bootstrap_symbols(gem)
|
134
|
+
engine_symbols = Static::SymbolLoader.engine_symbols(gem)
|
135
|
+
gem_symbols = Static::SymbolLoader.gem_symbols(gem)
|
136
|
+
|
137
|
+
gem_symbols.union(engine_symbols)
|
138
|
+
end
|
139
|
+
|
117
140
|
sig { returns(Gem::Event) }
|
118
141
|
def next_event
|
119
142
|
T.must(@events.shift)
|
@@ -150,13 +173,14 @@ module Tapioca
|
|
150
173
|
return if name.start_with?("#<")
|
151
174
|
return if name.downcase == name
|
152
175
|
return if alias_namespaced?(name)
|
153
|
-
return if seen?(name)
|
154
176
|
|
155
|
-
|
156
|
-
return if T::Enum === constant # T::Enum instances are defined via `compile_enums`
|
177
|
+
return if T::Enum === event.constant # T::Enum instances are defined via `compile_enums`
|
157
178
|
|
158
|
-
|
159
|
-
|
179
|
+
if event.is_a?(Gem::ForeignConstantFound)
|
180
|
+
compile_foreign_constant(name, event.constant)
|
181
|
+
else
|
182
|
+
compile_constant(name, event.constant)
|
183
|
+
end
|
160
184
|
end
|
161
185
|
|
162
186
|
sig { params(event: Gem::NodeAdded).void }
|
@@ -166,6 +190,11 @@ module Tapioca
|
|
166
190
|
|
167
191
|
# Compile
|
168
192
|
|
193
|
+
sig { params(symbol: String, constant: Module).void }
|
194
|
+
def compile_foreign_constant(symbol, constant)
|
195
|
+
compile_module(symbol, constant, foreign_constant: true)
|
196
|
+
end
|
197
|
+
|
169
198
|
sig { params(symbol: String, constant: BasicObject).void.checked(:never) }
|
170
199
|
def compile_constant(symbol, constant)
|
171
200
|
case constant
|
@@ -182,6 +211,10 @@ module Tapioca
|
|
182
211
|
|
183
212
|
sig { params(name: String, constant: Module).void }
|
184
213
|
def compile_alias(name, constant)
|
214
|
+
return if seen?(name)
|
215
|
+
|
216
|
+
mark_seen(name)
|
217
|
+
|
185
218
|
return if symbol_in_payload?(name)
|
186
219
|
|
187
220
|
target = name_of(constant)
|
@@ -199,6 +232,10 @@ module Tapioca
|
|
199
232
|
|
200
233
|
sig { params(name: String, value: BasicObject).void.checked(:never) }
|
201
234
|
def compile_object(name, value)
|
235
|
+
return if seen?(name)
|
236
|
+
|
237
|
+
mark_seen(name)
|
238
|
+
|
202
239
|
return if symbol_in_payload?(name)
|
203
240
|
|
204
241
|
klass = class_of(value)
|
@@ -228,10 +265,13 @@ module Tapioca
|
|
228
265
|
@root << node
|
229
266
|
end
|
230
267
|
|
231
|
-
sig { params(name: String, constant: Module).void }
|
232
|
-
def compile_module(name, constant)
|
233
|
-
return unless defined_in_gem?(constant, strict: false)
|
268
|
+
sig { params(name: String, constant: Module, foreign_constant: T::Boolean).void }
|
269
|
+
def compile_module(name, constant, foreign_constant: false)
|
270
|
+
return unless defined_in_gem?(constant, strict: false) || foreign_constant
|
234
271
|
return if Tapioca::TypeVariableModule === constant
|
272
|
+
return if seen?(name)
|
273
|
+
|
274
|
+
mark_seen(name)
|
235
275
|
|
236
276
|
scope =
|
237
277
|
if constant.is_a?(Class)
|
@@ -241,7 +281,12 @@ module Tapioca
|
|
241
281
|
RBI::Module.new(name)
|
242
282
|
end
|
243
283
|
|
244
|
-
|
284
|
+
if foreign_constant
|
285
|
+
push_foreign_scope(name, constant, scope)
|
286
|
+
else
|
287
|
+
push_scope(name, constant, scope)
|
288
|
+
end
|
289
|
+
|
245
290
|
@root << scope
|
246
291
|
end
|
247
292
|
|
data/lib/tapioca/gem.rb
ADDED
data/lib/tapioca/gemfile.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "bundler"
|
5
|
-
require "logger"
|
6
|
-
require "yard-sorbet"
|
7
|
-
|
8
4
|
module Tapioca
|
9
5
|
class Gemfile
|
10
6
|
extend(T::Sig)
|
@@ -140,8 +136,13 @@ module Tapioca
|
|
140
136
|
extend(T::Sig)
|
141
137
|
include GemHelper
|
142
138
|
|
143
|
-
IGNORED_GEMS = T.let(
|
144
|
-
|
139
|
+
IGNORED_GEMS = T.let(
|
140
|
+
[
|
141
|
+
"sorbet", "sorbet-static", "sorbet-runtime", "sorbet-static-and-runtime",
|
142
|
+
"debug", "fakefs",
|
143
|
+
].freeze,
|
144
|
+
T::Array[String]
|
145
|
+
)
|
145
146
|
|
146
147
|
sig { returns(String) }
|
147
148
|
attr_reader :full_gem_path, :version
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "thor"
|
5
|
-
|
6
4
|
module Tapioca
|
7
5
|
module CliHelper
|
8
6
|
extend T::Sig
|
@@ -30,5 +28,13 @@ module Tapioca
|
|
30
28
|
rbi_formatter.max_line_length = options[:rbi_max_line_length]
|
31
29
|
rbi_formatter
|
32
30
|
end
|
31
|
+
|
32
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(T.nilable(String)) }
|
33
|
+
def netrc_file(options)
|
34
|
+
return nil if options[:auth]
|
35
|
+
return nil unless options[:netrc]
|
36
|
+
|
37
|
+
options[:netrc_file] || ENV["TAPIOCA_NETRC_FILE"] || File.join(ENV["HOME"].to_s, ".netrc")
|
38
|
+
end
|
33
39
|
end
|
34
40
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module EnvHelper
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
requires_ancestor { Thor }
|
10
|
+
|
11
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).void }
|
12
|
+
def set_environment(options) # rubocop:disable Naming/AccessorMethodName
|
13
|
+
ENV["RAILS_ENV"] = ENV["RACK_ENV"] = options[:environment]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module RBIFilesHelper
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
requires_ancestor { Thor::Shell }
|
10
|
+
requires_ancestor { SorbetHelper }
|
11
|
+
|
12
|
+
sig { params(index: RBI::Index, kind: String, file: String).void }
|
13
|
+
def index_rbi(index, kind, file)
|
14
|
+
return unless File.exist?(file)
|
15
|
+
|
16
|
+
say("Loading #{kind} RBIs from #{file}... ")
|
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)")
|
22
|
+
end
|
23
|
+
|
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:)
|
26
|
+
return unless Dir.exist?(dir) && !Dir.empty?(dir)
|
27
|
+
|
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
|
+
end
|
40
|
+
|
41
|
+
sig do
|
42
|
+
params(
|
43
|
+
index: RBI::Index,
|
44
|
+
shim_rbi_dir: String,
|
45
|
+
todo_rbi_file: String
|
46
|
+
).returns(T::Hash[String, T::Array[RBI::Node]])
|
47
|
+
end
|
48
|
+
def duplicated_nodes_from_index(index, shim_rbi_dir:, todo_rbi_file:)
|
49
|
+
duplicates = {}
|
50
|
+
say("Looking for duplicates... ")
|
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)
|
55
|
+
|
56
|
+
duplicates[key] = nodes
|
57
|
+
end
|
58
|
+
end
|
59
|
+
say(" Done ", :green)
|
60
|
+
say("(#{time.round(2)}s)")
|
61
|
+
duplicates
|
62
|
+
end
|
63
|
+
|
64
|
+
sig { params(loc: RBI::Loc, path_prefix: T.nilable(String)).returns(String) }
|
65
|
+
def location_to_payload_url(loc, path_prefix:)
|
66
|
+
return loc.to_s unless path_prefix
|
67
|
+
|
68
|
+
url = loc.file || ""
|
69
|
+
return loc.to_s unless url.start_with?(path_prefix)
|
70
|
+
|
71
|
+
url = url.sub(path_prefix, SorbetHelper::SORBET_PAYLOAD_URL)
|
72
|
+
url = "#{url}#L#{loc.begin_line}"
|
73
|
+
url
|
74
|
+
end
|
75
|
+
|
76
|
+
sig do
|
77
|
+
params(
|
78
|
+
command: String,
|
79
|
+
gem_dir: String,
|
80
|
+
dsl_dir: String,
|
81
|
+
auto_strictness: T::Boolean,
|
82
|
+
gems: T::Array[Gemfile::GemSpec],
|
83
|
+
compilers: T::Enumerable[Class]
|
84
|
+
).void
|
85
|
+
end
|
86
|
+
def validate_rbi_files(command:, gem_dir:, dsl_dir:, auto_strictness:, gems: [], compilers: [])
|
87
|
+
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
88
|
+
|
89
|
+
say("Checking generated RBI files... ")
|
90
|
+
res = sorbet(
|
91
|
+
"--no-config",
|
92
|
+
"--error-url-base=#{error_url_base}",
|
93
|
+
"--stop-after namer",
|
94
|
+
dsl_dir,
|
95
|
+
gem_dir
|
96
|
+
)
|
97
|
+
say(" Done", :green)
|
98
|
+
|
99
|
+
errors = Spoom::Sorbet::Errors::Parser.parse_string(res.err)
|
100
|
+
|
101
|
+
if errors.empty?
|
102
|
+
say(" No errors found\n\n", [:green, :bold])
|
103
|
+
return
|
104
|
+
end
|
105
|
+
|
106
|
+
parse_errors = errors.select { |error| error.code < 4000 }
|
107
|
+
|
108
|
+
if parse_errors.any?
|
109
|
+
say_error(<<~ERR, :red)
|
110
|
+
|
111
|
+
##### INTERNAL ERROR #####
|
112
|
+
|
113
|
+
There are parse errors in the generated RBI files.
|
114
|
+
|
115
|
+
This seems related to a bug in Tapioca.
|
116
|
+
Please open an issue at https://github.com/Shopify/tapioca/issues/new with the following information:
|
117
|
+
|
118
|
+
Tapioca v#{Tapioca::VERSION}
|
119
|
+
|
120
|
+
Command:
|
121
|
+
#{command}
|
122
|
+
|
123
|
+
ERR
|
124
|
+
|
125
|
+
say_error(<<~ERR, :red) if gems.any?
|
126
|
+
Gems:
|
127
|
+
#{gems.map { |gem| " #{gem.name} (#{gem.version})" }.join("\n")}
|
128
|
+
|
129
|
+
ERR
|
130
|
+
|
131
|
+
say_error(<<~ERR, :red) if compilers.any?
|
132
|
+
Compilers:
|
133
|
+
#{compilers.map { |compiler| " #{compiler.name}" }.join("\n")}
|
134
|
+
|
135
|
+
ERR
|
136
|
+
|
137
|
+
say_error(<<~ERR, :red)
|
138
|
+
Errors:
|
139
|
+
#{parse_errors.map { |error| " #{error}" }.join("\n")}
|
140
|
+
|
141
|
+
##########################
|
142
|
+
|
143
|
+
ERR
|
144
|
+
end
|
145
|
+
|
146
|
+
if auto_strictness
|
147
|
+
redef_errors = errors.select { |error| error.code == 4010 }
|
148
|
+
update_gem_rbis_strictnesses(redef_errors, gem_dir)
|
149
|
+
end
|
150
|
+
|
151
|
+
Kernel.exit(1) if parse_errors.any?
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
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)
|
166
|
+
end
|
167
|
+
|
168
|
+
index.visit_all(trees)
|
169
|
+
end
|
170
|
+
|
171
|
+
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String, todo_rbi_file: String).returns(T::Boolean) }
|
172
|
+
def shims_or_todos_have_duplicates?(nodes, shim_rbi_dir:, todo_rbi_file:)
|
173
|
+
return false if nodes.size == 1
|
174
|
+
|
175
|
+
shims_or_todos = extract_shims_and_todos(nodes, shim_rbi_dir: shim_rbi_dir, todo_rbi_file: todo_rbi_file)
|
176
|
+
return false if shims_or_todos.empty?
|
177
|
+
|
178
|
+
shims_or_todos_empty_scopes = extract_empty_scopes(shims_or_todos)
|
179
|
+
return true unless shims_or_todos_empty_scopes.empty?
|
180
|
+
|
181
|
+
props = extract_methods_and_attrs(shims_or_todos)
|
182
|
+
return false if props.empty?
|
183
|
+
|
184
|
+
shims_or_todos_with_sigs = extract_nodes_with_sigs(props)
|
185
|
+
shims_or_todos_with_sigs.each do |shim_or_todo|
|
186
|
+
shims_or_todos_sigs = shim_or_todo.sigs
|
187
|
+
|
188
|
+
extract_methods_and_attrs(nodes).each do |node|
|
189
|
+
next if node == shim_or_todo
|
190
|
+
return true if shims_or_todos_sigs.all? { |sig| node.sigs.include?(sig) }
|
191
|
+
end
|
192
|
+
|
193
|
+
return false
|
194
|
+
end
|
195
|
+
|
196
|
+
true
|
197
|
+
end
|
198
|
+
|
199
|
+
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String, todo_rbi_file: String).returns(T::Array[RBI::Node]) }
|
200
|
+
def extract_shims_and_todos(nodes, shim_rbi_dir:, todo_rbi_file:)
|
201
|
+
nodes.select do |node|
|
202
|
+
node.loc&.file&.start_with?(shim_rbi_dir) || node.loc&.file == todo_rbi_file
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
sig { params(nodes: T::Array[RBI::Node]).returns(T::Array[RBI::Scope]) }
|
207
|
+
def extract_empty_scopes(nodes)
|
208
|
+
T.cast(nodes.select { |node| node.is_a?(RBI::Scope) && node.empty? }, T::Array[RBI::Scope])
|
209
|
+
end
|
210
|
+
|
211
|
+
sig { params(nodes: T::Array[RBI::Node]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
|
212
|
+
def extract_methods_and_attrs(nodes)
|
213
|
+
T.cast(nodes.select do |node|
|
214
|
+
node.is_a?(RBI::Method) || node.is_a?(RBI::Attr)
|
215
|
+
end, T::Array[T.any(RBI::Method, RBI::Attr)])
|
216
|
+
end
|
217
|
+
|
218
|
+
sig { params(nodes: T::Array[T.any(RBI::Method, RBI::Attr)]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
|
219
|
+
def extract_nodes_with_sigs(nodes)
|
220
|
+
nodes.reject { |node| node.sigs.empty? }
|
221
|
+
end
|
222
|
+
|
223
|
+
sig { params(errors: T::Array[Spoom::Sorbet::Errors::Error], gem_dir: String).void }
|
224
|
+
def update_gem_rbis_strictnesses(errors, gem_dir)
|
225
|
+
files = []
|
226
|
+
|
227
|
+
errors.each do |error|
|
228
|
+
# Collect the file with error
|
229
|
+
files << error.file
|
230
|
+
error.more.each do |line|
|
231
|
+
# Also collect the conflicting definition file paths
|
232
|
+
next unless line.include?("Previous definition")
|
233
|
+
|
234
|
+
files << line.split(":").first&.strip
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
files
|
239
|
+
.uniq
|
240
|
+
.sort
|
241
|
+
.select { |file| file.start_with?(gem_dir) }
|
242
|
+
.each do |file|
|
243
|
+
Spoom::Sorbet::Sigils.change_sigil_in_file(file, "false")
|
244
|
+
say("\n Changed strictness of #{file} to `typed: false` (conflicting with DSL files)", [:yellow, :bold])
|
245
|
+
end
|
246
|
+
|
247
|
+
say("\n")
|
248
|
+
end
|
249
|
+
|
250
|
+
sig { params(path: String).returns(String) }
|
251
|
+
def gem_name_from_rbi_path(path)
|
252
|
+
T.must(File.basename(path, ".rbi").split("@").first)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|