tapioca 0.15.1 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12203d783fb506aff6b357dc3dc02fd7f9d9e7147fd2fb715329a5311ff96bf6
4
- data.tar.gz: 0ac885bad578baec1701f6b2f46e48ca5f12e4c5d8fd9840d63195291400a430
3
+ metadata.gz: 3a448cd511151879e4eef761b28cc8f5b1904f0d57e560a38af09bdda9689f98
4
+ data.tar.gz: 563c5e68f866444da629a4b206fed22b8a27531c36c0378d669886aa2a704eaf
5
5
  SHA512:
6
- metadata.gz: f42230c7ca6b96bb6ed299faa2b4c8d8f87ae44244a6501006c55b22c6307b9a0b777cdbb8d713f6ee3320d44502428c17ddf86dd85929be842433557d3e3793
7
- data.tar.gz: df6f57d7c1446e70bd759bc84c5fa06f5985e01783be8d8f7655e06e3045c5de17cc1e931d4f0b5d78bc19f867de61dc228c77eb2f4df325038e09d908e2efd0
6
+ metadata.gz: c847afeb5c173ad8b0c46a0f2fd214336150124fd96f0c729029ce492a5f4aea4bf5d26adfd64cf386486f22342b21f502cdbc52c68ca544a65c4bf6777dacee
7
+ data.tar.gz: e25a62343cc4522d37e9508b4215f6efb9115b93e1e9ba38c71edf26da025126732b4762b834304d0e439c741d581f238e29e22dded9fcbf0415256041f8f849
@@ -49,7 +49,7 @@ module Tapioca
49
49
  sig { returns(T::Enumerable[T::Class[T.anything]]) }
50
50
  def all_classes
51
51
  @all_classes ||= T.let(
52
- T.cast(ObjectSpace.each_object(Class), T::Enumerable[T::Class[T.anything]]).each,
52
+ ObjectSpace.each_object(Class),
53
53
  T.nilable(T::Enumerable[T::Class[T.anything]]),
54
54
  )
55
55
  end
@@ -57,7 +57,7 @@ module Tapioca
57
57
  sig { returns(T::Enumerable[Module]) }
58
58
  def all_modules
59
59
  @all_modules ||= T.let(
60
- T.cast(ObjectSpace.each_object(Module), T::Enumerable[Module]).each,
60
+ ObjectSpace.each_object(Module),
61
61
  T.nilable(T::Enumerable[Module]),
62
62
  )
63
63
  end
@@ -57,9 +57,7 @@ module Tapioca
57
57
  sig { override.returns(T::Enumerable[Module]) }
58
58
  def gather_constants
59
59
  # Collect all the classes that include ActiveModel::Validations
60
- all_classes.select do |c|
61
- c < ActiveModel::Validations
62
- end
60
+ all_classes.select { |c| ActiveModel::Validations > c }
63
61
  end
64
62
  end
65
63
 
@@ -188,6 +188,7 @@ module Tapioca
188
188
  def populate_single_assoc_getter_setter(klass, association_name, reflection)
189
189
  association_class = type_for(reflection)
190
190
  association_type = as_nilable_type(association_class)
191
+ association_methods_module = constant.generated_association_methods
191
192
 
192
193
  klass.create_method(
193
194
  association_name.to_s,
@@ -206,6 +207,18 @@ module Tapioca
206
207
  "reset_#{association_name}",
207
208
  return_type: "void",
208
209
  )
210
+ if association_methods_module.method_defined?("#{association_name}_changed?")
211
+ klass.create_method(
212
+ "#{association_name}_changed?",
213
+ return_type: "T::Boolean",
214
+ )
215
+ end
216
+ if association_methods_module.method_defined?("#{association_name}_previously_changed?")
217
+ klass.create_method(
218
+ "#{association_name}_previously_changed?",
219
+ return_type: "T::Boolean",
220
+ )
221
+ end
209
222
  unless reflection.polymorphic?
210
223
  klass.create_method(
211
224
  "build_#{association_name}",
@@ -111,7 +111,7 @@ module Tapioca
111
111
  sig { params(mod: RBI::Scope, name: String).void }
112
112
  def create_fixture_method(mod, name)
113
113
  return_type = return_type_for_fixture(name)
114
- mod << RBI::Method.new(name) do |node|
114
+ mod.create_method(name) do |node|
115
115
  node.add_opt_param("fixture_name", "nil")
116
116
  node.add_rest_param("other_fixtures")
117
117
 
@@ -155,6 +155,25 @@ module Tapioca
155
155
 
156
156
  ConstantType = type_member { { fixed: T.class_of(::ActiveRecord::Base) } }
157
157
 
158
+ # From ActiveRecord::ConnectionAdapter::Quoting#quote, minus nil
159
+ ID_TYPES = T.let(
160
+ [
161
+ "String",
162
+ "Symbol",
163
+ "::ActiveSupport::Multibyte::Chars",
164
+ "T::Boolean",
165
+ "BigDecimal",
166
+ "Numeric",
167
+ "::ActiveRecord::Type::Binary::Data",
168
+ "::ActiveRecord::Type::Time::Value",
169
+ "Date",
170
+ "Time",
171
+ "::ActiveSupport::Duration",
172
+ "T::Class[T.anything]",
173
+ ].to_set.freeze,
174
+ T::Set[String],
175
+ )
176
+
158
177
  sig { override.void }
159
178
  def decorate
160
179
  create_classes_and_includes
@@ -220,7 +239,7 @@ module Tapioca
220
239
  [:find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :create_or_find_by, :create_or_find_by!],
221
240
  T::Array[Symbol],
222
241
  )
