tapioca 0.10.4 → 0.11.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tapioca/cli.rb +14 -5
  3. data/lib/tapioca/commands/annotations.rb +2 -0
  4. data/lib/tapioca/commands/configure.rb +1 -0
  5. data/lib/tapioca/commands/dsl.rb +17 -3
  6. data/lib/tapioca/commands/gem.rb +4 -2
  7. data/lib/tapioca/dsl/compilers/aasm.rb +78 -17
  8. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +1 -1
  9. data/lib/tapioca/dsl/compilers/active_record_columns.rb +3 -3
  10. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +8 -5
  11. data/lib/tapioca/dsl/compilers/active_record_relations.rb +140 -83
  12. data/lib/tapioca/dsl/compilers/active_record_scope.rb +1 -1
  13. data/lib/tapioca/dsl/compilers/active_record_secure_token.rb +74 -0
  14. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +14 -11
  15. data/lib/tapioca/dsl/compilers/active_resource.rb +22 -15
  16. data/lib/tapioca/dsl/compilers/active_storage.rb +4 -2
  17. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +21 -1
  18. data/lib/tapioca/dsl/compilers/kredis.rb +130 -0
  19. data/lib/tapioca/dsl/compilers/smart_properties.rb +7 -4
  20. data/lib/tapioca/dsl/compilers/url_helpers.rb +7 -4
  21. data/lib/tapioca/dsl/extensions/active_record.rb +9 -0
  22. data/lib/tapioca/dsl/extensions/kredis.rb +114 -0
  23. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +37 -27
  24. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +1 -0
  25. data/lib/tapioca/dsl/pipeline.rb +12 -5
  26. data/lib/tapioca/gem/listeners/sorbet_enums.rb +1 -1
  27. data/lib/tapioca/gem/listeners/yard_doc.rb +13 -10
  28. data/lib/tapioca/gem/pipeline.rb +14 -0
  29. data/lib/tapioca/gemfile.rb +6 -2
  30. data/lib/tapioca/helpers/rbi_files_helper.rb +12 -6
  31. data/lib/tapioca/helpers/sorbet_helper.rb +7 -4
  32. data/lib/tapioca/helpers/source_uri.rb +10 -7
  33. data/lib/tapioca/loaders/gem.rb +4 -2
  34. data/lib/tapioca/loaders/loader.rb +99 -35
  35. data/lib/tapioca/rbi_ext/model.rb +8 -3
  36. data/lib/tapioca/rbi_formatter.rb +11 -8
  37. data/lib/tapioca/runtime/attached_class_of_32.rb +20 -0
  38. data/lib/tapioca/runtime/attached_class_of_legacy.rb +27 -0
  39. data/lib/tapioca/runtime/reflection.rb +11 -10
  40. data/lib/tapioca/runtime/trackers.rb +17 -0
  41. data/lib/tapioca/static/symbol_loader.rb +14 -14
  42. data/lib/tapioca/version.rb +1 -1
  43. data/lib/tapioca.rb +8 -5
  44. 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("Tapioca attempted to load the Rails application after encountering a `config/application.rb` file, " \
51
- "but it failed. If your application uses Rails please ensure it can be loaded correctly before generating " \
52
- "RBIs.\n#{e}", :yellow)
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
- rails_engines.each do |engine|
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
- require(file)
64
- rescue LoadError, StandardError
65
- errored_files << file
116
+ require_dependency file
66
117
  end
67
- end
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::Array[T.untyped]) }
80
- def rails_engines
81
- return [] unless Object.const_defined?("Rails::Engine")
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
- Object.const_get("Rails::Engine")
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
- Object.const_get("ActiveSupport::Deprecation").silenced = true
104
- rescue NameError
105
- nil
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
- rails = Object.const_get("Rails")
111
- application = rails.application
112
-
113
- if Object.const_defined?("ActiveSupport")
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 Object.const_defined?("Zeitwerk::Loader")
121
- zeitwerk_loader = Object.const_get("Zeitwerk::Loader")
122
- zeitwerk_loader.eager_load_all
185
+ if defined?(Zeitwerk::Loader)
186
+ Zeitwerk::Loader.eager_load_all
123
187
  end
124
188
 
125
- if rails.respond_to?(:autoloaders) && rails.autoloaders.zeitwerk_enabled?
126
- rails.autoloaders.each(&:eager_load)
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)).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(name, sigs: [sig], is_singleton: class_method, visibility: visibility,
95
- comments: comments)
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(RBIFormatter.new(
30
- add_sig_templates: false,
31
- group_nodes: true,
32
- max_line_length: nil,
33
- nest_singleton_methods: true,
34
- nest_non_public_methods: true,
35
- sort_nodes: true,
36
- ), RBIFormatter)
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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.10.4"
5
+ VERSION = "0.11.0"
6
6
  end
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
- # ActiveSupport overrides some core methods with different signatures
48
- # so we generate a typed: false RBI for it to suppress errors
49
- "activesupport" => "false",
50
- }.freeze, T::Hash[String, String])
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.10.4
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: 2022-12-19 00:00:00.000000000 Z
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