tapioca 0.4.27 → 0.5.3
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 +15 -15
- data/README.md +2 -2
- data/Rakefile +5 -7
- data/exe/tapioca +2 -2
- data/lib/tapioca/cli.rb +172 -2
- data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
- data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
- data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
- data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
- data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
- data/lib/tapioca/compilers/dsl/active_model_secure_password.rb +101 -0
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
- data/lib/tapioca/compilers/dsl/active_record_fixtures.rb +86 -0
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
- data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
- data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
- data/lib/tapioca/compilers/dsl/active_support_concern.rb +106 -0
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
- data/lib/tapioca/compilers/dsl/base.rb +108 -82
- data/lib/tapioca/compilers/dsl/config.rb +111 -0
- data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
- data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
- data/lib/tapioca/compilers/dsl/mixed_in_class_attributes.rb +74 -0
- data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
- data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
- data/lib/tapioca/compilers/dsl/smart_properties.rb +21 -33
- data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
- data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
- data/lib/tapioca/compilers/dsl_compiler.rb +25 -40
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
- data/lib/tapioca/compilers/requires_compiler.rb +2 -2
- data/lib/tapioca/compilers/sorbet.rb +25 -5
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +122 -206
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
- data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
- data/lib/tapioca/compilers/todos_compiler.rb +1 -1
- data/lib/tapioca/config.rb +3 -0
- data/lib/tapioca/config_builder.rb +5 -2
- data/lib/tapioca/constant_locator.rb +6 -8
- data/lib/tapioca/gemfile.rb +14 -11
- data/lib/tapioca/generators/base.rb +61 -0
- data/lib/tapioca/generators/dsl.rb +362 -0
- data/lib/tapioca/generators/gem.rb +345 -0
- data/lib/tapioca/generators/init.rb +79 -0
- data/lib/tapioca/generators/require.rb +52 -0
- data/lib/tapioca/generators/todo.rb +76 -0
- data/lib/tapioca/generators.rb +9 -0
- data/lib/tapioca/generic_type_registry.rb +25 -98
- data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
- data/lib/tapioca/internal.rb +2 -10
- data/lib/tapioca/loader.rb +11 -31
- data/lib/tapioca/rbi_ext/model.rb +166 -0
- data/lib/tapioca/reflection.rb +138 -0
- data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
- data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +3 -0
- metadata +45 -23
- data/lib/tapioca/cli/main.rb +0 -146
- data/lib/tapioca/core_ext/class.rb +0 -28
- data/lib/tapioca/core_ext/string.rb +0 -18
- data/lib/tapioca/generator.rb +0 -633
- data/lib/tapioca/rbi/model.rb +0 -405
- data/lib/tapioca/rbi/printer.rb +0 -410
- data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
- data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
- data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
- data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -86
- data/lib/tapioca/rbi/visitor.rb +0 -21
@@ -20,14 +20,15 @@ module Tapioca
|
|
20
20
|
# variable to type variable serializers. This allows us to associate type variables
|
21
21
|
# to the constant names that represent them, easily.
|
22
22
|
module GenericTypeRegistry
|
23
|
+
TypeVariable = T.type_alias { T.any(TypeMember, TypeTemplate) }
|
23
24
|
@generic_instances = T.let(
|
24
25
|
{},
|
25
26
|
T::Hash[String, Module]
|
26
27
|
)
|
27
28
|
|
28
29
|
@type_variables = T.let(
|
29
|
-
{},
|
30
|
-
T::Hash[
|
30
|
+
{}.compare_by_identity,
|
31
|
+
T::Hash[Module, T::Hash[TypeVariable, String]]
|
31
32
|
)
|
32
33
|
|
33
34
|
class << self
|
@@ -49,7 +50,7 @@ module Tapioca
|
|
49
50
|
# Build the name of the instantiated generic type,
|
50
51
|
# something like `"Foo[X, Y, Z]"`
|
51
52
|
type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ")
|
52
|
-
name = "#{name_of(constant)}[#{type_list}]"
|
53
|
+
name = "#{Reflection.name_of(constant)}[#{type_list}]"
|
53
54
|
|
54
55
|
# Create a generic type with an overridden `name`
|
55
56
|
# method that returns the name we constructed above.
|
@@ -59,35 +60,30 @@ module Tapioca
|
|
59
60
|
@generic_instances[name] ||= create_generic_type(constant, name)
|
60
61
|
end
|
61
62
|
|
62
|
-
sig
|
63
|
-
|
64
|
-
|
65
|
-
type_member: T::Types::TypeVariable,
|
66
|
-
fixed: T.untyped,
|
67
|
-
lower: T.untyped,
|
68
|
-
upper: T.untyped
|
69
|
-
).void
|
70
|
-
end
|
71
|
-
def register_type_member(constant, type_member, fixed, lower, upper)
|
72
|
-
register_type_variable(constant, :type_member, type_member, fixed, lower, upper)
|
63
|
+
sig { params(constant: Module).returns(T.nilable(T::Hash[TypeVariable, String])) }
|
64
|
+
def lookup_type_variables(constant)
|
65
|
+
@type_variables[constant]
|
73
66
|
end
|
74
67
|
|
68
|
+
# This method is called from intercepted calls to `type_member` and `type_template`.
|
69
|
+
# We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
|
70
|
+
# instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
|
71
|
+
#
|
72
|
+
# This method creates a `String` with that data and stores it in the
|
73
|
+
# `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
|
74
|
+
#
|
75
|
+
# Finally, the original `type_variable` is returned from this method, so that the caller
|
76
|
+
# can return it from the original methods as well.
|
75
77
|
sig do
|
76
78
|
params(
|
77
79
|
constant: T.untyped,
|
78
|
-
|
79
|
-
fixed: T.untyped,
|
80
|
-
lower: T.untyped,
|
81
|
-
upper: T.untyped
|
80
|
+
type_variable: TypeVariable,
|
82
81
|
).void
|
83
82
|
end
|
84
|
-
def
|
85
|
-
|
86
|
-
end
|
83
|
+
def register_type_variable(constant, type_variable)
|
84
|
+
type_variables = lookup_or_initialize_type_variables(constant)
|
87
85
|
|
88
|
-
|
89
|
-
def lookup_type_variables(constant)
|
90
|
-
@type_variables[object_id_of(constant)]
|
86
|
+
type_variables[type_variable] = type_variable.serialize
|
91
87
|
end
|
92
88
|
|
93
89
|
private
|
@@ -117,39 +113,6 @@ module Tapioca
|
|
117
113
|
generic_type
|
118
114
|
end
|
119
115
|
|
120
|
-
# This method is called from intercepted calls to `type_member` and `type_template`.
|
121
|
-
# We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
|
122
|
-
# instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
|
123
|
-
#
|
124
|
-
# This method creates a `String` with that data and stores it in the
|
125
|
-
# `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
|
126
|
-
#
|
127
|
-
# Finally, the original `type_variable` is returned from this method, so that the caller
|
128
|
-
# can return it from the original methods as well.
|
129
|
-
sig do
|
130
|
-
params(
|
131
|
-
constant: T.untyped,
|
132
|
-
type_variable_type: T.enum([:type_member, :type_template]),
|
133
|
-
type_variable: T::Types::TypeVariable,
|
134
|
-
fixed: T.untyped,
|
135
|
-
lower: T.untyped,
|
136
|
-
upper: T.untyped
|
137
|
-
).void
|
138
|
-
end
|
139
|
-
# rubocop:disable Metrics/ParameterLists
|
140
|
-
def register_type_variable(constant, type_variable_type, type_variable, fixed, lower, upper)
|
141
|
-
# rubocop:enable Metrics/ParameterLists
|
142
|
-
type_variables = lookup_or_initialize_type_variables(constant)
|
143
|
-
|
144
|
-
type_variables[object_id_of(type_variable)] = serialize_type_variable(
|
145
|
-
type_variable_type,
|
146
|
-
type_variable.variance,
|
147
|
-
fixed,
|
148
|
-
lower,
|
149
|
-
upper
|
150
|
-
)
|
151
|
-
end
|
152
|
-
|
153
116
|
sig { params(constant: Class).returns(Class) }
|
154
117
|
def create_safe_subclass(constant)
|
155
118
|
# Lookup the "inherited" class method
|
@@ -164,11 +127,9 @@ module Tapioca
|
|
164
127
|
# Otherwise, some inherited method could be preventing us
|
165
128
|
# from creating subclasses, so let's override it and rescue
|
166
129
|
owner.send(:define_method, :inherited) do |s|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
# Ignoring errors
|
171
|
-
end
|
130
|
+
inherited_method.call(s)
|
131
|
+
rescue
|
132
|
+
# Ignoring errors
|
172
133
|
end
|
173
134
|
|
174
135
|
# return a subclass
|
@@ -179,43 +140,9 @@ module Tapioca
|
|
179
140
|
end
|
180
141
|
end
|
181
142
|
|
182
|
-
sig { params(constant: Module).returns(T::Hash[
|
143
|
+
sig { params(constant: Module).returns(T::Hash[TypeVariable, String]) }
|
183
144
|
def lookup_or_initialize_type_variables(constant)
|
184
|
-
@type_variables[
|
185
|
-
end
|
186
|
-
|
187
|
-
sig do
|
188
|
-
params(
|
189
|
-
type_variable_type: Symbol,
|
190
|
-
variance: Symbol,
|
191
|
-
fixed: T.untyped,
|
192
|
-
lower: T.untyped,
|
193
|
-
upper: T.untyped
|
194
|
-
).returns(String)
|
195
|
-
end
|
196
|
-
def serialize_type_variable(type_variable_type, variance, fixed, lower, upper)
|
197
|
-
parts = []
|
198
|
-
parts << ":#{variance}" unless variance == :invariant
|
199
|
-
parts << "fixed: #{fixed}" if fixed
|
200
|
-
parts << "lower: #{lower}" unless lower == T.untyped
|
201
|
-
parts << "upper: #{upper}" unless upper == BasicObject
|
202
|
-
|
203
|
-
parameters = parts.join(", ")
|
204
|
-
|
205
|
-
serialized = T.let(type_variable_type.to_s, String)
|
206
|
-
serialized += "(#{parameters})" unless parameters.empty?
|
207
|
-
|
208
|
-
serialized
|
209
|
-
end
|
210
|
-
|
211
|
-
sig { params(constant: Module).returns(T.nilable(String)) }
|
212
|
-
def name_of(constant)
|
213
|
-
Module.instance_method(:name).bind(constant).call
|
214
|
-
end
|
215
|
-
|
216
|
-
sig { params(object: BasicObject).returns(Integer) }
|
217
|
-
def object_id_of(object)
|
218
|
-
Object.instance_method(:object_id).bind(object).call
|
145
|
+
@type_variables[constant] ||= {}.compare_by_identity
|
219
146
|
end
|
220
147
|
end
|
221
148
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class ActiveRecordColumnTypeHelper
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { params(constant: T.class_of(ActiveRecord::Base)).void }
|
8
|
+
def initialize(constant)
|
9
|
+
@constant = constant
|
10
|
+
end
|
11
|
+
|
12
|
+
sig { params(column_name: String).returns([String, String]) }
|
13
|
+
def type_for(column_name)
|
14
|
+
return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(@constant)
|
15
|
+
|
16
|
+
column_type = @constant.attribute_types[column_name]
|
17
|
+
|
18
|
+
getter_type =
|
19
|
+
case column_type
|
20
|
+
when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
|
21
|
+
"::Money"
|
22
|
+
when ActiveRecord::Type::Integer
|
23
|
+
"::Integer"
|
24
|
+
when ActiveRecord::Type::String
|
25
|
+
"::String"
|
26
|
+
when ActiveRecord::Type::Date
|
27
|
+
"::Date"
|
28
|
+
when ActiveRecord::Type::Decimal
|
29
|
+
"::BigDecimal"
|
30
|
+
when ActiveRecord::Type::Float
|
31
|
+
"::Float"
|
32
|
+
when ActiveRecord::Type::Boolean
|
33
|
+
"T::Boolean"
|
34
|
+
when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
|
35
|
+
"::DateTime"
|
36
|
+
when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
|
37
|
+
"::ActiveSupport::TimeWithZone"
|
38
|
+
else
|
39
|
+
handle_unknown_type(column_type)
|
40
|
+
end
|
41
|
+
|
42
|
+
column = @constant.columns_hash[column_name]
|
43
|
+
setter_type = getter_type
|
44
|
+
|
45
|
+
if column&.null
|
46
|
+
return ["T.nilable(#{getter_type})", "T.nilable(#{setter_type})"]
|
47
|
+
end
|
48
|
+
|
49
|
+
if column_name == @constant.primary_key ||
|
50
|
+
column_name == "created_at" ||
|
51
|
+
column_name == "updated_at"
|
52
|
+
getter_type = "T.nilable(#{getter_type})"
|
53
|
+
end
|
54
|
+
|
55
|
+
[getter_type, setter_type]
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
sig { params(constant: Module).returns(T::Boolean) }
|
61
|
+
def do_not_generate_strong_types?(constant)
|
62
|
+
Object.const_defined?(:StrongTypeGeneration) &&
|
63
|
+
!(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { params(column_type: Object).returns(String) }
|
67
|
+
def handle_unknown_type(column_type)
|
68
|
+
return "T.untyped" unless ActiveModel::Type::Value === column_type
|
69
|
+
|
70
|
+
lookup_return_type_of_method(column_type, :deserialize) ||
|
71
|
+
lookup_return_type_of_method(column_type, :cast) ||
|
72
|
+
lookup_arg_type_of_method(column_type, :serialize) ||
|
73
|
+
"T.untyped"
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
|
77
|
+
def lookup_return_type_of_method(column_type, method)
|
78
|
+
signature = T::Private::Methods.signature_for_method(column_type.method(method))
|
79
|
+
return unless signature
|
80
|
+
|
81
|
+
return_type = signature.return_type
|
82
|
+
return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
|
83
|
+
|
84
|
+
return_type.to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
|
88
|
+
def lookup_arg_type_of_method(column_type, method)
|
89
|
+
signature = T::Private::Methods.signature_for_method(column_type.method(method))
|
90
|
+
return unless signature
|
91
|
+
|
92
|
+
# Arg types is an array [name, type] entries, so we desctructure the type of
|
93
|
+
# first argument to get the first argument type
|
94
|
+
_, first_argument_type = signature.arg_types.first
|
95
|
+
|
96
|
+
first_argument_type.to_s
|
97
|
+
end
|
98
|
+
end
|
data/lib/tapioca/internal.rb
CHANGED
@@ -3,22 +3,14 @@
|
|
3
3
|
|
4
4
|
require "tapioca"
|
5
5
|
require "tapioca/loader"
|
6
|
-
require "tapioca/generic_type_registry"
|
7
6
|
require "tapioca/sorbet_ext/generic_name_patch"
|
8
7
|
require "tapioca/sorbet_ext/fixed_hash_patch"
|
8
|
+
require "tapioca/generic_type_registry"
|
9
9
|
require "tapioca/config"
|
10
10
|
require "tapioca/config_builder"
|
11
|
-
require "tapioca/
|
11
|
+
require "tapioca/generators"
|
12
12
|
require "tapioca/cli"
|
13
|
-
require "tapioca/cli/main"
|
14
13
|
require "tapioca/gemfile"
|
15
|
-
require "tapioca/rbi/model"
|
16
|
-
require "tapioca/rbi/visitor"
|
17
|
-
require "tapioca/rbi/rewriters/nest_singleton_methods"
|
18
|
-
require "tapioca/rbi/rewriters/nest_non_public_methods"
|
19
|
-
require "tapioca/rbi/rewriters/group_nodes"
|
20
|
-
require "tapioca/rbi/rewriters/sort_nodes"
|
21
|
-
require "tapioca/rbi/printer"
|
22
14
|
require "tapioca/compilers/sorbet"
|
23
15
|
require "tapioca/compilers/requires_compiler"
|
24
16
|
require "tapioca/compilers/symbol_table_compiler"
|
data/lib/tapioca/loader.rb
CHANGED
@@ -5,13 +5,8 @@ module Tapioca
|
|
5
5
|
class Loader
|
6
6
|
extend(T::Sig)
|
7
7
|
|
8
|
-
sig { params(gemfile: Tapioca::Gemfile).void }
|
9
|
-
def
|
10
|
-
@gemfile = T.let(gemfile, Tapioca::Gemfile)
|
11
|
-
end
|
12
|
-
|
13
|
-
sig { params(initialize_file: T.nilable(String), require_file: T.nilable(String)).void }
|
14
|
-
def load_bundle(initialize_file, require_file)
|
8
|
+
sig { params(gemfile: Tapioca::Gemfile, initialize_file: T.nilable(String), require_file: T.nilable(String)).void }
|
9
|
+
def load_bundle(gemfile, initialize_file, require_file)
|
15
10
|
require_helper(initialize_file)
|
16
11
|
|
17
12
|
load_rails_application
|
@@ -40,9 +35,6 @@ module Tapioca
|
|
40
35
|
|
41
36
|
private
|
42
37
|
|
43
|
-
sig { returns(Tapioca::Gemfile) }
|
44
|
-
attr_reader :gemfile
|
45
|
-
|
46
38
|
sig { params(file: T.nilable(String)).void }
|
47
39
|
def require_helper(file)
|
48
40
|
return unless file
|
@@ -54,18 +46,10 @@ module Tapioca
|
|
54
46
|
|
55
47
|
sig { returns(T::Array[T.untyped]) }
|
56
48
|
def rails_engines
|
57
|
-
|
58
|
-
|
59
|
-
return engines unless Object.const_defined?("Rails::Engine")
|
60
|
-
|
61
|
-
base = Object.const_get("Rails::Engine")
|
62
|
-
ObjectSpace.each_object(base.singleton_class) do |k|
|
63
|
-
k = T.cast(k, Class)
|
64
|
-
next if k.singleton_class?
|
65
|
-
engines.unshift(k) unless k == base
|
66
|
-
end
|
49
|
+
return [] unless Object.const_defined?("Rails::Engine")
|
67
50
|
|
68
|
-
|
51
|
+
# We can use `Class#descendants` here, since we know Rails is loaded
|
52
|
+
Object.const_get("Rails::Engine").descendants.reject(&:abstract_railtie?)
|
69
53
|
end
|
70
54
|
|
71
55
|
sig { params(path: String).void }
|
@@ -116,22 +100,18 @@ module Tapioca
|
|
116
100
|
|
117
101
|
engine.config.eager_load_paths.each do |load_path|
|
118
102
|
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
errored_files << file
|
123
|
-
end
|
103
|
+
require(file)
|
104
|
+
rescue LoadError, StandardError
|
105
|
+
errored_files << file
|
124
106
|
end
|
125
107
|
end
|
126
108
|
|
127
109
|
# Try files that have errored one more time
|
128
110
|
# It might have been a load order problem
|
129
111
|
errored_files.each do |file|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
nil
|
134
|
-
end
|
112
|
+
require(file)
|
113
|
+
rescue LoadError, StandardError
|
114
|
+
nil
|
135
115
|
end
|
136
116
|
end
|
137
117
|
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "rbi"
|
5
|
+
|
6
|
+
module RBI
|
7
|
+
class File
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(String) }
|
11
|
+
def transformed_string
|
12
|
+
transform_rbi!
|
13
|
+
string
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { void }
|
17
|
+
def transform_rbi!
|
18
|
+
root.nest_singleton_methods!
|
19
|
+
root.nest_non_public_methods!
|
20
|
+
root.group_nodes!
|
21
|
+
root.sort_nodes!
|
22
|
+
end
|
23
|
+
|
24
|
+
sig do
|
25
|
+
params(
|
26
|
+
command: String,
|
27
|
+
reason: T.nilable(String),
|
28
|
+
display_heading: T::Boolean
|
29
|
+
).void
|
30
|
+
end
|
31
|
+
def set_file_header(command, reason: nil, display_heading: true)
|
32
|
+
return unless display_heading
|
33
|
+
comments << RBI::Comment.new("DO NOT EDIT MANUALLY")
|
34
|
+
comments << RBI::Comment.new("This is an autogenerated file for #{reason}.") unless reason.nil?
|
35
|
+
comments << RBI::Comment.new("Please instead update this file by running `#{command}`.")
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { void }
|
39
|
+
def set_empty_body_content
|
40
|
+
comments << RBI::EmptyComment.new unless comments.empty?
|
41
|
+
comments << RBI::Comment.new("THIS IS AN EMPTY RBI FILE.")
|
42
|
+
comments << RBI::Comment.new("see https://github.com/Shopify/tapioca/wiki/Manual-Gem-Requires")
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { returns(T::Boolean) }
|
46
|
+
def empty?
|
47
|
+
root.empty?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Tree
|
52
|
+
extend T::Sig
|
53
|
+
|
54
|
+
sig { params(constant: ::Module, block: T.nilable(T.proc.params(scope: Scope).void)).void }
|
55
|
+
def create_path(constant, &block)
|
56
|
+
constant_name = Tapioca::Reflection.name_of(constant)
|
57
|
+
raise "given constant does not have a name" unless constant_name
|
58
|
+
|
59
|
+
instance = ::Module.const_get(constant_name)
|
60
|
+
case instance
|
61
|
+
when ::Class
|
62
|
+
create_class(constant.to_s, &block)
|
63
|
+
when ::Module
|
64
|
+
create_module(constant.to_s, &block)
|
65
|
+
else
|
66
|
+
raise "unexpected type: #{constant_name} is a #{instance.class}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
sig { params(name: String, block: T.nilable(T.proc.params(scope: Scope).void)).void }
|
71
|
+
def create_module(name, &block)
|
72
|
+
node = create_node(RBI::Module.new(name))
|
73
|
+
block&.call(T.cast(node, RBI::Scope))
|
74
|
+
end
|
75
|
+
|
76
|
+
sig do
|
77
|
+
params(
|
78
|
+
name: String,
|
79
|
+
superclass_name: T.nilable(String),
|
80
|
+
block: T.nilable(T.proc.params(scope: RBI::Scope).void)
|
81
|
+
).void
|
82
|
+
end
|
83
|
+
def create_class(name, superclass_name: nil, &block)
|
84
|
+
node = create_node(RBI::Class.new(name, superclass_name: superclass_name))
|
85
|
+
block&.call(T.cast(node, RBI::Scope))
|
86
|
+
end
|
87
|
+
|
88
|
+
sig { params(name: String, value: String).void }
|
89
|
+
def create_constant(name, value:)
|
90
|
+
create_node(RBI::Const.new(name, value))
|
91
|
+
end
|
92
|
+
|
93
|
+
sig { params(name: String).void }
|
94
|
+
def create_include(name)
|
95
|
+
create_node(RBI::Include.new(name))
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { params(name: String).void }
|
99
|
+
def create_extend(name)
|
100
|
+
create_node(RBI::Extend.new(name))
|
101
|
+
end
|
102
|
+
|
103
|
+
sig { params(name: String).void }
|
104
|
+
def create_mixes_in_class_methods(name)
|
105
|
+
create_node(RBI::MixesInClassMethods.new(name))
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { params(name: String, value: String).void }
|
109
|
+
def create_type_member(name, value: "type_member")
|
110
|
+
create_node(RBI::TypeMember.new(name, value))
|
111
|
+
end
|
112
|
+
|
113
|
+
sig do
|
114
|
+
params(
|
115
|
+
name: String,
|
116
|
+
parameters: T::Array[TypedParam],
|
117
|
+
return_type: String,
|
118
|
+
class_method: T::Boolean
|
119
|
+
).void
|
120
|
+
end
|
121
|
+
def create_method(name, parameters: [], return_type: "T.untyped", class_method: false)
|
122
|
+
return unless valid_method_name?(name)
|
123
|
+
|
124
|
+
sig = RBI::Sig.new(return_type: return_type)
|
125
|
+
method = RBI::Method.new(name, sigs: [sig], is_singleton: class_method)
|
126
|
+
parameters.each do |param|
|
127
|
+
method << param.param
|
128
|
+
sig << RBI::SigParam.new(param.param.name, param.type)
|
129
|
+
end
|
130
|
+
self << method
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
SPECIAL_METHOD_NAMES = T.let(
|
136
|
+
["!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^", "<", "<=", "=>", ">", ">=",
|
137
|
+
"==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"].freeze,
|
138
|
+
T::Array[String]
|
139
|
+
)
|
140
|
+
|
141
|
+
sig { params(name: String).returns(T::Boolean) }
|
142
|
+
def valid_method_name?(name)
|
143
|
+
return true if SPECIAL_METHOD_NAMES.include?(name)
|
144
|
+
!!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
|
145
|
+
end
|
146
|
+
|
147
|
+
sig { returns(T::Hash[String, RBI::Node]) }
|
148
|
+
def nodes_cache
|
149
|
+
T.must(@nodes_cache ||= T.let({}, T.nilable(T::Hash[String, Node])))
|
150
|
+
end
|
151
|
+
|
152
|
+
sig { params(node: RBI::Node).returns(RBI::Node) }
|
153
|
+
def create_node(node)
|
154
|
+
cached = nodes_cache[node.to_s]
|
155
|
+
return cached if cached
|
156
|
+
nodes_cache[node.to_s] = node
|
157
|
+
self << node
|
158
|
+
node
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class TypedParam < T::Struct
|
163
|
+
const :param, RBI::Param
|
164
|
+
const :type, String
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Reflection
|
6
|
+
extend T::Sig
|
7
|
+
extend self
|
8
|
+
|
9
|
+
CLASS_METHOD = T.let(Kernel.instance_method(:class), UnboundMethod)
|
10
|
+
CONSTANTS_METHOD = T.let(Module.instance_method(:constants), UnboundMethod)
|
11
|
+
NAME_METHOD = T.let(Module.instance_method(:name), UnboundMethod)
|
12
|
+
SINGLETON_CLASS_METHOD = T.let(Object.instance_method(:singleton_class), UnboundMethod)
|
13
|
+
ANCESTORS_METHOD = T.let(Module.instance_method(:ancestors), UnboundMethod)
|
14
|
+
SUPERCLASS_METHOD = T.let(Class.instance_method(:superclass), UnboundMethod)
|
15
|
+
OBJECT_ID_METHOD = T.let(BasicObject.instance_method(:__id__), UnboundMethod)
|
16
|
+
EQUAL_METHOD = T.let(BasicObject.instance_method(:equal?), UnboundMethod)
|
17
|
+
PUBLIC_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:public_instance_methods), UnboundMethod)
|
18
|
+
PROTECTED_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:protected_instance_methods), UnboundMethod)
|
19
|
+
PRIVATE_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:private_instance_methods), UnboundMethod)
|
20
|
+
METHOD_METHOD = T.let(Kernel.instance_method(:method), UnboundMethod)
|
21
|
+
|
22
|
+
sig { params(object: BasicObject).returns(Class).checked(:never) }
|
23
|
+
def class_of(object)
|
24
|
+
CLASS_METHOD.bind(object).call
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
28
|
+
def constants_of(constant)
|
29
|
+
CONSTANTS_METHOD.bind(constant).call(false)
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
33
|
+
def name_of(constant)
|
34
|
+
name = NAME_METHOD.bind(constant).call
|
35
|
+
name&.start_with?("#<") ? nil : name
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { params(constant: Module).returns(Class) }
|
39
|
+
def singleton_class_of(constant)
|
40
|
+
SINGLETON_CLASS_METHOD.bind(constant).call
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { params(constant: Module).returns(T::Array[Module]) }
|
44
|
+
def ancestors_of(constant)
|
45
|
+
ANCESTORS_METHOD.bind(constant).call
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { params(constant: Class).returns(T.nilable(Class)) }
|
49
|
+
def superclass_of(constant)
|
50
|
+
SUPERCLASS_METHOD.bind(constant).call
|
51
|
+
end
|
52
|
+
|
53
|
+
sig { params(object: BasicObject).returns(Integer).checked(:never) }
|
54
|
+
def object_id_of(object)
|
55
|
+
OBJECT_ID_METHOD.bind(object).call
|
56
|
+
end
|
57
|
+
|
58
|
+
sig { params(object: BasicObject, other: BasicObject).returns(T::Boolean).checked(:never) }
|
59
|
+
def are_equal?(object, other)
|
60
|
+
EQUAL_METHOD.bind(object).call(other)
|
61
|
+
end
|
62
|
+
|
63
|
+
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
64
|
+
def public_instance_methods_of(constant)
|
65
|
+
PUBLIC_INSTANCE_METHODS_METHOD.bind(constant).call
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
69
|
+
def protected_instance_methods_of(constant)
|
70
|
+
PROTECTED_INSTANCE_METHODS_METHOD.bind(constant).call
|
71
|
+
end
|
72
|
+
|
73
|
+
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
74
|
+
def private_instance_methods_of(constant)
|
75
|
+
PRIVATE_INSTANCE_METHODS_METHOD.bind(constant).call
|
76
|
+
end
|
77
|
+
|
78
|
+
sig { params(constant: Module).returns(T::Array[Module]) }
|
79
|
+
def inherited_ancestors_of(constant)
|
80
|
+
if Class === constant
|
81
|
+
ancestors_of(superclass_of(constant) || Object)
|
82
|
+
else
|
83
|
+
Module.ancestors
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
88
|
+
def qualified_name_of(constant)
|
89
|
+
name = name_of(constant)
|
90
|
+
return if name.nil?
|
91
|
+
|
92
|
+
if name.start_with?("::")
|
93
|
+
name
|
94
|
+
else
|
95
|
+
"::#{name}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
|
100
|
+
def signature_of(method)
|
101
|
+
T::Private::Methods.signature_for_method(method)
|
102
|
+
rescue LoadError, StandardError
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
|
106
|
+
sig { params(type: T::Types::Base).returns(String) }
|
107
|
+
def name_of_type(type)
|
108
|
+
type.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
|
109
|
+
end
|
110
|
+
|
111
|
+
sig { params(constant: Module, method: Symbol).returns(Method) }
|
112
|
+
def method_of(constant, method)
|
113
|
+
METHOD_METHOD.bind(constant).call(method)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns an array with all classes that are < than the supplied class.
|
117
|
+
#
|
118
|
+
# class C; end
|
119
|
+
# descendants_of(C) # => []
|
120
|
+
#
|
121
|
+
# class B < C; end
|
122
|
+
# descendants_of(C) # => [B]
|
123
|
+
#
|
124
|
+
# class A < B; end
|
125
|
+
# descendants_of(C) # => [B, A]
|
126
|
+
#
|
127
|
+
# class D < C; end
|
128
|
+
# descendants_of(C) # => [B, A, D]
|
129
|
+
sig { type_parameters(:U).params(klass: T.type_parameter(:U)).returns(T::Array[T.type_parameter(:U)]) }
|
130
|
+
def descendants_of(klass)
|
131
|
+
result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
|
132
|
+
T.cast(k, Module).singleton_class? || T.unsafe(k) == klass
|
133
|
+
end
|
134
|
+
|
135
|
+
T.unsafe(result)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|