tapioca 0.7.0 → 0.8.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 +1 -1
- data/README.md +491 -73
- data/lib/tapioca/cli.rb +40 -3
- data/lib/tapioca/commands/annotations.rb +154 -0
- data/lib/tapioca/commands/dsl.rb +20 -1
- data/lib/tapioca/commands/gem.rb +17 -57
- data/lib/tapioca/commands/init.rb +1 -0
- data/lib/tapioca/commands/todo.rb +4 -2
- data/lib/tapioca/commands.rb +1 -0
- data/lib/tapioca/dsl/compiler.rb +2 -2
- data/lib/tapioca/dsl/compilers/aasm.rb +1 -1
- data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +1 -1
- data/lib/tapioca/dsl/compilers/action_mailer.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_job.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_associations.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_columns.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_enum.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +3 -1
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +8 -8
- data/lib/tapioca/dsl/compilers/active_record_scope.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_resource.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_storage.rb +6 -2
- data/lib/tapioca/dsl/compilers/active_support_concern.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +1 -1
- data/lib/tapioca/dsl/compilers/config.rb +2 -2
- data/lib/tapioca/dsl/compilers/frozen_record.rb +1 -1
- data/lib/tapioca/dsl/compilers/identity_cache.rb +1 -1
- data/lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb +1 -1
- data/lib/tapioca/dsl/compilers/protobuf.rb +27 -3
- data/lib/tapioca/dsl/compilers/rails_generators.rb +1 -1
- data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +1 -1
- data/lib/tapioca/dsl/compilers/smart_properties.rb +1 -1
- data/lib/tapioca/dsl/compilers/state_machines.rb +1 -1
- data/lib/tapioca/dsl/compilers/url_helpers.rb +5 -2
- data/lib/tapioca/dsl/helpers/param_helper.rb +4 -1
- data/lib/tapioca/dsl/pipeline.rb +32 -1
- data/lib/tapioca/dsl.rb +6 -0
- data/lib/tapioca/executor.rb +4 -46
- data/lib/tapioca/gem/listeners/methods.rb +26 -1
- data/lib/tapioca/gem/listeners/sorbet_props.rb +1 -1
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +1 -0
- data/lib/tapioca/gem/listeners/sorbet_signatures.rb +1 -1
- data/lib/tapioca/gem/pipeline.rb +5 -1
- data/lib/tapioca/gemfile.rb +50 -3
- data/lib/tapioca/helpers/config_helper.rb +13 -0
- data/lib/tapioca/helpers/rbi_helper.rb +114 -7
- data/lib/tapioca/helpers/shims_helper.rb +36 -8
- data/lib/tapioca/helpers/signatures_helper.rb +17 -0
- data/lib/tapioca/helpers/sorbet_helper.rb +5 -11
- data/lib/tapioca/helpers/test/content.rb +1 -0
- data/lib/tapioca/helpers/test/dsl_compiler.rb +1 -0
- data/lib/tapioca/helpers/test/template.rb +1 -0
- data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
- data/lib/tapioca/internal.rb +4 -1
- data/lib/tapioca/rbi_ext/model.rb +14 -2
- data/lib/tapioca/repo_index.rb +41 -0
- data/lib/tapioca/runtime/generic_type_registry.rb +4 -2
- data/lib/tapioca/runtime/loader.rb +3 -0
- data/lib/tapioca/runtime/reflection.rb +17 -13
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +38 -21
- data/lib/tapioca/static/symbol_table_parser.rb +2 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +5 -0
- metadata +26 -21
@@ -46,8 +46,12 @@ module Tapioca
|
|
46
46
|
class ActiveStorage < Compiler
|
47
47
|
extend T::Sig
|
48
48
|
|
49
|
-
ConstantType = type_member
|
50
|
-
|
49
|
+
ConstantType = type_member do
|
50
|
+
{
|
51
|
+
fixed: T.all(Module,
|
52
|
+
::ActiveStorage::Reflection::ActiveRecordExtensions::ClassMethods),
|
53
|
+
}
|
54
|
+
end
|
51
55
|
|
52
56
|
sig { override.void }
|
53
57
|
def decorate
|
@@ -62,7 +62,7 @@ module Tapioca
|
|
62
62
|
class ActiveSupportCurrentAttributes < Compiler
|
63
63
|
extend T::Sig
|
64
64
|
|
65
|
-
ConstantType = type_member
|
65
|
+
ConstantType = type_member { { fixed: T.class_of(::ActiveSupport::CurrentAttributes) } }
|
66
66
|
|
67
67
|
sig { override.void }
|
68
68
|
def decorate
|
@@ -49,7 +49,7 @@ module Tapioca
|
|
49
49
|
|
50
50
|
CONFIG_OPTIONS_SUFFIX = "ConfigOptions"
|
51
51
|
|
52
|
-
ConstantType = type_member
|
52
|
+
ConstantType = type_member { { fixed: Module } }
|
53
53
|
|
54
54
|
sig { override.void }
|
55
55
|
def decorate
|
@@ -77,7 +77,7 @@ module Tapioca
|
|
77
77
|
# enumerates the entries, we don't make any assumptions about their
|
78
78
|
# types.
|
79
79
|
mod.create_extend("T::Generic")
|
80
|
-
mod.
|
80
|
+
mod.create_type_variable("Elem", type: "type_member", fixed: "T.untyped")
|
81
81
|
|
82
82
|
method_names.each do |method_name|
|
83
83
|
# Create getter method
|
@@ -65,7 +65,7 @@ module Tapioca
|
|
65
65
|
class FrozenRecord < Compiler
|
66
66
|
extend T::Sig
|
67
67
|
|
68
|
-
ConstantType = type_member
|
68
|
+
ConstantType = type_member { { fixed: T.class_of(::FrozenRecord::Base) } }
|
69
69
|
|
70
70
|
sig { override.void }
|
71
71
|
def decorate
|
@@ -69,7 +69,7 @@ module Tapioca
|
|
69
69
|
T.proc.params(type: T.any(Module, String)).returns(String)
|
70
70
|
)
|
71
71
|
|
72
|
-
ConstantType = type_member
|
72
|
+
ConstantType = type_member { { fixed: T.class_of(::ActiveRecord::Base) } }
|
73
73
|
|
74
74
|
sig { override.void }
|
75
75
|
def decorate
|
@@ -70,7 +70,9 @@ module Tapioca
|
|
70
70
|
|
71
71
|
extend T::Sig
|
72
72
|
|
73
|
-
ConstantType = type_member
|
73
|
+
ConstantType = type_member { { fixed: Module } }
|
74
|
+
|
75
|
+
FIELD_RE = /^[a-z_][a-zA-Z0-9_]*$/
|
74
76
|
|
75
77
|
sig { override.void }
|
76
78
|
def decorate
|
@@ -81,6 +83,7 @@ module Tapioca
|
|
81
83
|
create_type_members(klass, "Key", "Value")
|
82
84
|
else
|
83
85
|
descriptor = T.let(T.unsafe(constant).descriptor, Google::Protobuf::Descriptor)
|
86
|
+
descriptor.each_oneof { |oneof| create_oneof_method(klass, oneof) }
|
84
87
|
fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
|
85
88
|
fields.sort_by!(&:name)
|
86
89
|
|
@@ -88,7 +91,15 @@ module Tapioca
|
|
88
91
|
create_kw_opt_param(field.name, type: field.init_type, default: field.default)
|
89
92
|
end
|
90
93
|
|
91
|
-
|
94
|
+
if fields.all? { |field| FIELD_RE.match?(field.name) }
|
95
|
+
klass.create_method("initialize", parameters: parameters, return_type: "void")
|
96
|
+
else
|
97
|
+
# One of the fields has an incorrect name for a named parameter so creating the default initialize for
|
98
|
+
# it would create a RBI with a syntax error.
|
99
|
+
# The workaround is to create an initialize that takes a **kwargs instead.
|
100
|
+
kwargs_parameter = create_kw_rest_param("fields", type: "T.untyped")
|
101
|
+
klass.create_method("initialize", parameters: [kwargs_parameter], return_type: "void")
|
102
|
+
end
|
92
103
|
end
|
93
104
|
end
|
94
105
|
end
|
@@ -107,7 +118,7 @@ module Tapioca
|
|
107
118
|
klass.create_extend("T::Generic")
|
108
119
|
|
109
120
|
names.each do |name|
|
110
|
-
klass.
|
121
|
+
klass.create_type_variable(name, type: "type_member")
|
111
122
|
end
|
112
123
|
end
|
113
124
|
|
@@ -206,6 +217,19 @@ module Tapioca
|
|
206
217
|
|
207
218
|
field
|
208
219
|
end
|
220
|
+
|
221
|
+
sig do
|
222
|
+
params(
|
223
|
+
klass: RBI::Scope,
|
224
|
+
desc: Google::Protobuf::OneofDescriptor
|
225
|
+
).void
|
226
|
+
end
|
227
|
+
def create_oneof_method(klass, desc)
|
228
|
+
klass.create_method(
|
229
|
+
desc.name,
|
230
|
+
return_type: "T.nilable(Symbol)"
|
231
|
+
)
|
232
|
+
end
|
209
233
|
end
|
210
234
|
end
|
211
235
|
end
|
@@ -118,7 +118,7 @@ module Tapioca
|
|
118
118
|
class StateMachines < Compiler
|
119
119
|
extend T::Sig
|
120
120
|
|
121
|
-
ConstantType = type_member
|
121
|
+
ConstantType = type_member { { fixed: T.all(Module, ::StateMachines::ClassMethods) } }
|
122
122
|
|
123
123
|
sig { override.void }
|
124
124
|
def decorate
|
@@ -87,7 +87,7 @@ module Tapioca
|
|
87
87
|
class UrlHelpers < Compiler
|
88
88
|
extend T::Sig
|
89
89
|
|
90
|
-
ConstantType = type_member
|
90
|
+
ConstantType = type_member { { fixed: Module } }
|
91
91
|
|
92
92
|
sig { override.void }
|
93
93
|
def decorate
|
@@ -109,6 +109,8 @@ module Tapioca
|
|
109
109
|
|
110
110
|
sig { override.returns(T::Enumerable[Module]) }
|
111
111
|
def self.gather_constants
|
112
|
+
return [] unless Rails.application
|
113
|
+
|
112
114
|
Object.const_set(:GeneratedUrlHelpersModule, Rails.application.routes.named_routes.url_helpers_module)
|
113
115
|
Object.const_set(:GeneratedPathHelpersModule, Rails.application.routes.named_routes.path_helpers_module)
|
114
116
|
|
@@ -160,7 +162,8 @@ module Tapioca
|
|
160
162
|
superclass_ancestors = ancestors_of(superclass) if superclass
|
161
163
|
end
|
162
164
|
|
163
|
-
(ancestors_of(mod)
|
165
|
+
ancestors = Set.new.compare_by_identity.merge(ancestors_of(mod)).subtract(superclass_ancestors)
|
166
|
+
ancestors.any? { |ancestor| helper == ancestor }
|
164
167
|
end
|
165
168
|
end
|
166
169
|
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "tapioca/helpers/signatures_helper"
|
5
|
+
|
4
6
|
module Tapioca
|
5
7
|
module Dsl
|
6
8
|
module Helpers
|
7
9
|
module ParamHelper
|
8
10
|
extend T::Sig
|
11
|
+
include SignaturesHelper
|
9
12
|
|
10
13
|
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
11
14
|
def create_param(name, type:)
|
@@ -44,7 +47,7 @@ module Tapioca
|
|
44
47
|
|
45
48
|
sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
|
46
49
|
def create_typed_param(param, type)
|
47
|
-
RBI::TypedParam.new(param: param, type: type)
|
50
|
+
RBI::TypedParam.new(param: param, type: sanitize_signature_types(type))
|
48
51
|
end
|
49
52
|
end
|
50
53
|
end
|
data/lib/tapioca/dsl/pipeline.rb
CHANGED
@@ -53,7 +53,7 @@ module Tapioca
|
|
53
53
|
end
|
54
54
|
def run(&blk)
|
55
55
|
constants_to_process = gather_constants(requested_constants)
|
56
|
-
.select { |c|
|
56
|
+
.select { |c| Module === c } # Filter value constants out
|
57
57
|
.sort_by! { |c| T.must(Runtime::Reflection.name_of(c)) }
|
58
58
|
|
59
59
|
if constants_to_process.empty?
|
@@ -112,18 +112,49 @@ module Tapioca
|
|
112
112
|
sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
|
113
113
|
def gather_constants(requested_constants)
|
114
114
|
constants = compilers.map(&:processable_constants).reduce(Set.new, :union)
|
115
|
+
constants = filter_anonymous_and_reloaded_constants(constants)
|
116
|
+
|
115
117
|
constants &= requested_constants unless requested_constants.empty?
|
116
118
|
constants
|
117
119
|
end
|
118
120
|
|
121
|
+
sig { params(constants: T::Set[Module]).returns(T::Set[Module]) }
|
122
|
+
def filter_anonymous_and_reloaded_constants(constants)
|
123
|
+
# Group constants by their names
|
124
|
+
constants_by_name = constants
|
125
|
+
.group_by { |c| T.must(Runtime::Reflection.name_of(c)) }
|
126
|
+
.select { |name, _| !name.nil? }
|
127
|
+
|
128
|
+
# Find the constants that have been reloaded
|
129
|
+
reloaded_constants = constants_by_name.select { |_, constants| constants.size > 1 }.keys
|
130
|
+
|
131
|
+
unless reloaded_constants.empty?
|
132
|
+
reloaded_constant_names = reloaded_constants.map { |name| "`#{name}`" }.join(", ")
|
133
|
+
|
134
|
+
$stderr.puts("WARNING: Multiple constants with the same name: #{reloaded_constant_names}")
|
135
|
+
$stderr.puts("Make sure some object is not holding onto these constants during an app reload.")
|
136
|
+
end
|
137
|
+
|
138
|
+
# Look up all the constants back from their names. The resulting constant set will be the
|
139
|
+
# set of constants that are actually in memory with those names.
|
140
|
+
constants_by_name
|
141
|
+
.keys
|
142
|
+
.map { |name| T.cast(Runtime::Reflection.constantize(name), Module) }
|
143
|
+
.to_set
|
144
|
+
end
|
145
|
+
|
119
146
|
sig { params(constant: Module).returns(T.nilable(RBI::File)) }
|
120
147
|
def rbi_for_constant(constant)
|
121
148
|
file = RBI::File.new(strictness: "true")
|
122
149
|
|
123
150
|
compilers.each do |compiler_class|
|
124
151
|
next unless compiler_class.handles?(constant)
|
152
|
+
|
125
153
|
compiler = compiler_class.new(self, file.root, constant)
|
126
154
|
compiler.decorate
|
155
|
+
rescue
|
156
|
+
$stderr.puts("Error: `#{compiler_class.name}` failed to generate RBI for `#{constant}`")
|
157
|
+
raise # This is an unexpected error, so re-raise it
|
127
158
|
end
|
128
159
|
|
129
160
|
return if file.root.empty?
|
data/lib/tapioca/dsl.rb
ADDED
data/lib/tapioca/executor.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "etc"
|
5
|
+
require "parallel"
|
5
6
|
|
6
7
|
module Tapioca
|
7
8
|
class Executor
|
@@ -20,10 +21,6 @@ module Tapioca
|
|
20
21
|
number_of_workers || [Etc.nprocessors, (queue.length.to_f / MINIMUM_ITEMS_PER_WORKER).ceil].min,
|
21
22
|
Integer
|
22
23
|
)
|
23
|
-
|
24
|
-
# The number of items that will be processed per worker, so that we can split the queue into groups and assign
|
25
|
-
# them to each one of the workers
|
26
|
-
@items_per_worker = T.let((queue.length.to_f / @number_of_workers).ceil, Integer)
|
27
24
|
end
|
28
25
|
|
29
26
|
sig do
|
@@ -32,48 +29,9 @@ module Tapioca
|
|
32
29
|
).returns(T::Array[T.type_parameter(:T)])
|
33
30
|
end
|
34
31
|
def run_in_parallel(&block)
|
35
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
read_pipes = []
|
39
|
-
write_pipes = []
|
40
|
-
|
41
|
-
# If we have more than one worker, fork the pool by shifting the expected number of items per worker from the
|
42
|
-
# queue
|
43
|
-
workers = (0...@number_of_workers).map do
|
44
|
-
items = @queue.shift(@items_per_worker)
|
45
|
-
|
46
|
-
# Each worker has their own pair of pipes, so that we can read the result from each worker separately
|
47
|
-
read, write = IO.pipe
|
48
|
-
read_pipes << read
|
49
|
-
write_pipes << write
|
50
|
-
|
51
|
-
fork do
|
52
|
-
read.close
|
53
|
-
result = items.map { |item| block.call(item) }
|
54
|
-
|
55
|
-
# Pack the result as a Base64 string of the Marshal dump of the array of values returned by the block that we
|
56
|
-
# ran in parallel
|
57
|
-
packed = [Marshal.dump(result)].pack("m")
|
58
|
-
write.puts(packed)
|
59
|
-
write.close
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# Close all the write pipes, then read and close from all the read pipes
|
64
|
-
write_pipes.each(&:close)
|
65
|
-
result = read_pipes.map do |pipe|
|
66
|
-
content = pipe.read
|
67
|
-
pipe.close
|
68
|
-
content
|
69
|
-
end
|
70
|
-
|
71
|
-
# Wait until all the workers finish. Notice that waiting for the PIDs can only happen after we read and close the
|
72
|
-
# pipe or else we may end up in a condition where writing to the pipe hangs indefinitely
|
73
|
-
workers.each { |pid| Process.waitpid(pid) }
|
74
|
-
|
75
|
-
# Decode the value back into the Ruby objects by doing the inverse of what each worker does
|
76
|
-
result.flat_map { |item| T.unsafe(Marshal.load(item.unpack1("m"))) }
|
32
|
+
# To have the parallel gem run jobs in the parent process, you must pass 0 as the number of processes
|
33
|
+
number_of_processes = @number_of_workers == 1 ? 0 : @number_of_workers
|
34
|
+
Parallel.map(@queue, { in_processes: number_of_processes }, &block)
|
77
35
|
end
|
78
36
|
end
|
79
37
|
end
|
@@ -41,6 +41,7 @@ module Tapioca
|
|
41
41
|
.each do |visibility, method_list|
|
42
42
|
method_list.sort!.map do |name|
|
43
43
|
next if name == :initialize
|
44
|
+
|
44
45
|
vis = case visibility
|
45
46
|
when :protected
|
46
47
|
RBI::Protected.new
|
@@ -65,7 +66,7 @@ module Tapioca
|
|
65
66
|
end
|
66
67
|
def compile_method(tree, symbol_name, constant, method, visibility = RBI::Public.new)
|
67
68
|
return unless method
|
68
|
-
return unless method
|
69
|
+
return unless method_owned_by_constant?(method, constant)
|
69
70
|
return if @pipeline.symbol_in_payload?(symbol_name) && !@pipeline.method_in_gem?(method)
|
70
71
|
|
71
72
|
signature = signature_of(method)
|
@@ -141,6 +142,29 @@ module Tapioca
|
|
141
142
|
tree << rbi_method
|
142
143
|
end
|
143
144
|
|
145
|
+
# Check whether the method is defined by the constant.
|
146
|
+
#
|
147
|
+
# In most cases, it works to check that the constant is the method owner. However,
|
148
|
+
# in the case that a method is also defined in a module prepended to the constant, it
|
149
|
+
# will be owned by the prepended module, not the constant.
|
150
|
+
#
|
151
|
+
# This method implements a better way of checking whether a constant defines a method.
|
152
|
+
# It walks up the ancestor tree via the `super_method` method; if any of the super
|
153
|
+
# methods are owned by the constant, it means that the constant declares the method.
|
154
|
+
sig { params(method: UnboundMethod, constant: Module).returns(T::Boolean) }
|
155
|
+
def method_owned_by_constant?(method, constant)
|
156
|
+
# Widen the type of `method` to be nilable
|
157
|
+
method = T.let(method, T.nilable(UnboundMethod))
|
158
|
+
|
159
|
+
while method
|
160
|
+
return true if method.owner == constant
|
161
|
+
|
162
|
+
method = method.super_method
|
163
|
+
end
|
164
|
+
|
165
|
+
false
|
166
|
+
end
|
167
|
+
|
144
168
|
sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
|
145
169
|
def method_names_by_visibility(mod)
|
146
170
|
{
|
@@ -163,6 +187,7 @@ module Tapioca
|
|
163
187
|
sig { params(name: String).returns(T::Boolean) }
|
164
188
|
def valid_method_name?(name)
|
165
189
|
return true if SPECIAL_METHOD_NAMES.include?(name)
|
190
|
+
|
166
191
|
!!name.match(/^[[:word:]]+[?!=]?$/)
|
167
192
|
end
|
168
193
|
|
@@ -19,7 +19,7 @@ module Tapioca
|
|
19
19
|
constant.props.map do |name, prop|
|
20
20
|
type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void")
|
21
21
|
|
22
|
-
default = prop.key?(:default) ? "T.unsafe(nil)" : nil
|
22
|
+
default = prop.key?(:default) || prop.key?(:factory) ? "T.unsafe(nil)" : nil
|
23
23
|
node << if prop.fetch(:immutable, false)
|
24
24
|
RBI::TStructConst.new(name.to_s, type, default: default)
|
25
25
|
else
|
@@ -14,6 +14,7 @@ module Tapioca
|
|
14
14
|
ancestors = Runtime::Trackers::RequiredAncestor.required_ancestors_by(event.constant)
|
15
15
|
ancestors.each do |ancestor|
|
16
16
|
next unless ancestor # TODO: We should have a way to warn from here
|
17
|
+
|
17
18
|
event.node << RBI::RequiresAncestor.new(ancestor.to_s)
|
18
19
|
end
|
19
20
|
end
|
data/lib/tapioca/gem/pipeline.rb
CHANGED
@@ -8,7 +8,7 @@ module Tapioca
|
|
8
8
|
class Pipeline
|
9
9
|
extend T::Sig
|
10
10
|
include Runtime::Reflection
|
11
|
-
include
|
11
|
+
include SignaturesHelper
|
12
12
|
|
13
13
|
IGNORED_SYMBOLS = T.let(["YAML", "MiniTest", "Mutex"], T::Array[String])
|
14
14
|
|
@@ -87,6 +87,7 @@ module Tapioca
|
|
87
87
|
def symbol_in_payload?(symbol_name)
|
88
88
|
symbol_name = symbol_name[2..-1] if symbol_name.start_with?("::")
|
89
89
|
return false unless symbol_name
|
90
|
+
|
90
91
|
@payload_symbols.include?(symbol_name)
|
91
92
|
end
|
92
93
|
|
@@ -102,9 +103,11 @@ module Tapioca
|
|
102
103
|
def name_of(constant)
|
103
104
|
name = name_of_proxy_target(constant, super(class_of(constant)))
|
104
105
|
return name if name
|
106
|
+
|
105
107
|
name = super(constant)
|
106
108
|
return if name.nil?
|
107
109
|
return unless are_equal?(constant, constantize(name, inherit: true))
|
110
|
+
|
108
111
|
name = "Struct" if name =~ /^(::)?Struct::[^:]+$/
|
109
112
|
name
|
110
113
|
end
|
@@ -350,6 +353,7 @@ module Tapioca
|
|
350
353
|
sig { params(constant: Module, class_name: T.nilable(String)).returns(T.nilable(String)) }
|
351
354
|
def name_of_proxy_target(constant, class_name)
|
352
355
|
return unless class_name == "ActiveSupport::Deprecation::DeprecatedConstantProxy"
|
356
|
+
|
353
357
|
# We are dealing with a ActiveSupport::Deprecation::DeprecatedConstantProxy
|
354
358
|
# so try to get the name of the target class
|
355
359
|
begin
|
data/lib/tapioca/gemfile.rb
CHANGED
@@ -16,6 +16,50 @@ module Tapioca
|
|
16
16
|
)
|
17
17
|
end
|
18
18
|
|
19
|
+
# This is a module that gets prepended to `Bundler::Dependency` and
|
20
|
+
# makes sure even gems marked as `require: false` are required during
|
21
|
+
# `Bundler.require`.
|
22
|
+
module AutoRequireHook
|
23
|
+
extend T::Sig
|
24
|
+
extend T::Helpers
|
25
|
+
|
26
|
+
requires_ancestor { ::Bundler::Dependency }
|
27
|
+
|
28
|
+
@exclude = T.let([], T::Array[String])
|
29
|
+
|
30
|
+
class << self
|
31
|
+
extend T::Sig
|
32
|
+
|
33
|
+
sig { params(exclude: T::Array[String]).returns(T::Array[String]) }
|
34
|
+
attr_writer :exclude
|
35
|
+
|
36
|
+
sig { params(name: T.untyped).returns(T::Boolean) }
|
37
|
+
def excluded?(name)
|
38
|
+
@exclude.include?(name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { returns(T.untyped).checked(:never) }
|
43
|
+
def autorequire
|
44
|
+
value = super
|
45
|
+
|
46
|
+
# If the gem is excluded, we don't want to force require it, in case
|
47
|
+
# it has side-effects users don't want. For example, `fakefs` gem, if
|
48
|
+
# loaded, takes over filesystem operations.
|
49
|
+
return value if AutoRequireHook.excluded?(name)
|
50
|
+
|
51
|
+
# If a gem is marked as `require: false`, then its `autorequire`
|
52
|
+
# value will be `[]`. But, we want those gems to be loaded for our
|
53
|
+
# purposes as well, so we return `nil` in those cases, instead, which
|
54
|
+
# means `require: true`.
|
55
|
+
return nil if value == []
|
56
|
+
|
57
|
+
value
|
58
|
+
end
|
59
|
+
|
60
|
+
::Bundler::Dependency.prepend(self)
|
61
|
+
end
|
62
|
+
|
19
63
|
sig { returns(Bundler::Definition) }
|
20
64
|
attr_reader(:definition)
|
21
65
|
|
@@ -25,8 +69,9 @@ module Tapioca
|
|
25
69
|
sig { returns(T::Array[String]) }
|
26
70
|
attr_reader(:missing_specs)
|
27
71
|
|
28
|
-
sig { void }
|
29
|
-
def initialize
|
72
|
+
sig { params(exclude: T::Array[String]).void }
|
73
|
+
def initialize(exclude)
|
74
|
+
AutoRequireHook.exclude = exclude
|
30
75
|
@gemfile = T.let(File.new(Bundler.default_gemfile), File)
|
31
76
|
@lockfile = T.let(File.new(Bundler.default_lockfile), File)
|
32
77
|
@definition = T.let(Bundler::Dsl.evaluate(gemfile, lockfile, {}), Bundler::Definition)
|
@@ -94,7 +139,8 @@ module Tapioca
|
|
94
139
|
class GemSpec
|
95
140
|
extend(T::Sig)
|
96
141
|
|
97
|
-
IGNORED_GEMS = T.let(["sorbet", "sorbet-static", "sorbet-runtime"].freeze,
|
142
|
+
IGNORED_GEMS = T.let(["sorbet", "sorbet-static", "sorbet-runtime", "sorbet-static-and-runtime"].freeze,
|
143
|
+
T::Array[String])
|
98
144
|
|
99
145
|
sig { returns(String) }
|
100
146
|
attr_reader :full_gem_path, :version
|
@@ -227,6 +273,7 @@ module Tapioca
|
|
227
273
|
# one of those folders to see if the path really belongs in the given gem
|
228
274
|
# or not.
|
229
275
|
return false unless Bundler::Source::Git === @spec.source
|
276
|
+
|
230
277
|
parent = Pathname.new(path)
|
231
278
|
|
232
279
|
until parent.root?
|
@@ -72,6 +72,7 @@ module Tapioca
|
|
72
72
|
def validate_config!(config_file, config)
|
73
73
|
# To ensure that this is not re-entered, we mark during validation
|
74
74
|
return if @validating_config
|
75
|
+
|
75
76
|
@validating_config = T.let(true, T.nilable(T::Boolean))
|
76
77
|
|
77
78
|
commands = T.cast(self, Thor).class.commands
|
@@ -125,6 +126,18 @@ module Tapioca
|
|
125
126
|
error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
|
126
127
|
"`#{command_option.type.capitalize}` but found #{config_option_value_type.capitalize}"
|
127
128
|
next build_error(error_msg) unless config_option_value_type == command_option.type
|
129
|
+
|
130
|
+
case config_option_value_type
|
131
|
+
when :array
|
132
|
+
error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
|
133
|
+
"`Array[String]` but found `#{config_option_value}`"
|
134
|
+
next build_error(error_msg) unless config_option_value.all? { |v| v.is_a?(String) }
|
135
|
+
when :hash
|
136
|
+
error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
|
137
|
+
"`Hash[String, String]` but found `#{config_option_value}`"
|
138
|
+
all_strings = (config_option_value.keys + config_option_value.values).all? { |v| v.is_a?(String) }
|
139
|
+
next build_error(error_msg) unless all_strings
|
140
|
+
end
|
128
141
|
end.compact
|
129
142
|
end
|
130
143
|
|