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.
- 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 +84 -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 +10 -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 +11 -11
- 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 +10 -8
- 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 +10 -8
- 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 +16 -14
- 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 +2 -2
- data/lib/tapioca/{compilers/dsl_compiler.rb → dsl/pipeline.rb} +41 -33
- 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/rbi_helper.rb +17 -0
- data/lib/tapioca/helpers/shims_helper.rb +87 -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/internal.rb +17 -10
- data/lib/tapioca/rbi_ext/model.rb +2 -48
- 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 +166 -0
- data/lib/tapioca/runtime/loader.rb +123 -0
- data/lib/tapioca/runtime/reflection.rb +153 -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 +33 -15
- 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 +80 -60
- 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/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,15 +1,15 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "tapioca/
|
|
4
|
+
require "tapioca/dsl/compilers"
|
|
5
5
|
|
|
6
6
|
module Tapioca
|
|
7
|
-
module
|
|
8
|
-
class
|
|
7
|
+
module Dsl
|
|
8
|
+
class Pipeline
|
|
9
9
|
extend T::Sig
|
|
10
10
|
|
|
11
|
-
sig { returns(T::Enumerable[
|
|
12
|
-
attr_reader :
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
32
|
-
|
|
34
|
+
requested_compilers: [],
|
|
35
|
+
excluded_compilers: [],
|
|
33
36
|
error_handler: $stderr.method(:puts).to_proc,
|
|
34
37
|
number_of_workers: nil
|
|
35
38
|
)
|
|
36
|
-
@
|
|
37
|
-
|
|
38
|
-
T::Enumerable[
|
|
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
|
-
|
|
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(
|
|
80
|
-
def
|
|
81
|
-
|
|
83
|
+
sig { params(error: String).void }
|
|
84
|
+
def add_error(error)
|
|
85
|
+
@errors << error
|
|
86
|
+
end
|
|
82
87
|
|
|
83
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
).returns(T::Enumerable[
|
|
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
|
|
97
|
-
|
|
98
|
-
(
|
|
99
|
-
!
|
|
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 =
|
|
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
|
-
|
|
117
|
-
next unless
|
|
118
|
-
|
|
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
|