tapioca 0.10.4 → 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 +14 -5
- 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/commands/gem.rb +4 -2
- data/lib/tapioca/dsl/compilers/aasm.rb +78 -17
- 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_fixtures.rb +8 -5
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +140 -83
- 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/active_record_typed_store.rb +14 -11
- data/lib/tapioca/dsl/compilers/active_resource.rb +22 -15
- data/lib/tapioca/dsl/compilers/active_storage.rb +4 -2
- data/lib/tapioca/dsl/compilers/graphql_input_object.rb +21 -1
- data/lib/tapioca/dsl/compilers/kredis.rb +130 -0
- data/lib/tapioca/dsl/compilers/smart_properties.rb +7 -4
- data/lib/tapioca/dsl/compilers/url_helpers.rb +7 -4
- 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_column_type_helper.rb +37 -27
- 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/listeners/yard_doc.rb +13 -10
- data/lib/tapioca/gem/pipeline.rb +14 -0
- data/lib/tapioca/gemfile.rb +6 -2
- data/lib/tapioca/helpers/rbi_files_helper.rb +12 -6
- data/lib/tapioca/helpers/sorbet_helper.rb +7 -4
- data/lib/tapioca/helpers/source_uri.rb +10 -7
- data/lib/tapioca/loaders/gem.rb +4 -2
- data/lib/tapioca/loaders/loader.rb +99 -35
- data/lib/tapioca/rbi_ext/model.rb +8 -3
- data/lib/tapioca/rbi_formatter.rb +11 -8
- 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/runtime/trackers.rb +17 -0
- data/lib/tapioca/static/symbol_loader.rb +14 -14
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +8 -5
- metadata +7 -2
@@ -47,44 +47,113 @@ module Tapioca
|
|
47
47
|
|
48
48
|
eager_load_rails_app if eager_load
|
49
49
|
rescue LoadError, StandardError => e
|
50
|
-
say(
|
51
|
-
"
|
52
|
-
|
50
|
+
say(
|
51
|
+
"Tapioca attempted to load the Rails application after encountering a `config/application.rb` file, " \
|
52
|
+
"but it failed. If your application uses Rails please ensure it can be loaded correctly before " \
|
53
|
+
"generating RBIs.\n#{e}",
|
54
|
+
:yellow,
|
55
|
+
)
|
53
56
|
say("Continuing RBI generation without loading the Rails application.")
|
54
57
|
end
|
55
58
|
|
56
59
|
sig { void }
|
57
60
|
def load_rails_engines
|
58
|
-
|
59
|
-
errored_files = []
|
61
|
+
return if engines.empty?
|
60
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|
|
61
114
|
engine.config.eager_load_paths.each do |load_path|
|
62
115
|
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
|
63
|
-
|
64
|
-
rescue LoadError, StandardError
|
65
|
-
errored_files << file
|
116
|
+
require_dependency file
|
66
117
|
end
|
67
|
-
|
68
|
-
|
69
|
-
# Try files that have errored one more time
|
70
|
-
# It might have been a load order problem
|
71
|
-
errored_files.each do |file|
|
72
|
-
require(file)
|
73
|
-
rescue LoadError, StandardError
|
118
|
+
rescue ScriptError, StandardError
|
74
119
|
nil
|
75
120
|
end
|
76
121
|
end
|
77
122
|
end
|
78
123
|
|
79
|
-
sig { returns(T::
|
80
|
-
def
|
81
|
-
|
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)
|
82
151
|
|
83
152
|
safe_require("active_support/core_ext/class/subclasses")
|
84
153
|
|
85
154
|
project_path = Bundler.default_gemfile.parent.expand_path
|
86
155
|
# We can use `Class#descendants` here, since we know Rails is loaded
|
87
|
-
|
156
|
+
Rails::Engine
|
88
157
|
.descendants
|
89
158
|
.reject(&:abstract_railtie?)
|
90
159
|
.reject { |engine| gem_in_app_dir?(project_path, engine.config.root.to_path) }
|
@@ -100,30 +169,25 @@ module Tapioca
|
|
100
169
|
sig { void }
|
101
170
|
def silence_deprecations
|
102
171
|
# Stop any ActiveSupport Deprecations from being reported
|
103
|
-
|
104
|
-
|
105
|
-
|
172
|
+
if defined?(ActiveSupport::Deprecation)
|
173
|
+
ActiveSupport::Deprecation.silenced = true
|
174
|
+
end
|
106
175
|
end
|
107
176
|
|
108
177
|
sig { void }
|
109
178
|
def eager_load_rails_app
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
Object.const_get("ActiveSupport").run_load_hooks(
|
115
|
-
:before_eager_load,
|
116
|
-
application,
|
117
|
-
)
|
179
|
+
application = Rails.application
|
180
|
+
|
181
|
+
if defined?(ActiveSupport)
|
182
|
+
ActiveSupport.run_load_hooks(:before_eager_load, application)
|
118
183
|
end
|
119
184
|
|
120
|
-
if
|
121
|
-
|
122
|
-
zeitwerk_loader.eager_load_all
|
185
|
+
if defined?(Zeitwerk::Loader)
|
186
|
+
Zeitwerk::Loader.eager_load_all
|
123
187
|
end
|
124
188
|
|
125
|
-
if
|
126
|
-
|
189
|
+
if Rails.respond_to?(:autoloaders)
|
190
|
+
Rails.autoloaders.each(&:eager_load)
|
127
191
|
end
|
128
192
|
|
129
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
|
@@ -91,8 +91,13 @@ module RBI
|
|
91
91
|
return unless Tapioca::RBIHelper.valid_method_name?(name)
|
92
92
|
|
93
93
|
sig = RBI::Sig.new(return_type: return_type)
|
94
|
-
method = RBI::Method.new(
|
95
|
-
|
94
|
+
method = RBI::Method.new(
|
95
|
+
name,
|
96
|
+
sigs: [sig],
|
97
|
+
is_singleton: class_method,
|
98
|
+
visibility: visibility,
|
99
|
+
comments: comments,
|
100
|
+
)
|
96
101
|
parameters.each do |param|
|
97
102
|
method << param.param
|
98
103
|
sig << RBI::SigParam.new(param.param.name, param.type)
|
@@ -26,12 +26,15 @@ module Tapioca
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
DEFAULT_RBI_FORMATTER = T.let(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
29
|
+
DEFAULT_RBI_FORMATTER = T.let(
|
30
|
+
RBIFormatter.new(
|
31
|
+
add_sig_templates: false,
|
32
|
+
group_nodes: true,
|
33
|
+
max_line_length: nil,
|
34
|
+
nest_singleton_methods: true,
|
35
|
+
nest_non_public_methods: true,
|
36
|
+
sort_nodes: true,
|
37
|
+
),
|
38
|
+
RBIFormatter,
|
39
|
+
)
|
37
40
|
end
|
@@ -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|
|
@@ -13,6 +13,23 @@ module Tapioca
|
|
13
13
|
class << self
|
14
14
|
extend T::Sig
|
15
15
|
|
16
|
+
sig do
|
17
|
+
type_parameters(:Return)
|
18
|
+
.params(blk: T.proc.returns(T.type_parameter(:Return)))
|
19
|
+
.returns(T.type_parameter(:Return))
|
20
|
+
end
|
21
|
+
def with_trackers_enabled(&blk)
|
22
|
+
# Currently this is a dirty hack to ensure disabling trackers
|
23
|
+
# doesn't work while in the block passed to this method.
|
24
|
+
disable_all_method = method(:disable_all!)
|
25
|
+
define_singleton_method(:disable_all!) {}
|
26
|
+
blk.call
|
27
|
+
ensure
|
28
|
+
if disable_all_method
|
29
|
+
define_singleton_method(:disable_all!, disable_all_method)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
16
33
|
sig { void }
|
17
34
|
def disable_all!
|
18
35
|
@trackers.each(&:disable!)
|
@@ -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
data/lib/tapioca.rb
CHANGED
@@ -43,11 +43,14 @@ module Tapioca
|
|
43
43
|
DEFAULT_TODO_FILE = T.let("#{DEFAULT_RBI_DIR}/todo.rbi", String)
|
44
44
|
DEFAULT_ANNOTATIONS_DIR = T.let("#{DEFAULT_RBI_DIR}/annotations", String)
|
45
45
|
|
46
|
-
DEFAULT_OVERRIDES = T.let(
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
DEFAULT_OVERRIDES = T.let(
|
47
|
+
{
|
48
|
+
# ActiveSupport overrides some core methods with different signatures
|
49
|
+
# so we generate a typed: false RBI for it to suppress errors
|
50
|
+
"activesupport" => "false",
|
51
|
+
}.freeze,
|
52
|
+
T::Hash[String, String],
|
53
|
+
)
|
51
54
|
|
52
55
|
DEFAULT_RBI_MAX_LINE_LENGTH = 120
|
53
56
|
DEFAULT_ENVIRONMENT = "development"
|
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:
|
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
|