tapioca 0.4.27 → 0.5.3

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +15 -15
  3. data/README.md +2 -2
  4. data/Rakefile +5 -7
  5. data/exe/tapioca +2 -2
  6. data/lib/tapioca/cli.rb +172 -2
  7. data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
  8. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
  9. data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
  10. data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
  11. data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
  12. data/lib/tapioca/compilers/dsl/active_model_secure_password.rb +101 -0
  13. data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
  14. data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
  15. data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
  16. data/lib/tapioca/compilers/dsl/active_record_fixtures.rb +86 -0
  17. data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
  18. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
  19. data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
  20. data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
  21. data/lib/tapioca/compilers/dsl/active_support_concern.rb +106 -0
  22. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
  23. data/lib/tapioca/compilers/dsl/base.rb +108 -82
  24. data/lib/tapioca/compilers/dsl/config.rb +111 -0
  25. data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
  26. data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
  27. data/lib/tapioca/compilers/dsl/mixed_in_class_attributes.rb +74 -0
  28. data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
  29. data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
  30. data/lib/tapioca/compilers/dsl/smart_properties.rb +21 -33
  31. data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
  32. data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
  33. data/lib/tapioca/compilers/dsl_compiler.rb +25 -40
  34. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
  35. data/lib/tapioca/compilers/requires_compiler.rb +2 -2
  36. data/lib/tapioca/compilers/sorbet.rb +25 -5
  37. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +122 -206
  38. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
  39. data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
  40. data/lib/tapioca/compilers/todos_compiler.rb +1 -1
  41. data/lib/tapioca/config.rb +3 -0
  42. data/lib/tapioca/config_builder.rb +5 -2
  43. data/lib/tapioca/constant_locator.rb +6 -8
  44. data/lib/tapioca/gemfile.rb +14 -11
  45. data/lib/tapioca/generators/base.rb +61 -0
  46. data/lib/tapioca/generators/dsl.rb +362 -0
  47. data/lib/tapioca/generators/gem.rb +345 -0
  48. data/lib/tapioca/generators/init.rb +79 -0
  49. data/lib/tapioca/generators/require.rb +52 -0
  50. data/lib/tapioca/generators/todo.rb +76 -0
  51. data/lib/tapioca/generators.rb +9 -0
  52. data/lib/tapioca/generic_type_registry.rb +25 -98
  53. data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
  54. data/lib/tapioca/internal.rb +2 -10
  55. data/lib/tapioca/loader.rb +11 -31
  56. data/lib/tapioca/rbi_ext/model.rb +166 -0
  57. data/lib/tapioca/reflection.rb +138 -0
  58. data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
  59. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
  60. data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
  61. data/lib/tapioca/version.rb +1 -1
  62. data/lib/tapioca.rb +3 -0
  63. metadata +45 -23
  64. data/lib/tapioca/cli/main.rb +0 -146
  65. data/lib/tapioca/core_ext/class.rb +0 -28
  66. data/lib/tapioca/core_ext/string.rb +0 -18
  67. data/lib/tapioca/generator.rb +0 -633
  68. data/lib/tapioca/rbi/model.rb +0 -405
  69. data/lib/tapioca/rbi/printer.rb +0 -410
  70. data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
  71. data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
  72. data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
  73. data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -86
  74. data/lib/tapioca/rbi/visitor.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91440af4ff5a8cd586284d4d5a2cc35600ec803b825bdcd57492a3eb94b65bf3
4
- data.tar.gz: d6db15fe94c5cd0e8100f8bf1fd8fb9a760123891cee3b079470011719dc55e0
3
+ metadata.gz: 8201f29f7abb78a5625f206893b18ff2a8f1ac9ffcae2784a90446764680bda3
4
+ data.tar.gz: 680b85b7a109985b60e4a727fd3295507daec445f26983703c6ba27a2882b24a
5
5
  SHA512:
