tapioca 0.10.5 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/tapioca/cli.rb +5 -1
- data/lib/tapioca/commands/annotations.rb +2 -0
- data/lib/tapioca/commands/configure.rb +1 -0
- data/lib/tapioca/commands/dsl.rb +17 -3
- data/lib/tapioca/dsl/compilers/aasm.rb +67 -15
- data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_columns.rb +3 -3
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +117 -66
- data/lib/tapioca/dsl/compilers/active_record_scope.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_secure_token.rb +74 -0
- data/lib/tapioca/dsl/compilers/graphql_input_object.rb +21 -1
- data/lib/tapioca/dsl/compilers/kredis.rb +130 -0
- data/lib/tapioca/dsl/extensions/active_record.rb +9 -0
- data/lib/tapioca/dsl/extensions/kredis.rb +114 -0
- data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +1 -0
- data/lib/tapioca/dsl/pipeline.rb +12 -5
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +1 -1
- data/lib/tapioca/gem/pipeline.rb +14 -0
- data/lib/tapioca/loaders/loader.rb +93 -32
- data/lib/tapioca/rbi_ext/model.rb +1 -1
- data/lib/tapioca/runtime/attached_class_of_32.rb +20 -0
- data/lib/tapioca/runtime/attached_class_of_legacy.rb +27 -0
- data/lib/tapioca/runtime/reflection.rb +11 -10
- data/lib/tapioca/static/symbol_loader.rb +14 -14
- data/lib/tapioca/version.rb +1 -1
- metadata +7 -2
@@ -21,6 +21,15 @@ module Tapioca
|
|
21
21
|
super
|
22
22
|
end
|
23
23
|
|
24
|
+
attr_reader :__tapioca_secure_tokens
|
25
|
+
|
26
|
+
def has_secure_token(attribute = :token, length: ::ActiveRecord::SecureToken::MINIMUM_TOKEN_LENGTH)
|
27
|
+
@__tapioca_secure_tokens ||= []
|
28
|
+
@__tapioca_secure_tokens << attribute
|
29
|
+
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
24
33
|
::ActiveRecord::Base.singleton_class.prepend(self)
|
25
34
|
end
|
26
35
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "kredis"
|
6
|
+
rescue LoadError
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
module Tapioca
|
11
|
+
module Dsl
|
12
|
+
module Compilers
|
13
|
+
module Extensions
|
14
|
+
module Kredis
|
15
|
+
attr_reader :__tapioca_kredis_types
|
16
|
+
|
17
|
+
def kredis_proxy(name, key: nil, config: :shared, after_change: nil)
|
18
|
+
collect_kredis_type(name, "Kredis::Types::Proxy")
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def kredis_string(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
|
23
|
+
collect_kredis_type(name, "Kredis::Types::Scalar")
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def kredis_integer(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
|
28
|
+
collect_kredis_type(name, "Kredis::Types::Scalar")
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def kredis_decimal(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
|
33
|
+
collect_kredis_type(name, "Kredis::Types::Scalar")
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
def kredis_datetime(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
|
38
|
+
collect_kredis_type(name, "Kredis::Types::Scalar")
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def kredis_flag(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
|
43
|
+
collect_kredis_type(name, "Kredis::Types::Flag")
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def kredis_float(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
|
48
|
+
collect_kredis_type(name, "Kredis::Types::Scalar")
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
def kredis_enum(name, key: nil, values:, default:, config: :shared, after_change: nil)
|
53
|
+
collect_kredis_type(name, "Kredis::Types::Enum", values: values)
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
def kredis_json(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
|
58
|
+
collect_kredis_type(name, "Kredis::Types::Scalar")
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
62
|
+
def kredis_list(name, key: nil, typed: :string, config: :shared, after_change: nil)
|
63
|
+
collect_kredis_type(name, "Kredis::Types::List")
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
def kredis_unique_list(name, limit: nil, key: nil, typed: :string, config: :shared, after_change: nil)
|
68
|
+
collect_kredis_type(name, "Kredis::Types::UniqueList")
|
69
|
+
super
|
70
|
+
end
|
71
|
+
|
72
|
+
def kredis_set(name, key: nil, typed: :string, config: :shared, after_change: nil)
|
73
|
+
collect_kredis_type(name, "Kredis::Types::Set")
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
def kredis_slot(name, key: nil, config: :shared, after_change: nil)
|
78
|
+
collect_kredis_type(name, "Kredis::Types::Slots")
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
def kredis_slots(name, available:, key: nil, config: :shared, after_change: nil)
|
83
|
+
collect_kredis_type(name, "Kredis::Types::Slots")
|
84
|
+
super
|
85
|
+
end
|
86
|
+
|
87
|
+
def kredis_counter(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
|
88
|
+
collect_kredis_type(name, "Kredis::Types::Counter")
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
def kredis_hash(name, key: nil, typed: :string, config: :shared, after_change: nil)
|
93
|
+
collect_kredis_type(name, "Kredis::Types::Hash")
|
94
|
+
super
|
95
|
+
end
|
96
|
+
|
97
|
+
def kredis_boolean(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
|
98
|
+
collect_kredis_type(name, "Kredis::Types::Scalar")
|
99
|
+
super
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def collect_kredis_type(method, type, values: nil)
|
105
|
+
@__tapioca_kredis_types ||= {}
|
106
|
+
@__tapioca_kredis_types[method.to_s] = { type: type, values: values }
|
107
|
+
end
|
108
|
+
|
109
|
+
::Kredis::Attributes::ClassMethods.prepend(self)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -14,6 +14,7 @@ module Tapioca
|
|
14
14
|
AttributeMethodsModuleName = T.let("GeneratedAttributeMethods", String)
|
15
15
|
AssociationMethodsModuleName = T.let("GeneratedAssociationMethods", String)
|
16
16
|
DelegatedTypesModuleName = T.let("GeneratedDelegatedTypeMethods", String)
|
17
|
+
SecureTokensModuleName = T.let("GeneratedSecureTokenMethods", String)
|
17
18
|
|
18
19
|
RelationMethodsModuleName = T.let("GeneratedRelationMethods", String)
|
19
20
|
AssociationRelationMethodsModuleName = T.let("GeneratedAssociationRelationMethods", String)
|
data/lib/tapioca/dsl/pipeline.rb
CHANGED
@@ -12,6 +12,9 @@ module Tapioca
|
|
12
12
|
sig { returns(T::Array[Module]) }
|
13
13
|
attr_reader :requested_constants
|
14
14
|
|
15
|
+
sig { returns(T::Array[Pathname]) }
|
16
|
+
attr_reader :requested_paths
|
17
|
+
|
15
18
|
sig { returns(T.proc.params(error: String).void) }
|
16
19
|
attr_reader :error_handler
|
17
20
|
|
@@ -21,6 +24,7 @@ module Tapioca
|
|
21
24
|
sig do
|
22
25
|
params(
|
23
26
|
requested_constants: T::Array[Module],
|
27
|
+
requested_paths: T::Array[Pathname],
|
24
28
|
requested_compilers: T::Array[T.class_of(Compiler)],
|
25
29
|
excluded_compilers: T::Array[T.class_of(Compiler)],
|
26
30
|
error_handler: T.proc.params(error: String).void,
|
@@ -29,6 +33,7 @@ module Tapioca
|
|
29
33
|
end
|
30
34
|
def initialize(
|
31
35
|
requested_constants:,
|
36
|
+
requested_paths: [],
|
32
37
|
requested_compilers: [],
|
33
38
|
excluded_compilers: [],
|
34
39
|
error_handler: $stderr.method(:puts).to_proc,
|
@@ -39,6 +44,7 @@ module Tapioca
|
|
39
44
|
T::Enumerable[T.class_of(Compiler)],
|
40
45
|
)
|
41
46
|
@requested_constants = requested_constants
|
47
|
+
@requested_paths = requested_paths
|
42
48
|
@error_handler = error_handler
|
43
49
|
@number_of_workers = number_of_workers
|
44
50
|
@errors = T.let([], T::Array[String])
|
@@ -50,11 +56,12 @@ module Tapioca
|
|
50
56
|
).returns(T::Array[T.type_parameter(:T)])
|
51
57
|
end
|
52
58
|
def run(&blk)
|
53
|
-
constants_to_process = gather_constants(requested_constants)
|
59
|
+
constants_to_process = gather_constants(requested_constants, requested_paths)
|
54
60
|
.select { |c| Module === c } # Filter value constants out
|
55
61
|
.sort_by! { |c| T.must(Runtime::Reflection.name_of(c)) }
|
56
62
|
|
57
|
-
if
|
63
|
+
# It's OK if there are no constants to process if we received a valid file/path.
|
64
|
+
if constants_to_process.empty? && requested_paths.select { |p| File.exist?(p) }.empty?
|
58
65
|
report_error(<<~ERROR)
|
59
66
|
No classes/modules can be matched for RBI generation.
|
60
67
|
Please check that the requested classes/modules include processable DSL methods.
|
@@ -115,12 +122,12 @@ module Tapioca
|
|
115
122
|
active_compilers
|
116
123
|
end
|
117
124
|
|
118
|
-
sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
|
119
|
-
def gather_constants(requested_constants)
|
125
|
+
sig { params(requested_constants: T::Array[Module], requested_paths: T::Array[Pathname]).returns(T::Set[Module]) }
|
126
|
+
def gather_constants(requested_constants, requested_paths)
|
120
127
|
constants = active_compilers.map(&:processable_constants).reduce(Set.new, :union)
|
121
128
|
constants = filter_anonymous_and_reloaded_constants(constants)
|
122
129
|
|
123
|
-
constants &= requested_constants unless requested_constants.empty?
|
130
|
+
constants &= requested_constants unless requested_constants.empty? && requested_paths.empty?
|
124
131
|
constants
|
125
132
|
end
|
126
133
|
|
@@ -12,7 +12,7 @@ module Tapioca
|
|
12
12
|
sig { override.params(event: ScopeNodeAdded).void }
|
13
13
|
def on_scope(event)
|
14
14
|
constant = event.constant
|
15
|
-
return unless T::Enum > event.constant
|
15
|
+
return unless T::Enum > event.constant # rubocop:disable Style/InvertibleUnlessCondition
|
16
16
|
|
17
17
|
enums = T.unsafe(constant).values.map do |enum_type|
|
18
18
|
enum_type.instance_variable_get(:@const_name).to_s
|
data/lib/tapioca/gem/pipeline.rb
CHANGED
@@ -106,6 +106,18 @@ module Tapioca
|
|
106
106
|
@payload_symbols.include?(symbol_name)
|
107
107
|
end
|
108
108
|
|
109
|
+
sig { params(name: T.any(String, Symbol)).returns(T::Boolean) }
|
110
|
+
def constant_in_gem?(name)
|
111
|
+
return true unless Object.respond_to?(:const_source_location)
|
112
|
+
|
113
|
+
source_location, _ = Object.const_source_location(name)
|
114
|
+
return true unless source_location
|
115
|
+
# If the source location of the constant is "(eval)", all bets are off.
|
116
|
+
return true if source_location == "(eval)"
|
117
|
+
|
118
|
+
gem.contains_path?(source_location)
|
119
|
+
end
|
120
|
+
|
109
121
|
sig { params(method: UnboundMethod).returns(T::Boolean) }
|
110
122
|
def method_in_gem?(method)
|
111
123
|
source_location = method.source_location&.first
|
@@ -216,6 +228,7 @@ module Tapioca
|
|
216
228
|
mark_seen(name)
|
217
229
|
|
218
230
|
return if symbol_in_payload?(name)
|
231
|
+
return unless constant_in_gem?(name)
|
219
232
|
|
220
233
|
target = name_of(constant)
|
221
234
|
# If target has no name, let's make it an anonymous class or module with `Class.new` or `Module.new`
|
@@ -237,6 +250,7 @@ module Tapioca
|
|
237
250
|
mark_seen(name)
|
238
251
|
|
239
252
|
return if symbol_in_payload?(name)
|
253
|
+
return unless constant_in_gem?(name)
|
240
254
|
|
241
255
|
klass = class_of(value)
|
242
256
|
|
@@ -58,36 +58,102 @@ module Tapioca
|
|
58
58
|
|
59
59
|
sig { void }
|
60
60
|
def load_rails_engines
|
61
|
-
|
62
|
-
errored_files = []
|
61
|
+
return if engines.empty?
|
63
62
|
|
63
|
+
with_rails_application do
|
64
|
+
run_initializers
|
65
|
+
|
66
|
+
if zeitwerk_mode?
|
67
|
+
load_engines_in_zeitwerk_mode
|
68
|
+
else
|
69
|
+
load_engines_in_classic_mode
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def run_initializers
|
75
|
+
engines.each do |engine|
|
76
|
+
engine.instance.initializers.tsort_each do |initializer|
|
77
|
+
initializer.run(Rails.application)
|
78
|
+
rescue ScriptError, StandardError
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
sig { void }
|
85
|
+
def load_engines_in_zeitwerk_mode
|
86
|
+
# Collect all the directories that are already managed by all existing Zeitwerk loaders.
|
87
|
+
managed_dirs = Zeitwerk::Registry.loaders.flat_map(&:dirs).to_set
|
88
|
+
# We use a fresh loader to load the engine directories, so that we don't interfere with
|
89
|
+
# any of the existing loaders.
|
90
|
+
autoloader = Zeitwerk::Loader.new
|
91
|
+
|
92
|
+
engines.each do |engine|
|
93
|
+
engine.config.eager_load_paths.each do |path|
|
94
|
+
# Zeitwerk only accepts existing directories in `push_dir`.
|
95
|
+
next unless File.directory?(path)
|
96
|
+
# We should not add directories that are already managed by a Zeitwerk loader.
|
97
|
+
next if managed_dirs.member?(path)
|
98
|
+
|
99
|
+
autoloader.push_dir(path)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
autoloader.setup
|
104
|
+
end
|
105
|
+
|
106
|
+
sig { void }
|
107
|
+
def load_engines_in_classic_mode
|
108
|
+
# This is code adapted from `Rails::Engine#eager_load!` in
|
109
|
+
# https://github.com/rails/rails/blob/d9e188dbab81b412f73dfb7763318d52f360af49/railties/lib/rails/engine.rb#L489-L495
|
110
|
+
#
|
111
|
+
# We can't use `Rails::Engine#eager_load!` directly because it will raise as soon as it encounters
|
112
|
+
# an error, which is not what we want. We want to try to load as much as we can.
|
113
|
+
engines.each do |engine|
|
64
114
|
engine.config.eager_load_paths.each do |load_path|
|
65
115
|
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
|
66
|
-
|
67
|
-
rescue LoadError, StandardError
|
68
|
-
errored_files << file
|
116
|
+
require_dependency file
|
69
117
|
end
|
70
|
-
|
71
|
-
|
72
|
-
# Try files that have errored one more time
|
73
|
-
# It might have been a load order problem
|
74
|
-
errored_files.each do |file|
|
75
|
-
require(file)
|
76
|
-
rescue LoadError, StandardError
|
118
|
+
rescue ScriptError, StandardError
|
77
119
|
nil
|
78
120
|
end
|
79
121
|
end
|
80
122
|
end
|
81
123
|
|
82
|
-
sig { returns(T::
|
83
|
-
def
|
84
|
-
|
124
|
+
sig { returns(T::Boolean) }
|
125
|
+
def zeitwerk_mode?
|
126
|
+
Rails.respond_to?(:autoloaders) &&
|
127
|
+
Rails.autoloaders.respond_to?(:zeitwerk_enabled?) &&
|
128
|
+
Rails.autoloaders.zeitwerk_enabled?
|
129
|
+
end
|
130
|
+
|
131
|
+
sig { params(blk: T.proc.void).void }
|
132
|
+
def with_rails_application(&blk)
|
133
|
+
# Store the current Rails.application object so that we can restore it
|
134
|
+
rails_application = T.unsafe(Rails.application)
|
135
|
+
|
136
|
+
# Create a new Rails::Application object, so that we can load the engines.
|
137
|
+
# Some engines and the `Rails.autoloaders` call might expect `Rails.application`
|
138
|
+
# to be set, so we need to create one here.
|
139
|
+
unless rails_application
|
140
|
+
Rails.application = Class.new(Rails::Application)
|
141
|
+
end
|
142
|
+
|
143
|
+
blk.call
|
144
|
+
ensure
|
145
|
+
Rails.app_class = Rails.application = rails_application
|
146
|
+
end
|
147
|
+
|
148
|
+
T::Sig::WithoutRuntime.sig { returns(T::Array[T.class_of(Rails::Engine)]) }
|
149
|
+
def engines
|
150
|
+
return [] unless defined?(Rails::Engine)
|
85
151
|
|
86
152
|
safe_require("active_support/core_ext/class/subclasses")
|
87
153
|
|
88
154
|
project_path = Bundler.default_gemfile.parent.expand_path
|
89
155
|
# We can use `Class#descendants` here, since we know Rails is loaded
|
90
|
-
|
156
|
+
Rails::Engine
|
91
157
|
.descendants
|
92
158
|
.reject(&:abstract_railtie?)
|
93
159
|
.reject { |engine| gem_in_app_dir?(project_path, engine.config.root.to_path) }
|
@@ -103,30 +169,25 @@ module Tapioca
|
|
103
169
|
sig { void }
|
104
170
|
def silence_deprecations
|
105
171
|
# Stop any ActiveSupport Deprecations from being reported
|
106
|
-
|
107
|
-
|
108
|
-
|
172
|
+
if defined?(ActiveSupport::Deprecation)
|
173
|
+
ActiveSupport::Deprecation.silenced = true
|
174
|
+
end
|
109
175
|
end
|
110
176
|
|
111
177
|
sig { void }
|
112
178
|
def eager_load_rails_app
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
Object.const_get("ActiveSupport").run_load_hooks(
|
118
|
-
:before_eager_load,
|
119
|
-
application,
|
120
|
-
)
|
179
|
+
application = Rails.application
|
180
|
+
|
181
|
+
if defined?(ActiveSupport)
|
182
|
+
ActiveSupport.run_load_hooks(:before_eager_load, application)
|
121
183
|
end
|
122
184
|
|
123
|
-
if
|
124
|
-
|
125
|
-
zeitwerk_loader.eager_load_all
|
185
|
+
if defined?(Zeitwerk::Loader)
|
186
|
+
Zeitwerk::Loader.eager_load_all
|
126
187
|
end
|
127
188
|
|
128
|
-
if
|
129
|
-
|
189
|
+
if Rails.respond_to?(:autoloaders)
|
190
|
+
Rails.autoloaders.each(&:eager_load)
|
130
191
|
end
|
131
192
|
|
132
193
|
if application.config.respond_to?(:eager_load_namespaces)
|
@@ -5,7 +5,7 @@ module RBI
|
|
5
5
|
class Tree
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
-
sig { params(constant: ::Module, block: T.nilable(T.proc.params(scope: Scope).void)).
|
8
|
+
sig { params(constant: ::Module, block: T.nilable(T.proc.params(scope: Scope).void)).returns(Scope) }
|
9
9
|
def create_path(constant, &block)
|
10
10
|
constant_name = Tapioca::Runtime::Reflection.name_of(constant)
|
11
11
|
raise "given constant does not have a name" unless constant_name
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Runtime
|
6
|
+
# This module should only be included when running Ruby version 3.2
|
7
|
+
# or newer. It relies on the Class#attached_object method, which was
|
8
|
+
# added in Ruby 3.2 and fetches the attached object of a singleton
|
9
|
+
# class without having to iterate through all of ObjectSpace.
|
10
|
+
module AttachedClassOf
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { params(singleton_class: Class).returns(T.nilable(Module)) }
|
14
|
+
def attached_class_of(singleton_class)
|
15
|
+
result = singleton_class.attached_object
|
16
|
+
Module === result ? result : nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Runtime
|
6
|
+
# This module should only be included when running versions of Ruby
|
7
|
+
# older than 3.2. Because the Class#attached_object method is not
|
8
|
+
# available, it implements finding the attached class of a singleton
|
9
|
+
# class by iterating through ObjectSpace.
|
10
|
+
module AttachedClassOf
|
11
|
+
extend T::Sig
|
12
|
+
extend T::Helpers
|
13
|
+
|
14
|
+
requires_ancestor { Tapioca::Runtime::Reflection }
|
15
|
+
|
16
|
+
sig { params(singleton_class: Class).returns(T.nilable(Module)) }
|
17
|
+
def attached_class_of(singleton_class)
|
18
|
+
# https://stackoverflow.com/a/36622320/98634
|
19
|
+
result = ObjectSpace.each_object(singleton_class).find do |klass|
|
20
|
+
singleton_class_of(T.cast(klass, Module)) == singleton_class
|
21
|
+
end
|
22
|
+
|
23
|
+
T.cast(result, T.nilable(Module))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,9 +1,20 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
# On Ruby 3.2 or newer, Class defines an attached_object method that returns the
|
5
|
+
# attached class of a singleton class without iterating ObjectSpace. On older
|
6
|
+
# versions of Ruby, we fall back to iterating ObjectSpace.
|
7
|
+
if Class.method_defined?(:attached_object)
|
8
|
+
require "tapioca/runtime/attached_class_of_32"
|
9
|
+
else
|
10
|
+
require "tapioca/runtime/attached_class_of_legacy"
|
11
|
+
end
|
12
|
+
|
4
13
|
module Tapioca
|
5
14
|
module Runtime
|
6
15
|
module Reflection
|
16
|
+
include AttachedClassOf
|
17
|
+
|
7
18
|
extend T::Sig
|
8
19
|
extend self
|
9
20
|
|
@@ -174,16 +185,6 @@ module Tapioca
|
|
174
185
|
resolved_loc.absolute_path || ""
|
175
186
|
end
|
176
187
|
|
177
|
-
sig { params(singleton_class: Module).returns(T.nilable(Module)) }
|
178
|
-
def attached_class_of(singleton_class)
|
179
|
-
# https://stackoverflow.com/a/36622320/98634
|
180
|
-
result = ObjectSpace.each_object(singleton_class).find do |klass|
|
181
|
-
singleton_class_of(T.cast(klass, Module)) == singleton_class
|
182
|
-
end
|
183
|
-
|
184
|
-
T.cast(result, Module)
|
185
|
-
end
|
186
|
-
|
187
188
|
sig { params(constant: Module).returns(T::Set[String]) }
|
188
189
|
def file_candidates_for(constant)
|
189
190
|
relevant_methods_for(constant).filter_map do |method|
|
@@ -41,6 +41,20 @@ module Tapioca
|
|
41
41
|
symbols_from_paths(gem.files)
|
42
42
|
end
|
43
43
|
|
44
|
+
sig { params(paths: T::Array[Pathname]).returns(T::Set[String]) }
|
45
|
+
def symbols_from_paths(paths)
|
46
|
+
output = Tempfile.create("sorbet") do |file|
|
47
|
+
file.write(Array(paths).join("\n"))
|
48
|
+
file.flush
|
49
|
+
|
50
|
+
symbol_table_json_from("@#{file.path.shellescape}")
|
51
|
+
end
|
52
|
+
|
53
|
+
return Set.new if output.empty?
|
54
|
+
|
55
|
+
SymbolTableParser.parse_json(output)
|
56
|
+
end
|
57
|
+
|
44
58
|
private
|
45
59
|
|
46
60
|
sig { returns(T::Array[T.class_of(Rails::Engine)]) }
|
@@ -59,20 +73,6 @@ module Tapioca
|
|
59
73
|
def symbol_table_json_from(input, table_type: "symbol-table-json")
|
60
74
|
sorbet("--no-config", "--quiet", "--print=#{table_type}", input).out
|
61
75
|
end
|
62
|
-
|
63
|
-
sig { params(paths: T::Array[Pathname]).returns(T::Set[String]) }
|
64
|
-
def symbols_from_paths(paths)
|
65
|
-
output = Tempfile.create("sorbet") do |file|
|
66
|
-
file.write(Array(paths).join("\n"))
|
67
|
-
file.flush
|
68
|
-
|
69
|
-
symbol_table_json_from("@#{file.path.shellescape}")
|
70
|
-
end
|
71
|
-
|
72
|
-
return Set.new if output.empty?
|
73
|
-
|
74
|
-
SymbolTableParser.parse_json(output)
|
75
|
-
end
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
data/lib/tapioca/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapioca
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ufuk Kayserilioglu
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2023-
|
14
|
+
date: 2023-02-21 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -176,6 +176,7 @@ files:
|
|
176
176
|
- lib/tapioca/dsl/compilers/active_record_fixtures.rb
|
177
177
|
- lib/tapioca/dsl/compilers/active_record_relations.rb
|
178
178
|
- lib/tapioca/dsl/compilers/active_record_scope.rb
|
179
|
+
- lib/tapioca/dsl/compilers/active_record_secure_token.rb
|
179
180
|
- lib/tapioca/dsl/compilers/active_record_typed_store.rb
|
180
181
|
- lib/tapioca/dsl/compilers/active_resource.rb
|
181
182
|
- lib/tapioca/dsl/compilers/active_storage.rb
|
@@ -186,6 +187,7 @@ files:
|
|
186
187
|
- lib/tapioca/dsl/compilers/graphql_input_object.rb
|
187
188
|
- lib/tapioca/dsl/compilers/graphql_mutation.rb
|
188
189
|
- lib/tapioca/dsl/compilers/identity_cache.rb
|
190
|
+
- lib/tapioca/dsl/compilers/kredis.rb
|
189
191
|
- lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb
|
190
192
|
- lib/tapioca/dsl/compilers/protobuf.rb
|
191
193
|
- lib/tapioca/dsl/compilers/rails_generators.rb
|
@@ -195,6 +197,7 @@ files:
|
|
195
197
|
- lib/tapioca/dsl/compilers/url_helpers.rb
|
196
198
|
- lib/tapioca/dsl/extensions/active_record.rb
|
197
199
|
- lib/tapioca/dsl/extensions/frozen_record.rb
|
200
|
+
- lib/tapioca/dsl/extensions/kredis.rb
|
198
201
|
- lib/tapioca/dsl/helpers/active_record_column_type_helper.rb
|
199
202
|
- lib/tapioca/dsl/helpers/active_record_constants_helper.rb
|
200
203
|
- lib/tapioca/dsl/helpers/graphql_type_helper.rb
|
@@ -239,6 +242,8 @@ files:
|
|
239
242
|
- lib/tapioca/rbi_ext/model.rb
|
240
243
|
- lib/tapioca/rbi_formatter.rb
|
241
244
|
- lib/tapioca/repo_index.rb
|
245
|
+
- lib/tapioca/runtime/attached_class_of_32.rb
|
246
|
+
- lib/tapioca/runtime/attached_class_of_legacy.rb
|
242
247
|
- lib/tapioca/runtime/dynamic_mixin_compiler.rb
|
243
248
|
- lib/tapioca/runtime/generic_type_registry.rb
|
244
249
|
- lib/tapioca/runtime/reflection.rb
|