tapioca 0.4.27 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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