tapioca 0.11.8 → 0.11.9

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +116 -49
  3. data/lib/tapioca/cli.rb +76 -67
  4. data/lib/tapioca/commands/{dsl.rb → abstract_dsl.rb} +32 -78
  5. data/lib/tapioca/commands/{gem.rb → abstract_gem.rb} +26 -93
  6. data/lib/tapioca/commands/annotations.rb +9 -7
  7. data/lib/tapioca/commands/check_shims.rb +2 -0
  8. data/lib/tapioca/commands/command.rb +9 -2
  9. data/lib/tapioca/commands/configure.rb +2 -2
  10. data/lib/tapioca/commands/dsl_compiler_list.rb +31 -0
  11. data/lib/tapioca/commands/dsl_generate.rb +40 -0
  12. data/lib/tapioca/commands/dsl_verify.rb +25 -0
  13. data/lib/tapioca/commands/gem_generate.rb +51 -0
  14. data/lib/tapioca/commands/gem_sync.rb +37 -0
  15. data/lib/tapioca/commands/gem_verify.rb +36 -0
  16. data/lib/tapioca/commands/require.rb +2 -0
  17. data/lib/tapioca/commands/todo.rb +21 -2
  18. data/lib/tapioca/commands.rb +8 -2
  19. data/lib/tapioca/dsl/compiler.rb +8 -4
  20. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +3 -1
  21. data/lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb +94 -0
  22. data/lib/tapioca/dsl/compilers/active_record_columns.rb +19 -9
  23. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +3 -2
  24. data/lib/tapioca/dsl/compilers/active_support_concern.rb +1 -1
  25. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +1 -1
  26. data/lib/tapioca/dsl/compilers/graphql_mutation.rb +9 -2
  27. data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +208 -0
  28. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +20 -4
  29. data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +20 -3
  30. data/lib/tapioca/dsl/pipeline.rb +6 -4
  31. data/lib/tapioca/gem/pipeline.rb +103 -36
  32. data/lib/tapioca/gemfile.rb +13 -7
  33. data/lib/tapioca/helpers/git_attributes.rb +34 -0
  34. data/lib/tapioca/helpers/test/template.rb +4 -4
  35. data/lib/tapioca/internal.rb +1 -0
  36. data/lib/tapioca/loaders/dsl.rb +11 -1
  37. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +0 -27
  38. data/lib/tapioca/static/symbol_loader.rb +9 -8
  39. data/lib/tapioca/version.rb +1 -1
  40. metadata +19 -10
@@ -6,6 +6,17 @@ module Tapioca
6
6
  class Todo < CommandWithoutTracker
7
7
  include SorbetHelper
8
8
 
9
+ DEPRECATION_MESSAGE = T.let(<<~DEPRECATION, String)
10
+ The `todo` command is deprecated and will be removed in a future release.
11
+
12
+ If your project is still missing type definitions for constants, try the following:
13
+ 1. Regenerate gem RBIs by running `bin/tapioca gem --all` and `bin/tapioca annotations`
14
+ 2. Generate RBIs for DSLs by running `bin/tapioca dsl`
15
+ 3. If the missing constants are defined in files that a gem does not load by default,
16
+ manually require those files in `sorbet/tapioca/require.rb` and regenerate gem RBIs
17
+ 4. Manually create an RBI shim defining the missing constants
18
+ DEPRECATION
19
+
9
20
  sig do
