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
@@ -1,23 +1,31 @@
|
|
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
|
8
8
|
module Dsl
|
9
|
+
COMPILERS_PATH = T.let(File.expand_path("..", __FILE__).to_s, String)
|
10
|
+
|
9
11
|
class Base
|
10
12
|
extend T::Sig
|
11
13
|
extend T::Helpers
|
12
14
|
|
15
|
+
include Reflection
|
16
|
+
|
13
17
|
abstract!
|
14
18
|
|
15
19
|
sig { returns(T::Set[Module]) }
|
16
20
|
attr_reader :processable_constants
|
17
21
|
|
22
|
+
sig { returns(T::Array[String]) }
|
23
|
+
attr_reader :errors
|
24
|
+
|
18
25
|
sig { void }
|
19
26
|
def initialize
|
20
27
|
@processable_constants = T.let(Set.new(gather_constants), T::Set[Module])
|
28
|
+
@errors = T.let([], T::Array[String])
|
21
29
|
end
|
22
30
|
|
23
31
|
sig { params(constant: Module).returns(T::Boolean) }
|
@@ -29,65 +37,117 @@ module Tapioca
|
|
29
37
|
abstract
|
30
38
|
.type_parameters(:T)
|
31
39
|
.params(
|
32
|
-
|
40
|
+
tree: RBI::Tree,
|
33
41
|
constant: T.type_parameter(:T)
|
34
42
|
)
|
35
43
|
.void
|
36
44
|
end
|
37
|
-
def decorate(
|
45
|
+
def decorate(tree, constant); end
|
38
46
|
|
39
47
|
sig { abstract.returns(T::Enumerable[Module]) }
|
40
48
|
def gather_constants; end
|
41
49
|
|
42
|
-
|
50
|
+
# NOTE: This should eventually accept an `Error` object or `Exception` rather than simply a `String`.
|
51
|
+
sig { params(error: String).void }
|
52
|
+
def add_error(error)
|
53
|
+
@errors << error
|
54
|
+
end
|
43
55
|
|
44
|
-
|
45
|
-
%w[! ~ +@ ** -@ * / % + - << >> & | ^ < <= => > >= == === != =~ !~ <=> [] []= `].freeze,
|
46
|
-
T::Array[String]
|
47
|
-
)
|
56
|
+
private
|
48
57
|
|
49
|
-
sig {
|
50
|
-
def
|
51
|
-
|
52
|
-
|
58
|
+
sig { returns(T::Enumerable[Class]) }
|
59
|
+
def all_classes
|
60
|
+
@all_classes = T.let(@all_classes, T.nilable(T::Enumerable[Class]))
|
61
|
+
@all_classes ||= T.cast(ObjectSpace.each_object(Class), T::Enumerable[Class]).each
|
53
62
|
end
|
54
63
|
|
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)
|
64
|
+
sig { returns(T::Enumerable[Module]) }
|
65
|
+
def all_modules
|
66
|
+
@all_modules = T.let(@all_modules, T.nilable(T::Enumerable[Module]))
|
67
|
+
@all_modules ||= T.cast(ObjectSpace.each_object(Module), T::Enumerable[Module]).each
|
65
68
|
end
|
66
69
|
|
67
|
-
#
|
70
|
+
# Get the types of each parameter from a method signature
|
68
71
|
sig do
|
69
72
|
params(
|
70
|
-
namespace: Parlour::RbiGenerator::Namespace,
|
71
73
|
method_def: T.any(Method, UnboundMethod),
|
72
|
-
|
73
|
-
).
|
74
|
+
signature: T.untyped # as `T::Private::Methods::Signature` is private
|
75
|
+
).returns(T::Array[String])
|
74
76
|
end
|
75
|
-
def
|
76
|
-
|
77
|
-
|
77
|
+
def parameters_types_from_signature(method_def, signature)
|
78
|
+
params = T.let([], T::Array[String])
|
79
|
+
|
80
|
+
return method_def.parameters.map { "T.untyped" } unless signature
|
81
|
+
|
82
|
+
# parameters types
|
83
|
+
signature.arg_types.each { |arg_type| params << arg_type[1].to_s }
|
84
|
+
|
85
|
+
# keyword parameters types
|
86
|
+
signature.kwarg_types.each { |_, kwarg_type| params << kwarg_type.to_s }
|
87
|
+
|
88
|
+
# rest parameter type
|
89
|
+
params << signature.rest_type.to_s if signature.has_rest
|
90
|
+
|
91
|
+
# special case `.void` in a proc
|
92
|
+
unless signature.block_name.nil?
|
93
|
+
params << signature.block_type.to_s.gsub("returns(<VOID>)", "void")
|
94
|
+
end
|
95
|
+
|
96
|
+
params
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { params(scope: RBI::Scope, method_def: T.any(Method, UnboundMethod), class_method: T::Boolean).void }
|
100
|
+
def create_method_from_def(scope, method_def, class_method: false)
|
101
|
+
scope.create_method(
|
78
102
|
method_def.name.to_s,
|
79
|
-
parameters:
|
80
|
-
return_type:
|
103
|
+
parameters: compile_method_parameters_to_rbi(method_def),
|
104
|
+
return_type: compile_method_return_type_to_rbi(method_def),
|
81
105
|
class_method: class_method
|
82
106
|
)
|
83
107
|
end
|
84
108
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
109
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
110
|
+
def create_param(name, type:)
|
111
|
+
create_typed_param(RBI::Param.new(name), type)
|
112
|
+
end
|
113
|
+
|
114
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
115
|
+
def create_opt_param(name, type:, default:)
|
116
|
+
create_typed_param(RBI::OptParam.new(name, default), type)
|
117
|
+
end
|
118
|
+
|
119
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
120
|
+
def create_rest_param(name, type:)
|
121
|
+
create_typed_param(RBI::RestParam.new(name), type)
|
122
|
+
end
|
123
|
+
|
124
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
125
|
+
def create_kw_param(name, type:)
|
126
|
+
create_typed_param(RBI::KwParam.new(name), type)
|
89
127
|
end
|
90
|
-
|
128
|
+
|
129
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
130
|
+
def create_kw_opt_param(name, type:, default:)
|
131
|
+
create_typed_param(RBI::KwOptParam.new(name, default), type)
|
132
|
+
end
|
133
|
+
|
134
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
135
|
+
def create_kw_rest_param(name, type:)
|
136
|
+
create_typed_param(RBI::KwRestParam.new(name), type)
|
137
|
+
end
|
138
|
+
|
139
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
140
|
+
def create_block_param(name, type:)
|
141
|
+
create_typed_param(RBI::BlockParam.new(name), type)
|
142
|
+
end
|
143
|
+
|
144
|
+
sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
|
145
|
+
def create_typed_param(param, type)
|
146
|
+
RBI::TypedParam.new(param: param, type: type)
|
147
|
+
end
|
148
|
+
|
149
|
+
sig { params(method_def: T.any(Method, UnboundMethod)).returns(T::Array[RBI::TypedParam]) }
|
150
|
+
def compile_method_parameters_to_rbi(method_def)
|
91
151
|
signature = T::Private::Methods.signature_for_method(method_def)
|
92
152
|
method_def = signature.nil? ? method_def : signature.method
|
93
153
|
method_types = parameters_types_from_signature(method_def, signature)
|
@@ -97,72 +157,38 @@ module Tapioca
|
|
97
157
|
|
98
158
|
name ||= fallback_arg_name
|
99
159
|
name = name.to_s.gsub(/&|\*/, fallback_arg_name) # avoid incorrect names from `delegate`
|
100
|
-
method_type = method_types[index]
|
160
|
+
method_type = T.must(method_types[index])
|
101
161
|
|
102
162
|
case type
|
103
163
|
when :req
|
104
|
-
|
164
|
+
create_param(name, type: method_type)
|
105
165
|
when :opt
|
106
|
-
|
166
|
+
create_opt_param(name, type: method_type, default: "T.unsafe(nil)")
|
107
167
|
when :rest
|
108
|
-
|
168
|
+
create_rest_param(name, type: method_type)
|
109
169
|
when :keyreq
|
110
|
-
|
170
|
+
create_kw_param(name, type: method_type)
|
111
171
|
when :key
|
112
|
-
|
172
|
+
create_kw_opt_param(name, type: method_type, default: "T.unsafe(nil)")
|
113
173
|
when :keyrest
|
114
|
-
|
174
|
+
create_kw_rest_param(name, type: method_type)
|
115
175
|
when :block
|
116
|
-
|
176
|
+
create_block_param(name, type: method_type)
|
117
177
|
else
|
118
178
|
raise "Unknown type `#{type}`."
|
119
179
|
end
|
120
180
|
end
|
121
181
|
end
|
122
182
|
|
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)
|
183
|
+
sig { params(method_def: T.any(Method, UnboundMethod)).returns(String) }
|
184
|
+
def compile_method_return_type_to_rbi(method_def)
|
129
185
|
signature = T::Private::Methods.signature_for_method(method_def)
|
130
|
-
return_type = signature.nil? ?
|
131
|
-
|
132
|
-
return_type = nil if return_type == "<VOID>"
|
186
|
+
return_type = signature.nil? ? "T.untyped" : name_of_type(signature.return_type)
|
187
|
+
return_type = "void" if return_type == "<VOID>"
|
133
188
|
# Map <NOT-TYPED> to `T.untyped`
|
134
189
|
return_type = "T.untyped" if return_type == "<NOT-TYPED>"
|
135
190
|
return_type
|
136
191
|
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
192
|
end
|
167
193
|
end
|
168
194
|
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
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "parlour"
|
5
|
-
|
6
4
|
begin
|
7
5
|
require "frozen_record"
|
8
6
|
rescue LoadError
|
@@ -67,18 +65,18 @@ module Tapioca
|
|
67
65
|
class FrozenRecord < Base
|
68
66
|
extend T::Sig
|
69
67
|
|
70
|
-
sig { override.params(root:
|
68
|
+
sig { override.params(root: RBI::Tree, constant: T.class_of(::FrozenRecord::Base)).void }
|
71
69
|
def decorate(root, constant)
|
72
70
|
attributes = constant.attributes
|
73
71
|
return if attributes.empty?
|
74
72
|
|
75
|
-
root.
|
73
|
+
root.create_path(constant) do |record|
|
76
74
|
module_name = "FrozenRecordAttributeMethods"
|
77
75
|
|
78
76
|
record.create_module(module_name) do |mod|
|
79
77
|
attributes.each do |attribute|
|
80
|
-
create_method(
|
81
|
-
create_method(
|
78
|
+
mod.create_method("#{attribute}?", return_type: "T::Boolean")
|
79
|
+
mod.create_method(attribute.to_s, return_type: "T.untyped")
|
82
80
|
end
|
83
81
|
end
|
84
82
|
|
@@ -88,7 +86,7 @@ module Tapioca
|
|
88
86
|
|
89
87
|
sig { override.returns(T::Enumerable[Module]) }
|
90
88
|
def gather_constants
|
91
|
-
::FrozenRecord::Base.
|
89
|
+
descendants_of(::FrozenRecord::Base).reject(&:abstract_class?)
|
92
90
|
end
|
93
91
|
end
|
94
92
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "parlour"
|
5
|
-
|
6
4
|
begin
|
7
5
|
require "rails/railtie"
|
8
6
|
require "identity_cache"
|
@@ -67,23 +65,16 @@ module Tapioca
|
|
67
65
|
|
68
66
|
COLLECTION_TYPE = T.let(
|
69
67
|
->(type) { "T::Array[::#{type}]" },
|
70
|
-
T.proc.params(type: Module).returns(String)
|
68
|
+
T.proc.params(type: T.any(Module, String)).returns(String)
|
71
69
|
)
|
72
70
|
|
73
|
-
sig
|
74
|
-
override
|
75
|
-
.params(
|
76
|
-
root: Parlour::RbiGenerator::Namespace,
|
77
|
-
constant: T.class_of(::ActiveRecord::Base)
|
78
|
-
)
|
79
|
-
.void
|
80
|
-
end
|
71
|
+
sig { override.params(root: RBI::Tree, constant: T.class_of(::ActiveRecord::Base)).void }
|
81
72
|
def decorate(root, constant)
|
82
73
|
caches = constant.send(:all_cached_associations)
|
83
74
|
cache_indexes = constant.send(:cache_indexes)
|
84
75
|
return if caches.empty? && cache_indexes.empty?
|
85
76
|
|
86
|
-
root.
|
77
|
+
root.create_path(constant) do |model|
|
87
78
|
cache_manys = constant.send(:cached_has_manys)
|
88
79
|
cache_ones = constant.send(:cached_has_ones)
|
89
80
|
cache_belongs = constant.send(:cached_belongs_tos)
|
@@ -108,7 +99,7 @@ module Tapioca
|
|
108
99
|
|
109
100
|
sig { override.returns(T::Enumerable[Module]) }
|
110
101
|
def gather_constants
|
111
|
-
::ActiveRecord::Base.
|
102
|
+
descendants_of(::ActiveRecord::Base).select do |klass|
|
112
103
|
klass < ::IdentityCache::WithoutPrimaryIndex
|
113
104
|
end
|
114
105
|
end
|
@@ -119,8 +110,7 @@ module Tapioca
|
|
119
110
|
params(
|
120
111
|
field: T.untyped,
|
121
112
|
returns_collection: T::Boolean
|
122
|
-
)
|
123
|
-
.returns(String)
|
113
|
+
).returns(String)
|
124
114
|
end
|
125
115
|
def type_for_field(field, returns_collection:)
|
126
116
|
cache_type = field.reflection.compute_class(field.reflection.class_name)
|
@@ -136,10 +126,9 @@ module Tapioca
|
|
136
126
|
sig do
|
137
127
|
params(
|
138
128
|
field: T.untyped,
|
139
|
-
klass:
|
129
|
+
klass: RBI::Scope,
|
140
130
|
returns_collection: T::Boolean
|
141
|
-
)
|
142
|
-
.void
|
131
|
+
).void
|
143
132
|
end
|
144
133
|
def create_fetch_field_methods(field, klass, returns_collection:)
|
145
134
|
name = field.cached_accessor_name.to_s
|
@@ -156,21 +145,36 @@ module Tapioca
|
|
156
145
|
sig do
|
157
146
|
params(
|
158
147
|
field: T.untyped,
|
159
|
-
klass:
|
148
|
+
klass: RBI::Scope,
|
160
149
|
constant: T.class_of(::ActiveRecord::Base),
|
161
|
-
)
|
162
|
-
.void
|
150
|
+
).void
|
163
151
|
end
|
164
152
|
def create_fetch_by_methods(field, klass, constant)
|
153
|
+
is_cache_index = field.instance_variable_defined?(:@attribute_proc)
|
154
|
+
|
155
|
+
# Both `cache_index` and `cache_attribute` generate aliased methods
|
156
|
+
create_aliased_fetch_by_methods(field, klass, constant)
|
157
|
+
|
158
|
+
# If the method used was `cache_index` a few extra methods are created
|
159
|
+
create_index_fetch_by_methods(field, klass, constant) if is_cache_index
|
160
|
+
end
|
161
|
+
|
162
|
+
sig do
|
163
|
+
params(
|
164
|
+
field: T.untyped,
|
165
|
+
klass: RBI::Scope,
|
166
|
+
constant: T.class_of(::ActiveRecord::Base),
|
167
|
+
).void
|
168
|
+
end
|
169
|
+
def create_index_fetch_by_methods(field, klass, constant)
|
165
170
|
field_length = field.key_fields.length
|
166
171
|
fields_name = field.key_fields.join("_and_")
|
167
|
-
|
172
|
+
name = "fetch_by_#{fields_name}"
|
168
173
|
parameters = field.key_fields.map do |arg|
|
169
|
-
|
174
|
+
create_param(arg.to_s, type: "T.untyped")
|
170
175
|
end
|
171
|
-
parameters <<
|
176
|
+
parameters << create_kw_opt_param("includes", default: "nil", type: "T.untyped")
|
172
177
|
|
173
|
-
name = "fetch_by_#{fields_name}"
|
174
178
|
if field.unique
|
175
179
|
klass.create_method(
|
176
180
|
"#{name}!",
|
@@ -195,18 +199,51 @@ module Tapioca
|
|
195
199
|
end
|
196
200
|
|
197
201
|
if field_length == 1
|
198
|
-
name = "fetch_multi_by_#{fields_name}"
|
199
202
|
klass.create_method(
|
200
|
-
|
203
|
+
"fetch_multi_by_#{fields_name}",
|
201
204
|
class_method: true,
|
202
205
|
parameters: [
|
203
|
-
|
204
|
-
|
206
|
+
create_param("index_values", type: "T::Enumerable[T.untyped]"),
|
207
|
+
create_kw_opt_param("includes", default: "nil", type: "T.untyped"),
|
205
208
|
],
|
206
209
|
return_type: COLLECTION_TYPE.call(constant)
|
207
210
|
)
|
208
211
|
end
|
209
212
|
end
|
213
|
+
|
214
|
+
sig do
|
215
|
+
params(
|
216
|
+
field: T.untyped,
|
217
|
+
klass: RBI::Scope,
|
218
|
+
constant: T.class_of(::ActiveRecord::Base),
|
219
|
+
).void
|
220
|
+
end
|
221
|
+
def create_aliased_fetch_by_methods(field, klass, constant)
|
222
|
+
type, _ = ActiveRecordColumnTypeHelper.new(constant).type_for(field.alias_name.to_s)
|
223
|
+
multi_type = type.delete_prefix("T.nilable(").delete_suffix(")").delete_prefix("::")
|
224
|
+
length = field.key_fields.length
|
225
|
+
suffix = field.send(:fetch_method_suffix)
|
226
|
+
|
227
|
+
parameters = field.key_fields.map do |arg|
|
228
|
+
create_param(arg.to_s, type: "T.untyped")
|
229
|
+
end
|
230
|
+
|
231
|
+
klass.create_method(
|
232
|
+
"fetch_#{suffix}",
|
233
|
+
class_method: true,
|
234
|
+
parameters: parameters,
|
235
|
+
return_type: type
|
236
|
+
)
|
237
|
+
|
238
|
+
if length == 1
|
239
|
+
klass.create_method(
|
240
|
+
"fetch_multi_#{suffix}",
|
241
|
+
class_method: true,
|
242
|
+
parameters: [create_param("keys", type: "T::Enumerable[T.untyped]")],
|
243
|
+
return_type: COLLECTION_TYPE.call(multi_type)
|
244
|
+
)
|
245
|
+
end
|
246
|
+
end
|
210
247
|
end
|
211
248
|
end
|
212
249
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "active_support/core_ext/class/attribute"
|
6
|
+
rescue LoadError
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
module Tapioca
|
11
|
+
module Compilers
|
12
|
+
module Dsl
|
13
|
+
# `Tapioca::Compilers::Dsl::MixedInClassAttributes` generates RBI files for modules that dynamically use
|
14
|
+
# `class_attribute` on classes.
|
15
|
+
#
|
16
|
+
# For example, given the following concern
|
17
|
+
#
|
18
|
+
# ~~~rb
|
19
|
+
# module Taggeable
|
20
|
+
# extend ActiveSupport::Concern
|
21
|
+
#
|
22
|
+
# included do
|
23
|
+
# class_attribute :tag
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# ~~~
|
27
|
+
#
|
28
|
+
# this generator will produce the RBI file `taggeable.rbi` with the following content:
|
29
|
+
#
|
30
|
+
# ~~~rbi
|
31
|
+
# # typed: strong
|
32
|
+
#
|
33
|
+
# module Taggeable
|
34
|
+
# include GeneratedInstanceMethods
|
35
|
+
#
|
36
|
+
# mixes_in_class_methods GeneratedClassMethods
|
37
|
+
#
|
38
|
+
# module GeneratedClassMethods
|
39
|
+
# def tag; end
|
40
|
+
# def tag=(value); end
|
41
|
+
# def tag?; end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# module GeneratedInstanceMethods
|
45
|
+
# def tag; end
|
46
|
+
# def tag=(value); end
|
47
|
+
# def tag?; end
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
# ~~~
|
51
|
+
class MixedInClassAttributes < Base
|
52
|
+
extend T::Sig
|
53
|
+
|
54
|
+
sig { override.params(root: RBI::Tree, constant: Module).void }
|
55
|
+
def decorate(root, constant)
|
56
|
+
mixin_compiler = DynamicMixinCompiler.new(constant)
|
57
|
+
return if mixin_compiler.empty_attributes?
|
58
|
+
|
59
|
+
root.create_path(constant) do |mod|
|
60
|
+
mixin_compiler.compile_class_attributes(mod)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
sig { override.returns(T::Enumerable[Module]) }
|
65
|
+
def gather_constants
|
66
|
+
# Select all non-anonymous modules that have overridden Module.included
|
67
|
+
all_modules.select do |mod|
|
68
|
+
!mod.is_a?(Class) && name_of(mod) && Tapioca::Reflection.method_of(mod, :included).owner != Module
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|