tapioca 0.8.3 → 0.9.0
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 +182 -36
- data/lib/tapioca/cli.rb +122 -65
- data/lib/tapioca/commands/annotations.rb +165 -33
- data/lib/tapioca/commands/check_shims.rb +91 -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 +5 -5
- 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/protobuf.rb +6 -0
- data/lib/tapioca/dsl/compilers.rb +0 -4
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +2 -2
- 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 +16 -2
- 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 +6 -17
- 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/subconstants.rb +5 -0
- data/lib/tapioca/gem/listeners/yard_doc.rb +5 -0
- data/lib/tapioca/gem/listeners.rb +1 -0
- data/lib/tapioca/gem/pipeline.rb +58 -15
- 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 +252 -0
- data/lib/tapioca/helpers/rbi_helper.rb +74 -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 +34 -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 +27 -0
- data/lib/tapioca/runtime/trackers/constant_definition.rb +17 -7
- data/lib/tapioca/runtime/trackers/mixin.rb +49 -14
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +1 -4
- 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 +22 -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
data/lib/tapioca/gem/pipeline.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
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
|
|
@@ -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,7 @@ 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)
|
44
43
|
@node_listeners << Gem::Listeners::RemoveEmptyPayloadScopes.new(self)
|
45
44
|
end
|
46
45
|
|
@@ -60,16 +59,30 @@ module Tapioca
|
|
60
59
|
@events << Gem::ConstantFound.new(symbol, constant)
|
61
60
|
end
|
62
61
|
|
62
|
+
sig { params(symbol: String, constant: Module).void.checked(:never) }
|
63
|
+
def push_foreign_constant(symbol, constant)
|
64
|
+
@events << Gem::ForeignConstantFound.new(symbol, constant)
|
65
|
+
end
|
66
|
+
|
63
67
|
sig { params(symbol: String, constant: Module, node: RBI::Const).void.checked(:never) }
|
64
68
|
def push_const(symbol, constant, node)
|
65
69
|
@events << Gem::ConstNodeAdded.new(symbol, constant, node)
|
66
70
|
end
|
67
71
|
|
68
|
-
sig
|
72
|
+
sig do
|
73
|
+
params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never)
|
74
|
+
end
|
69
75
|
def push_scope(symbol, constant, node)
|
70
76
|
@events << Gem::ScopeNodeAdded.new(symbol, constant, node)
|
71
77
|
end
|
72
78
|
|
79
|
+
sig do
|
80
|
+
params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never)
|
81
|
+
end
|
82
|
+
def push_foreign_scope(symbol, constant, node)
|
83
|
+
@events << Gem::ForeignScopeNodeAdded.new(symbol, constant, node)
|
84
|
+
end
|
85
|
+
|
73
86
|
sig do
|
74
87
|
params(
|
75
88
|
symbol: String,
|
@@ -114,6 +127,14 @@ module Tapioca
|
|
114
127
|
|
115
128
|
private
|
116
129
|
|
130
|
+
sig { params(gem: Gemfile::GemSpec).returns(T::Set[String]) }
|
131
|
+
def load_bootstrap_symbols(gem)
|
132
|
+
engine_symbols = Static::SymbolLoader.engine_symbols(gem)
|
133
|
+
gem_symbols = Static::SymbolLoader.gem_symbols(gem)
|
134
|
+
|
135
|
+
gem_symbols.union(engine_symbols)
|
136
|
+
end
|
137
|
+
|
117
138
|
sig { returns(Gem::Event) }
|
118
139
|
def next_event
|
119
140
|
T.must(@events.shift)
|
@@ -150,13 +171,14 @@ module Tapioca
|
|
150
171
|
return if name.start_with?("#<")
|
151
172
|
return if name.downcase == name
|
152
173
|
return if alias_namespaced?(name)
|
153
|
-
return if seen?(name)
|
154
174
|
|
155
|
-
|
156
|
-
return if T::Enum === constant # T::Enum instances are defined via `compile_enums`
|
175
|
+
return if T::Enum === event.constant # T::Enum instances are defined via `compile_enums`
|
157
176
|
|
158
|
-
|
159
|
-
|
177
|
+
if event.is_a?(Gem::ForeignConstantFound)
|
178
|
+
compile_foreign_constant(name, event.constant)
|
179
|
+
else
|
180
|
+
compile_constant(name, event.constant)
|
181
|
+
end
|
160
182
|
end
|
161
183
|
|
162
184
|
sig { params(event: Gem::NodeAdded).void }
|
@@ -166,6 +188,11 @@ module Tapioca
|
|
166
188
|
|
167
189
|
# Compile
|
168
190
|
|
191
|
+
sig { params(symbol: String, constant: Module).void }
|
192
|
+
def compile_foreign_constant(symbol, constant)
|
193
|
+
compile_module(symbol, constant, foreign_constant: true)
|
194
|
+
end
|
195
|
+
|
169
196
|
sig { params(symbol: String, constant: BasicObject).void.checked(:never) }
|
170
197
|
def compile_constant(symbol, constant)
|
171
198
|
case constant
|
@@ -182,6 +209,10 @@ module Tapioca
|
|
182
209
|
|
183
210
|
sig { params(name: String, constant: Module).void }
|
184
211
|
def compile_alias(name, constant)
|
212
|
+
return if seen?(name)
|
213
|
+
|
214
|
+
mark_seen(name)
|
215
|
+
|
185
216
|
return if symbol_in_payload?(name)
|
186
217
|
|
187
218
|
target = name_of(constant)
|
@@ -199,6 +230,10 @@ module Tapioca
|
|
199
230
|
|
200
231
|
sig { params(name: String, value: BasicObject).void.checked(:never) }
|
201
232
|
def compile_object(name, value)
|
233
|
+
return if seen?(name)
|
234
|
+
|
235
|
+
mark_seen(name)
|
236
|
+
|
202
237
|
return if symbol_in_payload?(name)
|
203
238
|
|
204
239
|
klass = class_of(value)
|
@@ -228,10 +263,13 @@ module Tapioca
|
|
228
263
|
@root << node
|
229
264
|
end
|
230
265
|
|
231
|
-
sig { params(name: String, constant: Module).void }
|
232
|
-
def compile_module(name, constant)
|
233
|
-
return unless defined_in_gem?(constant, strict: false)
|
266
|
+
sig { params(name: String, constant: Module, foreign_constant: T::Boolean).void }
|
267
|
+
def compile_module(name, constant, foreign_constant: false)
|
268
|
+
return unless defined_in_gem?(constant, strict: false) || foreign_constant
|
234
269
|
return if Tapioca::TypeVariableModule === constant
|
270
|
+
return if seen?(name)
|
271
|
+
|
272
|
+
mark_seen(name)
|
235
273
|
|
236
274
|
scope =
|
237
275
|
if constant.is_a?(Class)
|
@@ -241,7 +279,12 @@ module Tapioca
|
|
241
279
|
RBI::Module.new(name)
|
242
280
|
end
|
243
281
|
|
244
|
-
|
282
|
+
if foreign_constant
|
283
|
+
push_foreign_scope(name, constant, scope)
|
284
|
+
else
|
285
|
+
push_scope(name, constant, scope)
|
286
|
+
end
|
287
|
+
|
245
288
|
@root << scope
|
246
289
|
end
|
247
290
|
|
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,252 @@
|
|
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, dir: String).void }
|
13
|
+
def index_payload(index, dir)
|
14
|
+
return unless Dir.exist?(dir)
|
15
|
+
|
16
|
+
say("Loading Sorbet payload... ")
|
17
|
+
files = Dir.glob("#{dir}/**/*.rbi").sort
|
18
|
+
parse_and_index_files(index, files)
|
19
|
+
say(" Done", :green)
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { params(index: RBI::Index, kind: String, file: String).void }
|
23
|
+
def index_rbi(index, kind, file)
|
24
|
+
return unless File.exist?(file)
|
25
|
+
|
26
|
+
say("Loading #{kind} RBIs from #{file}... ")
|
27
|
+
parse_and_index_file(index, file)
|
28
|
+
say(" Done", :green)
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(index: RBI::Index, kind: String, dir: String).void }
|
32
|
+
def index_rbis(index, kind, dir)
|
33
|
+
return unless Dir.exist?(dir) && !Dir.empty?(dir)
|
34
|
+
|
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
|
+
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
|
+
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)
|
54
|
+
|
55
|
+
duplicates[key] = nodes
|
56
|
+
end
|
57
|
+
say(" Done", :green)
|
58
|
+
duplicates
|
59
|
+
end
|
60
|
+
|
61
|
+
sig { params(loc: RBI::Loc, path_prefix: T.nilable(String)).returns(String) }
|
62
|
+
def location_to_payload_url(loc, path_prefix:)
|
63
|
+
return loc.to_s unless path_prefix
|
64
|
+
|
65
|
+
url = loc.file || ""
|
66
|
+
return loc.to_s unless url.start_with?(path_prefix)
|
67
|
+
|
68
|
+
url = url.sub(path_prefix, SorbetHelper::SORBET_PAYLOAD_URL)
|
69
|
+
url = "#{url}#L#{loc.begin_line}"
|
70
|
+
url
|
71
|
+
end
|
72
|
+
|
73
|
+
sig do
|
74
|
+
params(
|
75
|
+
command: String,
|
76
|
+
gem_dir: String,
|
77
|
+
dsl_dir: String,
|
78
|
+
auto_strictness: T::Boolean,
|
79
|
+
gems: T::Array[Gemfile::GemSpec],
|
80
|
+
compilers: T::Enumerable[Class]
|
81
|
+
).void
|
82
|
+
end
|
83
|
+
def validate_rbi_files(command:, gem_dir:, dsl_dir:, auto_strictness:, gems: [], compilers: [])
|
84
|
+
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
85
|
+
|
86
|
+
say("Checking generated RBI files... ")
|
87
|
+
res = sorbet(
|
88
|
+
"--no-config",
|
89
|
+
"--error-url-base=#{error_url_base}",
|
90
|
+
"--stop-after namer",
|
91
|
+
dsl_dir,
|
92
|
+
gem_dir
|
93
|
+
)
|
94
|
+
say(" Done", :green)
|
95
|
+
|
96
|
+
errors = Spoom::Sorbet::Errors::Parser.parse_string(res.err)
|
97
|
+
|
98
|
+
if errors.empty?
|
99
|
+
say(" No errors found\n\n", [:green, :bold])
|
100
|
+
return
|
101
|
+
end
|
102
|
+
|
103
|
+
parse_errors = errors.select { |error| error.code < 4000 }
|
104
|
+
|
105
|
+
if parse_errors.any?
|
106
|
+
say_error(<<~ERR, :red)
|
107
|
+
|
108
|
+
##### INTERNAL ERROR #####
|
109
|
+
|
110
|
+
There are parse errors in the generated RBI files.
|
111
|
+
|
112
|
+
This seems related to a bug in Tapioca.
|
113
|
+
Please open an issue at https://github.com/Shopify/tapioca/issues/new with the following information:
|
114
|
+
|
115
|
+
Tapioca v#{Tapioca::VERSION}
|
116
|
+
|
117
|
+
Command:
|
118
|
+
#{command}
|
119
|
+
|
120
|
+
ERR
|
121
|
+
|
122
|
+
say_error(<<~ERR, :red) if gems.any?
|
123
|
+
Gems:
|
124
|
+
#{gems.map { |gem| " #{gem.name} (#{gem.version})" }.join("\n")}
|
125
|
+
|
126
|
+
ERR
|
127
|
+
|
128
|
+
say_error(<<~ERR, :red) if compilers.any?
|
129
|
+
Compilers:
|
130
|
+
#{compilers.map { |compiler| " #{compiler.name}" }.join("\n")}
|
131
|
+
|
132
|
+
ERR
|
133
|
+
|
134
|
+
say_error(<<~ERR, :red)
|
135
|
+
Errors:
|
136
|
+
#{parse_errors.map { |error| " #{error}" }.join("\n")}
|
137
|
+
|
138
|
+
##########################
|
139
|
+
|
140
|
+
ERR
|
141
|
+
end
|
142
|
+
|
143
|
+
if auto_strictness
|
144
|
+
redef_errors = errors.select { |error| error.code == 4010 }
|
145
|
+
update_gem_rbis_strictnesses(redef_errors, gem_dir)
|
146
|
+
end
|
147
|
+
|
148
|
+
Kernel.exit(1) if parse_errors.any?
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
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)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
sig { params(index: RBI::Index, file: String).void }
|
161
|
+
def parse_and_index_file(index, file)
|
162
|
+
tree = RBI::Parser.parse_file(file)
|
163
|
+
index.visit(tree)
|
164
|
+
rescue RBI::ParseError => e
|
165
|
+
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
166
|
+
end
|
167
|
+
|
168
|
+
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String, todo_rbi_file: String).returns(T::Boolean) }
|
169
|
+
def shims_or_todos_have_duplicates?(nodes, shim_rbi_dir:, todo_rbi_file:)
|
170
|
+
return false if nodes.size == 1
|
171
|
+
|
172
|
+
shims_or_todos = extract_shims_and_todos(nodes, shim_rbi_dir: shim_rbi_dir, todo_rbi_file: todo_rbi_file)
|
173
|
+
return false if shims_or_todos.empty?
|
174
|
+
|
175
|
+
shims_or_todos_empty_scopes = extract_empty_scopes(shims_or_todos)
|
176
|
+
return true unless shims_or_todos_empty_scopes.empty?
|
177
|
+
|
178
|
+
props = extract_methods_and_attrs(shims_or_todos)
|
179
|
+
return false if props.empty?
|
180
|
+
|
181
|
+
shims_or_todos_with_sigs = extract_nodes_with_sigs(props)
|
182
|
+
shims_or_todos_with_sigs.each do |shim_or_todo|
|
183
|
+
shims_or_todos_sigs = shim_or_todo.sigs
|
184
|
+
|
185
|
+
extract_methods_and_attrs(nodes).each do |node|
|
186
|
+
next if node == shim_or_todo
|
187
|
+
return true if shims_or_todos_sigs.all? { |sig| node.sigs.include?(sig) }
|
188
|
+
end
|
189
|
+
|
190
|
+
return false
|
191
|
+
end
|
192
|
+
|
193
|
+
true
|
194
|
+
end
|
195
|
+
|
196
|
+
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String, todo_rbi_file: String).returns(T::Array[RBI::Node]) }
|
197
|
+
def extract_shims_and_todos(nodes, shim_rbi_dir:, todo_rbi_file:)
|
198
|
+
nodes.select do |node|
|
199
|
+
node.loc&.file&.start_with?(shim_rbi_dir) || node.loc&.file == todo_rbi_file
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
sig { params(nodes: T::Array[RBI::Node]).returns(T::Array[RBI::Scope]) }
|
204
|
+
def extract_empty_scopes(nodes)
|
205
|
+
T.cast(nodes.select { |node| node.is_a?(RBI::Scope) && node.empty? }, T::Array[RBI::Scope])
|
206
|
+
end
|
207
|
+
|
208
|
+
sig { params(nodes: T::Array[RBI::Node]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
|
209
|
+
def extract_methods_and_attrs(nodes)
|
210
|
+
T.cast(nodes.select do |node|
|
211
|
+
node.is_a?(RBI::Method) || node.is_a?(RBI::Attr)
|
212
|
+
end, T::Array[T.any(RBI::Method, RBI::Attr)])
|
213
|
+
end
|
214
|
+
|
215
|
+
sig { params(nodes: T::Array[T.any(RBI::Method, RBI::Attr)]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
|
216
|
+
def extract_nodes_with_sigs(nodes)
|
217
|
+
nodes.reject { |node| node.sigs.empty? }
|
218
|
+
end
|
219
|
+
|
220
|
+
sig { params(errors: T::Array[Spoom::Sorbet::Errors::Error], gem_dir: String).void }
|
221
|
+
def update_gem_rbis_strictnesses(errors, gem_dir)
|
222
|
+
files = []
|
223
|
+
|
224
|
+
errors.each do |error|
|
225
|
+
# Collect the file with error
|
226
|
+
files << error.file
|
227
|
+
error.more.each do |line|
|
228
|
+
# Also collect the conflicting definition file paths
|
229
|
+
next unless line.include?("Previous definition")
|
230
|
+
|
231
|
+
files << line.split(":").first&.strip
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
files
|
236
|
+
.uniq
|
237
|
+
.sort
|
238
|
+
.select { |file| file.start_with?(gem_dir) }
|
239
|
+
.each do |file|
|
240
|
+
Spoom::Sorbet::Sigils.change_sigil_in_file(file, "false")
|
241
|
+
say("\n Changed strictness of #{file} to `typed: false` (conflicting with DSL files)", [:yellow, :bold])
|
242
|
+
end
|
243
|
+
|
244
|
+
say("\n")
|
245
|
+
end
|
246
|
+
|
247
|
+
sig { params(path: String).returns(String) }
|
248
|
+
def gem_name_from_rbi_path(path)
|
249
|
+
T.must(File.basename(path, ".rbi").split("@").first)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
@@ -4,121 +4,101 @@
|
|
4
4
|
module Tapioca
|
5
5
|
module RBIHelper
|
6
6
|
extend T::Sig
|
7
|
-
|
7
|
+
include SorbetHelper
|
8
|
+
extend SorbetHelper
|
9
|
+
extend self
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
sig do
|
13
|
-
params(
|
14
|
-
command: String,
|
15
|
-
gem_dir: String,
|
16
|
-
dsl_dir: String,
|
17
|
-
auto_strictness: T::Boolean,
|
18
|
-
gems: T::Array[Gemfile::GemSpec],
|
19
|
-
compilers: T::Enumerable[Class]
|
20
|
-
).void
|
11
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
12
|
+
def create_param(name, type:)
|
13
|
+
create_typed_param(RBI::Param.new(name), type)
|
21
14
|
end
|
22
|
-
def validate_rbi_files(command:, gem_dir:, dsl_dir:, auto_strictness:, gems: [], compilers: [])
|
23
|
-
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
24
|
-
|
25
|
-
say("Checking generated RBI files... ")
|
26
|
-
res = sorbet(
|
27
|
-
"--no-config",
|
28
|
-
"--error-url-base=#{error_url_base}",
|
29
|
-
"--stop-after namer",
|
30
|
-
dsl_dir,
|
31
|
-
gem_dir
|
32
|
-
)
|
33
|
-
say(" Done", :green)
|
34
|
-
|
35
|
-
errors = Spoom::Sorbet::Errors::Parser.parse_string(res.err)
|
36
|
-
|
37
|
-
if errors.empty?
|
38
|
-
say(" No errors found\n\n", [:green, :bold])
|
39
|
-
return
|
40
|
-
end
|
41
|
-
|
42
|
-
parse_errors = errors.select { |error| error.code < 4000 }
|
43
|
-
|
44
|
-
if parse_errors.any?
|
45
|
-
say_error(<<~ERR, :red)
|
46
|
-
|
47
|
-
##### INTERNAL ERROR #####
|
48
15
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
Tapioca v#{Tapioca::VERSION}
|
55
|
-
|
56
|
-
Command:
|
57
|
-
#{command}
|
58
|
-
|
59
|
-
ERR
|
60
|
-
|
61
|
-
say_error(<<~ERR, :red) if gems.any?
|
62
|
-
Gems:
|
63
|
-
#{gems.map { |gem| " #{gem.name} (#{gem.version})" }.join("\n")}
|
16
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
17
|
+
def create_opt_param(name, type:, default:)
|
18
|
+
create_typed_param(RBI::OptParam.new(name, default), type)
|
19
|
+
end
|
64
20
|
|
65
|
-
|
21
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
22
|
+
def create_rest_param(name, type:)
|
23
|
+
create_typed_param(RBI::RestParam.new(name), type)
|
24
|
+
end
|
66
25
|
|
67
|
-
|
68
|
-
|
69
|
-
|
26
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
27
|
+
def create_kw_param(name, type:)
|
28
|
+
create_typed_param(RBI::KwParam.new(name), type)
|
29
|
+
end
|
70
30
|
|
71
|
-
|
31
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
32
|
+
def create_kw_opt_param(name, type:, default:)
|
33
|
+
create_typed_param(RBI::KwOptParam.new(name, default), type)
|
34
|
+
end
|
72
35
|
|
73
|
-
|
74
|
-
|
75
|
-
|
36
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
37
|
+
def create_kw_rest_param(name, type:)
|
38
|
+
create_typed_param(RBI::KwRestParam.new(name), type)
|
39
|
+
end
|
76
40
|
|
77
|
-
|
41
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
42
|
+
def create_block_param(name, type:)
|
43
|
+
create_typed_param(RBI::BlockParam.new(name), type)
|
44
|
+
end
|
78
45
|
|
79
|
-
|
80
|
-
|
46
|
+
sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
|
47
|
+
def create_typed_param(param, type)
|
48
|
+
RBI::TypedParam.new(param: param, type: sanitize_signature_types(type))
|
49
|
+
end
|
81
50
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
51
|
+
sig { params(sig_string: String).returns(String) }
|
52
|
+
def sanitize_signature_types(sig_string)
|
53
|
+
sig_string
|
54
|
+
.gsub(".returns(<VOID>)", ".void")
|
55
|
+
.gsub("<VOID>", "void")
|
56
|
+
.gsub("<NOT-TYPED>", "T.untyped")
|
57
|
+
.gsub(".params()", "")
|
58
|
+
end
|
86
59
|
|
87
|
-
|
60
|
+
sig do
|
61
|
+
params(
|
62
|
+
type: String,
|
63
|
+
variance: Symbol,
|
64
|
+
fixed: T.nilable(String),
|
65
|
+
upper: T.nilable(String),
|
66
|
+
lower: T.nilable(String)
|
67
|
+
).returns(String)
|
88
68
|
end
|
69
|
+
def self.serialize_type_variable(type, variance, fixed, upper, lower)
|
70
|
+
variance = nil if variance == :invariant
|
89
71
|
|
90
|
-
|
72
|
+
bounds = []
|
73
|
+
bounds << "fixed: #{fixed}" if fixed
|
74
|
+
bounds << "lower: #{lower}" if lower
|
75
|
+
bounds << "upper: #{upper}" if upper
|
91
76
|
|
92
|
-
|
93
|
-
|
94
|
-
files = []
|
77
|
+
parameters = []
|
78
|
+
block = []
|
95
79
|
|
96
|
-
|
97
|
-
# Collect the file with error
|
98
|
-
files << error.file
|
99
|
-
error.more.each do |line|
|
100
|
-
# Also collect the conflicting definition file paths
|
101
|
-
next unless line.include?("Previous definition")
|
80
|
+
parameters << ":#{variance}" if variance
|
102
81
|
|
103
|
-
|
104
|
-
|
82
|
+
if sorbet_supports?(:type_variable_block_syntax)
|
83
|
+
block = bounds
|
84
|
+
else
|
85
|
+
parameters.concat(bounds)
|
105
86
|
end
|
106
87
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
Spoom::Sorbet::Sigils.change_sigil_in_file(file, "false")
|
113
|
-
say("\n Changed strictness of #{file} to `typed: false` (conflicting with DSL files)", [:yellow, :bold])
|
114
|
-
end
|
88
|
+
serialized = type.dup
|
89
|
+
serialized << "(#{parameters.join(", ")})" unless parameters.empty?
|
90
|
+
serialized << " { { #{block.join(", ")} } }" unless block.empty?
|
91
|
+
serialized
|
92
|
+
end
|
115
93
|
|
116
|
-
|
94
|
+
sig { params(name: String).returns(T::Boolean) }
|
95
|
+
def valid_method_name?(name)
|
96
|
+
!name.to_sym.inspect.start_with?(':"', ":@", ":$")
|
117
97
|
end
|
118
98
|
|
119
|
-
sig { params(
|
120
|
-
def
|
121
|
-
|
99
|
+
sig { params(name: String).returns(T::Boolean) }
|
100
|
+
def valid_parameter_name?(name)
|
101
|
+
/^([[:lower:]]|_|[^[[:ascii:]]])([[:alnum:]]|_|[^[[:ascii:]]])*$/.match?(name)
|
122
102
|
end
|
123
103
|
end
|
124
104
|
end
|