6
- metadata.gz: 536bcacea8d4b5848dc8075fc94f50d34d80c23f00b9ea331862f911be6eb58c3a62b30d529663ddfd60441f632aab62e6250d8dc1c7a1a8f32dbfc0d510ae18
7
- data.tar.gz: 57942419606b3d054fb392b6c3c026bff5aa7f63cb3cfc58defb8dbc81e49e72902eb26a8ebb9713561f21d4822a644fd6b9a36c7f2c0a135461b3b4e1d9c267
6
+ metadata.gz: 0df81ab9e557367adc414569a5bc67e581b2c98a13aa728ba46bcc831dfe490d4c3b81b9fe828c677f9eae74521b94f13c2217014dd900ddbe43144085f1cb83
7
+ data.tar.gz: 72f0f551108b0d1e2d3b7ce5420ff638fc81bec650a1563b5f0223c04b4646ce625cf5203b047495c873b4cb454b7fd2d910cafffd1afc39b987bac081b7e958
data/Gemfile CHANGED
@@ -11,28 +11,28 @@ gem("pry-byebug")
11
11
  gem("rubocop-shopify", require: false)
12
12
  gem("rubocop-sorbet", ">= 0.4.1")
13
13
  gem("sorbet")
14
- gem("yard", "~> 0.9.25")
15
14
 
16
15
  group(:deployment, :development) do
17
16
  gem("rake")
18
17
  end
19
18
 
20
19
  group(:development, :test) do
21
- gem("smart_properties", ">= 1.15.0", require: false)
22
- gem("frozen_record", ">= 0.17", require: false)
23
- gem("sprockets", "~> 3.7", require: false)
24
- gem("rails", "~> 5.2", require: false)
25
- gem("state_machines", "~> 0.5.0", require: false)
26
- gem("activerecord-typedstore", "~> 1.3", require: false)
20
+ gem("smart_properties", require: false)
21
+ gem("frozen_record", require: false)
22
+ gem("sprockets", require: false)
23
+ gem("rails", require: false)
24
+ gem("state_machines", require: false)
25
+ gem("activerecord-typedstore", require: false)
27
26
  gem("sqlite3")
28
- gem("identity_cache", "~> 1.0", require: false)
27
+ gem("identity_cache", require: false)
29
28
  gem("cityhash", git: "https://github.com/csfrancis/cityhash.git",
30
29
  ref: "3cfc7d01f333c01811d5e834f1495eaa29f87c36", require: false)
31
- gem("activemodel-serializers-xml", "~> 1.0", require: false)
32
- gem("activeresource", "~> 5.1", require: false)
33
- gem("google-protobuf", "~> 3.12.0", require: false)
34
- # Fix version to 0.14.1 since it is the last version to support Ruby 2.4
35
- gem("shopify-money", "= 0.14.1", require: false)
36
- gem("sidekiq", "~> 5.0", require: false) # Version 6 dropped support for Ruby 2.4
37
- gem("nokogiri", "1.10.10", require: false) # Lock to last supported for Ruby 2.4
30
+ gem("activeresource", require: false)
31
+ gem("google-protobuf", require: false)
32
+ gem("shopify-money", require: false)
33
+ gem("sidekiq", require: false)
34
+ gem("nokogiri", require: false)
35
+ gem("config", require: false)
36
+ gem("aasm", require: false)
37
+ gem("bcrypt", require: false)
38
38
  end
