tapioca 0.4.5 → 0.4.10

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: a6ca30be86eac3958354ca4204904993088bfeeef36c72d63b428ccc5ff3eff3
4
- data.tar.gz: e218a8ed57c6eb6c46b00f423771fa1a4874a8f0a945d8f5683e849fe5ca9e91
3
+ metadata.gz: db90e001ad45dbd8edd11b2288405dd406588e1bb46120c7cd5f0cb560647ea6
4
+ data.tar.gz: 1eab20c32a1785046f814ca2fc54866bbc0c9d083bf682a8b1ea15bf92c9cc1e
5
5
  SHA512:
6
- metadata.gz: 39b2b67d35896d998f0f11dcdeda386e6e8312e124b12d8e5e252650417ba3c8a452aedfe014059064ca69e465d97089bfad55f1801d6667612902a4edc24903
7
- data.tar.gz: 0fa4fe00a45d1f0e870f6d2cf985291ee1ed3f78d339c1f27be5653cff99490de31c6907abc462f1c1c7c197d7eb3eba63c2a90764c56a89fd607fd950c4a493
6
+ metadata.gz: 77d4598557fc72b8a76dea56b626d05a3f91dd88d53cb0e96156ed92419534a32beeed17cd0f4791e37c972f2da86c07bb6a386a0dbb5813a5b4f998438e3127
7
+ data.tar.gz: 102c5591a319f1846506d0e1e439f52613bc9f5d6110eab98ee857d7a943921584e12b56733ec7b4f6e95824396ea1052ffc691eed0505bf2b00a7438ceabbe0
data/Gemfile CHANGED
@@ -34,3 +34,5 @@ group(:development, :test) do
34
34
  gem("activeresource", "~> 5.1", require: false)
35
35
  gem("google-protobuf", "~>3.12.0", require: false)
36
36
  end
37
+
38
+ gem "rubocop-sorbet", ">= 0.4.1"
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Tapioca
2
2
 
