tapioca 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) 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 +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 +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 +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 +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 +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 +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 +10 -8
  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 +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/helpers/cli_helper.rb +7 -0
  64. data/lib/tapioca/helpers/config_helper.rb +5 -8
  65. data/lib/tapioca/helpers/rbi_helper.rb +17 -0
  66. data/lib/tapioca/helpers/shims_helper.rb +87 -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/internal.rb +17 -10
  72. data/lib/tapioca/rbi_ext/model.rb +2 -48
  73. data/lib/tapioca/rbi_formatter.rb +37 -0
  74. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  75. data/lib/tapioca/runtime/generic_type_registry.rb +166 -0
  76. data/lib/tapioca/runtime/loader.rb +123 -0
  77. data/lib/tapioca/runtime/reflection.rb +153 -0
  78. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  79. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  80. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  81. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  82. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  83. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +33 -15
  84. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  85. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  86. data/lib/tapioca/static/symbol_loader.rb +83 -0
  87. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  88. data/lib/tapioca/version.rb +1 -1
  89. data/lib/tapioca.rb +2 -7
  90. metadata +80 -60
  91. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  92. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  93. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  94. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  95. data/lib/tapioca/compilers/sorbet.rb +0 -59
  96. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  97. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  98. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  99. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  100. data/lib/tapioca/generators/todo.rb +0 -76
  101. data/lib/tapioca/generators.rb +0 -9
  102. data/lib/tapioca/generic_type_registry.rb +0 -164
  103. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  104. data/lib/tapioca/loader.rb +0 -119
  105. data/lib/tapioca/reflection.rb +0 -151
  106. data/lib/tapioca/trackers/autoload.rb +0 -70
  107. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  108. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -8,9 +8,9 @@ rescue LoadError
8
8
  end
9
9
 
10
10
  module Tapioca
11
- module Compilers
12
- module Dsl
13
- # `Tapioca::Compilers::Dsl::MixedInClassAttributes` generates RBI files for modules that dynamically use
11
+ module Dsl
12
+ module Compilers
13
+ # `Tapioca::Dsl::Compilers::MixedInClassAttributes` generates RBI files for modules that dynamically use
14
14
  # `class_attribute` on classes.
15
15
  #
16
16
  # For example, given the following concern
@@ -25,7 +25,7 @@ module Tapioca
25
25
  # end
26
26
  # ~~~
27
27
  #
28
- # this generator will produce the RBI file `taggeable.rbi` with the following content:
28
+ # this compiler will produce the RBI file `taggeable.rbi` with the following content:
29
29
  #
30
30
  # ~~~rbi
31
31
  # # typed: strong
@@ -48,12 +48,14 @@ module Tapioca
48
48
  # end
49
49
  # end
50
50
  # ~~~
51
- class MixedInClassAttributes < Base
51
+ class MixedInClassAttributes < Compiler
52
52
  extend T::Sig
53
53
 
54
- sig { override.params(root: RBI::Tree, constant: Module).void }
55
- def decorate(root, constant)
56
- mixin_compiler = DynamicMixinCompiler.new(constant)
54
+ ConstantType = type_member(fixed: Module)
55
+
56
+ sig { override.void }
57
+ def decorate
58
+ mixin_compiler = Runtime::DynamicMixinCompiler.new(constant)
57
59
  return if mixin_compiler.empty_attributes?
58
60
 
59
61
  root.create_path(constant) do |mod|
@@ -62,10 +64,10 @@ module Tapioca
62
64
  end
63
65
 
64
66
  sig { override.returns(T::Enumerable[Module]) }
65
- def gather_constants
67
+ def self.gather_constants
66
68
  # Select all non-anonymous modules that have overridden Module.included
67
69
  all_modules.select do |mod|
68
- !mod.is_a?(Class) && name_of(mod) && Tapioca::Reflection.method_of(mod, :included).owner != Module
70
+ !mod.is_a?(Class) && name_of(mod) && Runtime::Reflection.method_of(mod, :included).owner != Module
69
71
  end
