tapioca 0.4.16 → 0.4.21

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80a1d62bade6d75e6ec3a7de5a06f96eb7bc4ea002a70a2503d74393e834e864
4
- data.tar.gz: 235cd2477d3f07176bdbddd5653c7a5cff86516f81dd196a65043a5b2f7d68dc
3
+ metadata.gz: 832665c6f2a41c3eb5cfda2b0939b4a3f2d29eb624b64de49565b8275f461326
4
+ data.tar.gz: 29e1f7066050ce4f308b1dc8591dd7de236e19560618a37c9120a4835ac1b295
5
5
  SHA512:
6
- metadata.gz: 23bc803e2b251dfb4ef4c82b0793336810ca9d551355ac1cd8d514c1484ef096d9b00a12cda214ce9b4b83ccf1620a32159035a5bf798a4c2d8a3e96b026e352
7
- data.tar.gz: 606e2ad0e81c87e7f89b5933fa160d1b7825dee6f3f3451042ad2ec047a672041b559ed9cb79cf75aa0af7020b9aed2da85e1f52f36bb39b909f5037f8865717
6
+ metadata.gz: a16a1125e343398d7a2190aea74ed2fafc3b9ad5d4aaac8a2b72ae4a75cd65bdb651dcf84528189eec39296eebb42382f847221651190e611f1c7260923653ba
7
+ data.tar.gz: d636730273b8eb9d828de5609b73c73cb677ec0cf372a1ae5d028604ec53422cbe3c607d557c0b4070dadf704c0023cef1dfb40d09ddb61327d7793db65a0ef0
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ > :warning: **Note**: This software is currently under active development. The API and interface should be considered unstable until a v1.0.0 release.
2
+
1
3
  # Tapioca
2
4
 