3
- [![Build Status](https://travis-ci.org/Shopify/tapioca.svg?branch=master)](https://travis-ci.org/Shopify/tapioca)
3
+ ![Build Status](https://github.com/Shopify/tapioca/workflows/CI/badge.svg)
4
4
 
5
5
  Tapioca is a library used to generate RBI (Ruby interface) files for use with [Sorbet](https://sorbet.org). RBI files provide the structure (classes, modules, methods, parameters) of the gem/library to Sorbet to assist with typechecking.
6
6
 
@@ -55,6 +55,8 @@ and do not forget to execute `tapioca` using `bundler`:
55
55
  ```shell
56
56
  $ bundle exec tapioca
57
57
  Commands:
58
+ tapioca --version, -v # show version
59
+ tapioca dsl [constant...] # generate RBIs for dynamic methods
58
60
  tapioca generate [gem...] # generate RBIs from gems
59
61
  tapioca help [COMMAND] # Describe available commands or one specific command
60
62
  tapioca init # initializes folder structure
@@ -63,12 +65,12 @@ Commands:
63
65
  tapioca todo # generate the list of unresolved constants
64
66
 
65
67
  Options:
66
- --pre, -b, [--prerequire=file] # A file to be required before Bundler.require is called
67
- --post, -a, [--postrequire=file] # A file to be required after Bundler.require is called
68
- --out, -o, [--outdir=directory] # The output directory for generated RBI files
69
- # Default: sorbet/rbi/gems
70
- --cmd, -c, [--generate-command=command] # The command to run to regenerate RBI files
71
- --typed, -t, [--typed-overrides=gem:level] # Overrides for typed sigils for generated gem RBIs
68
+ --pre, -b, [--prerequire=file] # A file to be required before Bundler.require is called
69
+ --post, -a, [--postrequire=file] # A file to be required after Bundler.require is called
70
+ --out, -o, [--outdir=directory] # The output directory for generated RBI files
71
+ --cmd, -c, [--generate-command=command] # The command to run to regenerate RBI files
72
+ -x, [--exclude=gem [gem ...]] # Excludes the given gem(s) from RBI generation
73
+ --typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Overrides for typed sigils for generated gem RBIs
72
74
  ```
73
75
 
74
76
  ## Usage
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: true
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'thor'
5
5
 
@@ -34,6 +34,8 @@ module Tapioca
34
34
  banner: "gem:level [gem:level ...]",
35
35
  desc: "Overrides for typed sigils for generated gem RBIs"
36
36
 
37
+ map T.unsafe(%w[--version -v] => :__print_version)
38
+
37
39
  desc "init", "initializes folder structure"
38
40
  def init
39
41
  create_file(Config::SORBET_CONFIG, skip: true) do
@@ -92,6 +94,11 @@ module Tapioca
92
94
  end
93
95
  end
94
96
 
97
+ desc "--version, -v", "show version"
98
+ def __print_version
99
+ puts "Tapioca v#{Tapioca::VERSION}"
100
+ end
101
+
95
102
  no_commands do
96
103
  def self.exit_on_failure?
97
104
  true
@@ -45,10 +45,10 @@ module Tapioca
45
45
  # sig { params(value: T.nilable(::User)).void }
46
46
  # def author=(value); end
47
47
  #
48
- # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::User)) }
48
+ # sig { params(args: T.untyped, blk: T.untyped).returns(::User) }
49
49
  # def build_author(*args, &blk); end
50
50
  #
51
- # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::Category)) }
51
+ # sig { params(args: T.untyped, blk: T.untyped).returns(::Category) }
52
52
  # def build_category(*args, &blk); end
53
53
  #
54
54
  # sig { returns(T.nilable(::Category)) }
@@ -69,16 +69,16 @@ module Tapioca
69
69
  # sig { params(value: T::Enumerable[::Comment]).void }
70
70
  # def comments=(value); end
71
71
  #
72
- # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::User)) }
72
+ # sig { params(args: T.untyped, blk: T.untyped).returns(::User) }
73
73
  # def create_author(*args, &blk); end
74
74
  #
75
- # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::User)) }
75
+ # sig { params(args: T.untyped, blk: T.untyped).returns(::User) }
76
76
  # def create_author!(*args, &blk); end
77
77
  #
78
- # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::Category)) }
78
+ # sig { params(args: T.untyped, blk: T.untyped).returns(::Category) }
79
79
  # def create_category(*args, &blk); end
80
80
  #
81
- # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::Category)) }
81
+ # sig { params(args: T.untyped, blk: T.untyped).returns(::Category) }
82
82
  # def create_category!(*args, &blk); end
83
83
  #
84
84
  # sig { returns(T.nilable(::User)) }
@@ -152,7 +152,7 @@ module Tapioca
152
152
  "reload_#{association_name}",
153
153
  return_type: association_type,
154
154
  )
155
- if reflection.constructable?
155
+ unless reflection.polymorphic?
156
156
  create_method(
157
157
  klass,
158
158
  "build_#{association_name}",
@@ -160,7 +160,7 @@ module Tapioca
160
160
  Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
161
161
  Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
162
162
  ],
163
- return_type: association_type
163
+ return_type: association_class
164
164
  )
165
165
  create_method(
166
166
  klass,
@@ -169,7 +169,7 @@ module Tapioca
169
169
  Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
170
170
  Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
171
171
  ],
172
- return_type: association_type
172
+ return_type: association_class
173
173
  )
174
174
  create_method(
175
175
  klass,
@@ -178,7 +178,7 @@ module Tapioca
178
178
  Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
179
179
  Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
180
180
  ],
181
- return_type: association_type
181
+ return_type: association_class
182
182
  )
183
183
  end
184
184
  end
@@ -108,7 +108,7 @@ module Tapioca
108
108
  sig { override.returns(T::Enumerable[Module]) }
109
109
  def gather_constants
110
110
  ::ActiveRecord::Base.descendants.select do |klass|
111
- klass < IdentityCache
111
+ klass < IdentityCache::WithoutPrimaryIndex
112
112
  end
113
113
  end
114
114
 
@@ -126,7 +126,7 @@ module Tapioca
126
126
  if returns_collection
127
127
  COLLECTION_TYPE.call(cache_type)
128
128
  else
129
- "::#{cache_type}"
129
+ "T.nilable(::#{cache_type})"
130
130
  end
131
131
  rescue ArgumentError
132
132
  "T.untyped"
@@ -92,24 +92,28 @@ module Tapioca
92
92
  method_def = signature.nil? ? method_def : signature.method
93
93
  method_types = parameters_types_from_signature(method_def, signature)
94
94
 
95
- method_def.parameters.each_with_index.map do |(type, name), i|
96
- name ||= :_
97
- name = name.to_s.gsub(/&|\*/, '_') # avoid incorrect names from `delegate`
95
+ method_def.parameters.each_with_index.map do |(type, name), index|
96
+ fallback_arg_name = "_arg#{index}"
97
+
98
+ name ||= fallback_arg_name
99
+ name = name.to_s.gsub(/&|\*/, fallback_arg_name) # avoid incorrect names from `delegate`
100
+ method_type = method_types[index]
101
+
98
102
  case type
99
103
  when :req
