tapioca 0.9.4 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +21 -0
  3. data/README.md +48 -22
  4. data/lib/tapioca/cli.rb +17 -23
  5. data/lib/tapioca/commands/annotations.rb +2 -2
  6. data/lib/tapioca/commands/dsl.rb +43 -65
  7. data/lib/tapioca/commands/gem.rb +18 -50
  8. data/lib/tapioca/commands/todo.rb +1 -2
  9. data/lib/tapioca/dsl/compiler.rb +34 -37
  10. data/lib/tapioca/dsl/compilers/aasm.rb +7 -3
  11. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +7 -3
  12. data/lib/tapioca/dsl/compilers/action_mailer.rb +7 -3
  13. data/lib/tapioca/dsl/compilers/active_job.rb +7 -3
  14. data/lib/tapioca/dsl/compilers/active_model_attributes.rb +9 -5
  15. data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +11 -7
  16. data/lib/tapioca/dsl/compilers/active_record_associations.rb +7 -3
  17. data/lib/tapioca/dsl/compilers/active_record_columns.rb +7 -3
  18. data/lib/tapioca/dsl/compilers/active_record_enum.rb +7 -3
  19. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +8 -4
  20. data/lib/tapioca/dsl/compilers/active_record_relations.rb +7 -3
  21. data/lib/tapioca/dsl/compilers/active_record_scope.rb +5 -3
  22. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +8 -4
  23. data/lib/tapioca/dsl/compilers/active_resource.rb +7 -3
  24. data/lib/tapioca/dsl/compilers/active_storage.rb +9 -5
  25. data/lib/tapioca/dsl/compilers/active_support_concern.rb +21 -18
  26. data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +7 -3
  27. data/lib/tapioca/dsl/compilers/config.rb +12 -8
  28. data/lib/tapioca/dsl/compilers/frozen_record.rb +7 -3
  29. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +71 -0
  30. data/lib/tapioca/dsl/compilers/graphql_mutation.rb +81 -0
  31. data/lib/tapioca/dsl/compilers/identity_cache.rb +8 -4
  32. data/lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb +9 -5
  33. data/lib/tapioca/dsl/compilers/protobuf.rb +69 -25
  34. data/lib/tapioca/dsl/compilers/rails_generators.rb +12 -8
  35. data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +7 -3
  36. data/lib/tapioca/dsl/compilers/smart_properties.rb +10 -6
  37. data/lib/tapioca/dsl/compilers/state_machines.rb +7 -3
  38. data/lib/tapioca/dsl/compilers/url_helpers.rb +29 -26
  39. data/lib/tapioca/dsl/compilers.rb +0 -5
  40. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +1 -9
  41. data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +62 -0
  42. data/lib/tapioca/dsl/pipeline.rb +19 -11
  43. data/lib/tapioca/gem/listeners/source_location.rb +16 -9
  44. data/lib/tapioca/gemfile.rb +30 -0
  45. data/lib/tapioca/helpers/config_helper.rb +2 -2
  46. data/lib/tapioca/helpers/rbi_helper.rb +43 -30
  47. data/lib/tapioca/helpers/source_uri.rb +77 -0
  48. data/lib/tapioca/helpers/test/dsl_compiler.rb +1 -1
  49. data/lib/tapioca/helpers/test/isolation.rb +7 -3
  50. data/lib/tapioca/internal.rb +5 -1
  51. data/lib/tapioca/loaders/dsl.rb +84 -0
  52. data/lib/tapioca/loaders/gem.rb +85 -0
  53. data/lib/tapioca/{runtime → loaders}/loader.rb +39 -31
  54. data/lib/tapioca/repo_index.rb +12 -8
  55. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +2 -2
  56. data/lib/tapioca/runtime/trackers/constant_definition.rb +15 -13
  57. data/lib/tapioca/runtime/trackers/mixin.rb +35 -31
  58. data/lib/tapioca/runtime/trackers/required_ancestor.rb +21 -19
  59. data/lib/tapioca/static/requires_compiler.rb +2 -3
  60. data/lib/tapioca/static/symbol_table_parser.rb +19 -17
  61. data/lib/tapioca/version.rb +1 -1
  62. data/lib/tapioca.rb +24 -20
  63. metadata +10 -5
  64. data/Gemfile +0 -53
  65. data/Rakefile +0 -18
