tapioca 0.4.26 → 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 +138 -153
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
- 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 +2 -4
- data/lib/tapioca/generator.rb +124 -40
- 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 +11 -31
- 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 +34 -22
- 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 -86
- data/lib/tapioca/rbi/visitor.rb +0 -21
@@ -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,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
|
gemfile.require_bundle
|
@@ -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,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
|
@@ -0,0 +1,131 @@
|
|
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
|
+
|
21
|
+
sig { params(object: BasicObject).returns(Class).checked(:never) }
|
22
|
+
def class_of(object)
|
23
|
+
CLASS_METHOD.bind(object).call
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
27
|
+
def constants_of(constant)
|
28
|
+
CONSTANTS_METHOD.bind(constant).call(false)
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
32
|
+
def name_of(constant)
|
33
|
+
NAME_METHOD.bind(constant).call
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { params(constant: Module).returns(Class) }
|
37
|
+
def singleton_class_of(constant)
|
38
|
+
SINGLETON_CLASS_METHOD.bind(constant).call
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { params(constant: Module).returns(T::Array[Module]) }
|
42
|
+
def ancestors_of(constant)
|
43
|
+
ANCESTORS_METHOD.bind(constant).call
|
44
|
+
end
|
45
|
+
|
46
|
+
sig { params(constant: Class).returns(T.nilable(Class)) }
|
47
|
+
def superclass_of(constant)
|
48
|
+
SUPERCLASS_METHOD.bind(constant).call
|
49
|
+
end
|
50
|
+
|
51
|
+
sig { params(object: BasicObject).returns(Integer).checked(:never) }
|
52
|
+
def object_id_of(object)
|
53
|
+
OBJECT_ID_METHOD.bind(object).call
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { params(object: BasicObject, other: BasicObject).returns(T::Boolean).checked(:never) }
|
57
|
+
def are_equal?(object, other)
|
58
|
+
EQUAL_METHOD.bind(object).call(other)
|
59
|
+
end
|
60
|
+
|
61
|
+
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
62
|
+
def public_instance_methods_of(constant)
|
63
|
+
PUBLIC_INSTANCE_METHODS_METHOD.bind(constant).call
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
67
|
+
def protected_instance_methods_of(constant)
|
68
|
+
PROTECTED_INSTANCE_METHODS_METHOD.bind(constant).call
|
69
|
+
end
|
70
|
+
|
71
|
+
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
72
|
+
def private_instance_methods_of(constant)
|
73
|
+
PRIVATE_INSTANCE_METHODS_METHOD.bind(constant).call
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { params(constant: Module).returns(T::Array[Module]) }
|
77
|
+
def inherited_ancestors_of(constant)
|
78
|
+
if Class === constant
|
79
|
+
ancestors_of(superclass_of(constant) || Object)
|
80
|
+
else
|
81
|
+
Module.ancestors
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
86
|
+
def qualified_name_of(constant)
|
87
|
+
name = name_of(constant)
|
88
|
+
return if name.nil?
|
89
|
+
|
90
|
+
if name.start_with?("::")
|
91
|
+
name
|
92
|
+
else
|
93
|
+
"::#{name}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
|
98
|
+
def signature_of(method)
|
99
|
+
T::Private::Methods.signature_for_method(method)
|
100
|
+
rescue LoadError, StandardError
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
sig { params(type: T::Types::Base).returns(String) }
|
105
|
+
def name_of_type(type)
|
106
|
+
type.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns an array with all classes that are < than the supplied class.
|
110
|
+
#
|
111
|
+
# class C; end
|
112
|
+
# descendants_of(C) # => []
|
113
|
+
#
|
114
|
+
# class B < C; end
|
115
|
+
# descendants_of(C) # => [B]
|
116
|
+
#
|
117
|
+
# class A < B; end
|
118
|
+
# descendants_of(C) # => [B, A]
|
119
|
+
#
|
120
|
+
# class D < C; end
|
121
|
+
# descendants_of(C) # => [B, A, D]
|
122
|
+
sig { type_parameters(:U).params(klass: T.type_parameter(:U)).returns(T::Array[T.type_parameter(:U)]) }
|
123
|
+
def descendants_of(klass)
|
124
|
+
result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
|
125
|
+
T.cast(k, Module).singleton_class? || T.unsafe(k) == klass
|
126
|
+
end
|
127
|
+
|
128
|
+
T.unsafe(result)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|