tapioca 0.5.3 → 0.6.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +114 -23
  3. data/lib/tapioca/cli.rb +188 -65
  4. data/lib/tapioca/compilers/dsl/active_record_associations.rb +94 -8
  5. data/lib/tapioca/compilers/dsl/active_record_columns.rb +5 -4
  6. data/lib/tapioca/compilers/dsl/active_record_enum.rb +1 -1
  7. data/lib/tapioca/compilers/dsl/active_record_relations.rb +703 -0
  8. data/lib/tapioca/compilers/dsl/active_record_scope.rb +43 -13
  9. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +39 -33
  10. data/lib/tapioca/compilers/dsl/base.rb +26 -42
  11. data/lib/tapioca/compilers/dsl/extensions/frozen_record.rb +29 -0
  12. data/lib/tapioca/compilers/dsl/frozen_record.rb +37 -0
  13. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +27 -0
  14. data/lib/tapioca/compilers/dsl/identity_cache.rb +0 -1
  15. data/lib/tapioca/compilers/dsl/param_helper.rb +52 -0
  16. data/lib/tapioca/compilers/dsl/rails_generators.rb +120 -0
  17. data/lib/tapioca/compilers/dsl_compiler.rb +34 -6
  18. data/lib/tapioca/compilers/sorbet.rb +2 -0
  19. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +51 -48
  20. data/lib/tapioca/executor.rb +79 -0
  21. data/lib/tapioca/gemfile.rb +28 -4
  22. data/lib/tapioca/generators/base.rb +11 -18
  23. data/lib/tapioca/generators/dsl.rb +33 -38
  24. data/lib/tapioca/generators/gem.rb +64 -34
  25. data/lib/tapioca/generators/init.rb +41 -16
  26. data/lib/tapioca/generators/todo.rb +6 -6
  27. data/lib/tapioca/helpers/cli_helper.rb +26 -0
  28. data/lib/tapioca/helpers/config_helper.rb +84 -0
  29. data/lib/tapioca/helpers/test/content.rb +51 -0
  30. data/lib/tapioca/helpers/test/isolation.rb +125 -0
  31. data/lib/tapioca/helpers/test/template.rb +34 -0
  32. data/lib/tapioca/internal.rb +3 -2
  33. data/lib/tapioca/rbi_ext/model.rb +13 -10
  34. data/lib/tapioca/reflection.rb +13 -0
  35. data/lib/tapioca/trackers/autoload.rb +70 -0
  36. data/lib/tapioca/trackers/constant_definition.rb +42 -0
  37. data/lib/tapioca/trackers/mixin.rb +78 -0
  38. data/lib/tapioca/trackers.rb +14 -0
  39. data/lib/tapioca/version.rb +1 -1
  40. data/lib/tapioca.rb +28 -2
  41. metadata +37 -13
  42. data/lib/tapioca/config.rb +0 -45
  43. data/lib/tapioca/config_builder.rb +0 -73
  44. data/lib/tapioca/constant_locator.rb +0 -34
@@ -7,6 +7,8 @@ rescue LoadError
7
7
  return
8
8
  end
9
9
 
10
+ require "tapioca/compilers/dsl/helper/active_record_constants"
11
+
10
12
  module Tapioca
11
13
  module Compilers
12
14
  module Dsl
@@ -42,6 +44,7 @@ module Tapioca
42
44
  # ~~~
43
45
  class ActiveRecordScope < Base
44
46
  extend T::Sig
47
+ include Helper::ActiveRecordConstants
45
48
 
46
49
  sig do
47
50
  override.params(
@@ -50,19 +53,33 @@ module Tapioca
50
53
  ).void
51
54
  end
52
55
  def decorate(root, constant)
53
- scope_method_names = constant.send(:generated_relation_methods).instance_methods(false)
54
- return if scope_method_names.empty?
56
+ method_names = scope_method_names(constant)
57
+
58
+ return if method_names.empty?
55
59
 
