tapioca 0.6.1 → 0.7.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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +13 -2
  3. data/README.md +79 -25
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +66 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +17 -10
  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 +84 -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 +32 -24
  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 +29 -35
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +26 -24
  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 +10 -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 +12 -17
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +11 -11
  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 +10 -8
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +28 -25
  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 +10 -8
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +13 -14
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +12 -13
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +16 -14
  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 +2 -2
  46. data/lib/tapioca/{compilers/dsl_compiler.rb → dsl/pipeline.rb} +41 -33
  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/gemfile.rb +44 -20
  64. data/lib/tapioca/helpers/cli_helper.rb +16 -8
  65. data/lib/tapioca/helpers/config_helper.rb +113 -0
  66. data/lib/tapioca/helpers/rbi_helper.rb +17 -0
  67. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  68. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  69. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  70. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  71. data/lib/tapioca/helpers/test/template.rb +13 -2
  72. data/lib/tapioca/internal.rb +17 -10
  73. data/lib/tapioca/rbi_ext/model.rb +2 -48
  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 +166 -0
  77. data/lib/tapioca/runtime/loader.rb +123 -0
  78. data/lib/tapioca/runtime/reflection.rb +153 -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 +110 -54
  85. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  86. data/lib/tapioca/{compilers → static}/requires_compiler.rb +5 -12
  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 +82 -62
  92. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -711
  93. data/lib/tapioca/compilers/dsl/base.rb +0 -179
  94. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  95. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -198
  96. data/lib/tapioca/compilers/sorbet.rb +0 -59
  97. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  98. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  99. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  100. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  101. data/lib/tapioca/generators/todo.rb +0 -76
  102. data/lib/tapioca/generators.rb +0 -9
  103. data/lib/tapioca/generic_type_registry.rb +0 -149
  104. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -98
  105. data/lib/tapioca/loader.rb +0 -119
  106. data/lib/tapioca/reflection.rb +0 -151
  107. data/lib/tapioca/trackers/autoload.rb +0 -70
  108. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  109. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -0,0 +1,114 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Dsl