3
5
  ![Build Status](https://github.com/Shopify/tapioca/workflows/CI/badge.svg)
@@ -14,7 +16,7 @@ For gems that have a normal default `require` and load all of their constants th
14
16
 
15
17
  For example, suppose you are using the class `BetterHtml::Parser` exported from the `better_html` gem. Just doing a `require "better_html"` (which is the default require) does not load that type:
16
18
 
17
- ```ruby
19
+ ```shell
18
20
  $ bundle exec pry
19
21
  [1] pry(main)> require 'better_html'
20
22
  => true
@@ -110,7 +112,7 @@ This will generate DSL RBIs for specified constants (or for all handled constant
110
112
  - `--prerequire [file]`: A file to be required before `Bundler.require` is called.
111
113
  - `--postrequire [file]`: A file to be required after `Bundler.require` is called.
112
114
  - `--out [directory]`: The output directory for generated RBI files, default to `sorbet/rbi/gems`.
113
- - `--generate-command [command]`: The command to run to regenerate RBI files (used in header comment of the RBI files), defaults to the current command.
115
+ - `--generate-command [command]`: **[DEPRECATED]** The command to run to regenerate RBI files (used in header comment of the RBI files), defaults to the current command.
114
116
  - `--typed-overrides [gem:level]`: Overrides typed sigils for generated gem RBIs for gem `gem` to level `level` (`level` can be one of `ignore`, `false`, `true`, `strict`, or `strong`, see [the Sorbet docs](https://sorbet.org/docs/static#file-level-granularity-strictness-levels) for more details).
115
117
 
116
118
  ## Contributing
data/exe/tapioca CHANGED
@@ -1,6 +1,21 @@
1
1
  #! /usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative "../lib/tapioca"
4
+ require 'sorbet-runtime'
5
5
 
6
- Tapioca::Cli.start(ARGV)
6
+ begin
7
+ T::Configuration.default_checked_level = :never
8
+ # Suppresses errors caused by T.cast, T.let, T.must, etc.
9
+ T::Configuration.inline_type_error_handler = ->(*) {}
10
+ # Suppresses errors caused by incorrect parameter ordering
11
+ T::Configuration.sig_validation_error_handler = ->(*) {}
12
+ rescue
13
+ # Need this rescue so that if another gem has
14
+ # already set the checked level by the time we
15
+ # get to it, we don't fail outright.
16
+ nil
17
+ end
18
+
19
+ require_relative "../lib/tapioca/internal"
20
+
21
+ Tapioca::Cli::Main.start(ARGV)
data/lib/tapioca.rb CHANGED
@@ -17,31 +17,5 @@ module Tapioca
17
17
  class Error < StandardError; end
18
18
  end
19
19
 
20
- begin
21
- T::Configuration.default_checked_level = :never
22
- # Suppresses errors caused by T.cast, T.let, T.must, etc.
23
- T::Configuration.inline_type_error_handler = ->(*) {}
24
- # Suppresses errors caused by incorrect parameter ordering
25
- T::Configuration.sig_validation_error_handler = ->(*) {}
26
- rescue
27
- # Need this rescue so that if another gem has
28
- # already set the checked level by the time we
29
- # get to it, we don't fail outright.
30
- nil
31
- end
32
-
33
- require "tapioca/loader"
34
- require "tapioca/constant_locator"
35
- require "tapioca/config"
36
- require "tapioca/config_builder"
37
- require "tapioca/generator"
38
- require "tapioca/cli"
39
- require "tapioca/gemfile"
40
- require "tapioca/compilers/sorbet"
41
- require "tapioca/compilers/requires_compiler"
42
- require "tapioca/compilers/symbol_table_compiler"
43
- require "tapioca/compilers/symbol_table/symbol_generator"
44
- require "tapioca/compilers/symbol_table/symbol_loader"
45
- require "tapioca/compilers/todos_compiler"
46
- require "tapioca/compilers/dsl_compiler"
20
+ require "tapioca/compilers/dsl/base"
47
21
  require "tapioca/version"
data/lib/tapioca/cli.rb CHANGED
@@ -4,112 +4,5 @@
4
4
  require 'thor'
5
5
 
6
6
  module Tapioca
7
- class Cli < Thor
8
- include(Thor::Actions)
9
-
10
- class_option :prerequire,
11
- aliases: ["--pre", "-b"],
12
- banner: "file",
13
- desc: "A file to be required before Bundler.require is called"
14
- class_option :postrequire,
15
- aliases: ["--post", "-a"],
16
- banner: "file",
17
- desc: "A file to be required after Bundler.require is called"
18
- class_option :outdir,
19
- aliases: ["--out", "-o"],
20
- banner: "directory",
21
- desc: "The output directory for generated RBI files"
22
- class_option :generate_command,
23
- aliases: ["--cmd", "-c"],
24
- banner: "command",
25
- desc: "The command to run to regenerate RBI files"
26
- class_option :exclude,
27
- aliases: ["-x"],
28
- type: :array,
29
- banner: "gem [gem ...]",
30
- desc: "Excludes the given gem(s) from RBI generation"
31
- class_option :typed_overrides,
32
- aliases: ["--typed", "-t"],
33
- type: :hash,
34
- banner: "gem:level [gem:level ...]",
35
- desc: "Overrides for typed sigils for generated gem RBIs"
36
-
37
- map T.unsafe(%w[--version -v] => :__print_version)
38
-
39
- desc "init", "initializes folder structure"
40
- def init
41
- create_file(Config::SORBET_CONFIG, skip: true) do
42
- <<~CONTENT
43
- --dir
44
- .
45
- CONTENT
46
- end
47
- create_file(Config::DEFAULT_POSTREQUIRE, skip: true) do
48
- <<~CONTENT
49
- # typed: false
50
- # frozen_string_literal: true
51
-
52
- # Add your extra requires here
53
- CONTENT
54
- end
55
- end
56
-
57
- desc "require", "generate the list of files to be required by tapioca"
58
- def require
59
- Tapioca.silence_warnings do
60
- generator.build_requires
61
- end
62
- end
63
-
64
- desc "todo", "generate the list of unresolved constants"
65
- def todo
66
- Tapioca.silence_warnings do
67
- generator.build_todos
68
- end
69
- end
70
-
71
- desc "dsl [constant...]", "generate RBIs for dynamic methods"
72
- option :generators,
73
- type: :array,
74
- aliases: ["--gen", "-g"],
75
- banner: "generator [generator ...]",
76
- desc: "Only run supplied DSL generators"
77
- def dsl(*constants)
78
- Tapioca.silence_warnings do
79
- generator.build_dsl(constants)
80
- end
81
- end
82
-
83
- desc "generate [gem...]", "generate RBIs from gems"
84
- def generate(*gems)
85
- Tapioca.silence_warnings do
86
- generator.build_gem_rbis(gems)
87
- end
88
- end
89
-
90
- desc "sync", "sync RBIs to Gemfile"
91
- def sync
92
- Tapioca.silence_warnings do
93
- generator.sync_rbis_with_gemfile
94
- end
95
- end
96
-
97
- desc "--version, -v", "show version"
98
- def __print_version
99
- puts "Tapioca v#{Tapioca::VERSION}"
100
- end
101
-
102
- no_commands do
103
- def self.exit_on_failure?
104
- true
105
- end
106
-
107
- def generator
108
- current_command = T.must(current_command_chain.first)
109
- @generator ||= Generator.new(
110
- ConfigBuilder.from_options(current_command, options)
111
- )
112
- end
113
- end
114
- end
7
+ module Cli; end
115
8
  end
@@ -0,0 +1,146 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Cli
6
+ class Main < Thor
7
+ include(Thor::Actions)
8
+
9
+ class_option :prerequire,
10
+ aliases: ["--pre", "-b"],
11
+ banner: "file",
12
+ desc: "A file to be required before Bundler.require is called"
13
+ class_option :postrequire,
14
+ aliases: ["--post", "-a"],
15
+ banner: "file",
16
+ desc: "A file to be required after Bundler.require is called"
17
+ class_option :outdir,
18
+ aliases: ["--out", "-o"],
19
+ banner: "directory",
20
+ desc: "The output directory for generated RBI files"
21
+ class_option :generate_command,
22
+ aliases: ["--cmd", "-c"],
23
+ banner: "command",
24
+ desc: "The command to run to regenerate RBI files"
25
+ class_option :exclude,
26
+ aliases: ["-x"],
27
+ type: :array,
28
+ banner: "gem [gem ...]",
29
+ desc: "Excludes the given gem(s) from RBI generation"
30
+ class_option :typed_overrides,
31
+ aliases: ["--typed", "-t"],
32
+ type: :hash,
33
+ banner: "gem:level [gem:level ...]",
34
+ desc: "Overrides for typed sigils for generated gem RBIs"
35
+
36
+ map T.unsafe(%w[--version -v] => :__print_version)
37
+
38
+ desc "init", "initializes folder structure"
39
+ def init
40
+ create_config
41
+ create_post_require
42
+ generate_binstub
43
+ end
44
+
45
+ desc "require", "generate the list of files to be required by tapioca"
46
+ def require
47
+ Tapioca.silence_warnings do
48
+ generator.build_requires
49
+ end
50
+ end
51
+
52
+ desc "todo", "generate the list of unresolved constants"
53
+ def todo
54
+ Tapioca.silence_warnings do
55
+ generator.build_todos
56
+ end
57
+ end
58
+
59
+ desc "dsl [constant...]", "generate RBIs for dynamic methods"
60
+ option :generators,
61
+ type: :array,
62
+ aliases: ["--gen", "-g"],
63
+ banner: "generator [generator ...]",
64
+ desc: "Only run supplied DSL generators"
65
+ option :verify,
66
+ type: :boolean,
67
+ default: false,
68
+ desc: "Verifies RBIs are up-to-date"
69
+ option :quiet,
70
+ aliases: ["-q"],
71
+ type: :boolean,
72
+ desc: "Supresses file creation output"
73
+ def dsl(*constants)
74
+ Tapioca.silence_warnings do
75
+ generator.build_dsl(constants, should_verify: options[:verify], quiet: options[:quiet])
76
+ end
77
+ end
78
+
79
+ desc "generate [gem...]", "generate RBIs from gems"
80
+ def generate(*gems)
81
+ Tapioca.silence_warnings do
82
+ generator.build_gem_rbis(gems)
83
+ end
84
+ end
85
+
86
+ desc "sync", "sync RBIs to Gemfile"
87
+ def sync
88
+ Tapioca.silence_warnings do
89
+ generator.sync_rbis_with_gemfile
90
+ end
91
+ end
92
+
93
+ desc "--version, -v", "show version"
94
+ def __print_version
95
+ puts "Tapioca v#{Tapioca::VERSION}"
96
+ end
97
+
98
+ private
99
+
100
+ def create_config
101
+ create_file(Config::SORBET_CONFIG, skip: true) do
102
+ <<~CONTENT
103
+ --dir
104
+ .
105
+ CONTENT
106
+ end
107
+ end
108
+
109
+ def create_post_require
110
+ create_file(Config::DEFAULT_POSTREQUIRE, skip: true) do
111
+ <<~CONTENT
112
+ # typed: false
113
+ # frozen_string_literal: true
114
+
115
+ # Add your extra requires here
116
+ CONTENT
117
+ end
118
+ end
119
+
120
+ def generate_binstub
121
+ bin_stub_exists = File.exist?("bin/tapioca")
122
+ installer = Bundler::Installer.new(Bundler.root, Bundler.definition)
123
+ spec = Bundler.definition.specs.find { |s| s.name == "tapioca" }
124
+ installer.generate_bundler_executable_stubs(spec, { force: true })
125
+ if bin_stub_exists
126
+ shell.say_status(:force, "bin/tapioca", :yellow)
127
+ else
128
+ shell.say_status(:create, "bin/tapioca", :green)
129
+ end
130
+ end
131
+
132
+ no_commands do
133
+ def self.exit_on_failure?
134
+ true
135
+ end
136
+
137
+ def generator
138
+ current_command = T.must(current_command_chain.first)
139
+ @generator ||= Generator.new(
140
+ ConfigBuilder.from_options(current_command, options)
141
+ )
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -371,7 +371,6 @@ module Tapioca
371
371
  return unless signature
372
372
 
373
373
  return_type = signature.return_type
374
- return if T::Types::Simple === return_type && T::Generic === return_type.raw_type
375
374
  return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
376
375
 
377
376
  return_type.to_s
@@ -382,10 +381,11 @@ module Tapioca
382
381
  signature = T::Private::Methods.signature_for_method(column_type.method(method))
383
382
  return unless signature
384
383
 
385
- arg_type = signature.arg_types.first.last
386
- return if T::Types::Simple === arg_type && T::Generic === arg_type.raw_type
384
+ # Arg types is an array [name, type] entries, so we desctructure the type of
385
+ # first argument to get the first argument type
386
+ _, first_argument_type = signature.arg_types.first
387
387
 
388
- arg_type.to_s
388
+ first_argument_type.to_s
389
389
  end
390
390
 
391
391
  sig { params(type: String).returns(String) }
@@ -60,7 +60,7 @@ module Tapioca
60
60
 
61
61
  model.create_module(module_name) do |mod|
62
62
  scope_method_names.each do |scope_method|
63
- generate_scope_method(scope_method, mod)
63
+ generate_scope_method(scope_method.to_s, mod)
64
64
  end
65
65
  end
66
66
 
@@ -123,7 +123,7 @@ module Tapioca
123
123
  # Compile a Ruby method return type into a Parlour type
124
124
  sig do
125
125
  params(method_def: T.any(Method, UnboundMethod))
126
- .returns(String)
126
+ .returns(T.nilable(String))
127
127
  end
128
128
  def compile_method_return_type_to_parlour(method_def)
129
129
  signature = T::Private::Methods.signature_for_method(method_def)
@@ -62,33 +62,99 @@ module Tapioca
62
62
  # end
63
63
  # ~~~
64
64
  class Protobuf < Base
65
+ # Parlour doesn't support type members out of the box, so adding the
66
+ # ability to do that here. This should be upstreamed.
67
+ class TypeMember < Parlour::RbiGenerator::RbiObject
68
+ extend T::Sig
69
+
70
+ sig { params(other: Object).returns(T::Boolean) }
71
+ def ==(other)
72
+ TypeMember === other && name == other.name
73
+ end
74
+
75
+ sig do
76
+ override
77
+ .params(indent_level: Integer, options: Parlour::RbiGenerator::Options)
78
+ .returns(T::Array[String])
79
+ end
80
+ def generate_rbi(indent_level, options)
81
+ [options.indented(indent_level, "#{name} = type_member")]
82
+ end
83
+
84
+ sig do
85
+ override
86
+ .params(others: T::Array[Parlour::RbiGenerator::RbiObject])
87
+ .returns(T::Boolean)
88
+ end
89
+ def mergeable?(others)
90
+ others.all? { |other| self == other }
91
+ end
92
+
93
+ sig { override.params(others: T::Array[Parlour::RbiGenerator::RbiObject]).void }
94
+ def merge_into_self(others); end
95
+
96
+ sig { override.returns(String) }
97
+ def describe
98
+ "Type Member (#{name})"
99
+ end
100
+ end
101
+
102
+ class Field < T::Struct
103
+ prop :name, String
104
+ prop :type, String
105
+ prop :init_type, String
106
+ prop :default, String
107
+
108
+ extend T::Sig
109
+
110
+ sig { returns(Parlour::RbiGenerator::Parameter) }
111
+ def to_init
112
+ Parlour::RbiGenerator::Parameter.new("#{name}:", type: init_type, default: default)
113
+ end
114
+ end
115
+
65
116
  extend T::Sig
66
117
 
67
118
  sig do
68
119
  override.params(
69
120
  root: Parlour::RbiGenerator::Namespace,
70
- constant: T.class_of(Google::Protobuf::MessageExts)
121
+ constant: Module
71
122
  ).void
72
123
  end
73
124
  def decorate(root, constant)
74
- descriptor = T.let(T.unsafe(constant).descriptor, Google::Protobuf::Descriptor)
75
- return unless descriptor.any?
76
-
77
125
  root.path(constant) do |klass|
78
- descriptor.each do |desc|
79
- create_descriptor_method(klass, desc)
126
+ if constant == Google::Protobuf::RepeatedField
127
+ create_type_members(klass, "Elem")
128
+ elsif constant == Google::Protobuf::Map
129
+ create_type_members(klass, "Key", "Value")
130
+ else
131
+ descriptor = T.let(T.unsafe(constant).descriptor, Google::Protobuf::Descriptor)
132
+ fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
133
+ fields.sort_by!(&:name)
134
+
135
+ create_method(klass, "initialize", parameters: fields.map!(&:to_init))
80
136
  end
81
137
  end
82
138
  end
83
139
 
84
140
  sig { override.returns(T::Enumerable[Module]) }
85
141
  def gather_constants
86
- classes = T.cast(ObjectSpace.each_object(Class), T::Enumerable[Class])
87
- classes.select { |c| c < Google::Protobuf::MessageExts && !c.singleton_class? }
142
+ marker = Google::Protobuf::MessageExts::ClassMethods
143
+ results = T.cast(ObjectSpace.each_object(marker).to_a, T::Array[Module])
144
+ results.any? ? results + [Google::Protobuf::RepeatedField, Google::Protobuf::Map] : []
88
145
  end
89
146
 
90
147
  private
91
148
 
149
+ sig { params(klass: Parlour::RbiGenerator::Namespace, names: String).void }
150
+ def create_type_members(klass, *names)
151
+ klass.create_extend("T::Generic")
152
+
153
+ names.each do |name|
154
+ klass.children << TypeMember.new(klass.generator, name)
155
+ end
156
+ end
157
+
92
158
  sig do
93
159
  params(
94
160
  descriptor: Google::Protobuf::FieldDescriptor
@@ -113,30 +179,80 @@ module Tapioca
113
179
  end
114
180
  end
115
181
 
182
+ sig { params(descriptor: Google::Protobuf::FieldDescriptor).returns(Field) }
183
+ def field_of(descriptor)
184
+ if descriptor.label == :repeated
185
+ # Here we're going to check if the submsg_name is named according to
186
+ # how Google names map entries.
187
+ # https://github.com/protocolbuffers/protobuf/blob/f82e26/ruby/ext/google/protobuf_c/defs.c#L1963-L1966
188
+ if descriptor.submsg_name.to_s.end_with?("_MapEntry_#{descriptor.name}")
189
+ key = descriptor.subtype.lookup('key')
190
+ value = descriptor.subtype.lookup('value')
191
+
192
+ key_type = type_of(key)
193
+ value_type = type_of(value)
194
+ type = "Google::Protobuf::Map[#{key_type}, #{value_type}]"
195
+
196
+ default_args = [key.type.inspect, value.type.inspect]
197
+ default_args << value_type if %i[enum message].include?(value.type)
198
+
199
+ Field.new(
200
+ name: descriptor.name,
201
+ type: type,
202
+ init_type: "T.any(#{type}, T::Hash[#{key_type}, #{value_type}])",
203
+ default: "Google::Protobuf::Map.new(#{default_args.join(', ')})"
204
+ )
205
+ else
206
+ elem_type = type_of(descriptor)
207
+ type = "Google::Protobuf::RepeatedField[#{elem_type}]"
208
+
209
+ default_args = [descriptor.type.inspect]
210
+ default_args << elem_type if %i[enum message].include?(descriptor.type)
211
+
212
+ Field.new(
213
+ name: descriptor.name,
214
+ type: type,
215
+ init_type: "T.any(#{type}, T::Array[#{elem_type}])",
216
+ default: "Google::Protobuf::RepeatedField.new(#{default_args.join(', ')})"
217
+ )
218
+ end
219
+ else
220
+ type = type_of(descriptor)
221
+
222
+ Field.new(
223
+ name: descriptor.name,
224
+ type: type,
225
+ init_type: type,
226
+ default: "nil"
227
+ )
228
+ end
229
+ end
230
+
116
231
  sig do
117
232
  params(
118
233
  klass: Parlour::RbiGenerator::Namespace,
119
234
  desc: Google::Protobuf::FieldDescriptor,
120
- ).void
235
+ ).returns(Field)
121
236
  end
122
237
  def create_descriptor_method(klass, desc)
123
- name = desc.name
124
- type = type_of(desc)
238
+ field = field_of(desc)
125
239
 
126
240
  create_method(
127
241
  klass,
128
- name,
129
- return_type: type
242
+ field.name,
243
+ return_type: field.type
130
244
  )
131
245
 
132
246
  create_method(
133
247
  klass,
134
- "#{name}=",
248
+ "#{field.name}=",
135
249
  parameters: [
136
- Parlour::RbiGenerator::Parameter.new("value", type: type),
250
+ Parlour::RbiGenerator::Parameter.new("value", type: field.type),
137
251
  ],
138
- return_type: type
252
+ return_type: field.type
139
253
  )
254
+
255
+ field
140
256
  end
141
257
  end
142
258
  end