tapioca 0.4.24 → 0.5.1

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 +34 -22
  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 -86
  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 "active_record"
8
6
  rescue LoadError
@@ -99,11 +97,11 @@ module Tapioca
99
97
  class ActiveRecordColumns < Base
100
98
  extend T::Sig
101
99
 
102
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(ActiveRecord::Base)).void }
100
+ sig { override.params(root: RBI::Tree, constant: T.class_of(ActiveRecord::Base)).void }
103
101
  def decorate(root, constant)
104
102
  return unless constant.table_exists?
105
103
 
106
- root.path(constant) do |model|
104
+ root.create_path(constant) do |model|
107
105
  module_name = "GeneratedAttributeMethods"
108
106
 
109
107
  model.create_module(module_name) do |mod|
@@ -129,34 +127,31 @@ module Tapioca
129
127
 
130
128
  sig { override.returns(T::Enumerable[Module]) }
131
129
  def gather_constants
132
- ActiveRecord::Base.descendants.reject(&:abstract_class?)
130
+ descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
133
131
  end
134
132
 
135
133
  private
136
134
 
137
135
  sig do
138
136
  params(
139
- klass: Parlour::RbiGenerator::Namespace,
137
+ klass: RBI::Scope,
140
138
  name: String,
141
139
  methods_to_add: T.nilable(T::Array[String]),
142
- return_type: T.nilable(String),
140
+ return_type: String,
143
141
  parameters: T::Array[[String, String]]
144
142
  ).void
145
143
  end
146
- def add_method(klass, name, methods_to_add, return_type: nil, parameters: [])
147
- create_method(
148
- klass,
144
+ def add_method(klass, name, methods_to_add, return_type: "void", parameters: [])
145
+ klass.create_method(
149
146
  name,
150
- parameters: parameters.map do |param, type|
151
- Parlour::RbiGenerator::Parameter.new(param, type: type)
152
- end,
147
+ parameters: parameters.map { |param, type| create_param(param, type: type) },
153
148
  return_type: return_type
154
149
  ) if methods_to_add.nil? || methods_to_add.include?(name)
155
150
  end
156
151
 
157
152
  sig do
158
153
  params(
159
- klass: Parlour::RbiGenerator::Namespace,
154
+ klass: RBI::Scope,
160
155
  constant: T.class_of(ActiveRecord::Base),
161
156
  column_name: String,
162
157
  attribute_name: String,
@@ -164,7 +159,7 @@ module Tapioca
164
159
  ).void
165
160
  end
166
161
  def add_methods_for_attribute(klass, constant, column_name, attribute_name = column_name, methods_to_add = nil)
167
- getter_type, setter_type = type_for(constant, column_name)
162
+ getter_type, setter_type = ActiveRecordColumnTypeHelper.new(constant).type_for(column_name)
168
163
 
169
164
  # Added by ActiveRecord::AttributeMethods::Read
170
165
  #
@@ -298,96 +293,6 @@ module Tapioca
298
293
  )
299
294
  end
300
295
 