@@ -0,0 +1,81 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "graphql"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ require "tapioca/dsl/helpers/graphql_type_helper"
11
+
12
+ module Tapioca
13
+ module Dsl
14
+ module Compilers
15
+ # `Tapioca::Dsl::Compilers::GraphqlMutation` generates RBI files for subclasses of
16
+ # [`GraphQL::Schema::Mutation`](https://graphql-ruby.org/api-doc/2.0.11/GraphQL/Schema/Mutation).
17
+ #
18
+ # For example, with the following `GraphQL::Schema::Mutation` subclass:
19
+ #
20
+ # ~~~rb
21
+ # class CreateComment < GraphQL::Schema::Mutation
22
+ # argument :body, String, required: true
23
+ # argument :post_id, ID, required: true
24
+ #
25
+ # def resolve(body:, post_id:)
26
+ # # ...
27
+ # end
28
+ # end
29
+ # ~~~
30
+ #
31
+ # this compiler will produce the RBI file `notify_user_job.rbi` with the following content:
32
+ #
33
+ # ~~~rbi
34
+ # # create_comment.rbi
35
+ # # typed: true
36
+ # class CreateComment
37
+ # sig { params(body: String, post_id: String).returns(T.untyped) }
38
+ # def resolve(body:, post_id:); end
39
+ # end
40
+ # ~~~
41
+ class GraphqlMutation < Compiler
42
+ extend T::Sig
43
+
44
+ ConstantType = type_member { { fixed: T.class_of(GraphQL::Schema::InputObject) } }
45
+
46
+ sig { override.void }
47
+ def decorate
48
+ return unless constant.method_defined?(:resolve)
49
+
50
+ method_def = constant.instance_method(:resolve)
51
+ return if signature_of(method_def) # Skip if the mutation already has an inline sig
52
+
53
+ arguments = constant.all_argument_definitions
54
+ return if arguments.empty?
55
+
56
+ arguments_by_name = arguments.to_h { |a| [a.keyword.to_s, a] }
57
+ graphql_type_helper = Helpers::GraphqlTypeHelper.new
58
+
59
+ params = compile_method_parameters_to_rbi(method_def).map do |param|
60
+ name = param.param.name
61
+ argument = arguments_by_name.fetch(name, nil)
62
+ create_typed_param(param.param, argument ? graphql_type_helper.type_for(argument.type) : "T.untyped")
63
+ end
64
+
65
+ root.create_path(constant) do |mutation|
66
+ mutation.create_method("resolve", parameters: params, return_type: "T.untyped")
67
+ end
68
+ end
69
+
70
+ class << self
71
+ extend T::Sig
72
+
73
+ sig { override.returns(T::Enumerable[Module]) }
74
+ def gather_constants
75
+ all_classes.select { |c| c < GraphQL::Schema::Mutation && c != GraphQL::Schema::RelayClassicMutation }
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -100,10 +100,14 @@ module Tapioca
100
100
  end
101
101
  end
102
102
 
103
- sig { override.returns(T::Enumerable[Module]) }
104
- def self.gather_constants
105
- descendants_of(::ActiveRecord::Base).select do |klass|
106
- klass < ::IdentityCache::WithoutPrimaryIndex
103
+ class << self
104
+ extend T::Sig
105
+
106
+ sig { override.returns(T::Enumerable[Module]) }
107
+ def gather_constants
108
+ descendants_of(::ActiveRecord::Base).select do |klass|
109
+ klass < ::IdentityCache::WithoutPrimaryIndex
110
+ end
107
111
  end
