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