tapioca 0.4.27 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|