108
112
  end
109
113
 
@@ -63,11 +63,15 @@ module Tapioca
63
63
  end
64
64
  end
65
65
 
66
- sig { override.returns(T::Enumerable[Module]) }
67
- def self.gather_constants
68
- # Select all non-anonymous modules that have overridden Module.included
69
- all_modules.select do |mod|
70
- !mod.is_a?(Class) && name_of(mod) && Runtime::Reflection.method_of(mod, :included).owner != Module
66
+ class << self
67
+ extend T::Sig
68
+
69
+ sig { override.returns(T::Enumerable[Module]) }
70
+ def gather_constants
71
+ # Select all non-anonymous modules that have overridden Module.included
72
+ all_modules.select do |mod|
73
+ !mod.is_a?(Class) && name_of(mod) && Runtime::Reflection.method_of(mod, :included).owner != Module
74
+ end
71
75
  end
72
76
  end
73
77
  end
@@ -90,33 +90,70 @@ module Tapioca
90
90
  elsif constant == Google::Protobuf::Map
91
91
  create_type_members(klass, "Key", "Value")
92
92
  else
93
- descriptor = T.let(T.unsafe(constant).descriptor, Google::Protobuf::Descriptor)
94
- descriptor.each_oneof { |oneof| create_oneof_method(klass, oneof) }
95
- fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
96
- fields.sort_by!(&:name)
93
+ descriptor = T.unsafe(constant).descriptor
97
94
 
98
- parameters = fields.map do |field|
99
- create_kw_opt_param(field.name, type: field.init_type, default: field.default)
100
- end
95
+ case descriptor
96
+ when Google::Protobuf::EnumDescriptor
97
+ descriptor.to_h.each do |sym, val|
98
+ klass.create_constant(sym.to_s, value: val.to_s)
99
+ end
100
+
101
+ klass.create_method(
102
+ "lookup",
103
+ parameters: [create_param("number", type: "Integer")],
104
+ return_type: "T.nilable(Symbol)",
105
+ class_method: true,
106
+ )
107
+ klass.create_method(
108
+ "resolve",
109
+ parameters: [create_param("symbol", type: "Symbol")],
110
+ return_type: "T.nilable(Integer)",
111
+ class_method: true,
112
+ )
113
+ klass.create_method(
114
+ "descriptor",
115
+ return_type: "Google::Protobuf::EnumDescriptor",
116
+ class_method: true,
117
+ )
118
+ when Google::Protobuf::Descriptor
119
+ descriptor.each_oneof { |oneof| create_oneof_method(klass, oneof) }
120
+ fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
121
+ fields.sort_by!(&:name)
122
+
123
+ parameters = fields.map do |field|
124
+ create_kw_opt_param(field.name, type: field.init_type, default: field.default)
125
+ end
101
126
 
102
- if fields.all? { |field| FIELD_RE.match?(field.name) }
103
- klass.create_method("initialize", parameters: parameters, return_type: "void")
127
+ if fields.all? { |field| FIELD_RE.match?(field.name) }
128
+ klass.create_method("initialize", parameters: parameters, return_type: "void")
129
+ else
130
+ # One of the fields has an incorrect name for a named parameter so creating the default initialize for
131
+ # it would create a RBI with a syntax error.
132
+ # The workaround is to create an initialize that takes a **kwargs instead.
133
+ kwargs_parameter = create_kw_rest_param("fields", type: "T.untyped")
134
+ klass.create_method("initialize", parameters: [kwargs_parameter], return_type: "void")
135
+ end
104
136
  else
105
- # One of the fields has an incorrect name for a named parameter so creating the default initialize for
106
- # it would create a RBI with a syntax error.
107
- # The workaround is to create an initialize that takes a **kwargs instead.
108
- kwargs_parameter = create_kw_rest_param("fields", type: "T.untyped")
109
- klass.create_method("initialize", parameters: [kwargs_parameter], return_type: "void")
137
+ raise TypeError, "Unexpected descriptor class: #{descriptor.class.name}"
110
138
  end