223
- BUILDER_METHODS = T.let([:new, :build, :create, :create!], T::Array[Symbol])
242
+ BUILDER_METHODS = T.let([:new, :create, :create!, :build], T::Array[Symbol])
224
243
  TO_ARRAY_METHODS = T.let([:to_ary, :to_a], T::Array[Symbol])
225
244
 
226
245
  private
@@ -663,58 +682,43 @@ module Tapioca
663
682
  return_type: "T::Boolean",
664
683
  )
665
684
  when :find
666
- # From ActiveRecord::ConnectionAdapter::Quoting#quote, minus nil
667
- id_types = [
668
- "String",
669
- "Symbol",
670
- "::ActiveSupport::Multibyte::Chars",
671
- "T::Boolean",
672
- "BigDecimal",
673
- "Numeric",
674
- "::ActiveRecord::Type::Binary::Data",
675
- "::ActiveRecord::Type::Time::Value",
676
- "Date",
677
- "Time",
678
- "::ActiveSupport::Duration",
679
- "T::Class[T.anything]",
680
- ].to_set
685
+ id_types = ID_TYPES
681
686
 
682
687
  if constant.table_exists?
683
688
  primary_key_type = constant.type_for_attribute(constant.primary_key)
684
689
  type = Tapioca::Dsl::Helpers::ActiveModelTypeHelper.type_for(primary_key_type)
685
690
  type = RBIHelper.as_non_nilable_type(type)
686
- id_types << type if type != "T.untyped"
691
+
692
+ id_types = ID_TYPES.union([type]) if type != "T.untyped"
687
693
  end
688
694
 
689
695
  id_types = "T.any(#{id_types.to_a.join(", ")})"
696
+ if constant.try(:composite_primary_key?)
697
+ id_types = "T::Array[#{id_types}]"
698
+ end
690
699
 
691
- array_type = if constant.try(:composite_primary_key?)
692
- "T::Array[T::Array[#{id_types}]]"
693
- else
694
- "T::Array[#{id_types}]"
700
+ array_type = "T::Array[#{id_types}]"
701
+
702
+ common_relation_methods_module.create_method("find") do |method|
703
+ method.add_opt_param("args", "nil")
704
+ method.add_block_param("block")
705
+
706
+ method.add_sig do |sig|
707
+ sig.add_param("args", id_types)
708
+ sig.return_type = constant_name
709
+ end
710
+
711
+ method.add_sig do |sig|
712
+ sig.add_param("args", array_type)
713
+ sig.return_type = "T::Enumerable[#{constant_name}]"
714
+ end
715
+
716
+ method.add_sig do |sig|
717
+ sig.add_param("args", "NilClass")
718
+ sig.add_param("block", "T.proc.params(object: #{constant_name}).void")
719
+ sig.return_type = as_nilable_type(constant_name)
720
+ end
695
721
  end
696
- sigs = [
697
- common_relation_methods_module.create_sig(
698
- parameters: { args: id_types },
699
- return_type: constant_name,
700
- ),
701
- common_relation_methods_module.create_sig(
702
- parameters: { args: array_type },
703
- return_type: "T::Enumerable[#{constant_name}]",
704
- ),
705
- common_relation_methods_module.create_sig(
706
- parameters: {
707
- args: "NilClass",
708
- block: "T.proc.params(object: #{constant_name}).void",
709
- },
710
- return_type: as_nilable_type(constant_name),
711
- ),
712
- ]
713
- common_relation_methods_module.create_method_with_sigs(
714
- "find",
715
- sigs: sigs,
716
- parameters: [RBI::OptParam.new("args", "nil"), RBI::BlockParam.new("block")],
717
- )
718
722
  when :find_by
719
723
  create_common_method(
720
724
  "find_by",
@@ -747,21 +751,18 @@ module Tapioca
747
751
  return_type: constant_name,
748
752
  )
749
753
  when :first, :last, :take
750
- sigs = [
751
- common_relation_methods_module.create_sig(
752
- parameters: { limit: "NilClass" },
753
- return_type: as_nilable_type(constant_name),
754
- ),
755
- common_relation_methods_module.create_sig(
756
- parameters: { limit: "Integer" },
757
- return_type: "T::Array[#{constant_name}]",
758
- ),
759
- ]
760
- common_relation_methods_module.create_method_with_sigs(
761
- method_name.to_s,
762
- sigs: sigs,
763
- parameters: [RBI::OptParam.new("limit", "nil")],
764
- )
754
+ common_relation_methods_module.create_method(method_name.to_s) do |method|
755
+ method.add_opt_param("limit", "nil")
756
+
757
+ method.add_sig do |sig|
758
+ sig.return_type = as_nilable_type(constant_name)
759
+ end
760
+
761
+ method.add_sig do |sig|
762
+ sig.add_param("limit", "Integer")
763
+ sig.return_type = "T::Array[#{constant_name}]"
764
+ end
765
+ end
765
766
  when :raise_record_not_found_exception!
766
767
  # skip
767
768
  else
@@ -821,24 +822,21 @@ module Tapioca
821
822
  return_type: "T.any(Integer, Float, BigDecimal)",
822
823
  )
823
824
  when :count