6
+ module Helpers
7
+ class ActiveRecordColumnTypeHelper
8
+ extend T::Sig
9
+
10
+ sig { params(constant: T.class_of(ActiveRecord::Base)).void }
11
+ def initialize(constant)
12
+ @constant = constant
13
+ end
14
+
15
+ sig { params(column_name: String).returns([String, String]) }
16
+ def type_for(column_name)
17
+ return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(@constant)
18
+
19
+ column_type = @constant.attribute_types[column_name]
20
+
21
+ getter_type =
22
+ case column_type
23
+ when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
24
+ "::Money"
25
+ when ActiveRecord::Type::Integer
26
+ "::Integer"
27
+ when ActiveRecord::Type::String
28
+ "::String"
29
+ when ActiveRecord::Type::Date
30
+ "::Date"
31
+ when ActiveRecord::Type::Decimal
32
+ "::BigDecimal"
33
+ when ActiveRecord::Type::Float
34
+ "::Float"
35
+ when ActiveRecord::Type::Boolean
36
+ "T::Boolean"
37
+ when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
38
+ "::DateTime"
39
+ when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
40
+ "::ActiveSupport::TimeWithZone"
41
+ else
42
+ handle_unknown_type(column_type)
43
+ end
44
+
45
+ column = @constant.columns_hash[column_name]
46
+ setter_type = getter_type
47
+
48
+ if column&.null
49
+ return [as_nilable_type(getter_type), as_nilable_type(setter_type)]
50
+ end
51
+
52
+ if column_name == @constant.primary_key ||
53
+ column_name == "created_at" ||
54
+ column_name == "updated_at"
55
+ getter_type = as_nilable_type(getter_type)
56
+ end
57
+
58
+ [getter_type, setter_type]
59
+ end
60
+
61
+ private
62
+
63
+ sig { params(constant: Module).returns(T::Boolean) }
64
+ def do_not_generate_strong_types?(constant)
65
+ Object.const_defined?(:StrongTypeGeneration) &&
66
+ !(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
67
+ end
68
+
69
+ sig { params(type: String).returns(String) }
70
+ def as_nilable_type(type)
71
+ if type.start_with?("T.nilable(") || type == "T.untyped"
72
+ type
73
+ else
74
+ "T.nilable(#{type})"
75
+ end
76
+ end
77
+
78
+ sig { params(column_type: Object).returns(String) }
79
+ def handle_unknown_type(column_type)
80
+ return "T.untyped" unless ActiveModel::Type::Value === column_type
81
+ return "T.untyped" if Runtime::GenericTypeRegistry.generic_type_instance?(column_type)
82
+
83
+ lookup_return_type_of_method(column_type, :deserialize) ||
84
+ lookup_return_type_of_method(column_type, :cast) ||
85
+ lookup_arg_type_of_method(column_type, :serialize) ||
86
+ "T.untyped"
87
+ end
88
+
89
+ sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
90
+ def lookup_return_type_of_method(column_type, method)
91
+ signature = Runtime::Reflection.signature_of(column_type.method(method))
92
+ return unless signature
93
+
94
+ return_type = signature.return_type
95
+ return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
96
+
97
+ return_type.to_s
98
+ end
99
+
100
+ sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
101
+ def lookup_arg_type_of_method(column_type, method)
102
+ signature = Runtime::Reflection.signature_of(column_type.method(method))
103
+ return unless signature
104
+
105
+ # Arg types is an array [name, type] entries, so we desctructure the type of
106
+ # first argument to get the first argument type
107
+ _, first_argument_type = signature.arg_types.first
108
+
109
+ first_argument_type.to_s
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Dsl
6
+ module Helpers
7
+ module ActiveRecordConstantsHelper
8
+ extend T::Sig
9
+
10
+ ReflectionType = T.type_alias do
11
+ T.any(::ActiveRecord::Reflection::ThroughReflection, ::ActiveRecord::Reflection::AssociationReflection)
12
+ end
13
+
14
+ AttributeMethodsModuleName = T.let("GeneratedAttributeMethods", String)
15
+ AssociationMethodsModuleName = T.let("GeneratedAssociationMethods", String)
16
+
17
+ RelationMethodsModuleName = T.let("GeneratedRelationMethods", String)
18
+ AssociationRelationMethodsModuleName = T.let("GeneratedAssociationRelationMethods", String)
19
+ CommonRelationMethodsModuleName = T.let("CommonRelationMethods", String)
20
+
21
+ RelationClassName = T.let("PrivateRelation", String)
22
+ RelationWhereChainClassName = T.let("PrivateRelationWhereChain", String)
23
+ AssociationRelationClassName = T.let("PrivateAssociationRelation", String)
24
+ AssociationRelationWhereChainClassName = T.let("PrivateAssociationRelationWhereChain", String)
25
+ AssociationsCollectionProxyClassName = T.let("PrivateCollectionProxy", String)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,8 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- module Compilers
6
- module Dsl
5
+ module Dsl
6
+ module Helpers
7
7
  module ParamHelper
8
8
  extend T::Sig
9
9
 
@@ -1,15 +1,15 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "tapioca/compilers/dsl/base"
4
+ require "tapioca/dsl/compilers"
5
5
 
6
6
  module Tapioca
7
- module Compilers
8
- class DslCompiler
7
+ module Dsl
8
+ class Pipeline
9
9
  extend T::Sig
10
10
 
11
- sig { returns(T::Enumerable[Dsl::Base]) }
12
- attr_reader :generators
11
+ sig { returns(T::Enumerable[T.class_of(Compiler)]) }
12
+ attr_reader :compilers
13
13
 
14
14
  sig { returns(T::Array[Module]) }
15
15
  attr_reader :requested_constants
@@ -17,29 +17,33 @@ module Tapioca
17
17
  sig { returns(T.proc.params(error: String).void) }
18
18
  attr_reader :error_handler
19
19
 
20
+ sig { returns(T::Array[String]) }
21
+ attr_reader :errors
22
+
20
23
  sig do
21
24
  params(
22
25
  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)],