111
139
  end
112
140
  end
113
141
  end
114
142
 
115
- sig { override.returns(T::Enumerable[Module]) }
116
- def self.gather_constants
117
- marker = Google::Protobuf::MessageExts::ClassMethods
118
- results = T.cast(ObjectSpace.each_object(marker).to_a, T::Array[Module])
119
- results.any? ? results + [Google::Protobuf::RepeatedField, Google::Protobuf::Map] : []
143
+ class << self
144
+ extend T::Sig
145
+
146
+ sig { override.returns(T::Enumerable[Module]) }
147
+ def gather_constants
148
+ marker = Google::Protobuf::MessageExts::ClassMethods
149
+
150
+ enum_modules = ObjectSpace.each_object(Google::Protobuf::EnumDescriptor).map do |desc|
151
+ T.cast(desc, Google::Protobuf::EnumDescriptor).enummodule
152
+ end
153
+
154
+ results = T.cast(ObjectSpace.each_object(marker).to_a, T::Array[Module]).concat(enum_modules)
155
+ results.any? ? results + [Google::Protobuf::RepeatedField, Google::Protobuf::Map] : []
156
+ end
120
157
  end
121
158
 
122
159
  private
@@ -138,7 +175,13 @@ module Tapioca
138
175
  def type_of(descriptor)
139
176
  case descriptor.type
140
177
  when :enum
141
- descriptor.subtype.enummodule.name
178
+ # According to https://developers.google.com/protocol-buffers/docs/reference/ruby-generated#enum
179
+ # > You may assign either a number or a symbol to an enum field.
180
+ # > When reading the value back, it will be a symbol if the enum
181
+ # > value is known, or a number if it is unknown. Since proto3 uses
182
+ # > open enum semantics, any number may be assigned to an enum
183
+ # > field, even if it was not defined in the enum.
184
+ "T.any(Symbol, Integer)"
142
185
  when :message
143
186
  descriptor.subtype.msgclass.name
144
187
  when :int32, :int64, :uint32, :uint64
@@ -179,7 +222,7 @@ module Tapioca
179
222
  Field.new(
180
223
  name: descriptor.name,
181
224
  type: type,
182
- init_type: "T.any(#{type}, T::Hash[#{key_type}, #{value_type}])",
225
+ init_type: "T.nilable(T.any(#{type}, T::Hash[#{key_type}, #{value_type}]))",
183
226
  default: "Google::Protobuf::Map.new(#{default_args.join(", ")})"
184
227
  )
185
228
  else
@@ -192,18 +235,19 @@ module Tapioca
192
235
  Field.new(
193
236
  name: descriptor.name,
194
237
  type: type,
195
- init_type: "T.any(#{type}, T::Array[#{elem_type}])",
238
+ init_type: "T.nilable(T.any(#{type}, T::Array[#{elem_type}]))",
196
239
  default: "Google::Protobuf::RepeatedField.new(#{default_args.join(", ")})"
197
240
  )
198
241
  end
199
242
  else
200
243
  type = type_of(descriptor)
201
- type = as_nilable_type(type) if nilable_descriptor?(descriptor)
244
+ nilable_type = as_nilable_type(type)
245
+ type = nilable_type if nilable_descriptor?(descriptor)
202
246
 
203
247
  Field.new(
204
248
  name: descriptor.name,
205
249
  type: type,
206
- init_type: type,
250
+ init_type: nilable_type,
207
251
  default: "nil"
208
252
  )
209
253
  end
@@ -226,7 +270,7 @@ module Tapioca
226
270
  klass.create_method(
227
271
  "#{field.name}=",
228
272
  parameters: [create_param("value", type: field.type)],
229
- return_type: field.type
273
+ return_type: "void"
230
274
  )