824
- sigs = [
825
- common_relation_methods_module.create_sig(
826
- parameters: { column_name: "T.nilable(T.any(String, Symbol))" },
827
- return_type: "Integer",
828
- ),
829
- common_relation_methods_module.create_sig(
830
- parameters: { column_name: "NilClass", block: "T.proc.params(object: #{constant_name}).void" },
831
- return_type: "Integer",
832
- ),
833
- ]
834
- common_relation_methods_module.create_method_with_sigs(
835
- "count",
836
- sigs: sigs,
837
- parameters: [
838
- RBI::OptParam.new("column_name", "nil"),
839
- RBI::BlockParam.new("block"),
840
- ],
841
- )
825
+ common_relation_methods_module.create_method(method_name.to_s) do |method|
826
+ method.add_opt_param("column_name", "nil")
827
+ method.add_block_param("block")
828
+
829
+ method.add_sig do |sig|
830
+ sig.add_param("column_name", "T.nilable(T.any(String, Symbol))")
831
+ sig.return_type = "Integer"
832
+ end
833
+
834
+ method.add_sig do |sig|
835
+ sig.add_param("column_name", "NilClass")
836
+ sig.add_param("block", "T.proc.params(object: #{constant_name}).void")
837
+ sig.return_type = "Integer"
838
+ end
839
+ end
842
840
  when :ids
843
841
  create_common_method("ids", return_type: "Array")
844
842
  when :pick, :pluck
@@ -850,29 +848,21 @@ module Tapioca
850
848
  return_type: "T.untyped",
851
849
  )
852
850
  when :sum
853
- sigs = [
854
- common_relation_methods_module.create_sig(
855
- parameters: { initial_value_or_column: "T.untyped" },
856
- return_type: "T.any(Integer, Float, BigDecimal)",
857
- ),
858
- common_relation_methods_module.create_sig(
859
- type_parameters: ["U"],
860
- parameters:
861
- {
862
- initial_value_or_column: "T.nilable(T.type_parameter(:U))",
863
- block: "T.proc.params(object: #{constant_name}).returns(T.type_parameter(:U))",
864
- },
865
- return_type: "T.type_parameter(:U)",
866
- ),
867
- ]
868
- common_relation_methods_module.create_method_with_sigs(
869
- method_name.to_s,
870
- sigs: sigs,
871
- parameters: [
872
- RBI::OptParam.new("initial_value_or_column", "nil"),
873
- RBI::BlockParam.new("block"),
874
- ],
875
- )
851
+ common_relation_methods_module.create_method(method_name.to_s) do |method|
852
+ method.add_opt_param("initial_value_or_column", "nil")
853
+ method.add_block_param("block")
854
+
855
+ method.add_sig do |sig|
856
+ sig.add_param("initial_value_or_column", "T.untyped")
857
+ sig.return_type = "T.any(Integer, Float, BigDecimal)"
858
+ end
859
+
860
+ method.add_sig(type_params: ["U"]) do |sig|
861
+ sig.add_param("initial_value_or_column", "T.nilable(T.type_parameter(:U))")
862
+ sig.add_param("block", "T.proc.params(object: #{constant_name}).returns(T.type_parameter(:U))")
863
+ sig.return_type = "T.type_parameter(:U)"
864
+ end
865
+ end
876
866
  end
877
867
  end
878
868
 
@@ -880,102 +870,100 @@ module Tapioca
880
870
  case method_name
881
871
  when :find_each
882
872
  order = ActiveRecord::Batches.instance_method(:find_each).parameters.include?([:key, :order])
883
- parameters = {
884
- start: "T.untyped",
885
- finish: "T.untyped",
886
- batch_size: "Integer",
887
- error_on_ignore: "T.untyped",
888
- order: ("Symbol" if order),
889
- }.compact
890
- sigs = [
891
- common_relation_methods_module.create_sig(
892
- parameters: parameters.merge(block: "T.proc.params(object: #{constant_name}).void"),
893
- return_type: "void",
894
- ),
895
- common_relation_methods_module.create_sig(
896
- parameters: parameters,
897
- return_type: "T::Enumerator[#{constant_name}]",
898
- ),
899
- ]
900
- common_relation_methods_module.create_method_with_sigs(
901
- "find_each",
902
- sigs: sigs,
903
- parameters: [
904
- RBI::KwOptParam.new("start", "nil"),
905
- RBI::KwOptParam.new("finish", "nil"),
906
- RBI::KwOptParam.new("batch_size", "1000"),
907
- RBI::KwOptParam.new("error_on_ignore", "nil"),
908
- *(RBI::KwOptParam.new("order", ":asc") if order),
909
- RBI::BlockParam.new("block"),
910
- ],
911
- )
873
+
874
+ common_relation_methods_module.create_method("find_each") do |method|
875
+ method.add_kw_opt_param("start", "nil")
876
+ method.add_kw_opt_param("finish", "nil")
877
+ method.add_kw_opt_param("batch_size", "1000")
878
+ method.add_kw_opt_param("error_on_ignore", "nil")
879
+ method.add_kw_opt_param("order", ":asc") if order
880
+ method.add_block_param("block")
881
+
882
+ method.add_sig do |sig|
883
+ sig.add_param("start", "T.untyped")
884
+ sig.add_param("finish", "T.untyped")
885
+ sig.add_param("batch_size", "Integer")
886
+ sig.add_param("error_on_ignore", "T.untyped")
887
+ sig.add_param("order", "Symbol") if order
888
+ sig.add_param("block", "T.proc.params(object: #{constant_name}).void")
889
+ sig.return_type = "void"
890
+ end
891
+
892
+ method.add_sig do |sig|
893
+ sig.add_param("start", "T.untyped")
894
+ sig.add_param("finish", "T.untyped")
895
+ sig.add_param("batch_size", "Integer")
896
+ sig.add_param("error_on_ignore", "T.untyped")
897
+ sig.add_param("order", "Symbol") if order
898
+ sig.return_type = "T::Enumerator[#{constant_name}]"
899
+ end
900
+ end
912
901
  when :find_in_batches
