tapioca 0.4.23 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -14
  3. data/README.md +2 -2
  4. data/Rakefile +5 -7
  5. data/exe/tapioca +2 -2
  6. data/lib/tapioca/cli.rb +256 -2
  7. data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
  8. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
  9. data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
  10. data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
  11. data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
  12. data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
  13. data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
  14. data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
  15. data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
  16. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
  17. data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
  18. data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
  19. data/lib/tapioca/compilers/dsl/active_support_concern.rb +108 -0
  20. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
  21. data/lib/tapioca/compilers/dsl/base.rb +96 -82
  22. data/lib/tapioca/compilers/dsl/config.rb +111 -0
  23. data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
  24. data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
  25. data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
  26. data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
  27. data/lib/tapioca/compilers/dsl/smart_properties.rb +19 -31
  28. data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
  29. data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
  30. data/lib/tapioca/compilers/dsl_compiler.rb +22 -38
  31. data/lib/tapioca/compilers/requires_compiler.rb +2 -2
  32. data/lib/tapioca/compilers/sorbet.rb +26 -5
  33. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +139 -154
  34. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
  35. data/lib/tapioca/compilers/symbol_table_compiler.rb +1 -1
  36. data/lib/tapioca/compilers/todos_compiler.rb +1 -1
  37. data/lib/tapioca/config.rb +2 -0
  38. data/lib/tapioca/config_builder.rb +4 -2
  39. data/lib/tapioca/constant_locator.rb +6 -8
  40. data/lib/tapioca/gemfile.rb +26 -19
  41. data/lib/tapioca/generator.rb +127 -43
  42. data/lib/tapioca/generic_type_registry.rb +25 -98
  43. data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
  44. data/lib/tapioca/internal.rb +1 -9
  45. data/lib/tapioca/loader.rb +14 -48
  46. data/lib/tapioca/rbi_ext/model.rb +122 -0
  47. data/lib/tapioca/reflection.rb +131 -0
  48. data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
  49. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
  50. data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
  51. data/lib/tapioca/version.rb +1 -1
  52. data/lib/tapioca.rb +2 -0
  53. metadata +35 -23
  54. data/lib/tapioca/cli/main.rb +0 -146
  55. data/lib/tapioca/core_ext/class.rb +0 -28
  56. data/lib/tapioca/core_ext/string.rb +0 -18
  57. data/lib/tapioca/rbi/model.rb +0 -405
  58. data/lib/tapioca/rbi/printer.rb +0 -410
  59. data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
  60. data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
  61. data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
  62. data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -82
  63. data/lib/tapioca/rbi/visitor.rb +0 -21
@@ -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: Parlour::RbiGenerator::Namespace, constant: T.class_of(::FrozenRecord::Base)).void }
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.path(constant) do |record|
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(mod, "#{attribute}?", return_type: 'T::Boolean')
81
- create_method(mod, attribute.to_s, return_type: 'T.untyped')
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.descendants.reject(&:abstract_class?)
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 do
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.path(constant) do |model|
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.descendants.select do |klass|
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: Parlour::RbiGenerator::Namespace,
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: Parlour::RbiGenerator::Namespace,
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
- Parlour::RbiGenerator::Parameter.new(arg.to_s, type: "T.untyped")
174
+ create_param(arg.to_s, type: "T.untyped")
170
175
  end
171
- parameters << Parlour::RbiGenerator::Parameter.new("includes:", default: "nil", type: "T.untyped")
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
- name,
203
+ "fetch_multi_by_#{fields_name}",
201
204
  class_method: true,