10
21
  params(
11
22
  todo_file: String,
@@ -19,6 +30,16 @@ module Tapioca
19
30
  super()
20
31
  end
21
32
 
33
+ sig { void }
34
+ def run_with_deprecation
35
+ say(DEPRECATION_MESSAGE, :red)
36
+ say("")
37
+
38
+ run
39
+ end
40
+
41
+ private
42
+
22
43
  sig { override.void }
23
44
  def execute
24
45
  say("Finding all unresolved constants, this may take a few seconds... ")
@@ -43,8 +64,6 @@ module Tapioca
43
64
  say("Please review changes and commit them.", [:green, :bold])
44
65
  end
45
66
 
46
- private
47
-
48
67
  sig { params(constants: T::Array[String], command: String).returns(RBI::File) }
49
68
  def rbi(constants, command:)
50
69
  file = RBI::File.new
@@ -7,9 +7,15 @@ module Tapioca
7
7
  autoload :CommandWithoutTracker, "tapioca/commands/command_without_tracker"
8
8
  autoload :Annotations, "tapioca/commands/annotations"
9
9
  autoload :CheckShims, "tapioca/commands/check_shims"
10
- autoload :Dsl, "tapioca/commands/dsl"
10
+ autoload :AbstractDsl, "tapioca/commands/abstract_dsl"
11
+ autoload :DslCompilerList, "tapioca/commands/dsl_compiler_list"
12
+ autoload :DslGenerate, "tapioca/commands/dsl_generate"
13
+ autoload :DslVerify, "tapioca/commands/dsl_verify"
11
14
  autoload :Configure, "tapioca/commands/configure"
12
- autoload :Gem, "tapioca/commands/gem"
15
+ autoload :AbstractGem, "tapioca/commands/abstract_gem"
16
+ autoload :GemGenerate, "tapioca/commands/gem_generate"
17
+ autoload :GemSync, "tapioca/commands/gem_sync"
18
+ autoload :GemVerify, "tapioca/commands/gem_verify"
13
19
  autoload :Require, "tapioca/commands/require"
14
20
  autoload :Todo, "tapioca/commands/todo"
15
21
  end
@@ -45,14 +45,18 @@ module Tapioca
45
45
 
46
46
  sig { returns(T::Enumerable[T::Class[T.anything]]) }
47
47
  def all_classes
48
- @all_classes = T.let(@all_classes, T.nilable(T::Enumerable[T::Class[T.anything]]))
49
- @all_classes ||= T.cast(ObjectSpace.each_object(Class), T::Enumerable[T::Class[T.anything]]).each
48
+ @all_classes ||= T.let(
49
+ T.cast(ObjectSpace.each_object(Class), T::Enumerable[T::Class[T.anything]]).each,
50
+ T.nilable(T::Enumerable[T::Class[T.anything]]),
51
+ )
50
52
  end
51
53
 
52
54
  sig { returns(T::Enumerable[Module]) }
53
55
  def all_modules
54
- @all_modules = T.let(@all_modules, T.nilable(T::Enumerable[Module]))
55
- @all_modules ||= T.cast(ObjectSpace.each_object(Module), T::Enumerable[Module]).each
56
+ @all_modules ||= T.let(
57
+ T.cast(ObjectSpace.each_object(Module), T::Enumerable[Module]).each,
58
+ T.nilable(T::Enumerable[Module]),
59
+ )
56
60
  end
57
61
  end
58
62
 
@@ -126,7 +126,9 @@ module Tapioca
126
126
 
127
127
  sig { override.returns(T::Enumerable[Module]) }
128
128
  def gather_constants
129
- descendants_of(::ActionController::Base).reject(&:abstract?).select(&:name)
129
+ descendants_of(::ActionController::Base).select(&:name).select do |klass|
130
+ klass.const_defined?(:HelperMethods, false)
131
+ end
130
132
  end
131
133
  end
132
134
 
@@ -0,0 +1,94 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "active_model"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ module Tapioca
11
+ module Dsl
12
+ module Compilers
13
+ # `Tapioca::Dsl::Compilers::ActiveModelValidationsConfirmation` decorates RBI files for all
14
+ # classes that use [`ActiveModel::Validates::Confirmation`](https://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html#method-i-validates_confirmation_of).
15
+ #
16
+ # For example, with the following class:
17
+ #
18
+ # ~~~rb
19
+ # class User
20
+ # include ActiveModel::Validations
21
+ #
22
+ # validates_confirmation_of :password
23
+ #
24
+ # validates :email, confirmation: true
25
+ # end
26
+ # ~~~
27
+ #
28
+ # this compiler will produce an RBI file with the following content:
29
+ # ~~~rbi
30
+ # # typed: true
31
+ #
32
+ # class User
33
+ #
34
+ # sig { returns(T.untyped) }
35
+ # def email_confirmation; end
36
+ #
37
+ # sig { params(email_confirmation=: T.untyped).returns(T.untyped) }
38
+ # def email_confirmation=(email_confirmation); end
39
+ #
40
+ # sig { returns(T.untyped) }
41
+ # def password_confirmation; end
42
+ #
43
+ # sig { params(password_confirmation=: T.untyped).returns(T.untyped) }
44
+ # def password_confirmation=(password_confirmation); end
45
+ # end
46
+ # ~~~
47
+ class ActiveModelValidationsConfirmation < Compiler
48
+ extend T::Sig
49
+
50
+ ConstantType = type_member do
51
+ {
52
+ fixed: T.all(
53
+ T::Class[ActiveModel::Validations],
54
+ ActiveModel::Validations::HelperMethods,
55
+ ActiveModel::Validations::ClassMethods,
56
+ ),
57
+ }
58
+ end
59
+
60
+ class << self
61
+ sig { override.returns(T::Enumerable[Module]) }
62
+ def gather_constants
63
+ # Collect all the classes that include ActiveModel::Validations
64
+ all_classes.select do |c|
65
+ c < ActiveModel::Validations
66
+ end
67
+ end
68
+ end
69
+
70
+ sig { override.void }
71
+ def decorate
72
+ confirmation_validators = constant.validators.grep(ActiveModel::Validations::ConfirmationValidator)
73
+
74
+ return if confirmation_validators.empty?
75
+
76
+ # Create a RBI definition for each class that includes Active::Model::Validations
77
+ root.create_path(constant) do |klass|
78
+ # Create RBI definitions for all the attributes that use confirmation validation
79
+ confirmation_validators.each do |validator|
80
+ validator.attributes.each do |attr_name|
81
+ klass.create_method("#{attr_name}_confirmation", return_type: "T.untyped")
82
+ klass.create_method(
83
+ "#{attr_name}_confirmation=",
84
+ parameters: [create_param("#{attr_name}_confirmation", type: "T.untyped")],
85
+ return_type: "T.untyped",
86
+ )
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -110,8 +110,8 @@ module Tapioca
110
110
 
111
111
  root.create_path(constant) do |model|
112
112
  model.create_module(AttributeMethodsModuleName) do |mod|
113
- constant.attribute_names.each do |column_name|
114
- add_methods_for_attribute(mod, column_name)
113
+ (constant.attribute_names + ["id"]).uniq.each do |attribute_name|
114
+ add_methods_for_attribute(mod, attribute_name)
115
115
  end
116
116
 
117
117
  constant.attribute_aliases.each do |attribute_name, column_name|
@@ -127,7 +127,7 @@ module Tapioca
127
127
  old_method_names = patterns.map { |m| m.method_name(column_name) }
128
128
  methods_to_add = new_method_names - old_method_names
129
129
 
130
- add_methods_for_attribute(mod, column_name, attribute_name, methods_to_add)
130
+ add_methods_for_attribute(mod, attribute_name, column_name, methods_to_add)
131
131
  end
132
132
  end
133
133
 
@@ -152,13 +152,13 @@ module Tapioca
152
152
  name: String,
153
153
  methods_to_add: T.nilable(T::Array[String]),
154
154
  return_type: String,
155
- parameters: T::Array[[String, String]],
155
+ parameters: T::Array[RBI::TypedParam],
156
156
  ).void