70
72
  end
71
73
  end
@@ -8,9 +8,9 @@ rescue LoadError
8
8
  end
9
9
 
10
10
  module Tapioca
11
- module Compilers
12
- module Dsl
13
- # `Tapioca::Compilers::Dsl::Protobuf` decorates RBI files for subclasses of
11
+ module Dsl
12
+ module Compilers
13
+ # `Tapioca::Dsl::Compilers::Protobuf` decorates RBI files for subclasses of
14
14
  # [`Google::Protobuf::MessageExts`](https://github.com/protocolbuffers/protobuf/tree/master/ruby).
15
15
  #
16
16
  # For example, with the following "cart.rb" file:
@@ -28,7 +28,7 @@ module Tapioca
28
28
  # end
29
29
  # ~~~
30
30
  #
31
- # this generator will produce the RBI file `cart.rbi` with the following content:
31
+ # this compiler will produce the RBI file `cart.rbi` with the following content:
32
32
  #
33
33
  # ~~~rbi
34
34
  # # cart.rbi
@@ -60,7 +60,7 @@ module Tapioca
60
60
  # def number_value=(value); end
61
61
  # end
62
62
  # ~~~
63
- class Protobuf < Base
63
+ class Protobuf < Compiler
64
64
  class Field < T::Struct
65
65
  prop :name, String
66
66
  prop :type, String
@@ -70,8 +70,10 @@ module Tapioca
70
70
 
71
71
  extend T::Sig
72
72
 
73
- sig { override.params(root: RBI::Tree, constant: Module).void }
74
- def decorate(root, constant)
73
+ ConstantType = type_member(fixed: Module)
74
+
75
+ sig { override.void }
76
+ def decorate
75
77
  root.create_path(constant) do |klass|
76
78
  if constant == Google::Protobuf::RepeatedField
77
79
  create_type_members(klass, "Elem")
@@ -92,7 +94,7 @@ module Tapioca
92
94
  end
93
95
 
94
96
  sig { override.returns(T::Enumerable[Module]) }
95
- def gather_constants
97
+ def self.gather_constants
96
98
  marker = Google::Protobuf::MessageExts::ClassMethods
97
99
  results = T.cast(ObjectSpace.each_object(marker).to_a, T::Array[Module])
98
100
  results.any? ? results + [Google::Protobuf::RepeatedField, Google::Protobuf::Map] : []
@@ -9,9 +9,9 @@ rescue LoadError
9
9
  end
10
10
 
11
11
  module Tapioca
12
- module Compilers
13
- module Dsl
14
- # `Tapioca::Compilers::Dsl::RailsGenerators` generates RBI files for Rails generators
12
+ module Dsl
13
+ module Compilers
14
+ # `Tapioca::Dsl::Compilers::RailsGenerators` generates RBI files for Rails generators
15
15
  #
16
16
  # For example, with the following generator:
17
17
  #
@@ -38,7 +38,7 @@ module Tapioca
38
38
  # def skip_comments; end
39
39
  # end
40
40
  # ~~~
41
- class RailsGenerators < Base
41
+ class RailsGenerators < Compiler
42
42
  extend T::Sig
43
43
 
44
44
  BUILT_IN_MATCHER = T.let(
@@ -46,9 +46,11 @@ module Tapioca
46
46
  Regexp
47
47
  )
48
48
 
49
- sig { override.params(root: RBI::Tree, constant: T.class_of(::Rails::Generators::Base)).void }
50
- def decorate(root, constant)
51
- base_class = base_class_for(constant)
49
+ ConstantType = type_member(fixed: T.class_of(::Rails::Generators::Base))
50
+
51
+ sig { override.void }
52
+ def decorate
53
+ base_class = base_class_of_constant
52
54
  arguments = constant.arguments - base_class.arguments
53
55
  class_options = constant.class_options.reject do |name, option|
