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