157
157
  end
158
158
  def add_method(klass, name, methods_to_add, return_type: "void", parameters: [])
159
159
  klass.create_method(
160
160
  name,
161
- parameters: parameters.map { |param, type| create_param(param, type: type) },
161
+ parameters: parameters,
162
162
  return_type: return_type,
163
163
  ) if methods_to_add.nil? || methods_to_add.include?(name)
164
164
  end
@@ -166,13 +166,15 @@ module Tapioca
166
166
  sig do
167
167
  params(
168
168
  klass: RBI::Scope,
169
- column_name: String,
170
169
  attribute_name: String,
170
+ column_name: String,
171
171
  methods_to_add: T.nilable(T::Array[String]),
172
172
  ).void
173
173
  end
174
- def add_methods_for_attribute(klass, column_name, attribute_name = column_name, methods_to_add = nil)
175
- getter_type, setter_type = Helpers::ActiveRecordColumnTypeHelper.new(constant).type_for(column_name)
174
+ def add_methods_for_attribute(klass, attribute_name, column_name = attribute_name, methods_to_add = nil)
175
+ getter_type, setter_type = Helpers::ActiveRecordColumnTypeHelper
176
+ .new(constant)
177
+ .type_for(attribute_name, column_name)
176
178
 
177
179
  # Added by ActiveRecord::AttributeMethods::Read