56
60
  root.create_path(constant) do |model|
57
- module_name = "GeneratedRelationMethods"
61
+ relations_enabled = generator_enabled?("ActiveRecordRelations")
62
+
63
+ relation_methods_module = model.create_module(RelationMethodsModuleName)
64
+ assoc_relation_methods_mod = model.create_module(AssociationRelationMethodsModuleName) if relations_enabled
65
+
66
+ method_names.each do |scope_method|
67
+ generate_scope_method(
68
+ relation_methods_module,
69
+ scope_method.to_s,
70
+ relations_enabled ? RelationClassName : "T.untyped"
71
+ )
58
72
 
59
- model.create_module(module_name) do |mod|
60
- scope_method_names.each do |scope_method|
61
- generate_scope_method(scope_method.to_s, mod)
62
- end
73
+ next unless relations_enabled
74
+
75
+ generate_scope_method(
76
+ assoc_relation_methods_mod,
77
+ scope_method.to_s,
78
+ AssociationRelationClassName
79
+ )
63
80
  end
64
81
 
65
- model.create_extend(module_name)
82
+ model.create_extend(RelationMethodsModuleName)
66
83
  end
67
84
  end
68
85
 
@@ -73,16 +90,29 @@ module Tapioca
73
90
 
74
91
  private
75
92
 
93
+ sig { params(constant: T.class_of(::ActiveRecord::Base)).returns(T::Array[Symbol]) }
94
+ def scope_method_names(constant)
95
+ scope_methods = T.let([], T::Array[Symbol])
96
+
97
+ # Keep gathering scope methods until we hit "ActiveRecord::Base"
98
+ until constant == ActiveRecord::Base
99
+ scope_methods.concat(constant.send(:generated_relation_methods).instance_methods(false))
100
+
101
+ # we are guaranteed to have a superclass that is of type "ActiveRecord::Base"
102
+ constant = T.cast(constant.superclass, T.class_of(ActiveRecord::Base))
103
+ end
104
+
105
+ scope_methods
106
+ end
107
+
76
108
  sig do
77
109
  params(
78
- scope_method: String,
79
110
  mod: RBI::Scope,
111
+ scope_method: String,
112
+ return_type: String
80
113
  ).void
81
114
  end
