tapioca 0.4.26 → 0.5.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -14
  3. data/README.md +2 -2
  4. data/Rakefile +5 -7
  5. data/exe/tapioca +2 -2
  6. data/lib/tapioca/cli.rb +256 -2
  7. data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
  8. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
  9. data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
  10. data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
  11. data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
  12. data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
  13. data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
  14. data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
  15. data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
  16. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
  17. data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
  18. data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
  19. data/lib/tapioca/compilers/dsl/active_support_concern.rb +108 -0
  20. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
  21. data/lib/tapioca/compilers/dsl/base.rb +96 -82
  22. data/lib/tapioca/compilers/dsl/config.rb +111 -0
  23. data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
  24. data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
  25. data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
  26. data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
  27. data/lib/tapioca/compilers/dsl/smart_properties.rb +19 -31
  28. data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
  29. data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
  30. data/lib/tapioca/compilers/dsl_compiler.rb +22 -38
  31. data/lib/tapioca/compilers/requires_compiler.rb +2 -2
  32. data/lib/tapioca/compilers/sorbet.rb +26 -5
  33. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +138 -153
  34. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
  35. data/lib/tapioca/compilers/todos_compiler.rb +1 -1
  36. data/lib/tapioca/config.rb +2 -0
  37. data/lib/tapioca/config_builder.rb +4 -2
  38. data/lib/tapioca/constant_locator.rb +6 -8
  39. data/lib/tapioca/gemfile.rb +2 -4
  40. data/lib/tapioca/generator.rb +124 -40
  41. data/lib/tapioca/generic_type_registry.rb +25 -98
  42. data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
  43. data/lib/tapioca/internal.rb +1 -9
  44. data/lib/tapioca/loader.rb +11 -31
  45. data/lib/tapioca/rbi_ext/model.rb +122 -0
  46. data/lib/tapioca/reflection.rb +131 -0
  47. data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
  48. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
  49. data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
  50. data/lib/tapioca/version.rb +1 -1
  51. data/lib/tapioca.rb +2 -0
  52. metadata +34 -22
  53. data/lib/tapioca/cli/main.rb +0 -146
  54. data/lib/tapioca/core_ext/class.rb +0 -28
  55. data/lib/tapioca/core_ext/string.rb +0 -18
  56. data/lib/tapioca/rbi/model.rb +0 -405
  57. data/lib/tapioca/rbi/printer.rb +0 -410
  58. data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
  59. data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
  60. data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
  61. data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -86
  62. data/lib/tapioca/rbi/visitor.rb +0 -21
@@ -0,0 +1,98 @@
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
@@ -4,22 +4,14 @@
4
4
  require "tapioca"
5
5
  require "tapioca/loader"
6
6
  require "tapioca/constant_locator"
7
- require "tapioca/generic_type_registry"
8
7
  require "tapioca/sorbet_ext/generic_name_patch"
9
8
  require "tapioca/sorbet_ext/fixed_hash_patch"
9
+ require "tapioca/generic_type_registry"
10
10
  require "tapioca/config"
11
11
  require "tapioca/config_builder"
12
12
  require "tapioca/generator"
13
13
  require "tapioca/cli"
14
- require "tapioca/cli/main"
15
14
  require "tapioca/gemfile"
16
- require "tapioca/rbi/model"
17
- require "tapioca/rbi/visitor"
18
- require "tapioca/rbi/rewriters/nest_singleton_methods"
19
- require "tapioca/rbi/rewriters/nest_non_public_methods"
20
- require "tapioca/rbi/rewriters/group_nodes"
21
- require "tapioca/rbi/rewriters/sort_nodes"
22
- require "tapioca/rbi/printer"
23
15
  require "tapioca/compilers/sorbet"
24
16
  require "tapioca/compilers/requires_compiler"
25
17
  require "tapioca/compilers/symbol_table_compiler"
@@ -5,13 +5,8 @@ module Tapioca
5
5
  class Loader
6
6
  extend(T::Sig)