231
275
 
232
276
  field
@@ -64,14 +64,18 @@ module Tapioca
64
64
  end
65
65
  end
66
66
 
67
- sig { override.returns(T::Enumerable[Module]) }
68
- def self.gather_constants
69
- all_classes.select do |const|
70
- name = qualified_name_of(const)
71
-
72
- name &&
73
- !name.match?(BUILT_IN_MATCHER) &&
74
- ::Rails::Generators::Base > const
67
+ class << self
68
+ extend T::Sig
69
+
70
+ sig { override.returns(T::Enumerable[Module]) }
71
+ def gather_constants
72
+ all_classes.select do |const|
73
+ name = qualified_name_of(const)
74
+
75
+ name &&
76
+ !name.match?(BUILT_IN_MATCHER) &&
77
+ ::Rails::Generators::Base > const
78
+ end
75
79
  end
76
80
  end
77
81
 
@@ -72,9 +72,13 @@ module Tapioca
72
72
  end
73
73
  end
74
74
 
75
- sig { override.returns(T::Enumerable[Module]) }
76
- def self.gather_constants
77
- all_classes.select { |c| c < Sidekiq::Worker }
75
+ class << self
76
+ extend T::Sig
77
+
78
+ sig { override.returns(T::Enumerable[Module]) }
79
+ def gather_constants
80
+ all_classes.select { |c| c < Sidekiq::Worker }
81
+ end
78
82
  end
79
83
 
80
84
  private
@@ -85,12 +85,16 @@ module Tapioca
85
85
  end
86
86
  end
87
87
 
88
- sig { override.returns(T::Enumerable[Module]) }
89
- def self.gather_constants
90
- all_modules.select do |c|
91
- name_of(c) &&
92
- c != ::SmartProperties::Validations::Ancestor &&
93
- c < ::SmartProperties && ::SmartProperties::ClassMethods === c
88
+ class << self
89
+ extend T::Sig
90
+
91
+ sig { override.returns(T::Enumerable[Module]) }
92
+ def gather_constants
93
+ all_modules.select do |c|
94
+ name_of(c) &&
95
+ c != ::SmartProperties::Validations::Ancestor &&
96
+ c < ::SmartProperties && ::SmartProperties::ClassMethods === c
97
+ end
94
98
  end
95
99
  end
96
100
 
@@ -160,9 +160,13 @@ module Tapioca
160
160
  end
161
161
  end
162
162
 
163
- sig { override.returns(T::Enumerable[Module]) }
164
- def self.gather_constants
165
- all_classes.select { |mod| mod < ::StateMachines::InstanceMethods }
163
+ class << self
164
+ extend T::Sig
165
+
166
+ sig { override.returns(T::Enumerable[Module]) }
167
+ def gather_constants
168
+ all_classes.select { |mod| mod < ::StateMachines::InstanceMethods }
169
+ end
166
170
  end
167
171
 
168
172
  private
@@ -107,23 +107,39 @@ module Tapioca
107
107
  ActionView::Helpers,
108
108
  ], T::Array[Module])
109
109
 
110
- sig { override.returns(T::Enumerable[Module]) }
111
- def self.gather_constants
112
- return [] unless Rails.application
110
+ class << self
111
+ extend T::Sig
112
+ sig { override.returns(T::Enumerable[Module]) }
113
+ def gather_constants
114
+ return [] unless Rails.application
115
+
116
+ Object.const_set(:GeneratedUrlHelpersModule, Rails.application.routes.named_routes.url_helpers_module)
117
+ Object.const_set(:GeneratedPathHelpersModule, Rails.application.routes.named_routes.path_helpers_module)
118
+
119
+ constants = all_modules.select do |mod|
120
+ next unless name_of(mod)
121
+
122
+ includes_helper?(mod, GeneratedUrlHelpersModule) ||
123
+ includes_helper?(mod, GeneratedPathHelpersModule) ||
124
+ includes_helper?(mod.singleton_class, GeneratedUrlHelpersModule) ||
125
+ includes_helper?(mod.singleton_class, GeneratedPathHelpersModule)
126
+ end
113
127
 
