tapioca 0.4.25 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 +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 +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 +10 -12
- data/lib/tapioca/generator.rb +129 -45
- 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 -10
- 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 +3 -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,108 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "tapioca/compilers/sorbet"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "active_support"
|
8
|
+
rescue LoadError
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
return unless Tapioca::Compilers::Sorbet.supports?(:mixes_in_class_methods_multiple_args)
|
13
|
+
|
14
|
+
module Tapioca
|
15
|
+
module Compilers
|
16
|
+
module Dsl
|
17
|
+
# `Tapioca::Compilers::Dsl::ActiveSupportConcern` generates RBI files for classes that both `extend`
|
18
|
+
# `ActiveSupport::Concern` and `include` another class that extends `ActiveSupport::Concern`
|
19
|
+
#
|
20
|
+
# For example for the following hierarchy:
|
21
|
+
#
|
22
|
+
# ~~~rb
|
23
|
+
# # concern.rb
|
24
|
+
# module Foo
|
25
|
+
# extend ActiveSupport::Concern
|
26
|
+
# module ClassMethods; end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# module Bar
|
30
|
+
# extend ActiveSupport::Concern
|
31
|
+
# module ClassMethods; end
|
32
|
+
# include Foo
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# class Baz
|
36
|
+
# include Bar
|
37
|
+
# end
|
38
|
+
# ~~~
|
39
|
+
#
|
40
|
+
# this generator will produce the RBI file `concern.rbi` with the following content:
|
41
|
+
#
|
42
|
+
# ~~~rbi
|
43
|
+
# # typed: true
|
44
|
+
# module Bar
|
45
|
+
# mixes_in_class_methods(::Foo::ClassMethods)
|
46
|
+
# end
|
47
|
+
# ~~~
|
48
|
+
class ActiveSupportConcern < Base
|
49
|
+
extend T::Sig
|
50
|
+
|
51
|
+
sig { override.params(root: RBI::Tree, constant: Module).void }
|
52
|
+
def decorate(root, constant)
|
53
|
+
dependencies = linearized_dependencies_of(constant)
|
54
|
+
|
55
|
+
mixed_in_class_methods = dependencies
|
56
|
+
.uniq # Deduplicate
|
57
|
+
.map do |concern| # Map to class methods module name, if exists
|
58
|
+
"#{qualified_name_of(concern)}::ClassMethods" if concern.const_defined?(:ClassMethods)
|
59
|
+
end
|
60
|
+
.compact # Remove non-existent records
|
61
|
+
|
62
|
+
return if mixed_in_class_methods.empty?
|
63
|
+
|
64
|
+
root.create_path(constant) do |mod|
|
65
|
+
mixed_in_class_methods.each do |mix|
|
66
|
+
mod.create_mixes_in_class_methods(mix)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
sig { override.returns(T::Enumerable[Module]) }
|
72
|
+
def gather_constants
|
73
|
+
# Find all Modules that are:
|
74
|
+
all_modules.select do |mod|
|
75
|
+
# named (i.e. not anonymous)
|
76
|
+
name_of(mod) &&
|
77
|
+
# not singleton classes
|
78
|
+
!mod.singleton_class? &&
|
79
|
+
# extend ActiveSupport::Concern, and
|
80
|
+
mod.singleton_class < ActiveSupport::Concern &&
|
81
|
+
# have dependencies (i.e. include another concern)
|
82
|
+
!dependencies_of(mod).empty?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
sig { params(concern: Module).returns(T::Array[Module]) }
|
89
|
+
def dependencies_of(concern)
|
90
|
+
concern.instance_variable_get(:@_dependencies)
|
91
|
+
end
|
92
|
+
|
93
|
+
sig { params(concern: Module).returns(T::Array[Module]) }
|
94
|
+
def linearized_dependencies_of(concern)
|
95
|
+
# Grab all the dependencies of the concern
|
96
|
+
dependencies = dependencies_of(concern)
|
97
|
+
|
98
|
+
# Flatten this concern's dependencies and all of their dependencies
|
99
|
+
dependencies.flat_map do |dependency|
|
100
|
+
# Linearize dependencies of the current dependency,
|
101
|
+
# which, itself, is a concern
|
102
|
+
linearized_dependencies_of(dependency) << dependency
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "parlour"
|
5
|
-
|
6
4
|
begin
|
7
5
|
require "active_support"
|
6
|
+
# The following is needed due to https://github.com/rails/rails/pull/41610
|
7
|
+
require "active_support/core_ext/module/delegation"
|
8
8
|
rescue LoadError
|
9
9
|
return
|
10
10
|
end
|
@@ -65,7 +65,7 @@ module Tapioca
|
|
65
65
|
sig do
|
66
66
|
override
|
67
67
|
.params(
|
68
|
-
root:
|
68
|
+
root: RBI::Tree,
|
69
69
|
constant: T.class_of(::ActiveSupport::CurrentAttributes)
|
70
70
|
)
|
71
71
|
.void
|
@@ -75,7 +75,7 @@ module Tapioca
|
|
75
75
|
instance_methods = instance_methods_for(constant) - dynamic_methods
|
76
76
|
return if dynamic_methods.empty? && instance_methods.empty?
|
77
77
|
|
78
|
-
root.
|
78
|
+
root.create_path(constant) do |current_attributes|
|
79
79
|
dynamic_methods.each do |method|
|
80
80
|
method = method.to_s
|
81
81
|
# We want to generate each method both on the class
|
@@ -95,7 +95,7 @@ module Tapioca
|
|
95
95
|
|
96
96
|
sig { override.returns(T::Enumerable[Module]) }
|
97
97
|
def gather_constants
|
98
|
-
::ActiveSupport::CurrentAttributes
|
98
|
+
descendants_of(::ActiveSupport::CurrentAttributes)
|
99
99
|
end
|
100
100
|
|
101
101
|
private
|
@@ -110,11 +110,16 @@ module Tapioca
|
|
110
110
|
constant.instance_methods(false)
|
111
111
|
end
|
112
112
|
|
113
|
-
sig { params(klass:
|
113
|
+
sig { params(klass: RBI::Scope, method: String, class_method: T::Boolean).void }
|
114
114
|
def generate_method(klass, method, class_method:)
|
115
115
|
if method.end_with?("=")
|
116
|
-
parameter =
|
117
|
-
klass.create_method(
|
116
|
+
parameter = create_param("value", type: "T.untyped")
|
117
|
+
klass.create_method(
|
118
|
+
method,
|
119
|
+
class_method: class_method,
|
120
|
+
parameters: [parameter],
|
121
|
+
return_type: "T.untyped"
|
122
|
+
)
|
118
123
|
else
|
119
124
|
klass.create_method(method, class_method: class_method, return_type: "T.untyped")
|
120
125
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "
|
4
|
+
require "tapioca/rbi_ext/model"
|
5
5
|
|
6
6
|
module Tapioca
|
7
7
|
module Compilers
|
@@ -10,6 +10,8 @@ module Tapioca
|
|
10
10
|
extend T::Sig
|
11
11
|
extend T::Helpers
|
12
12
|
|
13
|
+
include Reflection
|
14
|
+
|
13
15
|
abstract!
|
14
16
|
|
15
17
|
sig { returns(T::Set[Module]) }
|
@@ -29,65 +31,111 @@ module Tapioca
|
|
29
31
|
abstract
|
30
32
|
.type_parameters(:T)
|
31
33
|
.params(
|
32
|
-
|
34
|
+
tree: RBI::Tree,
|
33
35
|
constant: T.type_parameter(:T)
|
34
36
|
)
|
35
37
|
.void
|
36
38
|
end
|
37
|
-
def decorate(
|
39
|
+
def decorate(tree, constant); end
|
38
40
|
|
39
41
|
sig { abstract.returns(T::Enumerable[Module]) }
|
40
42
|
def gather_constants; end
|
41
43
|
|
42
44
|
private
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
T::
|
47
|
-
|
48
|
-
|
49
|
-
sig { params(name: String).returns(T::Boolean) }
|
50
|
-
def valid_method_name?(name)
|
51
|
-
return true if SPECIAL_METHOD_NAMES.include?(name)
|
52
|
-
!!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
|
46
|
+
sig { returns(T::Enumerable[Class]) }
|
47
|
+
def all_classes
|
48
|
+
@all_classes = T.let(@all_classes, T.nilable(T::Enumerable[Class]))
|
49
|
+
@all_classes ||= T.cast(ObjectSpace.each_object(Class), T::Enumerable[Class]).each
|
53
50
|
end
|
54
51
|
|
55
|
-
sig
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
options: T::Hash[T.untyped, T.untyped]
|
60
|
-
).void
|
61
|
-
end
|
62
|
-
def create_method(namespace, name, options = {})
|
63
|
-
return unless valid_method_name?(name)
|
64
|
-
T.unsafe(namespace).create_method(name, **options)
|
52
|
+
sig { returns(T::Enumerable[Module]) }
|
53
|
+
def all_modules
|
54
|
+
@all_modules = T.let(@all_modules, T.nilable(T::Enumerable[Module]))
|
55
|
+
@all_modules ||= T.cast(ObjectSpace.each_object(Module), T::Enumerable[Module]).each
|
65
56
|
end
|
66
57
|
|
67
|
-
#
|
58
|
+
# Get the types of each parameter from a method signature
|
68
59
|
sig do
|
69
60
|
params(
|
70
|
-
namespace: Parlour::RbiGenerator::Namespace,
|
71
61
|
method_def: T.any(Method, UnboundMethod),
|
72
|
-
|
73
|
-
).
|
62
|
+
signature: T.untyped # as `T::Private::Methods::Signature` is private
|
63
|
+
).returns(T::Array[String])
|
74
64
|
end
|
75
|
-
def
|
76
|
-
|
77
|
-
|
65
|
+
def parameters_types_from_signature(method_def, signature)
|
66
|
+
params = T.let([], T::Array[String])
|
67
|
+
|
68
|
+
return method_def.parameters.map { "T.untyped" } unless signature
|
69
|
+
|
70
|
+
# parameters types
|
71
|
+
signature.arg_types.each { |arg_type| params << arg_type[1].to_s }
|
72
|
+
|
73
|
+
# keyword parameters types
|
74
|
+
signature.kwarg_types.each { |_, kwarg_type| params << kwarg_type.to_s }
|
75
|
+
|
76
|
+
# rest parameter type
|
77
|
+
params << signature.rest_type.to_s if signature.has_rest
|
78
|
+
|
79
|
+
# special case `.void` in a proc
|
80
|
+
unless signature.block_name.nil?
|
81
|
+
params << signature.block_type.to_s.gsub("returns(<VOID>)", "void")
|
82
|
+
end
|
83
|
+
|
84
|
+
params
|
85
|
+
end
|
86
|
+
|
87
|
+
sig { params(scope: RBI::Scope, method_def: T.any(Method, UnboundMethod), class_method: T::Boolean).void }
|
88
|
+
def create_method_from_def(scope, method_def, class_method: false)
|
89
|
+
scope.create_method(
|
78
90
|
method_def.name.to_s,
|
79
|
-
parameters:
|
80
|
-
return_type:
|
91
|
+
parameters: compile_method_parameters_to_rbi(method_def),
|
92
|
+
return_type: compile_method_return_type_to_rbi(method_def),
|
81
93
|
class_method: class_method
|
82
94
|
)
|
83
95
|
end
|
84
96
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
97
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
98
|
+
def create_param(name, type:)
|
99
|
+
create_typed_param(RBI::Param.new(name), type)
|
100
|
+
end
|
101
|
+
|
102
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
103
|
+
def create_opt_param(name, type:, default:)
|
104
|
+
create_typed_param(RBI::OptParam.new(name, default), type)
|
105
|
+
end
|
106
|
+
|
107
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
108
|
+
def create_rest_param(name, type:)
|
109
|
+
create_typed_param(RBI::RestParam.new(name), type)
|
110
|
+
end
|
111
|
+
|
112
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
113
|
+
def create_kw_param(name, type:)
|
114
|
+
create_typed_param(RBI::KwParam.new(name), type)
|
115
|
+
end
|
116
|
+
|
117
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
118
|
+
def create_kw_opt_param(name, type:, default:)
|
119
|
+
create_typed_param(RBI::KwOptParam.new(name, default), type)
|
89
120
|
end
|
90
|
-
|
121
|
+
|
122
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
123
|
+
def create_kw_rest_param(name, type:)
|
124
|
+
create_typed_param(RBI::KwRestParam.new(name), type)
|
125
|
+
end
|
126
|
+
|
127
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
128
|
+
def create_block_param(name, type:)
|
129
|
+
create_typed_param(RBI::BlockParam.new(name), type)
|
130
|
+
end
|
131
|
+
|
132
|
+
sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
|
133
|
+
def create_typed_param(param, type)
|
134
|
+
RBI::TypedParam.new(param: param, type: type)
|
135
|
+
end
|
136
|
+
|
137
|
+
sig { params(method_def: T.any(Method, UnboundMethod)).returns(T::Array[RBI::TypedParam]) }
|
138
|
+
def compile_method_parameters_to_rbi(method_def)
|
91
139
|
signature = T::Private::Methods.signature_for_method(method_def)
|
92
140
|
method_def = signature.nil? ? method_def : signature.method
|
93
141
|
method_types = parameters_types_from_signature(method_def, signature)
|
@@ -97,72 +145,38 @@ module Tapioca
|
|
97
145
|
|
98
146
|
name ||= fallback_arg_name
|
99
147
|
name = name.to_s.gsub(/&|\*/, fallback_arg_name) # avoid incorrect names from `delegate`
|
100
|
-
method_type = method_types[index]
|
148
|
+
method_type = T.must(method_types[index])
|
101
149
|
|
102
150
|
case type
|
103
151
|
when :req
|
104
|
-
|
152
|
+
create_param(name, type: method_type)
|
105
153
|
when :opt
|
106
|
-
|
154
|
+
create_opt_param(name, type: method_type, default: "T.unsafe(nil)")
|
107
155
|
when :rest
|
108
|
-
|
156
|
+
create_rest_param(name, type: method_type)
|
109
157
|
when :keyreq
|
110
|
-
|
158
|
+
create_kw_param(name, type: method_type)
|
111
159
|
when :key
|
112
|
-
|
160
|
+
create_kw_opt_param(name, type: method_type, default: "T.unsafe(nil)")
|
113
161
|
when :keyrest
|
114
|
-
|
162
|
+
create_kw_rest_param(name, type: method_type)
|
115
163
|
when :block
|
116
|
-
|
164
|
+
create_block_param(name, type: method_type)
|
117
165
|
else
|
118
166
|
raise "Unknown type `#{type}`."
|
119
167
|
end
|
120
168
|
end
|
121
169
|
end
|
122
170
|
|
123
|
-
|
124
|
-
|
125
|
-
params(method_def: T.any(Method, UnboundMethod))
|
126
|
-
.returns(T.nilable(String))
|
127
|
-
end
|
128
|
-
def compile_method_return_type_to_parlour(method_def)
|
171
|
+
sig { params(method_def: T.any(Method, UnboundMethod)).returns(String) }
|
172
|
+
def compile_method_return_type_to_rbi(method_def)
|
129
173
|
signature = T::Private::Methods.signature_for_method(method_def)
|
130
|
-
return_type = signature.nil? ?
|
131
|
-
|
132
|
-
return_type = nil if return_type == "<VOID>"
|
174
|
+
return_type = signature.nil? ? "T.untyped" : name_of_type(signature.return_type)
|
175
|
+
return_type = "void" if return_type == "<VOID>"
|
133
176
|
# Map <NOT-TYPED> to `T.untyped`
|
134
177
|
return_type = "T.untyped" if return_type == "<NOT-TYPED>"
|
135
178
|
return_type
|
136
179
|
end
|
137
|
-
|
138
|
-
# Get the types of each parameter from a method signature
|
139
|
-
sig do
|
140
|
-
params(
|
141
|
-
method_def: T.any(Method, UnboundMethod),
|
142
|
-
signature: T.untyped # as `T::Private::Methods::Signature` is private
|
143
|
-
).returns(T::Array[String])
|
144
|
-
end
|
145
|
-
def parameters_types_from_signature(method_def, signature)
|
146
|
-
params = T.let([], T::Array[String])
|
147
|
-
|
148
|
-
return method_def.parameters.map { 'T.untyped' } unless signature
|
149
|
-
|
150
|
-
# parameters types
|
151
|
-
signature.arg_types.each { |arg_type| params << arg_type[1].to_s }
|
152
|
-
|
153
|
-
# keyword parameters types
|
154
|
-
signature.kwarg_types.each { |_, kwarg_type| params << kwarg_type.to_s }
|
155
|
-
|
156
|
-
# rest parameter type
|
157
|
-
params << signature.rest_type.to_s if signature.has_rest
|
158
|
-
|
159
|
-
# special case `.void` in a proc
|
160
|
-
unless signature.block_name.nil?
|
161
|
-
params << signature.block_type.to_s.gsub('returns(<VOID>)', 'void')
|
162
|
-
end
|
163
|
-
|
164
|
-
params
|
165
|
-
end
|
166
180
|
end
|
167
181
|
end
|
168
182
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "config"
|
6
|
+
rescue LoadError
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
module Tapioca
|
11
|
+
module Compilers
|
12
|
+
module Dsl
|
13
|
+
# `Tapioca::Compilers::Dsl::Config` generates RBI files for classes generated by the
|
14
|
+
# [`config`](https://github.com/rubyconfig/config) gem.
|
15
|
+
#
|
16
|
+
# The gem creates a `Config::Options` instance based on the settings files and/or
|
17
|
+
# env variables. It then assigns this instance to a constant with a configurable name,
|
18
|
+
# by default `Settings`. Application code uses methods on this constant to read off
|
19
|
+
# config values.
|
20
|
+
#
|
21
|
+
# For a setting file like the following:
|
22
|
+
# ```yaml
|
23
|
+
# ---
|
24
|
+
# github:
|
25
|
+
# token: 12345
|
26
|
+
# client_id: 54321
|
27
|
+
# client_secret: super_secret
|
28
|
+
# ```
|
29
|
+
# and a `Config` setup like:
|
30
|
+
# ```ruby
|
31
|
+
# Config.setup do |config|
|
32
|
+
# config.const_name = "AppSettings"
|
33
|
+
# end
|
34
|
+
# ```
|
35
|
+
# this generator will produce the following RBI file:
|
36
|
+
# ```rbi
|
37
|
+
# AppSettings = T.let(T.unsafe(nil), AppSettingsConfigOptions)
|
38
|
+
#
|
39
|
+
# class AppSettingsConfigOptions < ::Config::Options
|
40
|
+
# sig { returns(T.untyped) }
|
41
|
+
# def github; end
|
42
|
+
#
|
43
|
+
# sig { params(value: T.untyped).returns(T.untyped) }
|
44
|
+
# def github=(value); end
|
45
|
+
# end
|
46
|
+
# ```
|
47
|
+
class Config < Base
|
48
|
+
extend T::Sig
|
49
|
+
|
50
|
+
CONFIG_OPTIONS_SUFFIX = "ConfigOptions"
|
51
|
+
|
52
|
+
sig { override.params(root: RBI::Tree, constant: Module).void }
|
53
|
+
def decorate(root, constant)
|
54
|
+
# The constant we are given is the specialized config options type
|
55
|
+
option_class_name = constant.name
|
56
|
+
return unless option_class_name
|
57
|
+
|
58
|
+
# Grab the config constant name and the actual config constant
|
59
|
+
config_constant_name = option_class_name
|
60
|
+
.gsub(/#{CONFIG_OPTIONS_SUFFIX}$/, "")
|
61
|
+
config_constant = Object.const_get(config_constant_name)
|
62
|
+
|
63
|
+
# Look up method names from the keys of the config constant
|
64
|
+
method_names = config_constant.keys
|
65
|
+
|
66
|
+
return if method_names.empty?
|
67
|
+
|
68
|
+
root.create_constant(config_constant_name, value: "T.let(T.unsafe(nil), #{option_class_name})")
|
69
|
+
|
70
|
+
root.create_class(option_class_name, superclass_name: "::Config::Options") do |mod|
|
71
|
+
# We need this to be generic only becuase `Config::Options` is an
|
72
|
+
# enumerable and, thus, needs to redeclare the `Elem` type member.
|
73
|
+
#
|
74
|
+
# We declare it as a fixed member of `T.untyped` so that if anyone
|
75
|
+
# enumerates the entries, we don't make any assumptions about their
|
76
|
+
# types.
|
77
|
+
mod.create_extend("T::Generic")
|
78
|
+
mod.create_type_member("Elem", value: "type_member(fixed: T.untyped)")
|
79
|
+
|
80
|
+
method_names.each do |method_name|
|
81
|
+
# Create getter method
|
82
|
+
mod.create_method(
|
83
|
+
method_name.to_s,
|
84
|
+
return_type: "T.untyped"
|
85
|
+
)
|
86
|
+
|
87
|
+
# Create setter method
|
88
|
+
mod.create_method(
|
89
|
+
"#{method_name}=",
|
90
|
+
parameters: [create_param("value", type: "T.untyped")],
|
91
|
+
return_type: "T.untyped"
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { override.returns(T::Enumerable[Module]) }
|
98
|
+
def gather_constants
|
99
|
+
name = ::Config.const_name
|
100
|
+
return [] unless Object.const_defined?(name)
|
101
|
+
|
102
|
+
config_object = Object.const_get(name)
|
103
|
+
options_class_name = "#{name}#{CONFIG_OPTIONS_SUFFIX}"
|
104
|
+
Object.const_set(options_class_name, config_object.singleton_class)
|
105
|
+
|
106
|
+
Array(config_object.singleton_class)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|