54
56
  base_class.class_options[name] == option
@@ -63,7 +65,7 @@ module Tapioca
63
65
  end
64
66
 
65
67
  sig { override.returns(T::Enumerable[Module]) }
66
- def gather_constants
68
+ def self.gather_constants
67
69
  all_classes.select do |const|
68
70
  name = qualified_name_of(const)
69
71
 
@@ -84,11 +86,8 @@ module Tapioca
84
86
  )
85
87
  end
86
88
 
87
- sig do
88
- params(constant: T.class_of(::Rails::Generators::Base))
89
- .returns(T.class_of(::Rails::Generators::Base))
90
- end
91
- def base_class_for(constant)
89
+ sig { returns(T.class_of(::Rails::Generators::Base)) }
90
+ def base_class_of_constant
92
91
  ancestor = inherited_ancestors_of(constant).find do |klass|
93
92
  qualified_name_of(klass)&.match?(BUILT_IN_MATCHER)
94
93
  end
@@ -8,9 +8,9 @@ rescue LoadError
8
8
  end
9
9
 
10
10
  module Tapioca
11
- module Compilers
12
- module Dsl
13
- # `Tapioca::Compilers::Dsl::SidekiqWorker` generates RBI files classes that include
11
+ module Dsl
12
+ module Compilers
13
+ # `Tapioca::Dsl::Compilers::SidekiqWorker` generates RBI files classes that include
14
14
  # [`Sidekiq::Worker`](https://github.com/mperham/sidekiq/wiki/Getting-Started).
15
15
  #
16
16
  # For example, with the following class that includes `Sidekiq::Worker`:
@@ -24,7 +24,7 @@ module Tapioca
24
24
  # end
25
25
  # ~~~
26
26
  #
27
- # this generator will produce the RBI file `notifier_worker.rbi` with the following content:
27
+ # this compiler will produce the RBI file `notifier_worker.rbi` with the following content:
28
28
  #
29
29
  # ~~~rbi
30
30
  # # notifier_worker.rbi
@@ -40,11 +40,13 @@ module Tapioca
40
40
  # def self.perform_in(interval, customer_id); end
41
41
  # end
42
42
  # ~~~
43
- class SidekiqWorker < Base
43
+ class SidekiqWorker < Compiler
44
44
  extend T::Sig
45
45
 
46
- sig { override.params(root: RBI::Tree, constant: T.class_of(::Sidekiq::Worker)).void }
47
- def decorate(root, constant)
46
+ ConstantType = type_member(fixed: T.class_of(::Sidekiq::Worker))
47
+
48
+ sig { override.void }
49
+ def decorate
48
50
  return unless constant.instance_methods.include?(:perform)
49
51
 
50
52
  root.create_path(constant) do |worker|
@@ -64,14 +66,14 @@ module Tapioca
64
66
  *async_params,
65
67
  ]
66
68
 
67
- generate_perform_method(constant, worker, "perform_async", async_params)
68
- generate_perform_method(constant, worker, "perform_at", at_params)
69
- generate_perform_method(constant, worker, "perform_in", in_params)
69
+ generate_perform_method(worker, "perform_async", async_params)
70
+ generate_perform_method(worker, "perform_at", at_params)
71
+ generate_perform_method(worker, "perform_in", in_params)
70
72
  end
71
73
  end
72
74
 
73
75
  sig { override.returns(T::Enumerable[Module]) }
74
- def gather_constants
76
+ def self.gather_constants
75
77
  all_classes.select { |c| c < Sidekiq::Worker }
76
78
  end
77
79
 
@@ -79,13 +81,12 @@ module Tapioca
79
81
 
80
82
  sig do
81
83
  params(
82
- constant: T.class_of(::Sidekiq::Worker),
83
84
  worker: RBI::Scope,
84
85
  method_name: String,
85
86
  parameters: T::Array[RBI::TypedParam]
86
87
  ).void
87
88
  end