913
902
  order = ActiveRecord::Batches.instance_method(:find_in_batches).parameters.include?([:key, :order])
914
- parameters = {
915
- start: "T.untyped",
916
- finish: "T.untyped",
917
- batch_size: "Integer",
918
- error_on_ignore: "T.untyped",
919
- order: ("Symbol" if order),
920
- }.compact
921
- sigs = [
922
- common_relation_methods_module.create_sig(
923
- parameters: parameters.merge(block: "T.proc.params(object: T::Array[#{constant_name}]).void"),
924
- return_type: "void",
925
- ),
926
- common_relation_methods_module.create_sig(
927
- parameters: parameters,
928
- return_type: "T::Enumerator[T::Enumerator[#{constant_name}]]",
929
- ),
930
- ]
931
- common_relation_methods_module.create_method_with_sigs(
932
- "find_in_batches",
933
- sigs: sigs,
934
- parameters: [
935
- RBI::KwOptParam.new("start", "nil"),
936
- RBI::KwOptParam.new("finish", "nil"),
937
- RBI::KwOptParam.new("batch_size", "1000"),
938
- RBI::KwOptParam.new("error_on_ignore", "nil"),
939
- *(RBI::KwOptParam.new("order", ":asc") if order),
940
- RBI::BlockParam.new("block"),
941
- ],
942
- )
903
+ common_relation_methods_module.create_method("find_in_batches") do |method|
904
+ method.add_kw_opt_param("start", "nil")
905
+ method.add_kw_opt_param("finish", "nil")
906
+ method.add_kw_opt_param("batch_size", "1000")
907
+ method.add_kw_opt_param("error_on_ignore", "nil")
908
+ method.add_kw_opt_param("order", ":asc") if order
909
+ method.add_block_param("block")
910
+
911
+ method.add_sig do |sig|
912
+ sig.add_param("start", "T.untyped")
913
+ sig.add_param("finish", "T.untyped")
914
+ sig.add_param("batch_size", "Integer")
915
+ sig.add_param("error_on_ignore", "T.untyped")
916
+ sig.add_param("order", "Symbol") if order
917
+ sig.add_param("block", "T.proc.params(object: T::Array[#{constant_name}]).void")
918
+ sig.return_type = "void"
919
+ end
920
+
921
+ method.add_sig do |sig|
922
+ sig.add_param("start", "T.untyped")
923
+ sig.add_param("finish", "T.untyped")
924
+ sig.add_param("batch_size", "Integer")
925
+ sig.add_param("error_on_ignore", "T.untyped")
926
+ sig.add_param("order", "Symbol") if order
927
+ sig.return_type = "T::Enumerator[T::Enumerator[#{constant_name}]]"
928
+ end
929
+ end
943
930
  when :in_batches
944
931
  order = ActiveRecord::Batches.instance_method(:in_batches).parameters.include?([:key, :order])
945
932
  use_ranges = ActiveRecord::Batches.instance_method(:in_batches).parameters.include?([:key, :use_ranges])
946
- parameters = {
947
- of: "Integer",
948
- start: "T.untyped",
949
- finish: "T.untyped",
950
- load: "T.untyped",
951
- error_on_ignore: "T.untyped",
952
- order: ("Symbol" if order),
953
- use_ranges: ("T.untyped" if use_ranges),
954
- }.compact
955
- sigs = [
956
- common_relation_methods_module.create_sig(
957
- parameters: parameters.merge(block: "T.proc.params(object: #{RelationClassName}).void"),
958
- return_type: "void",
959
- ),
960
- common_relation_methods_module.create_sig(
961
- parameters: parameters,
962
- return_type: "::ActiveRecord::Batches::BatchEnumerator",
963
- ),
964
- ]
965
- common_relation_methods_module.create_method_with_sigs(
966
- "in_batches",
967
- sigs: sigs,
968
- parameters: [
969
- RBI::KwOptParam.new("of", "1000"),
970
- RBI::KwOptParam.new("start", "nil"),
971
- RBI::KwOptParam.new("finish", "nil"),
972
- RBI::KwOptParam.new("load", "false"),
973
- RBI::KwOptParam.new("error_on_ignore", "nil"),
974
- *(RBI::KwOptParam.new("order", ":asc") if order),
975
- *(RBI::KwOptParam.new("use_ranges", "nil") if use_ranges),
976
- RBI::BlockParam.new("block"),
977
- ],
978
- )
933
+
934
+ common_relation_methods_module.create_method("in_batches") do |method|
935
+ method.add_kw_opt_param("of", "1000")
936
+ method.add_kw_opt_param("start", "nil")
937
+ method.add_kw_opt_param("finish", "nil")
938
+ method.add_kw_opt_param("load", "false")
939
+ method.add_kw_opt_param("error_on_ignore", "nil")
940
+ method.add_kw_opt_param("order", ":asc") if order
941
+ method.add_kw_opt_param("use_ranges", "nil") if use_ranges
942
+ method.add_block_param("block")
943
+
944
+ method.add_sig do |sig|
945
+ sig.add_param("of", "Integer")
946
+ sig.add_param("start", "T.untyped")
947
+ sig.add_param("finish", "T.untyped")
948
+ sig.add_param("load", "T.untyped")
949
+ sig.add_param("error_on_ignore", "T.untyped")
950
+ sig.add_param("order", "Symbol") if order
951
+ sig.add_param("use_ranges", "T.untyped") if use_ranges
952
+ sig.add_param("block", "T.proc.params(object: #{RelationClassName}).void")
953
+ sig.return_type = "void"
954
+ end
955
+
956
+ method.add_sig do |sig|
957
+ sig.add_param("of", "Integer")
958
+ sig.add_param("start", "T.untyped")
959
+ sig.add_param("finish", "T.untyped")
960
+ sig.add_param("load", "T.untyped")
961
+ sig.add_param("error_on_ignore", "T.untyped")
962
+ sig.add_param("order", "Symbol") if order
963
+ sig.add_param("use_ranges", "T.untyped") if use_ranges
964
+ sig.return_type = "::ActiveRecord::Batches::BatchEnumerator"
965
+ end
966
+ end
979
967
  end
