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
@@ -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 RBIHelper
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"