82
- def generate_scope_method(scope_method, mod)
83
- # This return type should actually be Model::ActiveRecord_Relation
84
- return_type = "T.untyped"
85
-
115
+ def generate_scope_method(mod, scope_method, return_type)
86
116
  mod.create_method(
87
117
  scope_method,
88
118
  parameters: [
@@ -34,53 +34,57 @@ module Tapioca
34
34
  # # post.rbi
35
35
  # # typed: true
36
36
  # class Post
37
- # sig { params(review_date: T.nilable(Date)).returns(T.nilable(Date)) }
38
- # def review_date=(review_date); end
37
+ # include StoreAccessors
39
38
  #
40
- # sig { returns(T.nilable(Date)) }
41
- # def review_date; end
39
+ # module StoreAccessors
40
+ # sig { params(review_date: T.nilable(Date)).returns(T.nilable(Date)) }
41
+ # def review_date=(review_date); end
42
42
  #
43
- # sig { returns(T.nilable(Date)) }
44
- # def review_date_was; end
43
+ # sig { returns(T.nilable(Date)) }
44
+ # def review_date; end
45
45
  #
46
- # sig { returns(T::Boolean) }
47
- # def review_date_changed?; end
46
+ # sig { returns(T.nilable(Date)) }
47
+ # def review_date_was; end
48
48
  #
49
- # sig { returns(T.nilable(Date)) }
50
- # def review_date_before_last_save; end
49
+ # sig { returns(T::Boolean) }
50
+ # def review_date_changed?; end
51
51
  #
52
- # sig { returns(T::Boolean) }
53
- # def saved_change_to_review_date?; end
52
+ # sig { returns(T.nilable(Date)) }
53
+ # def review_date_before_last_save; end
54
54
  #
55
- # sig { returns(T.nilable([T.nilable(Date), T.nilable(Date)])) }
56
- # def review_date_change; end
55
+ # sig { returns(T::Boolean) }
56
+ # def saved_change_to_review_date?; end
57
57
  #
58
- # sig { returns(T.nilable([T.nilable(Date), T.nilable(Date)])) }
59
- # def saved_change_to_review_date; end
58
+ # sig { returns(T.nilable([T.nilable(Date), T.nilable(Date)])) }
59
+ # def review_date_change; end
60
60
  #
61
- # sig { params(reviewd: T::Boolean).returns(T::Boolean) }
62
- # def reviewed=(reviewed); end
61
+ # sig { returns(T.nilable([T.nilable(Date), T.nilable(Date)])) }
62
+ # def saved_change_to_review_date; end
63
63
  #
64
- # sig { returns(T::Boolean) }
65
- # def reviewed; end
64
+ # sig { params(reviewd: T::Boolean).returns(T::Boolean) }
65
+ # def reviewed=(reviewed); end
66
66
  #
67
- # sig { returns(T::Boolean) }
68
- # def reviewed_was; end
67
+ # sig { returns(T::Boolean) }
68
+ # def reviewed; end
69
69
  #
70
- # sig { returns(T::Boolean) }
71
- # def reviewed_changed?; end
70
+ # sig { returns(T::Boolean) }
71
+ # def reviewed_was; end
72
72
  #
73
- # sig { returns(T::Boolean) }
74
- # def reviewed_before_last_save; end
73
+ # sig { returns(T::Boolean) }
74
+ # def reviewed_changed?; end
75
75
  #
76
- # sig { returns(T::Boolean) }
77
- # def saved_change_to_reviewed?; end
76
+ # sig { returns(T::Boolean) }
77
+ # def reviewed_before_last_save; end
78
78
  #
79
- # sig { returns(T.nilable([T::Boolean, T::Boolean])) }
80
- # def reviewed_change; end
79
+ # sig { returns(T::Boolean) }
80
+ # def saved_change_to_reviewed?; end
81
81
  #
82
- # sig { returns(T.nilable([T::Boolean, T::Boolean])) }
83
- # def saved_change_to_reviewed; end
82
+ # sig { returns(T.nilable([T::Boolean, T::Boolean])) }
83
+ # def reviewed_change; end
84
+ #
85
+ # sig { returns(T.nilable([T::Boolean, T::Boolean])) }
86
+ # def saved_change_to_reviewed; end
87
+ # end
84
88
  # end
85
89
  # ~~~
86
90
  class ActiveRecordTypedStore < Base
@@ -105,7 +109,9 @@ module Tapioca
105
109
  type = type_for(field.type_sym)
106
110
  type = "T.nilable(#{type})" if field.null && type != "T.untyped"
107
111
 
108
- generate_methods(model, field.name.to_s, type)
112
+ store_accessors_module = model.create_module("StoreAccessors")
113
+ generate_methods(store_accessors_module, field.name.to_s, type)
114
+ model.create_include("StoreAccessors")
109
115
  end
110
116
  end
111
117
  end
@@ -2,11 +2,13 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "tapioca/rbi_ext/model"
5
+ require "tapioca/compilers/dsl/param_helper"
6
+ require "tapioca/compilers/dsl_compiler"
5
7
 
6
8
  module Tapioca
7
9
  module Compilers
8
10
  module Dsl
9
- COMPILERS_PATH = T.let(File.expand_path("..", __FILE__).to_s, String)
11
+ DSL_COMPILERS_DIR = T.let(File.expand_path("..", __FILE__).to_s, String)
10
12
 
11
13
  class Base
12
14
  extend T::Sig
@@ -22,9 +24,24 @@ module Tapioca
22
24
  sig { returns(T::Array[String]) }
23
25
  attr_reader :errors
24
26
 
25
- sig { void }
26
- def initialize
27
+ sig { params(name: String).returns(T.nilable(T.class_of(Tapioca::Compilers::Dsl::Base))) }
28
+ def self.resolve(name)
29
+ # Try to find built-in tapioca generator first, then globally defined generator.
30
+ potentials = ["Tapioca::Compilers::Dsl::#{name}", name].map do |potential_name|
31
+ Object.const_get(potential_name)
32
+ rescue NameError
33
+ # Skip if we can't find generator by the potential name
34
+ nil
35
+ end
36
+
37
+ potentials.compact.first
38
+ end
39
+
40
+ sig { params(compiler: Tapioca::Compilers::DslCompiler).void }
41
+ def initialize(compiler)
42
+ @compiler = compiler
27
43
  @processable_constants = T.let(Set.new(gather_constants), T::Set[Module])
44
+ @processable_constants.compare_by_identity
28
45
  @errors = T.let([], T::Array[String])
29
46
  end
30
47
 
@@ -33,6 +50,11 @@ module Tapioca
33
50
  processable_constants.include?(constant)
34
51
  end
35
52
 
53
+ sig { params(generator_name: String).returns(T::Boolean) }
54
+ def generator_enabled?(generator_name)
55
+ @compiler.generator_enabled?(generator_name)
56
+ end
57
+
36
58
  sig do
37
59
  abstract
38
60
  .type_parameters(:T)
@@ -106,45 +128,7 @@ module Tapioca
106
128
  )
107
129
  end
108
130
 
109
- sig { params(name: String, type: String).returns(RBI::TypedParam) }
110
- def create_param(name, type:)
111
- create_typed_param(RBI::Param.new(name), type)
112
- end
113
-
114
- sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
115
- def create_opt_param(name, type:, default:)
116
- create_typed_param(RBI::OptParam.new(name, default), type)
117
- end
118
-
119
- sig { params(name: String, type: String).returns(RBI::TypedParam) }
120
- def create_rest_param(name, type:)
121
- create_typed_param(RBI::RestParam.new(name), type)
122
- end
123
-
124
- sig { params(name: String, type: String).returns(RBI::TypedParam) }
125
- def create_kw_param(name, type:)
126
- create_typed_param(RBI::KwParam.new(name), type)
127
- end
128
-
129
- sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
130
- def create_kw_opt_param(name, type:, default:)
131
- create_typed_param(RBI::KwOptParam.new(name, default), type)
132
- end
133
-
134
- sig { params(name: String, type: String).returns(RBI::TypedParam) }
135
- def create_kw_rest_param(name, type:)
136
- create_typed_param(RBI::KwRestParam.new(name), type)
137
- end
138
-
139
- sig { params(name: String, type: String).returns(RBI::TypedParam) }
140
- def create_block_param(name, type:)
141
- create_typed_param(RBI::BlockParam.new(name), type)
142
- end
143
-
144
- sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
145
- def create_typed_param(param, type)
146
- RBI::TypedParam.new(param: param, type: type)
147
- end
131
+ include ParamHelper
148
132
 
149
133
  sig { params(method_def: T.any(Method, UnboundMethod)).returns(T::Array[RBI::TypedParam]) }
150
134
  def compile_method_parameters_to_rbi(method_def)
@@ -0,0 +1,29 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "frozen_record"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ module Tapioca
11
+ module Compilers
12
+ module Dsl
13
+ module Extensions
14
+ module FrozenRecord
15
+ attr_reader :__tapioca_scope_names
16
+
17
+ def scope(name, body)
18
+ @__tapioca_scope_names ||= []
19
+ @__tapioca_scope_names << name
20
+
21
+ super
22
+ end
23
+
24
+ ::FrozenRecord::Base.singleton_class.prepend(self)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -81,6 +81,8 @@ module Tapioca
81
81
  end
82
82
 
83
83
  record.create_include(module_name)
84
+
85
+ decorate_scopes(constant, record)
84
86
  end
85
87
  end
86
88
 
@@ -88,6 +90,41 @@ module Tapioca
88
90
  def gather_constants
89
91
  descendants_of(::FrozenRecord::Base).reject(&:abstract_class?)
90
92
  end
93
+
94
+ private
95
+
96
+ sig { params(constant: T.class_of(::FrozenRecord::Base), record: RBI::Scope).void }
97
+ def decorate_scopes(constant, record)
98
+ scopes = T.unsafe(constant).__tapioca_scope_names
99
+ return if scopes.nil?
100
+
101
+ module_name = "GeneratedRelationMethods"
102
+
103
+ record.create_module(module_name) do |mod|
104
+ scopes.each do |name|
105
+ generate_scope_method(name.to_s, mod)
106
+ end
107
+ end
108
+
109
+ record.create_extend(module_name)
110
+ end
111
+
112
+ sig do
113
+ params(
114
+ scope_method: String,
115
+ mod: RBI::Scope,
116
+ ).void
117
+ end
118
+ def generate_scope_method(scope_method, mod)
119
+ mod.create_method(
120
+ scope_method,
121
+ parameters: [
122
+ create_rest_param("args", type: "T.untyped"),
123
+ create_block_param("blk", type: "T.untyped"),
124
+ ],
125
+ return_type: "T.untyped",
126
+ )
127
+ end
91
128
  end
92
129
  end
93
130
  end
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Compilers
6
+ module Dsl
7
+ module Helper
8
+ module ActiveRecordConstants
9
+ extend T::Sig
10
+
11
+ AttributeMethodsModuleName = T.let("GeneratedAttributeMethods", String)
12
+ AssociationMethodsModuleName = T.let("GeneratedAssociationMethods", String)
13
+
14
+ RelationMethodsModuleName = T.let("GeneratedRelationMethods", String)
15
+ AssociationRelationMethodsModuleName = T.let("GeneratedAssociationRelationMethods", String)
16
+ CommonRelationMethodsModuleName = T.let("CommonRelationMethods", String)
17
+
18
+ RelationClassName = T.let("PrivateRelation", String)
19
+ RelationWhereChainClassName = T.let("PrivateRelationWhereChain", String)
20
+ AssociationRelationClassName = T.let("PrivateAssociationRelation", String)
21
+ AssociationRelationWhereChainClassName = T.let("PrivateAssociationRelationWhereChain", String)
22
+ AssociationsCollectionProxyClassName = T.let("PrivateCollectionProxy", String)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  begin
5
- require "rails/railtie"
6
5
  require "identity_cache"
7
6
  rescue LoadError
8
7
  # means IdentityCache is not installed,
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Compilers
6
+ module Dsl
7
+ module ParamHelper
8
+ extend T::Sig
9
+
10
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
11
+ def create_param(name, type:)
12
+ create_typed_param(RBI::Param.new(name), type)
13
+ end
14
+
15
+ sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
16
+ def create_opt_param(name, type:, default:)
17
+ create_typed_param(RBI::OptParam.new(name, default), type)
18
+ end
19
+
20
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
21
+ def create_rest_param(name, type:)
22
+ create_typed_param(RBI::RestParam.new(name), type)
23
+ end
24
+
25
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
26
+ def create_kw_param(name, type:)
27
+ create_typed_param(RBI::KwParam.new(name), type)
28
+ end
29
+
30
+ sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
31
+ def create_kw_opt_param(name, type:, default:)
32
+ create_typed_param(RBI::KwOptParam.new(name, default), type)
33
+ end
34
+
35
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
36
+ def create_kw_rest_param(name, type:)
37
+ create_typed_param(RBI::KwRestParam.new(name), type)
38
+ end
39
+
40
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
41
+ def create_block_param(name, type:)
42
+ create_typed_param(RBI::BlockParam.new(name), type)
43
+ end
44
+
45
+ sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
46
+ def create_typed_param(param, type)
47
+ RBI::TypedParam.new(param: param, type: type)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,120 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "rails/generators"
6
+ require "rails/generators/app_base"
7
+ rescue LoadError
8
+ return
9
+ end
10
+
11
+ module Tapioca
12
+ module Compilers
13
+ module Dsl
14
+ # `Tapioca::Compilers::Dsl::RailsGenerators` generates RBI files for Rails generators
15
+ #
16
+ # For example, with the following generator:
17
+ #
18
+ # ~~~rb
19
+ # # lib/generators/sample_generator.rb
20
+ # class ServiceGenerator < Rails::Generators::NamedBase
21
+ # argument :result_type, type: :string
22
+ #
23
+ # class_option :skip_comments, type: :boolean, default: false
24
+ # end
25
+ # ~~~
26
+ #
27
+ # this compiler will produce the RBI file `service_generator.rbi` with the following content:
28
+ #
29
+ # ~~~rbi
30
+ # # service_generator.rbi
31
+ # # typed: strong
32
+ #
33
+ # class ServiceGenerator
34
+ # sig { returns(::String)}
35
+ # def result_type; end
36
+ #
37
+ # sig { returns(T::Boolean)}
38
+ # def skip_comments; end
39
+ # end
40
+ # ~~~
41
+ class RailsGenerators < Base
42
+ extend T::Sig
43
+
44
+ BUILT_IN_MATCHER = T.let(
45
+ /::(ActionMailbox|ActionText|ActiveRecord|Rails)::Generators/,
46
+ Regexp
47
+ )
48
+
49
+ sig { override.params(root: RBI::Tree, constant: T.class_of(::Rails::Generators::Base)).void }
50
+ def decorate(root, constant)
51
+ base_class = base_class_for(constant)
52
+ arguments = constant.arguments - base_class.arguments
53
+ class_options = constant.class_options.reject do |name, option|
54
+ base_class.class_options[name] == option
55
+ end
56
+
57
+ return if arguments.empty? && class_options.empty?
58
+
59
+ root.create_path(constant) do |klass|
60
+ arguments.each { |argument| generate_methods_for_argument(klass, argument) }
61
+ class_options.each { |_name, option| generate_methods_for_argument(klass, option) }
62
+ end
63
+ end
64
+
65
+ sig { override.returns(T::Enumerable[Module]) }
66
+ def gather_constants
67
+ all_modules.select do |const|
68
+ name = qualified_name_of(const)
69
+
70
+ name &&
71
+ !name.match?(BUILT_IN_MATCHER) &&
72
+ const < ::Rails::Generators::Base
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ sig { params(klass: RBI::Tree, argument: T.any(Thor::Argument, Thor::Option)).void }
79
+ def generate_methods_for_argument(klass, argument)
80
+ klass.create_method(
81
+ argument.name,
82
+ parameters: [],
83
+ return_type: type_for(argument)
84
+ )
85
+ end
86
+
87
+ sig do
88
+ params(constant: T.class_of(::Rails::Generators::Base))
89
+ .returns(T.class_of(::Rails::Generators::Base))
90
+ end
91
+ def base_class_for(constant)
92
+ ancestor = inherited_ancestors_of(constant).find do |klass|
93
+ qualified_name_of(klass)&.match?(BUILT_IN_MATCHER)
94
+ end
95
+
96
+ T.cast(ancestor, T.class_of(::Rails::Generators::Base))
97
+ end
98
+
99
+ sig { params(arg: T.any(Thor::Argument, Thor::Option)).returns(String) }
100
+ def type_for(arg)
101
+ type =
102
+ case arg.type
103
+ when :array then "T::Array[::String]"
104
+ when :boolean then "T::Boolean"
105
+ when :hash then "T::Hash[::String, ::String]"
106
+ when :numeric then "::Numeric"
107
+ when :string then "::String"
108
+ else "T.untyped"
109
+ end
110
+
111
+ if arg.required || arg.default
112
+ type
113
+ else
114
+ "T.nilable(#{type})"
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -22,21 +22,35 @@ module Tapioca
22
22
  requested_constants: T::Array[Module],
23
23
  requested_generators: T::Array[T.class_of(Dsl::Base)],
24
24
  excluded_generators: T::Array[T.class_of(Dsl::Base)],
25
- error_handler: T.nilable(T.proc.params(error: String).void)
25
+ error_handler: T.proc.params(error: String).void,
26
+ number_of_workers: T.nilable(Integer),
26
27
  ).void