data/README.md CHANGED
@@ -117,8 +117,8 @@ This will generate DSL RBIs for specified constants (or for all handled constant
117
117
 
118
118
  ## Contributing
119
119
 
120
- Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/tapioca. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://github.com/Shopify/tapioca/blob/master/CODE_OF_CONDUCT.md) code of conduct.
120
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/tapioca. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://github.com/Shopify/tapioca/blob/main/CODE_OF_CONDUCT.md) code of conduct.
121
121
 
122
122
  ## License
123
123
 
124
- The gem is available as open source under the terms of the [MIT License](https://github.com/Shopify/tapioca/blob/master/LICENSE.txt).
124
+ The gem is available as open source under the terms of the [MIT License](https://github.com/Shopify/tapioca/blob/main/LICENSE.txt).
data/Rakefile CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
- Dir['tasks/**/*.rake'].each { |t| load t }
5
+ Dir["tasks/**/*.rake"].each { |t| load t }
6
6
 
7
7
  Rake.application.options.trace = false
8
8
 
@@ -10,15 +10,13 @@ Rake::TestTask.new do |t|
10
10
  t.libs << "lib"
11
11
  t.libs << "spec"
12
12
  t.warning = false
13
- t.test_files = FileList['spec/**/*_spec.rb']
13
+ t.test_files = FileList["spec/**/*_spec.rb"]
14
14
  end
15
15
 
16
16
  task(:spec) do
17
- begin
18
- Rake::Task[:test].execute
19
- rescue RuntimeError
20
- exit(1)
21
- end
17
+ Rake::Task[:test].execute
18
+ rescue RuntimeError
19
+ exit(1)
22
20
  end
23
21
 
24
22
  task(default: :spec)
data/exe/tapioca CHANGED
@@ -1,7 +1,7 @@
1
1
  #! /usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'sorbet-runtime'
4
+ require "sorbet-runtime"
5
5
 
6
6
  begin
7
7
  T::Configuration.default_checked_level = :never
@@ -20,4 +20,4 @@ end
20
20
 
21
21
  require_relative "../lib/tapioca/internal"
22
22
 
23
- Tapioca::Cli::Main.start(ARGV)
23
+ Tapioca::Cli.start(ARGV)
data/lib/tapioca/cli.rb CHANGED
@@ -1,8 +1,178 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'thor'
4
+ require "thor"
5
5
 
6
6
  module Tapioca
7
- module Cli; end
7
+ class Cli < Thor
8
+ class_option :outdir,
9
+ aliases: ["--out", "-o"],
10
+ banner: "directory",
11
+ desc: "The output directory for generated RBI files"
12
+ class_option :generate_command,
13
+ aliases: ["--cmd", "-c"],
14
+ banner: "command",
15
+ desc: "The command to run to regenerate RBI files"
16
+ class_option :file_header,
17
+ type: :boolean,
18
+ default: true,
19
+ desc: "Add a \"This file is generated\" header on top of each generated RBI file"
20
+ class_option :verbose,
21
+ aliases: ["-V"],
22
+ type: :boolean,
23
+ default: false,
24
+ desc: "Verbose output for debugging purposes"
25
+
26
+ map T.unsafe(["--version", "-v"] => :__print_version)
27
+
28
+ desc "init", "initializes folder structure"
29
+ def init
30
+ generator = Generators::Init.new(
31
+ sorbet_config: Config::SORBET_CONFIG,
32
+ default_postrequire: Config::DEFAULT_POSTREQUIRE,
33
+ default_command: Config::DEFAULT_COMMAND
34
+ )
35
+ generator.generate
36
+ end
37
+
38
+ desc "require", "generate the list of files to be required by tapioca"
39
+ def require
40
+ generator = Generators::Require.new(
41
+ requires_path: ConfigBuilder.from_options(:require, options).postrequire,
42
+ sorbet_config_path: Config::SORBET_CONFIG,
43
+ default_command: Config::DEFAULT_COMMAND
44
+ )
45
+ Tapioca.silence_warnings do
46
+ generator.generate
47
+ end
48
+ end
49
+
50
+ desc "todo", "generate the list of unresolved constants"
51
+ def todo
52
+ current_command = T.must(current_command_chain.first)
53
+ config = ConfigBuilder.from_options(current_command, options)
54
+ generator = Generators::Todo.new(
55
+ todos_path: config.todos_path,
56
+ file_header: config.file_header,
57
+ default_command: Config::DEFAULT_COMMAND
58
+ )
59
+ Tapioca.silence_warnings do
60
+ generator.generate
61
+ end
62
+ end
63
+
64
+ desc "dsl [constant...]", "generate RBIs for dynamic methods"
65
+ option :generators,
66
+ type: :array,
67
+ aliases: ["--gen", "-g"],
68
+ banner: "generator [generator ...]",
69
+ desc: "Only run supplied DSL generators"
70
+ option :exclude_generators,
71
+ type: :array,
72
+ banner: "generator [generator ...]",
73
+ desc: "Exclude supplied DSL generators"
74
+ option :verify,
75
+ type: :boolean,
76
+ default: false,
77
+ desc: "Verifies RBIs are up-to-date"
78
+ option :quiet,
79
+ aliases: ["-q"],
80
+ type: :boolean,
81
+ desc: "Supresses file creation output"
82
+ def dsl(*constants)
83
+ current_command = T.must(current_command_chain.first)
84
+ config = ConfigBuilder.from_options(current_command, options)
85
+ generator = Generators::Dsl.new(
86
+ requested_constants: constants,
87
+ outpath: config.outpath,
88
+ generators: config.generators,
89
+ exclude_generators: config.exclude_generators,
90
+ file_header: config.file_header,
91
+ compiler_path: Tapioca::Compilers::Dsl::COMPILERS_PATH,
92
+ tapioca_path: Config::TAPIOCA_PATH,
93
+ default_command: Config::DEFAULT_COMMAND,
94
+ should_verify: options[:verify],
95
+ quiet: options[:quiet],
96
+ verbose: options[:verbose]
97
+ )
98
+ Tapioca.silence_warnings do
99
+ generator.generate
100
+ end
101
+ end
102
+
103
+ desc "gem [gem...]", "generate RBIs from gems"
104
+ option :all,
105
+ type: :boolean,
106
+ default: false,
107
+ desc: "Regenerate RBI files for all gems"
108
+ option :prerequire,
109
+ aliases: ["--pre", "-b"],
110
+ banner: "file",
111
+ desc: "A file to be required before Bundler.require is called"
112
+ option :postrequire,
113
+ aliases: ["--post", "-a"],
114
+ banner: "file",
115
+ desc: "A file to be required after Bundler.require is called"
116
+ option :exclude,
117
+ aliases: ["-x"],
118
+ type: :array,
119
+ banner: "gem [gem ...]",
120
+ desc: "Excludes the given gem(s) from RBI generation"
121
+ option :typed_overrides,
122
+ aliases: ["--typed", "-t"],
123
+ type: :hash,
124
+ banner: "gem:level [gem:level ...]",
125
+ desc: "Overrides for typed sigils for generated gem RBIs"
126
+ option :verify,
127
+ type: :boolean,
128
+ default: false,
129
+ desc: "Verifies RBIs are up-to-date"
130
+ option :doc,
131
+ type: :boolean,
132
+ default: false,
133
+ desc: "Include YARD documentation from sources when generating RBIs. Warning: this might be slow"
134
+ def gem(*gems)
135
+ Tapioca.silence_warnings do
136
+ all = options[:all]
137
+ verify = options[:verify]
138
+ current_command = T.must(current_command_chain.first)
139
+ config = ConfigBuilder.from_options(current_command, options)
140
+ generator = Generators::Gem.new(
141
+ gem_names: all ? [] : gems,
142
+ gem_excludes: config.exclude,
143
+ prerequire: config.prerequire,
144
+ postrequire: config.postrequire,
145
+ typed_overrides: config.typed_overrides,
146
+ default_command: Config::DEFAULT_COMMAND,
147
+ outpath: config.outpath,
148
+ file_header: config.file_header,
149
+ doc: config.doc
150
+ )
151
+
152
+ raise MalformattedArgumentError, "Options '--all' and '--verify' are mutually exclusive" if all && verify
153
+
154
+ unless gems.empty?
155
+ raise MalformattedArgumentError, "Option '--all' must be provided without any other arguments" if all
156
+ raise MalformattedArgumentError, "Option '--verify' must be provided without any other arguments" if verify
157
+ end
158
+
159
+ if gems.empty? && !all
160
+ generator.sync(should_verify: verify)
161
+ else
162
+ generator.generate
163
+ end
164
+ end
165
+ end
166
+
167
+ desc "--version, -v", "show version"
168
+ def __print_version
169
+ puts "Tapioca v#{Tapioca::VERSION}"
170
+ end
171
+
172
+ no_commands do
173
+ def self.exit_on_failure?
174
+ true
175
+ end
176
+ end
177
+ end
8
178
  end
@@ -0,0 +1,122 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "active_record"
6
+ require "aasm"
7
+ rescue LoadError
8
+ return
9
+ end
10
+
11
+ module Tapioca
12
+ module Compilers
13
+ module Dsl
14
+ # `Tapioca::Compilers::Dsl::AASM` generate types for AASM state machines.
15
+ # This gem dynamically defines constants and methods at runtime. For
16
+ # example, given a class:
17
+ #
18
+ # class MyClass
19
+ # include AASM
20
+ #
21
+ # aasm do
22
+ # state :sleeping, initial: true
23
+ # state :running, :cleaning
24
+ #
25
+ # event :run do
26
+ # transitions from: :sleeping, to: :running
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # This will result in the following constants being defined:
32
+ #
33
+ # STATE_SLEEPING, STATE_RUNNING, STATE_CLEANING
34
+ #
35
+ # and the following methods being defined:
36
+ #
37
+ # sleeping?, running?, cleaning?
38
+ # run, run!, run_without_validation!, may_run?
39
+ #
40
+ class AASM < Tapioca::Compilers::Dsl::Base
41
+ extend T::Sig
42
+
43
+ # Taken directly from the AASM::Core::Event class, here:
44
+ # https://github.com/aasm/aasm/blob/0e03746/lib/aasm/core/event.rb#L21-L29
45
+ EVENT_CALLBACKS =
46
+ T.let(
47
+ ["after", "after_commit", "after_transaction", "before", "before_transaction", "ensure", "error",
48
+ "before_success", "success"].freeze,
49
+ T::Array[String]
50
+ )
51
+
52
+ sig { override.params(root: RBI::Tree, constant: T.all(::AASM::ClassMethods, Class)).void }
53
+ def decorate(root, constant)
54
+ aasm = constant.aasm
55
+ return if !aasm || aasm.states.empty?
56
+
57
+ root.create_path(constant) do |model|
58
+ # Create all of the constants and methods for each state
59
+ aasm.states.each do |state|
60
+ model.create_constant("STATE_#{state.name.upcase}", value: "T.let(T.unsafe(nil), Symbol)")
61
+ model.create_method("#{state.name}?", return_type: "T::Boolean")
62
+ end
63
+
64
+ # Create all of the methods for each event
65
+ parameters = [create_rest_param("opts", type: "T.untyped")]
66
+ aasm.events.each do |event|
67
+ model.create_method(event.name.to_s, parameters: parameters)
68
+ model.create_method("#{event.name}!", parameters: parameters)
69
+ model.create_method("#{event.name}_without_validation!", parameters: parameters)
70
+ model.create_method("may_#{event.name}?", return_type: "T::Boolean")
71
+ end
72
+
73
+ # Create the overall state machine method, which will return an
74
+ # instance of the PrivateAASMMachine class.
75
+ model.create_method(
76
+ "aasm",
77
+ parameters: [
78
+ create_rest_param("args", type: "T.untyped"),
79
+ create_block_param("block", type: "T.nilable(T.proc.bind(PrivateAASMMachine).void)"),
80
+ ],
81
+ return_type: "PrivateAASMMachine",
82
+ class_method: true
83
+ )
84
+
85
+ # Create a private machine class that we can pass around for the
86
+ # purpose of binding various procs passed to methods without having
87
+ # to explicitly bind self in each one.
88
+ model.create_class("PrivateAASMMachine", superclass_name: "AASM::Base") do |machine|
89
+ machine.create_method(
90
+ "event",
91
+ parameters: [
92
+ create_param("name", type: "T.untyped"),
93
+ create_opt_param("options", default: "nil", type: "T.untyped"),
94
+ create_block_param("block", type: "T.proc.bind(PrivateAASMEvent).void"),
95
+ ]
96
+ )
97
+
98
+ # Create a private event class that we can pass around for the
99
+ # purpose of binding all of the callbacks without having to
100
+ # explicitly bind self in each one.
101
+ machine.create_class("PrivateAASMEvent", superclass_name: "AASM::Core::Event") do |event|
102
+ EVENT_CALLBACKS.each do |method|
103
+ event.create_method(
104
+ method,
105
+ parameters: [
106
+ create_block_param("block", type: "T.proc.bind(#{constant.name}).void"),
107
+ ]
108
+ )
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ sig { override.returns(T::Enumerable[Module]) }
116
+ def gather_constants
117
+ T.cast(ObjectSpace.each_object(::AASM::ClassMethods), T::Enumerable[Module])
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
5
-
6
4
  begin
7
5
  require "action_controller"
8
6
  rescue LoadError
@@ -72,38 +70,54 @@ module Tapioca
72
70
 
73
71
  sig do
74
72
  override
75
- .params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(::ActionController::Base))
73
+ .params(root: RBI::Tree, constant: T.class_of(::ActionController::Base))
76
74
  .void
77
75
  end
78
76
  def decorate(root, constant)
77
+ helpers_module = constant._helpers
78
+ proxied_helper_methods = constant._helper_methods.map(&:to_s).map(&:to_sym)
79
+
79
80
  helper_proxy_name = "HelperProxy"
80
81
  helper_methods_name = "HelperMethods"
81
- proxied_helper_methods = constant._helper_methods.map(&:to_s).map(&:to_sym)
82
82
 
83
83
  # Define the helpers method
84
- root.path(constant) do |controller|
85
- create_method(controller, 'helpers', return_type: helper_proxy_name)
84
+ root.create_path(constant) do |controller|
85
+ controller.create_method("helpers", return_type: helper_proxy_name)
86
86
 
87
87
  # Create helper method module
88
88
  controller.create_module(helper_methods_name) do |helper_methods|
89
- helpers_module = constant._helpers
89
+ # If the controller has no helper defined, then it just inherits
90
+ # the Action Controlller base helper methods module, so we should
91
+ # just add that as an include and stop doing more processing.
92
+ if helpers_module.name == "ActionController::Base::HelperMethods"
93
+ next helper_methods.create_include(T.must(qualified_name_of(helpers_module)))
94
+ end
90
95
 
96
+ # Find all the included helper modules and generate an include
97
+ # for each of those helper modules
91
98
  gather_includes(helpers_module).each do |ancestor|
92
99
  helper_methods.create_include(ancestor)
93
100
  end
94
101
 
102
+ # Generate a method definition in the helper module for each
103
+ # helper method defined via the `helper_method` call in the controller.
95
104
  helpers_module.instance_methods(false).each do |method_name|
96
105
  method = if proxied_helper_methods.include?(method_name)
97
- constant.instance_method(method_name)
106
+ helper_method_proxy_target(constant, method_name)
98
107
  else
99
108
  helpers_module.instance_method(method_name)
100
109
  end
101
- create_method_from_def(helper_methods, method)
110
+
111
+ if method
112
+ create_method_from_def(helper_methods, method)
113
+ else
114
+ create_unknown_proxy_method(helper_methods, method_name)
115
+ end
102
116
  end
103
117
  end
104
118
 
105
119
  # Create helper proxy class
106
- controller.create_class(helper_proxy_name, superclass: "::ActionView::Base") do |proxy|
120
+ controller.create_class(helper_proxy_name, superclass_name: "::ActionView::Base") do |proxy|
107
121
  proxy.create_include(helper_methods_name)
108
122
  end
109
123
  end
@@ -111,16 +125,42 @@ module Tapioca
111
125
 
112
126
  sig { override.returns(T::Enumerable[Module]) }
113
127
  def gather_constants
114
- ::ActionController::Base.descendants.reject(&:abstract?).select(&:name)
128
+ descendants_of(::ActionController::Base).reject(&:abstract?).select(&:name)
115
129
  end
116
130
 
117
131
  private
118
132
 
133
+ sig do
134
+ params(
135
+ constant: T.class_of(::ActionController::Base),
136
+ method_name: Symbol
137
+ ).returns(T.nilable(UnboundMethod))
138
+ end
139
+ def helper_method_proxy_target(constant, method_name)
140
+ # Lookup the proxy target method only if it is defined as a public/protected or private method.
141
+ if constant.method_defined?(method_name) || constant.private_method_defined?(method_name)
142
+ constant.instance_method(method_name)
143
+ end
144
+ end
145
+
146
+ sig { params(helper_methods: RBI::Scope, method_name: Symbol).void }
147
+ def create_unknown_proxy_method(helper_methods, method_name)
148
+ helper_methods.create_method(
149
+ method_name.to_s,
150
+ parameters: [
151
+ create_rest_param("args", type: "T.untyped"),
152
+ create_kw_rest_param("kwargs", type: "T.untyped"),
153
+ create_block_param("blk", type: "T.untyped"),
154
+ ],
155
+ return_type: "T.untyped"
156
+ )
157
+ end
158
+
119
159
  sig { params(mod: Module).returns(T::Array[String]) }
120
160
  def gather_includes(mod)
121
161
  mod.ancestors
122
162
  .reject { |ancestor| ancestor.is_a?(Class) || ancestor == mod || ancestor.name.nil? }
123
- .map { |ancestor| "::#{ancestor.name}" }
163
+ .map { |ancestor| T.must(qualified_name_of(ancestor)) }
124
164
  .reverse
125
165
  end
126
166
  end
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
5
-
6
4
  begin
7
5
  require "action_mailer"
8
6
  rescue LoadError
@@ -38,17 +36,16 @@ module Tapioca
38
36
  class ActionMailer < Base
39
37
  extend T::Sig
40
38
 
41
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(::ActionMailer::Base)).void }
39
+ sig { override.params(root: RBI::Tree, constant: T.class_of(::ActionMailer::Base)).void }
42
40
  def decorate(root, constant)