114
- Object.const_set(:GeneratedUrlHelpersModule, Rails.application.routes.named_routes.url_helpers_module)
115
- Object.const_set(:GeneratedPathHelpersModule, Rails.application.routes.named_routes.path_helpers_module)
128
+ constants.concat(NON_DISCOVERABLE_INCLUDERS)
129
+ end
116
130
 
117
- constants = all_modules.select do |mod|
118
- next unless name_of(mod)
131
+ sig { params(mod: Module, helper: Module).returns(T::Boolean) }
132
+ private def includes_helper?(mod, helper)
133
+ superclass_ancestors = []
119
134
 
120
- includes_helper?(mod, GeneratedUrlHelpersModule) ||
121
- includes_helper?(mod, GeneratedPathHelpersModule) ||
122
- includes_helper?(mod.singleton_class, GeneratedUrlHelpersModule) ||
123
- includes_helper?(mod.singleton_class, GeneratedPathHelpersModule)
124
- end
135
+ if Class === mod
136
+ superclass = superclass_of(mod)
137
+ superclass_ancestors = ancestors_of(superclass) if superclass
138
+ end
125
139
 
126
- constants.concat(NON_DISCOVERABLE_INCLUDERS)
140
+ ancestors = Set.new.compare_by_identity.merge(ancestors_of(mod)).subtract(superclass_ancestors)
141
+ ancestors.any? { |ancestor| helper == ancestor }
142
+ end
127
143
  end
128
144
 
129
145
  private
@@ -152,19 +168,6 @@ module Tapioca
152
168
  mod.create_include(T.must(helper_module.name)) if include_helper
153
169
  mod.create_extend(T.must(helper_module.name)) if extend_helper
154
170
  end
155
-
156
- sig { params(mod: Module, helper: Module).returns(T::Boolean) }
157
- private_class_method def self.includes_helper?(mod, helper)
158
- superclass_ancestors = []
159
-
160
- if Class === mod
161
- superclass = superclass_of(mod)
162
- superclass_ancestors = ancestors_of(superclass) if superclass
163
- end
164
-
165
- ancestors = Set.new.compare_by_identity.merge(ancestors_of(mod)).subtract(superclass_ancestors)
166
- ancestors.any? { |ancestor| helper == ancestor }
167
- end
168
171
  end
169
172
  end
170
173
  end
@@ -4,11 +4,6 @@
4
4
  module Tapioca
5
5
  module Dsl
6
6
  module Compilers
7
- DIRECTORY = T.let(
8
- File.expand_path("compilers", __dir__),
9
- String
10
- )
11
-
12
7
  # DSL compilers are either built-in to Tapioca and live under the
13
8
  # `Tapioca::Dsl::Compilers` namespace (i.e. this namespace), and
14
9
  # can be referred to by just using the class name, or they live in
@@ -6,6 +6,7 @@ module Tapioca
6
6
  module Helpers
7
7
  class ActiveRecordColumnTypeHelper
8
8
  extend T::Sig
9
+ include RBIHelper
9
10
 
10
11
  sig { params(constant: T.class_of(ActiveRecord::Base)).void }
11
12
  def initialize(constant)