980
968
  end
981
969
 
@@ -991,26 +979,50 @@ module Tapioca
991
979
  end
992
980
 
993
981
  FIND_OR_CREATE_METHODS.each do |method_name|
994
- block_type = "T.nilable(T.proc.params(object: #{constant_name}).void)"
995
- create_common_method(
996
- method_name,
997
- parameters: [
998
- create_param("attributes", type: "T.untyped"),
999
- create_block_param("block", type: block_type),
1000
- ],
1001
- return_type: constant_name,
1002
- )
982
+ common_relation_methods_module.create_method(method_name.to_s) do |method|
983
+ method.add_param("attributes")
984
+ method.add_block_param("block")
985
+
986
+ # `T.untyped` matches `T::Array[T.untyped]` so the array signature
987
+ # must be defined first for Sorbet to pick it, if valid.
988
+ method.add_sig do |sig|
989
+ sig.add_param("attributes", "T::Array[T.untyped]")
990
+ sig.add_param("block", "T.nilable(T.proc.params(object: #{constant_name}).void)")
991
+ sig.return_type = "T::Array[#{constant_name}]"
992
+ end
993
+
994
+ method.add_sig do |sig|
995
+ sig.add_param("attributes", "T.untyped")
996
+ sig.add_param("block", "T.nilable(T.proc.params(object: #{constant_name}).void)")
997
+ sig.return_type = constant_name
998
+ end
999
+ end
1003
1000
  end
1004
1001
 
1005
1002
  BUILDER_METHODS.each do |method_name|
1006
- create_common_method(
1007
- method_name,
1008
- parameters: [
1009
- create_opt_param("attributes", type: "T.untyped", default: "nil"),
1010
- create_block_param("block", type: "T.nilable(T.proc.params(object: #{constant_name}).void)"),
1011
- ],
1012
- return_type: constant_name,
1013
- )
1003
+ common_relation_methods_module.create_method(method_name.to_s) do |method|
1004
+ method.add_opt_param("attributes", "nil")
1005
+ method.add_block_param("block")
1006
+
1007
+ method.add_sig do |sig|
1008
+ sig.add_param("block", "T.nilable(T.proc.params(object: #{constant_name}).void)")
1009
+ sig.return_type = constant_name
1010
+ end
1011
+
1012
+ # `T.untyped` matches `T::Array[T.untyped]` so the array signature
1013
+ # must be defined first for Sorbet to pick it, if valid.
1014
+ method.add_sig do |sig|
1015
+ sig.add_param("attributes", "T::Array[T.untyped]")
1016
+ sig.add_param("block", "T.nilable(T.proc.params(object: #{constant_name}).void)")
1017
+ sig.return_type = "T::Array[#{constant_name}]"
1018
+ end
1019
+
1020
+ method.add_sig do |sig|
1021
+ sig.add_param("attributes", "T.untyped")
1022
+ sig.add_param("block", "T.nilable(T.proc.params(object: #{constant_name}).void)")
1023
+ sig.return_type = constant_name
1024
+ end
1025
+ end
1014
1026
  end
1015
1027
  end
1016
1028
 
@@ -66,19 +66,18 @@ module Tapioca
66
66
 
67
67
  sig { override.returns(T::Enumerable[Module]) }
68
68
  def gather_constants
69
- # Find all Modules that are:
70
69
  all_modules.select do |mod|
71
- # named (i.e. not anonymous)
72
- name_of(mod) &&
73
- # not singleton classes
70
+ name_of(mod) && # i.e. not anonymous
74
71
  !mod.singleton_class? &&
75
- # extend ActiveSupport::Concern, and
76
- mod.singleton_class < ActiveSupport::Concern &&
77
- # have dependencies (i.e. include another concern)
78
- !dependencies_of(mod).empty?
72
+ ActiveSupport::Concern > mod.singleton_class &&
73
+ has_dependencies?(mod)
79
74
  end
80
75
  end
81
76
 
77
+ # Returns true when `mod` includes other concerns
78
+ sig { params(mod: Module).returns(T::Boolean) }
79
+ def has_dependencies?(mod) = dependencies_of(mod).any?
80
+
82
81
  sig { params(concern: Module).returns(T::Array[Module]) }
83
82
  def dependencies_of(concern)
84
83
  concern.instance_variable_get(:@_dependencies) || []