43
- root.path(constant) do |mailer|
41
+ root.create_path(constant) do |mailer|
44
42
  constant.action_methods.to_a.each do |mailer_method|
45
43
  method_def = constant.instance_method(mailer_method)
46
- parameters = compile_method_parameters_to_parlour(method_def)
47
- create_method(
48
- mailer,
44
+ parameters = compile_method_parameters_to_rbi(method_def)
45
+ mailer.create_method(
49
46
  mailer_method,
50
47
  parameters: parameters,
51
- return_type: '::ActionMailer::MessageDelivery',
48
+ return_type: "::ActionMailer::MessageDelivery",
52
49
  class_method: true
53
50
  )
54
51
  end
@@ -57,7 +54,7 @@ module Tapioca
57
54
 
58
55
  sig { override.returns(T::Enumerable[Module]) }
59
56
  def gather_constants
60
- ::ActionMailer::Base.descendants.reject(&:abstract?)
57
+ descendants_of(::ActionMailer::Base).reject(&:abstract?)
61
58
  end
62
59
  end
63
60
  end
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
5
-
6
4
  begin
7
5
  require "active_job"
8
6
  rescue LoadError
@@ -42,25 +40,23 @@ module Tapioca
42
40
  class ActiveJob < Base
43
41
  extend T::Sig
44
42
 
45
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(::ActiveJob::Base)).void }
43
+ sig { override.params(root: RBI::Tree, constant: T.class_of(::ActiveJob::Base)).void }
46
44
  def decorate(root, constant)