27
28
  end
28
- def initialize(requested_constants:, requested_generators: [], excluded_generators: [], error_handler: nil)
29
+ def initialize(
30
+ requested_constants:,
31
+ requested_generators: [],
32
+ excluded_generators: [],
33
+ error_handler: $stderr.method(:puts).to_proc,
34
+ number_of_workers: nil
35
+ )
29
36
  @generators = T.let(
30
37
  gather_generators(requested_generators, excluded_generators),
31
38
  T::Enumerable[Dsl::Base]
32
39
  )
33
40
  @requested_constants = requested_constants
34
- @error_handler = T.let(error_handler || $stderr.method(:puts), T.proc.params(error: String).void)
41
+ @error_handler = error_handler
42
+ @number_of_workers = number_of_workers
35
43
  end
36
44
 
37
- sig { params(blk: T.proc.params(constant: Module, rbi: RBI::File).void).void }
45
+ sig do
46
+ type_parameters(:T).params(
47
+ blk: T.proc.params(constant: Module, rbi: RBI::File).returns(T.type_parameter(:T))
48
+ ).returns(T::Array[T.type_parameter(:T)])
49
+ end
38
50
  def run(&blk)
39
51
  constants_to_process = gather_constants(requested_constants)
52
+ .select { |c| Reflection.name_of(c) && Module === c } # Filter anonymous or value constants
53
+ .sort_by! { |c| T.must(Reflection.name_of(c)) }
40
54
 
