tapioca 0.4.26 → 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 +1 -9
  44. data/lib/tapioca/loader.rb +11 -31
  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 -0
  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 "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