tapioca 0.5.0 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/lib/tapioca/cli.rb +55 -139
- data/lib/tapioca/compilers/dsl/active_model_secure_password.rb +101 -0
- data/lib/tapioca/compilers/dsl/active_record_fixtures.rb +86 -0
- data/lib/tapioca/compilers/dsl/active_support_concern.rb +0 -2
- data/lib/tapioca/compilers/dsl/base.rb +12 -0
- data/lib/tapioca/compilers/dsl/mixed_in_class_attributes.rb +74 -0
- data/lib/tapioca/compilers/dsl/smart_properties.rb +4 -4
- data/lib/tapioca/compilers/dsl_compiler.rb +7 -6
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
- data/lib/tapioca/compilers/sorbet.rb +0 -1
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +84 -153
- data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
- data/lib/tapioca/config.rb +1 -0
- data/lib/tapioca/config_builder.rb +1 -0
- data/lib/tapioca/gemfile.rb +11 -5
- data/lib/tapioca/generators/base.rb +61 -0
- data/lib/tapioca/generators/dsl.rb +362 -0
- data/lib/tapioca/generators/gem.rb +345 -0
- data/lib/tapioca/generators/init.rb +79 -0
- data/lib/tapioca/generators/require.rb +52 -0
- data/lib/tapioca/generators/todo.rb +76 -0
- data/lib/tapioca/generators.rb +9 -0
- data/lib/tapioca/internal.rb +1 -2
- data/lib/tapioca/loader.rb +2 -2
- data/lib/tapioca/rbi_ext/model.rb +44 -0
- data/lib/tapioca/reflection.rb +8 -1
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +2 -0
- metadata +16 -6
- data/lib/tapioca/generator.rb +0 -717
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28c385b223b0ab1221b81420ad3a40222ff243552e121959e1e2290b1711357e
|
4
|
+
data.tar.gz: 1c06cd621dee12c9afd506a669c2762d2c7ca2f471f2a22b4e38bde6092f9f13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8f6278d058bef31b5fdac1d94556814de7486355d1bcdf9fb2e29da6f33743ec5c330befb492b51ada82d392d10bae2e0c663eda7a365c1272631add49adce6
|
7
|
+
data.tar.gz: 4a342639d1ab6a79b83f6273ac9d44589919953f70773d0492c8c038250f344541d280801db88be72883dd8c55ff8ccd9fafa9ffcbff32e5421df2e821b59365
|
data/Gemfile
CHANGED
@@ -11,7 +11,6 @@ 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")
|
@@ -35,4 +34,5 @@ group(:development, :test) do
|
|
35
34
|
gem("nokogiri", require: false)
|
36
35
|
gem("config", require: false)
|
37
36
|
gem("aasm", require: false)
|
37
|
+
gem("bcrypt", require: false)
|
38
38
|
end
|
data/lib/tapioca/cli.rb
CHANGED
@@ -5,8 +5,6 @@ require "thor"
|
|
5
5
|
|
6
6
|
module Tapioca
|
7
7
|
class Cli < Thor
|
8
|
-
include(Thor::Actions)
|
9
|
-
|
10
8
|
class_option :outdir,
|
11
9
|
aliases: ["--out", "-o"],
|
12
10
|
banner: "directory",
|
@@ -29,22 +27,37 @@ module Tapioca
|
|
29
27
|
|
30
28
|
desc "init", "initializes folder structure"
|
31
29
|
def init
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
35
36
|
end
|
36
37
|
|
37
38
|
desc "require", "generate the list of files to be required by tapioca"
|
38
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
|
+
)
|
39
45
|
Tapioca.silence_warnings do
|
40
|
-
generator.
|
46
|
+
generator.generate
|
41
47
|
end
|
42
48
|
end
|
43
49
|
|
44
50
|
desc "todo", "generate the list of unresolved constants"
|
45
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
|
+
)
|
46
59
|
Tapioca.silence_warnings do
|
47
|
-
generator.
|
60
|
+
generator.generate
|
48
61
|
end
|
49
62
|
end
|
50
63
|
|
@@ -67,13 +80,23 @@ module Tapioca
|
|
67
80
|
type: :boolean,
|
68
81
|
desc: "Supresses file creation output"
|
69
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
|
+
)
|
70
98
|
Tapioca.silence_warnings do
|
71
|
-
generator.
|
72
|
-
constants,
|
73
|
-
should_verify: options[:verify],
|
74
|
-
quiet: options[:quiet],
|
75
|
-
verbose: options[:verbose]
|
76
|
-
)
|
99
|
+
generator.generate
|
77
100
|
end
|
78
101
|
end
|
79
102
|
|
@@ -104,10 +127,27 @@ module Tapioca
|
|
104
127
|
type: :boolean,
|
105
128
|
default: false,
|
106
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"
|
107
134
|
def gem(*gems)
|
108
135
|
Tapioca.silence_warnings do
|
109
136
|
all = options[:all]
|
110
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
|
+
)
|
111
151
|
|
112
152
|
raise MalformattedArgumentError, "Options '--all' and '--verify' are mutually exclusive" if all && verify
|
113
153
|
|
@@ -117,146 +157,22 @@ module Tapioca
|
|
117
157
|
end
|
118
158
|
|
119
159
|
if gems.empty? && !all
|
120
|
-
generator.
|
160
|
+
generator.sync(should_verify: verify)
|
121
161
|
else
|
122
|
-
generator.
|
162
|
+
generator.generate
|
123
163
|
end
|
124
164
|
end
|
125
165
|
end
|
126
166
|
|
127
|
-
desc "generate [gem...]", "DEPRECATED: generate RBIs from gems"
|
128
|
-
option :prerequire,
|
129
|
-
aliases: ["--pre", "-b"],
|
130
|
-
banner: "file",
|
131
|
-
desc: "A file to be required before Bundler.require is called"
|
132
|
-
option :postrequire,
|
133
|
-
aliases: ["--post", "-a"],
|
134
|
-
banner: "file",
|
135
|
-
desc: "A file to be required after Bundler.require is called"
|
136
|
-
option :exclude,
|
137
|
-
aliases: ["-x"],
|
138
|
-
type: :array,
|
139
|
-
banner: "gem [gem ...]",
|
140
|
-
desc: "Excludes the given gem(s) from RBI generation"
|
141
|
-
option :typed_overrides,
|
142
|
-
aliases: ["--typed", "-t"],
|
143
|
-
type: :hash,
|
144
|
-
banner: "gem:level [gem:level ...]",
|
145
|
-
desc: "Overrides for typed sigils for generated gem RBIs"
|
146
|
-
def generate(*gems)
|
147
|
-
gem_names = if gems.empty?
|
148
|
-
"--all"
|
149
|
-
else
|
150
|
-
gems.join(" ")
|
151
|
-
end
|
152
|
-
deprecation_message = <<~MSG
|
153
|
-
DEPRECATION: The `generate` command will be removed in a future release.
|
154
|
-
|
155
|
-
Start using `bin/tapioca gem #{gem_names}` instead.
|
156
|
-
MSG
|
157
|
-
|
158
|
-
say(deprecation_message, :red)
|
159
|
-
say("")
|
160
|
-
|
161
|
-
Tapioca.silence_warnings do
|
162
|
-
generator.build_gem_rbis(gems)
|
163
|
-
end
|
164
|
-
|
165
|
-
say("")
|
166
|
-
say(deprecation_message, :red)
|
167
|
-
end
|
168
|
-
|
169
|
-
desc "sync", "DEPRECATED: sync RBIs to Gemfile"
|
170
|
-
option :prerequire,
|
171
|
-
aliases: ["--pre", "-b"],
|
172
|
-
banner: "file",
|
173
|
-
desc: "A file to be required before Bundler.require is called"
|
174
|
-
option :postrequire,
|
175
|
-
aliases: ["--post", "-a"],
|
176
|
-
banner: "file",
|
177
|
-
desc: "A file to be required after Bundler.require is called"
|
178
|
-
option :exclude,
|
179
|
-
aliases: ["-x"],
|
180
|
-
type: :array,
|
181
|
-
banner: "gem [gem ...]",
|
182
|
-
desc: "Excludes the given gem(s) from RBI generation"
|
183
|
-
option :typed_overrides,
|
184
|
-
aliases: ["--typed", "-t"],
|
185
|
-
type: :hash,
|
186
|
-
banner: "gem:level [gem:level ...]",
|
187
|
-
desc: "Overrides for typed sigils for generated gem RBIs"
|
188
|
-
option :verify,
|
189
|
-
type: :boolean,
|
190
|
-
default: false,
|
191
|
-
desc: "Verifies RBIs are up-to-date"
|
192
|
-
def sync
|
193
|
-
deprecation_message = <<~MSG
|
194
|
-
DEPRECATION: The `sync` command will be removed in a future release.
|
195
|
-
|
196
|
-
Start using `bin/tapioca gem` instead.
|
197
|
-
MSG
|
198
|
-
|
199
|
-
say(deprecation_message, :red)
|
200
|
-
say("")
|
201
|
-
|
202
|
-
Tapioca.silence_warnings do
|
203
|
-
generator.sync_rbis_with_gemfile(should_verify: options[:verify])
|
204
|
-
end
|
205
|
-
|
206
|
-
say("")
|
207
|
-
say(deprecation_message, :red)
|
208
|
-
end
|
209
|
-
|
210
167
|
desc "--version, -v", "show version"
|
211
168
|
def __print_version
|
212
169
|
puts "Tapioca v#{Tapioca::VERSION}"
|
213
170
|
end
|
214
171
|
|
215
|
-
private
|
216
|
-
|
217
|
-
def create_config
|
218
|
-
create_file(Config::SORBET_CONFIG, skip: true) do
|
219
|
-
<<~CONTENT
|
220
|
-
--dir
|
221
|
-
.
|
222
|
-
CONTENT
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def create_post_require
|
227
|
-
create_file(Config::DEFAULT_POSTREQUIRE, skip: true) do
|
228
|
-
<<~CONTENT
|
229
|
-
# typed: true
|
230
|
-
# frozen_string_literal: true
|
231
|
-
|
232
|
-
# Add your extra requires here (`tapioca require` can be used to boostrap this list)
|
233
|
-
CONTENT
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
def generate_binstub
|
238
|
-
bin_stub_exists = File.exist?("bin/tapioca")
|
239
|
-
installer = Bundler::Installer.new(Bundler.root, Bundler.definition)
|
240
|
-
spec = Bundler.definition.specs.find { |s| s.name == "tapioca" }
|
241
|
-
installer.generate_bundler_executable_stubs(spec, { force: true })
|
242
|
-
if bin_stub_exists
|
243
|
-
shell.say_status(:force, "bin/tapioca", :yellow)
|
244
|
-
else
|
245
|
-
shell.say_status(:create, "bin/tapioca", :green)
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
172
|
no_commands do
|
250
173
|
def self.exit_on_failure?
|
251
174
|
true
|
252
175
|
end
|
253
|
-
|
254
|
-
def generator
|
255
|
-
current_command = T.must(current_command_chain.first)
|
256
|
-
@generator ||= Generator.new(
|
257
|
-
ConfigBuilder.from_options(current_command, options)
|
258
|
-
)
|
259
|
-
end
|
260
176
|
end
|
261
177
|
end
|
262
178
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "active_model"
|
6
|
+
rescue LoadError
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
module Tapioca
|
11
|
+
module Compilers
|
12
|
+
module Dsl
|
13
|
+
# `Tapioca::Compilers::Dsl::ActiveModelSecurePassword` decorates RBI files for all
|
14
|
+
# classes that use [`ActiveModel::SecurePassword`](http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html).
|
15
|
+
#
|
16
|
+
# For example, with the following class:
|
17
|
+
#
|
18
|
+
# ~~~rb
|
19
|
+
# class User
|
20
|
+
# include ActiveModel::SecurePassword
|
21
|
+
#
|
22
|
+
# has_secure_password
|
23
|
+
# has_secure_password :token
|
24
|
+
# end
|
25
|
+
# ~~~
|
26
|
+
#
|
27
|
+
# this generator will produce an RBI file with the following content:
|
28
|
+
# ~~~rbi
|
29
|
+
# # typed: true
|
30
|
+
#
|
31
|
+
# class User
|
32
|
+
# sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
|
33
|
+
# def authenticate(unencrypted_password); end
|
34
|
+
#
|
35
|
+
# sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
|
36
|
+
# def authenticate_password(unencrypted_password); end
|
37
|
+
#
|
38
|
+
# sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
|
39
|
+
# def authenticate_token(unencrypted_password); end
|
40
|
+
#
|
41
|
+
# sig { returns(T.untyped) }
|
42
|
+
# def password; end
|
43
|
+
#
|
44
|
+
# sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
|
45
|
+
# def password=(unencrypted_password); end
|
46
|
+
#
|
47
|
+
# sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
|
48
|
+
# def password_confirmation=(unencrypted_password); end
|
49
|
+
#
|
50
|
+
# sig { returns(T.untyped) }
|
51
|
+
# def token; end
|
52
|
+
#
|
53
|
+
# sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
|
54
|
+
# def token=(unencrypted_password); end
|
55
|
+
#
|
56
|
+
# sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
|
57
|
+
# def token_confirmation=(unencrypted_password); end
|
58
|
+
# end
|
59
|
+
# ~~~
|
60
|
+
class ActiveModelSecurePassword < Base
|
61
|
+
extend T::Sig
|
62
|
+
|
63
|
+
sig do
|
64
|
+
override
|
65
|
+
.params(root: RBI::Tree, constant: T.all(Class, ::ActiveModel::SecurePassword::ClassMethods))
|
66
|
+
.void
|
67
|
+
end
|
68
|
+
def decorate(root, constant)
|
69
|
+
instance_methods_modules = if constant < ActiveModel::SecurePassword::InstanceMethodsOnActivation
|
70
|
+
# pre Rails 6.0, this used to be a single static module
|
71
|
+
[ActiveModel::SecurePassword::InstanceMethodsOnActivation]
|
72
|
+
else
|
73
|
+
# post Rails 6.0, this is now using a dynmaic module builder pattern
|
74
|
+
# and we can have multiple different ones included into the model
|
75
|
+
constant.ancestors.grep(ActiveModel::SecurePassword::InstanceMethodsOnActivation)
|
76
|
+
end
|
77
|
+
|
78
|
+
return if instance_methods_modules.empty?
|
79
|
+
|
80
|
+
methods = instance_methods_modules.flat_map { |mod| mod.instance_methods(false) }
|
81
|
+
return if methods.empty?
|
82
|
+
|
83
|
+
root.create_path(constant) do |klass|
|
84
|
+
methods.each do |method|
|
85
|
+
create_method_from_def(klass, constant.instance_method(method))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
sig { override.returns(T::Enumerable[Module]) }
|
91
|
+
def gather_constants
|
92
|
+
# This selects all classes that are `ActiveModel::SecurePassword::ClassMethods === klass`.
|
93
|
+
# In other words, we select all classes that have `ActiveModel::SecurePassword::ClassMethods`
|
94
|
+
# as an ancestor of its singleton class, i.e. all classes that have extended the
|
95
|
+
# `ActiveModel::SecurePassword::ClassMethods` module.
|
96
|
+
all_classes.grep(::ActiveModel::SecurePassword::ClassMethods)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "rails"
|
6
|
+
require "active_record"
|
7
|
+
require "active_record/fixtures"
|
8
|
+
require "active_support/test_case"
|
9
|
+
rescue LoadError
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
module Tapioca
|
14
|
+
module Compilers
|
15
|
+
module Dsl
|
16
|
+
# `Tapioca::Compilers::Dsl::ActiveRecordFixtures` decorates RBIs for test fixture methods
|
17
|
+
# that are created dynamically by Rails.
|
18
|
+
#
|
19
|
+
# For example, given an application with a posts table, we can have a fixture file
|
20
|
+
#
|
21
|
+
# ~~~yaml
|
22
|
+
# first_post:
|
23
|
+
# author: John
|
24
|
+
# title: My post
|
25
|
+
# ~~~
|
26
|
+
#
|
27
|
+
# Rails will allow us to invoke `posts(:first_post)` in tests to get the fixture record.
|
28
|
+
# The generated RBI by this generator will produce the following
|
29
|
+
#
|
30
|
+
# ~~~rbi
|
31
|
+
# # test_case.rbi
|
32
|
+
# # typed: true
|
33
|
+
# class ActiveSupport::TestCase
|
34
|
+
# sig { params(fixture_names: Symbol).returns(T.untyped) }
|
35
|
+
# def posts(*fixture_names); end
|
36
|
+
# end
|
37
|
+
# ~~~
|
38
|
+
class ActiveRecordFixtures < Base
|
39
|
+
extend T::Sig
|
40
|
+
|
41
|
+
sig { override.params(root: RBI::Tree, constant: T.class_of(ActiveSupport::TestCase)).void }
|
42
|
+
def decorate(root, constant)
|
43
|
+
method_names = fixture_loader.ancestors # get all ancestors from class that includes AR fixtures
|
44
|
+
.drop(1) # drop the anonymous class itself from the array
|
45
|
+
.reject(&:name) # only collect anonymous ancestors because fixture methods are always on an anonymous module
|
46
|
+
.map! do |mod|
|
47
|
+
[mod.private_instance_methods(false), mod.instance_methods(false)]
|
48
|
+
end
|
49
|
+
.flatten # merge methods into a single list
|
50
|
+
return if method_names.empty?
|
51
|
+
|
52
|
+
root.create_path(constant) do |mod|
|
53
|
+
method_names.each do |name|
|
54
|
+
create_fixture_method(mod, name.to_s)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { override.returns(T::Enumerable[Module]) }
|
60
|
+
def gather_constants
|
61
|
+
[ActiveSupport::TestCase]
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
sig { returns(Class) }
|
67
|
+
def fixture_loader
|
68
|
+
Class.new do
|
69
|
+
T.unsafe(self).include(ActiveRecord::TestFixtures)
|
70
|
+
T.unsafe(self).fixture_path = Rails.root.join("test", "fixtures")
|
71
|
+
T.unsafe(self).fixtures(:all)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
sig { params(mod: RBI::Scope, name: String).void }
|
76
|
+
def create_fixture_method(mod, name)
|
77
|
+
mod.create_method(
|
78
|
+
name,
|
79
|
+
parameters: [create_rest_param("fixture_names", type: "Symbol")],
|
80
|
+
return_type: "T.untyped"
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -6,6 +6,8 @@ require "tapioca/rbi_ext/model"
|
|
6
6
|
module Tapioca
|
7
7
|
module Compilers
|
8
8
|
module Dsl
|
9
|
+
COMPILERS_PATH = T.let(File.expand_path("..", __FILE__).to_s, String)
|
10
|
+
|
9
11
|
class Base
|
10
12
|
extend T::Sig
|
11
13
|
extend T::Helpers
|
@@ -17,9 +19,13 @@ module Tapioca
|
|
17
19
|
sig { returns(T::Set[Module]) }
|
18
20
|
attr_reader :processable_constants
|
19
21
|
|
22
|
+
sig { returns(T::Array[String]) }
|
23
|
+
attr_reader :errors
|
24
|
+
|
20
25
|
sig { void }
|
21
26
|
def initialize
|
22
27
|
@processable_constants = T.let(Set.new(gather_constants), T::Set[Module])
|
28
|
+
@errors = T.let([], T::Array[String])
|
23
29
|
end
|
24
30
|
|
25
31
|
sig { params(constant: Module).returns(T::Boolean) }
|
@@ -41,6 +47,12 @@ module Tapioca
|
|
41
47
|
sig { abstract.returns(T::Enumerable[Module]) }
|
42
48
|
def gather_constants; end
|
43
49
|
|
50
|
+
# NOTE: This should eventually accept an `Error` object or `Exception` rather than simply a `String`.
|
51
|
+
sig { params(error: String).void }
|
52
|
+
def add_error(error)
|
53
|
+
@errors << error
|
54
|
+
end
|
55
|
+
|
44
56
|
private
|
45
57
|
|
46
58
|
sig { returns(T::Enumerable[Class]) }
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "active_support/core_ext/class/attribute"
|
6
|
+
rescue LoadError
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
module Tapioca
|
11
|
+
module Compilers
|
12
|
+
module Dsl
|
13
|
+
# `Tapioca::Compilers::Dsl::MixedInClassAttributes` generates RBI files for modules that dynamically use
|
14
|
+
# `class_attribute` on classes.
|
15
|
+
#
|
16
|
+
# For example, given the following concern
|
17
|
+
#
|
18
|
+
# ~~~rb
|
19
|
+
# module Taggeable
|
20
|
+
# extend ActiveSupport::Concern
|
21
|
+
#
|
22
|
+
# included do
|
23
|
+
# class_attribute :tag
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# ~~~
|
27
|
+
#
|
28
|
+
# this generator will produce the RBI file `taggeable.rbi` with the following content:
|
29
|
+
#
|
30
|
+
# ~~~rbi
|
31
|
+
# # typed: strong
|
32
|
+
#
|
33
|
+
# module Taggeable
|
34
|
+
# include GeneratedInstanceMethods
|
35
|
+
#
|
36
|
+
# mixes_in_class_methods GeneratedClassMethods
|
37
|
+
#
|
38
|
+
# module GeneratedClassMethods
|
39
|
+
# def tag; end
|
40
|
+
# def tag=(value); end
|
41
|
+
# def tag?; end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# module GeneratedInstanceMethods
|
45
|
+
# def tag; end
|
46
|
+
# def tag=(value); end
|
47
|
+
# def tag?; end
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
# ~~~
|
51
|
+
class MixedInClassAttributes < Base
|
52
|
+
extend T::Sig
|
53
|
+
|
54
|
+
sig { override.params(root: RBI::Tree, constant: Module).void }
|
55
|
+
def decorate(root, constant)
|
56
|
+
mixin_compiler = DynamicMixinCompiler.new(constant)
|
57
|
+
return if mixin_compiler.empty_attributes?
|
58
|
+
|
59
|
+
root.create_path(constant) do |mod|
|
60
|
+
mixin_compiler.compile_class_attributes(mod)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
sig { override.returns(T::Enumerable[Module]) }
|
65
|
+
def gather_constants
|
66
|
+
# Select all non-anonymous modules that have overridden Module.included
|
67
|
+
all_modules.select do |mod|
|
68
|
+
!mod.is_a?(Class) && name_of(mod) && Tapioca::Reflection.method_of(mod, :included).owner != Module
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -84,10 +84,10 @@ module Tapioca
|
|
84
84
|
|
85
85
|
sig { override.returns(T::Enumerable[Module]) }
|
86
86
|
def gather_constants
|
87
|
-
|
88
|
-
c
|
89
|
-
|
90
|
-
|
87
|
+
all_modules.select do |c|
|
88
|
+
name_of(c) &&
|
89
|
+
c != ::SmartProperties::Validations::Ancestor &&
|
90
|
+
c < ::SmartProperties && ::SmartProperties::ClassMethods === c
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
@@ -34,7 +34,7 @@ module Tapioca
|
|
34
34
|
@error_handler = T.let(error_handler || $stderr.method(:puts), T.proc.params(error: String).void)
|
35
35
|
end
|
36
36
|
|
37
|
-
sig { params(blk: T.proc.params(constant: Module, rbi:
|
37
|
+
sig { params(blk: T.proc.params(constant: Module, rbi: RBI::File).void).void }
|
38
38
|
def run(&blk)
|
39
39
|
constants_to_process = gather_constants(requested_constants)
|
40
40
|
|
@@ -51,6 +51,10 @@ module Tapioca
|
|
51
51
|
|
52
52
|
blk.call(constant, rbi)
|
53
53
|
end
|
54
|
+
|
55
|
+
generators.flat_map(&:errors).each do |msg|
|
56
|
+
report_error(msg)
|
57
|
+
end
|
54
58
|
end
|
55
59
|
|
56
60
|
private
|
@@ -77,7 +81,7 @@ module Tapioca
|
|
77
81
|
constants
|
78
82
|
end
|
79
83
|
|
80
|
-
sig { params(constant: Module).returns(T.nilable(
|
84
|
+
sig { params(constant: Module).returns(T.nilable(RBI::File)) }
|
81
85
|
def rbi_for_constant(constant)
|
82
86
|
file = RBI::File.new(strictness: "true")
|
83
87
|
|
@@ -88,10 +92,7 @@ module Tapioca
|
|
88
92
|
|
89
93
|
return if file.root.empty?
|
90
94
|
|
91
|
-
file
|
92
|
-
file.root.group_nodes!
|
93
|
-
file.root.sort_nodes!
|
94
|
-
file.string
|
95
|
+
file
|
95
96
|
end
|
96
97
|
|
97
98
|
sig { params(error: String).returns(T.noreturn) }
|