tapioca 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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