tapioca 0.3.1 → 0.4.4
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.
- checksums.yaml +4 -4
- data/Gemfile +25 -1
- data/README.md +23 -2
- data/Rakefile +15 -4
- data/lib/tapioca.rb +8 -2
- data/lib/tapioca/cli.rb +32 -3
- data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +129 -0
- data/lib/tapioca/compilers/dsl/action_mailer.rb +65 -0
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +267 -0
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +393 -0
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +112 -0
- data/lib/tapioca/compilers/dsl/active_record_identity_cache.rb +213 -0
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +100 -0
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +170 -0
- data/lib/tapioca/compilers/dsl/active_resource.rb +140 -0
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +126 -0
- data/lib/tapioca/compilers/dsl/base.rb +165 -0
- data/lib/tapioca/compilers/dsl/frozen_record.rb +96 -0
- data/lib/tapioca/compilers/dsl/protobuf.rb +144 -0
- data/lib/tapioca/compilers/dsl/smart_properties.rb +173 -0
- data/lib/tapioca/compilers/dsl/state_machines.rb +378 -0
- data/lib/tapioca/compilers/dsl/url_helpers.rb +92 -0
- data/lib/tapioca/compilers/dsl_compiler.rb +121 -0
- data/lib/tapioca/compilers/requires_compiler.rb +67 -0
- data/lib/tapioca/compilers/sorbet.rb +34 -0
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +171 -26
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +1 -20
- data/lib/tapioca/compilers/todos_compiler.rb +32 -0
- data/lib/tapioca/config.rb +14 -6
- data/lib/tapioca/config_builder.rb +22 -9
- data/lib/tapioca/constant_locator.rb +1 -0
- data/lib/tapioca/core_ext/class.rb +23 -0
- data/lib/tapioca/gemfile.rb +32 -9
- data/lib/tapioca/generator.rb +231 -23
- data/lib/tapioca/loader.rb +30 -9
- data/lib/tapioca/version.rb +1 -1
- metadata +32 -39
@@ -0,0 +1,92 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "parlour"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "rails"
|
8
|
+
require "action_controller"
|
9
|
+
require "action_view"
|
10
|
+
rescue LoadError
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
module Tapioca
|
15
|
+
module Compilers
|
16
|
+
module Dsl
|
17
|
+
class UrlHelpers < Base
|
18
|
+
extend T::Sig
|
19
|
+
|
20
|
+
sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
|
21
|
+
def decorate(root, constant)
|
22
|
+
case constant
|
23
|
+
when GeneratedPathHelpersModule.singleton_class, GeneratedUrlHelpersModule.singleton_class
|
24
|
+
generate_module_for(root, constant)
|
25
|
+
else
|
26
|
+
root.path(constant) do |mod|
|
27
|
+
create_mixins_for(mod, constant, GeneratedUrlHelpersModule)
|
28
|
+
create_mixins_for(mod, constant, GeneratedPathHelpersModule)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
NON_DISCOVERABLE_INCLUDERS = T.let([
|
34
|
+
ActionDispatch::IntegrationTest,
|
35
|
+
ActionView::Helpers,
|
36
|
+
], T::Array[Module])
|
37
|
+
|
38
|
+
sig { override.returns(T::Enumerable[Module]) }
|
39
|
+
def gather_constants
|
40
|
+
Object.const_set(:GeneratedUrlHelpersModule, Rails.application.routes.named_routes.url_helpers_module)
|
41
|
+
Object.const_set(:GeneratedPathHelpersModule, Rails.application.routes.named_routes.path_helpers_module)
|
42
|
+
|
43
|
+
module_enumerator = T.cast(ObjectSpace.each_object(Module), T::Enumerator[Module])
|
44
|
+
constants = module_enumerator.select do |mod|
|
45
|
+
next unless Module.instance_method(:name).bind(mod).call
|
46
|
+
|
47
|
+
includes_helper?(mod, GeneratedUrlHelpersModule) ||
|
48
|
+
includes_helper?(mod, GeneratedPathHelpersModule) ||
|
49
|
+
includes_helper?(mod.singleton_class, GeneratedUrlHelpersModule) ||
|
50
|
+
includes_helper?(mod.singleton_class, GeneratedPathHelpersModule)
|
51
|
+
end
|
52
|
+
|
53
|
+
constants.concat(NON_DISCOVERABLE_INCLUDERS)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
sig { params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
|
59
|
+
def generate_module_for(root, constant)
|
60
|
+
root.create_module(T.must(constant.name)) do |mod|
|
61
|
+
mod.create_include("ActionDispatch::Routing::UrlFor")
|
62
|
+
mod.create_include("ActionDispatch::Routing::PolymorphicRoutes")
|
63
|
+
|
64
|
+
constant.instance_methods(false).each do |method|
|
65
|
+
mod.create_method(
|
66
|
+
method.to_s,
|
67
|
+
parameters: [Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped")],
|
68
|
+
return_type: "String"
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module), helper_module: Module).void }
|
75
|
+
def create_mixins_for(mod, constant, helper_module)
|
76
|
+
include_helper = constant.ancestors.include?(helper_module) || NON_DISCOVERABLE_INCLUDERS.include?(constant)
|
77
|
+
extend_helper = constant.singleton_class.ancestors.include?(helper_module)
|
78
|
+
|
79
|
+
mod.create_include(T.must(helper_module.name)) if include_helper
|
80
|
+
mod.create_extend(T.must(helper_module.name)) if extend_helper
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { params(mod: Module, helper: Module).returns(T::Boolean) }
|
84
|
+
def includes_helper?(mod, helper)
|
85
|
+
superclass_ancestors = mod.superclass&.ancestors if Class === mod
|
86
|
+
superclass_ancestors ||= []
|
87
|
+
(mod.ancestors - superclass_ancestors).include?(helper)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
require "tapioca/compilers/dsl/base"
|
5
|
+
|
6
|
+
module Tapioca
|
7
|
+
module Compilers
|
8
|
+
class DslCompiler
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { returns(T::Enumerable[Dsl::Base]) }
|
12
|
+
attr_reader :generators
|
13
|
+
|
14
|
+
sig { returns(T::Array[Module]) }
|
15
|
+
attr_reader :requested_constants
|
16
|
+
|
17
|
+
sig { returns(T.proc.params(error: String).void) }
|
18
|
+
attr_reader :error_handler
|
19
|
+
|
20
|
+
sig do
|
21
|
+
params(
|
22
|
+
requested_constants: T::Array[Module],
|
23
|
+
requested_generators: T::Array[String],
|
24
|
+
error_handler: T.nilable(T.proc.params(error: String).void)
|
25
|
+
).void
|
26
|
+
end
|
27
|
+
def initialize(requested_constants:, requested_generators: [], error_handler: nil)
|
28
|
+
@generators = T.let(
|
29
|
+
gather_generators(requested_generators),
|
30
|
+
T::Enumerable[Dsl::Base]
|
31
|
+
)
|
32
|
+
@requested_constants = requested_constants
|
33
|
+
@error_handler = error_handler || $stderr.method(:puts)
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { params(blk: T.proc.params(constant: Module, rbi: String).void).void }
|
37
|
+
def run(&blk)
|
38
|
+
constants_to_process = gather_constants(requested_constants)
|
39
|
+
|
40
|
+
if constants_to_process.empty?
|
41
|
+
report_error(<<~ERROR)
|
42
|
+
No classes/modules can be matched for RBI generation.
|
43
|
+
Please check that the requested classes/modules include processable DSL methods.
|
44
|
+
ERROR
|
45
|
+
end
|
46
|
+
|
47
|
+
constants_to_process.sort_by { |c| c.name.to_s }.each do |constant|
|
48
|
+
rbi = rbi_for_constant(constant)
|
49
|
+
next if rbi.nil?
|
50
|
+
|
51
|
+
blk.call(constant, rbi)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
sig { params(requested_generators: T::Array[String]).returns(Proc) }
|
58
|
+
def generator_filter(requested_generators)
|
59
|
+
return proc { true } if requested_generators.empty?
|
60
|
+
|
61
|
+
generators = requested_generators.map(&:downcase)
|
62
|
+
|
63
|
+
proc do |klass|
|
64
|
+
generator = klass.name&.sub(/^Tapioca::Compilers::Dsl::/, '')&.downcase
|
65
|
+
generators.include?(generator)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { params(requested_generators: T::Array[String]).returns(T::Enumerable[Dsl::Base]) }
|
70
|
+
def gather_generators(requested_generators)
|
71
|
+
generator_filter = generator_filter(requested_generators)
|
72
|
+
|
73
|
+
Dsl::Base.descendants.select(&generator_filter).map(&:new)
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
|
77
|
+
def gather_constants(requested_constants)
|
78
|
+
constants = generators.map(&:processable_constants).reduce(Set.new, :union)
|
79
|
+
constants &= requested_constants unless requested_constants.empty?
|
80
|
+
constants
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
84
|
+
def rbi_for_constant(constant)
|
85
|
+
parlour = Parlour::RbiGenerator.new(sort_namespaces: true)
|
86
|
+
|
87
|
+
generators.each do |generator|
|
88
|
+
next unless generator.handles?(constant)
|
89
|
+
generator.decorate(parlour.root, constant)
|
90
|
+
end
|
91
|
+
|
92
|
+
return if parlour.root.children.empty?
|
93
|
+
|
94
|
+
resolve_conflicts(parlour)
|
95
|
+
|
96
|
+
parlour.rbi("true").strip
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { params(parlour: Parlour::RbiGenerator).void }
|
100
|
+
def resolve_conflicts(parlour)
|
101
|
+
Parlour::ConflictResolver.new.resolve_conflicts(parlour.root) do |msg, candidates|
|
102
|
+
error = StringIO.new
|
103
|
+
error.puts "=== Error ==="
|
104
|
+
error.puts msg
|
105
|
+
error.puts "# Candidates"
|
106
|
+
candidates.each_with_index do |candidate, index|
|
107
|
+
error.puts " #{index}. #{candidate.describe}"
|
108
|
+
end
|
109
|
+
report_error(error.string)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
sig { params(error: String).returns(T.noreturn) }
|
114
|
+
def report_error(error)
|
115
|
+
handler = error_handler
|
116
|
+
handler.call(error)
|
117
|
+
exit(1)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
require 'spoom'
|
5
|
+
|
6
|
+
module Tapioca
|
7
|
+
module Compilers
|
8
|
+
class RequiresCompiler
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { params(sorbet_path: String).void }
|
12
|
+
def initialize(sorbet_path)
|
13
|
+
@sorbet_path = sorbet_path
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { returns(String) }
|
17
|
+
def compile
|
18
|
+
config = Spoom::Sorbet::Config.parse_file(@sorbet_path)
|
19
|
+
files = collect_files(config)
|
20
|
+
files.flat_map do |file|
|
21
|
+
collect_requires(file).reject do |req|
|
22
|
+
name_in_project?(files, req)
|
23
|
+
end
|
24
|
+
end.sort.uniq.map do |name|
|
25
|
+
"require '#{name}'\n"
|
26
|
+
end.join
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
sig { params(config: Spoom::Sorbet::Config).returns(T::Array[String]) }
|
32
|
+
def collect_files(config)
|
33
|
+
config.paths.flat_map do |path|
|
34
|
+
path = (Pathname.new(@sorbet_path) / "../.." / path).cleanpath
|
35
|
+
if path.directory?
|
36
|
+
Dir.glob("#{path}/**/*.rb", File::FNM_EXTGLOB).reject do |file|
|
37
|
+
file_ignored_by_sorbet?(config, file)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
[path.to_s]
|
41
|
+
end
|
42
|
+
end.sort.uniq
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { params(file_path: String).returns(T::Enumerable[String]) }
|
46
|
+
def collect_requires(file_path)
|
47
|
+
File.read(file_path).lines.map do |line|
|
48
|
+
/^\s*require\s*(\(\s*)?['"](?<name>[^'"]+)['"](\s*\))?/.match(line) { |m| m["name"] }
|
49
|
+
end.compact
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { params(config: Spoom::Sorbet::Config, file: String).returns(T::Boolean) }
|
53
|
+
def file_ignored_by_sorbet?(config, file)
|
54
|
+
config.ignore.any? do |path|
|
55
|
+
Regexp.new(Regexp.escape(path)) =~ file
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { params(files: T::Enumerable[String], name: String).returns(T::Boolean) }
|
60
|
+
def name_in_project?(files, name)
|
61
|
+
files.any? do |file|
|
62
|
+
File.basename(file, '.rb') == name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
require 'shellwords'
|
6
|
+
|
7
|
+
module Tapioca
|
8
|
+
module Compilers
|
9
|
+
module Sorbet
|
10
|
+
SORBET = Pathname.new(Gem::Specification.find_by_name("sorbet-static").full_gem_path) / "libexec" / "sorbet"
|
11
|
+
|
12
|
+
class << self
|
13
|
+
extend(T::Sig)
|
14
|
+
|
15
|
+
sig { params(args: String).returns(String) }
|
16
|
+
def run(*args)
|
17
|
+
IO.popen(
|
18
|
+
[
|
19
|
+
sorbet_path,
|
20
|
+
"--quiet",
|
21
|
+
*args,
|
22
|
+
].join(' '),
|
23
|
+
err: "/dev/null"
|
24
|
+
).read
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { returns(String) }
|
28
|
+
def sorbet_path
|
29
|
+
SORBET.to_s.shellescape
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -29,7 +29,7 @@ module Tapioca
|
|
29
29
|
def generate
|
30
30
|
symbols
|
31
31
|
.sort
|
32
|
-
.map(
|
32
|
+
.map { |symbol| generate_from_symbol(symbol) }
|
33
33
|
.compact
|
34
34
|
.join("\n\n")
|
35
35
|
.concat("\n")
|
@@ -176,13 +176,43 @@ module Tapioca
|
|
176
176
|
return if symbol_ignored?(name) && methods.nil?
|
177
177
|
|
178
178
|
[
|
179
|
+
compile_module_helpers(constant),
|
179
180
|
compile_mixins(constant),
|
180
181
|
compile_mixes_in_class_methods(constant),
|
182
|
+
compile_props(constant),
|
181
183
|
methods,
|
182
184
|
].select { |b| b != "" }.join("\n\n")
|
183
185
|
end
|
184
186
|
end
|
185
187
|
|
188
|
+
sig { params(constant: Module).returns(String) }
|
189
|
+
def compile_module_helpers(constant)
|
190
|
+
abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
|
191
|
+
|
192
|
+
if abstract_type
|
193
|
+
indented("#{abstract_type}!")
|
194
|
+
elsif T::Private::Final.final_module?(constant)
|
195
|
+
indented("final!")
|
196
|
+
elsif T::Private::Sealed.sealed_module?(constant)
|
197
|
+
indented("sealed!")
|
198
|
+
else
|
199
|
+
""
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
sig { params(constant: Module).returns(String) }
|
204
|
+
def compile_props(constant)
|
205
|
+
return "" unless T::Props::ClassMethods === constant
|
206
|
+
|
207
|
+
constant.props.map do |name, prop|
|
208
|
+
method = "prop"
|
209
|
+
method = "const" if prop.fetch(:immutable, false)
|
210
|
+
type = prop.fetch(:type_object, "T.untyped")
|
211
|
+
|
212
|
+
indented("#{method} :#{name}, #{type}")
|
213
|
+
end.join("\n")
|
214
|
+
end
|
215
|
+
|
186
216
|
sig { params(name: String, constant: Module).returns(T.nilable(String)) }
|
187
217
|
def compile_subconstants(name, constant)
|
188
218
|
output = constants_of(constant).sort.uniq.map do |constant_name|
|
@@ -287,6 +317,7 @@ module Tapioca
|
|
287
317
|
end
|
288
318
|
|
289
319
|
prepends = prepend
|
320
|
+
.reverse
|
290
321
|
.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
|
291
322
|
.select(&method(:public_module?))
|
292
323
|
.map do |mod|
|
@@ -297,6 +328,7 @@ module Tapioca
|
|
297
328
|
end
|
298
329
|
|
299
330
|
includes = include
|
331
|
+
.reverse
|
300
332
|
.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
|
301
333
|
.select(&method(:public_module?))
|
302
334
|
.map do |mod|
|
@@ -304,6 +336,7 @@ module Tapioca
|
|
304
336
|
end
|
305
337
|
|
306
338
|
extends = extend
|
339
|
+
.reverse
|
307
340
|
.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
|
308
341
|
.select(&method(:public_module?))
|
309
342
|
.map do |mod|
|
@@ -376,7 +409,7 @@ module Tapioca
|
|
376
409
|
)
|
377
410
|
|
378
411
|
instance_methods = compile_directly_owned_methods(name, constant)
|
379
|
-
singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant)
|
412
|
+
singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
|
380
413
|
|
381
414
|
return if symbol_ignored?(name) && instance_methods.empty? && singleton_methods.empty?
|
382
415
|
|
@@ -389,24 +422,44 @@ module Tapioca
|
|
389
422
|
|
390
423
|
sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(String) }
|
391
424
|
def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
425
|
+
indent_step = 0
|
426
|
+
preamble = nil
|
427
|
+
postamble = nil
|
428
|
+
|
429
|
+
if mod.singleton_class?
|
430
|
+
indent_step = 1
|
431
|
+
preamble = indented("class << self")
|
432
|
+
postamble = indented("end")
|
433
|
+
end
|
400
434
|
|
401
|
-
|
402
|
-
|
403
|
-
|
435
|
+
methods = with_indentation(indent_step) do
|
436
|
+
method_names_by_visibility(mod)
|
437
|
+
.delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
|
438
|
+
.flat_map do |visibility, method_list|
|
439
|
+
compiled = method_list.sort!.map do |name|
|
440
|
+
next if name == :initialize
|
441
|
+
compile_method(module_name, mod, mod.instance_method(name))
|
442
|
+
end
|
443
|
+
compiled.compact!
|
444
|
+
|
445
|
+
unless compiled.empty? || visibility == :public
|
446
|
+
# add visibility badge
|
447
|
+
compiled.unshift('', indented(visibility.to_s), '')
|
448
|
+
end
|
449
|
+
|
450
|
+
compiled
|
404
451
|
end
|
452
|
+
.compact
|
453
|
+
.join("\n")
|
454
|
+
end
|
405
455
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
456
|
+
return "" if methods.strip == ""
|
457
|
+
|
458
|
+
[
|
459
|
+
preamble,
|
460
|
+
methods,
|
461
|
+
postamble,
|
462
|
+
].compact.join("\n")
|
410
463
|
end
|
411
464
|
|
412
465
|
sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
|
@@ -418,6 +471,16 @@ module Tapioca
|
|
418
471
|
}
|
419
472
|
end
|
420
473
|
|
474
|
+
sig { params(constant: Module, method_name: String).returns(T::Boolean) }
|
475
|
+
def struct_method?(constant, method_name)
|
476
|
+
return false unless T::Props::ClassMethods === constant
|
477
|
+
|
478
|
+
constant
|
479
|
+
.props
|
480
|
+
.keys
|
481
|
+
.include?(method_name.gsub(/=$/, '').to_sym)
|
482
|
+
end
|
483
|
+
|
421
484
|
sig do
|
422
485
|
params(
|
423
486
|
symbol_name: String,
|
@@ -430,12 +493,37 @@ module Tapioca
|
|
430
493
|
return unless method.owner == constant
|
431
494
|
return if symbol_ignored?(symbol_name) && !method_in_gem?(method)
|
432
495
|
|
496
|
+
signature = signature_of(method)
|
497
|
+
method = signature.method if signature
|
498
|
+
|
433
499
|
method_name = method.name.to_s
|
434
500
|
return unless valid_method_name?(method_name)
|
501
|
+
return if struct_method?(constant, method_name)
|
502
|
+
return if method_name.start_with?("__t_props_generated_")
|
435
503
|
|
436
504
|
params = T.let(method.parameters, T::Array[T::Array[Symbol]])
|
437
505
|
parameters = params.map do |(type, name)|
|
438
|
-
name
|
506
|
+
unless name
|
507
|
+
# For attr_writer methods, Sorbet signatures have the name
|
508
|
+
# of the method (without the trailing = sign) as the name of
|
509
|
+
# the only parameter. So, if the parameter does not have a name
|
510
|
+
# then the replacement name should be the name of the method
|
511
|
+
# (minus trailing =) if and only if there is a signature for the
|
512
|
+
# method and the parameter is required and there is a single
|
513
|
+
# parameter and the signature also defines a single parameter and
|
514
|
+
# the name of the method ends with a = character.
|
515
|
+
writer_method_with_sig = signature &&
|
516
|
+
type == :req &&
|
517
|
+
params.size == 1 &&
|
518
|
+
signature.arg_types.size == 1 &&
|
519
|
+
method_name[-1] == "="
|
520
|
+
|
521
|
+
name = if writer_method_with_sig
|
522
|
+
method_name[0...-1].to_sym
|
523
|
+
else
|
524
|
+
:_
|
525
|
+
end
|
526
|
+
end
|
439
527
|
|
440
528
|
# Sanitize param names
|
441
529
|
name = name.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
|
@@ -444,13 +532,13 @@ module Tapioca
|
|
444
532
|
when :req
|
445
533
|
name
|
446
534
|
when :opt
|
447
|
-
"#{name} =
|
535
|
+
"#{name} = T.unsafe(nil)"
|
448
536
|
when :rest
|
449
537
|
"*#{name}"
|
450
538
|
when :keyreq
|
451
539
|
"#{name}:"
|
452
540
|
when :key
|
453
|
-
"#{name}:
|
541
|
+
"#{name}: T.unsafe(nil)"
|
454
542
|
when :keyrest
|
455
543
|
"**#{name}"
|
456
544
|
when :block
|
@@ -458,10 +546,54 @@ module Tapioca
|
|
458
546
|
end
|
459
547
|
end.join(', ')
|
460
548
|
|
461
|
-
method_name = "#{'self.' if constant.singleton_class?}#{method_name}"
|
462
549
|
parameters = "(#{parameters})" if parameters != ""
|
463
550
|
|
464
|
-
indented(
|
551
|
+
signature_str = indented(compile_signature(signature)) if signature
|
552
|
+
[
|
553
|
+
signature_str,
|
554
|
+
indented("def #{method_name}#{parameters}; end"),
|
555
|
+
].compact.join("\n")
|
556
|
+
end
|
557
|
+
|
558
|
+
TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
|
559
|
+
|
560
|
+
sig { params(signature: T.untyped).returns(String) }
|
561
|
+
def compile_signature(signature)
|
562
|
+
params = signature.arg_types
|
563
|
+
params += signature.kwarg_types.to_a
|
564
|
+
params << [signature.rest_name, signature.rest_type] if signature.has_rest
|
565
|
+
params << [signature.block_name, signature.block_type] if signature.block_name
|
566
|
+
|
567
|
+
params = params.compact.map { |name, type| "#{name}: #{type}" }.join(", ")
|
568
|
+
returns = type_of(signature.return_type)
|
569
|
+
|
570
|
+
type_parameters = (params + returns).scan(TYPE_PARAMETER_MATCHER).flatten.uniq.map { |p| ":#{p}" }.join(", ")
|
571
|
+
type_parameters = ".type_parameters(#{type_parameters})" unless type_parameters.empty?
|
572
|
+
|
573
|
+
mode = case signature.mode
|
574
|
+
when "abstract"
|
575
|
+
".abstract"
|
576
|
+
when "override"
|
577
|
+
".override"
|
578
|
+
when "overridable_override"
|
579
|
+
".overridable.override"
|
580
|
+
when "overridable"
|
581
|
+
".overridable"
|
582
|
+
else
|
583
|
+
""
|
584
|
+
end
|
585
|
+
|
586
|
+
signature_body = +""
|
587
|
+
signature_body << mode
|
588
|
+
signature_body << type_parameters
|
589
|
+
signature_body << ".params(#{params})" unless params.empty?
|
590
|
+
signature_body << ".returns(#{returns})"
|
591
|
+
signature_body = signature_body
|
592
|
+
.gsub(".returns(<VOID>)", ".void")
|
593
|
+
.gsub("<NOT-TYPED>", "T.untyped")
|
594
|
+
.gsub(TYPE_PARAMETER_MATCHER, "T.type_parameter(:\\1)")[1..-1]
|
595
|
+
|
596
|
+
"sig { #{signature_body} }"
|
465
597
|
end
|
466
598
|
|
467
599
|
sig { params(symbol_name: String).returns(T::Boolean) }
|
@@ -480,16 +612,17 @@ module Tapioca
|
|
480
612
|
sig do
|
481
613
|
type_parameters(:U)
|
482
614
|
.params(
|
615
|
+
step: Integer,
|
483
616
|
_blk: T.proc
|
484
617
|
.returns(T.type_parameter(:U))
|
485
618
|
)
|
486
619
|
.returns(T.type_parameter(:U))
|
487
620
|
end
|
488
|
-
def with_indentation(&_blk)
|
489
|
-
@indent += 2
|
621
|
+
def with_indentation(step = 1, &_blk)
|
622
|
+
@indent += 2 * step
|
490
623
|
yield
|
491
624
|
ensure
|
492
|
-
@indent -= 2
|
625
|
+
@indent -= 2 * step
|
493
626
|
end
|
494
627
|
|
495
628
|
sig { params(str: String).returns(String) }
|
@@ -624,7 +757,7 @@ module Tapioca
|
|
624
757
|
return nil
|
625
758
|
end
|
626
759
|
|
627
|
-
|
760
|
+
raw_name_of(target)
|
628
761
|
end
|
629
762
|
|
630
763
|
sig { params(constant: Module).returns(T.nilable(String)) }
|
@@ -644,6 +777,18 @@ module Tapioca
|
|
644
777
|
Class.instance_method(:superclass).bind(constant).call
|
645
778
|
end
|
646
779
|
|
780
|
+
sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
|
781
|
+
def signature_of(method)
|
782
|
+
T::Private::Methods.signature_for_method(method)
|
783
|
+
rescue LoadError, StandardError
|
784
|
+
nil
|
785
|
+
end
|
786
|
+
|
787
|
+
sig { params(constant: Module).returns(String) }
|
788
|
+
def type_of(constant)
|
789
|
+
constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
|
790
|
+
end
|
791
|
+
|
647
792
|
sig { params(constant: Module, other: BasicObject).returns(T::Boolean).checked(:never) }
|
648
793
|
def are_equal?(constant, other)
|
649
794
|
BasicObject.instance_method(:equal?).bind(constant).call(other)
|