7
7
 
8
- sig { params(gemfile: Tapioca::Gemfile).void }
9
- def initialize(gemfile)
10
- @gemfile = T.let(gemfile, Tapioca::Gemfile)
11
- end
12
-
13
- sig { params(initialize_file: T.nilable(String), require_file: T.nilable(String)).void }
14
- def load_bundle(initialize_file, require_file)
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)
15
10
  require_helper(initialize_file)
16
11
 
17
12
  gemfile.require_bundle
@@ -40,9 +35,6 @@ module Tapioca
40
35
 
41
36
  private
42
37
 
43
- sig { returns(Tapioca::Gemfile) }
44
- attr_reader :gemfile
45
-
46
38
  sig { params(file: T.nilable(String)).void }
47
39
  def require_helper(file)
48
40
  return unless file
@@ -54,18 +46,10 @@ module Tapioca
54
46
 
55
47
  sig { returns(T::Array[T.untyped]) }
56
48
  def rails_engines
57
- engines = []
58
-
59
- return engines unless Object.const_defined?("Rails::Engine")
60
-
61
- base = Object.const_get("Rails::Engine")
62
- ObjectSpace.each_object(base.singleton_class) do |k|
63
- k = T.cast(k, Class)
64
- next if k.singleton_class?
65
- engines.unshift(k) unless k == base
66
- end
49
+ return [] unless Object.const_defined?("Rails::Engine")
67
50
 
68
- engines.reject(&:abstract_railtie?)
51
+ # We can use `Class#descendants` here, since we know Rails is loaded
52
+ Object.const_get("Rails::Engine").descendants.reject(&:abstract_railtie?)
69
53
  end
70
54
 
71
55
  sig { params(path: String).void }
@@ -116,22 +100,18 @@ module Tapioca
116
100
 
117
101
  engine.config.eager_load_paths.each do |load_path|
118
102
  Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
119
- begin
120
- require(file)
121
- rescue LoadError, StandardError
122
- errored_files << file
123
- end
103
+ require(file)
104
+ rescue LoadError, StandardError
105
+ errored_files << file
124
106
  end
125
107
  end
126
108
 
127
109
  # Try files that have errored one more time
128
110
  # It might have been a load order problem
129
111
  errored_files.each do |file|
130
- begin
131
- require(file)
132
- rescue LoadError, StandardError
133
- nil
134
- end
112
+ require(file)
113
+ rescue LoadError, StandardError
114
+ nil
135
115
  end
136
116
  end
137
117
  end
