tapioca 0.4.27 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) 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 +138 -153
  34. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
  35. data/lib/tapioca/compilers/todos_compiler.rb +1 -1
  36. data/lib/tapioca/config.rb +2 -0
  37. data/lib/tapioca/config_builder.rb +4 -2
  38. data/lib/tapioca/constant_locator.rb +6 -8
  39. data/lib/tapioca/gemfile.rb +2 -4
  40. data/lib/tapioca/generator.rb +124 -40
  41. data/lib/tapioca/generic_type_registry.rb +25 -98
  42. data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
  43. data/lib/tapioca/internal.rb +2 -9
  44. data/lib/tapioca/loader.rb +13 -33
  45. data/lib/tapioca/rbi_ext/model.rb +122 -0
  46. data/lib/tapioca/reflection.rb +131 -0
  47. data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
  48. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
  49. data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
  50. data/lib/tapioca/version.rb +1 -1
  51. data/lib/tapioca.rb +2 -1
  52. metadata +34 -22
  53. data/lib/tapioca/cli/main.rb +0 -146
  54. data/lib/tapioca/core_ext/class.rb +0 -28
  55. data/lib/tapioca/core_ext/string.rb +0 -18
  56. data/lib/tapioca/rbi/model.rb +0 -405
  57. data/lib/tapioca/rbi/printer.rb +0 -410
  58. data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
  59. data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
  60. data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
  61. data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -86
  62. 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