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.
- checksums.yaml +4 -4
- data/Gemfile +8 -2
- data/README.md +27 -15
- data/Rakefile +10 -14
- data/lib/tapioca/cli.rb +65 -80
- data/lib/tapioca/{generators/base.rb → commands/command.rb} +16 -9
- data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
- data/lib/tapioca/{generators → commands}/gem.rb +93 -30
- data/lib/tapioca/{generators → commands}/init.rb +9 -13
- data/lib/tapioca/{generators → commands}/require.rb +8 -10
- data/lib/tapioca/commands/todo.rb +86 -0
- data/lib/tapioca/commands.rb +13 -0
- data/lib/tapioca/dsl/compiler.rb +185 -0
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +13 -11
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +28 -34
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +18 -16
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +12 -8
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +11 -16
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +14 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +23 -22
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +22 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +12 -13
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +20 -15
- data/lib/tapioca/dsl/compilers.rb +31 -0
- data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
- data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
- data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +6 -3
- data/lib/tapioca/dsl/pipeline.rb +169 -0
- data/lib/tapioca/gem/events.rb +120 -0
- data/lib/tapioca/gem/listeners/base.rb +48 -0
- data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
- data/lib/tapioca/gem/listeners/methods.rb +183 -0
- data/lib/tapioca/gem/listeners/mixins.rb +101 -0
- data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
- data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
- data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
- data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
- data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
- data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
- data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
- data/lib/tapioca/gem/listeners.rb +16 -0
- data/lib/tapioca/gem/pipeline.rb +365 -0
- data/lib/tapioca/helpers/cli_helper.rb +7 -0
- data/lib/tapioca/helpers/config_helper.rb +5 -8
- data/lib/tapioca/helpers/shims_helper.rb +87 -0
- data/lib/tapioca/helpers/signatures_helper.rb +17 -0
- data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
- data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
- data/lib/tapioca/helpers/test/isolation.rb +1 -1
- data/lib/tapioca/helpers/test/template.rb +13 -2
- data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
- data/lib/tapioca/internal.rb +18 -10
- data/lib/tapioca/rbi_ext/model.rb +14 -50
- data/lib/tapioca/rbi_formatter.rb +37 -0
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
- data/lib/tapioca/runtime/generic_type_registry.rb +168 -0
- data/lib/tapioca/runtime/loader.rb +123 -0
- data/lib/tapioca/runtime/reflection.rb +157 -0
- data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
- data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
- data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
- data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
- data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +69 -34
- data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
- data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
- data/lib/tapioca/static/symbol_loader.rb +83 -0
- data/lib/tapioca/static/symbol_table_parser.rb +63 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +2 -7
- metadata +83 -62
- data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
- data/lib/tapioca/compilers/dsl/base.rb +0 -195
- data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
- data/lib/tapioca/compilers/dsl_compiler.rb +0 -134
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
- data/lib/tapioca/compilers/sorbet.rb +0 -59
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
- data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
- data/lib/tapioca/compilers/todos_compiler.rb +0 -32
- data/lib/tapioca/generators/todo.rb +0 -76
- data/lib/tapioca/generators.rb +0 -9
- data/lib/tapioca/generic_type_registry.rb +0 -164
- data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
- data/lib/tapioca/loader.rb +0 -119
- data/lib/tapioca/reflection.rb +0 -151
- data/lib/tapioca/trackers/autoload.rb +0 -70
- data/lib/tapioca/trackers/constant_definition.rb +0 -42
- data/lib/tapioca/trackers/mixin.rb +0 -78
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "tapioca/helpers/signatures_helper"
|
|
5
|
+
|
|
4
6
|
module Tapioca
|
|
5
|
-
module
|
|
6
|
-
module
|
|
7
|
+
module Dsl
|
|
8
|
+
module Helpers
|
|
7
9
|
module ParamHelper
|
|
8
10
|
extend T::Sig
|
|
11
|
+
include SignaturesHelper
|
|
9
12
|
|
|
10
13
|
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
|
11
14
|
def create_param(name, type:)
|
|
@@ -44,7 +47,7 @@ module Tapioca
|
|
|
44
47
|
|
|
45
48
|
sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
|
|
46
49
|
def create_typed_param(param, type)
|
|
47
|
-
RBI::TypedParam.new(param: param, type: type)
|
|
50
|
+
RBI::TypedParam.new(param: param, type: sanitize_signature_types(type))
|
|
48
51
|
end
|
|
49
52
|
end
|
|
50
53
|
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "tapioca/dsl/compilers"
|
|
5
|
+
|
|
6
|
+
module Tapioca
|
|
7
|
+
module Dsl
|
|
8
|
+
class Pipeline
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
sig { returns(T::Enumerable[T.class_of(Compiler)]) }
|
|
12
|
+
attr_reader :compilers
|
|
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 { returns(T::Array[String]) }
|
|
21
|
+
attr_reader :errors
|
|
22
|
+
|
|
23
|
+
sig do
|
|
24
|
+
params(
|
|
25
|
+
requested_constants: T::Array[Module],
|
|
26
|
+
requested_compilers: T::Array[T.class_of(Compiler)],
|
|
27
|
+
excluded_compilers: T::Array[T.class_of(Compiler)],
|
|
28
|
+
error_handler: T.proc.params(error: String).void,
|
|
29
|
+
number_of_workers: T.nilable(Integer),
|
|
30
|
+
).void
|
|
31
|
+
end
|
|
32
|
+
def initialize(
|
|
33
|
+
requested_constants:,
|
|
34
|
+
requested_compilers: [],
|
|
35
|
+
excluded_compilers: [],
|
|
36
|
+
error_handler: $stderr.method(:puts).to_proc,
|
|
37
|
+
number_of_workers: nil
|
|
38
|
+
)
|
|
39
|
+
@compilers = T.let(
|
|
40
|
+
gather_compilers(requested_compilers, excluded_compilers),
|
|
41
|
+
T::Enumerable[T.class_of(Compiler)]
|
|
42
|
+
)
|
|
43
|
+
@requested_constants = requested_constants
|
|
44
|
+
@error_handler = error_handler
|
|
45
|
+
@number_of_workers = number_of_workers
|
|
46
|
+
@errors = T.let([], T::Array[String])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
sig do
|
|
50
|
+
type_parameters(:T).params(
|
|
51
|
+
blk: T.proc.params(constant: Module, rbi: RBI::File).returns(T.type_parameter(:T))
|
|
52
|
+
).returns(T::Array[T.type_parameter(:T)])
|
|
53
|
+
end
|
|
54
|
+
def run(&blk)
|
|
55
|
+
constants_to_process = gather_constants(requested_constants)
|
|
56
|
+
.select { |c| Module === c } # Filter value constants out
|
|
57
|
+
.sort_by! { |c| T.must(Runtime::Reflection.name_of(c)) }
|
|
58
|
+
|
|
59
|
+
if constants_to_process.empty?
|
|
60
|
+
report_error(<<~ERROR)
|
|
61
|
+
No classes/modules can be matched for RBI generation.
|
|
62
|
+
Please check that the requested classes/modules include processable DSL methods.
|
|
63
|
+
ERROR
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
result = Executor.new(
|
|
67
|
+
constants_to_process,
|
|
68
|
+
number_of_workers: @number_of_workers
|
|
69
|
+
).run_in_parallel do |constant|
|
|
70
|
+
rbi = rbi_for_constant(constant)
|
|
71
|
+
next if rbi.nil?
|
|
72
|
+
|
|
73
|
+
blk.call(constant, rbi)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
errors.each do |msg|
|
|
77
|
+
report_error(msg)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
result.compact
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
sig { params(error: String).void }
|
|
84
|
+
def add_error(error)
|
|
85
|
+
@errors << error
|
|
86
|
+
end
|
|
87
|
+
|
|
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 }
|
|
91
|
+
|
|
92
|
+
@compilers.any? do |compiler|
|
|
93
|
+
potential_names.any?(compiler.name)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
sig do
|
|
100
|
+
params(
|
|
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)])
|
|
104
|
+
end
|
|
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)
|
|
109
|
+
end.sort_by { |klass| T.must(klass.name) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
|
|
113
|
+
def gather_constants(requested_constants)
|
|
114
|
+
constants = compilers.map(&:processable_constants).reduce(Set.new, :union)
|
|
115
|
+
constants = filter_anonymous_and_reloaded_constants(constants)
|
|
116
|
+
|
|
117
|
+
constants &= requested_constants unless requested_constants.empty?
|
|
118
|
+
constants
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
sig { params(constants: T::Set[Module]).returns(T::Set[Module]) }
|
|
122
|
+
def filter_anonymous_and_reloaded_constants(constants)
|
|
123
|
+
# Group constants by their names
|
|
124
|
+
constants_by_name = constants
|
|
125
|
+
.group_by { |c| T.must(Runtime::Reflection.name_of(c)) }
|
|
126
|
+
.select { |name, _| !name.nil? }
|
|
127
|
+
|
|
128
|
+
# Find the constants that have been reloaded
|
|
129
|
+
reloaded_constants = constants_by_name.select { |_, constants| constants.size > 1 }.keys
|
|
130
|
+
|
|
131
|
+
unless reloaded_constants.empty?
|
|
132
|
+
reloaded_constant_names = reloaded_constants.map { |name| "`#{name}`" }.join(", ")
|
|
133
|
+
|
|
134
|
+
$stderr.puts("WARNING: Multiple constants with the same name: #{reloaded_constant_names}")
|
|
135
|
+
$stderr.puts("Make sure some object is not holding onto these constants during an app reload.")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Look up all the constants back from their names. The resulting constant set will be the
|
|
139
|
+
# set of constants that are actually in memory with those names.
|
|
140
|
+
constants_by_name
|
|
141
|
+
.keys
|
|
142
|
+
.map { |name| T.cast(Runtime::Reflection.constantize(name), Module) }
|
|
143
|
+
.to_set
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
sig { params(constant: Module).returns(T.nilable(RBI::File)) }
|
|
147
|
+
def rbi_for_constant(constant)
|
|
148
|
+
file = RBI::File.new(strictness: "true")
|
|
149
|
+
|
|
150
|
+
compilers.each do |compiler_class|
|
|
151
|
+
next unless compiler_class.handles?(constant)
|
|
152
|
+
compiler = compiler_class.new(self, file.root, constant)
|
|
153
|
+
compiler.decorate
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
return if file.root.empty?
|
|
157
|
+
|
|
158
|
+
file
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
sig { params(error: String).returns(T.noreturn) }
|
|
162
|
+
def report_error(error)
|
|
163
|
+
handler = error_handler
|
|
164
|
+
handler.call(error)
|
|
165
|
+
exit(1)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -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
|