tapioca 0.6.4 → 0.7.0

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