tapioca 0.6.4 → 0.7.2
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 +8 -2
- data/README.md +27 -15
- data/Rakefile +10 -14
- data/lib/tapioca/cli.rb +65 -80
- data/lib/tapioca/{generators/base.rb → commands/command.rb} +16 -9
- data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
- data/lib/tapioca/{generators → commands}/gem.rb +93 -30
- data/lib/tapioca/{generators → commands}/init.rb +9 -13
- data/lib/tapioca/{generators → commands}/require.rb +8 -10
- data/lib/tapioca/commands/todo.rb +86 -0
- data/lib/tapioca/commands.rb +13 -0
- data/lib/tapioca/dsl/compiler.rb +185 -0
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +13 -11
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +28 -34
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +18 -16
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +12 -8
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +11 -16
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +14 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +23 -22
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +22 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +12 -13
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +20 -15
- data/lib/tapioca/dsl/compilers.rb +31 -0
- data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
- data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
- data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +6 -3
- data/lib/tapioca/dsl/pipeline.rb +169 -0
- data/lib/tapioca/gem/events.rb +120 -0
- data/lib/tapioca/gem/listeners/base.rb +48 -0
- data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
- data/lib/tapioca/gem/listeners/methods.rb +183 -0
- data/lib/tapioca/gem/listeners/mixins.rb +101 -0
- data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
- data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
- data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
- data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
- data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
- data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
- data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
- data/lib/tapioca/gem/listeners.rb +16 -0
- data/lib/tapioca/gem/pipeline.rb +365 -0
- data/lib/tapioca/helpers/cli_helper.rb +7 -0
- data/lib/tapioca/helpers/config_helper.rb +5 -8
- data/lib/tapioca/helpers/shims_helper.rb +87 -0
- data/lib/tapioca/helpers/signatures_helper.rb +17 -0
- data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
- data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
- data/lib/tapioca/helpers/test/isolation.rb +1 -1
- data/lib/tapioca/helpers/test/template.rb +13 -2
- data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
- data/lib/tapioca/internal.rb +18 -10
- data/lib/tapioca/rbi_ext/model.rb +14 -50
- data/lib/tapioca/rbi_formatter.rb +37 -0
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
- data/lib/tapioca/runtime/generic_type_registry.rb +168 -0
- data/lib/tapioca/runtime/loader.rb +123 -0
- data/lib/tapioca/runtime/reflection.rb +157 -0
- data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
- data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
- data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
- data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
- data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +69 -34
- data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
- data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
- data/lib/tapioca/static/symbol_loader.rb +83 -0
- data/lib/tapioca/static/symbol_table_parser.rb +63 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +2 -7
- metadata +83 -62
- data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
- data/lib/tapioca/compilers/dsl/base.rb +0 -195
- data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
- data/lib/tapioca/compilers/dsl_compiler.rb +0 -134
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
- data/lib/tapioca/compilers/sorbet.rb +0 -59
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
- data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
- data/lib/tapioca/compilers/todos_compiler.rb +0 -32
- data/lib/tapioca/generators/todo.rb +0 -76
- data/lib/tapioca/generators.rb +0 -9
- data/lib/tapioca/generic_type_registry.rb +0 -164
- data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
- data/lib/tapioca/loader.rb +0 -119
- data/lib/tapioca/reflection.rb +0 -151
- data/lib/tapioca/trackers/autoload.rb +0 -70
- data/lib/tapioca/trackers/constant_definition.rb +0 -42
- data/lib/tapioca/trackers/mixin.rb +0 -78
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# typed: true
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "json"
|
|
5
|
-
require "tempfile"
|
|
6
|
-
|
|
7
|
-
module Tapioca
|
|
8
|
-
module Compilers
|
|
9
|
-
module SymbolTable
|
|
10
|
-
module SymbolLoader
|
|
11
|
-
class << self
|
|
12
|
-
extend(T::Sig)
|
|
13
|
-
|
|
14
|
-
sig { params(paths: T::Array[Pathname]).returns(T::Set[String]) }
|
|
15
|
-
def list_from_paths(paths)
|
|
16
|
-
load_symbols(paths.map(&:to_s))
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def ignore_symbol?(symbol)
|
|
20
|
-
symbol = symbol[2..-1] if symbol.start_with?("::")
|
|
21
|
-
ignored_symbols.include?(symbol)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
sig { params(paths: T::Array[String]).returns(T::Set[String]) }
|
|
27
|
-
def load_symbols(paths)
|
|
28
|
-
output = T.cast(Tempfile.create("sorbet") do |file|
|
|
29
|
-
file.write(Array(paths).join("\n"))
|
|
30
|
-
file.flush
|
|
31
|
-
|
|
32
|
-
symbol_table_json_from("@#{file.path.shellescape}")
|
|
33
|
-
end, T.nilable(String))
|
|
34
|
-
|
|
35
|
-
return Set.new if output.nil? || output.empty?
|
|
36
|
-
|
|
37
|
-
json = JSON.parse(output)
|
|
38
|
-
SymbolTableParser.parse(json)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def ignored_symbols
|
|
42
|
-
unless @ignored_symbols
|
|
43
|
-
output = symbol_table_json_from("-e ''", table_type: "symbol-table-full-json")
|
|
44
|
-
json = JSON.parse(output)
|
|
45
|
-
@ignored_symbols = SymbolTableParser.parse(json)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
@ignored_symbols
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def symbol_table_json_from(input, table_type: "symbol-table-json")
|
|
52
|
-
Tapioca::Compilers::Sorbet.run("--no-config", "--print=#{table_type}", input)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
class SymbolTableParser
|
|
57
|
-
def self.parse(object, parents = [])
|
|
58
|
-
symbols = Set.new
|
|
59
|
-
|
|
60
|
-
children = object.fetch("children", [])
|
|
61
|
-
|
|
62
|
-
children.each do |child|
|
|
63
|
-
kind = child.fetch("kind")
|
|
64
|
-
name = child.fetch("name")
|
|
65
|
-
name = name.fetch("name") if name.is_a?(Hash)
|
|
66
|
-
|
|
67
|
-
next if kind.nil? || name.nil?
|
|
68
|
-
|
|
69
|
-
# TODO: CLASS is removed since v0.4.4730 of Sorbet
|
|
70
|
-
# but keeping here for backward compatibility. Remove
|
|
71
|
-
# once the minimum version is moved past that.
|
|
72
|
-
next unless ["CLASS", "CLASS_OR_MODULE", "STATIC_FIELD"].include?(kind)
|
|
73
|
-
next if name =~ /[<>()$]/
|
|
74
|
-
next if name =~ /^[0-9]+$/
|
|
75
|
-
next if name == "T::Helpers"
|
|
76
|
-
|
|
77
|
-
parents << name
|
|
78
|
-
|
|
79
|
-
symbols.add(parents.join("::"))
|
|
80
|
-
symbols.merge(parse(child, parents))
|
|
81
|
-
|
|
82
|
-
parents.pop
|
|
83
|
-
end
|
|
84
|
-
symbols
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# typed: strong
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module Tapioca
|
|
5
|
-
module Compilers
|
|
6
|
-
class SymbolTableCompiler
|
|
7
|
-
extend(T::Sig)
|
|
8
|
-
|
|
9
|
-
sig { params(gem: Gemfile::GemSpec, rbi: RBI::File, indent: Integer, include_docs: T::Boolean).void }
|
|
10
|
-
def compile(gem, rbi, indent = 0, include_docs = false)
|
|
11
|
-
Tapioca::Compilers::SymbolTable::SymbolGenerator
|
|
12
|
-
.new(gem, indent, include_docs)
|
|
13
|
-
.generate(rbi)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# typed: strong
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module Tapioca
|
|
5
|
-
module Compilers
|
|
6
|
-
# Taken from https://github.com/sorbet/sorbet/blob/master/gems/sorbet/lib/todo-rbi.rb
|
|
7
|
-
class TodosCompiler
|
|
8
|
-
extend(T::Sig)
|
|
9
|
-
|
|
10
|
-
sig do
|
|
11
|
-
returns(String)
|
|
12
|
-
end
|
|
13
|
-
def compile
|
|
14
|
-
list_todos.each_line.map do |line|
|
|
15
|
-
next if line.include?("<") || line.include?("class_of")
|
|
16
|
-
"module #{line.strip.gsub("T.untyped::", "")}; end"
|
|
17
|
-
end.compact.join("\n")
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
sig { returns(String) }
|
|
23
|
-
def list_todos
|
|
24
|
-
Tapioca::Compilers::Sorbet.run(
|
|
25
|
-
"--print=missing-constants",
|
|
26
|
-
"--stdout-hup-hack",
|
|
27
|
-
"--no-error-count"
|
|
28
|
-
).strip
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module Tapioca
|
|
5
|
-
module Generators
|
|
6
|
-
class Todo < Base
|
|
7
|
-
sig do
|
|
8
|
-
params(
|
|
9
|
-
todo_file: String,
|
|
10
|
-
file_header: T::Boolean,
|
|
11
|
-
default_command: String,
|
|
12
|
-
file_writer: Thor::Actions
|
|
13
|
-
).void
|
|
14
|
-
end
|
|
15
|
-
def initialize(todo_file:, file_header:, default_command:, file_writer: FileWriter.new)
|
|
16
|
-
@todo_file = todo_file
|
|
17
|
-
@file_header = file_header
|
|
18
|
-
|
|
19
|
-
super(default_command: default_command, file_writer: file_writer)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
sig { override.void }
|
|
23
|
-
def generate
|
|
24
|
-
compiler = Compilers::TodosCompiler.new
|
|
25
|
-
say("Finding all unresolved constants, this may take a few seconds... ")
|
|
26
|
-
|
|
27
|
-
# Clean all existing unresolved constants before regenerating the list
|
|
28
|
-
# so Sorbet won't grab them as already resolved.
|
|
29
|
-
File.delete(@todo_file) if File.exist?(@todo_file)
|
|
30
|
-
|
|
31
|
-
rbi_string = compiler.compile
|
|
32
|
-
if rbi_string.empty?
|
|
33
|
-
say("Nothing to do", :green)
|
|
34
|
-
return
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
content = String.new
|
|
38
|
-
content << rbi_header(
|
|
39
|
-
"#{@default_command} todo",
|
|
40
|
-
reason: "unresolved constants",
|
|
41
|
-
strictness: "false"
|
|
42
|
-
)
|
|
43
|
-
content << rbi_string
|
|
44
|
-
content << "\n"
|
|
45
|
-
|
|
46
|
-
say("Done", :green)
|
|
47
|
-
create_file(@todo_file, content, verbose: false)
|
|
48
|
-
|
|
49
|
-
name = set_color(@todo_file, :yellow, :bold)
|
|
50
|
-
say("\nAll unresolved constants have been written to #{name}.", [:green, :bold])
|
|
51
|
-
say("Please review changes and commit them.", [:green, :bold])
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
sig { params(command: String, reason: T.nilable(String), strictness: T.nilable(String)).returns(String) }
|
|
55
|
-
def rbi_header(command, reason: nil, strictness: nil)
|
|
56
|
-
statement = <<~HEAD
|
|
57
|
-
# DO NOT EDIT MANUALLY
|
|
58
|
-
# This is an autogenerated file for #{reason}.
|
|
59
|
-
# Please instead update this file by running `#{command}`.
|
|
60
|
-
HEAD
|
|
61
|
-
|
|
62
|
-
sigil = <<~SIGIL if strictness
|
|
63
|
-
# typed: #{strictness}
|
|
64
|
-
SIGIL
|
|
65
|
-
|
|
66
|
-
if @file_header
|
|
67
|
-
[statement, sigil].compact.join("\n").strip.concat("\n\n")
|
|
68
|
-
elsif sigil
|
|
69
|
-
sigil.strip.concat("\n\n")
|
|
70
|
-
else
|
|
71
|
-
""
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
data/lib/tapioca/generators.rb
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# typed: true
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require_relative "generators/base"
|
|
5
|
-
require_relative "generators/dsl"
|
|
6
|
-
require_relative "generators/init"
|
|
7
|
-
require_relative "generators/gem"
|
|
8
|
-
require_relative "generators/require"
|
|
9
|
-
require_relative "generators/todo"
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module Tapioca
|
|
5
|
-
# This class is responsible for storing and looking up information related to generic types.
|
|
6
|
-
#
|
|
7
|
-
# The class stores 2 different kinds of data, in two separate lookup tables:
|
|
8
|
-
# 1. a lookup of generic type instances by name: `@generic_instances`
|
|
9
|
-
# 2. a lookup of type variable serializer by constant and type variable
|
|
10
|
-
# instance: `@type_variables`
|
|
11
|
-
#
|
|
12
|
-
# By storing the above data, we can cheaply query each constant against this registry
|
|
13
|
-
# to see if it declares any generic type variables. This becomes a simple lookup in the
|
|
14
|
-
# `@type_variables` hash table with the given constant.
|
|
15
|
-
#
|
|
16
|
-
# If there is no entry, then we can cheaply know that we can skip generic type
|
|
17
|
-
# information generation for this type.
|
|
18
|
-
#
|
|
19
|
-
# On the other hand, if we get a result, then the result will be a hash of type
|
|
20
|
-
# variable to type variable serializers. This allows us to associate type variables
|
|
21
|
-
# to the constant names that represent them, easily.
|
|
22
|
-
module GenericTypeRegistry
|
|
23
|
-
@generic_instances = T.let(
|
|
24
|
-
{},
|
|
25
|
-
T::Hash[String, Module]
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
@type_variables = T.let(
|
|
29
|
-
{}.compare_by_identity,
|
|
30
|
-
T::Hash[Module, T::Array[TypeVariableModule]]
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
class << self
|
|
34
|
-
extend T::Sig
|
|
35
|
-
|
|
36
|
-
# This method is responsible for building the name of the instantiated concrete type
|
|
37
|
-
# and cloning the given constant so that we can return a type that is the same
|
|
38
|
-
# as the current type but is a different instance and has a different name method.
|
|
39
|
-
#
|
|
40
|
-
# We cache those cloned instances by their name in `@generic_instances`, so that
|
|
41
|
-
# we don't keep instantiating a new type every single time it is referenced.
|
|
42
|
-
# For example, `[Foo[Integer], Foo[Integer], Foo[Integer], Foo[String]]` will only
|
|
43
|
-
# result in 2 clones (1 for `Foo[Integer]` and another for `Foo[String]`) and
|
|
44
|
-
# 2 hash lookups (for the other two `Foo[Integer]`s).
|
|
45
|
-
#
|
|
46
|
-
# This method returns the created or cached clone of the constant.
|
|
47
|
-
sig { params(constant: T.untyped, types: T.untyped).returns(Module) }
|
|
48
|
-
def register_type(constant, types)
|
|
49
|
-
# Build the name of the instantiated generic type,
|
|
50
|
-
# something like `"Foo[X, Y, Z]"`
|
|
51
|
-
type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ")
|
|
52
|
-
name = "#{Reflection.name_of(constant)}[#{type_list}]"
|
|
53
|
-
|
|
54
|
-
# Create a generic type with an overridden `name`
|
|
55
|
-
# method that returns the name we constructed above.
|
|
56
|
-
#
|
|
57
|
-
# Also, we try to memoize the generic type based on the name, so that
|
|
58
|
-
# we don't have to keep recreating them all the time.
|
|
59
|
-
@generic_instances[name] ||= create_generic_type(constant, name)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
sig { params(instance: Object).returns(T::Boolean) }
|
|
63
|
-
def generic_type_instance?(instance)
|
|
64
|
-
@generic_instances.values.any? { |generic_type| generic_type === instance }
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
sig { params(constant: Module).returns(T.nilable(T::Array[TypeVariableModule])) }
|
|
68
|
-
def lookup_type_variables(constant)
|
|
69
|
-
@type_variables[constant]
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# This method is called from intercepted calls to `type_member` and `type_template`.
|
|
73
|
-
# We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
|
|
74
|
-
# instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
|
|
75
|
-
#
|
|
76
|
-
# This method creates a `String` with that data and stores it in the
|
|
77
|
-
# `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
|
|
78
|
-
#
|
|
79
|
-
# Finally, the original `type_variable` is returned from this method, so that the caller
|
|
80
|
-
# can return it from the original methods as well.
|
|
81
|
-
sig do
|
|
82
|
-
params(
|
|
83
|
-
constant: T.untyped,
|
|
84
|
-
type_variable: TypeVariableModule,
|
|
85
|
-
).void
|
|
86
|
-
end
|
|
87
|
-
def register_type_variable(constant, type_variable)
|
|
88
|
-
type_variables = lookup_or_initialize_type_variables(constant)
|
|
89
|
-
|
|
90
|
-
type_variables << type_variable
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
private
|
|
94
|
-
|
|
95
|
-
sig { params(constant: Module, name: String).returns(Module) }
|
|
96
|
-
def create_generic_type(constant, name)
|
|
97
|
-
generic_type = case constant
|
|
98
|
-
when Class
|
|
99
|
-
# For classes, we want to create a subclass, so that an instance of
|
|
100
|
-
# the generic class `Foo[Bar]` is still a `Foo`. That is:
|
|
101
|
-
# `Foo[Bar].new.is_a?(Foo)` should be true, which isn't the case
|
|
102
|
-
# if we just clone the class. But subclassing works just fine.
|
|
103
|
-
create_safe_subclass(constant)
|
|
104
|
-
else
|
|
105
|
-
# This can only be a module and it is fine to just clone modules
|
|
106
|
-
# since they can't have instances and will not have `is_a?` relationships.
|
|
107
|
-
# Moreover, we never `include`/`extend` any generic modules into the
|
|
108
|
-
# ancestor tree, so this doesn't become a problem with checking the
|
|
109
|
-
# instance of a class being `is_a?` of a module type.
|
|
110
|
-
constant.clone
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Let's set the `name` method to return the proper generic name
|
|
114
|
-
generic_type.define_singleton_method(:name) { name }
|
|
115
|
-
|
|
116
|
-
# We need to define a `<=` method on the cloned constant, so that Sorbet
|
|
117
|
-
# can do covariance/contravariance checks on the type variables.
|
|
118
|
-
#
|
|
119
|
-
# Normally, we would be doing proper covariance/contravariance checks here, but
|
|
120
|
-
# that is not necessary, since we are not implementing a runtime type checker
|
|
121
|
-
# here. It is just enough for the checks to pass, so that we can serialize the
|
|
122
|
-
# signatures, assuming the sigs were well-formed.
|
|
123
|
-
#
|
|
124
|
-
# So we act like all subtype checks pass.
|
|
125
|
-
generic_type.define_singleton_method(:<=) { |_| true }
|
|
126
|
-
|
|
127
|
-
# Return the generic type we created
|
|
128
|
-
generic_type
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
sig { params(constant: Class).returns(Class) }
|
|
132
|
-
def create_safe_subclass(constant)
|
|
133
|
-
# Lookup the "inherited" class method
|
|
134
|
-
inherited_method = constant.method(:inherited)
|
|
135
|
-
# and the module that defines it
|
|
136
|
-
owner = inherited_method.owner
|
|
137
|
-
|
|
138
|
-
# If no one has overriden the inherited method yet, just subclass
|
|
139
|
-
return Class.new(constant) if Class == owner
|
|
140
|
-
|
|
141
|
-
begin
|
|
142
|
-
# Otherwise, some inherited method could be preventing us
|
|
143
|
-
# from creating subclasses, so let's override it and rescue
|
|
144
|
-
owner.send(:define_method, :inherited) do |s|
|
|
145
|
-
inherited_method.call(s)
|
|
146
|
-
rescue
|
|
147
|
-
# Ignoring errors
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# return a subclass
|
|
151
|
-
Class.new(constant)
|
|
152
|
-
ensure
|
|
153
|
-
# Reinstate the original inherited method back.
|
|
154
|
-
owner.send(:define_method, :inherited, inherited_method)
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
sig { params(constant: Module).returns(T::Array[TypeVariableModule]) }
|
|
159
|
-
def lookup_or_initialize_type_variables(constant)
|
|
160
|
-
@type_variables[constant] ||= []
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
class ActiveRecordColumnTypeHelper
|
|
5
|
-
extend T::Sig
|
|
6
|
-
|
|
7
|
-
sig { params(constant: T.class_of(ActiveRecord::Base)).void }
|
|
8
|
-
def initialize(constant)
|
|
9
|
-
@constant = constant
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
sig { params(column_name: String).returns([String, String]) }
|
|
13
|
-
def type_for(column_name)
|
|
14
|
-
return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(@constant)
|
|
15
|
-
|
|
16
|
-
column_type = @constant.attribute_types[column_name]
|
|
17
|
-
|
|
18
|
-
getter_type =
|
|
19
|
-
case column_type
|
|
20
|
-
when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
|
|
21
|
-
"::Money"
|
|
22
|
-
when ActiveRecord::Type::Integer
|
|
23
|
-
"::Integer"
|
|
24
|
-
when ActiveRecord::Type::String
|
|
25
|
-
"::String"
|
|
26
|
-
when ActiveRecord::Type::Date
|
|
27
|
-
"::Date"
|
|
28
|
-
when ActiveRecord::Type::Decimal
|
|
29
|
-
"::BigDecimal"
|
|
30
|
-
when ActiveRecord::Type::Float
|
|
31
|
-
"::Float"
|
|
32
|
-
when ActiveRecord::Type::Boolean
|
|
33
|
-
"T::Boolean"
|
|
34
|
-
when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
|
|
35
|
-
"::DateTime"
|
|
36
|
-
when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
|
|
37
|
-
"::ActiveSupport::TimeWithZone"
|
|
38
|
-
else
|
|
39
|
-
handle_unknown_type(column_type)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
column = @constant.columns_hash[column_name]
|
|
43
|
-
setter_type = getter_type
|
|
44
|
-
|
|
45
|
-
if column&.null
|
|
46
|
-
return [as_nilable_type(getter_type), as_nilable_type(setter_type)]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
if column_name == @constant.primary_key ||
|
|
50
|
-
column_name == "created_at" ||
|
|
51
|
-
column_name == "updated_at"
|
|
52
|
-
getter_type = as_nilable_type(getter_type)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
[getter_type, setter_type]
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
private
|
|
59
|
-
|
|
60
|
-
sig { params(constant: Module).returns(T::Boolean) }
|
|
61
|
-
def do_not_generate_strong_types?(constant)
|
|
62
|
-
Object.const_defined?(:StrongTypeGeneration) &&
|
|
63
|
-
!(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
sig { params(type: String).returns(String) }
|
|
67
|
-
def as_nilable_type(type)
|
|
68
|
-
if type.start_with?("T.nilable(") || type == "T.untyped"
|
|
69
|
-
type
|
|
70
|
-
else
|
|
71
|
-
"T.nilable(#{type})"
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
sig { params(column_type: Object).returns(String) }
|
|
76
|
-
def handle_unknown_type(column_type)
|
|
77
|
-
return "T.untyped" unless ActiveModel::Type::Value === column_type
|
|
78
|
-
return "T.untyped" if Tapioca::GenericTypeRegistry.generic_type_instance?(column_type)
|
|
79
|
-
|
|
80
|
-
lookup_return_type_of_method(column_type, :deserialize) ||
|
|
81
|
-
lookup_return_type_of_method(column_type, :cast) ||
|
|
82
|
-
lookup_arg_type_of_method(column_type, :serialize) ||
|
|
83
|
-
"T.untyped"
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
|
|
87
|
-
def lookup_return_type_of_method(column_type, method)
|
|
88
|
-
signature = T::Private::Methods.signature_for_method(column_type.method(method))
|
|
89
|
-
return unless signature
|
|
90
|
-
|
|
91
|
-
return_type = signature.return_type
|
|
92
|
-
return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
|
|
93
|
-
|
|
94
|
-
return_type.to_s
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
|
|
98
|
-
def lookup_arg_type_of_method(column_type, method)
|
|
99
|
-
signature = T::Private::Methods.signature_for_method(column_type.method(method))
|
|
100
|
-
return unless signature
|
|
101
|
-
|
|
102
|
-
# Arg types is an array [name, type] entries, so we desctructure the type of
|
|
103
|
-
# first argument to get the first argument type
|
|
104
|
-
_, first_argument_type = signature.arg_types.first
|
|
105
|
-
|
|
106
|
-
first_argument_type.to_s
|
|
107
|
-
end
|
|
108
|
-
end
|
data/lib/tapioca/loader.rb
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module Tapioca
|
|
5
|
-
class Loader
|
|
6
|
-
extend(T::Sig)
|
|
7
|
-
|
|
8
|
-
sig { params(gemfile: Tapioca::Gemfile, initialize_file: T.nilable(String), require_file: T.nilable(String)).void }
|
|
9
|
-
def load_bundle(gemfile, initialize_file, require_file)
|
|
10
|
-
require_helper(initialize_file)
|
|
11
|
-
|
|
12
|
-
load_rails_application
|
|
13
|
-
|
|
14
|
-
gemfile.require_bundle
|
|
15
|
-
|
|
16
|
-
require_helper(require_file)
|
|
17
|
-
|
|
18
|
-
load_rails_engines
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
sig { params(environment_load: T::Boolean, eager_load: T::Boolean).void }
|
|
22
|
-
def load_rails_application(environment_load: false, eager_load: false)
|
|
23
|
-
return unless File.exist?("config/application.rb")
|
|
24
|
-
|
|
25
|
-
silence_deprecations
|
|
26
|
-
|
|
27
|
-
if environment_load
|
|
28
|
-
safe_require("./config/environment")
|
|
29
|
-
else
|
|
30
|
-
safe_require("./config/application")
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
eager_load_rails_app if eager_load
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
sig { params(file: T.nilable(String)).void }
|
|
39
|
-
def require_helper(file)
|
|
40
|
-
return unless file
|
|
41
|
-
file = File.absolute_path(file)
|
|
42
|
-
return unless File.exist?(file)
|
|
43
|
-
|
|
44
|
-
require(file)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
sig { returns(T::Array[T.untyped]) }
|
|
48
|
-
def rails_engines
|
|
49
|
-
return [] unless Object.const_defined?("Rails::Engine")
|
|
50
|
-
|
|
51
|
-
# We can use `Class#descendants` here, since we know Rails is loaded
|
|
52
|
-
Object.const_get("Rails::Engine").descendants.reject(&:abstract_railtie?)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
sig { params(path: String).void }
|
|
56
|
-
def safe_require(path)
|
|
57
|
-
require path
|
|
58
|
-
rescue LoadError
|
|
59
|
-
nil
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
sig { void }
|
|
63
|
-
def silence_deprecations
|
|
64
|
-
# Stop any ActiveSupport Deprecations from being reported
|
|
65
|
-
Object.const_get("ActiveSupport::Deprecation").silenced = true
|
|
66
|
-
rescue NameError
|
|
67
|
-
nil
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
sig { void }
|
|
71
|
-
def eager_load_rails_app
|
|
72
|
-
rails = Object.const_get("Rails")
|
|
73
|
-
application = rails.application
|
|
74
|
-
|
|
75
|
-
if Object.const_defined?("ActiveSupport")
|
|
76
|
-
Object.const_get("ActiveSupport").run_load_hooks(
|
|
77
|
-
:before_eager_load,
|
|
78
|
-
application
|
|
79
|
-
)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
if Object.const_defined?("Zeitwerk::Loader")
|
|
83
|
-
zeitwerk_loader = Object.const_get("Zeitwerk::Loader")
|
|
84
|
-
zeitwerk_loader.eager_load_all
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
if rails.respond_to?(:autoloaders) && rails.autoloaders.zeitwerk_enabled?
|
|
88
|
-
rails.autoloaders.each(&:eager_load)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
if application.config.respond_to?(:eager_load_namespaces)
|
|
92
|
-
application.config.eager_load_namespaces.each(&:eager_load!)
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
sig { void }
|
|
97
|
-
def load_rails_engines
|
|
98
|
-
rails_engines.each do |engine|
|
|
99
|
-
errored_files = []
|
|
100
|
-
|
|
101
|
-
engine.config.eager_load_paths.each do |load_path|
|
|
102
|
-
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
|
|
103
|
-
require(file)
|
|
104
|
-
rescue LoadError, StandardError
|
|
105
|
-
errored_files << file
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Try files that have errored one more time
|
|
110
|
-
# It might have been a load order problem
|
|
111
|
-
errored_files.each do |file|
|
|
112
|
-
require(file)
|
|
113
|
-
rescue LoadError, StandardError
|
|
114
|
-
nil
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|