301
- sig do
302
- params(
303
- constant: T.class_of(ActiveRecord::Base),
304
- column_name: String
305
- ).returns([String, String])
306
- end
307
- def type_for(constant, column_name)
308
- return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(constant)
309
-
310
- column_type = constant.attribute_types[column_name]
311
-
312
- getter_type =
313
- case column_type
314
- when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
315
- "::Money"
316
- when ActiveRecord::Type::Integer
317
- "::Integer"
318
- when ActiveRecord::Type::String
319
- "::String"
320
- when ActiveRecord::Type::Date
321
- "::Date"
322
- when ActiveRecord::Type::Decimal
323
- "::BigDecimal"
324
- when ActiveRecord::Type::Float
325
- "::Float"
326
- when ActiveRecord::Type::Boolean
327
- "T::Boolean"
328
- when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
329
- "::DateTime"
330
- when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
331
- "::ActiveSupport::TimeWithZone"
332
- else
333
- handle_unknown_type(column_type)
334
- end
335
-
336
- column = constant.columns_hash[column_name]
337
- setter_type = getter_type
338
-
339
- if column&.null
340
- return ["T.nilable(#{getter_type})", "T.nilable(#{setter_type})"]
341
- end
342
-
343
- if column_name == constant.primary_key ||
344
- column_name == "created_at" ||
345
- column_name == "updated_at"
346
- getter_type = "T.nilable(#{getter_type})"
347
- end
348
-
349
- [getter_type, setter_type]
350
- end
351
-
352
- sig { params(constant: Module).returns(T::Boolean) }
353
- def do_not_generate_strong_types?(constant)
354
- Object.const_defined?(:StrongTypeGeneration) &&
355
- !(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
356
- end
357
-
358
- sig { params(column_type: Object).returns(String) }
359
- def handle_unknown_type(column_type)
360
- return "T.untyped" unless ActiveModel::Type::Value === column_type
361
-
362
- lookup_return_type_of_method(column_type, :deserialize) ||
363
- lookup_return_type_of_method(column_type, :cast) ||
364
- lookup_arg_type_of_method(column_type, :serialize) ||
365
- "T.untyped"
366
- end
367
-
368
- sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
369
- def lookup_return_type_of_method(column_type, method)
370
- signature = T::Private::Methods.signature_for_method(column_type.method(method))
371
- return unless signature
372
-
373
- return_type = signature.return_type
374
- return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
375
-
376
- return_type.to_s
377
- end
378
-
379
- sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
380
- def lookup_arg_type_of_method(column_type, method)
381
- signature = T::Private::Methods.signature_for_method(column_type.method(method))
382
- return unless signature
383
-
384
- # Arg types is an array [name, type] entries, so we desctructure the type of
385
- # first argument to get the first argument type
386
- _, first_argument_type = signature.arg_types.first
387
-
388
- first_argument_type.to_s
389
- end
390
-
391
296
  sig { params(type: String).returns(String) }
392
297
  def as_nilable_type(type)
393
298
  return type if type.start_with?("T.nilable(")
@@ -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 "active_record"
8
6
  rescue LoadError
@@ -60,11 +58,11 @@ module Tapioca
60
58
  class ActiveRecordEnum < Base
61
59
  extend T::Sig
62
60
 
63
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(::ActiveRecord::Base)).void }
61
+ sig { override.params(root: RBI::Tree, constant: T.class_of(::ActiveRecord::Base)).void }
64
62
  def decorate(root, constant)
65
63
  return if constant.defined_enums.empty?
66
64
 
67
- root.path(constant) do |model|
65
+ root.create_path(constant) do |model|
68
66
  module_name = "EnumMethodsModule"
69
67
 
70
68
  model.create_module(module_name) do |mod|
@@ -75,14 +73,14 @@ module Tapioca
75
73
 
76
74
  constant.defined_enums.each do |name, enum_map|
77
75
  type = type_for_enum(enum_map)
78
- create_method(model, name.pluralize, class_method: true, return_type: type)
76
+ model.create_method(name.pluralize, class_method: true, return_type: type)
79
77
  end
80
78
  end
81
79
  end
82
80
 
83
81
  sig { override.returns(T::Enumerable[Module]) }
84
82
  def gather_constants
85
- ::ActiveRecord::Base.descendants.reject(&:abstract_class?)
83
+ descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
86
84
  end
87
85
 
88
86
  private
@@ -93,21 +91,21 @@ module Tapioca
93
91
  value_type = if value_type.length == 1
94
92
  value_type.first
95
93
  else
96
- "T.any(#{value_type.join(', ')})"
94
+ "T.any(#{value_type.join(", ")})"
97
95
  end
98
96
 
99
97
  "T::Hash[T.any(String, Symbol), #{value_type}]"
100
98
  end
101
99
 
102
- sig { params(constant: T.class_of(::ActiveRecord::Base), klass: Parlour::RbiGenerator::Namespace).void }
100
+ sig { params(constant: T.class_of(::ActiveRecord::Base), klass: RBI::Scope).void }
103
101
  def generate_instance_methods(constant, klass)
104
102
  methods = constant.send(:_enum_methods_module).instance_methods
105
103
 
106
104
  methods.each do |method|
107
105
  method = method.to_s
108
- return_type = "T::Boolean" if method.end_with?("?")
106
+ return_type = method.end_with?("?") ? "T::Boolean" : "void"
109
107
 
110
- create_method(klass, method, return_type: return_type)
108
+ klass.create_method(method, return_type: return_type)
111
109
  end
112
110
  end
113
111
  end
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
5
-
6
4
  begin
7
5
  require "active_record"
8
6
  rescue LoadError
@@ -47,7 +45,7 @@ module Tapioca
47
45
 
48
46
  sig do
49
47
  override.params(
50
- root: Parlour::RbiGenerator::Namespace,
48
+ root: RBI::Tree,
51
49
  constant: T.class_of(::ActiveRecord::Base)
52
50
  ).void
53
51
  end
@@ -55,7 +53,7 @@ module Tapioca
55
53
  scope_method_names = constant.send(:generated_relation_methods).instance_methods(false)
56
54
  return if scope_method_names.empty?
57
55
 