178
180
  #
@@ -189,7 +191,7 @@ module Tapioca
189
191
  klass,
190
192
  "#{attribute_name}=",
191
193
  methods_to_add,
192
- parameters: [["value", setter_type]],
194
+ parameters: [create_param("value", type: setter_type)],
193
195
  return_type: setter_type,
194
196
  )
195
197
 
@@ -254,6 +256,10 @@ module Tapioca
254
256
  "#{attribute_name}_changed?",
255
257
  methods_to_add,
256
258
  return_type: "T::Boolean",
259
+ parameters: [
260
+ create_kw_opt_param("from", type: setter_type, default: "T.unsafe(nil)"),
261
+ create_kw_opt_param("to", type: setter_type, default: "T.unsafe(nil)"),
262
+ ],
257
263
  )
258
264
  add_method(
259
265
  klass,
@@ -277,6 +283,10 @@ module Tapioca
277
283
  "#{attribute_name}_previously_changed?",
278
284
  methods_to_add,
279
285
  return_type: "T::Boolean",
286
+ parameters: [
287
+ create_kw_opt_param("from", type: setter_type, default: "T.unsafe(nil)"),
288
+ create_kw_opt_param("to", type: setter_type, default: "T.unsafe(nil)"),
289
+ ],
280
290
  )