@@ -74,15 +75,6 @@ module Tapioca
74
75
  !(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
75
76
  end
76
77
 
77
- sig { params(type: String).returns(String) }
78
- def as_nilable_type(type)
79
- if type.start_with?("T.nilable(") || type == "T.untyped"
80
- type
81
- else
82
- "T.nilable(#{type})"
83
- end
84
- end
85
-
86
78
  sig { params(column_type: BasicObject).returns(String) }
87
79
  def handle_unknown_type(column_type)
88
80
  return "T.untyped" unless ActiveModel::Type::Value === column_type
@@ -0,0 +1,62 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Dsl
6
+ module Helpers
7
+ class GraphqlTypeHelper
8
+ extend T::Sig
9
+ include RBIHelper
10
+ include Runtime::Reflection
11
+
12
+ sig { params(type: GraphQL::Schema::Wrapper).returns(String) }
13
+ def type_for(type)
14
+ unwrapped_type = type.unwrap
15
+
16
+ parsed_type = case unwrapped_type
17
+ when GraphQL::Types::Boolean.singleton_class
18
+ "T::Boolean"
19
+ when GraphQL::Types::Float.singleton_class
20
+ qualified_name_of(Float)
21
+ when GraphQL::Types::ID.singleton_class
22
+ qualified_name_of(String)
23
+ when GraphQL::Types::Int.singleton_class
24
+ qualified_name_of(Integer)
25
+ when GraphQL::Types::ISO8601Date.singleton_class
26
+ qualified_name_of(Date)
27
+ when GraphQL::Types::ISO8601DateTime.singleton_class
28
+ qualified_name_of(DateTime)
29
+ when GraphQL::Types::JSON.singleton_class
30
+ "T::Hash[::String, T.untyped]"
31
+ when GraphQL::Types::String.singleton_class
32
+ qualified_name_of(String)
33
+ when GraphQL::Schema::Enum.singleton_class
34
+ enum_values = T.cast(unwrapped_type.enum_values, T::Array[GraphQL::Schema::EnumValue])
35
+ value_types = enum_values.map { |v| qualified_name_of(v.value.class) }.uniq
36
+ if value_types.size == 1
37
+ value_types.first
38
+ else
39
+ "T.any(#{value_types.join(", ")})"
40
+ end
41
+ when GraphQL::Schema::InputObject.singleton_class
42
+ qualified_name_of(unwrapped_type)
43
+ else
44
+ "T.untyped"
45
+ end
46
+
47
+ parsed_type = T.must(parsed_type)
48
+
49
+ if type.list?
50
+ parsed_type = "T::Array[#{parsed_type}]"
51
+ end
52
+
53
+ unless type.non_null?
54
+ parsed_type = as_nilable_type(parsed_type)
55
+ end
56
+
57
+ parsed_type
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -7,7 +7,7 @@ module Tapioca
7
7
  extend T::Sig
8
8
 
9
9
  sig { returns(T::Enumerable[T.class_of(Compiler)]) }
10
- attr_reader :compilers
10
+ attr_reader :active_compilers
11
11
 
12
12
  sig { returns(T::Array[Module]) }
13
13
  attr_reader :requested_constants
@@ -34,8 +34,8 @@ module Tapioca
34
34
  error_handler: $stderr.method(:puts).to_proc,
35
35
  number_of_workers: nil
36
36
  )
37
- @compilers = T.let(
38
- gather_compilers(requested_compilers, excluded_compilers),
37
+ @active_compilers = T.let(
38
+ gather_active_compilers(requested_compilers, excluded_compilers),
39
39
  T::Enumerable[T.class_of(Compiler)]
40
40
  )
41
41
  @requested_constants = requested_constants
@@ -87,11 +87,19 @@ module Tapioca
87
87
  def compiler_enabled?(compiler_name)
88
88
  potential_names = Compilers::NAMESPACES.map { |namespace| namespace + compiler_name }
89
89
 
90
- @compilers.any? do |compiler|
90
+ active_compilers.any? do |compiler|
91
91
  potential_names.any?(compiler.name)
92
92
  end
93
93
  end
94
94
 
95
+ sig { returns(T::Array[T.class_of(Compiler)]) }
96
+ def compilers
97
+ @compilers = T.let(@compilers, T.nilable(T::Array[T.class_of(Compiler)]))
98
+ @compilers ||= Runtime::Reflection.descendants_of(Compiler).sort_by do |compiler|
99
+ T.must(compiler.name)
100
+ end
101
+ end
102
+
95
103
  private
96
104
 
97
105
  sig do
@@ -100,16 +108,16 @@ module Tapioca
100
108
  excluded_compilers: T::Array[T.class_of(Compiler)]
101
109
  ).returns(T::Enumerable[T.class_of(Compiler)])