41
55
  if constants_to_process.empty?
42
56
  report_error(<<~ERROR)
@@ -45,7 +59,10 @@ module Tapioca
45
59
  ERROR
46
60
  end
47
61
 
48
- constants_to_process.sort_by { |c| c.name.to_s }.each do |constant|
62
+ result = Executor.new(
63
+ constants_to_process,
64
+ number_of_workers: @number_of_workers
65
+ ).run_in_parallel do |constant|
49
66
  rbi = rbi_for_constant(constant)
50
67
  next if rbi.nil?
51
68
 
@@ -55,6 +72,17 @@ module Tapioca
55
72
  generators.flat_map(&:errors).each do |msg|
56
73
  report_error(msg)
57
74
  end
75
+
76
+ result.compact
77
+ end
78
+
79
+ sig { params(generator_name: String).returns(T::Boolean) }
80
+ def generator_enabled?(generator_name)
81
+ generator = Dsl::Base.resolve(generator_name)
82
+
83
+ return false unless generator
84
+
85
+ @generators.any?(generator)
58
86
  end
59
87
 
60
88
  private
@@ -71,7 +99,7 @@ module Tapioca
71
99
  !excluded_generators.include?(klass)
72
100
  end.sort_by { |klass| T.must(klass.name) }
73
101
 
74
- generator_klasses.map(&:new)
102
+ generator_klasses.map { |generator_klass| generator_klass.new(self) }
75
103
  end
76
104
 
77
105
  sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
@@ -18,6 +18,8 @@ module Tapioca
18
18
  EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
19
19
 
20
20
  FEATURE_REQUIREMENTS = T.let({
21
+ # First tag that includes https://github.com/sorbet/sorbet/pull/4706
22
+ to_ary_nil_support: Gem::Requirement.new(">= 0.5.9220"),
21
23
  }.freeze, T::Hash[Symbol, Gem::Requirement])
22
24
 
23
25
  class << self