tapioca 0.4.23 → 0.5.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 +14 -14
- data/README.md +2 -2
- data/Rakefile +5 -7
- data/exe/tapioca +2 -2
- data/lib/tapioca/cli.rb +256 -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_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_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 +108 -0
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
- data/lib/tapioca/compilers/dsl/base.rb +96 -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/protobuf.rb +19 -69
- data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
- data/lib/tapioca/compilers/dsl/smart_properties.rb +19 -31
- 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 +22 -38
- data/lib/tapioca/compilers/requires_compiler.rb +2 -2
- data/lib/tapioca/compilers/sorbet.rb +26 -5
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +139 -154
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
- data/lib/tapioca/compilers/symbol_table_compiler.rb +1 -1
- data/lib/tapioca/compilers/todos_compiler.rb +1 -1
- data/lib/tapioca/config.rb +2 -0
- data/lib/tapioca/config_builder.rb +4 -2
- data/lib/tapioca/constant_locator.rb +6 -8
- data/lib/tapioca/gemfile.rb +26 -19
- data/lib/tapioca/generator.rb +127 -43
- 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 +1 -9
- data/lib/tapioca/loader.rb +14 -48
- data/lib/tapioca/rbi_ext/model.rb +122 -0
- data/lib/tapioca/reflection.rb +131 -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 +2 -0
- metadata +35 -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/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 -82
- 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
@@ -4,22 +4,14 @@
|
|
4
4
|
require "tapioca"
|
5
5
|
require "tapioca/loader"
|
6
6
|
require "tapioca/constant_locator"
|
7
|
-
require "tapioca/generic_type_registry"
|
8
7
|
require "tapioca/sorbet_ext/generic_name_patch"
|
9
8
|
require "tapioca/sorbet_ext/fixed_hash_patch"
|
9
|
+
require "tapioca/generic_type_registry"
|
10
10
|
require "tapioca/config"
|
11
11
|
require "tapioca/config_builder"
|
12
12
|
require "tapioca/generator"
|
13
13
|
require "tapioca/cli"
|
14
|
-
require "tapioca/cli/main"
|
15
14
|
require "tapioca/gemfile"
|
16
|
-
require "tapioca/rbi/model"
|
17
|
-
require "tapioca/rbi/visitor"
|
18
|
-
require "tapioca/rbi/rewriters/nest_singleton_methods"
|
19
|
-
require "tapioca/rbi/rewriters/nest_non_public_methods"
|
20
|
-
require "tapioca/rbi/rewriters/group_nodes"
|
21
|
-
require "tapioca/rbi/rewriters/sort_nodes"
|
22
|
-
require "tapioca/rbi/printer"
|
23
15
|
require "tapioca/compilers/sorbet"
|
24
16
|
require "tapioca/compilers/requires_compiler"
|
25
17
|
require "tapioca/compilers/symbol_table_compiler"
|
data/lib/tapioca/loader.rb
CHANGED
@@ -5,19 +5,13 @@ 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
|
-
|
18
|
-
load_rake
|
12
|
+
gemfile.require_bundle
|
19
13
|
|
20
|
-
|
14
|
+
load_rails_application
|
21
15
|
|
22
16
|
require_helper(require_file)
|
23
17
|
|
@@ -25,14 +19,11 @@ module Tapioca
|
|
25
19
|
end
|
26
20
|
|
27
21
|
sig { params(environment_load: T::Boolean, eager_load: T::Boolean).void }
|
28
|
-
def
|
22
|
+
def load_rails_application(environment_load: false, eager_load: false)
|
29
23
|
return unless File.exist?("config/application.rb")
|
30
24
|
|
31
|
-
safe_require("rails")
|
32
|
-
|
33
25
|
silence_deprecations
|
34
26
|
|
35
|
-
safe_require("rails/generators/test_case")
|
36
27
|
if environment_load
|
37
28
|
safe_require("./config/environment")
|
38
29
|
else
|
@@ -44,9 +35,6 @@ module Tapioca
|
|
44
35
|
|
45
36
|
private
|
46
37
|
|
47
|
-
sig { returns(Tapioca::Gemfile) }
|
48
|
-
attr_reader :gemfile
|
49
|
-
|
50
38
|
sig { params(file: T.nilable(String)).void }
|
51
39
|
def require_helper(file)
|
52
40
|
return unless file
|
@@ -56,25 +44,12 @@ module Tapioca
|
|
56
44
|
require(file)
|
57
45
|
end
|
58
46
|
|
59
|
-
sig { void }
|
60
|
-
def require_bundle
|
61
|
-
gemfile.require
|
62
|
-
end
|
63
|
-
|
64
47
|
sig { returns(T::Array[T.untyped]) }
|
65
48
|
def rails_engines
|
66
|
-
|
67
|
-
|
68
|
-
return engines unless Object.const_defined?("Rails::Engine")
|
69
|
-
|
70
|
-
base = Object.const_get("Rails::Engine")
|
71
|
-
ObjectSpace.each_object(base.singleton_class) do |k|
|
72
|
-
k = T.cast(k, Class)
|
73
|
-
next if k.singleton_class?
|
74
|
-
engines.unshift(k) unless k == base
|
75
|
-
end
|
49
|
+
return [] unless Object.const_defined?("Rails::Engine")
|
76
50
|
|
77
|
-
|
51
|
+
# We can use `Class#descendants` here, since we know Rails is loaded
|
52
|
+
Object.const_get("Rails::Engine").descendants.reject(&:abstract_railtie?)
|
78
53
|
end
|
79
54
|
|
80
55
|
sig { params(path: String).void }
|
@@ -84,11 +59,6 @@ module Tapioca
|
|
84
59
|
nil
|
85
60
|
end
|
86
61
|
|
87
|
-
sig { void }
|
88
|
-
def load_rake
|
89
|
-
safe_require("rake")
|
90
|
-
end
|
91
|
-
|
92
62
|
sig { void }
|
93
63
|
def silence_deprecations
|
94
64
|
# Stop any ActiveSupport Deprecations from being reported
|
@@ -130,22 +100,18 @@ module Tapioca
|
|
130
100
|
|
131
101
|
engine.config.eager_load_paths.each do |load_path|
|
132
102
|
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
errored_files << file
|
137
|
-
end
|
103
|
+
require(file)
|
104
|
+
rescue LoadError, StandardError
|
105
|
+
errored_files << file
|
138
106
|
end
|
139
107
|
end
|
140
108
|
|
141
109
|
# Try files that have errored one more time
|
142
110
|
# It might have been a load order problem
|
143
111
|
errored_files.each do |file|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
nil
|
148
|
-
end
|
112
|
+
require(file)
|
113
|
+
rescue LoadError, StandardError
|
114
|
+
nil
|
149
115
|
end
|
150
116
|
end
|
151
117
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "rbi"
|
5
|
+
|
6
|
+
module RBI
|
7
|
+
class Tree
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(constant: ::Module, block: T.nilable(T.proc.params(scope: Scope).void)).void }
|
11
|
+
def create_path(constant, &block)
|
12
|
+
constant_name = Tapioca::Reflection.name_of(constant)
|
13
|
+
raise "given constant does not have a name" unless constant_name
|
14
|
+
|
15
|
+
instance = ::Module.const_get(constant_name)
|
16
|
+
case instance
|
17
|
+
when ::Class
|
18
|
+
create_class(constant.to_s, &block)
|
19
|
+
when ::Module
|
20
|
+
create_module(constant.to_s, &block)
|
21
|
+
else
|
22
|
+
raise "unexpected type: #{constant_name} is a #{instance.class}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { params(name: String, block: T.nilable(T.proc.params(scope: Scope).void)).void }
|
27
|
+
def create_module(name, &block)
|
28
|
+
node = create_node(RBI::Module.new(name))
|
29
|
+
block&.call(T.cast(node, RBI::Scope))
|
30
|
+
end
|
31
|
+
|
32
|
+
sig do
|
33
|
+
params(
|
34
|
+
name: String,
|
35
|
+
superclass_name: T.nilable(String),
|
36
|
+
block: T.nilable(T.proc.params(scope: RBI::Scope).void)
|
37
|
+
).void
|
38
|
+
end
|
39
|
+
def create_class(name, superclass_name: nil, &block)
|
40
|
+
node = create_node(RBI::Class.new(name, superclass_name: superclass_name))
|
41
|
+
block&.call(T.cast(node, RBI::Scope))
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(name: String, value: String).void }
|
45
|
+
def create_constant(name, value:)
|
46
|
+
create_node(RBI::Const.new(name, value))
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(name: String).void }
|
50
|
+
def create_include(name)
|
51
|
+
create_node(RBI::Include.new(name))
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(name: String).void }
|
55
|
+
def create_extend(name)
|
56
|
+
create_node(RBI::Extend.new(name))
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { params(name: String).void }
|
60
|
+
def create_mixes_in_class_methods(name)
|
61
|
+
create_node(RBI::MixesInClassMethods.new(name))
|
62
|
+
end
|
63
|
+
|
64
|
+
sig { params(name: String, value: String).void }
|
65
|
+
def create_type_member(name, value: "type_member")
|
66
|
+
create_node(RBI::TypeMember.new(name, value))
|
67
|
+
end
|
68
|
+
|
69
|
+
sig do
|
70
|
+
params(
|
71
|
+
name: String,
|
72
|
+
parameters: T::Array[TypedParam],
|
73
|
+
return_type: String,
|
74
|
+
class_method: T::Boolean
|
75
|
+
).void
|
76
|
+
end
|
77
|
+
def create_method(name, parameters: [], return_type: "T.untyped", class_method: false)
|
78
|
+
return unless valid_method_name?(name)
|
79
|
+
|
80
|
+
sig = RBI::Sig.new(return_type: return_type)
|
81
|
+
method = RBI::Method.new(name, sigs: [sig], is_singleton: class_method)
|
82
|
+
parameters.each do |param|
|
83
|
+
method << param.param
|
84
|
+
sig << RBI::SigParam.new(param.param.name, param.type)
|
85
|
+
end
|
86
|
+
self << method
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
SPECIAL_METHOD_NAMES = T.let(
|
92
|
+
["!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^", "<", "<=", "=>", ">", ">=",
|
93
|
+
"==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"].freeze,
|
94
|
+
T::Array[String]
|
95
|
+
)
|
96
|
+
|
97
|
+
sig { params(name: String).returns(T::Boolean) }
|
98
|
+
def valid_method_name?(name)
|
99
|
+
return true if SPECIAL_METHOD_NAMES.include?(name)
|
100
|
+
!!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
|
101
|
+
end
|
102
|
+
|
103
|
+
sig { returns(T::Hash[String, RBI::Node]) }
|
104
|
+
def nodes_cache
|
105
|
+
T.must(@nodes_cache ||= T.let({}, T.nilable(T::Hash[String, Node])))
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { params(node: RBI::Node).returns(RBI::Node) }
|
109
|
+
def create_node(node)
|
110
|
+
cached = nodes_cache[node.to_s]
|
111
|
+
return cached if cached
|
112
|
+
nodes_cache[node.to_s] = node
|
113
|
+
self << node
|
114
|
+
node
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class TypedParam < T::Struct
|
119
|
+
const :param, RBI::Param
|
120
|
+
const :type, String
|
121
|
+
end
|
122
|
+
end
|