@@ -0,0 +1,68 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ return unless defined?(Time.current) && defined?(ActiveSupport::TimeWithZone)
5
+
6
+ module Tapioca
7
+ module Dsl
8
+ module Compilers
9
+ # `Tapioca::Dsl::Compilers::ActiveSupportTimeExt` generates an RBI file for the `Time#current` method
10
+ # defined by [Active Support's Time extensions](https://api.rubyonrails.org/classes/Time.html).
11
+ #
12
+ # If `Time.zone` or `config.time_zone` are set, then the `Time.current` method will be defined as returning
13
+ # an instance of `ActiveSupport::TimeWithZone`, otherwise it will return an instance of `Time`.
14
+ #
15
+ # For an application that is configured with:
16
+ # ```ruby
17
+ # config.time_zone = "UTC"
18
+ # ```
19
+ # this compiler will produce the following RBI file:
20
+ # ```rbi
21
+ # class Time
22
+ # class << self
23
+ # sig { returns(::ActiveSupport::TimeWithZone) }
24
+ # def current; end
25
+ # end
26
+ # end
27
+ # ```
28
+ # whereas if `Time.zone` and `config.time_zone` are not set, it will produce:
29
+ # ```rbi
30
+ # class Time
31
+ # class << self
32
+ # sig { returns(::Time) }
33
+ # def current; end
34
+ # end
35
+ # end
36
+ # ```
37
+ class ActiveSupportTimeExt < Compiler
38
+ extend T::Sig
39
+
40
+ ConstantType = type_member { { fixed: T.class_of(::Time) } }
41
+
42
+ sig { override.void }
43
+ def decorate
44
+ return unless constant.respond_to?(:zone)
45
+
46
+ root.create_path(constant) do |mod|
47
+ return_type = if ::Time.zone
48
+ "::ActiveSupport::TimeWithZone"
49
+ else
50
+ "::Time"
51
+ end
52
+
53
+ mod.create_method("current", return_type: return_type, class_method: true)
54
+ end
55
+ end
56
+
57
+ class << self
58
+ extend T::Sig
59
+
60
+ sig { override.returns(T::Enumerable[Module]) }
61
+ def gather_constants
62
+ [::Time]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -80,7 +80,7 @@ module Tapioca
80
80
 
81
81
  sig { override.returns(T::Enumerable[Module]) }
82
82
  def gather_constants
83
- all_classes.select { |c| c < GraphQL::Schema::InputObject }
83
+ all_classes.select { |c| GraphQL::Schema::InputObject > c }
84
84
  end
85
85
  end
86
86
  end
@@ -80,7 +80,7 @@ module Tapioca
80
80
 
81
81
  sig { override.returns(T::Enumerable[Module]) }
82
82
  def gather_constants
83
- all_classes.select { |c| c < GraphQL::Schema::Mutation && c != GraphQL::Schema::RelayClassicMutation }
83
+ all_classes.select { |c| GraphQL::Schema::Mutation > c && GraphQL::Schema::RelayClassicMutation != c }
84
84
  end
85
85
  end
86
86
  end
@@ -100,7 +100,7 @@ module Tapioca
100
100
  sig { override.returns(T::Enumerable[Module]) }
101
101
  def gather_constants
102
102
  descendants_of(::ActiveRecord::Base).select do |klass|
103
- klass < ::IdentityCache::WithoutPrimaryIndex
103
+ ::IdentityCache::WithoutPrimaryIndex > klass
104
104
  end
105
105
  end
106
106
  end
@@ -111,7 +111,7 @@ module Tapioca
111
111
  sig { override.returns(T::Enumerable[Module]) }
112
112
  def gather_constants
113
113
  all_modules.select do |c|
114
- name_of(c) && c < ::JsonApiClient::Resource
114
+ name_of(c) && ::JsonApiClient::Resource > c
115
115
  end
116
116
  end
117
117
  end
@@ -159,9 +159,7 @@ module Tapioca
159
159
  def gather_constants
160
160
  marker = Google::Protobuf::MessageExts::ClassMethods
161
161
 
162
- enum_modules = ObjectSpace.each_object(Google::Protobuf::EnumDescriptor).map do |desc|
163
- T.cast(desc, Google::Protobuf::EnumDescriptor).enummodule
164
- end
162
+ enum_modules = ObjectSpace.each_object(Google::Protobuf::EnumDescriptor).map(&:enummodule)
165
163
 
166
164
  results = if Google::Protobuf.const_defined?(:AbstractMessage)
167
165
  abstract_message_const = ::Google::Protobuf.const_get(:AbstractMessage)
@@ -73,7 +73,7 @@ module Tapioca
73
73
 
74
74
  sig { override.returns(T::Enumerable[Module]) }
75
75
  def gather_constants
76
- all_classes.select { |c| c < Sidekiq::Worker }
76
+ all_classes.select { |c| Sidekiq::Worker > c }
77
77
  end
78
78
  end
79
79
 
@@ -86,8 +86,9 @@ module Tapioca
86
86
  def gather_constants
87
87
  all_modules.select do |c|
88
88
  name_of(c) &&
89
- c != ::SmartProperties::Validations::Ancestor &&
90
- c < ::SmartProperties && ::SmartProperties::ClassMethods === c
89
+ ::SmartProperties > c &&
90
+ ::SmartProperties::Validations::Ancestor != c &&
91
+ ::SmartProperties::ClassMethods === c
91
92
  end
92
93
  end
93
94
  end
@@ -159,7 +159,7 @@ module Tapioca
159
159
 
160
160
  sig { override.returns(T::Enumerable[Module]) }
161
161
  def gather_constants
162
- all_classes.select { |mod| mod < ::StateMachines::InstanceMethods }
162
+ all_classes.select { |mod| ::StateMachines::InstanceMethods > mod }
163
163
  end
164
164
  end
165
165
 
@@ -231,12 +231,12 @@ module Tapioca
231
231
  instance_module.create_method(
232
232
  attribute,
233
233
  return_type: state_type,
234
- )
234
+ ) if ::StateMachines::HelperModule === machine.owner_class.instance_method(attribute).owner
235
235
  instance_module.create_method(
236
236
  "#{attribute}=",
237
237
  parameters: [create_param("value", type: state_type)],
238
238
  return_type: state_type,
239
- )
239
+ ) if ::StateMachines::HelperModule === machine.owner_class.instance_method("#{attribute}=").owner
240
240
  end
