tapioca 0.14.2 → 0.14.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -0
- data/lib/tapioca/cli.rb +5 -0
- data/lib/tapioca/commands/abstract_dsl.rb +5 -1
- data/lib/tapioca/dsl/compiler.rb +13 -2
- data/lib/tapioca/dsl/compilers/active_record_columns.rb +43 -2
- data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +10 -5
- data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +18 -5
- data/lib/tapioca/dsl/compilers/active_support_concern.rb +1 -1
- data/lib/tapioca/dsl/compilers/identity_cache.rb +4 -1
- data/lib/tapioca/dsl/compilers/protobuf.rb +4 -1
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +106 -30
- data/lib/tapioca/dsl/pipeline.rb +5 -2
- data/lib/tapioca/helpers/config_helper.rb +3 -1
- data/lib/tapioca/helpers/rbi_files_helper.rb +14 -0
- data/lib/tapioca/helpers/test/dsl_compiler.rb +16 -6
- data/lib/tapioca/static/symbol_loader.rb +1 -1
- data/lib/tapioca/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f75efebcf13cb6839056d8869fe4d2d6e2051605a90edf517904d8a14a33913
|
4
|
+
data.tar.gz: 41fcfa1d3da8c919577146cbbc6a92ad07a62b9e7ac077642ac78290ffb2ecc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea78667fe6cf4f54a0b4dd004de2ab465548a231aa2b35d52de83d89e43a0cbd0b21989e249c90415f41dfb5ac8c5082b9a84575bc4eed83418eab9a4a4f33b1
|
7
|
+
data.tar.gz: 05ea62993f5983a3fb69285f13a07678ddc912e5b7aa5657e81e02906747ab19f17d63a26141deb764d217827ba8097643e3797acf98c31f2323390a52780659
|
data/README.md
CHANGED
@@ -47,6 +47,7 @@ Tapioca makes it easy to work with [Sorbet](https://sorbet.org) in your codebase
|
|
47
47
|
* [Changing the typed strictness of annotations files](#changing-the-typed-strictness-of-annotations-files)
|
48
48
|
* [Generating RBI files for Rails and other DSLs](#generating-rbi-files-for-rails-and-other-dsls)
|
49
49
|
* [Keeping RBI files for DSLs up-to-date](#keeping-rbi-files-for-dsls-up-to-date)
|
50
|
+
* [Using DSL compiler options](#using-dsl-compiler-options)
|
50
51
|
* [Writing custom DSL compilers](#writing-custom-dsl-compilers)
|
51
52
|
* [Writing custom DSL extensions](#writing-custom-dsl-extensions)
|
52
53
|
* [RBI files for missing constants and methods](#rbi-files-for-missing-constants-and-methods)
|
@@ -501,6 +502,7 @@ Options:
|
|
501
502
|
[--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error] # Halt upon a load error while loading the Rails application
|
502
503
|
# Default: true
|
503
504
|
[--skip-constant=constant [constant ...]] # Do not generate RBI definitions for the given application constant(s)
|
505
|
+
[--compiler-options=key:value] # Options to pass to the DSL compilers
|
504
506
|
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
505
507
|
# Default: sorbet/tapioca/config.yml
|
506
508
|
-V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
|
@@ -545,6 +547,25 @@ if Rails.env.development?
|
|
545
547
|
end
|
546
548
|
```
|
547
549
|
|
550
|
+
#### Using DSL compiler options
|
551
|
+
|
552
|
+
Some DSL compilers are able to change their behaviour based on the options passed to them. For example, the
|
553
|
+
`ActiveRecordColumns` compiler can be configured to change how it generates types for method related to Active Record
|
554
|
+
column attributes. To pass options during DSL RBI generation, use the `--compiler-options` flag:
|
555
|
+
```shell
|
556
|
+
$ bin/tapioca dsl --compiler-options=ActiveRecordColumnTypes:untyped
|
557
|
+
```
|
558
|
+
which will make the `ActiveRecordColumns` compiler generate untyped signatures for column attribute methods.
|
559
|
+
|
560
|
+
Compiler options can be passed through the configuration file, as like any other option, and we expect most users to
|
561
|
+
configure them this way. For example, to configure the `ActiveRecordColumns` compiler to generate untyped signatures,
|
562
|
+
you need to add the following to your `sorbet/tapioca/config.yml` file:
|
563
|
+
```yaml
|
564
|
+
dsl:
|
565
|
+
compiler_options:
|
566
|
+
ActiveRecordColumnTypes: untyped
|
567
|
+
```
|
568
|
+
|
548
569
|
#### Writing custom DSL compilers
|
549
570
|
|
550
571
|
It is possible to create your own compilers for DSLs not supported by Tapioca out of the box.
|
@@ -935,6 +956,7 @@ dsl:
|
|
935
956
|
app_root: "."
|
936
957
|
halt_upon_load_error: true
|
937
958
|
skip_constant: []
|
959
|
+
compiler_options: {}
|
938
960
|
gem:
|
939
961
|
outdir: sorbet/rbi/gems
|
940
962
|
file_header: true
|
data/lib/tapioca/cli.rb
CHANGED
@@ -140,6 +140,10 @@ module Tapioca
|
|
140
140
|
banner: "constant [constant ...]",
|
141
141
|
desc: "Do not generate RBI definitions for the given application constant(s)",
|
142
142
|
default: []
|
143
|
+
option :compiler_options,
|
144
|
+
type: :hash,
|
145
|
+
desc: "Options to pass to the DSL compilers",
|
146
|
+
default: {}
|
143
147
|
def dsl(*constant_or_paths)
|
144
148
|
set_environment(options)
|
145
149
|
|
@@ -161,6 +165,7 @@ module Tapioca
|
|
161
165
|
rbi_formatter: rbi_formatter(options),
|
162
166
|
app_root: options[:app_root],
|
163
167
|
halt_upon_load_error: options[:halt_upon_load_error],
|
168
|
+
compiler_options: options[:compiler_options],
|
164
169
|
}
|
165
170
|
|
166
171
|
command = if options[:verify]
|
@@ -27,6 +27,7 @@ module Tapioca
|
|
27
27
|
rbi_formatter: RBIFormatter,
|
28
28
|
app_root: String,
|
29
29
|
halt_upon_load_error: T::Boolean,
|
30
|
+
compiler_options: T::Hash[String, T.untyped],
|
30
31
|
).void
|
31
32
|
end
|
32
33
|
def initialize(
|
@@ -45,7 +46,8 @@ module Tapioca
|
|
45
46
|
gem_dir: DEFAULT_GEM_DIR,
|
46
47
|
rbi_formatter: DEFAULT_RBI_FORMATTER,
|
47
48
|
app_root: ".",
|
48
|
-
halt_upon_load_error: true
|
49
|
+
halt_upon_load_error: true,
|
50
|
+
compiler_options: {}
|
49
51
|
)
|
50
52
|
@requested_constants = requested_constants
|
51
53
|
@requested_paths = requested_paths
|
@@ -63,6 +65,7 @@ module Tapioca
|
|
63
65
|
@app_root = app_root
|
64
66
|
@halt_upon_load_error = halt_upon_load_error
|
65
67
|
@skip_constant = skip_constant
|
68
|
+
@compiler_options = compiler_options
|
66
69
|
|
67
70
|
super()
|
68
71
|
end
|
@@ -129,6 +132,7 @@ module Tapioca
|
|
129
132
|
},
|
130
133
|
skipped_constants: constantize(@skip_constant, ignore_missing: true),
|
131
134
|
number_of_workers: @number_of_workers,
|
135
|
+
compiler_options: @compiler_options,
|
132
136
|
)
|
133
137
|
end
|
134
138
|
|
data/lib/tapioca/dsl/compiler.rb
CHANGED
@@ -22,6 +22,9 @@ module Tapioca
|
|
22
22
|
sig { returns(RBI::Tree) }
|
23
23
|
attr_reader :root
|
24
24
|
|
25
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
26
|
+
attr_reader :options
|
27
|
+
|
25
28
|
class << self
|
26
29
|
extend T::Sig
|
27
30
|
|
@@ -60,11 +63,19 @@ module Tapioca
|
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
63
|
-
sig
|
64
|
-
|
66
|
+
sig do
|
67
|
+
params(
|
68
|
+
pipeline: Tapioca::Dsl::Pipeline,
|
69
|
+
root: RBI::Tree,
|
70
|
+
constant: ConstantType,
|
71
|
+
options: T::Hash[String, T.untyped],
|
72
|
+
).void
|
73
|
+
end
|
74
|
+
def initialize(pipeline, root, constant, options = {})
|
65
75
|
@pipeline = pipeline
|
66
76
|
@root = root
|
67
77
|
@constant = constant
|
78
|
+
@options = options
|
68
79
|
@errors = T.let([], T::Array[String])
|
69
80
|
end
|
70
81
|
|
@@ -15,6 +15,21 @@ module Tapioca
|
|
15
15
|
# created for columns and virtual attributes that are defined in the Active Record
|
16
16
|
# model.
|
17
17
|
#
|
18
|
+
# This compiler accepts a `ActiveRecordColumnTypes` option that can be used to specify
|
19
|
+
# how the types of the column related methods should be generated. The option can be one of the following:
|
20
|
+
# - `persisted` (_default_): The methods will be generated with the type that matches the actual database
|
21
|
+
# column type as the return type. This means that if the column is a string, the method return type
|
22
|
+
# will be `String`, but if the column is also nullable, then the return type will be `T.nilable(String)`. This
|
23
|
+
# mode basically treats each model as if it was a valid and persisted model. Note that this makes typing
|
24
|
+
# Active Record models easier, but does not match the behaviour of non-persisted or invalid models, which can
|
25
|
+
# have all kinds of non-sensical values in their column attributes.
|
26
|
+
# - `nilable`: All column methods will be generated with `T.nilable` return types. This is strictly the most
|
27
|
+
# correct way to type the methods, but it can make working with the models more cumbersome, as you will have to
|
28
|
+
# handle the `nil` cases explicitly using `T.must` or the safe navigation operator `&.`, even for valid
|
29
|
+
# persisted models.
|
30
|
+
# - `untyped`: The methods will be generated with `T.untyped` return types. This mode is practical if you are not
|
31
|
+
# ready to start typing your models strictly yet, but still want to generate RBI files for them.
|
32
|
+
#
|
18
33
|
# For example, with the following model class:
|
19
34
|
# ~~~rb
|
20
35
|
# class Post < ActiveRecord::Base
|
@@ -33,7 +48,7 @@ module Tapioca
|
|
33
48
|
# end
|
34
49
|
# ~~~
|
35
50
|
#
|
36
|
-
# this compiler will produce the following methods in the RBI file
|
51
|
+
# this compiler will, by default, produce the following methods in the RBI file
|
37
52
|
# `post.rbi`:
|
38
53
|
#
|
39
54
|
# ~~~rbi
|
@@ -94,6 +109,17 @@ module Tapioca
|
|
94
109
|
# end
|
95
110
|
# end
|
96
111
|
# ~~~
|
112
|
+
#
|
113
|
+
# However, if `ActiveRecordColumnTypes` is set to `nilable`, the `title` method will be generated as:
|
114
|
+
# ~~~rbi
|
115
|
+
# sig { returns(T.nilable(::String)) }
|
116
|
+
# def title; end
|
117
|
+
# ~~~
|
118
|
+
# and if the option is set to `untyped`, the `title` method will be generated as:
|
119
|
+
# ~~~rbi
|
120
|
+
# sig { returns(T.untyped) }
|
121
|
+
# def title; end
|
122
|
+
# ~~~
|
97
123
|
class ActiveRecordColumns < Compiler
|
98
124
|
extend T::Sig
|
99
125
|
include Helpers::ActiveRecordConstantsHelper
|
@@ -147,6 +173,21 @@ module Tapioca
|
|
147
173
|
|
148
174
|
private
|
149
175
|
|
176
|
+
ColumnTypeOption = Helpers::ActiveRecordColumnTypeHelper::ColumnTypeOption
|
177
|
+
|
178
|
+
sig { returns(ColumnTypeOption) }
|
179
|
+
def column_type_option
|
180
|
+
@column_type_option ||= T.let(
|
181
|
+
ColumnTypeOption.from_options(options) do |value, default_column_type_option|
|
182
|
+
add_error(<<~MSG.strip)
|
183
|
+
Unknown value for compiler option `ActiveRecordColumnTypes` given: `#{value}`.
|
184
|
+
Proceeding with the default value: `#{default_column_type_option.serialize}`.
|
185
|
+
MSG
|
186
|
+
end,
|
187
|
+
T.nilable(ColumnTypeOption),
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
150
191
|
sig do
|
151
192
|
params(
|
152
193
|
klass: RBI::Scope,
|
@@ -174,7 +215,7 @@ module Tapioca
|
|
174
215
|
end
|
175
216
|
def add_methods_for_attribute(klass, attribute_name, column_name = attribute_name, methods_to_add = nil)
|
176
217
|
getter_type, setter_type = Helpers::ActiveRecordColumnTypeHelper
|
177
|
-
.new(constant)
|
218
|
+
.new(constant, column_type_option: column_type_option)
|
178
219
|
.type_for(attribute_name, column_name)
|
179
220
|
|
180
221
|
# Added by ActiveRecord::AttributeMethods::Read
|
@@ -37,6 +37,7 @@ module Tapioca
|
|
37
37
|
extend T::Sig
|
38
38
|
|
39
39
|
ConstantType = type_member { { fixed: T.class_of(ActiveSupport::TestCase) } }
|
40
|
+
MISSING = Object.new
|
40
41
|
|
41
42
|
sig { override.void }
|
42
43
|
def decorate
|
@@ -46,6 +47,7 @@ module Tapioca
|
|
46
47
|
method_names_from_eager_fixture_loader
|
47
48
|
end
|
48
49
|
|
50
|
+
method_names.select! { |name| fixture_class_mapping_from_fixture_files[name] != MISSING }
|
49
51
|
return if method_names.empty?
|
50
52
|
|
51
53
|
root.create_path(constant) do |mod|
|
@@ -96,15 +98,14 @@ module Tapioca
|
|
96
98
|
T.unsafe(fixture_loader).fixture_sets.keys
|
97
99
|
end
|
98
100
|
|
99
|
-
sig { returns(T::Array[
|
101
|
+
sig { returns(T::Array[String]) }
|
100
102
|
def method_names_from_eager_fixture_loader
|
101
103
|
fixture_loader.ancestors # get all ancestors from class that includes AR fixtures
|
102
104
|
.drop(1) # drop the anonymous class itself from the array
|
103
105
|
.reject(&:name) # only collect anonymous ancestors because fixture methods are always on an anonymous module
|
104
|
-
.
|
105
|
-
|
106
|
+
.flat_map do |mod|
|
107
|
+
mod.private_instance_methods(false).map(&:to_s) + mod.instance_methods(false).map(&:to_s)
|
106
108
|
end
|
107
|
-
.flatten # merge methods into a single list
|
108
109
|
end
|
109
110
|
|
110
111
|
sig { params(mod: RBI::Scope, name: String).void }
|
@@ -190,10 +191,14 @@ module Tapioca
|
|
190
191
|
next unless ::File.file?(file)
|
191
192
|
|
192
193
|
ActiveRecord::FixtureSet::File.open(file) do |fh|
|
194
|
+
fixture_name = file.delete_prefix(path.to_s).delete_prefix("/").delete_suffix(".yml")
|
193
195
|
next unless fh.model_class
|
194
196
|
|
195
|
-
fixture_name = file.delete_prefix(path.to_s).delete_prefix("/").delete_suffix(".yml")
|
196
197
|
mapping[fixture_name] = fh.model_class
|
198
|
+
rescue ActiveRecord::Fixture::FormatError
|
199
|
+
# For fixtures that are not associated to any models and just contain raw data or fixtures that
|
200
|
+
# contain invalid formatting, we want to skip them and avoid crashing
|
201
|
+
mapping[fixture_name] = MISSING
|
197
202
|
end
|
198
203
|
end
|
199
204
|
end
|
@@ -98,8 +98,7 @@ module Tapioca
|
|
98
98
|
stores.values.each do |store_data|
|
99
99
|
store_data.accessors.each do |accessor, name|
|
100
100
|
field = store_data.fields.fetch(accessor)
|
101
|
-
type = type_for(field
|
102
|
-
type = as_nilable_type(type) if field.null
|
101
|
+
type = type_for(field)
|
103
102
|
name ||= field.name # support < 1.5.0
|
104
103
|
|
105
104
|
generate_methods(store_accessors_module, name.to_s, type)
|
@@ -136,9 +135,23 @@ module Tapioca
|
|
136
135
|
T::Hash[Symbol, String],
|
137
136
|
)
|
138
137
|
|
139
|
-
sig { params(
|
140
|
-
def type_for(
|
141
|
-
TYPES.fetch(
|
138
|
+
sig { params(field: ActiveRecord::TypedStore::Field).returns(String) }
|
139
|
+
def type_for(field)
|
140
|
+
type = TYPES.fetch(field.type_sym, "T.untyped")
|
141
|
+
|
142
|
+
type = if field.array
|
143
|
+
# `null: false` applies to the array itself, not the elements, which are always nilable.
|
144
|
+
# https://github.com/byroot/activerecord-typedstore/blob/2f3fb98/spec/support/models.rb#L46C34-L46C45
|
145
|
+
# https://github.com/byroot/activerecord-typedstore/blob/2f3fb98/spec/active_record/typed_store_spec.rb#L854-L857
|
146
|
+
nilable_element_type = as_nilable_type(type)
|
147
|
+
"T::Array[#{nilable_element_type}]"
|
148
|
+
else
|
149
|
+
type
|
150
|
+
end
|
151
|
+
|
152
|
+
type = as_nilable_type(type) if field.null
|
153
|
+
|
154
|
+
type
|
142
155
|
end
|
143
156
|
|
144
157
|
sig do
|
@@ -49,7 +49,7 @@ module Tapioca
|
|
49
49
|
mixed_in_class_methods = dependencies
|
50
50
|
.uniq # Deduplicate
|
51
51
|
.filter_map do |concern| # Map to class methods module name, if exists
|
52
|
-
"#{qualified_name_of(concern)}::ClassMethods" if concern.const_defined?(:ClassMethods)
|
52
|
+
"#{qualified_name_of(concern)}::ClassMethods" if concern.const_defined?(:ClassMethods, false)
|
53
53
|
end
|
54
54
|
|
55
55
|
return if mixed_in_class_methods.empty?
|
@@ -216,7 +216,10 @@ module Tapioca
|
|
216
216
|
).void
|
217
217
|
end
|
218
218
|
def create_aliased_fetch_by_methods(field, klass)
|
219
|
-
type, _ = Helpers::ActiveRecordColumnTypeHelper.new(
|
219
|
+
type, _ = Helpers::ActiveRecordColumnTypeHelper.new(
|
220
|
+
constant,
|
221
|
+
column_type_option: Helpers::ActiveRecordColumnTypeHelper::ColumnTypeOption::Nilable,
|
222
|
+
).type_for(field.alias_name.to_s)
|
220
223
|
multi_type = type.delete_prefix("T.nilable(").delete_suffix(")").delete_prefix("::")
|
221
224
|
suffix = field.send(:fetch_method_suffix)
|
222
225
|
|
@@ -29,7 +29,7 @@ module Tapioca
|
|
29
29
|
# ~~~rbi
|
30
30
|
# # cart.rbi
|
31
31
|
# # typed: strong
|
32
|
-
# class Cart
|
32
|
+
# class Cart < Google::Protobuf::AbstractMessage
|
33
33
|
# sig { returns(Integer) }
|
34
34
|
# def customer_id; end
|
35
35
|
#
|
@@ -123,6 +123,9 @@ module Tapioca
|
|
123
123
|
class_method: true,
|
124
124
|
)
|
125
125
|
when Google::Protobuf::Descriptor
|
126
|
+
raise "#{klass} is not a RBI::Class" unless klass.is_a?(RBI::Class)
|
127
|
+
|
128
|
+
klass.superclass_name = "Google::Protobuf::AbstractMessage"
|
126
129
|
descriptor.each_oneof { |oneof| create_oneof_method(klass, oneof) }
|
127
130
|
fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
|
128
131
|
fields.sort_by!(&:name)
|
@@ -10,12 +10,73 @@ module Tapioca
|
|
10
10
|
extend T::Sig
|
11
11
|
include RBIHelper
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
class ColumnTypeOption < T::Enum
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
enums do
|
17
|
+
Untyped = new("untyped")
|
18
|
+
Nilable = new("nilable")
|
19
|
+
Persisted = new("persisted")
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
extend T::Sig
|
24
|
+
|
25
|
+
sig do
|
26
|
+
params(
|
27
|
+
options: T::Hash[String, T.untyped],
|
28
|
+
block: T.proc.params(value: String, default_column_type_option: ColumnTypeOption).void,
|
29
|
+
).returns(ColumnTypeOption)
|
30
|
+
end
|
31
|
+
def from_options(options, &block)
|
32
|
+
column_type_option = Persisted
|
33
|
+
value = options["ActiveRecordColumnTypes"]
|
34
|
+
|
35
|
+
if value
|
36
|
+
if has_serialized?(value)
|
37
|
+
column_type_option = from_serialized(value)
|
38
|
+
else
|
39
|
+
block.call(value, column_type_option)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
column_type_option
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { returns(T::Boolean) }
|
48
|
+
def persisted?
|
49
|
+
self == ColumnTypeOption::Persisted
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { returns(T::Boolean) }
|
53
|
+
def nilable?
|
54
|
+
self == ColumnTypeOption::Nilable
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { returns(T::Boolean) }
|
58
|
+
def untyped?
|
59
|
+
self == ColumnTypeOption::Untyped
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
sig do
|
64
|
+
params(
|
65
|
+
constant: T.class_of(ActiveRecord::Base),
|
66
|
+
column_type_option: ColumnTypeOption,
|
67
|
+
).void
|
68
|
+
end
|
69
|
+
def initialize(constant, column_type_option: ColumnTypeOption::Persisted)
|
15
70
|
@constant = constant
|
71
|
+
@column_type_option = column_type_option
|
16
72
|
end
|
17
73
|
|
18
|
-
sig
|
74
|
+
sig do
|
75
|
+
params(
|
76
|
+
attribute_name: String,
|
77
|
+
column_name: String,
|
78
|
+
).returns([String, String])
|
79
|
+
end
|
19
80
|
def type_for(attribute_name, column_name = attribute_name)
|
20
81
|
return id_type if attribute_name == "id"
|
21
82
|
|
@@ -27,7 +88,11 @@ module Tapioca
|
|
27
88
|
sig { returns([String, String]) }
|
28
89
|
def id_type
|
29
90
|
if @constant.respond_to?(:composite_primary_key?) && T.unsafe(@constant).composite_primary_key?
|
30
|
-
@constant.primary_key.map
|
91
|
+
@constant.primary_key.map do |column|
|
92
|
+
column_type_for(column)
|
93
|
+
end.map do |tuple|
|
94
|
+
"[#{tuple.join(", ")}]"
|
95
|
+
end
|
31
96
|
else
|
32
97
|
column_type_for(@constant.primary_key)
|
33
98
|
end
|
@@ -35,7 +100,7 @@ module Tapioca
|
|
35
100
|
|
36
101
|
sig { params(column_name: String).returns([String, String]) }
|
37
102
|
def column_type_for(column_name)
|
38
|
-
return ["T.untyped", "T.untyped"] if
|
103
|
+
return ["T.untyped", "T.untyped"] if @column_type_option.untyped?
|
39
104
|
|
40
105
|
column = @constant.columns_hash[column_name]
|
41
106
|
column_type = @constant.attribute_types[column_name]
|
@@ -48,27 +113,31 @@ module Tapioca
|
|
48
113
|
getter_type
|
49
114
|
end
|
50
115
|
|
51
|
-
if column&.null
|
116
|
+
if @column_type_option.persisted? && !column&.null
|
117
|
+
[getter_type, setter_type]
|
118
|
+
else
|
52
119
|
getter_type = as_nilable_type(getter_type) unless not_nilable_serialized_column?(column_type)
|
53
|
-
|
120
|
+
[getter_type, as_nilable_type(setter_type)]
|
54
121
|
end
|
55
|
-
|
56
|
-
if Array(@constant.primary_key).include?(column_name) ||
|
57
|
-
column_name == "created_at" ||
|
58
|
-
column_name == "updated_at"
|
59
|
-
getter_type = as_nilable_type(getter_type)
|
60
|
-
end
|
61
|
-
|
62
|
-
[getter_type, setter_type]
|
63
122
|
end
|
64
123
|
|
65
124
|
sig { params(column_type: T.untyped).returns(String) }
|
66
125
|
def type_for_activerecord_value(column_type)
|
67
126
|
case column_type
|
68
|
-
when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
|
127
|
+
when ->(type) { defined?(MoneyColumn) && MoneyColumn::ActiveRecordType === type }
|
69
128
|
"::Money"
|
70
129
|
when ActiveRecord::Type::Integer
|
71
130
|
"::Integer"
|
131
|
+
when ->(type) {
|
132
|
+
defined?(ActiveRecord::Encryption) && ActiveRecord::Encryption::EncryptedAttributeType === type
|
133
|
+
}
|
134
|
+
# Reflect to see if `ActiveModel::Type::Value` is being used first.
|
135
|
+
getter_type = Tapioca::Dsl::Helpers::ActiveModelTypeHelper.type_for(column_type)
|
136
|
+
return getter_type unless getter_type == "T.untyped"
|
137
|
+
|
138
|
+
# Otherwise fallback to String as `ActiveRecord::Encryption::EncryptedAttributeType` inherits from
|
139
|
+
# `ActiveRecord::Type::Text` which inherits from `ActiveModel::Type::String`.
|
140
|
+
"::String"
|
72
141
|
when ActiveRecord::Type::String
|
73
142
|
"::String"
|
74
143
|
when ActiveRecord::Type::Date
|
@@ -87,29 +156,36 @@ module Tapioca
|
|
87
156
|
"::String"
|
88
157
|
when ActiveRecord::Type::Serialized
|
89
158
|
serialized_column_type(column_type)
|
90
|
-
when
|
91
|
-
|
159
|
+
when ->(type) {
|
160
|
+
defined?(ActiveRecord::Normalization::NormalizedValueType) &&
|
161
|
+
ActiveRecord::Normalization::NormalizedValueType === type
|
162
|
+
}
|
92
163
|
type_for_activerecord_value(column_type.cast_type)
|
93
|
-
when
|
94
|
-
|
164
|
+
when ->(type) {
|
165
|
+
defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid) &&
|
166
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid === type
|
167
|
+
}
|
95
168
|
"::String"
|
96
|
-
when
|
97
|
-
|
169
|
+
when ->(type) {
|
170
|
+
defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Cidr) &&
|
171
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Cidr === type
|
172
|
+
}
|
173
|
+
"::IPAddr"
|
174
|
+
when ->(type) {
|
175
|
+
defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore) &&
|
176
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore === type
|
177
|
+
}
|
98
178
|
"T::Hash[::String, ::String]"
|
99
|
-
when
|
100
|
-
|
179
|
+
when ->(type) {
|
180
|
+
defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array) &&
|
181
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array === type
|
182
|
+
}
|
101
183
|
"T::Array[#{type_for_activerecord_value(column_type.subtype)}]"
|
102
184
|
else
|
103
185
|
ActiveModelTypeHelper.type_for(column_type)
|
104
186
|
end
|
105
187
|
end
|
106
188
|
|
107
|
-
sig { params(constant: Module).returns(T::Boolean) }
|
108
|
-
def do_not_generate_strong_types?(constant)
|
109
|
-
Object.const_defined?(:StrongTypeGeneration) &&
|
110
|
-
!(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
|
111
|
-
end
|
112
|
-
|
113
189
|
sig { params(column_type: ActiveRecord::Enum::EnumType).returns(String) }
|
114
190
|
def enum_setter_type(column_type)
|
115
191
|
# In Rails < 7 this method is private. When support for that is dropped we can call the method directly
|
data/lib/tapioca/dsl/pipeline.rb
CHANGED
@@ -33,6 +33,7 @@ module Tapioca
|
|
33
33
|
error_handler: T.proc.params(error: String).void,
|
34
34
|
skipped_constants: T::Array[Module],
|
35
35
|
number_of_workers: T.nilable(Integer),
|
36
|
+
compiler_options: T::Hash[String, T.untyped],
|
36
37
|
).void
|
37
38
|
end
|
38
39
|
def initialize(
|
@@ -42,7 +43,8 @@ module Tapioca
|
|
42
43
|
excluded_compilers: [],
|
43
44
|
error_handler: $stderr.method(:puts).to_proc,
|
44
45
|
skipped_constants: [],
|
45
|
-
number_of_workers: nil
|
46
|
+
number_of_workers: nil,
|
47
|
+
compiler_options: {}
|
46
48
|
)
|
47
49
|
@active_compilers = T.let(
|
48
50
|
gather_active_compilers(requested_compilers, excluded_compilers),
|
@@ -53,6 +55,7 @@ module Tapioca
|
|
53
55
|
@error_handler = error_handler
|
54
56
|
@skipped_constants = skipped_constants
|
55
57
|
@number_of_workers = number_of_workers
|
58
|
+
@compiler_options = compiler_options
|
56
59
|
@errors = T.let([], T::Array[String])
|
57
60
|
end
|
58
61
|
|
@@ -197,7 +200,7 @@ module Tapioca
|
|
197
200
|
active_compilers.each do |compiler_class|
|
198
201
|
next unless compiler_class.handles?(constant)
|
199
202
|
|
200
|
-
compiler = compiler_class.new(self, file.root, constant)
|
203
|
+
compiler = compiler_class.new(self, file.root, constant, @compiler_options)
|
201
204
|
compiler.decorate
|
202
205
|
rescue
|
203
206
|
$stderr.puts("Error: `#{compiler_class.name}` failed to generate RBI for `#{constant}`")
|
@@ -132,7 +132,9 @@ module Tapioca
|
|
132
132
|
when :hash
|
133
133
|
error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
|
134
134
|
"`Hash[String, String]` but found `#{config_option_value}`"
|
135
|
-
|
135
|
+
values_to_validate = config_option_value.keys
|
136
|
+
values_to_validate += config_option_value.values
|
137
|
+
all_strings = values_to_validate.all? { |v| v.is_a?(String) }
|
136
138
|
next build_error(error_msg) unless all_strings
|
137
139
|
end
|
138
140
|
end
|
@@ -174,7 +174,21 @@ module Tapioca
|
|
174
174
|
shims_or_todos = extract_shims_and_todos(nodes, shim_rbi_dir: shim_rbi_dir, todo_rbi_file: todo_rbi_file)
|
175
175
|
return false if shims_or_todos.empty?
|
176
176
|
|
177
|
+
not_shims_or_todos = nodes - shims_or_todos
|
177
178
|
shims_or_todos_empty_scopes = extract_empty_scopes(shims_or_todos)
|
179
|
+
|
180
|
+
# We need to discard classes that are redefining the parent of a class
|
181
|
+
shims_or_todos_empty_scopes.select! do |scope|
|
182
|
+
# Empty modules are always duplicates
|
183
|
+
next true unless scope.is_a?(RBI::Class)
|
184
|
+
|
185
|
+
# Empty classes without parents are also duplicates
|
186
|
+
parent_name = scope.superclass_name
|
187
|
+
next true unless parent_name
|
188
|
+
|
189
|
+
# Empty classes that are not redefining the parent are also duplicates
|
190
|
+
not_shims_or_todos.any? { |node| node.is_a?(RBI::Class) && node.superclass_name == parent_name }
|
191
|
+
end
|
178
192
|
return true unless shims_or_todos_empty_scopes.empty?
|
179
193
|
|
180
194
|
mixins = extract_mixins(shims_or_todos)
|
@@ -29,9 +29,14 @@ module Tapioca
|
|
29
29
|
context.activate_other_dsl_compilers(compiler_classes)
|
30
30
|
end
|
31
31
|
|
32
|
-
sig
|
33
|
-
|
34
|
-
|
32
|
+
sig do
|
33
|
+
params(
|
34
|
+
constant_name: T.any(Symbol, String),
|
35
|
+
compiler_options: T::Hash[Symbol, T.untyped],
|
36
|
+
).returns(String)
|
37
|
+
end
|
38
|
+
def rbi_for(constant_name, compiler_options: {})
|
39
|
+
context.rbi_for(constant_name, compiler_options: compiler_options)
|
35
40
|
end
|
36
41
|
|
37
42
|
sig { returns(T::Array[String]) }
|
@@ -85,8 +90,13 @@ module Tapioca
|
|
85
90
|
compiler_class.processable_constants.filter_map(&:name).sort
|
86
91
|
end
|
87
92
|
|
88
|
-
sig
|
89
|
-
|
93
|
+
sig do
|
94
|
+
params(
|
95
|
+
constant_name: T.any(Symbol, String),
|
96
|
+
compiler_options: T::Hash[Symbol, T.untyped],
|
97
|
+
).returns(String)
|
98
|
+
end
|
99
|
+
def rbi_for(constant_name, compiler_options: {})
|
90
100
|
# Make sure this is a constant that we can handle.
|
91
101
|
unless gathered_constants.include?(constant_name.to_s)
|
92
102
|
raise "`#{constant_name}` is not processable by the `#{compiler_class}` compiler."
|
@@ -95,7 +105,7 @@ module Tapioca
|
|
95
105
|
file = RBI::File.new(strictness: "strong")
|
96
106
|
constant = Object.const_get(constant_name)
|
97
107
|
|
98
|
-
compiler = compiler_class.new(pipeline, file.root, constant)
|
108
|
+
compiler = compiler_class.new(pipeline, file.root, constant, compiler_options.transform_keys(&:to_s))
|
99
109
|
compiler.decorate
|
100
110
|
|
101
111
|
rbi = Tapioca::DEFAULT_RBI_FORMATTER.print_file(file)
|
@@ -22,7 +22,7 @@ module Tapioca
|
|
22
22
|
sig { params(gem: Gemfile::GemSpec).returns(T::Set[String]) }
|
23
23
|
def engine_symbols(gem)
|
24
24
|
gem_engine = engines.find do |engine|
|
25
|
-
gem.
|
25
|
+
gem.full_gem_path == engine.config.root.to_s
|
26
26
|
end
|
27
27
|
|
28
28
|
return Set.new unless gem_engine
|
data/lib/tapioca/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapioca
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.14.
|
4
|
+
version: 0.14.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ufuk Kayserilioglu
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2024-
|
14
|
+
date: 2024-06-20 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -288,7 +288,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
288
288
|
- !ruby/object:Gem::Version
|
289
289
|
version: '0'
|
290
290
|
requirements: []
|
291
|
-
rubygems_version: 3.5.
|
291
|
+
rubygems_version: 3.5.13
|
292
292
|
signing_key:
|
293
293
|
specification_version: 4
|
294
294
|
summary: A Ruby Interface file generator for gems, core types and the Ruby standard
|