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
@@ -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,12 @@ 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
+ FIELD_RE = /^[a-z_][a-zA-Z0-9_]*$/
76
+
77
+ sig { override.void }
78
+ def decorate
75
79
  root.create_path(constant) do |klass|
76
80
  if constant == Google::Protobuf::RepeatedField
77
81
  create_type_members(klass, "Elem")
@@ -86,13 +90,21 @@ module Tapioca
86
90
  create_kw_opt_param(field.name, type: field.init_type, default: field.default)
87
91
  end
88
92
 
89
- klass.create_method("initialize", parameters: parameters, return_type: "void")
93
+ if fields.all? { |field| FIELD_RE.match?(field.name) }
94
+ klass.create_method("initialize", parameters: parameters, return_type: "void")
95
+ else
96
+ # One of the fields has an incorrect name for a named parameter so creating the default initialize for
97
+ # it would create a RBI with a syntax error.
98
+ # The workaround is to create an initialize that takes a **kwargs instead.
99
+ kwargs_parameter = create_kw_rest_param("fields", type: "T.untyped")
100
+ klass.create_method("initialize", parameters: [kwargs_parameter], return_type: "void")
101
+ end
90
102
  end
91
103
  end
92
104
  end
93
105
 
94
106
  sig { override.returns(T::Enumerable[Module]) }
95
- def gather_constants
107
+ def self.gather_constants
96
108
  marker = Google::Protobuf::MessageExts::ClassMethods
97
109
  results = T.cast(ObjectSpace.each_object(marker).to_a, T::Array[Module])
98
110
  results.any? ? results + [Google::Protobuf::RepeatedField, Google::Protobuf::Map] : []
@@ -105,7 +117,7 @@ module Tapioca
105
117
  klass.create_extend("T::Generic")
106
118
 
107
119
  names.each do |name|
108
- klass.create_type_member(name)
120
+ klass.create_type_variable(name, type: "type_member")
109
121
  end
110
122
  end
111
123
 
@@ -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,9 @@ 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
112
+ return [] unless Rails.application
113
+
110
114
  Object.const_set(:GeneratedUrlHelpersModule, Rails.application.routes.named_routes.url_helpers_module)
111
115
  Object.const_set(:GeneratedPathHelpersModule, Rails.application.routes.named_routes.path_helpers_module)
112
116
 
@@ -140,8 +144,8 @@ module Tapioca
140
144
  end
141
145
  end
142
146
 
143
- sig { params(mod: RBI::Scope, constant: Module, helper_module: Module).void }
144
- def create_mixins_for(mod, constant, helper_module)
147
+ sig { params(mod: RBI::Scope, helper_module: Module).void }
148
+ def create_mixins_for(mod, helper_module)
145
149
  include_helper = constant.ancestors.include?(helper_module) || NON_DISCOVERABLE_INCLUDERS.include?(constant)
146
150
  extend_helper = constant.singleton_class.ancestors.include?(helper_module)
147
151
 
@@ -150,7 +154,7 @@ module Tapioca
150
154
  end
151
155
 
152
156
  sig { params(mod: Module, helper: Module).returns(T::Boolean) }
153
- def includes_helper?(mod, helper)
157
+ private_class_method def self.includes_helper?(mod, helper)
154
158
  superclass_ancestors = []
155
159
 
156
160
  if Class === mod
@@ -158,7 +162,8 @@ module Tapioca
158
162
  superclass_ancestors = ancestors_of(superclass) if superclass
159
163
  end
160
164
 
161
- (ancestors_of(mod) - superclass_ancestors).any? { |ancestor| helper == ancestor }
165
+ ancestors = Set.new.compare_by_identity.merge(ancestors_of(mod)).subtract(superclass_ancestors)
166
+ ancestors.any? { |ancestor| helper == ancestor }
162
167
  end
163
168
  end
164
169
  end
@@ -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