88
- def generate_perform_method(constant, worker, method_name, parameters)
89
+ def generate_perform_method(worker, method_name, parameters)
89
90
  if constant.method(method_name.to_sym).owner == Sidekiq::Worker::ClassMethods
90
91
  worker.create_method(method_name, parameters: parameters, return_type: "String", class_method: true)
91
92
  end
@@ -5,14 +5,14 @@ begin
5
5
  require "smart_properties"
6
6
  rescue LoadError
7
7
  # means SmartProperties is not installed,
8
- # so let's not even define the generator.
8
+ # so let's not even define the compiler.
9
9
  return
10
10
  end
11
11
 
12
12
  module Tapioca
13
- module Compilers
14
- module Dsl
15
- # `Tapioca::Compilers::Dsl::SmartProperties` generates RBI files for classes that include
13
+ module Dsl
14
+ module Compilers
15
+ # `Tapioca::Dsl::Compilers::SmartProperties` generates RBI files for classes that include
16
16
  # [`SmartProperties`](https://github.com/t6d/smart_properties).
17
17
  #
18
18
  # For example, with the following class that includes `SmartProperties`:
@@ -29,7 +29,7 @@ module Tapioca
29
29
  # end
30
30
  # ~~~
31
31
  #
32
- # this generator will produce the RBI file `post.rbi` with the following content:
32
+ # this compiler will produce the RBI file `post.rbi` with the following content:
33
33
  #
34
34
  # ~~~rbi
35
35
  # # post.rbi
@@ -60,11 +60,13 @@ module Tapioca
60
60
  # def enabled=(enabled); end
61
61
  # end
62
62
  # ~~~
63
- class SmartProperties < Base
63
+ class SmartProperties < Compiler
64
64
  extend T::Sig
65
65
 
