tapioca 0.4.5 → 0.4.10

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: 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