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
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetEnums < Base
8
+ extend T::Sig
9
+
10
+ private
11
+
12
+ sig { override.params(event: ScopeNodeAdded).void }
13
+ def on_scope(event)
14
+ constant = event.constant
15
+ return unless T::Enum > event.constant
16
+
17
+ enums = T.unsafe(constant).values.map do |enum_type|
18
+ enum_type.instance_variable_get(:@const_name).to_s
19
+ end
20
+
21
+ event.node << RBI::TEnumBlock.new(enums)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetHelpers < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ private
13
+
14
+ sig { override.params(event: ScopeNodeAdded).void }
15
+ def on_scope(event)
16
+ constant = event.constant
17
+ node = event.node
18
+
19
+ abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type) ||
20
+ T::Private::Abstract::Data.get(singleton_class_of(constant), :abstract_type)
21
+
22
+ node << RBI::Helper.new(abstract_type.to_s) if abstract_type
23
+ node << RBI::Helper.new("final") if T::Private::Final.final_module?(constant)
24
+ node << RBI::Helper.new("sealed") if T::Private::Sealed.sealed_module?(constant)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetProps < Base
8
+ extend T::Sig
9
+
10
+ private
11
+
12
+ sig { override.params(event: ScopeNodeAdded).void }
13
+ def on_scope(event)
14
+ constant = event.constant
15
+ node = event.node
16
+
17
+ return unless T::Props::ClassMethods === constant
18
+
19
+ constant.props.map do |name, prop|
20
+ type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void")
21
+
22
+ default = prop.key?(:default) ? "T.unsafe(nil)" : nil
23
+ node << if prop.fetch(:immutable, false)
24
+ RBI::TStructConst.new(name.to_s, type, default: default)
25
+ else
26
+ RBI::TStructProp.new(name.to_s, type, default: default)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetRequiredAncestors < Base
8
+ extend T::Sig
9
+
10
+ private
11
+
12
+ sig { override.params(event: ScopeNodeAdded).void }
13
+ def on_scope(event)
14
+ ancestors = Runtime::Trackers::RequiredAncestor.required_ancestors_by(event.constant)
15
+ ancestors.each do |ancestor|
16
+ next unless ancestor # TODO: We should have a way to warn from here
17
+ event.node << RBI::RequiresAncestor.new(ancestor.to_s)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,79 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetSignatures < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+ include SignaturesHelper
12
+
13
+ TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
14
+
15
+ private
16
+
17
+ sig { override.params(event: MethodNodeAdded).void }
18
+ def on_method(event)
19
+ signature = event.signature
20
+ return unless signature
21
+
22
+ event.node.sigs << compile_signature(signature, event.parameters)
23
+ end
24
+
25
+ sig { params(signature: T.untyped, parameters: T::Array[[Symbol, String]]).returns(RBI::Sig) }
26
+ def compile_signature(signature, parameters)
27
+ parameter_types = T.let(signature.arg_types.to_h, T::Hash[Symbol, T::Types::Base])
28
+ parameter_types.merge!(signature.kwarg_types)
29
+ parameter_types[signature.rest_name] = signature.rest_type if signature.has_rest
30
+ parameter_types[signature.keyrest_name] = signature.keyrest_type if signature.has_keyrest
31
+ parameter_types[signature.block_name] = signature.block_type if signature.block_name
32
+
33
+ sig = RBI::Sig.new
34
+
35
+ parameters.each do |_, name|
36
+ type = sanitize_signature_types(parameter_types[name.to_sym].to_s)
37
+ @pipeline.push_symbol(type)
38
+ sig << RBI::SigParam.new(name, type)
39
+ end
40
+
41
+ return_type = name_of_type(signature.return_type)
42
+ return_type = sanitize_signature_types(return_type)
43
+ sig.return_type = return_type
44
+ @pipeline.push_symbol(return_type)
45
+
46
+ parameter_types.values.join(", ").scan(TYPE_PARAMETER_MATCHER).flatten.uniq.each do |k, _|
47
+ sig.type_params << k
48
+ end
49
+
50
+ case signature.mode
51
+ when "abstract"
52
+ sig.is_abstract = true
53
+ when "override"
54
+ sig.is_override = true
55
+ when "overridable_override"
56
+ sig.is_overridable = true
57
+ sig.is_override = true
58
+ when "overridable"
59
+ sig.is_overridable = true
60
+ end
61
+
62
+ sig.is_final = signature_final?(signature)
63
+
64
+ sig
65
+ end
66
+
67
+ sig { params(signature: T.untyped).returns(T::Boolean) }
68
+ def signature_final?(signature)
69
+ modules_with_final = T::Private::Methods.instance_variable_get(:@modules_with_final)
70
+ final_methods = modules_with_final[signature.owner.object_id]
71
+
72
+ return false unless final_methods
73
+
74
+ final_methods.include?(signature.method_name)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetTypeVariables < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ private
13
+
14
+ sig { override.params(event: ScopeNodeAdded).void }
15
+ def on_scope(event)
16
+ constant = event.constant
17
+ node = event.node
18
+
19
+ compile_type_variable_declarations(node, constant)
20
+
21
+ sclass = RBI::SingletonClass.new
22
+ compile_type_variable_declarations(sclass, singleton_class_of(constant))
23
+ node << sclass if sclass.nodes.length > 1
24
+ end
25
+
26
+ sig { params(tree: RBI::Tree, constant: Module).void }
27
+ def compile_type_variable_declarations(tree, constant)
28
+ # Try to find the type variables defined on this constant, bail if we can't
29
+ type_variables = Runtime::GenericTypeRegistry.lookup_type_variables(constant)
30
+ return unless type_variables
31
+
32
+ # Map each type variable to its string representation.
33
+ #
34
+ # Each entry of `type_variables` maps a Module to a String,
35
+ # and the order they are inserted into the hash is the order they should be
36
+ # defined in the source code.
37
+ type_variable_declarations = type_variables.map do |type_variable|
38
+ type_variable_name = type_variable.name
39
+ next unless type_variable_name
40
+
41
+ tree << RBI::TypeMember.new(type_variable_name, type_variable.serialize)
42
+ end
43
+
44
+ return if type_variable_declarations.empty?
45
+
46
+ tree << RBI::Extend.new("T::Generic")
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class Subconstants < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ private
13
+
14
+ sig { override.params(event: ScopeNodeAdded).void }
15
+ def on_scope(event)
16
+ symbol = event.symbol
17
+ return if @pipeline.symbol_in_payload?(symbol) && event.node.empty?
18
+
19
+ prefix = symbol == "Object" ? "" : symbol
20
+
21
+ constant = event.constant
22
+ constants_of(constant).sort.uniq.map do |constant_name|
23
+ name = "#{prefix}::#{constant_name}"
24
+ subconstant = constantize(name)
25
+
26
+ # Don't compile modules of Object because Object::Foo == Foo
27
+ # Don't compile modules of BasicObject because BasicObject::BasicObject == BasicObject
28
+ next if (Object == constant || BasicObject == constant) && Module === subconstant
29
+ next unless subconstant
30
+
31
+ @pipeline.push_constant(name, subconstant)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,96 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class YardDoc < Base
8
+ extend T::Sig
9
+
10
+ IGNORED_COMMENTS = T.let([
11
+ ":doc:",
12
+ ":nodoc:",
13
+ "typed:",
14
+ "frozen_string_literal:",
15
+ "encoding:",
16
+ "warn_indent:",
17
+ "shareable_constant_value:",
18
+ "rubocop:",
19
+ ], T::Array[String])
20
+
21
+ IGNORED_SIG_TAGS = T.let(["param", "return"], T::Array[String])
22
+
23
+ sig { params(pipeline: Pipeline).void }
24
+ def initialize(pipeline)
25
+ super(pipeline)
26
+ pipeline.gem.parse_yard_docs
27
+ end
28
+
29
+ private
30
+
31
+ sig { override.params(event: ConstNodeAdded).void }
32
+ def on_const(event)
33
+ event.node.comments = documentation_comments(event.symbol)
34
+ end
35
+
36
+ sig { override.params(event: ScopeNodeAdded).void }
37
+ def on_scope(event)
38
+ event.node.comments = documentation_comments(event.symbol)
39
+ end
40
+
41
+ sig { override.params(event: MethodNodeAdded).void }
42
+ def on_method(event)
43
+ separator = event.constant.singleton_class? ? "." : "#"
44
+ event.node.comments = documentation_comments(
45
+ "#{event.symbol}#{separator}#{event.node.name}",
46
+ sigs: event.node.sigs
47
+ )
48
+ end
49
+
50
+ sig { params(name: String, sigs: T::Array[RBI::Sig]).returns(T::Array[RBI::Comment]) }
51
+ def documentation_comments(name, sigs: [])
52
+ yard_docs = YARD::Registry.at(name)
53
+ return [] unless yard_docs
54
+
55
+ docstring = yard_docs.docstring
56
+ return [] if /(copyright|license)/i.match?(docstring)
57
+
58
+ comments = docstring.lines
59
+ .reject { |line| IGNORED_COMMENTS.any? { |comment| line.include?(comment) } }
60
+ .map! { |line| RBI::Comment.new(line) }
61
+
62
+ tags = yard_docs.tags
63
+ tags.reject! { |tag| IGNORED_SIG_TAGS.include?(tag.tag_name) } unless sigs.empty?
64
+
65
+ comments << RBI::Comment.new("") if comments.any? && tags.any?
66
+
67
+ tags.sort_by(&:tag_name).each do |tag|
68
+ line = +"@#{tag.tag_name}"
69
+
70
+ tag_name = tag.name
71
+ line << " #{tag_name}" if tag_name
72
+
73
+ tag_types = tag.types
74
+ line << " [#{tag_types.join(", ")}]" if tag_types&.any?
75
+
76
+ tag_text = tag.text
77
+ if tag_text && !tag_text.empty?
78
+ text_lines = tag_text.lines
79
+
80
+ # Example are a special case because we want the text to start on the next line
81
+ line << " #{text_lines.shift&.strip}" unless tag.tag_name == "example"
82
+
83
+ text_lines.each do |text_line|
84
+ line << "\n #{text_line.strip}"
85
+ end
86
+ end
87
+
88
+ comments << RBI::Comment.new(line)
89
+ end
90
+
91
+ comments
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca/gem/listeners/base"
5
+ require "tapioca/gem/listeners/dynamic_mixins"
6
+ require "tapioca/gem/listeners/methods"
7
+ require "tapioca/gem/listeners/mixins"
8
+ require "tapioca/gem/listeners/remove_empty_payload_scopes"
9
+ require "tapioca/gem/listeners/sorbet_enums"
10
+ require "tapioca/gem/listeners/sorbet_helpers"
11
+ require "tapioca/gem/listeners/sorbet_props"
12
+ require "tapioca/gem/listeners/sorbet_required_ancestors"
13
+ require "tapioca/gem/listeners/sorbet_signatures"
14
+ require "tapioca/gem/listeners/sorbet_type_variables"
15
+ require "tapioca/gem/listeners/subconstants"
16
+ require "tapioca/gem/listeners/yard_doc"