66
- sig { override.params(root: RBI::Tree, constant: T.class_of(::SmartProperties)).void }
67
- def decorate(root, constant)
66
+ ConstantType = type_member(fixed: T.class_of(::SmartProperties))
67
+
68
+ sig { override.void }
69
+ def decorate
68
70
  properties = T.let(
69
71
  T.unsafe(constant).properties,
70
72
  ::SmartProperties::PropertyCollection
@@ -84,7 +86,7 @@ module Tapioca
84
86
  end
85
87
 
86
88
  sig { override.returns(T::Enumerable[Module]) }
87
- def gather_constants
89
+ def self.gather_constants
88
90
  all_modules.select do |c|
89
91
  name_of(c) &&
90
92
  c != ::SmartProperties::Validations::Ancestor &&
@@ -5,15 +5,15 @@ begin
5
5
  require "state_machines"
6
6
  rescue LoadError
7
7
  # means StateMachines is not installed,
8
- # so let's not even define the generator.
8
+ # so let's not even define the compiler.
9
9
  return
10
10
  end
11
11
 
12
12
  module Tapioca
13
- module Compilers
14
- module Dsl
15
- # `Tapioca::Compilers::Dsl::StateMachines` generates RBI files for classes that setup a
16
- # [`state_machine`](https://github.com/state-machines/state_machines). The generator also
13
+ module Dsl
14
+ module Compilers
15
+ # `Tapioca::Dsl::Compilers::StateMachines` generates RBI files for classes that setup a
16
+ # [`state_machine`](https://github.com/state-machines/state_machines). The compiler also
17
17
  # processes the extra methods generated by
18
18
  # [StateMachines Active Record](https://github.com/state-machines/state_machines-activerecord)
19
19
  # and [StateMachines Active Model](https://github.com/state-machines/state_machines-activemodel)
@@ -38,7 +38,7 @@ module Tapioca
38
38
  # end
39
39
  # ~~~
40
40
  #
41
- # this generator will produce the RBI file `vehicle.rbi` with the following content:
41
+ # this compiler will produce the RBI file `vehicle.rbi` with the following content:
42
42
  #
43
43
  # ~~~rbi
44
44
  # # vehicle.rbi
@@ -115,11 +115,13 @@ module Tapioca
115
115
  # end
116
116
  # end
117
117
  # ~~~
118
- class StateMachines < Base
118
+ class StateMachines < Compiler
119
119
  extend T::Sig
120
120
 
121
- sig { override.params(root: RBI::Tree, constant: ::StateMachines::ClassMethods).void }
122
- def decorate(root, constant)
121
+ ConstantType = type_member(fixed: T.all(Module, ::StateMachines::ClassMethods))
122
+
123
+ sig { override.void }
124
+ def decorate
123
125
  return if constant.state_machines.empty?
124
126
 
125
127
  root.create_path(T.unsafe(constant)) do |klass|
@@ -159,7 +161,7 @@ module Tapioca
159
161
  end
160
162
 
161
163
  sig { override.returns(T::Enumerable[Module]) }
162
- def gather_constants
164
+ def self.gather_constants
163
165
  all_classes.select { |mod| mod < ::StateMachines::InstanceMethods }
164
166
  end
165
167
 
@@ -10,9 +10,9 @@ rescue LoadError
10
10
  end
11
11
 
12
12
  module Tapioca
13
- module Compilers
14
- module Dsl
15
- # `Tapioca::Compilers::Dsl::UrlHelpers` generates RBI files for classes that include or extend
13
+ module Dsl
14
+ module Compilers
15
+ # `Tapioca::Dsl::Compilers::UrlHelpers` generates RBI files for classes that include or extend
16
16
  # [`Rails.application.routes.url_helpers`](https://api.rubyonrails.org/v5.1.7/classes/ActionDispatch/Routing/UrlFor.html#module-ActionDispatch::Routing::UrlFor-label-URL+generation+for+named+routes).
17
17
  #
18
18
  # For example, with the following setup:
@@ -32,13 +32,13 @@ module Tapioca
32
32
  # # Use `T.unsafe` so that Sorbet does not complain about a dynamic
33
33
  # # module being included. This allows the `include` to happen properly
34
34
  # # at runtime but Sorbet won't see the include. However, since this
35
- # # generator will generate the proper RBI files for the include,
35
+ # # compiler will generate the proper RBI files for the include,
36
36
  # # static type checking will work as expected.
37
37
  # T.unsafe(self).include Rails.application.routes.url_helpers
38
38
  # end
39
39
  # ~~~
40
40
  #
41
- # this generator will produce the following RBI files:
41
+ # this compiler will produce the following RBI files:
42
42
  #
43
43
  # ~~~rbi
44
44
  # # generated_path_helpers_module.rbi
@@ -84,18 +84,20 @@ module Tapioca
84
84
  # include GeneratedUrlHelpersModule
85
85
  # end
86
86
  # ~~~
87
- class UrlHelpers < Base
87
+ class UrlHelpers < Compiler
88
88
  extend T::Sig
89
89
 
90
- sig { override.params(root: RBI::Tree, constant: Module).void }
91
- def decorate(root, constant)
90
+ ConstantType = type_member(fixed: Module)
91
+
92
+ sig { override.void }
93
+ def decorate
92
94
  case constant
93
95
  when GeneratedPathHelpersModule.singleton_class, GeneratedUrlHelpersModule.singleton_class
94
96
  generate_module_for(root, constant)
95
97
  else
96
98
  root.create_path(constant) do |mod|
97
- create_mixins_for(mod, constant, GeneratedUrlHelpersModule)
98
- create_mixins_for(mod, constant, GeneratedPathHelpersModule)
99
+ create_mixins_for(mod, GeneratedUrlHelpersModule)
100
+ create_mixins_for(mod, GeneratedPathHelpersModule)
99
101
  end
100
102
  end
101
103
  end
@@ -106,7 +108,7 @@ module Tapioca
106
108
  ], T::Array[Module])
107
109
 
108
110
  sig { override.returns(T::Enumerable[Module]) }
