tapioca 0.6.0 → 0.6.4
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 +6 -0
- data/README.md +56 -14
- data/lib/tapioca/cli.rb +1 -0
- data/lib/tapioca/compilers/dsl/active_model_attributes.rb +19 -13
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +1 -1
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +8 -8
- data/lib/tapioca/compilers/dsl/active_record_relations.rb +20 -3
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +1 -1
- data/lib/tapioca/compilers/dsl/base.rb +19 -3
- data/lib/tapioca/compilers/dsl/identity_cache.rb +5 -3
- data/lib/tapioca/compilers/dsl/rails_generators.rb +3 -3
- data/lib/tapioca/compilers/dsl/smart_properties.rb +12 -19
- data/lib/tapioca/compilers/dsl/url_helpers.rb +8 -3
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +30 -5
- data/lib/tapioca/compilers/requires_compiler.rb +4 -11
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +26 -26
- data/lib/tapioca/gemfile.rb +44 -20
- data/lib/tapioca/generators/base.rb +1 -1
- data/lib/tapioca/generic_type_registry.rb +22 -7
- data/lib/tapioca/helpers/active_record_column_type_helper.rb +12 -2
- data/lib/tapioca/helpers/cli_helper.rb +10 -9
- data/lib/tapioca/helpers/config_helper.rb +116 -0
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +92 -54
- data/lib/tapioca/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ea7bddec4d5d00918ca3236afee754f7f8dd9481d78bf8e26819c0140b154ff
|
4
|
+
data.tar.gz: 681d5ed15d27c20833aa4df86dc1a61d721e0d5993a43073873f2406a254d806
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2e885fa0010d90899b1f167e3a68c1d65f7cec21d7386a321a51d753fdf6e4deaf97d5c0866b7969cf90f42a5aa68af858e173237f8fc0a7c31bc08c3931185
|
7
|
+
data.tar.gz: a48f142ad8402b07c2b4724801b2dee912f1b95975bb9951feb97aef433a46c248e458527827be3a450336f2c62195714a5f5b476ed7704dad4ccefdb93aca98
|
data/Gemfile
CHANGED
@@ -35,4 +35,10 @@ group(:development, :test) do
|
|
35
35
|
gem("config", require: false)
|
36
36
|
gem("aasm", require: false)
|
37
37
|
gem("bcrypt", require: false)
|
38
|
+
gem("xpath", require: false)
|
39
|
+
|
40
|
+
# net-smtp was removed from default gems in Ruby 3.1, but is used by the `mail` gem.
|
41
|
+
# So we need to add it as a dependency until `mail` is fixed:
|
42
|
+
# https://github.com/rails/rails/blob/0919aa97260ab8240150278d3b07a1547489e3fd/Gemfile#L178-L191
|
43
|
+
gem("net-smtp", "0.3.1", require: false)
|
38
44
|
end
|
data/README.md
CHANGED
@@ -80,8 +80,8 @@ Command: `tapioca init`
|
|
80
80
|
|
81
81
|
This will create the `sorbet/config` and `sorbet/tapioca/require.rb` files for you, if they don't exist. If any of the files already exist, they will not be changed.
|
82
82
|
|
83
|
+
<!-- START_HELP_COMMAND_INIT -->
|
83
84
|
```shell
|
84
|
-
$ bundle exec tapioca help init
|
85
85
|
Usage:
|
86
86
|
tapioca init
|
87
87
|
|
@@ -92,6 +92,7 @@ Options:
|
|
92
92
|
|
93
93
|
initializes folder structure
|
94
94
|
```
|
95
|
+
<!-- END_HELP_COMMAND_INIT -->
|
95
96
|
|
96
97
|
### Generate RBI files for gems
|
97
98
|
|
@@ -99,13 +100,13 @@ Command: `tapioca gem [gems...]`
|
|
99
100
|
|
100
101
|
This will generate RBIs for the specified gems and place them in the RBI directory.
|
101
102
|
|
103
|
+
<!-- START_HELP_COMMAND_GEM -->
|
102
104
|
```shell
|
103
|
-
$ bundle exec tapioca help gem
|
104
105
|
Usage:
|
105
106
|
tapioca gem [gem...]
|
106
107
|
|
107
108
|
Options:
|
108
|
-
--out, -o, [--outdir=directory] # The output directory for generated RBI files
|
109
|
+
--out, -o, [--outdir=directory] # The output directory for generated gem RBI files
|
109
110
|
# Default: sorbet/rbi/gems
|
110
111
|
[--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
|
111
112
|
# Default: true
|
@@ -113,10 +114,10 @@ Options:
|
|
113
114
|
--pre, -b, [--prerequire=file] # A file to be required before Bundler.require is called
|
114
115
|
--post, -a, [--postrequire=file] # A file to be required after Bundler.require is called
|
115
116
|
# Default: sorbet/tapioca/require.rb
|
116
|
-
-x, [--exclude=gem [gem ...]] #
|
117
|
-
--typed, -t, [--typed-overrides=gem:level [gem:level ...]] #
|
117
|
+
-x, [--exclude=gem [gem ...]] # Exclude the given gem(s) from RBI generation
|
118
|
+
--typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Override for typed sigils for generated gem RBIs
|
118
119
|
# Default: {"activesupport"=>"false"}
|
119
|
-
[--verify], [--no-verify] #
|
120
|
+
[--verify], [--no-verify] # Verify RBIs are up-to-date
|
120
121
|
[--doc], [--no-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
|
121
122
|
[--exported-gem-rbis], [--no-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
|
122
123
|
# Default: true
|
@@ -128,6 +129,7 @@ Options:
|
|
128
129
|
|
129
130
|
generate RBIs from gems
|
130
131
|
```
|
132
|
+
<!-- END_HELP_COMMAND_GEM -->
|
131
133
|
|
132
134
|
### Generate the list of all unresolved constants
|
133
135
|
|
@@ -135,13 +137,13 @@ Command: `tapioca todo`
|
|
135
137
|
|
136
138
|
This will generate the file `sorbet/rbi/todo.rbi` defining all unresolved constants as empty modules.
|
137
139
|
|
140
|
+
<!-- START_HELP_COMMAND_TODO -->
|
138
141
|
```shell
|
139
|
-
$ bundle exec tapioca help todo
|
140
142
|
Usage:
|
141
143
|
tapioca todo
|
142
144
|
|
143
145
|
Options:
|
144
|
-
[--todo-file=TODO_FILE]
|
146
|
+
[--todo-file=TODO_FILE] # Path to the generated todo RBI file
|
145
147
|
# Default: sorbet/rbi/todo.rbi
|
146
148
|
[--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
|
147
149
|
# Default: true
|
@@ -151,6 +153,7 @@ Options:
|
|
151
153
|
|
152
154
|
generate the list of unresolved constants
|
153
155
|
```
|
156
|
+
<!-- END_HELP_COMMAND_TODO -->
|
154
157
|
|
155
158
|
### Generate DSL RBI files
|
156
159
|
|
@@ -158,18 +161,18 @@ Command: `tapioca dsl [constant...]`
|
|
158
161
|
|
159
162
|
This will generate DSL RBIs for specified constants (or for all handled constants, if a constant name is not supplied). You can read about DSL RBI generators supplied by `tapioca` in [the manual](manual/generators.md).
|
160
163
|
|
164
|
+
<!-- START_HELP_COMMAND_DSL -->
|
161
165
|
```shell
|
162
|
-
$ bundle exec tapioca help dsl
|
163
166
|
Usage:
|
164
167
|
tapioca dsl [constant...]
|
165
168
|
|
166
169
|
Options:
|
167
|
-
--out, -o, [--outdir=directory] # The output directory for generated RBI files
|
170
|
+
--out, -o, [--outdir=directory] # The output directory for generated DSL RBI files
|
168
171
|
# Default: sorbet/rbi/dsl
|
169
172
|
[--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
|
170
173
|
# Default: true
|
171
|
-
[--only=generator [generator ...]] # Only run supplied DSL
|
172
|
-
[--exclude=generator [generator ...]] # Exclude supplied DSL
|
174
|
+
[--only=generator [generator ...]] # Only run supplied DSL generator(s)
|
175
|
+
[--exclude=generator [generator ...]] # Exclude supplied DSL generator(s)
|
173
176
|
[--verify], [--no-verify] # Verifies RBIs are up-to-date
|
174
177
|
-q, [--quiet], [--no-quiet] # Supresses file creation output
|
175
178
|
-w, [--workers=N] # EXPERIMENTAL: Number of parallel workers to use when generating RBIs
|
@@ -180,6 +183,8 @@ Options:
|
|
180
183
|
|
181
184
|
generate RBIs for dynamic methods
|
182
185
|
```
|
186
|
+
<!-- END_HELP_COMMAND_DSL -->
|
187
|
+
|
183
188
|
## Configuration
|
184
189
|
|
185
190
|
Tapioca supports loading command defaults from a configuration file. The default configuration
|
@@ -192,20 +197,57 @@ For example, if you always want to generate gem RBIs with inline documentation,
|
|
192
197
|
|
193
198
|
```yaml
|
194
199
|
gem:
|
195
|
-
|
200
|
+
doc: true
|
196
201
|
```
|
197
202
|
|
198
203
|
Additionally, if you always want to exclude the `AASM` and `ActiveRecordFixtures` DSL compilers in your DSL RBI generation runs, your config file would then look like this:
|
199
204
|
|
200
205
|
```yaml
|
201
206
|
gem:
|
202
|
-
|
207
|
+
doc: true
|
203
208
|
dsl:
|
204
209
|
exclude:
|
205
210
|
- UrlHelpers
|
206
211
|
- ActiveRecordFixtures
|
207
212
|
```
|
208
213
|
|
214
|
+
The full configuration file, with each option and its default value, would look something like this:
|
215
|
+
<!-- START_CONFIG_TEMPLATE -->
|
216
|
+
```yaml
|
217
|
+
---
|
218
|
+
require:
|
219
|
+
postrequire: sorbet/tapioca/require.rb
|
220
|
+
todo:
|
221
|
+
todo_file: sorbet/rbi/todo.rbi
|
222
|
+
file_header: true
|
223
|
+
dsl:
|
224
|
+
outdir: sorbet/rbi/dsl
|
225
|
+
file_header: true
|
226
|
+
only: []
|
227
|
+
exclude: []
|
228
|
+
verify: false
|
229
|
+
quiet: false
|
230
|
+
workers: 1
|
231
|
+
gem:
|
232
|
+
outdir: sorbet/rbi/gems
|
233
|
+
file_header: true
|
234
|
+
all: false
|
235
|
+
prerequire: ''
|
236
|
+
postrequire: sorbet/tapioca/require.rb
|
237
|
+
exclude: []
|
238
|
+
typed_overrides:
|
239
|
+
activesupport: 'false'
|
240
|
+
verify: false
|
241
|
+
doc: false
|
242
|
+
exported_gem_rbis: true
|
243
|
+
workers: 1
|
244
|
+
clean_shims:
|
245
|
+
gem_rbi_dir: sorbet/rbi/gems
|
246
|
+
dsl_rbi_dir: sorbet/rbi/dsl
|
247
|
+
shim_rbi_dir: sorbet/rbi/shims
|
248
|
+
```
|
249
|
+
<!-- END_CONFIG_TEMPLATE -->
|
250
|
+
|
209
251
|
## Contributing
|
210
252
|
|
211
253
|
Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/tapioca. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://github.com/Shopify/tapioca/blob/main/CODE_OF_CONDUCT.md) code of conduct.
|
data/lib/tapioca/cli.rb
CHANGED
@@ -62,26 +62,32 @@ module Tapioca
|
|
62
62
|
|
63
63
|
sig { params(constant: ::ActiveModel::Attributes::ClassMethods).returns(T::Array[[::String, ::String]]) }
|
64
64
|
def attribute_methods_for(constant)
|
65
|
-
constant.
|
65
|
+
patterns = if constant.respond_to?(:attribute_method_patterns)
|
66
|
+
# https://github.com/rails/rails/pull/44367
|
67
|
+
T.unsafe(constant).attribute_method_patterns
|
68
|
+
else
|
69
|
+
constant.attribute_method_matchers
|
70
|
+
end
|
71
|
+
patterns.flat_map do |pattern|
|
66
72
|
constant.attribute_types.map do |name, value|
|
67
|
-
next unless
|
73
|
+
next unless handle_method_pattern?(pattern)
|
68
74
|
|
69
|
-
[
|
75
|
+
[pattern.method_name(name), type_for(value)]
|
70
76
|
end.compact
|
71
77
|
end
|
72
78
|
end
|
73
79
|
|
74
|
-
sig
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
def handle_method_matcher?(matcher)
|
79
|
-
target = if matcher.respond_to?(:method_missing_target)
|
80
|
+
sig { params(pattern: T.untyped).returns(T::Boolean) }
|
81
|
+
def handle_method_pattern?(pattern)
|
82
|
+
target = if pattern.respond_to?(:method_missing_target)
|
80
83
|
# Pre-Rails 6.0, the field is named "method_missing_target"
|
81
|
-
T.unsafe(
|
82
|
-
|
84
|
+
T.unsafe(pattern).method_missing_target
|
85
|
+
elsif pattern.respond_to?(:target)
|
83
86
|
# Rails 6.0+ has renamed the field to "target"
|
84
|
-
|
87
|
+
pattern.target
|
88
|
+
else
|
89
|
+
# https://github.com/rails/rails/pull/44367/files
|
90
|
+
T.unsafe(pattern).proxy_target
|
85
91
|
end
|
86
92
|
|
87
93
|
HANDLED_METHOD_TARGETS.include?(target.to_s)
|
@@ -109,7 +115,7 @@ module Tapioca
|
|
109
115
|
return "T.untyped"
|
110
116
|
end
|
111
117
|
|
112
|
-
|
118
|
+
as_nilable_type(type)
|
113
119
|
end
|
114
120
|
|
115
121
|
sig { params(klass: RBI::Scope, method: String, type: String).void }
|
@@ -184,7 +184,7 @@ module Tapioca
|
|
184
184
|
end
|
185
185
|
def populate_single_assoc_getter_setter(klass, constant, association_name, reflection)
|
186
186
|
association_class = type_for(constant, reflection)
|
187
|
-
association_type =
|
187
|
+
association_type = as_nilable_type(association_class)
|
188
188
|
|
189
189
|
klass.create_method(
|
190
190
|
association_name.to_s,
|
@@ -114,8 +114,14 @@ module Tapioca
|
|
114
114
|
constant.attribute_aliases.each do |attribute_name, column_name|
|
115
115
|
attribute_name = attribute_name.to_s
|
116
116
|
column_name = column_name.to_s
|
117
|
-
|
118
|
-
|
117
|
+
patterns = if constant.respond_to?(:attribute_method_patterns)
|
118
|
+
# https://github.com/rails/rails/pull/44367
|
119
|
+
T.unsafe(constant).attribute_method_patterns
|
120
|
+
else
|
121
|
+
constant.attribute_method_matchers
|
122
|
+
end
|
123
|
+
new_method_names = patterns.map { |m| m.method_name(attribute_name) }
|
124
|
+
old_method_names = patterns.map { |m| m.method_name(column_name) }
|
119
125
|
methods_to_add = new_method_names - old_method_names
|
120
126
|
|
121
127
|
add_methods_for_attribute(mod, constant, column_name, attribute_name, methods_to_add)
|
@@ -293,12 +299,6 @@ module Tapioca
|
|
293
299
|
return_type: "T::Boolean"
|
294
300
|
)
|
295
301
|
end
|
296
|
-
|
297
|
-
sig { params(type: String).returns(String) }
|
298
|
-
def as_nilable_type(type)
|
299
|
-
return type if type.start_with?("T.nilable(")
|
300
|
-
"T.nilable(#{type})"
|
301
|
-
end
|
302
302
|
end
|
303
303
|
end
|
304
304
|
end
|
@@ -250,6 +250,15 @@ module Tapioca
|
|
250
250
|
sig { returns(String) }
|
251
251
|
attr_reader :constant_name
|
252
252
|
|
253
|
+
sig { params(type: String).returns(String) }
|
254
|
+
def as_nilable_type(type)
|
255
|
+
if type.start_with?("T.nilable(", "::T.nilable(") || type == "T.untyped" || type == "::T.untyped"
|
256
|
+
type
|
257
|
+
else
|
258
|
+
"T.nilable(#{type})"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
253
262
|
sig { void }
|
254
263
|
def create_classes_and_includes
|
255
264
|
model.create_extend(CommonRelationMethodsModuleName)
|
@@ -532,7 +541,7 @@ module Tapioca
|
|
532
541
|
],
|
533
542
|
return_type: "T::Boolean"
|
534
543
|
)
|
535
|
-
when :find
|
544
|
+
when :find
|
536
545
|
create_common_method(
|
537
546
|
"find",
|
538
547
|
parameters: [
|
@@ -546,7 +555,15 @@ module Tapioca
|
|
546
555
|
parameters: [
|
547
556
|
create_rest_param("args", type: "T.untyped"),
|
548
557
|
],
|
549
|
-
return_type:
|
558
|
+
return_type: as_nilable_type(constant_name)
|
559
|
+
)
|
560
|
+
when :find_by!
|
561
|
+
create_common_method(
|
562
|
+
"find_by!",
|
563
|
+
parameters: [
|
564
|
+
create_rest_param("args", type: "T.untyped"),
|
565
|
+
],
|
566
|
+
return_type: constant_name
|
550
567
|
)
|
551
568
|
when :first, :last, :take
|
552
569
|
create_common_method(
|
@@ -562,7 +579,7 @@ module Tapioca
|
|
562
579
|
return_type = if method_name.end_with?("!")
|
563
580
|
constant_name
|
564
581
|
else
|
565
|
-
|
582
|
+
as_nilable_type(constant_name)
|
566
583
|
end
|
567
584
|
|
568
585
|
create_common_method(
|
@@ -107,7 +107,7 @@ module Tapioca
|
|
107
107
|
store_data.accessors.each do |accessor|
|
108
108
|
field = store_data.fields[accessor]
|
109
109
|
type = type_for(field.type_sym)
|
110
|
-
type =
|
110
|
+
type = as_nilable_type(type) if field.null
|
111
111
|
|
112
112
|
store_accessors_module = model.create_module("StoreAccessors")
|
113
113
|
generate_methods(store_accessors_module, field.name.to_s, type)
|
@@ -136,11 +136,13 @@ module Tapioca
|
|
136
136
|
method_def = signature.nil? ? method_def : signature.method
|
137
137
|
method_types = parameters_types_from_signature(method_def, signature)
|
138
138
|
|
139
|
-
|
139
|
+
parameters = T.let(method_def.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
|
140
|
+
|
141
|
+
parameters.each_with_index.map do |(type, name), index|
|
140
142
|
fallback_arg_name = "_arg#{index}"
|
141
143
|
|
142
|
-
name
|
143
|
-
name =
|
144
|
+
name = name ? name.to_s : fallback_arg_name
|
145
|
+
name = fallback_arg_name unless valid_parameter_name?(name)
|
144
146
|
method_type = T.must(method_types[index])
|
145
147
|
|
146
148
|
case type
|
@@ -173,6 +175,20 @@ module Tapioca
|
|
173
175
|
return_type = "T.untyped" if return_type == "<NOT-TYPED>"
|
174
176
|
return_type
|
175
177
|
end
|
178
|
+
|
179
|
+
sig { params(type: String).returns(String) }
|
180
|
+
def as_nilable_type(type)
|
181
|
+
if type.start_with?("T.nilable(", "::T.nilable(") || type == "T.untyped" || type == "::T.untyped"
|
182
|
+
type
|
183
|
+
else
|
184
|
+
"T.nilable(#{type})"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
sig { params(name: String).returns(T::Boolean) }
|
189
|
+
def valid_parameter_name?(name)
|
190
|
+
name.match?(/^[[[:alnum:]]_]+$/)
|
191
|
+
end
|
176
192
|
end
|
177
193
|
end
|
178
194
|
end
|
@@ -116,7 +116,7 @@ module Tapioca
|
|
116
116
|
if returns_collection
|
117
117
|
COLLECTION_TYPE.call(cache_type)
|
118
118
|
else
|
119
|
-
|
119
|
+
as_nilable_type(T.must(qualified_name_of(cache_type)))
|
120
120
|
end
|
121
121
|
rescue ArgumentError
|
122
122
|
"T.untyped"
|
@@ -175,18 +175,20 @@ module Tapioca
|
|
175
175
|
parameters << create_kw_opt_param("includes", default: "nil", type: "T.untyped")
|
176
176
|
|
177
177
|
if field.unique
|
178
|
+
type = T.must(qualified_name_of(constant))
|
179
|
+
|
178
180
|
klass.create_method(
|
179
181
|
"#{name}!",
|
180
182
|
class_method: true,
|
181
183
|
parameters: parameters,
|
182
|
-
return_type:
|
184
|
+
return_type: type
|
183
185
|
)
|
184
186
|
|
185
187
|
klass.create_method(
|
186
188
|
name,
|
187
189
|
class_method: true,
|
188
190
|
parameters: parameters,
|
189
|
-
return_type:
|
191
|
+
return_type: as_nilable_type(type)
|
190
192
|
)
|
191
193
|
else
|
192
194
|
klass.create_method(
|
@@ -64,12 +64,12 @@ module Tapioca
|
|
64
64
|
|
65
65
|
sig { override.returns(T::Enumerable[Module]) }
|
66
66
|
def gather_constants
|
67
|
-
|
67
|
+
all_classes.select do |const|
|
68
68
|
name = qualified_name_of(const)
|
69
69
|
|
70
70
|
name &&
|
71
71
|
!name.match?(BUILT_IN_MATCHER) &&
|
72
|
-
|
72
|
+
::Rails::Generators::Base > const
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
@@ -111,7 +111,7 @@ module Tapioca
|
|
111
111
|
if arg.required || arg.default
|
112
112
|
type
|
113
113
|
else
|
114
|
-
|
114
|
+
as_nilable_type(type)
|
115
115
|
end
|
116
116
|
end
|
117
117
|
end
|
@@ -71,14 +71,15 @@ module Tapioca
|
|
71
71
|
)
|
72
72
|
return if properties.keys.empty?
|
73
73
|
|
74
|
-
instance_methods = constant.instance_methods(false).map(&:to_s).to_set
|
75
|
-
|
76
74
|
root.create_path(constant) do |k|
|
77
|
-
|
78
|
-
|
79
|
-
|
75
|
+
smart_properties_methods_name = "SmartPropertiesGeneratedMethods"
|
76
|
+
k.create_module(smart_properties_methods_name) do |mod|
|
77
|
+
properties.values.each do |property|
|
78
|
+
generate_methods_for_property(mod, property)
|
80
79
|
end
|
81
80
|
end
|
81
|
+
|
82
|
+
k.create_include(smart_properties_methods_name)
|
82
83
|
end
|
83
84
|
end
|
84
85
|
|
@@ -95,26 +96,21 @@ module Tapioca
|
|
95
96
|
|
96
97
|
sig do
|
97
98
|
params(
|
98
|
-
|
99
|
-
property: ::SmartProperties::Property
|
100
|
-
block: T.proc.params(arg: String).returns(T::Boolean)
|
99
|
+
mod: RBI::Scope,
|
100
|
+
property: ::SmartProperties::Property
|
101
101
|
).void
|
102
102
|
end
|
103
|
-
def generate_methods_for_property(
|
103
|
+
def generate_methods_for_property(mod, property)
|
104
104
|
type = type_for(property)
|
105
105
|
|
106
106
|
if property.writable?
|
107
107
|
name = property.name.to_s
|
108
108
|
method_name = "#{name}="
|
109
109
|
|
110
|
-
|
111
|
-
method_name,
|
112
|
-
parameters: [create_param(name, type: type)],
|
113
|
-
return_type: type
|
114
|
-
) if block.call(method_name)
|
110
|
+
mod.create_method(method_name, parameters: [create_param(name, type: type)], return_type: type)
|
115
111
|
end
|
116
112
|
|
117
|
-
|
113
|
+
mod.create_method(property.reader.to_s, return_type: type)
|
118
114
|
end
|
119
115
|
|
120
116
|
BOOLEANS = T.let([
|
@@ -147,11 +143,8 @@ module Tapioca
|
|
147
143
|
"T.untyped"
|
148
144
|
end
|
149
145
|
|
150
|
-
# Early return for "T.untyped", nothing more to do.
|
151
|
-
return type if type == "T.untyped"
|
152
|
-
|
153
146
|
might_be_optional = Proc === required || !required
|
154
|
-
type =
|
147
|
+
type = as_nilable_type(type) if might_be_optional
|
155
148
|
|
156
149
|
type
|
157
150
|
end
|
@@ -151,9 +151,14 @@ module Tapioca
|
|
151
151
|
|
152
152
|
sig { params(mod: Module, helper: Module).returns(T::Boolean) }
|
153
153
|
def includes_helper?(mod, helper)
|
154
|
-
superclass_ancestors =
|
155
|
-
|
156
|
-
|
154
|
+
superclass_ancestors = []
|
155
|
+
|
156
|
+
if Class === mod
|
157
|
+
superclass = superclass_of(mod)
|
158
|
+
superclass_ancestors = ancestors_of(superclass) if superclass
|
159
|
+
end
|
160
|
+
|
161
|
+
(ancestors_of(mod) - superclass_ancestors).any? { |ancestor| helper == ancestor }
|
157
162
|
end
|
158
163
|
end
|
159
164
|
end
|
@@ -174,20 +174,31 @@ class DynamicMixinCompiler
|
|
174
174
|
|
175
175
|
sig { params(tree: RBI::Tree).returns([T::Array[Module], T::Array[Module]]) }
|
176
176
|
def compile_mixes_in_class_methods(tree)
|
177
|
-
includes = dynamic_includes.
|
178
|
-
includes.each do |mod|
|
177
|
+
includes = dynamic_includes.map do |mod|
|
179
178
|
qname = qualified_name_of(mod)
|
180
|
-
|
181
|
-
|
179
|
+
|
180
|
+
next if qname.nil? || qname.empty?
|
181
|
+
next if filtered_mixin?(qname)
|
182
|
+
|
183
|
+
tree << RBI::Include.new(qname)
|
184
|
+
|
185
|
+
mod
|
186
|
+
end.compact
|
182
187
|
|
183
188
|
# If we can generate multiple mixes_in_class_methods, then we want to use all dynamic extends that are not the
|
184
189
|
# constant itself
|
185
|
-
mixed_in_class_methods = dynamic_extends.select
|
190
|
+
mixed_in_class_methods = dynamic_extends.select do |mod|
|
191
|
+
mod != @constant && !module_included_by_another_dynamic_extend?(mod, dynamic_extends)
|
192
|
+
end
|
193
|
+
|
186
194
|
return [[], []] if mixed_in_class_methods.empty?
|
187
195
|
|
188
196
|
mixed_in_class_methods.each do |mod|
|
189
197
|
qualified_name = qualified_name_of(mod)
|
198
|
+
|
190
199
|
next if qualified_name.nil? || qualified_name.empty?
|
200
|
+
next if filtered_mixin?(qualified_name)
|
201
|
+
|
191
202
|
tree << RBI::MixesInClassMethods.new(qualified_name)
|
192
203
|
end
|
193
204
|
|
@@ -195,4 +206,18 @@ class DynamicMixinCompiler
|
|
195
206
|
rescue
|
196
207
|
[[], []] # silence errors
|
197
208
|
end
|
209
|
+
|
210
|
+
sig { params(mod: Module, dynamic_extends: T::Array[Module]).returns(T::Boolean) }
|
211
|
+
def module_included_by_another_dynamic_extend?(mod, dynamic_extends)
|
212
|
+
dynamic_extends.any? do |dynamic_extend|
|
213
|
+
mod != dynamic_extend && ancestors_of(dynamic_extend).include?(mod)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
sig { params(qualified_mixin_name: String).returns(T::Boolean) }
|
218
|
+
def filtered_mixin?(qualified_mixin_name)
|
219
|
+
# filter T:: namespace mixins that aren't T::Props
|
220
|
+
# T::Props and subconstants have semantic value
|
221
|
+
qualified_mixin_name.start_with?("::T::") && !qualified_mixin_name.start_with?("::T::Props")
|
222
|
+
end
|
198
223
|
end
|
@@ -17,10 +17,9 @@ module Tapioca
|
|
17
17
|
def compile
|
18
18
|
config = Spoom::Sorbet::Config.parse_file(@sorbet_path)
|
19
19
|
files = collect_files(config)
|
20
|
+
names_in_project = files.map { |file| [File.basename(file, ".rb"), true] }.to_h
|
20
21
|
files.flat_map do |file|
|
21
|
-
collect_requires(file).reject
|
22
|
-
name_in_project?(files, req)
|
23
|
-
end
|
22
|
+
collect_requires(file).reject { |req| names_in_project[req] }
|
24
23
|
end.sort.uniq.map do |name|
|
25
24
|
"require \"#{name}\"\n"
|
26
25
|
end.join
|
@@ -45,9 +44,10 @@ module Tapioca
|
|
45
44
|
|
46
45
|
sig { params(file_path: String).returns(T::Enumerable[String]) }
|
47
46
|
def collect_requires(file_path)
|
48
|
-
File.
|
47
|
+
File.binread(file_path).lines.map do |line|
|
49
48
|
/^\s*require\s*(\(\s*)?['"](?<name>[^'"]+)['"](\s*\))?/.match(line) { |m| m["name"] }
|
50
49
|
end.compact
|
50
|
+
.reject { |require| require.include?('#{') } # ignore interpolation
|
51
51
|
end
|
52
52
|
|
53
53
|
sig { params(config: Spoom::Sorbet::Config, file_path: Pathname).returns(T::Boolean) }
|
@@ -83,13 +83,6 @@ module Tapioca
|
|
83
83
|
def path_parts(path)
|
84
84
|
T.unsafe(path).descend.map { |part| part.basename.to_s }
|
85
85
|
end
|
86
|
-
|
87
|
-
sig { params(files: T::Enumerable[String], name: String).returns(T::Boolean) }
|
88
|
-
def name_in_project?(files, name)
|
89
|
-
files.any? do |file|
|
90
|
-
File.basename(file, ".rb") == name
|
91
|
-
end
|
92
|
-
end
|
93
86
|
end
|
94
87
|
end
|
95
88
|
end
|