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
@@ -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
|