241
241
 
242
242
  sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
@@ -106,19 +106,28 @@ module Tapioca
106
106
  routes_reloader = Rails.application.routes_reloader
107
107
  routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
108
108
 
109
- Object.const_set(:GeneratedUrlHelpersModule, Rails.application.routes.named_routes.url_helpers_module)
110
- Object.const_set(:GeneratedPathHelpersModule, Rails.application.routes.named_routes.path_helpers_module)
109
+ url_helpers_module = Rails.application.routes.named_routes.url_helpers_module
110
+ path_helpers_module = Rails.application.routes.named_routes.path_helpers_module
111
+
112
+ Object.const_set(:GeneratedUrlHelpersModule, url_helpers_module)
113
+ Object.const_set(:GeneratedPathHelpersModule, path_helpers_module)
111
114
 
112
115
  constants = all_modules.select do |mod|
113
116
  next unless name_of(mod)
114
117
 
115
- includes_helper?(mod, GeneratedUrlHelpersModule) ||
116
- includes_helper?(mod, GeneratedPathHelpersModule) ||
117
- includes_helper?(mod.singleton_class, GeneratedUrlHelpersModule) ||
118
- includes_helper?(mod.singleton_class, GeneratedPathHelpersModule)
118
+ # Fast-path to quickly disqualify most cases
119
+ next false unless url_helpers_module > mod || # rubocop:disable Style/InvertibleUnlessCondition
120
+ path_helpers_module > mod ||
121
+ url_helpers_module > mod.singleton_class ||
122
+ path_helpers_module > mod.singleton_class
123
+
124
+ includes_helper?(mod, url_helpers_module) ||
125
+ includes_helper?(mod, path_helpers_module) ||
126
+ includes_helper?(mod.singleton_class, url_helpers_module) ||
127
+ includes_helper?(mod.singleton_class, path_helpers_module)
119
128
  end
120
129
 
121
- constants.concat(NON_DISCOVERABLE_INCLUDERS)
130
+ constants.concat(NON_DISCOVERABLE_INCLUDERS).push(GeneratedUrlHelpersModule, GeneratedPathHelpersModule)
122
131
  end
123
132
 
124
133
  sig { returns(T::Array[Module]) }
@@ -134,17 +143,20 @@ module Tapioca
134
143
  end.freeze
135
144
  end
136
145
 
146
+ # Returns `true` if `mod` "directly" includes `helper`.
147
+ # For classes, this method will return false if the `helper` is included only by a superclass
137
148
  sig { params(mod: Module, helper: Module).returns(T::Boolean) }
138
149
  private def includes_helper?(mod, helper)
139
- superclass_ancestors = []
150
+ ancestors = ancestors_of(mod)
140
151
 
141
- if Class === mod
142
- superclass = superclass_of(mod)
143
- superclass_ancestors = ancestors_of(superclass) if superclass
152
+ own_ancestors = if Class === mod && (superclass = superclass_of(mod))
153
+ # These ancestors are unique to `mod`, and exclude those in common with `superclass`.
154
+ ancestors.take(ancestors.count - ancestors_of(superclass).size)
155
+ else
156
+ ancestors
144
157
  end
145
158
 
146
- ancestors = Set.new.compare_by_identity.merge(ancestors_of(mod)).subtract(superclass_ancestors)
147
- ancestors.any? { |ancestor| helper == ancestor }
159
+ own_ancestors.include?(helper)
148
160
  end
149
161
  end
150
162
 
@@ -1,6 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "tapioca/dsl"
4
5
  require "tapioca/helpers/test/content"
5
6
  require "tapioca/helpers/test/isolation"
6
7
  require "tapioca/helpers/test/template"
@@ -63,10 +63,17 @@ module Tapioca
63
63
  def load_dsl_compilers
64
64
  say("Loading DSL compiler classes... ")
65
65
 
66
+ # Load all built-in compilers
67
+ Dir.glob("#{Tapioca::LIB_ROOT_DIR}/tapioca/dsl/compilers/*.rb").each do |compiler|
68
+ require File.expand_path(compiler)
69
+ end
70
+
71
+ # Load all custom compilers exported from gems
66
72
  ::Gem.find_files("tapioca/dsl/compilers/*.rb").each do |compiler|
67
73
  require File.expand_path(compiler)
68
74
  end
69
75
 