109
- def gather_constants
111
+ def self.gather_constants
110
112
  Object.const_set(:GeneratedUrlHelpersModule, Rails.application.routes.named_routes.url_helpers_module)
111
113
  Object.const_set(:GeneratedPathHelpersModule, Rails.application.routes.named_routes.path_helpers_module)
112
114
 
@@ -140,8 +142,8 @@ module Tapioca
140
142
  end
141
143
  end
142
144
 
143
- sig { params(mod: RBI::Scope, constant: Module, helper_module: Module).void }
144
- def create_mixins_for(mod, constant, helper_module)
145
+ sig { params(mod: RBI::Scope, helper_module: Module).void }
146
+ def create_mixins_for(mod, helper_module)
145
147
  include_helper = constant.ancestors.include?(helper_module) || NON_DISCOVERABLE_INCLUDERS.include?(constant)
146
148
  extend_helper = constant.singleton_class.ancestors.include?(helper_module)
147
149
 
@@ -150,7 +152,7 @@ module Tapioca
150
152
  end
151
153
 
152
154
  sig { params(mod: Module, helper: Module).returns(T::Boolean) }
153
- def includes_helper?(mod, helper)
155
+ private_class_method def self.includes_helper?(mod, helper)
154
156
  superclass_ancestors = []
155
157
 
156
158
  if Class === mod
@@ -0,0 +1,31 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca/rbi_ext/model"
5
+ require "tapioca/dsl/helpers/param_helper"
6
+ require "tapioca/dsl/pipeline"
7
+
8
+ module Tapioca
9
+ module Dsl
10
+ module Compilers
11
+ DIRECTORY = T.let(
12
+ File.expand_path("compilers", __dir__),
13
+ String
14
+ )
15
+
16
+ # DSL compilers are either built-in to Tapioca and live under the
17
+ # `Tapioca::Dsl::Compilers` namespace (i.e. this namespace), and
18
+ # can be referred to by just using the class name, or they live in
19
+ # a different namespace and can only be referred to using their fully
20
+ # qualified name. This constant encapsulates that dual lookup when
21
+ # a compiler needs to be resolved by name.
22
+ NAMESPACES = T.let(
23
+ [
24
+ "#{name}::", # compilers in this namespace
25
+ "::", # compilers that need to be fully namespaced
26
+ ],
27
+ T::Array[String]
28
+ )
29
+ end
30
+ end
31
+ end
@@ -8,8 +8,8 @@ rescue LoadError
8
8
  end
9
9
 
10
10
  module Tapioca
11
- module Compilers
12
- module Dsl
11
+ module Dsl
12
+ module Compilers
13
13
  module Extensions
14
14
  module FrozenRecord
15
15
  attr_reader :__tapioca_scope_names