100
- ::Parlour::RbiGenerator::Parameter.new(name, type: method_types[i])
104
+ ::Parlour::RbiGenerator::Parameter.new(name, type: method_type)
101
105
  when :opt
102
- ::Parlour::RbiGenerator::Parameter.new(name, type: method_types[i], default: 'T.unsafe(nil)')
106
+ ::Parlour::RbiGenerator::Parameter.new(name, type: method_type, default: 'T.unsafe(nil)')
103
107
  when :rest
104
- ::Parlour::RbiGenerator::Parameter.new("*#{name}", type: method_types[i])
108
+ ::Parlour::RbiGenerator::Parameter.new("*#{name}", type: method_type)
105
109
  when :keyreq
106
- ::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_types[i])
110
+ ::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_type)
107
111
  when :key
108
- ::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_types[i], default: 'T.unsafe(nil)')
112
+ ::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_type, default: 'T.unsafe(nil)')
109
113
  when :keyrest
110
- ::Parlour::RbiGenerator::Parameter.new("**#{name}", type: method_types[i])
114
+ ::Parlour::RbiGenerator::Parameter.new("**#{name}", type: method_type)
111
115
  when :block
112
- ::Parlour::RbiGenerator::Parameter.new("&#{name}", type: method_types[i])
116
+ ::Parlour::RbiGenerator::Parameter.new("&#{name}", type: method_type)
113
117
  else
114
118
  raise "Unknown type `#{type}`."
115
119
  end
@@ -1,5 +1,5 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
- # typed: true
3
3
 
4
4
  require "tapioca/compilers/dsl/base"
5
5
 
@@ -30,7 +30,7 @@ module Tapioca
30
30
  T::Enumerable[Dsl::Base]
31
31
  )
32
32
  @requested_constants = requested_constants
33
- @error_handler = error_handler || $stderr.method(:puts)
33
+ @error_handler = T.let(error_handler || $stderr.method(:puts), T.proc.params(error: String).void)
34
34
  end
35
35
 
36
36
  sig { params(blk: T.proc.params(constant: Module, rbi: String).void).void }
@@ -54,9 +54,9 @@ module Tapioca
54
54
 
55
55
  private
56
56
 
57
- sig { params(requested_generators: T::Array[String]).returns(Proc) }
57
+ sig { params(requested_generators: T::Array[String]).returns(T.proc.params(klass: Class).returns(T::Boolean)) }
58
58
  def generator_filter(requested_generators)
59
- return proc { true } if requested_generators.empty?
59
+ return ->(_klass) { true } if requested_generators.empty?
60
60
 
61
61
  generators = requested_generators.map(&:downcase)
62
62
 
@@ -70,7 +70,7 @@ module Tapioca
70
70
  def gather_generators(requested_generators)
71
71
  generator_filter = generator_filter(requested_generators)
72
72
 
73
- Dsl::Base.descendants.select(&generator_filter).map(&:new)
73
+ T.cast(Dsl::Base.descendants.select(&generator_filter).map(&:new), T::Enumerable[Dsl::Base])
74
74
  end
75
75
 
76
76
  sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: strict
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'spoom'
5
5
 
@@ -34,7 +34,8 @@ module Tapioca
34
34
  path = (Pathname.new(@sorbet_path) / "../.." / path).cleanpath
35
35
  if path.directory?
36
36
  Dir.glob("#{path}/**/*.rb", File::FNM_EXTGLOB).reject do |file|
37
- file_ignored_by_sorbet?(config, file)
37
+ relative_file_path = Pathname.new(file).relative_path_from(path)
38
+ file_ignored_by_sorbet?(config, relative_file_path)
38
39
  end
39
40
  else
40
41
  [path.to_s]
@@ -49,13 +50,40 @@ module Tapioca
49
50
  end.compact
50
51
  end
51
52
 
52
- sig { params(config: Spoom::Sorbet::Config, file: String).returns(T::Boolean) }
53
- def file_ignored_by_sorbet?(config, file)
54
- config.ignore.any? do |path|
55
- Regexp.new(Regexp.escape(path)) =~ file
53
+ sig { params(config: Spoom::Sorbet::Config, file_path: Pathname).returns(T::Boolean) }
54
+ def file_ignored_by_sorbet?(config, file_path)
55
+ file_path_parts = path_parts(file_path)
56
+
57
+ config.ignore.any? do |ignore|
58
+ # Sorbet --ignore matching method:
59
+ # ---
60
+ # Ignores input files that contain the given
61
+ # string in their paths (relative to the input
62
+ # path passed to Sorbet).
63
+ #
64
+ # Strings beginning with / match against the
65
+ # prefix of these relative paths; others are
66
+ # substring matchs.
67
+
68
+ # Matches must be against whole folder and file
69
+ # names, so `foo` matches `/foo/bar.rb` and
70
+ # `/bar/foo/baz.rb` but not `/foo.rb` or
71
+ # `/foo2/bar.rb`.
72
+ ignore_parts = path_parts(Pathname.new(ignore))
73
+ file_path_part_sequences = file_path_parts.each_cons(ignore_parts.size)
74
+ # if ignore string begins with /, we only want the first sequence to match
75
+ file_path_part_sequences = [file_path_part_sequences.first].to_enum if ignore.start_with?("/")
76
+
77
+ # we need to match whole segments
78
+ file_path_part_sequences.include?(ignore_parts)
56
79
  end
