tapioca 0.2.7 → 0.4.1
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 +27 -1
- data/README.md +21 -2
- data/Rakefile +15 -4
- data/lib/tapioca.rb +15 -9
- data/lib/tapioca/cli.rb +41 -12
- 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 +285 -0
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +379 -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 +163 -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 +83 -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 +209 -49
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +3 -17
- data/lib/tapioca/compilers/todos_compiler.rb +32 -0
- data/lib/tapioca/config.rb +42 -0
- data/lib/tapioca/config_builder.rb +75 -0
- data/lib/tapioca/constant_locator.rb +1 -0
- data/lib/tapioca/core_ext/class.rb +23 -0
- data/lib/tapioca/gemfile.rb +14 -1
- data/lib/tapioca/generator.rb +235 -67
- data/lib/tapioca/loader.rb +20 -9
- data/lib/tapioca/sorbet_config_parser.rb +77 -0
- data/lib/tapioca/version.rb +1 -1
- metadata +35 -66
@@ -0,0 +1,83 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "parlour"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "rails"
|
8
|
+
require "action_controller"
|
9
|
+
rescue LoadError
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
module Tapioca
|
14
|
+
module Compilers
|
15
|
+
module Dsl
|
16
|
+
class UrlHelpers < Base
|
17
|
+
extend T::Sig
|
18
|
+
|
19
|
+
sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
|
20
|
+
def decorate(root, constant)
|
21
|
+
case constant
|
22
|
+
when GeneratedPathHelpersModule.singleton_class, GeneratedUrlHelpersModule.singleton_class
|
23
|
+
generate_module_for(root, constant)
|
24
|
+
else
|
25
|
+
root.path(constant) do |mod|
|
26
|
+
create_mixins_for(mod, constant, GeneratedUrlHelpersModule)
|
27
|
+
create_mixins_for(mod, constant, GeneratedPathHelpersModule)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { override.returns(T::Enumerable[T.untyped]) }
|
33
|
+
def gather_constants
|
34
|
+
Object.const_set(:GeneratedUrlHelpersModule, Rails.application.routes.named_routes.url_helpers_module)
|
35
|
+
Object.const_set(:GeneratedPathHelpersModule, Rails.application.routes.named_routes.path_helpers_module)
|
36
|
+
|
37
|
+
constants = ObjectSpace.each_object(Module).select do |mod|
|
38
|
+
mod = T.cast(mod, T.class_of(Module))
|
39
|
+
next unless Module.instance_method(:name).bind(mod).call
|
40
|
+
|
41
|
+
includes_helper?(mod, GeneratedUrlHelpersModule) ||
|
42
|
+
includes_helper?(mod, GeneratedPathHelpersModule) ||
|
43
|
+
includes_helper?(mod.singleton_class, GeneratedUrlHelpersModule) ||
|
44
|
+
includes_helper?(mod.singleton_class, GeneratedPathHelpersModule)
|
45
|
+
end
|
46
|
+
|
47
|
+
constants << ActionDispatch::IntegrationTest
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
sig { params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
|
53
|
+
def generate_module_for(root, constant)
|
54
|
+
root.create_module(T.must(constant.name)) do |mod|
|
55
|
+
mod.create_include("ActionDispatch::Routing::UrlFor")
|
56
|
+
mod.create_include("ActionDispatch::Routing::PolymorphicRoutes")
|
57
|
+
|
58
|
+
constant.instance_methods(false).each do |method|
|
59
|
+
mod.create_method(
|
60
|
+
method.to_s,
|
61
|
+
parameters: [Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped")],
|
62
|
+
return_type: "String"
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module), helper_module: Module).void }
|
69
|
+
def create_mixins_for(mod, constant, helper_module)
|
70
|
+
mod.create_include(T.must(helper_module.name)) if constant.ancestors.include?(helper_module)
|
71
|
+
mod.create_extend(T.must(helper_module.name)) if constant.singleton_class.ancestors.include?(helper_module)
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(mod: Module, helper: Module).returns(T::Boolean) }
|
75
|
+
def includes_helper?(mod, helper)
|
76
|
+
superclass_ancestors = mod.superclass&.ancestors if Class === mod
|
77
|
+
superclass_ancestors ||= []
|
78
|
+
(mod.ancestors - superclass_ancestors).include?(helper)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
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_relative '../sorbet_config_parser'
|
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 = SorbetConfig.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: SorbetConfig).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: SorbetConfig, 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
|
@@ -137,14 +137,11 @@ module Tapioca
|
|
137
137
|
end
|
138
138
|
def compile_object(name, value)
|
139
139
|
return if symbol_ignored?(name)
|
140
|
-
indented("#{name} = T.let(T.unsafe(nil), #{type_name_of(value)})")
|
141
|
-
end
|
142
|
-
|
143
|
-
sig { params(value: BasicObject).returns(String).checked(:never) }
|
144
|
-
def type_name_of(value)
|
145
140
|
klass = class_of(value)
|
141
|
+
return if name_of(klass)&.start_with?("T::Types::", "T::Private::")
|
146
142
|
|
147
|
-
public_module?(klass) && name_of(klass) || "T.untyped"
|
143
|
+
type_name = public_module?(klass) && name_of(klass) || "T.untyped"
|
144
|
+
indented("#{name} = T.let(T.unsafe(nil), #{type_name})")
|
148
145
|
end
|
149
146
|
|
150
147
|
sig { params(name: String, constant: Module).returns(T.nilable(String)) }
|
@@ -179,12 +176,43 @@ module Tapioca
|
|
179
176
|
return if symbol_ignored?(name) && methods.nil?
|
180
177
|
|
181
178
|
[
|
179
|
+
compile_module_helpers(constant),
|
182
180
|
compile_mixins(constant),
|
181
|
+
compile_mixes_in_class_methods(constant),
|
182
|
+
compile_props(constant),
|
183
183
|
methods,
|
184
184
|
].select { |b| b != "" }.join("\n\n")
|
185
185
|
end
|
186
186
|
end
|
187
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
|
+
|
188
216
|
sig { params(name: String, constant: Module).returns(T.nilable(String)) }
|
189
217
|
def compile_subconstants(name, constant)
|
190
218
|
output = constants_of(constant).sort.uniq.map do |constant_name|
|
@@ -289,7 +317,8 @@ module Tapioca
|
|
289
317
|
end
|
290
318
|
|
291
319
|
prepends = prepend
|
292
|
-
.
|
320
|
+
.reverse
|
321
|
+
.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
|
293
322
|
.select(&method(:public_module?))
|
294
323
|
.map do |mod|
|
295
324
|
# TODO: Sorbet currently does not handle prepend
|
@@ -299,30 +328,76 @@ module Tapioca
|
|
299
328
|
end
|
300
329
|
|
301
330
|
includes = include
|
302
|
-
.
|
331
|
+
.reverse
|
332
|
+
.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
|
303
333
|
.select(&method(:public_module?))
|
304
334
|
.map do |mod|
|
305
335
|
indented("include(#{qualified_name_of(mod)})")
|
306
336
|
end
|
307
337
|
|
308
338
|
extends = extend
|
309
|
-
.
|
339
|
+
.reverse
|
340
|
+
.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
|
310
341
|
.select(&method(:public_module?))
|
311
342
|
.map do |mod|
|
312
343
|
indented("extend(#{qualified_name_of(mod)})")
|
313
344
|
end
|
314
345
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
346
|
+
(prepends + includes + extends).join("\n")
|
347
|
+
end
|
348
|
+
|
349
|
+
sig { params(constant: Module).returns(String) }
|
350
|
+
def compile_mixes_in_class_methods(constant)
|
351
|
+
return "" if constant.is_a?(Class)
|
352
|
+
|
353
|
+
mixins_from_modules = {}
|
354
|
+
|
355
|
+
Class.new do
|
356
|
+
# rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
|
357
|
+
def method_missing(symbol, *args)
|
358
|
+
end
|
359
|
+
|
360
|
+
define_singleton_method(:include) do |mod|
|
361
|
+
before = singleton_class.ancestors
|
362
|
+
super(mod).tap do
|
363
|
+
mixins_from_modules[mod] = singleton_class.ancestors - before
|
364
|
+
end
|
319
365
|
end
|
320
|
-
|
321
|
-
|
322
|
-
|
366
|
+
|
367
|
+
class << self
|
368
|
+
def method_missing(symbol, *args)
|
369
|
+
end
|
323
370
|
end
|
371
|
+
# rubocop:enable Style/MethodMissingSuper, Style/MissingRespondToMissing
|
372
|
+
end.include(constant)
|
373
|
+
|
374
|
+
all_dynamic_extends = mixins_from_modules.delete(constant)
|
375
|
+
all_dynamic_includes = mixins_from_modules.keys
|
376
|
+
dynamic_extends_from_dynamic_includes = mixins_from_modules.values.flatten
|
377
|
+
dynamic_extends = all_dynamic_extends - dynamic_extends_from_dynamic_includes
|
324
378
|
|
325
|
-
|
379
|
+
result = all_dynamic_includes
|
380
|
+
.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
|
381
|
+
.select(&method(:public_module?))
|
382
|
+
.map do |mod|
|
383
|
+
indented("include(#{qualified_name_of(mod)})")
|
384
|
+
end.join("\n")
|
385
|
+
|
386
|
+
mixed_in_module = dynamic_extends.find do |mod|
|
387
|
+
mod != constant && public_module?(mod)
|
388
|
+
end
|
389
|
+
|
390
|
+
return result if mixed_in_module.nil?
|
391
|
+
|
392
|
+
qualified_name = qualified_name_of(mixed_in_module)
|
393
|
+
return result if qualified_name == ""
|
394
|
+
|
395
|
+
[
|
396
|
+
result,
|
397
|
+
indented("mixes_in_class_methods(#{qualified_name})"),
|
398
|
+
].select { |b| b != "" }.join("\n\n")
|
399
|
+
rescue
|
400
|
+
""
|
326
401
|
end
|
327
402
|
|
328
403
|
sig { params(name: String, constant: Module).returns(T.nilable(String)) }
|
@@ -334,7 +409,7 @@ module Tapioca
|
|
334
409
|
)
|
335
410
|
|
336
411
|
instance_methods = compile_directly_owned_methods(name, constant)
|
337
|
-
singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant)
|
412
|
+
singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
|
338
413
|
|
339
414
|
return if symbol_ignored?(name) && instance_methods.empty? && singleton_methods.empty?
|
340
415
|
|
@@ -347,24 +422,44 @@ module Tapioca
|
|
347
422
|
|
348
423
|
sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(String) }
|
349
424
|
def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
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
|
358
434
|
|
359
|
-
|
360
|
-
|
361
|
-
|
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
|
362
451
|
end
|
452
|
+
.compact
|
453
|
+
.join("\n")
|
454
|
+
end
|
363
455
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
456
|
+
return "" if methods.strip == ""
|
457
|
+
|
458
|
+
[
|
459
|
+
preamble,
|
460
|
+
methods,
|
461
|
+
postamble,
|
462
|
+
].compact.join("\n")
|
368
463
|
end
|
369
464
|
|
370
465
|
sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
|
@@ -376,6 +471,16 @@ module Tapioca
|
|
376
471
|
}
|
377
472
|
end
|
378
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
|
+
|
379
484
|
sig do
|
380
485
|
params(
|
381
486
|
symbol_name: String,
|
@@ -388,24 +493,32 @@ module Tapioca
|
|
388
493
|
return unless method.owner == constant
|
389
494
|
return if symbol_ignored?(symbol_name) && !method_in_gem?(method)
|
390
495
|
|
496
|
+
signature = signature_of(method)
|
497
|
+
method = signature.method if signature
|
498
|
+
|
391
499
|
method_name = method.name.to_s
|
392
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_")
|
393
503
|
|
394
504
|
params = T.let(method.parameters, T::Array[T::Array[Symbol]])
|
395
505
|
parameters = params.map do |(type, name)|
|
396
506
|
name ||= :_
|
397
507
|
|
508
|
+
# Sanitize param names
|
509
|
+
name = name.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
|
510
|
+
|
398
511
|
case type
|
399
512
|
when :req
|
400
|
-
name
|
513
|
+
name
|
401
514
|
when :opt
|
402
|
-
"#{name} =
|
515
|
+
"#{name} = T.unsafe(nil)"
|
403
516
|
when :rest
|
404
517
|
"*#{name}"
|
405
518
|
when :keyreq
|
406
519
|
"#{name}:"
|
407
520
|
when :key
|
408
|
-
"#{name}:
|
521
|
+
"#{name}: T.unsafe(nil)"
|
409
522
|
when :keyrest
|
410
523
|
"**#{name}"
|
411
524
|
when :block
|
@@ -413,10 +526,54 @@ module Tapioca
|
|
413
526
|
end
|
414
527
|
end.join(', ')
|
415
528
|
|
416
|
-
method_name = "#{'self.' if constant.singleton_class?}#{method_name}"
|
417
529
|
parameters = "(#{parameters})" if parameters != ""
|
418
530
|
|
419
|
-
indented(
|
531
|
+
signature_str = indented(compile_signature(signature)) if signature
|
532
|
+
[
|
533
|
+
signature_str,
|
534
|
+
indented("def #{method_name}#{parameters}; end"),
|
535
|
+
].compact.join("\n")
|
536
|
+
end
|
537
|
+
|
538
|
+
TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
|
539
|
+
|
540
|
+
sig { params(signature: T.untyped).returns(String) }
|
541
|
+
def compile_signature(signature)
|
542
|
+
params = signature.arg_types
|
543
|
+
params += signature.kwarg_types.to_a
|
544
|
+
params << [signature.rest_name, signature.rest_type] if signature.has_rest
|
545
|
+
params << [signature.block_name, signature.block_type] if signature.block_name
|
546
|
+
|
547
|
+
params = params.compact.map { |name, type| "#{name}: #{type}" }.join(", ")
|
548
|
+
returns = signature.return_type.to_s
|
549
|
+
|
550
|
+
type_parameters = (params + returns).scan(TYPE_PARAMETER_MATCHER).flatten.uniq.map { |p| ":#{p}" }.join(", ")
|
551
|
+
type_parameters = ".type_parameters(#{type_parameters})" unless type_parameters.empty?
|
552
|
+
|
553
|
+
mode = case signature.mode
|
554
|
+
when "abstract"
|
555
|
+
".abstract"
|
556
|
+
when "override"
|
557
|
+
".override"
|
558
|
+
when "overridable_override"
|
559
|
+
".overridable.override"
|
560
|
+
when "overridable"
|
561
|
+
".overridable"
|
562
|
+
else
|
563
|
+
""
|
564
|
+
end
|
565
|
+
|
566
|
+
signature_body = +""
|
567
|
+
signature_body << mode
|
568
|
+
signature_body << type_parameters
|
569
|
+
signature_body << ".params(#{params})" unless params.empty?
|
570
|
+
signature_body << ".returns(#{returns})"
|
571
|
+
signature_body = signature_body
|
572
|
+
.gsub(".returns(<VOID>)", ".void")
|
573
|
+
.gsub("<NOT-TYPED>", "T.untyped")
|
574
|
+
.gsub(TYPE_PARAMETER_MATCHER, "T.type_parameter(:\\1)")[1..-1]
|
575
|
+
|
576
|
+
"sig { #{signature_body} }"
|
420
577
|
end
|
421
578
|
|
422
579
|
sig { params(symbol_name: String).returns(T::Boolean) }
|
@@ -424,11 +581,6 @@ module Tapioca
|
|
424
581
|
SymbolLoader.ignore_symbol?(symbol_name)
|
425
582
|
end
|
426
583
|
|
427
|
-
sig { params(path: String).returns(T::Boolean) }
|
428
|
-
def path_in_gem?(path)
|
429
|
-
path.start_with?(gem.full_gem_path)
|
430
|
-
end
|
431
|
-
|
432
584
|
SPECIAL_METHOD_NAMES = %w[! ~ +@ ** -@ * / % + - << >> & | ^ < <= => > >= == === != =~ !~ <=> [] []= `]
|
433
585
|
|
434
586
|
sig { params(name: String).returns(T::Boolean) }
|
@@ -440,16 +592,17 @@ module Tapioca
|
|
440
592
|
sig do
|
441
593
|
type_parameters(:U)
|
442
594
|
.params(
|
595
|
+
step: Integer,
|
443
596
|
_blk: T.proc
|
444
597
|
.returns(T.type_parameter(:U))
|
445
598
|
)
|
446
599
|
.returns(T.type_parameter(:U))
|
447
600
|
end
|
448
|
-
def with_indentation(&_blk)
|
449
|
-
@indent += 2
|
601
|
+
def with_indentation(step = 1, &_blk)
|
602
|
+
@indent += 2 * step
|
450
603
|
yield
|
451
604
|
ensure
|
452
|
-
@indent -= 2
|
605
|
+
@indent -= 2 * step
|
453
606
|
end
|
454
607
|
|
455
608
|
sig { params(str: String).returns(String) }
|
@@ -462,7 +615,7 @@ module Tapioca
|
|
462
615
|
source_location = method.source_location&.first
|
463
616
|
return false if source_location.nil?
|
464
617
|
|
465
|
-
|
618
|
+
gem.contains_path?(source_location)
|
466
619
|
end
|
467
620
|
|
468
621
|
sig { params(constant: Module, strict: T::Boolean).returns(T::Boolean) }
|
@@ -473,7 +626,7 @@ module Tapioca
|
|
473
626
|
return !strict if files.empty?
|
474
627
|
|
475
628
|
files.any? do |file|
|
476
|
-
|
629
|
+
gem.contains_path?(file)
|
477
630
|
end
|
478
631
|
end
|
479
632
|
|
@@ -584,7 +737,7 @@ module Tapioca
|
|
584
737
|
return nil
|
585
738
|
end
|
586
739
|
|
587
|
-
|
740
|
+
raw_name_of(target)
|
588
741
|
end
|
589
742
|
|
590
743
|
sig { params(constant: Module).returns(T.nilable(String)) }
|
@@ -604,6 +757,13 @@ module Tapioca
|
|
604
757
|
Class.instance_method(:superclass).bind(constant).call
|
605
758
|
end
|
606
759
|
|
760
|
+
sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
|
761
|
+
def signature_of(method)
|
762
|
+
T::Private::Methods.signature_for_method(method)
|
763
|
+
rescue LoadError, StandardError
|
764
|
+
nil
|
765
|
+
end
|
766
|
+
|
607
767
|
sig { params(constant: Module, other: BasicObject).returns(T::Boolean).checked(:never) }
|
608
768
|
def are_equal?(constant, other)
|
609
769
|
BasicObject.instance_method(:equal?).bind(constant).call(other)
|