26
+ requested_compilers: T::Array[T.class_of(Compiler)],
27
+ excluded_compilers: T::Array[T.class_of(Compiler)],
25
28
  error_handler: T.proc.params(error: String).void,
26
29
  number_of_workers: T.nilable(Integer),
27
30
  ).void
28
31
  end
29
32
  def initialize(
30
33
  requested_constants:,
31
- requested_generators: [],
32
- excluded_generators: [],
34
+ requested_compilers: [],
35
+ excluded_compilers: [],
33
36
  error_handler: $stderr.method(:puts).to_proc,
34
37
  number_of_workers: nil
35
38
  )
36
- @generators = T.let(
37
- gather_generators(requested_generators, excluded_generators),
38
- T::Enumerable[Dsl::Base]
39
+ @compilers = T.let(
40
+ gather_compilers(requested_compilers, excluded_compilers),
41
+ T::Enumerable[T.class_of(Compiler)]
39
42
  )
40
43
  @requested_constants = requested_constants
41
44
  @error_handler = error_handler
42
45
  @number_of_workers = number_of_workers
46
+ @errors = T.let([], T::Array[String])
43
47
  end
44
48
 
45
49
  sig do
@@ -49,8 +53,8 @@ module Tapioca
49
53
  end
50
54
  def run(&blk)
51
55
  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)) }
56
+ .select { |c| Runtime::Reflection.name_of(c) && Module === c } # Filter anonymous or value constants
57
+ .sort_by! { |c| T.must(Runtime::Reflection.name_of(c)) }
54
58
 
55
59
  if constants_to_process.empty?
56
60
  report_error(<<~ERROR)
@@ -69,42 +73,45 @@ module Tapioca
69
73
  blk.call(constant, rbi)
70
74
  end
71
75
 
72
- generators.flat_map(&:errors).each do |msg|
76
+ errors.each do |msg|
73
77
  report_error(msg)
74
78
  end
75
79
 
76
80
  result.compact
77
81
  end
78
82
 
79
- sig { params(generator_name: String).returns(T::Boolean) }
80
- def generator_enabled?(generator_name)
81
- generator = Dsl::Base.resolve(generator_name)
83
+ sig { params(error: String).void }
84
+ def add_error(error)
85
+ @errors << error
86
+ end
82
87
 
83
- return false unless generator
88
+ sig { params(compiler_name: String).returns(T::Boolean) }
89
+ def compiler_enabled?(compiler_name)
90
+ potential_names = Compilers::NAMESPACES.map { |namespace| namespace + compiler_name }
84
91
 
85
- @generators.any?(generator)
92
+ @compilers.any? do |compiler|
93
+ potential_names.any?(compiler.name)
94
+ end
86
95
  end
87
96
 
88
97
  private
89
98
 
90
99
  sig do
91
100
  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])
101
+ requested_compilers: T::Array[T.class_of(Compiler)],
102
+ excluded_compilers: T::Array[T.class_of(Compiler)]
103
+ ).returns(T::Enumerable[T.class_of(Compiler)])
95
104
  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)
105
+ def gather_compilers(requested_compilers, excluded_compilers)
106
+ Runtime::Reflection.descendants_of(Compiler).select do |klass|
107
+ (requested_compilers.empty? || requested_compilers.include?(klass)) &&
108
+ !excluded_compilers.include?(klass)
100
109
  end.sort_by { |klass| T.must(klass.name) }
101
-
102
- generator_klasses.map { |generator_klass| generator_klass.new(self) }
103
110
  end
104
111
 
105
112
  sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
106
113
  def gather_constants(requested_constants)
