tapioca 0.6.4 → 0.7.2

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -2
  3. data/README.md +27 -15
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +65 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +16 -9
  7. data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
  8. data/lib/tapioca/{generators → commands}/gem.rb +93 -30
  9. data/lib/tapioca/{generators → commands}/init.rb +9 -13
  10. data/lib/tapioca/{generators → commands}/require.rb +8 -10
  11. data/lib/tapioca/commands/todo.rb +86 -0
  12. data/lib/tapioca/commands.rb +13 -0
  13. data/lib/tapioca/dsl/compiler.rb +185 -0
  14. data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
  15. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
  16. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
  17. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
  18. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +13 -11
  19. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
  20. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +28 -34
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +18 -16
  22. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
  23. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +12 -8
  24. data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
  25. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
  26. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +11 -16
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +14 -10
  29. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
  30. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
  31. data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +11 -9
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +23 -22
  34. data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
  35. data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +22 -10
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +12 -13
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +11 -9
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +20 -15
  41. data/lib/tapioca/dsl/compilers.rb +31 -0
  42. data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
  43. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
  44. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
  45. data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +6 -3
  46. data/lib/tapioca/dsl/pipeline.rb +169 -0
  47. data/lib/tapioca/gem/events.rb +120 -0
  48. data/lib/tapioca/gem/listeners/base.rb +48 -0
  49. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
  50. data/lib/tapioca/gem/listeners/methods.rb +183 -0
  51. data/lib/tapioca/gem/listeners/mixins.rb +101 -0
  52. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
  53. data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
  54. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
  55. data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
  56. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
  57. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
  58. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
  59. data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
  60. data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
  61. data/lib/tapioca/gem/listeners.rb +16 -0
  62. data/lib/tapioca/gem/pipeline.rb +365 -0
  63. data/lib/tapioca/helpers/cli_helper.rb +7 -0
  64. data/lib/tapioca/helpers/config_helper.rb +5 -8
  65. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  66. data/lib/tapioca/helpers/signatures_helper.rb +17 -0
  67. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  68. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  69. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  70. data/lib/tapioca/helpers/test/template.rb +13 -2
  71. data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
  72. data/lib/tapioca/internal.rb +18 -10
  73. data/lib/tapioca/rbi_ext/model.rb +14 -50
  74. data/lib/tapioca/rbi_formatter.rb +37 -0
  75. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  76. data/lib/tapioca/runtime/generic_type_registry.rb +168 -0
  77. data/lib/tapioca/runtime/loader.rb +123 -0
  78. data/lib/tapioca/runtime/reflection.rb +157 -0
  79. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  80. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  81. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  82. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  83. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  84. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +69 -34
  85. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  86. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  87. data/lib/tapioca/static/symbol_loader.rb +83 -0
  88. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  89. data/lib/tapioca/version.rb +1 -1
  90. data/lib/tapioca.rb +2 -7
  91. metadata +83 -62
  92. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  93. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  94. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  95. data/lib/tapioca/compilers/dsl_compiler.rb +0 -134
  96. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  97. data/lib/tapioca/compilers/sorbet.rb +0 -59
  98. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  99. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  100. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  101. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  102. data/lib/tapioca/generators/todo.rb +0 -76
  103. data/lib/tapioca/generators.rb +0 -9
  104. data/lib/tapioca/generic_type_registry.rb +0 -164
  105. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  106. data/lib/tapioca/loader.rb +0 -119
  107. data/lib/tapioca/reflection.rb +0 -151
  108. data/lib/tapioca/trackers/autoload.rb +0 -70
  109. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  110. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -1,195 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "tapioca/rbi_ext/model"