@@ -0,0 +1,122 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rbi"
5
+
6
+ module RBI
7
+ class Tree
8
+ extend T::Sig
9
+
10
+ sig { params(constant: ::Module, block: T.nilable(T.proc.params(scope: Scope).void)).void }
11
+ def create_path(constant, &block)
12
+ constant_name = Tapioca::Reflection.name_of(constant)
13
+ raise "given constant does not have a name" unless constant_name
14
+
15
+ instance = ::Module.const_get(constant_name)
16
+ case instance
17
+ when ::Class
18
+ create_class(constant.to_s, &block)
19
+ when ::Module
20
+ create_module(constant.to_s, &block)
21
+ else
22
+ raise "unexpected type: #{constant_name} is a #{instance.class}"
23
+ end
24
+ end
25
+
26
+ sig { params(name: String, block: T.nilable(T.proc.params(scope: Scope).void)).void }
27
+ def create_module(name, &block)
28
+ node = create_node(RBI::Module.new(name))
29
+ block&.call(T.cast(node, RBI::Scope))
30
+ end
31
+
32
+ sig do
33
+ params(
34
+ name: String,
35
+ superclass_name: T.nilable(String),
36
+ block: T.nilable(T.proc.params(scope: RBI::Scope).void)
37
+ ).void
38
+ end
39
+ def create_class(name, superclass_name: nil, &block)
40
+ node = create_node(RBI::Class.new(name, superclass_name: superclass_name))
41
+ block&.call(T.cast(node, RBI::Scope))
42
+ end
43
+
44
+ sig { params(name: String, value: String).void }
45
+ def create_constant(name, value:)
46
+ create_node(RBI::Const.new(name, value))
47
+ end
48
+
49
+ sig { params(name: String).void }
50
+ def create_include(name)
51
+ create_node(RBI::Include.new(name))
52
+ end
53
+
54
+ sig { params(name: String).void }
55
+ def create_extend(name)
56
+ create_node(RBI::Extend.new(name))
57
+ end
58
+
59
+ sig { params(name: String).void }
60
+ def create_mixes_in_class_methods(name)
61
+ create_node(RBI::MixesInClassMethods.new(name))
62
+ end
63
+
64
+ sig { params(name: String, value: String).void }
65
+ def create_type_member(name, value: "type_member")
66
+ create_node(RBI::TypeMember.new(name, value))
67
+ end
68
+
69
+ sig do
70
+ params(
71
+ name: String,
72
+ parameters: T::Array[TypedParam],
73
+ return_type: String,
74
+ class_method: T::Boolean
75
+ ).void
76
+ end
77
+ def create_method(name, parameters: [], return_type: "T.untyped", class_method: false)
78
+ return unless valid_method_name?(name)
79
+
80
+ sig = RBI::Sig.new(return_type: return_type)
81
+ method = RBI::Method.new(name, sigs: [sig], is_singleton: class_method)
82
+ parameters.each do |param|
83
+ method << param.param
84
+ sig << RBI::SigParam.new(param.param.name, param.type)
85
+ end
86
+ self << method
87
+ end
88
+
89
+ private
90
+
91
+ SPECIAL_METHOD_NAMES = T.let(
92
+ ["!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^", "<", "<=", "=>", ">", ">=",
93
+ "==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"].freeze,
94
+ T::Array[String]
95
+ )
96
+
97
+ sig { params(name: String).returns(T::Boolean) }
98
+ def valid_method_name?(name)
99
+ return true if SPECIAL_METHOD_NAMES.include?(name)
100
+ !!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
101
+ end
102
+
103
+ sig { returns(T::Hash[String, RBI::Node]) }
104
+ def nodes_cache
105
+ T.must(@nodes_cache ||= T.let({}, T.nilable(T::Hash[String, Node])))
106
+ end
107
+
108
+ sig { params(node: RBI::Node).returns(RBI::Node) }
109
+ def create_node(node)
110
+ cached = nodes_cache[node.to_s]
111
+ return cached if cached
112
+ nodes_cache[node.to_s] = node
113
+ self << node
114
+ node
115
+ end
116
+ end
117
+
118
+ class TypedParam < T::Struct
119
+ const :param, RBI::Param
120
+ const :type, String
121
+ end
122
+ end
@@ -0,0 +1,131 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Reflection
6
+ extend T::Sig
7
+ extend self
8
+
9
+ CLASS_METHOD = T.let(Kernel.instance_method(:class), UnboundMethod)
10
+ CONSTANTS_METHOD = T.let(Module.instance_method(:constants), UnboundMethod)
11
+ NAME_METHOD = T.let(Module.instance_method(:name), UnboundMethod)
12
+ SINGLETON_CLASS_METHOD = T.let(Object.instance_method(:singleton_class), UnboundMethod)
13
+ ANCESTORS_METHOD = T.let(Module.instance_method(:ancestors), UnboundMethod)
14
+ SUPERCLASS_METHOD = T.let(Class.instance_method(:superclass), UnboundMethod)
15
+ OBJECT_ID_METHOD = T.let(BasicObject.instance_method(:__id__), UnboundMethod)
16
+ EQUAL_METHOD = T.let(BasicObject.instance_method(:equal?), UnboundMethod)
17
+ PUBLIC_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:public_instance_methods), UnboundMethod)
18
+ PROTECTED_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:protected_instance_methods), UnboundMethod)
19
+ PRIVATE_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:private_instance_methods), UnboundMethod)
20
+
21
+ sig { params(object: BasicObject).returns(Class).checked(:never) }
22
+ def class_of(object)
23
+ CLASS_METHOD.bind(object).call
24
+ end
25
+
26
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
27
+ def constants_of(constant)
28
+ CONSTANTS_METHOD.bind(constant).call(false)
29
+ end
30
+
31
+ sig { params(constant: Module).returns(T.nilable(String)) }
32
+ def name_of(constant)
33
+ NAME_METHOD.bind(constant).call
34
+ end
35
+
36
+ sig { params(constant: Module).returns(Class) }
37
+ def singleton_class_of(constant)
38
+ SINGLETON_CLASS_METHOD.bind(constant).call
39
+ end
40
+
41
+ sig { params(constant: Module).returns(T::Array[Module]) }
42
+ def ancestors_of(constant)
43
+ ANCESTORS_METHOD.bind(constant).call
44
+ end
45
+
46
+ sig { params(constant: Class).returns(T.nilable(Class)) }
47
+ def superclass_of(constant)
48
+ SUPERCLASS_METHOD.bind(constant).call
49
+ end
50
+
51
+ sig { params(object: BasicObject).returns(Integer).checked(:never) }
52
+ def object_id_of(object)
53
+ OBJECT_ID_METHOD.bind(object).call
54
+ end
55
+
56
+ sig { params(object: BasicObject, other: BasicObject).returns(T::Boolean).checked(:never) }
57
+ def are_equal?(object, other)
58
+ EQUAL_METHOD.bind(object).call(other)
59
+ end
60
+
61
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
62
+ def public_instance_methods_of(constant)
63
+ PUBLIC_INSTANCE_METHODS_METHOD.bind(constant).call
64
+ end
65
+
66
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
67
+ def protected_instance_methods_of(constant)
68
+ PROTECTED_INSTANCE_METHODS_METHOD.bind(constant).call
69
+ end
70
+
71
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
72
+ def private_instance_methods_of(constant)
73
+ PRIVATE_INSTANCE_METHODS_METHOD.bind(constant).call
74
+ end
75
+
76
+ sig { params(constant: Module).returns(T::Array[Module]) }
77
+ def inherited_ancestors_of(constant)
78
+ if Class === constant
79
+ ancestors_of(superclass_of(constant) || Object)
80
+ else
81
+ Module.ancestors
82
+ end
83
+ end
84
+
85
+ sig { params(constant: Module).returns(T.nilable(String)) }
86
+ def qualified_name_of(constant)
87
+ name = name_of(constant)
88
+ return if name.nil?
89
+
90
+ if name.start_with?("::")
91
+ name
92
+ else
93
+ "::#{name}"
94
+ end
95
+ end
96
+
97
+ sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
98
+ def signature_of(method)
99
+ T::Private::Methods.signature_for_method(method)
100
+ rescue LoadError, StandardError
101
+ nil
102
+ end
103
+
104
+ sig { params(type: T::Types::Base).returns(String) }
105
+ def name_of_type(type)
106
+ type.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
107
+ end
108
+
109
+ # Returns an array with all classes that are < than the supplied class.
110
+ #
111
+ # class C; end
112
+ # descendants_of(C) # => []
113
+ #
114
+ # class B < C; end
115
+ # descendants_of(C) # => [B]
116
+ #
117
+ # class A < B; end
118
+ # descendants_of(C) # => [B, A]
119
+ #
120
+ # class D < C; end
121
+ # descendants_of(C) # => [B, A, D]
122
+ sig { type_parameters(:U).params(klass: T.type_parameter(:U)).returns(T::Array[T.type_parameter(:U)]) }
123
+ def descendants_of(klass)
124
+ result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
125
+ T.cast(k, Module).singleton_class? || T.unsafe(k) == klass
126
+ end
127
+
128
+ T.unsafe(result)
129
+ end
130
+ end
131
+ end