202
205
  parameters: [
203
- Parlour::RbiGenerator::Parameter.new("index_values", type: "T.untyped"),
204
- Parlour::RbiGenerator::Parameter.new("includes:", default: "nil", type: "T.untyped"),
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
@@ -1,6 +1,5 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
- require "parlour"
4
3
 
5
4
  begin
6
5
  require "google/protobuf"
@@ -62,67 +61,18 @@ module Tapioca
62
61
  # end
63
62
  # ~~~
64
63
  class Protobuf < Base
65
- # Parlour doesn't support type members out of the box, so adding the
66
- # ability to do that here. This should be upstreamed.
67
- class TypeMember < Parlour::RbiGenerator::RbiObject
68
- extend T::Sig
69
-
70
- sig { params(other: Object).returns(T::Boolean) }
71
- def ==(other)
72
- TypeMember === other && name == other.name
73
- end
74
-
75
- sig do
76
- override
77
- .params(indent_level: Integer, options: Parlour::RbiGenerator::Options)
78
- .returns(T::Array[String])
79
- end
80
- def generate_rbi(indent_level, options)
81
- [options.indented(indent_level, "#{name} = type_member")]
82
- end
83
-
84
- sig do
85
- override
86
- .params(others: T::Array[Parlour::RbiGenerator::RbiObject])
87
- .returns(T::Boolean)
88
- end
89
- def mergeable?(others)
90
- others.all? { |other| self == other }
91
- end
92
-
93
- sig { override.params(others: T::Array[Parlour::RbiGenerator::RbiObject]).void }
94
- def merge_into_self(others); end
95
-
96
- sig { override.returns(String) }
97
- def describe
98
- "Type Member (#{name})"
99
- end
100
- end
101
-
102
64
  class Field < T::Struct
103
65
  prop :name, String
104
66
  prop :type, String
105
67
  prop :init_type, String
106
68
  prop :default, String
107
-
108
- extend T::Sig
109
-
110
- sig { returns(Parlour::RbiGenerator::Parameter) }
111
- def to_init
112
- Parlour::RbiGenerator::Parameter.new("#{name}:", type: init_type, default: default)
113
- end
114
69
  end
115
70
 
116
71
  extend T::Sig
117
72
 
118
- sig do
119
- override.params(
120
- root: Parlour::RbiGenerator::Namespace,
121
- constant: Module
122
- ).void
123
- end
73
+ sig { override.params(root: RBI::Tree, constant: Module).void }
124
74
  def decorate(root, constant)
125
- root.path(constant) do |klass|
75
+ root.create_path(constant) do |klass|
126
76
  if constant == Google::Protobuf::RepeatedField
127
77
  create_type_members(klass, "Elem")
128
78
  elsif constant == Google::Protobuf::Map
@@ -132,7 +82,11 @@ module Tapioca
132
82
  fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
133
83
  fields.sort_by!(&:name)
134
84
 
135
- create_method(klass, "initialize", parameters: fields.map!(&:to_init))
85
+ parameters = fields.map do |field|
86
+ create_kw_opt_param(field.name, type: field.init_type, default: field.default)
87
+ end
88
+
89
+ klass.create_method("initialize", parameters: parameters, return_type: "void")
136
90
  end
137
91
  end
138
92
  end
@@ -146,12 +100,12 @@ module Tapioca
146
100
 
147
101
  private
148
102
 
149
- sig { params(klass: Parlour::RbiGenerator::Namespace, names: String).void }
103
+ sig { params(klass: RBI::Scope, names: String).void }
150
104
  def create_type_members(klass, *names)
151
105
  klass.create_extend("T::Generic")
152
106
 
153
107
  names.each do |name|
154
- klass.children << TypeMember.new(klass.generator, name)
108
+ klass.create_type_member(name)
155
109
  end
156
110
  end
157
111
 
@@ -186,34 +140,34 @@ module Tapioca
186
140
  # how Google names map entries.
187
141
  # https://github.com/protocolbuffers/protobuf/blob/f82e26/ruby/ext/google/protobuf_c/defs.c#L1963-L1966
188
142
  if descriptor.submsg_name.to_s.end_with?("_MapEntry_#{descriptor.name}")
189
- key = descriptor.subtype.lookup('key')
190
- value = descriptor.subtype.lookup('value')
143
+ key = descriptor.subtype.lookup("key")
144
+ value = descriptor.subtype.lookup("value")
191
145
 
192
146
  key_type = type_of(key)
193
147
  value_type = type_of(value)
194
148
  type = "Google::Protobuf::Map[#{key_type}, #{value_type}]"
195
149
 
196
150
  default_args = [key.type.inspect, value.type.inspect]
197
- default_args << value_type if %i[enum message].include?(value.type)
151
+ default_args << value_type if [:enum, :message].include?(value.type)
198
152
 
199
153
  Field.new(
200
154
  name: descriptor.name,
201
155
  type: type,
202
156
  init_type: "T.any(#{type}, T::Hash[#{key_type}, #{value_type}])",
203
- default: "Google::Protobuf::Map.new(#{default_args.join(', ')})"
157
+ default: "Google::Protobuf::Map.new(#{default_args.join(", ")})"
204
158
  )
205
159
  else
206
160
  elem_type = type_of(descriptor)
207
161
  type = "Google::Protobuf::RepeatedField[#{elem_type}]"
208
162
 
209
163
  default_args = [descriptor.type.inspect]
210
- default_args << elem_type if %i[enum message].include?(descriptor.type)
164
+ default_args << elem_type if [:enum, :message].include?(descriptor.type)
211
165
 
212
166
  Field.new(
213
167
  name: descriptor.name,
214
168
  type: type,
215
169
  init_type: "T.any(#{type}, T::Array[#{elem_type}])",
216
- default: "Google::Protobuf::RepeatedField.new(#{default_args.join(', ')})"
170
+ default: "Google::Protobuf::RepeatedField.new(#{default_args.join(", ")})"
217
171
  )
218
172
  end
219
173
  else
@@ -230,25 +184,21 @@ module Tapioca
230
184
 
231
185
  sig do
232
186
  params(
233
- klass: Parlour::RbiGenerator::Namespace,
187
+ klass: RBI::Scope,
234
188
  desc: Google::Protobuf::FieldDescriptor,
235
189
  ).returns(Field)
236
190
  end
237
191
  def create_descriptor_method(klass, desc)
238
192
  field = field_of(desc)
239
193
 
240
- create_method(
241
- klass,
194
+ klass.create_method(
242
195
  field.name,
243
196
  return_type: field.type
244
197
  )
245
198
 
246
- create_method(
247
- klass,
199
+ klass.create_method(
248
200
  "#{field.name}=",
249
- parameters: [
250
- Parlour::RbiGenerator::Parameter.new("value", type: field.type),
251
- ],
201
+ parameters: [create_param("value", type: field.type)],
252
202
  return_type: field.type
253
203
  )
254
204
 
@@ -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 "sidekiq"
8
6
  rescue LoadError
@@ -45,37 +43,52 @@ module Tapioca
45
43
  class SidekiqWorker < Base
46
44
  extend T::Sig
47
45
 
48
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(::Sidekiq::Worker)).void }
46
+ sig { override.params(root: RBI::Tree, constant: T.class_of(::Sidekiq::Worker)).void }
49
47
  def decorate(root, constant)
50
48
  return unless constant.instance_methods.include?(:perform)
51
49
 
52
- root.path(constant) do |worker|
50
+ root.create_path(constant) do |worker|
53
51
  method_def = constant.instance_method(:perform)
54
52
 
55
- async_params = compile_method_parameters_to_parlour(method_def)
53
+ async_params = compile_method_parameters_to_rbi(method_def)
56
54
 
57
55
  # `perform_at` and is just an alias for `perform_in` so both methods technically
58
56
  # accept a datetime, time, or numeric but we're typing them differently so they
59
57
  # semantically make sense.
60
58
  at_params = [
61
- Parlour::RbiGenerator::Parameter.new('interval', type: 'T.any(DateTime, Time)'),
59
+ create_param("interval", type: "T.any(DateTime, Time)"),
62
60
  *async_params,
63
61
  ]
64
62
  in_params = [
65
- Parlour::RbiGenerator::Parameter.new('interval', type: 'Numeric'),
63
+ create_param("interval", type: "Numeric"),
66
64
  *async_params,
67
65
  ]
68
66
 
69
- create_method(worker, 'perform_async', parameters: async_params, return_type: 'String', class_method: true)
70
- create_method(worker, 'perform_at', parameters: at_params, return_type: 'String', class_method: true)
71
- create_method(worker, 'perform_in', parameters: in_params, return_type: 'String', class_method: true)
67
+ generate_perform_method(constant, worker, "perform_async", async_params)
68
+ generate_perform_method(constant, worker, "perform_at", at_params)
69
+ generate_perform_method(constant, worker, "perform_in", in_params)
72
70
  end
73
71
  end
74
72
 
75
73
  sig { override.returns(T::Enumerable[Module]) }
76
74
  def gather_constants
77
- classes = T.cast(ObjectSpace.each_object(Class), T::Enumerable[Class])
78
- classes.select { |c| c < Sidekiq::Worker }
75
+ all_classes.select { |c| c < Sidekiq::Worker }
76
+ end
77
+
78
+ private
79
+
80
+ sig do
81
+ params(
82
+ constant: T.class_of(::Sidekiq::Worker),
83
+ worker: RBI::Scope,
84
+ method_name: String,
85
+ parameters: T::Array[RBI::TypedParam]
86
+ ).void
87
+ end
88
+ def generate_perform_method(constant, worker, method_name, parameters)
89
+ if constant.method(method_name.to_sym).owner == Sidekiq::Worker::ClassMethods
90
+ worker.create_method(method_name, parameters: parameters, return_type: "String", class_method: true)
91
+ end
79
92
  end
80
93
  end
81
94
  end