58
- root.path(constant) do |model|
56
+ root.create_path(constant) do |model|
59
57
  module_name = "GeneratedRelationMethods"
60
58
 
61
59
  model.create_module(module_name) do |mod|
@@ -70,7 +68,7 @@ module Tapioca
70
68
 
71
69
  sig { override.returns(T::Enumerable[Module]) }
72
70
  def gather_constants
73
- ::ActiveRecord::Base.descendants.reject(&:abstract_class?)
71
+ descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
74
72
  end
75
73
 
76
74
  private
@@ -78,19 +76,18 @@ module Tapioca
78
76
  sig do
79
77
  params(
80
78
  scope_method: String,
81
- mod: Parlour::RbiGenerator::Namespace,
79
+ mod: RBI::Scope,
82
80
  ).void
83
81
  end
84
82
  def generate_scope_method(scope_method, mod)
85
83
  # This return type should actually be Model::ActiveRecord_Relation
86
84
  return_type = "T.untyped"
87
85
 
88
- create_method(
89
- mod,
86
+ mod.create_method(
90
87
  scope_method,
91
88
  parameters: [
92
- Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
93
- Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
89
+ create_rest_param("args", type: "T.untyped"),
90
+ create_block_param("blk", type: "T.untyped"),
94
91
  ],
95
92
  return_type: return_type,
96
93
  )
@@ -1,9 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
5
- require "tapioca/core_ext/class"
6
-
7
4
  begin
8
5
  require "activerecord-typedstore"
9
6
  rescue LoadError
@@ -92,7 +89,7 @@ module Tapioca
92
89
  sig do
93
90
  override
94
91
  .params(
95
- root: Parlour::RbiGenerator::Namespace,
92
+ root: RBI::Tree,
96
93
  constant: T.class_of(::ActiveRecord::Base)
97
94
  )
98
95
  .void
@@ -101,7 +98,7 @@ module Tapioca
101
98
  stores = constant.typed_stores
102
99
  return if stores.values.flat_map(&:accessors).empty?
103
100
 
104
- root.path(constant) do |model|
101
+ root.create_path(constant) do |model|
105
102
  stores.values.each do |store_data|
106
103
  store_data.accessors.each do |accessor|
107
104
  field = store_data.fields[accessor]
@@ -116,7 +113,7 @@ module Tapioca
116
113
 
117
114
  sig { override.returns(T::Enumerable[Module]) }
118
115
  def gather_constants
119
- ::ActiveRecord::Base.descendants.select do |klass|
116
+ descendants_of(::ActiveRecord::Base).select do |klass|
120
117
  klass.include?(ActiveRecord::TypedStore::Behavior)
121
118
  end
122
119
  end
@@ -142,7 +139,7 @@ module Tapioca
142
139
 
143
140
  sig do
144
141
  params(
145
- klass: Parlour::RbiGenerator::Namespace,
142
+ klass: RBI::Scope,
146
143
  name: String,
147
144
  type: String
148
145
  )
@@ -151,7 +148,7 @@ module Tapioca
151
148
  def generate_methods(klass, name, type)
152
149
  klass.create_method(
153
150
  "#{name}=",
154
- parameters: [Parlour::RbiGenerator::Parameter.new(name, type: type)],
151
+ parameters: [create_param(name, type: type)],
155
152
  return_type: type
156
153
  )
157
154
  klass.create_method(name, return_type: type)
@@ -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 "active_resource"
8
6
  rescue LoadError
@@ -63,16 +61,11 @@ module Tapioca
63
61
  class ActiveResource < Base
64
62
  extend T::Sig
65
63
 
66
- sig do
67
- override.params(
68
- root: Parlour::RbiGenerator::Namespace,
69
- constant: T.class_of(::ActiveResource::Base)
70
- ).void
71
- end
64
+ sig { override.params(root: RBI::Tree, constant: T.class_of(::ActiveResource::Base)).void }
72
65
  def decorate(root, constant)
73
66
  return if constant.schema.blank?
74
67
 
75
- root.path(constant) do |resource|
68
+ root.create_path(constant) do |resource|
76
69
  constant.schema.each do |attribute, type|
77
70
  create_schema_methods(resource, attribute, type)
78
71
  end
@@ -81,7 +74,7 @@ module Tapioca
81
74
 
82
75
  sig { override.returns(T::Enumerable[Module]) }
83
76
  def gather_constants
84
- ::ActiveResource::Base.descendants
77
+ descendants_of(::ActiveResource::Base)
85
78
  end
86
79
 
87
80
  private
@@ -104,36 +97,15 @@ module Tapioca
104
97
  TYPES.fetch(attr_type, "T.untyped")
105
98
  end
106
99
 
107
- sig do
108
- params(
109
- klass: Parlour::RbiGenerator::Namespace,
110
- attribute: String,
111
- type: String
112
- ).void
113
- end
100
+ sig { params(klass: RBI::Scope, attribute: String, type: String).void }
114
101
  def create_schema_methods(klass, attribute, type)
115
102
  return_type = type_for(type.to_sym)
116
103
 
117
- create_method(
118
- klass,
119
- attribute,
120
- return_type: return_type
121
- )
122
-
123
- create_method(
124
- klass,
125
- "#{attribute}?",
126
- return_type: "T::Boolean"
127
- )
128
-
129
- create_method(
130
- klass,
131
- "#{attribute}=",
132
- parameters: [
133
- Parlour::RbiGenerator::Parameter.new("value", type: return_type),
134
- ],
135
- return_type: return_type
136
- )
104
+ klass.create_method(attribute, return_type: return_type)
105
+ klass.create_method("#{attribute}?", return_type: "T::Boolean")
106
+ klass.create_method("#{attribute}=", parameters: [
107
+ create_param("value", type: return_type),
108
+ ], return_type: return_type)
137
109
  end
138
110
  end
139
111
  end
@@ -0,0 +1,98 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "active_storage"
6
+ require "active_storage/reflection"
7
+ rescue LoadError
8
+ return
9
+ end
10
+
11
+ module Tapioca
12
+ module Compilers
13
+ module Dsl
14
+ # `Tapioca::Compilers::Dsl::ActiveStorage` decorates RBI files for subclasses of
15
+ # `ActiveRecord::Base` that declare [one](https://edgeguides.rubyonrails.org/active_storage_overview.html#has-one-attached)
16
+ # or [many](https://edgeguides.rubyonrails.org/active_storage_overview.html#has-many-attached) attachments.
17
+ #
18
+ # For example, with the following `ActiveRecord::Base` subclass:
19
+ #
20
+ # ~~~rb
21
+ # class Post < ApplicationRecord
22
+ # has_one_attached :photo
23
+ # has_many_attached :blogs
24
+ # end
25
+ # ~~~
26
+ #
27
+ # this generator will produce the RBI file `post.rbi` with the following content:
28
+ #
29
+ # ~~~rbi
30
+ # # typed: strong
31
+ #
32
+ # class Post
33
+ # sig { returns(ActiveStorage::Attached::Many) }
34
+ # def blogs; end
35
+ #
36
+ # sig { params(attachable: T.untyped).returns(T.untyped) }
37
+ # def blogs=(attachable); end
38
+ #
39
+ # sig { returns(ActiveStorage::Attached::One) }
40
+ # def photo; end
41
+ #
42
+ # sig { params(attachable: T.untyped).returns(T.untyped) }
43
+ # def photo=(attachable); end
44
+ # end
45
+ # ~~~
46
+ class ActiveStorage < Base
47
+ extend T::Sig
48
+
49
+ sig do
50
+ override.params(root: RBI::Tree,
51
+ constant: T.all(Module, ::ActiveStorage::Reflection::ActiveRecordExtensions::ClassMethods)).void
52
+ end
53
+ def decorate(root, constant)
54
+ return if constant.reflect_on_all_attachments.empty?
55
+
56
+ root.create_path(constant) do |scope|
57
+ constant.reflect_on_all_attachments.each do |reflection|
58
+ type = type_of(reflection)
59
+ name = reflection.name.to_s
60
+ scope.create_method(
61
+ name,
62
+ return_type: type
63
+ )
64
+ scope.create_method(
65
+ "#{name}=",
66
+ parameters: [create_param("attachable", type: "T.untyped")],
67
+ return_type: "T.untyped"
68
+ )
69
+ end
70
+ end
71
+ end
72
+
73
+ sig { override.returns(T::Enumerable[Module]) }
74
+ def gather_constants
75
+ descendants_of(::ActiveRecord::Base)
76
+ .reject(&:abstract_class?)
77
+ .grep(::ActiveStorage::Reflection::ActiveRecordExtensions::ClassMethods)
78
+ end
79
+
80
+ private
81
+
82
+ sig do
83
+ params(reflection: ActiveRecord::Reflection::MacroReflection).returns(String)
84
+ end
85
+ def type_of(reflection)
86
+ case reflection
87
+ when ::ActiveStorage::Reflection::HasOneAttachedReflection
88
+ "ActiveStorage::Attached::One"
89
+ when ::ActiveStorage::Reflection::HasManyAttachedReflection
90
+ "ActiveStorage::Attached::Many"
91
+ else
92
+ "T.untyped"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end