5
- require "tapioca/compilers/dsl/param_helper"
6
- require "tapioca/compilers/dsl_compiler"
7
-
8
- module Tapioca
9
- module Compilers
10
- module Dsl
11
- DSL_COMPILERS_DIR = T.let(File.expand_path("..", __FILE__).to_s, String)
12
-
13
- class Base
14
- extend T::Sig
15
- extend T::Helpers
16
-
17
- include Reflection
18
-
19
- abstract!
20
-
21
- sig { returns(T::Set[Module]) }
22
- attr_reader :processable_constants
23
-
24
- sig { returns(T::Array[String]) }
25
- attr_reader :errors
26
-
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
43
- @processable_constants = T.let(Set.new(gather_constants), T::Set[Module])
44
- @processable_constants.compare_by_identity
45
- @errors = T.let([], T::Array[String])
46
- end
47
-
48
- sig { params(constant: Module).returns(T::Boolean) }
49
- def handles?(constant)
50
- processable_constants.include?(constant)
51
- end
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
-
58
- sig do
59
- abstract
60
- .type_parameters(:T)
61
- .params(
62
- tree: RBI::Tree,
63
- constant: T.type_parameter(:T)
64
- )
65
- .void
66
- end
67
- def decorate(tree, constant); end
68
-
69
- sig { abstract.returns(T::Enumerable[Module]) }
70
- def gather_constants; end
71
-
72
- # NOTE: This should eventually accept an `Error` object or `Exception` rather than simply a `String`.
73
- sig { params(error: String).void }
74
- def add_error(error)
75
- @errors << error
76
- end
77
-
78
- private
79
-
80
- sig { returns(T::Enumerable[Class]) }
81
- def all_classes
82
- @all_classes = T.let(@all_classes, T.nilable(T::Enumerable[Class]))
83
- @all_classes ||= T.cast(ObjectSpace.each_object(Class), T::Enumerable[Class]).each
84
- end
85
-
86
- sig { returns(T::Enumerable[Module]) }
87
- def all_modules
88
- @all_modules = T.let(@all_modules, T.nilable(T::Enumerable[Module]))
89
- @all_modules ||= T.cast(ObjectSpace.each_object(Module), T::Enumerable[Module]).each
90
- end
91
-
92
- # Get the types of each parameter from a method signature
93
- sig do
94
- params(
95
- method_def: T.any(Method, UnboundMethod),
96
- signature: T.untyped # as `T::Private::Methods::Signature` is private
97
- ).returns(T::Array[String])
98
- end
99
- def parameters_types_from_signature(method_def, signature)
100
- params = T.let([], T::Array[String])
101
-
102
- return method_def.parameters.map { "T.untyped" } unless signature
103
-
104
- # parameters types
105
- signature.arg_types.each { |arg_type| params << arg_type[1].to_s }
106
-
107
- # keyword parameters types
108
- signature.kwarg_types.each { |_, kwarg_type| params << kwarg_type.to_s }
109
-
110
- # rest parameter type
111
- params << signature.rest_type.to_s if signature.has_rest
112
-
113
- # special case `.void` in a proc
114
- unless signature.block_name.nil?
115
- params << signature.block_type.to_s.gsub("returns(<VOID>)", "void")
116
- end
117
-
118
- params
119
- end
120
-
121
- sig { params(scope: RBI::Scope, method_def: T.any(Method, UnboundMethod), class_method: T::Boolean).void }
122
- def create_method_from_def(scope, method_def, class_method: false)
123
- scope.create_method(
124
- method_def.name.to_s,
125
- parameters: compile_method_parameters_to_rbi(method_def),
126
- return_type: compile_method_return_type_to_rbi(method_def),
127
- class_method: class_method
128
- )
129
- end
130
-
131
- include ParamHelper
132
-
133
- sig { params(method_def: T.any(Method, UnboundMethod)).returns(T::Array[RBI::TypedParam]) }
134
- def compile_method_parameters_to_rbi(method_def)
135
- signature = T::Private::Methods.signature_for_method(method_def)
136
- method_def = signature.nil? ? method_def : signature.method
137
- method_types = parameters_types_from_signature(method_def, signature)
138
-
139
- parameters = T.let(method_def.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
140
-
141
- parameters.each_with_index.map do |(type, name), index|
142
- fallback_arg_name = "_arg#{index}"
143
-
144
- name = name ? name.to_s : fallback_arg_name
145
- name = fallback_arg_name unless valid_parameter_name?(name)
146
- method_type = T.must(method_types[index])
147
-
148
- case type
149
- when :req
150
- create_param(name, type: method_type)
151
- when :opt
152
- create_opt_param(name, type: method_type, default: "T.unsafe(nil)")
153
- when :rest
154
- create_rest_param(name, type: method_type)
155
- when :keyreq
156
- create_kw_param(name, type: method_type)
157
- when :key
158
- create_kw_opt_param(name, type: method_type, default: "T.unsafe(nil)")
159
- when :keyrest
160
- create_kw_rest_param(name, type: method_type)
161
- when :block
162
- create_block_param(name, type: method_type)
163
- else
164
- raise "Unknown type `#{type}`."
165
- end
166
- end
167
- end
168
-
169
- sig { params(method_def: T.any(Method, UnboundMethod)).returns(String) }
170
- def compile_method_return_type_to_rbi(method_def)
171
- signature = T::Private::Methods.signature_for_method(method_def)
172
- return_type = signature.nil? ? "T.untyped" : name_of_type(signature.return_type)
173
- return_type = "void" if return_type == "<VOID>"
174
- # Map <NOT-TYPED> to `T.untyped`
175
- return_type = "T.untyped" if return_type == "<NOT-TYPED>"
176
- return_type
177
- end
178
-
179
- sig { params(type: String).returns(String) }
180
- def as_nilable_type(type)
181
- if type.start_with?("T.nilable(", "::T.nilable(") || type == "T.untyped" || type == "::T.untyped"
182
- type
183
- else
184
- "T.nilable(#{type})"
185
- end
186
- end
187
-
188
- sig { params(name: String).returns(T::Boolean) }
189
- def valid_parameter_name?(name)
190
- name.match?(/^[[[:alnum:]]_]+$/)
191
- end
192
- end
193
- end
194
- end
195
- end
@@ -1,27 +0,0 @@
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
@@ -1,134 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "tapioca/compilers/dsl/base"
5
-
6
- module Tapioca
7
- module Compilers
8
- class DslCompiler
9
- extend T::Sig
10
-
11
- sig { returns(T::Enumerable[Dsl::Base]) }
12
- attr_reader :generators
13
-
14
- sig { returns(T::Array[Module]) }
15
- attr_reader :requested_constants
16
-
17
- sig { returns(T.proc.params(error: String).void) }
18
- attr_reader :error_handler
19
-
20
- sig do
21
- params(
22
- requested_constants: T::Array[Module],
23
- requested_generators: T::Array[T.class_of(Dsl::Base)],
24
- excluded_generators: T::Array[T.class_of(Dsl::Base)],
25
- error_handler: T.proc.params(error: String).void,
26
- number_of_workers: T.nilable(Integer),
27
- ).void
28
- end
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
- )
36
- @generators = T.let(
37
- gather_generators(requested_generators, excluded_generators),
38
- T::Enumerable[Dsl::Base]
39
- )
40
- @requested_constants = requested_constants
41
- @error_handler = error_handler
42
- @number_of_workers = number_of_workers
43
- end
44
-
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
50
- def run(&blk)
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)) }
54
-
55
- if constants_to_process.empty?
56
- report_error(<<~ERROR)
57
- No classes/modules can be matched for RBI generation.
58
- Please check that the requested classes/modules include processable DSL methods.
59
- ERROR
60
- end
61
-
62
- result = Executor.new(
63
- constants_to_process,
64
- number_of_workers: @number_of_workers
65
- ).run_in_parallel do |constant|
66
- rbi = rbi_for_constant(constant)
67
- next if rbi.nil?
68
-
69
- blk.call(constant, rbi)
70
- end
71
-
72
- generators.flat_map(&:errors).each do |msg|
73
- report_error(msg)
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)
86
- end
87
-
88
- private
89
-
90
- sig do
91
- params(
92
- requested_generators: T::Array[T.class_of(Dsl::Base)],
93
- excluded_generators: T::Array[T.class_of(Dsl::Base)]
94
- ).returns(T::Enumerable[Dsl::Base])
95
- end
96
- def gather_generators(requested_generators, excluded_generators)
97
- generator_klasses = ::Tapioca::Reflection.descendants_of(Dsl::Base).select do |klass|
98
- (requested_generators.empty? || requested_generators.include?(klass)) &&
99
- !excluded_generators.include?(klass)
100
- end.sort_by { |klass| T.must(klass.name) }
101
-
102
- generator_klasses.map { |generator_klass| generator_klass.new(self) }
103
- end
104
-
105
- sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
106
- def gather_constants(requested_constants)
107
- constants = generators.map(&:processable_constants).reduce(Set.new, :union)
108
- constants &= requested_constants unless requested_constants.empty?
109
- constants
110
- end
111
-
112
- sig { params(constant: Module).returns(T.nilable(RBI::File)) }
113
- def rbi_for_constant(constant)
114
- file = RBI::File.new(strictness: "true")
115
-
116
- generators.each do |generator|
117
- next unless generator.handles?(constant)
118
- generator.decorate(file.root, constant)
119
- end
120
-
121
- return if file.root.empty?
122
-
123
- file
124
- end
125
-
126
- sig { params(error: String).returns(T.noreturn) }
127
- def report_error(error)
128
- handler = error_handler
129
- handler.call(error)
130
- exit(1)
131
- end
132
- end
133
- end
134
- end
@@ -1,223 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- class DynamicMixinCompiler
5
- extend T::Sig
6
- include Tapioca::Reflection
7
-
8
- sig { returns(T::Array[Module]) }
9
- attr_reader :dynamic_extends, :dynamic_includes
10
-
11
- sig { returns(T::Array[Symbol]) }
12
- attr_reader :class_attribute_readers, :class_attribute_writers, :class_attribute_predicates
13
-
14
- sig { returns(T::Array[Symbol]) }
15
- attr_reader :instance_attribute_readers, :instance_attribute_writers, :instance_attribute_predicates
16
-
17
- sig { params(constant: Module).void }
18
- def initialize(constant)
19
- @constant = constant
20
- mixins_from_modules = {}.compare_by_identity
21
- class_attribute_readers = T.let([], T::Array[Symbol])
22
- class_attribute_writers = T.let([], T::Array[Symbol])
23
- class_attribute_predicates = T.let([], T::Array[Symbol])
24
-
25
- instance_attribute_readers = T.let([], T::Array[Symbol])
26
- instance_attribute_writers = T.let([], T::Array[Symbol])
27
- instance_attribute_predicates = T.let([], T::Array[Symbol])
28
-
29
- Class.new do
30
- # Override the `self.include` method
31
- define_singleton_method(:include) do |mod|
32
- # Take a snapshot of the list of singleton class ancestors
33
- # before the actual include
34
- before = singleton_class.ancestors
35
- # Call the actual `include` method with the supplied module
36
- super(mod).tap do
37
- # Take a snapshot of the list of singleton class ancestors
38
- # after the actual include
39
- after = singleton_class.ancestors
40
- # The difference is the modules that are added to the list
41
- # of ancestors of the singleton class. Those are all the
42
- # modules that were `extend`ed due to the `include` call.
43
- #
44
- # We record those modules on our lookup table keyed by
45
- # the included module with the values being all the modules
46
- # that that module pulls into the singleton class.
47
- #
48
- # We need to reverse the order, since the extend order should
49
- # be the inverse of the ancestor order. That is, earlier
50
- # extended modules would be later in the ancestor chain.
51
- mixins_from_modules[mod] = (after - before).reverse!
52
- end
53
- rescue Exception # rubocop:disable Lint/RescueException
54
- # this is a best effort, bail if we can't perform this
55
- end
56
-
57
- define_singleton_method(:class_attribute) do |*attrs, **kwargs|
58
- class_attribute_readers.concat(attrs)
59
- class_attribute_writers.concat(attrs)
60
-
61
- instance_predicate = kwargs.fetch(:instance_predicate, true)
62
- instance_accessor = kwargs.fetch(:instance_accessor, true)
63
- instance_reader = kwargs.fetch(:instance_reader, instance_accessor)
64
- instance_writer = kwargs.fetch(:instance_writer, instance_accessor)
65
-
66
- if instance_reader
67
- instance_attribute_readers.concat(attrs)
68
- end
69
-
70
- if instance_writer
71
- instance_attribute_writers.concat(attrs)
72
- end
73
-
74
- if instance_predicate
75
- class_attribute_predicates.concat(attrs)
76
-
77
- if instance_reader
78
- instance_attribute_predicates.concat(attrs)
79
- end
80
- end
81
-
82
- super(*attrs, **kwargs) if defined?(super)
83
- end
84
-
85
- # rubocop:disable Style/MissingRespondToMissing
86
- T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) }
87
- def method_missing(symbol, *args)
88
- # We need this here so that we can handle any random instance
89
- # method calls on the fake including class that may be done by
90
- # the included module during the `self.included` hook.
91
- end
92
-
93
- class << self
94
- extend T::Sig
95
-
96
- T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) }
97
- def method_missing(symbol, *args)
98
- # Similarly, we need this here so that we can handle any
99
- # random class method calls on the fake including class
100
- # that may be done by the included module during the
101
- # `self.included` hook.
102
- end
103
- end
104
- # rubocop:enable Style/MissingRespondToMissing
105
- end.include(constant)
106
-
107
- # The value that corresponds to the original included constant
108
- # is the list of all dynamically extended modules because of that
109
- # constant. We grab that value by deleting the key for the original
110
- # constant.
111
- @dynamic_extends = T.let(mixins_from_modules.delete(constant) || [], T::Array[Module])
112
-
113
- # Since we deleted the original constant from the list of keys, all
114
- # the keys that remain are the ones that are dynamically included modules
115
- # during the include of the original constant.
116
- @dynamic_includes = T.let(mixins_from_modules.keys, T::Array[Module])
117
-
118
- @class_attribute_readers = T.let(class_attribute_readers, T::Array[Symbol])
119
- @class_attribute_writers = T.let(class_attribute_writers, T::Array[Symbol])
120
- @class_attribute_predicates = T.let(class_attribute_predicates, T::Array[Symbol])
121
-
122
- @instance_attribute_readers = T.let(instance_attribute_readers, T::Array[Symbol])
123
- @instance_attribute_writers = T.let(instance_attribute_writers, T::Array[Symbol])
124
- @instance_attribute_predicates = T.let(instance_attribute_predicates, T::Array[Symbol])
125
- end
126
-
127
- sig { returns(T::Boolean) }
128
- def empty_attributes?
129
- @class_attribute_readers.empty? && @class_attribute_writers.empty?
130
- end
131
-
132
- sig { params(tree: RBI::Tree).void }
133
- def compile_class_attributes(tree)
134
- return if empty_attributes?
135
-
136
- # Create a synthetic module to hold the generated class methods
137
- tree << RBI::Module.new("GeneratedClassMethods") do |mod|
138
- class_attribute_readers.each do |attribute|
139
- mod << RBI::Method.new(attribute.to_s)
140
- end
141
-
142
- class_attribute_writers.each do |attribute|
143
- mod << RBI::Method.new("#{attribute}=") do |method|
144
- method << RBI::Param.new("value")
145
- end
146
- end
147
-
148
- class_attribute_predicates.each do |attribute|
149
- mod << RBI::Method.new("#{attribute}?")
150
- end
151
- end
152
-
153
- # Create a synthetic module to hold the generated instance methods
154
- tree << RBI::Module.new("GeneratedInstanceMethods") do |mod|
155
- instance_attribute_readers.each do |attribute|
156
- mod << RBI::Method.new(attribute.to_s)
157
- end
158
-
159
- instance_attribute_writers.each do |attribute|
160
- mod << RBI::Method.new("#{attribute}=") do |method|
161
- method << RBI::Param.new("value")
162
- end
163
- end
164
-
165
- instance_attribute_predicates.each do |attribute|
166
- mod << RBI::Method.new("#{attribute}?")
167
- end
168
- end
169
-
170
- # Add a mixes_in_class_methods and include for the generated modules
171
- tree << RBI::MixesInClassMethods.new("GeneratedClassMethods")
172
- tree << RBI::Include.new("GeneratedInstanceMethods")
173
- end
174
-
175
- sig { params(tree: RBI::Tree).returns([T::Array[Module], T::Array[Module]]) }
176
- def compile_mixes_in_class_methods(tree)
177
- includes = dynamic_includes.map do |mod|
178
- qname = qualified_name_of(mod)
179
-
180
- next if qname.nil? || qname.empty?
181
- next if filtered_mixin?(qname)
182
-
183
- tree << RBI::Include.new(qname)
184
-
185
- mod
186
- end.compact
187
-
188
- # If we can generate multiple mixes_in_class_methods, then we want to use all dynamic extends that are not the
189
- # constant itself
190
- mixed_in_class_methods = dynamic_extends.select do |mod|
191
- mod != @constant && !module_included_by_another_dynamic_extend?(mod, dynamic_extends)
192
- end
193
-
194
- return [[], []] if mixed_in_class_methods.empty?
195
-
196
- mixed_in_class_methods.each do |mod|
197
- qualified_name = qualified_name_of(mod)
198
-
199
- next if qualified_name.nil? || qualified_name.empty?
200
- next if filtered_mixin?(qualified_name)
201
-
202
- tree << RBI::MixesInClassMethods.new(qualified_name)
203
- end
204
-
205
- [mixed_in_class_methods, includes]
206
- rescue
207
- [[], []] # silence errors
208
- end
209
-
210
- sig { params(mod: Module, dynamic_extends: T::Array[Module]).returns(T::Boolean) }
211
- def module_included_by_another_dynamic_extend?(mod, dynamic_extends)
212
- dynamic_extends.any? do |dynamic_extend|
213
- mod != dynamic_extend && ancestors_of(dynamic_extend).include?(mod)
214
- end
215
- end
216
-
217
- sig { params(qualified_mixin_name: String).returns(T::Boolean) }
218
- def filtered_mixin?(qualified_mixin_name)
219
- # filter T:: namespace mixins that aren't T::Props
220
- # T::Props and subconstants have semantic value
221
- qualified_mixin_name.start_with?("::T::") && !qualified_mixin_name.start_with?("::T::Props")
222
- end
223
- end
@@ -1,59 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "pathname"
5
- require "shellwords"
6
-
7
- module Tapioca
8
- module Compilers
9
- module Sorbet
10
- SORBET_GEM_SPEC = T.let(
11
- Gem::Specification.find_by_name("sorbet-static"),
12
- Gem::Specification
13
- )
14
- SORBET = T.let(
15
- Pathname.new(SORBET_GEM_SPEC.full_gem_path) / "libexec" / "sorbet",
16
- Pathname
17
- )
18
- EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
19
-
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"),
23
- }.freeze, T::Hash[Symbol, Gem::Requirement])
24
-
25
- class << self
26
- extend(T::Sig)
27
-
28
- sig { params(args: String).returns(String) }
29
- def run(*args)
30
- IO.popen(
31
- [
32
- sorbet_path,
33
- "--quiet",
34
- *args,
35
- ].join(" "),
36
- err: "/dev/null"
37
- ).read
38
- end
39
-
40
- sig { returns(String) }
41
- def sorbet_path
42
- sorbet_path = ENV.fetch(EXE_PATH_ENV_VAR, SORBET)
43
- sorbet_path = SORBET if sorbet_path.empty?
44
- sorbet_path.to_s.shellescape
45
- end
46
-
47
- sig { params(feature: Symbol, version: T.nilable(Gem::Version)).returns(T::Boolean) }
48
- def supports?(feature, version: nil)
49
- version = SORBET_GEM_SPEC.version unless version
50
- requirement = FEATURE_REQUIREMENTS[feature]
51
-
52
- raise "Invalid Sorbet feature #{feature}" unless requirement
53
-
54
- requirement.satisfied_by?(version)
55
- end
56
- end
57
- end
58
- end
59
- end