@@ -0,0 +1,114 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Dsl
6
+ module Helpers
7
+ class ActiveRecordColumnTypeHelper
8
+ extend T::Sig
9
+
10
+ sig { params(constant: T.class_of(ActiveRecord::Base)).void }
11
+ def initialize(constant)
12
+ @constant = constant
13
+ end
14
+
15
+ sig { params(column_name: String).returns([String, String]) }
16
+ def type_for(column_name)
17
+ return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(@constant)
18
+
19
+ column_type = @constant.attribute_types[column_name]
20
+
21
+ getter_type =
22
+ case column_type
23
+ when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
24
+ "::Money"
25
+ when ActiveRecord::Type::Integer
26
+ "::Integer"
27
+ when ActiveRecord::Type::String
28
+ "::String"
29
+ when ActiveRecord::Type::Date
30
+ "::Date"
31
+ when ActiveRecord::Type::Decimal
32
+ "::BigDecimal"
33
+ when ActiveRecord::Type::Float
34
+ "::Float"
35
+ when ActiveRecord::Type::Boolean
36
+ "T::Boolean"
37
+ when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
38
+ "::DateTime"
39
+ when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
40
+ "::ActiveSupport::TimeWithZone"
41
+ else
42
+ handle_unknown_type(column_type)
43
+ end
44
+
45
+ column = @constant.columns_hash[column_name]
46
+ setter_type = getter_type
47
+
48
+ if column&.null
49
+ return [as_nilable_type(getter_type), as_nilable_type(setter_type)]
50
+ end
51
+
52
+ if column_name == @constant.primary_key ||
53
+ column_name == "created_at" ||
54
+ column_name == "updated_at"
55
+ getter_type = as_nilable_type(getter_type)
56
+ end
57
+
58
+ [getter_type, setter_type]
59
+ end
60
+
61
+ private
62
+
63
+ sig { params(constant: Module).returns(T::Boolean) }
64
+ def do_not_generate_strong_types?(constant)
65
+ Object.const_defined?(:StrongTypeGeneration) &&
66
+ !(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
67
+ end
68
+
69
+ sig { params(type: String).returns(String) }
70
+ def as_nilable_type(type)
71
+ if type.start_with?("T.nilable(") || type == "T.untyped"
72
+ type
73
+ else
74
+ "T.nilable(#{type})"
75
+ end
76
+ end
77
+
78
+ sig { params(column_type: Object).returns(String) }
79
+ def handle_unknown_type(column_type)
80
+ return "T.untyped" unless ActiveModel::Type::Value === column_type
81
+ return "T.untyped" if Runtime::GenericTypeRegistry.generic_type_instance?(column_type)
82
+
83
+ lookup_return_type_of_method(column_type, :deserialize) ||
84
+ lookup_return_type_of_method(column_type, :cast) ||
85
+ lookup_arg_type_of_method(column_type, :serialize) ||
86
+ "T.untyped"
87
+ end
88
+
89
+ sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
90
+ def lookup_return_type_of_method(column_type, method)
91
+ signature = Runtime::Reflection.signature_of(column_type.method(method))
92
+ return unless signature
93
+
94
+ return_type = signature.return_type
95
+ return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
96
+
97
+ return_type.to_s
98
+ end
99
+
100
+ sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
101
+ def lookup_arg_type_of_method(column_type, method)
102
+ signature = Runtime::Reflection.signature_of(column_type.method(method))
103
+ return unless signature
104
+
105
+ # Arg types is an array [name, type] entries, so we desctructure the type of
106
+ # first argument to get the first argument type
107
+ _, first_argument_type = signature.arg_types.first
108
+
109
+ first_argument_type.to_s
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Dsl
6
+ module Helpers
7
+ module ActiveRecordConstantsHelper
8
+ extend T::Sig
9
+
10
+ ReflectionType = T.type_alias do
11
+ T.any(::ActiveRecord::Reflection::ThroughReflection, ::ActiveRecord::Reflection::AssociationReflection)
12
+ end
13
+
14
+ AttributeMethodsModuleName = T.let("GeneratedAttributeMethods", String)
15
+ AssociationMethodsModuleName = T.let("GeneratedAssociationMethods", String)
16
+
17
+ RelationMethodsModuleName = T.let("GeneratedRelationMethods", String)
18
+ AssociationRelationMethodsModuleName = T.let("GeneratedAssociationRelationMethods", String)
19
+ CommonRelationMethodsModuleName = T.let("CommonRelationMethods", String)
20
+
21
+ RelationClassName = T.let("PrivateRelation", String)
22
+ RelationWhereChainClassName = T.let("PrivateRelationWhereChain", String)
23
+ AssociationRelationClassName = T.let("PrivateAssociationRelation", String)
24
+ AssociationRelationWhereChainClassName = T.let("PrivateAssociationRelationWhereChain", String)
25
+ AssociationsCollectionProxyClassName = T.let("PrivateCollectionProxy", String)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,8 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- module Compilers
6
- module Dsl
5
+ module Dsl
6
+ module Helpers
7
7
  module ParamHelper
8
8
  extend T::Sig
9
9