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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -2
  3. data/README.md +27 -15
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +65 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +16 -9
  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 +86 -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 +13 -11
  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 +28 -34
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +18 -16
  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 +12 -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 +11 -16
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +14 -10
  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 +11 -9
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +23 -22
  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 +22 -10
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +12 -13
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +11 -9
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +20 -15
  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 +6 -3
  46. data/lib/tapioca/dsl/pipeline.rb +169 -0
  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/helpers/cli_helper.rb +7 -0
  64. data/lib/tapioca/helpers/config_helper.rb +5 -8
  65. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  66. data/lib/tapioca/helpers/signatures_helper.rb +17 -0
  67. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  68. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  69. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  70. data/lib/tapioca/helpers/test/template.rb +13 -2
  71. data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
  72. data/lib/tapioca/internal.rb +18 -10
  73. data/lib/tapioca/rbi_ext/model.rb +14 -50
  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 +168 -0
  77. data/lib/tapioca/runtime/loader.rb +123 -0
  78. data/lib/tapioca/runtime/reflection.rb +157 -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 +69 -34
  85. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  86. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  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 +83 -62
  92. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  93. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  94. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  95. data/lib/tapioca/compilers/dsl_compiler.rb +0 -134
  96. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  97. data/lib/tapioca/compilers/sorbet.rb +0 -59
  98. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  99. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  100. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  101. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  102. data/lib/tapioca/generators/todo.rb +0 -76
  103. data/lib/tapioca/generators.rb +0 -9
  104. data/lib/tapioca/generic_type_registry.rb +0 -164
  105. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  106. data/lib/tapioca/loader.rb +0 -119
  107. data/lib/tapioca/reflection.rb +0 -151
  108. data/lib/tapioca/trackers/autoload.rb +0 -70
  109. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  110. 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,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
@@ -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