102
110
  end
103
- def gather_compilers(requested_compilers, excluded_compilers)
104
- Runtime::Reflection.descendants_of(Compiler).select do |klass|
105
- (requested_compilers.empty? || requested_compilers.include?(klass)) &&
106
- !excluded_compilers.include?(klass)
107
- end.sort_by { |klass| T.must(klass.name) }
111
+ def gather_active_compilers(requested_compilers, excluded_compilers)
112
+ active_compilers = compilers
113
+ active_compilers -= excluded_compilers
114
+ active_compilers &= requested_compilers unless requested_compilers.empty?
115
+ active_compilers
108
116
  end
109
117
 
110
118
  sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
111
119
  def gather_constants(requested_constants)
112
- constants = compilers.map(&:processable_constants).reduce(Set.new, :union)
120
+ constants = active_compilers.map(&:processable_constants).reduce(Set.new, :union)
113
121
  constants = filter_anonymous_and_reloaded_constants(constants)
114
122
 
115
123
  constants &= requested_constants unless requested_constants.empty?
@@ -145,7 +153,7 @@ module Tapioca
145
153
  def rbi_for_constant(constant)
146
154
  file = RBI::File.new(strictness: "true")
147
155
 
148
- compilers.each do |compiler_class|
156
+ active_compilers.each do |compiler_class|
149
157
  next unless compiler_class.handles?(constant)
150
158
 
151
159
  compiler = compiler_class.new(self, file.root, constant)
@@ -41,7 +41,6 @@ module Tapioca
41
41
  def add_source_location_comment(node, file, line)
42
42
  return unless file && line
43
43
 
44
- gem = @pipeline.gem
45
44
  path = Pathname.new(file)
46
45
  return unless File.exist?(path)
47
46
 
@@ -49,17 +48,25 @@ module Tapioca
49
48
  # use for jump to definition. Only add source comments on Ruby files
50
49
  return unless path.extname == ".rb"
51
50
 
52
- path = if path.realpath.to_s.start_with?(gem.full_gem_path)
53
- "#{gem.name}-#{gem.version}/#{path.realpath.relative_path_from(gem.full_gem_path)}"
54
- else
55
- path.sub("#{Bundler.bundle_path}/gems/", "").to_s
56
- end
51
+ realpath = path.realpath.to_s
52
+ gem = Gemfile::GemSpec.spec_lookup_by_file_path[realpath]
53
+ return if gem.nil?
57
54
 
58
- # Strip out the RUBY_ROOT prefix, which is different for each user
59
- path = path.sub(RbConfig::CONFIG["rubylibdir"], "RUBY_ROOT")
55
+ path = gem.relative_path_for(path)
56
+ version = gem.version
57
+ # we can clear the gem version if the gem is the same one we are processing
58
+ version = "" if gem == @pipeline.gem
60
59
 
60
+ uri = URI::Source.build(
61
+ gem_name: gem.name,
62
+ gem_version: version,
63
+ path: path.to_s,
64
+ line_number: line.to_s
65
+ )
61
66
  node.comments << RBI::Comment.new("") if node.comments.any?
62
- node.comments << RBI::Comment.new("source://#{path}:#{line}")
67
+ node.comments << RBI::Comment.new(uri.to_s)
68
+ rescue URI::InvalidComponentError, URI::InvalidURIError
69
+ # Bad uri
63
70
  end
64
71
  end
65
72
  end