tapioca 0.6.1 → 0.7.0

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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +13 -2
  3. data/README.md +79 -25
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +66 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +17 -10
  7. data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
  8. data/lib/tapioca/{generators → commands}/gem.rb +93 -30
  9. data/lib/tapioca/{generators → commands}/init.rb +9 -13
  10. data/lib/tapioca/{generators → commands}/require.rb +8 -10
  11. data/lib/tapioca/commands/todo.rb +84 -0
  12. data/lib/tapioca/commands.rb +13 -0
  13. data/lib/tapioca/dsl/compiler.rb +185 -0
  14. data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
  15. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
  16. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
  17. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
  18. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +32 -24
  19. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
  20. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +29 -35
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +26 -24
  22. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
  23. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +10 -8
  24. data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
  25. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
  26. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +12 -17
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +11 -11
  29. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
  30. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
  31. data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +10 -8
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +28 -25
  34. data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
  35. data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +10 -8
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +13 -14
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +12 -13
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +16 -14
  41. data/lib/tapioca/dsl/compilers.rb +31 -0
  42. data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
  43. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
  44. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
  45. data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +2 -2
  46. data/lib/tapioca/{compilers/dsl_compiler.rb → dsl/pipeline.rb} +41 -33
  47. data/lib/tapioca/gem/events.rb +120 -0
  48. data/lib/tapioca/gem/listeners/base.rb +48 -0
  49. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
  50. data/lib/tapioca/gem/listeners/methods.rb +183 -0
  51. data/lib/tapioca/gem/listeners/mixins.rb +101 -0
  52. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
  53. data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
  54. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
  55. data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
  56. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
  57. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
  58. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
  59. data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
  60. data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
  61. data/lib/tapioca/gem/listeners.rb +16 -0
  62. data/lib/tapioca/gem/pipeline.rb +365 -0
  63. data/lib/tapioca/gemfile.rb +44 -20
  64. data/lib/tapioca/helpers/cli_helper.rb +16 -8
  65. data/lib/tapioca/helpers/config_helper.rb +113 -0
  66. data/lib/tapioca/helpers/rbi_helper.rb +17 -0
  67. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  68. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  69. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  70. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  71. data/lib/tapioca/helpers/test/template.rb +13 -2
  72. data/lib/tapioca/internal.rb +17 -10
  73. data/lib/tapioca/rbi_ext/model.rb +2 -48
  74. data/lib/tapioca/rbi_formatter.rb +37 -0
  75. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  76. data/lib/tapioca/runtime/generic_type_registry.rb +166 -0
  77. data/lib/tapioca/runtime/loader.rb +123 -0
  78. data/lib/tapioca/runtime/reflection.rb +153 -0
  79. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  80. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  81. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  82. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  83. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  84. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +110 -54
  85. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  86. data/lib/tapioca/{compilers → static}/requires_compiler.rb +5 -12
  87. data/lib/tapioca/static/symbol_loader.rb +83 -0
  88. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  89. data/lib/tapioca/version.rb +1 -1
  90. data/lib/tapioca.rb +2 -7
  91. metadata +82 -62
  92. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -711
  93. data/lib/tapioca/compilers/dsl/base.rb +0 -179
  94. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  95. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -198
  96. data/lib/tapioca/compilers/sorbet.rb +0 -59
  97. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  98. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  99. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  100. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  101. data/lib/tapioca/generators/todo.rb +0 -76
  102. data/lib/tapioca/generators.rb +0 -9
  103. data/lib/tapioca/generic_type_registry.rb +0 -149
  104. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -98
  105. data/lib/tapioca/loader.rb +0 -119
  106. data/lib/tapioca/reflection.rb +0 -151
  107. data/lib/tapioca/trackers/autoload.rb +0 -70
  108. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  109. 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
