tapioca 0.6.4 → 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 (108) 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 +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 +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 +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 +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 +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 +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 +10 -8
  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 +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/helpers/cli_helper.rb +7 -0
  64. data/lib/tapioca/helpers/config_helper.rb +5 -8
  65. data/lib/tapioca/helpers/rbi_helper.rb +17 -0
  66. data/lib/tapioca/helpers/shims_helper.rb +87 -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/internal.rb +17 -10
  72. data/lib/tapioca/rbi_ext/model.rb +2 -48
  73. data/lib/tapioca/rbi_formatter.rb +37 -0
  74. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  75. data/lib/tapioca/runtime/generic_type_registry.rb +166 -0
  76. data/lib/tapioca/runtime/loader.rb +123 -0
  77. data/lib/tapioca/runtime/reflection.rb +153 -0
  78. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  79. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  80. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  81. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  82. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  83. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +33 -15
  84. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  85. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  86. data/lib/tapioca/static/symbol_loader.rb +83 -0
  87. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  88. data/lib/tapioca/version.rb +1 -1
  89. data/lib/tapioca.rb +2 -7
  90. metadata +80 -60
  91. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  92. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  93. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  94. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  95. data/lib/tapioca/compilers/sorbet.rb +0 -59
  96. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  97. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  98. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  99. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  100. data/lib/tapioca/generators/todo.rb +0 -76
  101. data/lib/tapioca/generators.rb +0 -9
  102. data/lib/tapioca/generic_type_registry.rb +0 -164
  103. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  104. data/lib/tapioca/loader.rb +0 -119
  105. data/lib/tapioca/reflection.rb +0 -151
  106. data/lib/tapioca/trackers/autoload.rb +0 -70
  107. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  108. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -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
@@ -0,0 +1,101 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class Mixins < 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
+ singleton_class = singleton_class_of(constant)
18
+
19
+ interesting_ancestors = interesting_ancestors_of(constant)
20
+ interesting_singleton_class_ancestors = interesting_ancestors_of(singleton_class)
21
+
22
+ prepends = interesting_ancestors.take_while { |c| !are_equal?(constant, c) }
23
+ includes = interesting_ancestors.drop(prepends.size + 1)
24
+ extends = interesting_singleton_class_ancestors.reject do |mod|
25
+ Module != class_of(mod) || are_equal?(mod, singleton_class)
26
+ end
27
+
28
+ node = event.node
29
+ add_mixins(node, prepends.reverse, Runtime::Trackers::Mixin::Type::Prepend)
30
+ add_mixins(node, includes.reverse, Runtime::Trackers::Mixin::Type::Include)
31
+ add_mixins(node, extends.reverse, Runtime::Trackers::Mixin::Type::Extend)
32
+ end
33
+
34
+ sig do
35
+ params(
36
+ tree: RBI::Tree,
37
+ mods: T::Array[Module],
38
+ mixin_type: Runtime::Trackers::Mixin::Type
39
+ ).void
40
+ end
41
+ def add_mixins(tree, mods, mixin_type)
42
+ mods
43
+ .select do |mod|
44
+ name = @pipeline.name_of(mod)
45
+
46
+ name && !filtered_mixin?(name)
47
+ end
48
+ .map do |mod|
49
+ name = @pipeline.name_of(mod)
50
+ @pipeline.push_symbol(name) if name
51
+
52
+ qname = qualified_name_of(mod)
53
+ case mixin_type
54
+ # TODO: Sorbet currently does not handle prepend
55
+ # properly for method resolution, so we generate an
56
+ # include statement instead
57
+ when Runtime::Trackers::Mixin::Type::Include, Runtime::Trackers::Mixin::Type::Prepend
58
+ tree << RBI::Include.new(T.must(qname))
59
+ when Runtime::Trackers::Mixin::Type::Extend
60
+ tree << RBI::Extend.new(T.must(qname))
61
+ end
62
+ end
63
+ end
64
+
65
+ sig { params(mixin_name: String).returns(T::Boolean) }
66
+ def filtered_mixin?(mixin_name)
67
+ # filter T:: namespace mixins that aren't T::Props
68
+ # T::Props and subconstants have semantic value
69
+ mixin_name.start_with?("T::") && !mixin_name.start_with?("T::Props")
70
+ end
71
+
72
+ sig { params(constant: Module).returns(T::Array[Module]) }
73
+ def interesting_ancestors_of(constant)
74
+ inherited_ancestors_ids = Set.new(
75
+ inherited_ancestors_of(constant).map { |mod| object_id_of(mod) }
76
+ )
77
+ # TODO: There is actually a bug here where this will drop modules that
78
+ # may be included twice. For example:
79
+ #
80
+ # ```ruby
81
+ # class Foo
82
+ # prepend Kernel
83
+ # end
84
+ # ````
85
+ # would give:
86
+ # ```ruby
87
+ # Foo.ancestors #=> [Kernel, Foo, Object, Kernel, BasicObject]
88
+ # ````
89
+ # but since we drop `Kernel` whenever we match it, we would miss
90
+ # the `prepend Kernel` in the output.
91
+ #
92
+ # Instead, we should only drop the tail matches of the ancestors and
93
+ # inherited ancestors, past the location of the constant itself.
94
+ constant.ancestors.reject do |mod|
95
+ inherited_ancestors_ids.include?(object_id_of(mod))
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class RemoveEmptyPayloadScopes < 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
+ event.node.detach if @pipeline.symbol_in_payload?(event.symbol) && event.node.empty?
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetEnums < Base
8
+ extend T::Sig
9
+
10
+ private
11
+
12
+ sig { override.params(event: ScopeNodeAdded).void }
13
+ def on_scope(event)
14
+ constant = event.constant
15
+ return unless T::Enum > event.constant
16
+
17
+ enums = T.unsafe(constant).values.map do |enum_type|
18
+ enum_type.instance_variable_get(:@const_name).to_s
19
+ end
20
+
21
+ event.node << RBI::TEnumBlock.new(enums)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end