281
291
  add_method(
282
292
  klass,
@@ -98,6 +98,9 @@ module Tapioca
98
98
  return if stores.values.all? { |store| store.accessors.empty? }
99
99
 
100
100
  root.create_path(constant) do |model|
101
+ store_accessors_module = model.create_module("StoreAccessors")
102
+ model.create_include("StoreAccessors")
103
+
101
104
  stores.values.each do |store_data|
102
105
  store_data.accessors.each do |accessor, name|
103
106
  field = store_data.fields.fetch(accessor)
@@ -105,9 +108,7 @@ module Tapioca
105
108
  type = as_nilable_type(type) if field.null
106
109
  name ||= field.name # support < 1.5.0
107
110
 
108
- store_accessors_module = model.create_module("StoreAccessors")
109
111
  generate_methods(store_accessors_module, name.to_s, type)
110
- model.create_include("StoreAccessors")
111
112
  end
112
113
  end
113
114
  end
@@ -85,7 +85,7 @@ module Tapioca
85
85
 
86
86
  sig { params(concern: Module).returns(T::Array[Module]) }
87
87
  def dependencies_of(concern)
88
- concern.instance_variable_get(:@_dependencies)
88
+ concern.instance_variable_get(:@_dependencies) || []
89
89
  end
90
90
  end
91
91
 
@@ -55,7 +55,7 @@ module Tapioca
55
55
  root.create_path(constant) do |input_object|
56
56
  arguments.each do |argument|
57
57
  name = argument.keyword.to_s
58
- input_object.create_method(name, return_type: Helpers::GraphqlTypeHelper.type_for(argument.type))
58
+ input_object.create_method(name, return_type: Helpers::GraphqlTypeHelper.type_for(argument))
59
59
  end
60
60
  end
61
61
  end
@@ -42,7 +42,7 @@ module Tapioca
42
42
  class GraphqlMutation < Compiler
43
43
  extend T::Sig
44
44
 
45
- ConstantType = type_member { { fixed: T.class_of(GraphQL::Schema::InputObject) } }
45
+ ConstantType = type_member { { fixed: T.class_of(GraphQL::Schema::Mutation) } }
46
46
 
47
47
  sig { override.void }
48
48
  def decorate
@@ -59,7 +59,7 @@ module Tapioca
59
59
  params = compile_method_parameters_to_rbi(method_def).map do |param|
60
60
  name = param.param.name
61
61
  argument = arguments_by_name.fetch(name, nil)
62
- create_typed_param(param.param, argument ? Helpers::GraphqlTypeHelper.type_for(argument.type) : "T.untyped")
62
+ create_typed_param(param.param, argument_type(argument))
63
63
  end
64
64
 
65
65
  root.create_path(constant) do |mutation|
@@ -67,6 +67,13 @@ module Tapioca
67
67
  end
68
68
  end
69
69
 
70
+ sig { params(argument: T.nilable(GraphQL::Schema::Argument)).returns(String) }
71
+ def argument_type(argument)
72
+ return "T.untyped" unless argument
73
+
74
+ Helpers::GraphqlTypeHelper.type_for(argument)
75
+ end
76
+
70
77
  class << self
71
78
  extend T::Sig
72
79
 
@@ -0,0 +1,208 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "json_api_client"
6
+ rescue LoadError
7
+ # means JsonApiClient is not installed,
8
+ # so let's not even define the compiler.
9
+ return
10
+ end
11
+
12
+ module Tapioca
13
+ module Dsl
14
+ module Compilers
15
+ # `Tapioca::Dsl::Compilers::JsonApiClientResource` generates RBI files for classes that inherit
16
+ # [`JsonApiClient::Resource`](https://github.com/JsonApiClient/json_api_client).
17
+ #
18
+ # For example, with the following classes that inherits `JsonApiClient::Resource`:
19
+ #
20
+ # ~~~rb
21
+ # # user.rb
22
+ # class User < JsonApiClient::Resource
23
+ # has_many :posts
24
+ #
25
+ # property :name, type: :string
26
+ # property :is_admin, type: :boolean, default: false
27
+ # end
28
+ #
29
+ # # post.rb
30
+ # class Post < JsonApiClient::Resource
31
+ # belongs_to :user
32
+ #
33
+ # property :title, type: :string
34
+ # end
35
+ # ~~~
36
+ #
37
+ # this compiler will produce RBI files with the following content:
38
+ #
39
+ # ~~~rbi
40
+ # # user.rbi
41
+ # # typed: strong
42
+ #
43
+ # class User
44
+ # include JsonApiClientResourceGeneratedMethods
45
+ #
46
+ # module JsonApiClientResourceGeneratedMethods
47
+ # sig { returns(T::Boolean) }
48
+ # def is_admin; end
49
+ #
50
+ # sig { params(is_admin: T::Boolean).returns(T::Boolean) }
51
+ # def is_admin=(is_admin); end
52
+ #
53
+ # sig { returns(T.nilable(::String)) }
54
+ # def name; end
55
+ #
56
+ # sig { params(name: T.nilable(::String)).returns(T.nilable(::String)) }
57
+ # def name=(name); end
58
+ #
59
+ # sig { returns(T.nilable(T::Array[Post])) }
60
+ # def posts; end
61
+ #
62
+ # sig { params(posts: T.nilable(T::Array[Post])).returns(T.nilable(T::Array[Post])) }
63
+ # def posts=(posts); end
64
+ # end
65
+ # end
66
+ #
67
+ # # post.rbi
68
+ # # typed: strong
69
+ #
70
+ # class Post
71
+ # include JsonApiClientResourceGeneratedMethods
72
+ #
73
+ # module JsonApiClientResourceGeneratedMethods
74
+ # sig { returns(T.nilable(::String)) }
75
+ # def title; end
76
+ #
77
+ # sig { params(title: T.nilable(::String)).returns(T.nilable(::String)) }
78
+ # def title=(title); end
79
+ #
80
+ # sig { returns(T.nilable(::String)) }
81
+ # def user_id; end
82
+ #
83
+ # sig { params(user_id: T.nilable(::String)).returns(T.nilable(::String)) }
84
+ # def user_id=(user_id); end
85
+ # end
86
+ # end
87
+ # ~~~
88
+ class JsonApiClientResource < Compiler
89
+ extend T::Sig
90
+
91
+ ConstantType = type_member { { fixed: T.class_of(::JsonApiClient::Resource) } }
92
+
93
+ sig { override.void }
94
+ def decorate
95
+ schema = resource_schema
96
+ return if schema.nil? && constant.associations.empty?
97
+
98
+ root.create_path(constant) do |k|
99
+ module_name = "JsonApiClientResourceGeneratedMethods"
100
+ k.create_module(module_name) do |mod|
101
+ schema&.each_property do |property|
102
+ generate_methods_for_property(mod, property)
103
+ end
104
+
105
+ constant.associations.each do |association|
106
+ generate_methods_for_association(mod, association)
107
+ end
108
+ end
109
+
110
+ k.create_include(module_name)
111
+ end
112
+ end
113
+
114
+ class << self
115
+ extend T::Sig
116
+
117
+ sig { override.returns(T::Enumerable[Module]) }
118
+ def gather_constants
119
+ all_modules.select do |c|
120
+ name_of(c) && c < ::JsonApiClient::Resource
121
+ end
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ sig { returns(T.nilable(::JsonApiClient::Schema)) }
128
+ def resource_schema
129
+ schema = constant.schema
130
+
131
+ # empty? does not exist on JsonApiClient::Schema
132
+ schema if schema.size > 0 # rubocop:disable Style/ZeroLengthPredicate
133
+ end
134
+
135
+ sig do
136
+ params(
137
+ mod: RBI::Scope,
138
+ property: ::JsonApiClient::Schema::Property,
139
+ ).void
140
+ end
141
+ def generate_methods_for_property(mod, property)
142
+ type = type_for(property)
143
+
144
+ name = property.name.to_s
145
+
146
+ mod.create_method(name, return_type: type)
147
+ mod.create_method("#{name}=", parameters: [create_param(name, type: type)], return_type: type)
148
+ end
149
+
150
+ sig { params(property: ::JsonApiClient::Schema::Property).returns(String) }
151
+ def type_for(property)
152
+ type = ::JsonApiClient::Schema::TypeFactory.type_for(property.type)
153
+ return "T.untyped" if type.nil?
154
+
155
+ sorbet_type = if type.respond_to?(:sorbet_type)
156
+ type.sorbet_type
157
+ elsif type == ::JsonApiClient::Schema::Types::Integer
158
+ "::Integer"
159
+ elsif type == ::JsonApiClient::Schema::Types::String
160
+ "::String"
161
+ elsif type == ::JsonApiClient::Schema::Types::Float
162
+ "::Float"
163
+ elsif type == ::JsonApiClient::Schema::Types::Time
164
+ "::Time"
165
+ elsif type == ::JsonApiClient::Schema::Types::Decimal
166
+ "::BigDecimal"
167
+ elsif type == ::JsonApiClient::Schema::Types::Boolean
168
+ "T::Boolean"
169
+ else
170
+ "T.untyped"
171
+ end
172
+
173
+ if property.default.nil?
174
+ as_nilable_type(sorbet_type)
175
+ else
176
+ sorbet_type
177
+ end
178
+ end
179
+
180
+ sig do
181
+ params(
182
+ mod: RBI::Scope,
183
+ association: JsonApiClient::Associations::BaseAssociation,
184
+ ).void
185
+ end
186
+ def generate_methods_for_association(mod, association)
187
+ # If the association is broken, it will raise a NameError when trying to access the association_class
188
+ klass = association.association_class
189
+
190
+ name, type = case association
191
+ when ::JsonApiClient::Associations::BelongsTo::Association
192
+ # id must be a string: # https://jsonapi.org/format/#document-resource-object-identification
193
+ [association.param.to_s, "T.nilable(::String)"]
194
+ when ::JsonApiClient::Associations::HasOne::Association
195
+ [association.attr_name.to_s, "T.nilable(#{klass})"]
196
+ when ::JsonApiClient::Associations::HasMany::Association
197
+ [association.attr_name.to_s, "T.nilable(T::Array[#{klass}])"]
198
+ else
199
+ return # Unsupported association type
200
+ end
201
+
202
+ mod.create_method(name, return_type: type)
203
+ mod.create_method("#{name}=", parameters: [create_param(name, type: type)], return_type: type)
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -13,8 +13,26 @@ module Tapioca
13
13
  @constant = constant
14
14
  end
15
15
 
16
+ sig { params(attribute_name: String, column_name: String).returns([String, String]) }
17
+ def type_for(attribute_name, column_name = attribute_name)
18
+ return id_type if attribute_name == "id"
19
+
20
+ column_type_for(column_name)
21
+ end
22
+
23
+ private
24
+
25
+ sig { returns([String, String]) }
26
+ def id_type
27
+ if @constant.respond_to?(:composite_primary_key?) && T.unsafe(@constant).composite_primary_key?
28
+ @constant.primary_key.map(&method(:column_type_for)).map { |tuple| "[#{tuple.join(", ")}]" }
29
+ else
30
+ column_type_for(@constant.primary_key)
31
+ end
32
+ end
33
+
16
34
  sig { params(column_name: String).returns([String, String]) }
17
- def type_for(column_name)
35
+ def column_type_for(column_name)
18
36
  return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(@constant)
19
37
 
20
38
  column = @constant.columns_hash[column_name]
@@ -33,7 +51,7 @@ module Tapioca
33
51
  return [getter_type, as_nilable_type(setter_type)]
34
52
  end
35
53
 
36
- if column_name == @constant.primary_key ||
54
+ if Array(@constant.primary_key).include?(column_name) ||
37
55
  column_name == "created_at" ||
38
56
  column_name == "updated_at"
39
57
  getter_type = as_nilable_type(getter_type)
@@ -42,8 +60,6 @@ module Tapioca
42
60
  [getter_type, setter_type]
43
61
  end
44
62
 
45
- private
46
-
47
63
  sig { params(column_type: T.untyped).returns(String) }
48
64
  def type_for_activerecord_value(column_type)
49
65
  case column_type
@@ -9,8 +9,16 @@ module Tapioca
9
9
 
10
10
  extend T::Sig
11
11
 
12
- sig { params(type: GraphQL::Schema::Wrapper).returns(String) }
13
- def type_for(type)
12
+ sig { params(argument: GraphQL::Schema::Argument).returns(String) }
13
+ def type_for(argument)
14
+ type = if argument.loads
15
+ loads_type = ::GraphQL::Schema::Wrapper.new(argument.loads)
16
+ loads_type = loads_type.to_list_type if argument.type.list?
17
+ loads_type = loads_type.to_non_null_type if argument.type.non_null?
18
+ loads_type
19
+ else
20
+ argument.type
21
+ end
14
22
  unwrapped_type = type.unwrap
15
23
 
16
24
  parsed_type = case unwrapped_type
@@ -39,6 +47,10 @@ module Tapioca
39
47
  end
40
48
  when GraphQL::Schema::InputObject.singleton_class
41
49
  type_for_constant(unwrapped_type)
50
+ when GraphQL::Schema::NonNull.singleton_class
51
+ type_for(unwrapped_type.of_type)
52
+ when Module
53
+ Runtime::Reflection.qualified_name_of(unwrapped_type) || "T.untyped"
42
54
  else
43
55
  "T.untyped"
44
56
  end
@@ -47,7 +59,7 @@ module Tapioca
47
59
  parsed_type = "T::Array[#{parsed_type}]"
48
60
  end
49
61
 
50
- unless type.non_null?
62
+ unless type.non_null? || has_replaceable_default?(argument)
51
63
  parsed_type = RBIHelper.as_nilable_type(parsed_type)
52
64
  end
53
65
 
@@ -60,6 +72,11 @@ module Tapioca
60
72
  def type_for_constant(constant)
61
73
  Runtime::Reflection.qualified_name_of(constant) || "T.untyped"
62
74
  end
75
+
76
+ sig { params(argument: GraphQL::Schema::Argument).returns(T::Boolean) }
77
+ def has_replaceable_default?(argument)
78
+ !!argument.replace_null_with_default? && !argument.default_value.nil?
79
+ end
63
80
  end
64
81
  end
65
82
  end
@@ -105,10 +105,12 @@ module Tapioca
105
105
 
106
106
  sig { returns(T::Array[T.class_of(Compiler)]) }
107
107
  def compilers
108
- @compilers = T.let(@compilers, T.nilable(T::Array[T.class_of(Compiler)]))
109
- @compilers ||= Runtime::Reflection.descendants_of(Compiler).sort_by do |compiler|
110
- T.must(compiler.name)
111
- end
108
+ @compilers ||= T.let(
109
+ Runtime::Reflection.descendants_of(Compiler).sort_by do |compiler|
110
+ T.must(compiler.name)
111
+ end,
112
+ T.nilable(T::Array[T.class_of(Compiler)]),
113
+ )
112
114
  end
113
115
 
114
116
  private