@@ -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,149 +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
- TypeVariable = T.type_alias { T.any(TypeMember, TypeTemplate) }
24
- @generic_instances = T.let(
25
- {},
26
- T::Hash[String, Module]
27
- )
28
-
29
- @type_variables = T.let(
30
- {}.compare_by_identity,
31
- T::Hash[Module, T::Hash[TypeVariable, String]]
32
- )
33
-
34
- class << self
35
- extend T::Sig
36
-
37
- # This method is responsible for building the name of the instantiated concrete type
38
- # and cloning the given constant so that we can return a type that is the same
39
- # as the current type but is a different instance and has a different name method.
40
- #
41
- # We cache those cloned instances by their name in `@generic_instances`, so that
42
- # we don't keep instantiating a new type every single time it is referenced.
43
- # For example, `[Foo[Integer], Foo[Integer], Foo[Integer], Foo[String]]` will only
44
- # result in 2 clones (1 for `Foo[Integer]` and another for `Foo[String]`) and
45
- # 2 hash lookups (for the other two `Foo[Integer]`s).
46
- #
47
- # This method returns the created or cached clone of the constant.
48
- sig { params(constant: T.untyped, types: T.untyped).returns(Module) }
49
- def register_type(constant, types)
50
- # Build the name of the instantiated generic type,
51
- # something like `"Foo[X, Y, Z]"`
52
- type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ")
53
- name = "#{Reflection.name_of(constant)}[#{type_list}]"
54
-
55
- # Create a generic type with an overridden `name`
56
- # method that returns the name we constructed above.
57
- #
58
- # Also, we try to memoize the generic type based on the name, so that
59
- # we don't have to keep recreating them all the time.
60
- @generic_instances[name] ||= create_generic_type(constant, name)
61
- end
62
-
63
- sig { params(constant: Module).returns(T.nilable(T::Hash[TypeVariable, String])) }
64
- def lookup_type_variables(constant)
65
- @type_variables[constant]
66
- end
67
-
68
- # This method is called from intercepted calls to `type_member` and `type_template`.
69
- # We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
70
- # instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
71
- #
72
- # This method creates a `String` with that data and stores it in the
73
- # `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
74
- #
75
- # Finally, the original `type_variable` is returned from this method, so that the caller
76
- # can return it from the original methods as well.
77
- sig do
78
- params(
79
- constant: T.untyped,
80
- type_variable: TypeVariable,
81
- ).void
82
- end
83
- def register_type_variable(constant, type_variable)
84
- type_variables = lookup_or_initialize_type_variables(constant)
85
-
86
- type_variables[type_variable] = type_variable.serialize
87
- end
88
-
89
- private
90
-
91
- sig { params(constant: Module, name: String).returns(Module) }
92
- def create_generic_type(constant, name)
93
- generic_type = case constant
94
- when Class
95
- # For classes, we want to create a subclass, so that an instance of
96
- # the generic class `Foo[Bar]` is still a `Foo`. That is:
97
- # `Foo[Bar].new.is_a?(Foo)` should be true, which isn't the case
98
- # if we just clone the class. But subclassing works just fine.
99
- create_safe_subclass(constant)
100
- else
101
- # This can only be a module and it is fine to just clone modules
102
- # since they can't have instances and will not have `is_a?` relationships.
103
- # Moreover, we never `include`/`extend` any generic modules into the
104
- # ancestor tree, so this doesn't become a problem with checking the
105
- # instance of a class being `is_a?` of a module type.
106
- constant.clone
107
- end
108
-
109
- # Let's set the `name` method to return the proper generic name
110
- generic_type.define_singleton_method(:name) { name }
111
-
112
- # Return the generic type we created
113
- generic_type
114
- end
115
-
116
- sig { params(constant: Class).returns(Class) }
117
- def create_safe_subclass(constant)
118
- # Lookup the "inherited" class method
119
- inherited_method = constant.method(:inherited)
120
- # and the module that defines it
121
- owner = inherited_method.owner
122
-
123
- # If no one has overriden the inherited method yet, just subclass
124
- return Class.new(constant) if Class == owner
125
-
126
- begin
127
- # Otherwise, some inherited method could be preventing us
128
- # from creating subclasses, so let's override it and rescue
129
- owner.send(:define_method, :inherited) do |s|
130
- inherited_method.call(s)
131
- rescue
132
- # Ignoring errors
133
- end
134
-
135
- # return a subclass
136
- Class.new(constant)
137
- ensure
138
- # Reinstate the original inherited method back.
139
- owner.send(:define_method, :inherited, inherited_method)
140
- end
141
- end
142
-
143
- sig { params(constant: Module).returns(T::Hash[TypeVariable, String]) }
144
- def lookup_or_initialize_type_variables(constant)
145
- @type_variables[constant] ||= {}.compare_by_identity
146
- end
147
- end
148
- end
149
- end
@@ -1,98 +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 ["T.nilable(#{getter_type})", "T.nilable(#{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 = "T.nilable(#{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(column_type: Object).returns(String) }
67
- def handle_unknown_type(column_type)
68
- return "T.untyped" unless ActiveModel::Type::Value === column_type
69
-
70
- lookup_return_type_of_method(column_type, :deserialize) ||
71
- lookup_return_type_of_method(column_type, :cast) ||
72
- lookup_arg_type_of_method(column_type, :serialize) ||
73
- "T.untyped"
74
- end
75
-
76
- sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
77
- def lookup_return_type_of_method(column_type, method)
78
- signature = T::Private::Methods.signature_for_method(column_type.method(method))
79
- return unless signature
80
-
81
- return_type = signature.return_type
82
- return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
83
-
84
- return_type.to_s
85
- end
86
-
87
- sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
88
- def lookup_arg_type_of_method(column_type, method)
89
- signature = T::Private::Methods.signature_for_method(column_type.method(method))
90
- return unless signature
91
-
92
- # Arg types is an array [name, type] entries, so we desctructure the type of
93
- # first argument to get the first argument type
94
- _, first_argument_type = signature.arg_types.first
95
-
96
- first_argument_type.to_s
97
- end
98
- end
@@ -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