57
80
  end
58
81
 
82
+ sig { params(path: Pathname).returns(T::Array[String]) }
83
+ def path_parts(path)
84
+ T.unsafe(path).descend.map { |part| part.basename.to_s }
85
+ end
86
+
59
87
  sig { params(files: T::Enumerable[String], name: String).returns(T::Boolean) }
60
88
  def name_in_project?(files, name)
61
89
  files.any? do |file|
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: true
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'pathname'
5
5
  require 'shellwords'
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: true
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'pathname'
5
5
 
@@ -209,7 +209,11 @@ module Tapioca
209
209
  method = "const" if prop.fetch(:immutable, false)
210
210
  type = prop.fetch(:type_object, "T.untyped")
211
211
 
212
- indented("#{method} :#{name}, #{type}")
212
+ if prop.key?(:default)
213
+ indented("#{method} :#{name}, #{type}, default: T.unsafe(nil)")
214
+ else
215
+ indented("#{method} :#{name}, #{type}")
216
+ end
213
217
  end.join("\n")
214
218
  end
215
219
 
@@ -513,7 +517,9 @@ module Tapioca
513
517
 
514
518
  parameters = T.let(method.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
515
519
 
516
- sanitized_parameters = parameters.map do |type, name|
520
+ sanitized_parameters = parameters.each_with_index.map do |(type, name), index|
521
+ fallback_arg_name = "_arg#{index}"
522
+
517
523
  unless name
518
524
  # For attr_writer methods, Sorbet signatures have the name
519
525
  # of the method (without the trailing = sign) as the name of
@@ -533,12 +539,12 @@ module Tapioca
533
539
  name = if writer_method_with_sig
534
540
  T.must(method_name[0...-1]).to_sym
535
541
  else
536
- :_
542
+ fallback_arg_name
537
543
  end
538
544
  end
539
545
 
540
546
  # Sanitize param names
541
- name = name.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
547
+ name = name.to_s.gsub(/[^a-zA-Z0-9_]/, fallback_arg_name)
542
548
 
543
549
  [type, name]
544
550
  end
@@ -612,6 +618,7 @@ module Tapioca
612
618
  signature_body = signature_body
613
619
  .gsub(".returns(<VOID>)", ".void")
614
620
  .gsub("<NOT-TYPED>", "T.untyped")
621
+ .gsub(".params()", "")
615
622
  .gsub(TYPE_PARAMETER_MATCHER, "T.type_parameter(:\\1)")[1..-1]
616
623
 
617
624
  "sig { #{signature_body} }"
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: true
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'json'
5
5
  require 'tempfile'
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: strong
2
+ # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
5
  module Compilers
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: strong
2
+ # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
5
  module Compilers
@@ -1,7 +1,9 @@
1
- # typed: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class Class
5
+ extend T::Sig
6
+
5
7
  # Returns an array with all classes that are < than its receiver.
6
8
  #
7
9
  # class C; end
@@ -15,9 +17,12 @@ class Class
15
17
  #
16
18
  # class D < C; end
17
19
  # C.descendants # => [B, A, D]
20
+ sig { returns(T::Array[Class]) }
18
21
  def descendants
19
- ObjectSpace.each_object(singleton_class).reject do |k|
20
- k.singleton_class? || k == self
22
+ result = ObjectSpace.each_object(singleton_class).reject do |k|
23
+ T.cast(k, Module).singleton_class? || k == self
21
24
  end
25
+
26
+ T.cast(result, T::Array[Class])
22
27
  end
23
28
  end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: strict
2
+ # frozen_string_literal: true
3
3
 
4
4
  require "bundler"
5
5
 
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: strict
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'pathname'
5
5
  require 'thor'
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: strict
2
+ # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
5
  class Loader
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.4.5"
5
+ VERSION = "0.4.10"
6
6
  end
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.4.5
4
+ version: 0.4.10
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: 2020-09-10 00:00:00.000000000 Z
14
+ date: 2020-12-01 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: pry