47
- root.path(constant) do |job|
48
- next unless constant.instance_methods(false).include?(:perform)
45
+ return unless constant.instance_methods(false).include?(:perform)
49
46
 
47
+ root.create_path(constant) do |job|
50
48
  method = constant.instance_method(:perform)
51
- parameters = compile_method_parameters_to_parlour(method)
52
- return_type = compile_method_return_type_to_parlour(method)
49
+ parameters = compile_method_parameters_to_rbi(method)
50
+ return_type = compile_method_return_type_to_rbi(method)
53
51
 
54
- create_method(
55
- job,
52
+ job.create_method(
56
53
  "perform_later",
57
54
  parameters: parameters,
58
55
  return_type: "T.any(#{constant.name}, FalseClass)",
59
56
  class_method: true
60
57
  )
61
58
 
62
- create_method(
63
- job,
59
+ job.create_method(
64
60
  "perform_now",
65
61
  parameters: parameters,
66
62
  return_type: return_type,
@@ -71,7 +67,7 @@ module Tapioca
71
67
 
72
68
  sig { override.returns(T::Enumerable[Module]) }
73
69
  def gather_constants
74
- ::ActiveJob::Base.descendants
70
+ descendants_of(::ActiveJob::Base)
75
71
  end
76
72
  end
77
73
  end