tapioca 0.6.4 → 0.7.2

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