tapioca 0.9.4 → 0.10.0

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 (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