76
+ # Load all custom compilers from the project
70
77
  Dir.glob([
71
78
  "#{@tapioca_path}/generators/**/*.rb", # TODO: Here for backcompat, remove later
72
79
  "#{@tapioca_path}/compilers/**/*.rb",
@@ -47,12 +47,23 @@ module Tapioca
47
47
  ).void
48
48
  end
49
49
  def load_rails_application(environment_load: false, eager_load: false, app_root: ".", halt_upon_load_error: true)
50
- return unless File.exist?("#{app_root}/config/application.rb")
50
+ return unless File.exist?(File.expand_path("config/application.rb", app_root))
51
51
 
52
- if environment_load
53
- require "./#{app_root}/config/environment"
52
+ load_path = if environment_load
53
+ "config/environment"
54
54
  else
55
- require "./#{app_root}/config/application"
55
+ "config/application"
56
+ end
57
+
58
+ require File.expand_path(load_path, app_root)
59
+
60
+ unless defined?(Rails)
61
+ say(
62
+ "\nTried to load the app from `#{load_path}` as a Rails application " \
63
+ "but the `Rails` constant wasn't defined after loading the file.",
64
+ :yellow,
65
+ )
66
+ return
56
67
  end
57
68
 
58
69
  eager_load_rails_app if eager_load
@@ -80,65 +80,38 @@ module RBI
80
80
  params(
81
81
  name: String,
82
82
  parameters: T::Array[TypedParam],
83
- return_type: String,
83
+ return_type: T.nilable(String),
84
84
  class_method: T::Boolean,
85
85
  visibility: RBI::Visibility,
86
86
  comments: T::Array[RBI::Comment],
87
+ block: T.nilable(T.proc.params(node: RBI::Method).void),
87
88
  ).void
88
89
  end
89
- def create_method(name, parameters: [], return_type: "T.untyped", class_method: false, visibility: RBI::Public.new,
90
- comments: [])
91
- sig_params = parameters.to_h { |param| [param.param.name, param.type] }
92
- sig = create_sig(parameters: sig_params, return_type: return_type)
93
- create_method_with_sigs(
94
- name,
95
- sigs: [sig],
96
- parameters: parameters.map(&:param),
97
- class_method: class_method,
98
- visibility: visibility,
99
- comments: comments,
100
- )
101
- end
102
-
103
- sig do
104
- params(
105
- name: String,
106
- sigs: T::Array[RBI::Sig],
107
- parameters: T::Array[RBI::Param],
108
- class_method: T::Boolean,
109
- visibility: RBI::Visibility,
110
- comments: T::Array[RBI::Comment],
111
- ).void
112
- end
113
- def create_method_with_sigs(name, sigs:, parameters: [], class_method: false, visibility: RBI::Public.new,
114
- comments: [])
90
+ def create_method(name, parameters: [], return_type: nil, class_method: false, visibility: RBI::Public.new,
91
+ comments: [], &block)
115
92
  return unless Tapioca::RBIHelper.valid_method_name?(name)
116
93
 
94
+ sigs = []
95
+
96
+ if !block || !parameters.empty? || return_type
97
+ # If there is no block, and the params and return type have not been supplied, then
98
+ # we create a single signature with the given parameters and return type
99
+ params = parameters.map { |param| RBI::SigParam.new(param.param.name.to_s, param.type) }
100
+ sigs << RBI::Sig.new(params: params, return_type: return_type || "T.untyped")
101
+ end
102
+
117
103
  method = RBI::Method.new(
118
104
  name,
119
105
  sigs: sigs,
120
- params: parameters,
106
+ params: parameters.map(&:param),
121
107
  is_singleton: class_method,
122
108
  visibility: visibility,
123
109
  comments: comments,
110
+ &block
124
111
  )
125
112
  self << method
126
113
  end
127
114
 
128
- sig do
129
- params(
130
- parameters: T::Hash[T.any(String, Symbol), String],
131
- type_parameters: T::Array[String],
132
- return_type: String,
133
- ).returns(RBI::Sig)
134
- end
135
- def create_sig(parameters:, type_parameters: [], return_type: "T.untyped")
136
- params = parameters.map do |name, type|
137
- RBI::SigParam.new(name.to_s, type)
138
- end
139
- RBI::Sig.new(type_params: type_parameters, params: params, return_type: return_type)
140
- end
141
-
142
115
  private
143
116
 
144
117
  sig { returns(T::Hash[String, RBI::Node]) }
@@ -166,7 +166,7 @@ module Tapioca
166
166
  end
167
167
  def descendants_of(klass)
168
168
  result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
169
- T.cast(k, Module).singleton_class? || T.unsafe(k) == klass
169
+ k.singleton_class? || k == klass
170
170
  end
171
171
 
172
172
  T.unsafe(result)
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.15.1"
5
+ VERSION = "0.16.0"
6
6
  end
data/lib/tapioca.rb CHANGED
@@ -30,6 +30,7 @@ module Tapioca
30
30
 
31
31
  class Error < StandardError; end
32
32
 
33
+ LIB_ROOT_DIR = T.let(T.must(__dir__), String)
33
34
  SORBET_DIR = T.let("sorbet", String)
34
35
  SORBET_CONFIG_FILE = T.let("#{SORBET_DIR}/config", String)
35
36
  TAPIOCA_DIR = T.let("#{SORBET_DIR}/tapioca", String)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapioca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2024-07-10 00:00:00.000000000 Z
14
+ date: 2024-08-06 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -61,7 +61,7 @@ dependencies:
61
61
  requirements:
62
62
  - - ">="
63
63
  - !ruby/object:Gem::Version
64
- version: 0.1.4
64
+ version: 0.1.14
65
65
  - - "<"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0.2'
@@ -71,7 +71,7 @@ dependencies:
71
71
  requirements:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: 0.1.4
74
+ version: 0.1.14
75
75
  - - "<"
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0.2'
@@ -186,6 +186,7 @@ files:
186
186
  - lib/tapioca/dsl/compilers/active_storage.rb
187
187
  - lib/tapioca/dsl/compilers/active_support_concern.rb
188
188
  - lib/tapioca/dsl/compilers/active_support_current_attributes.rb
189
+ - lib/tapioca/dsl/compilers/active_support_time_ext.rb
189
190
  - lib/tapioca/dsl/compilers/config.rb
190
191
  - lib/tapioca/dsl/compilers/frozen_record.rb
191
192
  - lib/tapioca/dsl/compilers/graphql_input_object.rb
@@ -289,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
289
290
  - !ruby/object:Gem::Version
290
291
  version: '0'
291
292
  requirements: []
292
- rubygems_version: 3.5.14
293
+ rubygems_version: 3.5.16
293
294
  signing_key:
294
295
  specification_version: 4
295
296
  summary: A Ruby Interface file generator for gems, core types and the Ruby standard