107
- constants = generators.map(&:processable_constants).reduce(Set.new, :union)
114
+ constants = compilers.map(&:processable_constants).reduce(Set.new, :union)
108
115
  constants &= requested_constants unless requested_constants.empty?
109
116
  constants
110
117
  end
@@ -113,9 +120,10 @@ module Tapioca
113
120
  def rbi_for_constant(constant)
114
121
  file = RBI::File.new(strictness: "true")
115
122
 
116
- generators.each do |generator|
117
- next unless generator.handles?(constant)
118
- generator.decorate(file.root, constant)
123
+ compilers.each do |compiler_class|
124
+ next unless compiler_class.handles?(constant)
125
+ compiler = compiler_class.new(self, file.root, constant)
126
+ compiler.decorate
119
127
  end
120
128
 
121
129
  return if file.root.empty?
@@ -0,0 +1,120 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "pathname"
5
+
6
+ module Tapioca
7
+ module Gem
8
+ class Event
9
+ extend T::Sig
10
+ extend T::Helpers
11
+
12
+ abstract!
13
+ end
14
+
15
+ class SymbolFound < Event
16
+ extend T::Sig
17
+
18
+ sig { returns(String) }
19
+ attr_reader :symbol
20
+
21
+ sig { params(symbol: String).void }
22
+ def initialize(symbol)
23
+ super()
24
+ @symbol = symbol
25
+ end
26
+ end
27
+
28
+ class ConstantFound < Event
29
+ extend T::Sig
30
+
31
+ sig { returns(String) }
32
+ attr_reader :symbol
33
+
34
+ sig { returns(BasicObject).checked(:never) }
35
+ attr_reader :constant
36
+
37
+ sig { params(symbol: String, constant: BasicObject).void.checked(:never) }
38
+ def initialize(symbol, constant)
39
+ super()
40
+ @symbol = symbol
41
+ @constant = constant
42
+ end
43
+ end
44
+
45
+ class NodeAdded < Event
46
+ extend T::Helpers
47
+ extend T::Sig
48
+
49
+ abstract!
50
+
51
+ sig { returns(String) }
52
+ attr_reader :symbol
53
+
54
+ sig { returns(Module).checked(:never) }
55
+ attr_reader :constant
56
+
57
+ sig { params(symbol: String, constant: Module).void.checked(:never) }
58
+ def initialize(symbol, constant)
59
+ super()
60
+ @symbol = symbol
61
+ @constant = constant
62
+ end
63
+ end
64
+
65
+ class ConstNodeAdded < NodeAdded
66
+ extend T::Sig
67
+
68
+ sig { returns(RBI::Const) }
69
+ attr_reader :node
70
+
71
+ sig { params(symbol: String, constant: Module, node: RBI::Const).void.checked(:never) }
72
+ def initialize(symbol, constant, node)
73
+ super(symbol, constant)
74
+ @node = node
75
+ end
76
+ end
77
+
78
+ class ScopeNodeAdded < NodeAdded
79
+ extend T::Sig
80
+
81
+ sig { returns(RBI::Scope) }
82
+ attr_reader :node
83
+
84
+ sig { params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never) }
85
+ def initialize(symbol, constant, node)
86
+ super(symbol, constant)
87
+ @node = node
88
+ end
89
+ end
90
+
91
+ class MethodNodeAdded < NodeAdded
92
+ extend T::Sig
93
+
94
+ sig { returns(RBI::Method) }
95
+ attr_reader :node
96
+
97
+ sig { returns(T.untyped) }
98
+ attr_reader :signature
99
+
100
+ sig { returns(T::Array[[Symbol, String]]) }
101
+ attr_reader :parameters
102
+
103
+ sig do
104
+ params(
105
+ symbol: String,
106
+ constant: Module,
107
+ node: RBI::Method,
108
+ signature: T.untyped,
109
+ parameters: T::Array[[Symbol, String]]
110
+ ).void.checked(:never)
111
+ end
112
+ def initialize(symbol, constant, node, signature, parameters)
113
+ super(symbol, constant)
114
+ @node = node
115
+ @signature = signature
116
+ @parameters = parameters
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,48 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class Base
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ abstract!
12
+
13
+ sig { params(pipeline: Pipeline).void }
14
+ def initialize(pipeline)
15
+ @pipeline = pipeline
16
+ end
17
+
18
+ sig { params(event: NodeAdded).void }
19
+ def dispatch(event)
20
+ case event
21
+ when ConstNodeAdded
22
+ on_const(event)
23
+ when ScopeNodeAdded
24
+ on_scope(event)
25
+ when MethodNodeAdded
26
+ on_method(event)
27
+ else
28
+ raise "Unsupported event #{event.class}"
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ sig { params(event: ConstNodeAdded).void }
35
+ def on_const(event)
36
+ end
37
+
38
+ sig { params(event: ScopeNodeAdded).void }
39
+ def on_scope(event)
40
+ end
41
+
42
+ sig { params(event: MethodNodeAdded).void }
43
+ def on_method(event)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class DynamicMixins < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ private
13
+
14
+ sig { override.params(event: ScopeNodeAdded).void }
15
+ def on_scope(event)
16
+ constant = event.constant
17
+ return if constant.is_a?(Class)
18
+
19
+ node = event.node
20
+ mixin_compiler = Runtime::DynamicMixinCompiler.new(constant)
21
+ mixin_compiler.compile_class_attributes(node)
22
+ dynamic_extends, dynamic_includes = mixin_compiler.compile_mixes_in_class_methods(node)
23
+
24
+ (dynamic_includes + dynamic_extends).each do |mod|
25
+ name = @pipeline.name_of(mod)
26
+ @pipeline.push_symbol(name) if name
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,183 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class Methods < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ SPECIAL_METHOD_NAMES = T.let([
13
+ "!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^",
14
+ "<", "<=", "=>", ">", ">=", "==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`",
15
+ ], T::Array[String])
16
+
17
+ private
18
+
19
+ sig { override.params(event: ScopeNodeAdded).void }
20
+ def on_scope(event)
21
+ symbol = event.symbol
22
+ constant = event.constant
23
+ node = event.node
24
+
25
+ compile_method(node, symbol, constant, initialize_method_for(constant))
26
+ compile_directly_owned_methods(node, symbol, constant)
27
+ compile_directly_owned_methods(node, symbol, singleton_class_of(constant))
28
+ end
29
+
30
+ sig do
31
+ params(
32
+ tree: RBI::Tree,
33
+ module_name: String,
34
+ mod: Module,
35
+ for_visibility: T::Array[Symbol]
36
+ ).void
37
+ end
38
+ def compile_directly_owned_methods(tree, module_name, mod, for_visibility = [:public, :protected, :private])
39
+ method_names_by_visibility(mod)
40
+ .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
41
+ .each do |visibility, method_list|
42
+ method_list.sort!.map do |name|
43
+ next if name == :initialize
44
+ vis = case visibility
45
+ when :protected
46
+ RBI::Protected.new
47
+ when :private
48
+ RBI::Private.new
49
+ else
50
+ RBI::Public.new
51
+ end
52
+ compile_method(tree, module_name, mod, mod.instance_method(name), vis)
53
+ end
54
+ end
55
+ end
56
+
57
+ sig do
58
+ params(
59
+ tree: RBI::Tree,
60
+ symbol_name: String,
61
+ constant: Module,
62
+ method: T.nilable(UnboundMethod),
63
+ visibility: RBI::Visibility
64
+ ).void
65
+ end
66
+ def compile_method(tree, symbol_name, constant, method, visibility = RBI::Public.new)
67
+ return unless method
68
+ return unless method.owner == constant
69
+ return if @pipeline.symbol_in_payload?(symbol_name) && !@pipeline.method_in_gem?(method)
70
+
71
+ signature = signature_of(method)
72
+ method = T.let(signature.method, UnboundMethod) if signature
73
+
74
+ method_name = method.name.to_s
75
+ return unless valid_method_name?(method_name)
76
+ return if struct_method?(constant, method_name)
77
+ return if method_name.start_with?("__t_props_generated_")
78
+
79
+ parameters = T.let(method.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
80
+
81
+ sanitized_parameters = parameters.each_with_index.map do |(type, name), index|
82
+ fallback_arg_name = "_arg#{index}"
83
+
84
+ name = if name
85
+ name.to_s
86
+ else
87
+ # For attr_writer methods, Sorbet signatures have the name
88
+ # of the method (without the trailing = sign) as the name of
89
+ # the only parameter. So, if the parameter does not have a name
90
+ # then the replacement name should be the name of the method
91
+ # (minus trailing =) if and only if there is a signature for the
92
+ # method and the parameter is required and there is a single
93
+ # parameter and the signature also defines a single parameter and
94
+ # the name of the method ends with a = character.
95
+ writer_method_with_sig = (
96
+ signature && type == :req &&
97
+ parameters.size == 1 &&
98
+ signature.arg_types.size == 1 &&
99
+ method_name[-1] == "="
100
+ )
101
+
102
+ if writer_method_with_sig
103
+ method_name.delete_suffix("=")
104
+ else
105
+ fallback_arg_name
106
+ end
107
+ end
108
+
109
+ # Sanitize param names
110
+ name = fallback_arg_name unless valid_parameter_name?(name)
111
+
112
+ [type, name]
113
+ end
114
+
115
+ rbi_method = RBI::Method.new(
116
+ method_name,
117
+ is_singleton: constant.singleton_class?,
118
+ visibility: visibility
119
+ )
120
+
121
+ sanitized_parameters.each do |type, name|
122
+ case type
123
+ when :req
124
+ rbi_method << RBI::Param.new(name)
125
+ when :opt
126
+ rbi_method << RBI::OptParam.new(name, "T.unsafe(nil)")
127
+ when :rest
128
+ rbi_method << RBI::RestParam.new(name)
129
+ when :keyreq
130
+ rbi_method << RBI::KwParam.new(name)
131
+ when :key
132
+ rbi_method << RBI::KwOptParam.new(name, "T.unsafe(nil)")
133
+ when :keyrest
134
+ rbi_method << RBI::KwRestParam.new(name)
135
+ when :block
136
+ rbi_method << RBI::BlockParam.new(name)
137
+ end
138
+ end
139
+
140
+ @pipeline.push_method(symbol_name, constant, rbi_method, signature, sanitized_parameters)
141
+ tree << rbi_method
142
+ end
143
+
144
+ sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
145
+ def method_names_by_visibility(mod)
146
+ {
147
+ public: public_instance_methods_of(mod),
148
+ protected: protected_instance_methods_of(mod),
149
+ private: private_instance_methods_of(mod),
150
+ }
151
+ end
152
+
153
+ sig { params(constant: Module, method_name: String).returns(T::Boolean) }
154
+ def struct_method?(constant, method_name)
155
+ return false unless T::Props::ClassMethods === constant
156
+
157
+ constant
158
+ .props
159
+ .keys
160
+ .include?(method_name.gsub(/=$/, "").to_sym)
161
+ end
162
+
163
+ sig { params(name: String).returns(T::Boolean) }
164
+ def valid_method_name?(name)
165
+ return true if SPECIAL_METHOD_NAMES.include?(name)
166
+ !!name.match(/^[[:word:]]+[?!=]?$/)
167
+ end
168
+
169
+ sig { params(name: String).returns(T::Boolean) }
170
+ def valid_parameter_name?(name)
171
+ name.match?(/^[[[:alnum:]]_]+$/)
172
+ end
173
+
174
+ sig { params(constant: Module).returns(T.nilable(UnboundMethod)) }
175
+ def initialize_method_for(constant)
176
+ constant.instance_method(:initialize)
177
+ rescue
178
+ nil
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end